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
。但如果有多个名称空间,事情就会变得很难看。
答案 0 :(得分:31)
XPath本身没有办法将命名空间前缀与命名空间绑定。这些设施由托管图书馆提供。
建议您使用这些工具并定义名称空间前缀,然后可以根据需要使用这些名称前缀来限定XML元素和属性名称。
以下是XPath主机提供的用于指定命名空间URI的命名空间前缀绑定的一些机制:
<强> XSLT:强>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:i="http://schema.intuit.com/finance/v3">
...
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)
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";
// ...
}
});
DocumentBuilderFactory.setNamespaceAware(true)
。<强> xmlstarlet:强>
-N i="http://schema.intuit.com/finance/v3"
<强> JavaScript的:强>
请参阅Implementing a User Defined Namespace Resolver:
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 );
<强> PHP的:强>
改编自@Tomalak's answer using 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's canonical Q/A on PHP SimpleXML namespaces。
<强> 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);
<强> 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)
Ruby(Nokogiri):
puts doc.xpath('/i:IntuitResponse/i:QueryResponse',
'i' => "http://schema.intuit.com/finance/v3")
请注意,Nokogiri支持删除命名空间,
doc.remove_namespaces!
但请参阅以下警告,以阻止XML命名空间的失效。
一旦声明了名称空间前缀,就可以编写XPath来使用它:
/i:IntuitResponse/i:QueryResponse
另一种方法是编写针对local-name()
进行测试的谓词:
/*[local-name()='IntuitResponse']/*[local-name()='QueryResponse']/@startPosition
或者,在XPath 2.0中:
/*:IntuitResponse/*:QueryResponse/@startPosition
以这种方式绘制命名空间有效但不建议使用,因为它
无法区分不同的元素/属性名称 名称空间(名称空间的目的)。请注意,可以通过添加额外谓词来明确检查命名空间URI 1 来解决此问题:
/*[ namespace-uri()='http://schema.intuit.com/finance/v3'
and local-name()='IntuitResponse']
/*[ namespace-uri()='http://schema.intuit.com/finance/v3'
and local-name()='QueryResponse']
/@startPosition
1 感谢namespace-uri()
注意Daniel Haley。
过于冗长。
答案 1 :(得分:1)
我在Google工作表中使用/*[name()='...']
从Wikidata获取一些计数。我有这样的桌子
thes WD prop links items
NOM P7749 3925 3789
AAT P1014 21157 20224
,列links
和items
中的公式为
=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
无法正常工作。