XML 名称空间是否需要在根元素中声明才能被 XPath 查询匹配?

Do XML namespaces need to be declared in the root element to be matchable by an XPath query?

我无法弄清楚是 XPath 本身造成的,还是特定的 XPath 实现让这件事变得如此困难。 SO 问题 – How to change an an XML element in a namespace with MSDeploy Parameters.xml file? – 是我的灵感来源。

什么不起作用

这是不起作用的基本示例。

XML:

<spring>
    <objects xmlns="http://www.springframework.net">
        <object id="CultureResolver" type="Spring.Globalization.Resolvers.SessionCultureResolver, Spring.Web">
             <!--configure for server--> 
            <property name="DefaultCulture" value="en" />
        </object>
    </objects>
</spring>

XPath:

//spring/objects/object[@id='CultureResolver']/@type

XPath 查询 returns 什么都没有,而不是:

Spring.Globalization.Resolvers.SessionCultureResolver, Spring.Web

我期望的工作

我可能天真地希望以下内容有效。

已修改 XML:

<spring>
    <spring:objects xmlns:spring="http://www.springframework.net">
        <spring:object id="CultureResolver" type="Spring.Globalization.Resolvers.SessionCultureResolver, Spring.Web">
             <!--configure for server--> 
            <spring:property name="DefaultCulture" value="en" />
        </spring:object>
    </spring:objects>
</spring>

修改后的 XPath 查询:

//spring/spring:objects/spring:object[@id='CultureResolver']/@type

此查询在 the online tester 中引发错误 我使用:

ERROR - Failed to evaluate XPath expression: org.apache.xpath.domapi.XPathStylesheetDOM3Exception: Prefix must resolve to a namespace: spring

什么有效

已修改 XML:

<spring xmlns="" xmlns:spring="http://www.springframework.net">
    <spring:objects>
        <spring:object id="CultureResolver" type="Spring.Globalization.Resolvers.SessionCultureResolver, Spring.Web">
             <!--configure for server--> 
            <spring:property name="DefaultCulture" value="en" />
        </spring:object>
    </spring:objects>
</spring>

修改后的 XPath 查询(与 我期望的工作 下相同):

//spring/spring:objects/spring:object[@id='CultureResolver']/@type

为了增加混乱,我发现以下 XPath 查询适用于原始示例 XML(在在线测试器 XPath 引擎中):

//spring/*[local-name() = 'objects' and namespace-uri() = 'http://www.springframework.net']/*[@id='CultureResolver' and local-name() = 'object' and namespace-uri() = 'http://www.springframework.net']/@type

为什么?

这是不是因为名称空间和前缀之间的相互作用而造成混淆?似乎声明一个没有前缀的命名空间不仅包括该命名空间中的相关元素,而且还包括它的所有子元素,因此将其描述为 "default namespace" (如 this answer 中的相关问题) .而使用前缀声明命名空间甚至不包括该命名空间中的相关元素!

命名空间 需要 包含在 XML 文档的根元素中,独立于特定的 XPath 实现,有什么原因吗?

我的 XPath 引擎

我试图解决的问题涉及 Microsoft Web 部署 (MSDeploy) 使用的任何 XPath 引擎。

我也在用this online XPath tester

在 "What doesn't Work" 中,问题是 <object> 及其后代位于 http://www.springframework.net 命名空间中,但 XPath 表达式要求的 <object> 位于没有命名空间。

为什么 "What I Expect to Work" 不应该工作不是很明显,因为 <objects><object> 都明确地在 http://www.springframework.net 命名空间中,并且 XPath 表达式限定了元素名称正确(假设解析 spring 前缀的任何代码都可以访问命名空间绑定)。

在 "What Does Work" 中,<objects><object> 都显式地位于 http://www.springframework.net 命名空间中,并且 XPath 表达式正确地限定了元素名称。

与"What I Expect to Work"的不同之处在于,适用于<spring>的默认命名空间未明确绑定到任何命名空间;所以我只能猜测使用 "What I Expect to Work",默认命名空间(适用于 <spring>)绑定到某个您不知道的命名空间。我建议您检查 //spring 是否有效 - 我想这已经暴露出问题了。

一个有趣且问得很好的问题!据我所知,困难在于您的 XPath 引擎处理输入文档中的命名空间声明的方式。

简答

不,此行为与一般的 XPath 或 XPath 规范无关。这是由于个别实施。


规格说明

就XML和XPath规范而言,命名空间可以在任何元素上声明,最外层(或"root")元素没有什么特别之处。根元素上的命名空间声明与任何其他声明一样。

当然还是有规矩的。例如,前缀必须与使用其 QName 的元素或该元素(或该属性)的祖先上的命名空间 URI 相关联。因此,以下格式不正确 XML:

<prefix:root>
    <child xmlns:prefix="www.example.com"/>
</prefix:root>

第二条重要规则:默认命名空间只能应用于声明它的元素和所有后代元素。在以下文档中,root 元素根本不在命名空间中:

<root>
   <child xmlns="www.example.com">
      <grandchild/>
   </child>
</root>

我说的规格是XML, XML Namespaces and Xpath规格。

在您的 XPath 实现中发生了什么

现在,如果针对 XML 文档评估 XPath 表达式,则此输入文档中存在的所有命名空间声明也必须显式提供(声明或 "registered")XPath 引擎.

XPath 的某些实现通过简单地重新声明元素或属性范围内的所有名称空间声明来简化此操作 XML 文档用作输入Xpath 引擎(另见 this)。

在你的例子中,似乎只考虑了对最外层元素所做的声明。这就是为什么你的最后一个 XML 文档:

<spring xmlns="" xmlns:spring="http://www.springframework.net">
    <spring:objects>
        <spring:object id="CultureResolver" type="Spring.Globalization.Resolvers.SessionCultureResolver, Spring.Web">
             <!--configure for server--> 
            <spring:property name="DefaultCulture" value="en" />
        </spring:object>
    </spring:objects>
</spring>

有效 - 因为命名空间声明是在根元素上进行的,并且您从根元素执行 XPath 表达式。不过,您可以省略默认命名空间的取消声明,因为它没有任何效果。


最后,回答你最后一个问题:

Is there some reason why namespaces need to be included in the root element of the XML document, independent of particular XPath implementations?

不,没有理由将命名空间声明放在根元素上,除了

  • 在我看来,在根元素上声明它们更容易找到(非常主观)
  • 如果您想为整个文档声明一个默认命名空间。在根元素上声明它是让它也应用于根元素的唯一方法
  • 如果根元素本身有限定名称,即前缀。然后,您必须在根元素上声明此前缀和命名空间 URI。

如果您的 XPath 实现自动重新声明范围内的名称空间声明,您当然可以利用它,但正如您所注意到的,有时它也会造成混淆。

不,文档的名称space 定义和 XPath 是分开的。默认情况下,某些实现会自动注册当前上下文的 space 定义。我认为这是一个错误,因为它使 XPath 不明确。

让我们从一个简单的例子开始:

<foo:element xmlns:foo="urn:foo"/>

为名称spaceurn:foo定义了一个alias/prefixfoo。 XML 解析器解析并识别节点 element 属于名称 space urn:foo。出于调试原因,节点名称可以写成 {urn:foo}element.

如果您更改前缀,甚至将其删除,这总是以相同的方式解决。考虑以下示例:

<foo:element xmlns:foo="urn:foo"/>
<bar:element xmlns:bar="urn:foo"/>
<element xmlns="urn:foo"/>

prefix/alias仅对节点及其后代有效。任何后代都可以有自己的定义,可能会覆盖其祖先之一。

对于 XPath,您可以定义自己的别名。您编写一个 namespace 解析器或在 XPath 引擎上注册它们。这真的取决于实施。

这是一个小 PHP 示例:

$dom = new DOMDocument();
$dom->loadXml('<foo:element xmlns:foo="urn:foo"/>');

$xpath = new DOMXPath($dom);
$xpath->registerNamespace('alias', 'urn:foo');

var_dump($xpath->evaluate('name(/alias:element)'));

输出:

string(11) "foo:element"

您可以看到 XPath 的名称space 定义与 XML 文档中定义的前缀是分开和独立的。

在Javascript 中,XPath 与Document.evaluate() 一起使用。第三个参数是名称space解析器。

var resolver = {
  namespaces : {
   'alias' : 'urn:foo'
  },
  lookupNamespaceURI : function(prefix) {
    if (prefix == '') {
      return null;
    }
    return this.namespaces[prefix] || null;
  }
};

console.log(
    document.evaluate(
       'name(/alias:element)'
    ),
    document,
    resolver,
    XPathResult.ANY_TYPE,
    null
  ).stringValue
);

回到你的问题。您必须了解您如何 register/define aliases/prefixes 为您命名 space。之后,您可以在 XPath 表达式中使用它们。如果您为名称 space http://www.springframework.net" 定义别名 spring,则以下 XPath 表达式应该有效:

//spring/spring:objects/spring:object[@id='CultureResolver']/@type