是否需要在根元素中声明XML命名空间以便通过XPath查询进行匹配?

时间:2015-01-23 20:41:58

标签: xml xpath namespaces

我无法弄清楚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查询不返回任何内容而不是:

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

为什么?

由于命名空间和前缀之间的相互作用,这会让人感到困惑吗?似乎声明没有前缀的命名空间不仅包括该命名空间中的相关元素,还包括其所有子节点,因此将其描述为&#34;默认命名空间&#34; (如this answer中的相关问题)。声明带有前缀的命名空间甚至不包括该命名空间中的相关元素!

是否有一些理由将名称空间需要包含在XML文档的根元素中,而与特定的XPath实现无关?

我的XPath引擎

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

我也在使用this online XPath tester

3 个答案:

答案 0 :(得分:8)

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

简短回答

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


规范说什么

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

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

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

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

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

我所说的规格是XMLXML NamespacesXpath规格。

您的XPath实施会发生什么

现在,如果针对XML文档计算XPath表达式,则此输入文档中存在的所有名称空间声明也必须明确地可用于(声明或“注册”)到XPath引擎。

XPath的一些实现通过简单地重新声明属于作为Xpath引擎输入的XML文档的元素或属性的范围内的所有命名空间声明来简化此操作(另请参阅{{3} })。

在您的情况下,似乎只考虑在最外层元素上做出的声明。这就是你上一篇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表达式。您可以省略默认命名空间的未声明,因为它没有任何效果。


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

  

是否有某些原因需要将名称空间包含在XML文档的根元素中,而不依赖于特定的XPath实现?

不,除了

之外,没有理由应该在根元素上使用名称空间声明
  • 在我认为(非常主观)
  • 时,在根元素上声明它们会更容易找到
  • 如果您要为整个文档声明默认命名空间。在根元素上声明它是使它也适用于根元素的唯一方法
  • 如果根元素本身具有限定名称,即带前缀。然后,您必须在根元素上声明此前缀和名称空间URI。

如果您的XPath实现自动重新声明范围内的命名空间声明,您当然可以利用它,但有时也会让您感到困惑,正如您所注意到的那样。

答案 1 :(得分:2)

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

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

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

为名称空间foo定义了别名/前缀urn:foo。 XML解析器解析该解析并识别节点element属于命名空间urn:foo。出于调试原因,节点名称可以写为{urn:foo}element

如果您更改前缀,甚至删除它,则始终以相同的方式解析。请考虑以下示例:

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

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

对于XPath,您可以定义自己的别名。您编写名称空间解析程序或在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的命名空间定义是独立的,与XML文档中定义的前缀无关。

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

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
);

回到你的问题。您必须了解如何为命名空间注册/定义别名/前缀。之后,您可以在XPath表达式中使用它们。如果为命名空间spring定义别名http://www.springframework.net",则以下XPath表达式应该起作用:

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

答案 2 :(得分:1)

在&#34;什么不起作用&#34;,问题是<object>及其后代位于http://www.springframework.net命名空间,但XPath表达式要求{ {1}}没有名称空间。

我不能立即明白为什么&#34;我期待的工作&#34;不应该工作,因为<object><objects>都明确地在<object>命名空间中,并且XPath表达式正确地限定了元素名称(假设任何代码都解析了{{1}前缀可以访问命名空间绑定)。

在&#34;什么是工作&#34;,再次,http://www.springframework.netspring都明确地在<objects>命名空间中,并且XPath表达式正确地限定了元素名称。

与我期待工作的差异&#34;是适用于<object>的默认命名空间,显式绑定到没有命名空间;所以我只能用&#34;我期待工作&#34;来猜测,默认命名空间(适用于http://www.springframework.net)绑定到你不知道的某个命名空间。我建议你检查<spring>是否有效 - 我想这已经证明了问题。