如何使用Xpath检索XML文件中的名称空间
我有一个像这样开始的XML文件:
<Elements name="Entities" xmlns="XS-GenerationToolElements">
我将不得不打开很多这些文件。它们中的每一个都有不同的名称空间,但一次只能有一个名称空间(我永远不会在一个xml文件中找到两个定义的名称空间)。
使用XPath,我希望有一种自动的方法来将给定的名称空间添加到名称空间管理器中。
到目前为止,我只能通过解析xml文件来获得名称空间,但是我有一个XPathNavigator实例,并且它应该具有一种不错且干净的方式来获取名称空间,对吗?
-或者 -
鉴于我只有一个名称空间,因此以某种方式使XPath使用xml中存在的唯一名称空间,从而通过始终添加名称空间来避免使代码混乱。
解决方案
不幸的是,XPath没有任何"默认名称空间"的概念。我们需要在XPath上下文中注册带有前缀的名称空间,然后在XPath表达式中使用这些前缀。这意味着非常冗长的xpath,但这是XPath 1的一个基本缺点。显然XPath 2可以解决这个问题,但这对我们现在没有用。
我建议我们以编程方式检查XML文档中的名称空间,将该名称空间与XPath上下文中的前缀相关联,然后在xpath表达式中使用该前缀。
我们可以尝试几种技巧。我们使用哪种信息将取决于我们需要从文档中获取哪些信息,我们想要的严格程度以及所使用的XPath实现的一致性。
获取与特定前缀关联的名称空间URI的一种方法是使用" namespace ::"轴。这将为我们提供一个名称空间节点,其名称是前缀,其值是名称空间URI。例如,我们可以使用以下路径在document元素上获取默认的名称空间URI:
/*/namespace::*[name()='']
我们也许可以使用它来为XPathNavigator设置名称空间关联。但是,请注意," namespace ::"轴是XPath 1.0那些并非始终实现的角落之一。
获取该名称空间URI的第二种方法是在文档元素上使用namespace-uri()
函数(我们已经说过将始终在该名称空间中)。表达方式:
namespace-uri(/*)
将为我们提供该名称空间。
另一种选择是忘记将前缀与该名称空间相关联,而只将路径设为无名称空间。每当需要引用不知道其名称空间的元素时,都可以使用local-name()函数来实现此目的。例如:
//*[local-name() = 'Element']
如果我们确实需要,可以再进一步一步,针对document元素之一测试该元素的名称空间URI:
//*[local-name() = 'Element' and namespace-uri() = namespace-uri(/*)]
考虑到名称空间对我们而言毫无意义,最后一个选择是通过剥离名称空间的过滤器运行XML。这样,我们完全不必担心XPath中的它们。最简单的方法是使用正则表达式删除xmlns
属性,但是如果需要同时进行其他整理,则可以执行更复杂的操作。
这个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>
当应用于以下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>