如何从XML获取所有叶元素的xpath?

时间:2012-01-30 13:55:25

标签: xml xslt xpath

我想知道是否可以创建一个XSLT样式表来为给定XML文件中的所有叶元素提取XPATH。 例如。为了

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <item1>value1</item1>
    <subitem>
        <item2>value2</item2>
    </subitem>
</root>

输出为

/root/item1
/root/subitem/item2

4 个答案:

答案 0 :(得分:15)

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="text" indent="no" />

    <xsl:template match="*[not(*)]">
        <xsl:for-each select="ancestor-or-self::*">
            <xsl:value-of select="concat('/', name())"/>

            <xsl:if test="count(preceding-sibling::*[name() = name(current())]) != 0">
                <xsl:value-of select="concat('[', count(preceding-sibling::*[name() = name(current())]) + 1, ']')"/>
            </xsl:if>
        </xsl:for-each>
        <xsl:text>&#xA;</xsl:text>
        <xsl:apply-templates select="*"/>
    </xsl:template>

    <xsl:template match="*">
        <xsl:apply-templates select="*"/>
    </xsl:template>

</xsl:stylesheet>

输出:

/root/item1
/root/subitem/item2

答案 1 :(得分:9)

此转化

<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output omit-xml-declaration="yes" indent="yes"/>
        <xsl:strip-space elements="*"/>

        <xsl:variable name="vApos">'</xsl:variable>

        <xsl:template match="*[@* or not(*)] ">
          <xsl:if test="not(*)">
             <xsl:apply-templates select="ancestor-or-self::*" mode="path"/>
             <xsl:text>&#xA;</xsl:text>
            </xsl:if>
            <xsl:apply-templates select="@*|*"/>
        </xsl:template>

        <xsl:template match="*" mode="path">
            <xsl:value-of select="concat('/',name())"/>
            <xsl:variable name="vnumSiblings" select=
             "count(../*[name()=name(current())])"/>
            <xsl:if test="$vnumSiblings > 1">
                <xsl:value-of select=
                 "concat('[',
                         count(preceding-sibling::*
                                [name()=name(current())]) +1,
                         ']')"/>
            </xsl:if>
        </xsl:template>

        <xsl:template match="@*">
            <xsl:apply-templates select="../ancestor-or-self::*" mode="path"/>
            <xsl:value-of select="concat('[@',name(), '=',$vApos,.,$vApos,']')"/>
            <xsl:text>&#xA;</xsl:text>
        </xsl:template>
</xsl:stylesheet>

应用于提供的XML文档

<root>
    <item1>value1</item1>
    <subitem>
        <item2>value2</item2>
    </subitem>
</root>

生成想要的正确结果

/root/item1
/root/subitem/item2

使用此XML文档

<root>
    <item1>value1</item1>
    <subitem>
        <item>value2</item>
        <item>value3</item>
    </subitem>
</root>

正确生成

/root/item1
/root/subitem/item[1]
/root/subitem/item[2]

另请参阅此相关答案https://stackoverflow.com/a/4747858/36305

答案 2 :(得分:3)

我认为以下更正仅在异常情况下才有意义,在这种情况下,对于相同的名称空间使用不同的前缀,或者在文档中的兄弟元素中使用相同前缀的不同名称空间。但是,这种输入在理论上没有任何错误,并且在某些生成的XML中可能很常见。

无论如何,以下答案修复了这个案例(从@ Kirill的回答中复制和修改):

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

   <xsl:output method="text" indent="no" />

   <xsl:template match="*[not(*)]">
      <xsl:for-each select="ancestor-or-self::*">
         <xsl:value-of select="concat('/', name())"/>

         <!-- Suggestions on how to refactor the repetition of long XPath
              expression parts are welcome. -->
         <xsl:if test="count(../*[local-name() = local-name(current())
               and namespace-uri(.) = namespace-uri(current())]) > 1">
            <xsl:value-of select="concat('[', count(
               preceding-sibling::*[local-name() = local-name(current())
               and namespace-uri(.) = namespace-uri(current())]) + 1, ']')"/>
         </xsl:if>
      </xsl:for-each>
      <xsl:text>&#xA;</xsl:text>
      <xsl:apply-templates select="*"/>
   </xsl:template>

   <xsl:template match="*">
      <xsl:apply-templates select="*"/>
   </xsl:template>

</xsl:stylesheet>

它还解决了其他答案中的问题,其中一系列兄弟姐妹中的第一个元素缺少位置谓词。

E.g。输入

<root>
   <item1>value1</item1>
   <subitem>
      <a:item xmlns:a="uri">value2</a:item>
      <b:item xmlns:b="uri">value3</b:item>
   </subitem>
</root>

这个答案产生了

/root/item1
/root/subitem/a:item[1]
/root/subitem/b:item[2]

这是正确的。

但是,与所有XPath表达式一样,这些表达式只有在使用它们的环境为所使用的名称空间前缀指定正确的绑定时才有效。从理论上讲,可以有更多的病理文档,无论前缀绑定如何,上述答案都会生成无法工作的XPath表达式(至少在XPath 1.0中)。例如。这个输入:

<root>
   <item1>value1</item1>
   <a:subitem xmlns:a="differentURI">
      <a:item xmlns:a="uri">value2</a:item>
      <b:item xmlns:b="uri">value3</b:item>
   </a:subitem>
</root>

产生输出

/root/item1
/root/a:subitem/a:item[1]
/root/a:subitem/b:item[2]

但是这里的第二个XPath表达式永远不会起作用,因为前缀a引用同一表达式中的两个不同的名称空间。

答案 3 :(得分:2)

你可以找到//*[not(*)]的叶子元素,当然你可以for-each祖先或者自己的轴然后输出路径。但是一旦你有名称空间涉及生成XPath表达式变得复杂。