XPath 如何处理 XML 命名空间?

How does XPath deal with XML namespaces?

提问人:Adam 提问时间:11/25/2016 最后编辑:kjhughesAdam 更新时间:12/14/2022 访问量:28127

问:

XPath 如何处理 XML 命名空间?

如果我使用

/IntuitResponse/QueryResponse/Bill/Id

解析下面的 XML 文档,我得到 0 个节点。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<IntuitResponse xmlns="http://schema.intuit.com/finance/v3" 
                time="2016-10-14T10:48:39.109-07:00">
    <QueryResponse startPosition="1" maxResults="79" totalCount="79">
        <Bill domain="QBO" sparse="false">
            <Id>=1</Id>
        </Bill>
    </QueryResponse>
</IntuitResponse>

但是,我没有在 XPath 中指定命名空间(即 不是路径的每个标记的前缀)。如果我不明确告诉 XPath 我怎么知道我想要哪个?我想在这种情况下(因为只有一个命名空间),XPath 可以完全忽略它。但是,如果有多个命名空间,事情可能会变得丑陋。http://schema.intuit.com/finance/v3Idxmlns

xml xpath xml 命名空间

评论

0赞 har07 11/25/2016
XPath 不应返回任何节点:INFO - XPath 返回 0 个项目(以 0 毫秒为单位编译,以 1 毫秒为单位计算)。你是如何执行XPath的?
0赞 Adam 11/25/2016
@har07 我在 Java 中使用 import .我同意它不能使用在线测试器。这是令人困惑的事情之一。javax.xml.xpath.XPath
0赞 kjhughes 11/25/2016
好问题!XPath 本身无法指定默认命名空间或命名空间前缀与命名空间的绑定。然而,幸运的是,托管语言和库可以。 有关详细信息,请参阅下面的答案...
2赞 kjhughes 11/25/2016
这个问题给我留下了深刻的印象,因为与之前的大多数提问者不同,Adam 不仅包含了一个最小的可重现示例,他还感觉到并传达了 XPath 以某种方式处理 XML 命名空间的必要性。大多数这样的问题只是发布一个 XPath,也许是一些 XML(如果我们幸运的话,它不是图像或指向大量异地资源的链接),并声明它“不起作用”。Adam 感觉到它与命名空间有关,确定了标题,并写了我认为值得规范回答的问题。
1赞 rogerdpack 2/7/2019
如何使用 XPath 在 Java 中使用命名空间查询 XML 的可能副本?

答:

80赞 kjhughes 11/25/2016 #1

XPath 1.0/2.0 版本

在 XPath 中定义命名空间(推荐)

XPath 本身没有办法将命名空间前缀与命名空间绑定。这些设施由托管图书馆提供。

建议您使用这些工具并定义命名空间前缀,然后可以使用这些前缀根据需要限定 XML 元素和属性名称。


以下是 XPath 主机提供的一些各种机制,用于指定命名空间前缀绑定到命名空间 URI。

(OP 的原始 XPath /IntuitResponse/QueryResponse/Bill/Id 已省略为 /IntuitResponse/QueryResponse

C#:

XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("i", "http://schema.intuit.com/finance/v3");
XmlNodeList nodes = el.SelectNodes(@"/i:IntuitResponse/i:QueryResponse", nsmgr);

谷歌文档:

不幸的是,IMPORTXML() 没有提供命名空间前缀绑定机制。请参阅下一节 Defeating namespaces in XPath,了解如何将其用作解决方法。local-name()

Java (SAX):

NamespaceSupport support = new NamespaceSupport();
support.pushContext();
support.declarePrefix("i", "http://schema.intuit.com/finance/v3");

Java (XPath):

xpath.setNamespaceContext(new NamespaceContext() {
    public String getNamespaceURI(String prefix) {
      switch (prefix) {
        case "i": return "http://schema.intuit.com/finance/v3";
        // ...
       }
    });

JavaScript的:

请参阅实现用户定义的命名空间解析程序

function nsResolver(prefix) {
  var ns = {
    'i' : 'http://schema.intuit.com/finance/v3'
  };
  return ns[prefix] || null;
}
document.evaluate( '/i:IntuitResponse/i:QueryResponse', 
                   document, nsResolver, XPathResult.ANY_TYPE, 
                   null );

请注意,如果默认命名空间定义了关联的命名空间前缀,则使用 Document.createNSResolver() 返回的 可以消除对 customer 的需求。nsResolver()nsResolver()

Perl (LibXML):

my $xc = XML::LibXML::XPathContext->new($doc);
$xc->registerNs('i', 'http://schema.intuit.com/finance/v3');
my @nodes = $xc->findnodes('/i:IntuitResponse/i:QueryResponse');

Python (lxml):

from lxml import etree
f = StringIO('<IntuitResponse>...</IntuitResponse>')
doc = etree.parse(f)
r = doc.xpath('/i:IntuitResponse/i:QueryResponse', 
              namespaces={'i':'http://schema.intuit.com/finance/v3'})

蟒蛇 (ElementTree):

namespaces = {'i': 'http://schema.intuit.com/finance/v3'}
root.findall('/i:IntuitResponse/i:QueryResponse', namespaces)

蟒蛇(Scrapy):

response.selector.register_namespace('i', 'http://schema.intuit.com/finance/v3')
response.xpath('/i:IntuitResponse/i:QueryResponse').getall()

磷酸:

改编自 @Tomalak 使用 DOMDocument 的回答

$result = new DOMDocument();
$result->loadXML($xml);

$xpath = new DOMXpath($result);
$xpath->registerNamespace("i", "http://schema.intuit.com/finance/v3");

$result = $xpath->query("/i:IntuitResponse/i:QueryResponse");

另请参阅 @IMSoP 关于 PHP SimpleXML 命名空间的规范 Q/A

红宝石 (Nokogiri):

puts doc.xpath('/i:IntuitResponse/i:QueryResponse',
                'i' => "http://schema.intuit.com/finance/v3")

请注意,Nokogiri 支持删除命名空间,

doc.remove_namespaces!

但请参阅以下警告,这些警告不鼓励失败 XML 命名空间。

VBA:

xmlNS = "xmlns:i='http://schema.intuit.com/finance/v3'"
doc.setProperty "SelectionNamespaces", xmlNS  
Set queryResponseElement =doc.SelectSingleNode("/i:IntuitResponse/i:QueryResponse")

VB.NET:

xmlDoc = New XmlDocument()
xmlDoc.Load("file.xml")
nsmgr = New XmlNamespaceManager(New XmlNameTable())
nsmgr.AddNamespace("i", "http://schema.intuit.com/finance/v3");
nodes = xmlDoc.DocumentElement.SelectNodes("/i:IntuitResponse/i:QueryResponse",
                                           nsmgr)

SoapUI (文档):

declare namespace i='http://schema.intuit.com/finance/v3';
/i:IntuitResponse/i:QueryResponse

xmlstarlet:

-N i="http://schema.intuit.com/finance/v3"

XSLT的:

<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:i="http://schema.intuit.com/finance/v3">
   ...

声明命名空间前缀后,可以编写 XPath 以使用它:

/i:IntuitResponse/i:QueryResponse

在 XPath 中击败命名空间(不推荐)

另一种方法是编写针对以下条件进行测试的谓词:local-name()

/*[local-name()='IntuitResponse']/*[local-name()='QueryResponse']

或者,在 XPath 2.0 中:

/*:IntuitResponse/*:QueryResponse

以这种方式绕过命名空间可以工作,但不建议这样做,因为它

  • 指定完整的元素/属性名称。

  • 无法区分不同 命名空间(命名空间的真正目的)。请注意,可以通过添加一个额外的谓词来显式检查命名空间 URI 来解决此问题:

     /*[    namespace-uri()='http://schema.intuit.com/finance/v3' 
        and local-name()='IntuitResponse']
     /*[    namespace-uri()='http://schema.intuit.com/finance/v3' 
        and local-name()='QueryResponse']
    

    感谢 Daniel Haleynamespace-uri() 注释。

  • 过于冗长。

XPath 3.0/3.1 版本

支持现代 XPath 3.0/3.1 的库和工具允许直接在 XPath 表达式中指定命名空间 URI:

/Q{http://schema.intuit.com/finance/v3}IntuitResponse/Q{http://schema.intuit.com/finance/v3}QueryResponse

虽然比使用 XML 命名空间前缀要详细得多,但它的优点是独立于宿主库的命名空间前缀绑定机制。该符号以其创始人詹姆斯·克拉克(James Clark)的名字被称为克拉克符号。W3C XPath 3.1 EBNF 语法称其为 BracedURILiteralQ{http://schema.intuit.com/finance/v3}Q{}

感谢 Michael Kay 对 XPath 3.0/3.1 的 BracedURILiteral 的建议。

评论

2赞 kjhughes 11/26/2016
Pugi:文档中的不合格声明 + 奇怪的行为观察 = 转身就跑 / 生命太短了。Javax:不要忘记在 .DocumentBuilderFactory
2赞 Adam 11/26/2016
事实证明,pugixml根本不支持xml命名空间(stackoverflow.com/questions/1042855/...)。转身和奔跑。
1赞 Doug Glancy 11/20/2017
谢谢,非常有帮助。
2赞 undetected Selenium 8/24/2020
答案的宝石。
2赞 Michael Kay 12/10/2022
值得一提的是,在 XPath 3.0/3.1 中,您可以编写完全避免使用命名空间前缀的脚本,而不是在外部 API 中使用和定义绑定。当额外的详细程度无关紧要时,这最有用,例如,如果 XPath 代码是软件生成的。foo:elementfooQ{http://example.com/ns}element
-1赞 Vladimir Alexiev 1/16/2020 #2

我在谷歌表格中用于从维基数据中获取一些计数。我有一张这样的桌子/*[name()='...']

 thes    WD prop links   items
 NOM     P7749   3925    3789
 AAT     P1014   21157   20224

和 cols 中的公式是linksitems

=IMPORTXML("https://query.wikidata.org/sparql?query=SELECT(COUNT(*)as?c){?item wdt:"&$B14&"[]}","//*[name()='literal']")
=IMPORTXML("https://query.wikidata.org/sparql?query=SELECT(COUNT(distinct?item)as?c){?item wdt:"&$B14&"[]}","//*[name()='literal']")

分别。SPARQL 查询恰好没有任何空格...

我看到 used 而不是在 Xml 命名空间中破坏了我的 xpath!,并且由于某种原因不起作用。name()local-name()//*:literal