查找 XML 文档中的所有命名空间声明 - xPath 1.0 与 xPath 2.0
Posted
技术标签:
【中文标题】查找 XML 文档中的所有命名空间声明 - xPath 1.0 与 xPath 2.0【英文标题】:Find all namespace declarations in an XML document - xPath 1.0 vs xPath 2.0 【发布时间】:2012-04-29 20:43:25 【问题描述】:作为 Java 6 应用程序的一部分,我想查找 XML 文档中的所有命名空间声明,包括所有重复项。
编辑:根据 Martin 的要求,这是我正在使用的 Java 代码:
XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xPath = xPathFactory.newXPath();
XPathExpression xPathExpression = xPathExpression = xPath.compile("//namespace::*");
NodeList nodeList = (NodeList) xPathExpression.evaluate(xmlDomDocument, XPathConstants.NODESET);
假设我有这个 XML 文档:
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:ele="element.com" xmlns:att="attribute.com" xmlns:txt="textnode.com">
<ele:one>a</ele:one>
<two att:c="d">e</two>
<three>txt:f</three>
</root>
为了查找所有命名空间声明,我将这个 xPath 语句应用于 XML 文档使用 xPath 1.0:
//namespace::*
它找到了 4 个命名空间声明,这是我所期望(和希望)的:
/root[1]/@xmlns:att - attribute.com
/root[1]/@xmlns:ele - element.com
/root[1]/@xmlns:txt - textnode.com
/root[1]/@xmlns:xml - http://www.w3.org/XML/1998/namespace
但是如果我改为使用 xPath 2.0,那么我会得到 16 个命名空间声明(之前的每个声明 4 次),这不是我所期望的(或期望的):
/root[1]/@xmlns:xml - http://www.w3.org/XML/1998/namespace
/root[1]/@xmlns:att - attribute.com
/root[1]/@xmlns:ele - element.com
/root[1]/@xmlns:txt - textnode.com
/root[1]/@xmlns:xml - http://www.w3.org/XML/1998/namespace
/root[1]/@xmlns:att - attribute.com
/root[1]/@xmlns:ele - element.com
/root[1]/@xmlns:txt - textnode.com
/root[1]/@xmlns:xml - http://www.w3.org/XML/1998/namespace
/root[1]/@xmlns:att - attribute.com
/root[1]/@xmlns:ele - element.com
/root[1]/@xmlns:txt - textnode.com
/root[1]/@xmlns:xml - http://www.w3.org/XML/1998/namespace
/root[1]/@xmlns:att - attribute.com
/root[1]/@xmlns:ele - element.com
/root[1]/@xmlns:txt - textnode.com
即使我使用 xPath 语句的非缩写版本,也会看到同样的差异:
/descendant-or-self::node()/namespace::*
在 oXygen 中测试过的各种 XML 解析器(LIBXML、MSXML.NET、Saxon)都可以看到它。 (编辑:正如我稍后在 cmets 中提到的,这种说法是不正确的。虽然我认为我正在测试各种 XML 解析器,但我真的没有。)
问题 #1:为什么从 xPath 1.0 到 xPath 2.0 的差异?
问题 #2: 使用 xPath 2.0 是否可能/合理地获得预期结果?
提示:在 xPath 2.0 中使用 distinct-values()
函数将不返回所需的结果,因为我想要所有命名空间声明,即使同一个命名空间被声明了两次。例如,考虑这个 XML 文档:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<bar:one xmlns:bar="http://www.bar.com">alpha</bar:one>
<bar:two xmlns:bar="http://www.bar.com">bravo</bar:two>
</root>
想要的结果是:
/root[1]/@xmlns:xml - http://www.w3.org/XML/1998/namespace
/root[1]/bar:one[1]/@xmlns:bar - http://www.bar.com
/root[1]/bar:two[1]/@xmlns:bar - http://www.bar.com
【问题讨论】:
James,请向我们展示查找命名空间“声明”的代码。在我的理解中,XPath//namespace::*
查找与命名空间声明不同的所有命名空间节点,因为命名空间节点存在于每个元素节点并且不在节点之间共享。因此,对于具有四个元素节点且根元素上有三个命名空间声明的 XML 文档,路径应该为四个元素中的每一个找到四个命名空间节点。据我所知,这在 XPath 1.0 和 2.0 之间应该是相同的。此外,/root[1]/@xmlns:txt
之类的符号也具有误导性。
/root[1]/@xmlns:txt 表示法来自 oXygen。这是他们在节点列表中的节点表示,这很好。
上面添加的Java代码。很标准的东西。感谢您的解释。
我认为一个问题是您使用的 Java API 在 DOM 节点模型上工作,或者更确切地说将 XPath/XSLT 数据模型映射到 DOM 模型。 DOM 模型只有属性节点,其中一些是命名空间声明属性。 XSLT/XPath 模型具有属性节点和命名空间节点,命名空间声明不是该模型中的属性节点,例如<foo xmlns:ns1="http://example.com/ns1"/>
和 foo
元素在 XPath/XSLT 数据模型中没有属性节点,但有两个范围命名空间节点(一个在标记中,一个在 xml 命名空间中)。
继续我的评论:问题是您使用 XPath //namespace::*
选择了一些命名空间节点,然后使用 API 将结果呈现为 DOM 节点。该映射可能取决于实现。将 XPath 映射到 DOM 时还有其他已知问题,例如对于<foo><![CDATA[text 1]]>text2</foo>
,当映射到DOM 时/foo/text()[1]
选择的内容取决于实现,因为在DOM 中foo
元素有两个子节点,一个CDATA 节节点和一个文本节点,而XPath 模型只有一个文本节点。跨度>
【参考方案1】:
我认为这将获得所有命名空间,没有任何重复:
for $i in 1 to count(//namespace::*) return
if (empty(index-of((//namespace::*)[position() = (1 to ($i - 1))][name() = name((//namespace::*)[$i])], (//namespace::*)[$i])))
then (//namespace::*)[$i]
else ()
【讨论】:
就是这样!这个 xPath 2.0 将找到所有命名空间声明,它适用于我在 OP 中给出的两个示例。这种方法的优雅之处在于将命名空间作为序列处理。干得好,@Roger。【参考方案2】:为了查找所有命名空间声明,我将此 xPath 语句应用于 使用 xPath 1.0 的 XML 文档:
//namespace::* It finds 4 namespace declarations, which is what I expect (and desire): /root[1]/@xmlns:att - attribute.com /root[1]/@xmlns:ele - element.com /root[1]/@xmlns:txt - textnode.com /root[1]/@xmlns:xml - http://www.w3.org/XML/1998/namespace
您正在使用不兼容(有缺陷)的 XPath 1.0 实现。
我使用我拥有的所有 XSLT 1.0 处理器都得到了不同且正确的结果。这种转换(仅评估 XPath 表达式并为每个选定的命名空间节点打印一行):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:for-each select="//namespace::*">
<xsl:value-of select="concat(name(), ': ', ., '
')"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
应用于提供的 XML 文档时:
<root xmlns:ele="element.com" xmlns:att="attribute.com" xmlns:txt="textnode.com">
<ele:one>a</ele:one>
<two att:c="d">e</two>
<three>txt:f</three>
</root>
产生正确的结果:
xml: http://www.w3.org/XML/1998/namespace
ele: element.com
att: attribute.com
txt: textnode.com
xml: http://www.w3.org/XML/1998/namespace
ele: element.com
att: attribute.com
txt: textnode.com
xml: http://www.w3.org/XML/1998/namespace
ele: element.com
att: attribute.com
txt: textnode.com
xml: http://www.w3.org/XML/1998/namespace
ele: element.com
att: attribute.com
txt: textnode.com
使用所有这些 XSLT 1.0 和 XSLT 2.0 处理器:
MSXML3、MSXML4、MSXML6、.NET XslCompiledTransform、.NET XslTransform、Altova (XML SPY)、Saxon 6.5.4、Saxon 9.1.07、XQSharp。
这是一个简短的 C# 程序,确认在 .NET 中选择的节点数为 16:
namespace TestNamespaces
using System;
using System.IO;
using System.Xml.XPath;
class Test
static void Main(string[] args)
string xml =
@"<root xmlns:ele='element.com' xmlns:att='attribute.com' xmlns:txt='textnode.com'>
<ele:one>a</ele:one>
<two att:c='d'>e</two>
<three>txt:f</three>
</root>";
XPathDocument doc = new XPathDocument(new StringReader(xml));
double count =
(double) doc.CreateNavigator().Evaluate("count(//namespace::*)");
Console.WriteLine(count);
结果是:
16
.
更新:
这是一个 XPath 2.0 表达式,它只查找“不同的”命名空间节点并为每个节点生成一行名称 - 值对:
for $i in distinct-values(
for $ns in //namespace::*
return
index-of(
(for $x in //namespace::*
return
concat(name($x), ' ', string($x))
),
concat(name($ns), ' ', string($ns))
)
[1]
)
return
for $x in (//namespace::*)[$i]
return
concat(name($x), ' :', string($x), '
')
【讨论】:
在以下情况下获得 4 个节点:1) 我使用 Java 6 中的默认解析器,使用 xPath API。 2) 我在 oXygen 12.1 中使用 XSV、LIBXML、MSXML4.0、MSXML.NET 和 Saxon-EE 应用 xPath(作为 1.0)。 3) 我将您的 XSLT 与 Xalan 一起使用(也在 oXygen 中)。当我将您的 XSLT 与各种口味的 Saxon(在 oXygen 中)一起使用时,我得到了 16。我不明白为什么我在撒克逊人那里得到不同的答案。我一定缺少一些简单的东西...... 我不倾向于相信 Java 6 解析器、MSXML.NET 和 Saxon-EE 是“不兼容(有缺陷)的 XPath 1.0 实现”。一定还有别的…… @james.garriss:正如我所指出的,我使用 MSXML 和 .NET 得到了相同的结果——这些 XSLT 处理器没有单独/自己的 XPath 评估——它们使用可用的 XPath 引擎.因此,您的代码中有一些内容会导致选择的节点更少。有时间我会添加一个 C# 代码示例,显示选择了 16 个节点。 @james.garriss:添加了承诺的 C# 示例——16 个节点。为什么您认为 .NET XPath 评估会产生其他结果?显然,您得到不同结果的原因在于您的代码。 确实,这是另外一回事,尤其是我(不正确)使用 oXygen。允许我在解析器中进行选择的下拉列表不连接到 xPath 查询,而仅连接到 XSD 验证。因此,我认为真正发生的是:当我选择 xPath 1.0 时,oXygen 在后台使用 Xalan,而 Xalan 的实现不兼容。当我选择 xPath 2.0 时,它使用 Saxon(兼容)并且我得到正确答案(16 个节点)。【参考方案3】:正如前面的线程所指出的,//namespace::*
将根据 XPath 1.0 和 XPath 2.0 实现返回所有命名空间节点,其中有 16 个。如果您发现没有正确实现规范的实现,我不会感到惊讶。
使用 XPath 1.0 或 XPath 2.0 通常不可能找到所有命名空间声明(不同于命名空间节点),因为以下两个文档在数据模型级别被认为是等效的:
文档 A:
<a xmlns="one">
<b/>
</a>
文档 B:
<a xmlns="one">
<b xmlns="one"/>
</a>
但是,如果我们将“重要命名空间声明”定义为存在于子元素上但不存在于其父元素上的命名空间,那么您可以试试这个 XPath 2.0 表达式:
for $e in //* return
for $n in $e/namespace::* return
if (not(some $p in $n/../namespace::* satisfies ($p/name() eq $e/name() and string($p) eq string($n)))) then concat($e/name(), '->', $n/name(), '=', string($n)) else ()
【讨论】:
虽然我非常希望这个答案能够奏效,因为它触及了我问题的真正核心,但这个 xPath 不会返回任何与我已经使用的不同的东西 (//namespace:: *)。不过,感谢您的尝试。【参考方案4】:这是我使用 .NET 的 XPathDocument
(XSLT/XPath 1.0 数据模型)、XmlDocument
(DOM 数据模型)和 MSXML 6 的 DOM 的 XPath 1.0 实现的结果;针对您的示例 XML 文档运行的测试代码是
Console.WriteLine("XPathDocument:");
XPathDocument xpathDoc = new XPathDocument("../../XMLFile4.xml");
foreach (XPathNavigator nav in xpathDoc.CreateNavigator().Select("//namespace::*"))
Console.WriteLine("Node type: 0; name: 1; value: 2.", nav.NodeType, nav.Name, nav.Value);
Console.WriteLine();
Console.WriteLine("DOM XmlDocument:");
XmlDocument doc = new XmlDocument();
doc.Load("../../XMLFile4.xml");
foreach (XmlNode node in doc.SelectNodes("//namespace::*"))
Console.WriteLine("Node type: 0; name: 1; value: 2.", node.NodeType, node.Name, node.Value);
Console.WriteLine();
Console.WriteLine("MSXML 6 DOM:");
dynamic msxmlDoc = Activator.CreateInstance(Type.GetTypeFromProgID("Msxml2.DOMDocument.6.0"));
msxmlDoc.load("../../XMLFile4.xml");
foreach (dynamic node in msxmlDoc.selectNodes("//namespace::*"))
Console.WriteLine("Node type: 0; name: 1; value: 2.", node.nodeType, node.name, node.nodeValue);
它的输出是
XPathDocument:
Node type: Namespace; name: txt; value: textnode.com.
Node type: Namespace; name: att; value: attribute.com.
Node type: Namespace; name: ele; value: element.com.
Node type: Namespace; name: xml; value: http://www.w3.org/XML/1998/namespace.
Node type: Namespace; name: txt; value: textnode.com.
Node type: Namespace; name: att; value: attribute.com.
Node type: Namespace; name: ele; value: element.com.
Node type: Namespace; name: xml; value: http://www.w3.org/XML/1998/namespace.
Node type: Namespace; name: txt; value: textnode.com.
Node type: Namespace; name: att; value: attribute.com.
Node type: Namespace; name: ele; value: element.com.
Node type: Namespace; name: xml; value: http://www.w3.org/XML/1998/namespace.
Node type: Namespace; name: txt; value: textnode.com.
Node type: Namespace; name: att; value: attribute.com.
Node type: Namespace; name: ele; value: element.com.
Node type: Namespace; name: xml; value: http://www.w3.org/XML/1998/namespace.
DOM XmlDocument:
Node type: Attribute; name: xmlns:txt; value: textnode.com.
Node type: Attribute; name: xmlns:att; value: attribute.com.
Node type: Attribute; name: xmlns:ele; value: element.com.
Node type: Attribute; name: xmlns:xml; value: http://www.w3.org/XML/1998/namespa
ce.
Node type: Attribute; name: xmlns:txt; value: textnode.com.
Node type: Attribute; name: xmlns:att; value: attribute.com.
Node type: Attribute; name: xmlns:ele; value: element.com.
Node type: Attribute; name: xmlns:xml; value: http://www.w3.org/XML/1998/namespa
ce.
Node type: Attribute; name: xmlns:txt; value: textnode.com.
Node type: Attribute; name: xmlns:att; value: attribute.com.
Node type: Attribute; name: xmlns:ele; value: element.com.
Node type: Attribute; name: xmlns:xml; value: http://www.w3.org/XML/1998/namespa
ce.
Node type: Attribute; name: xmlns:txt; value: textnode.com.
Node type: Attribute; name: xmlns:att; value: attribute.com.
Node type: Attribute; name: xmlns:ele; value: element.com.
Node type: Attribute; name: xmlns:xml; value: http://www.w3.org/XML/1998/namespa
ce.
MSXML 6 DOM:
Node type: 2; name: xmlns:xml; value: http://www.w3.org/XML/1998/namespace.
Node type: 2; name: xmlns:ele; value: element.com.
Node type: 2; name: xmlns:att; value: attribute.com.
Node type: 2; name: xmlns:txt; value: textnode.com.
Node type: 2; name: xmlns:xml; value: http://www.w3.org/XML/1998/namespace.
Node type: 2; name: xmlns:ele; value: element.com.
Node type: 2; name: xmlns:att; value: attribute.com.
Node type: 2; name: xmlns:txt; value: textnode.com.
Node type: 2; name: xmlns:xml; value: http://www.w3.org/XML/1998/namespace.
Node type: 2; name: xmlns:ele; value: element.com.
Node type: 2; name: xmlns:att; value: attribute.com.
Node type: 2; name: xmlns:txt; value: textnode.com.
Node type: 2; name: xmlns:xml; value: http://www.w3.org/XML/1998/namespace.
Node type: 2; name: xmlns:ele; value: element.com.
Node type: 2; name: xmlns:att; value: attribute.com.
Node type: 2; name: xmlns:txt; value: textnode.com.
所以这肯定不是 XPath 1.0 与 XPath 2.0 的问题。我认为您看到的问题是将具有命名空间节点的 XPath 数据模型映射到具有属性节点的 DOM 模型的缺点。更熟悉 Java XPath API 的人需要告诉您,您看到的行为是否正确地依赖于实现,因为 API 规范对于将 XPath 命名空间轴映射到 DOM 模型的情况不够精确,或者它是否是一个错误。
【讨论】:
我同意这不是 xPath 1.0 与 2.0 的问题,但我(还)不倾向于认为问题是 Java 6 中的 xPath API(尽管它可能有各种缺点)因为当我用 Saxon 9 HE 替换 Java 6 (Xalan) 中的默认 XML 解析器时(同时对我的 Java 代码进行没有更改),它可以工作(也就是说,它返回 16 个节点而不是 4 个)。这让我得出结论,Xalan 的实现才是真正的原因。以上是关于查找 XML 文档中的所有命名空间声明 - xPath 1.0 与 xPath 2.0的主要内容,如果未能解决你的问题,请参考以下文章