XPath如何处理XML命名空间?

51

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中我没有指定命名空间(即路径的每个标记的前缀都不是http://schema.intuit.com/finance/v3)。如果我不明确告诉XPath我想要哪个Id,它如何知道呢?我想在这种情况下(因为只有一个命名空间),XPath可以完全忽略xmlns。但是,如果存在多个命名空间,情况可能会变得非常复杂。


你的XPath不应该返回任何节点:[INFO - XPath返回了0个项目(编译时间为0ms,评估时间为1ms)](http://www.xpathtester.com/xpath/db27cc0f978057b5faabc32c6aa149c8)。你是如何执行XPath的? - har07
@har07 我用Java编写,使用import javax.xml.xpath.XPath。我同意在在线测试器中它不起作用是令人困惑的事情之一。 - Adam
非常好的问题!XPath本身没有提供指定默认命名空间或绑定命名空间前缀到命名空间的方法。然而,幸运的是,托管语言和库可以提供这些功能。详见下面我的回答:https://dev59.com/1FkR5IYBdhLWcg3w0QRL#40796315。 - kjhughes
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Michael Kay
2
我对这个问题印象深刻,因为与大多数之前的提问者不同,Adam不仅包含了一个[mcve],而且意识到并传达了XPath处理XML命名空间的必要性。 大多数这样的问题只是发布一个XPath,可能还有一些XML(如果我们很幸运它不是一张图片或指向一个巨大的外部资源的链接),然后说明它“不起作用”。 Adam感觉到这与命名空间有关,给出了准确的标题,并写下了我认为值得得到规范答案的问题。 - kjhughes
1
可能是如何在Java中使用XPath查询具有名称空间的XML?的重复问题。 - rogerdpack
2个回答

80

XPath 1.0/2.0

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

XPath本身没有将命名空间前缀与命名空间绑定的方法。这些功能由托管库提供。

建议使用这些功能并定义命名空间前缀,然后根据需要用于限定XML元素和属性名称。


这里列出了XPath主机提供的一些不同机制,用于指定命名空间前缀绑定到命名空间URI。
(原始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() 不提供命名空间前缀绑定机制。请参见下一节,击败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() 返回的 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'})

Python (ElementTree):

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

Python(Scrapy):

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

PhP:

这段内容是从@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命名空间的经典问答

Ruby(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

使用这种方式绕过命名空间是可行的,但并不推荐,因为它
  • Under-specifies the full element/attribute name.

  • Fails to differentiate between element/attribute names in different namespaces (the very purpose of namespaces). Note that this concern could be addressed by adding an additional predicate to check the namespace URI explicitly:

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

    Thanks to Daniel Haley for the namespace-uri() note.

  • Is excessively verbose.

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

虽然Q{http://schema.intuit.com/finance/v3}比使用XML命名空间前缀更冗长,但它具有独立于托管库的命名空间前缀绑定机制的优点。 Q {}符号被称为Clark符号,以其发起者James Clark命名。 W3C XPath 3.1 EBNF语法将其称为BracedURILiteral感谢Michael Kay提出涵盖XPath 3.0 / 3.1的BracedURILiteral的建议。

2
pugi:文档中的不符合声明+奇怪的行为观察=转身离开/生命太短暂。Javax:别忘了在DocumentBuilderFactory上调用setNamespaceAware(true) - kjhughes
2
原来 pugixml 根本不支持 XML 命名空间 (https://dev59.com/wnNA5IYBdhLWcg3wPLL-)。正在转换和运行。 - Adam
1
谢谢,非常有帮助。 - Doug Glancy
2
绝妙的答案。 - undetected Selenium
2
值得一提的是,在XPath 3.0/3.1中,可以使用Q{http://example.com/ns}element来代替使用foo:element并在外部API中定义foo绑定。这样可以完全避免使用命名空间前缀,特别是当额外的冗长性不重要时,例如如果XPath代码是由软件生成的,这种方法非常有用。 - Michael Kay
显示剩余6条评论

-1

我在谷歌表格中使用/*[name()='...']来从Wikidata获取一些计数。我有一个如下的表格:

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

并且在列 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查询恰好没有任何空格... 我在Xml Namespace breaking my xpath!中看到了使用name()而不是local-name(),出于某种原因,//*:literal无法工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接