XPath故意不设计用于那些想要在XML文档中使用同一XPath表达式但未知命名空间的情况。你需要提前了解命名空间,向XPath处理器声明命名空间并在表达式中使用名称。Martin和Dan的答案展示了如何在C#中实现这一点。
这种困难的原因最好在
XML namespaces规范中表述。
我们设想可扩展标记语言(XML)的应用,其中单个XML文档可以包含为多个软件模块定义和使用的元素和属性(这里称为“标记词汇”)。其中一个动机是模块化:如果存在这样一个众所周知且有有用软件可用的标记词汇,最好重用此标记,而不是重新发明它。
这些包含多个标记词汇的文档会带来识别和冲突问题。即使面对意图供其他软件包使用的标记使用相同的元素名称或属性名称时,软件模块也需要能够识别它们设计的处理元素和属性。
这些考虑要求文档结构应具有构造名称,以避免来自不同标记词汇的名称之间的冲突。本规范描述了一种机制,即XML命名空间,通过为元素和属性分配扩展名称来实现这一点。
换句话说,命名空间应该用于确保您知道文档正在讨论什么:是
<head>
元素在谈论 XHTML 文档的前言还是 AnatomyML 文档中的某个人的头部?您永远不应该对命名空间持不可知论态度,并且这基本上是任何 XML 词汇表中您应该定义的第一件事情。
你想要的是可能可以实现,但我认为它不能在单个XPath表达式中完成。首先,您需要在文档中搜寻并提取所有的namespaceURIs,然后将这些添加到命名空间管理器中,然后运行您想要的实际XPath表达式(此时您需要了解文档中命名空间的分布,否则您将需要运行很多表达式)。我认为最好使用其他东西而不是XPath(例如DOM或SAX-like API)来查找namespaceURIs,但您也可以探索XPath命名空间轴(在XPath 1.0中),使用
namespace-uri-from-QName
函数(在XPath 2.0中),或者使用类似Oleg的
"configuration/*[local-name() = 'MyNode']"
的表达式。无论如何,我认为您最好尽量避免编写与命名空间无关的XPath!为什么您事先不知道您的命名空间?您将如何避免匹配您不打算匹配的内容?
编辑-您知道namespaceURI吗?
结果证明,您的问题让我们所有人都感到困惑。 显然,您知道命名空间URI,但不知道在XML文档中使用的命名空间前缀。 实际上,在这种情况下,没有使用命名空间前缀,URI成为定义它的默认命名空间。需要知道的关键是,所选的前缀(或缺乏前缀)与XPath表达式(以及XML解析总体而言)无关。前缀/xmlns属性只是将节点与表示为文本的命名空间URI相关联的一种方法。您可能需要查看
this answer,我在其中尝试澄清命名空间前缀。
您应该尽量像解析器一样考虑XML文档-每个节点都有一个命名空间URI和一个本地名称。 命名空间前缀/继承规则只是节省了多次输入URI的输入。一种书写方式是Clark注释:即,您编写{
http://www.example.com/namespace/example}LocalNodeName,但此注释通常仅用于文档- XPath对此注释一无所知。
相反,XPath使用自己的命名空间前缀。例如
/ns1:root/ns2:node
。但是这些与原始XML文档中可能使用的任何前缀完全独立无关。任何XPath实现都将有一种方法将其自己的前缀与命名空间URI映射起来。对于您使用的C#实现,您需要一个
XmlNamespaceManager
,在Perl中,您提供哈希表,xmllint采用命令行参数...因此,您只需要为您知道的命名空间URI创建一些任意前缀,并在XPath表达式中使用此前缀。您使用的前缀并不重要,在XML中,您只关心URI和localName的组合。
另一个需要记住的事情(通常会让人惊讶)是XPath不执行命名空间继承。您需要为每个具有命名空间的内容添加前缀,而不管该命名空间是从继承,xmlns属性还是命名空间前缀中获取的。此外,虽然您应该始终以URI和localName为基础思考,但也有从XML文档中访问前缀的方法。很少需要使用这些方法。
foreach (XElement myNode in doc.Descendants("{lcmp}MyNode"))
。当然,您也可以使用变量,例如XNamespace df = "lcmp"; foreach (XElement myNode in doc.Descendants(df + "MyNode"))
。 - Martin Honnen