40

我有一个这样开头的 XML 文件:

<Elements name="Entities" xmlns="XS-GenerationToolElements">

我将不得不打开很多这些文件。它们中的每一个都有不同的命名空间,但一次只能有一个命名空间(我永远不会在一个 xml 文件中找到两个定义的命名空间)。

使用 XPath,我希望有一种自动方式将给定的命名空间添加到命名空间管理器。到目前为止,我只能通过解析 xml 文件来获取命名空间,但我有一个 XPathNavigator 实例,它应该有一种很好且干净的方式来获取命名空间,对吧?

- 或者 -

鉴于我只有一个命名空间,以某种方式使 XPath 使用 xml 中唯一存在的命名空间,从而通过始终附加命名空间来避免代码混乱。

4

3 回答 3

88

您可以尝试一些技巧;您使用哪一个将取决于您需要从文档中获取哪些信息、您想要的严格程度以及您使用的 XPath 实现的一致性程度。

获取与特定前缀关联的命名空间 URI 的一种方法是使用namespace::轴。这将为您提供一个名称空间节点,其名称是前缀,其值是名称空间 URI。例如,您可以使用以下路径获取文档元素上的默认命名空间 URI:

/*/namespace::*[name()='']

您也许可以使用它来为您的 XPathNavigator 设置名称空间关联。但是请注意,namespace::轴是 XPath 1.0 中并不总是实现的那些角落之一。

获取该命名空间 URI 的第二种方法是使用namespace-uri()文档元素上的函数(您说过将始终在该命名空间中)。表达方式:

namespace-uri(/*)

会给你那个命名空间。

另一种方法是忘记将前缀与该名称空间相关联,而只是使您的路径无名称空间。local-name()每当您需要引用一个您不知道其名称空间的元素时,您都可以使用该函数来做到这一点。例如:

//*[local-name() = 'Element']

如果您真的想要,您可以更进一步,针对文档元素之一测试元素的命名空间 URI:

//*[local-name() = 'Element' and namespace-uri() = namespace-uri(/*)]

考虑到名称空间对您似乎没有任何意义,最后一个选择是通过一个过滤器运行您的 XML,该过滤器去除名称空间。然后,您根本不必担心 XPath 中的它们。最简单的方法是简单地xmlns使用正则表达式删除属性,但如果您需要同时进行其他整理,您可以做一些更复杂的事情。

于 2008-09-23T18:53:32.460 回答
12

这个 40 行的 xslt 转换提供了有关给定 XML 文档中名称空间的所有有用信息

    <xsl:stylesheet version="1.0"
       xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
       xmlns:ext="http://exslt.org/common"
       exclude-result-prefixes="ext"
    >

    <xsl:output omit-xml-declaration="yes" indent="yes"/>

    <xsl:strip-space elements="*"/>

    <xsl:key name="kNsByNsUri" match="ns" use="@uri"/>

    <xsl:variable name="vXmlNS" 
        select="'http://www.w3.org/XML/1998/namespace'"/>

    <xsl:template match="/">
      <xsl:variable name="vrtfNamespaces">
        <xsl:for-each select=
          "//namespace::*
                 [not(. = $vXmlNS)
                 and
                  . = namespace-uri(..)
               ]">
          <ns element="{name(..)}"
              prefix="{name()}" uri="{.}"/>
        </xsl:for-each>
      </xsl:variable>

      <xsl:variable name="vNamespaces"
        select="ext:node-set($vrtfNamespaces)/*"/>

      <namespaces>
              <xsl:for-each select=
               "$vNamespaces[generate-id()
                            =
                             generate-id(key('kNsByNsUri',@uri)[1])
                            ]">
                <namespace uri="{@uri}">
                  <xsl:for-each select="key('kNsByNsUri',@uri)/@element">
                    <element name="{.}" prefix="{../@prefix}"/>
                  </xsl:for-each>
                </namespace>
              </xsl:for-each>
      </namespaces>
    </xsl:template>
   </xsl:stylesheet>

应用于以下 XML 文档时:

<a xmlns="my:def1" xmlns:n1="my:n1"
   xmlns:n2="my:n2" xmlns:n3="my:n3">
  <b>
    <n1:d/>
  </b>
  <n1:c>
    <n2:e>
      <f/>
    </n2:e>
  </n1:c>
  <n2:g/>
</a>

产生了想要的结果:

<namespaces>
   <namespace uri="my:def1">
      <element name="a" prefix=""/>
      <element name="b" prefix=""/>
      <element name="f" prefix=""/>
   </namespace>
   <namespace uri="my:n1">
      <element name="n1:d" prefix="n1"/>
      <element name="n1:c" prefix="n1"/>
   </namespace>
   <namespace uri="my:n2">
      <element name="n2:e" prefix="n2"/>
      <element name="n2:g" prefix="n2"/>
   </namespace>
</namespaces>
于 2008-11-14T02:56:27.517 回答
4

不幸的是,XPath 没有任何“默认命名空间”的概念。您需要在 XPath 上下文中注册带有前缀的名称空间,然后在 XPath 表达式中使用这些前缀。这意味着非常冗长的 xpath,但它是 XPath 1 的一个基本缺点。显然 XPath 2 将解决这个问题,但现在这对您没有用处。

我建议您以编程方式检查 XML 文档中的名称空间,将该名称空间与 XPath 上下文中的前缀相关联,然后在 xpath 表达式中使用该前缀。

于 2008-09-23T18:23:41.970 回答