代码之家  ›  专栏  ›  技术社区  ›  e-holder

如何在XSLT中输出当前元素路径?

  •  24
  • e-holder  · 技术社区  · 17 年前

    在XSLT中,有没有一种方法可以在处理元素时确定XML文档中的位置?

    示例:给出了以下XML文档片段…

    <Doc>
      <Ele1>
        <Ele11>
          <Ele111>
          </Ele111>
        </Ele11>
      </Ele1>
      <Ele2>
      </Ele2>
    </Doc>
    

    在XSLT中,如果上下文是元素“ele111”,如何让XSLT输出完整路径?我希望它输出:“/doc/ele1/ele11/ele111”。

    这个问题的背景:我有一个非常大,非常深的文档,我想彻底遍历(一般使用递归),如果我找到一个具有特定属性的元素,我想知道在哪里找到它。我想我可以沿着当前路径进行遍历,但我认为XSLT/XPath应该知道。

    5 回复  |  直到 9 年前
        1
  •  6
  •   brabster    17 年前

    不要认为这是内置在xpath中的,您可能需要一个递归模板,比如 here ,我基于这个例子。它将遍历XML文档中的每个元素,并以类似于您描述的样式输出该元素的路径。

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
          xmlns:xs="http://www.w3.org/2001/XMLSchema"
          exclude-result-prefixes="xs"
          version="2.0">
    
        <xsl:template match="/">
            <paths>
                <xsl:apply-templates/>
            </paths>
        </xsl:template>
    
        <xsl:template match="//*">
            <path>
            <xsl:for-each select="ancestor-or-self::*">
                <xsl:call-template name="print-step"/>
            </xsl:for-each>
            </path>
            <xsl:apply-templates select="*"/>
        </xsl:template>
    
        <xsl:template name="print-step">
            <xsl:text>/</xsl:text>
            <xsl:value-of select="name()"/>
            <xsl:text>[</xsl:text>
            <xsl:value-of select="1+count(preceding-sibling::*)"/>
            <xsl:text>]</xsl:text>
        </xsl:template>
    
    </xsl:stylesheet>
    

    有一些复杂的情况;考虑到这棵树:

    <root>
      <child/>
      <child/>
    </root>
    

    如何区分两个子节点之间的区别?所以你需要一些索引到你的项目序列中,孩子。 1 例如,child[2]。

        2
  •  22
  •   Daniel Haley    12 年前

    当前接受的答案将返回不正确的路径。例如,元素 Ele2 在操作示例中,XML将返回路径 /Doc[1]/Ele2[2] . 应该是 /Doc[1]/Ele2[1] .

    下面是一个类似的XSLT1.0模板,它返回正确的路径:

      <xsl:template name="genPath">
        <xsl:param name="prevPath"/>
        <xsl:variable name="currPath" select="concat('/',name(),'[',
          count(preceding-sibling::*[name() = name(current())])+1,']',$prevPath)"/>
        <xsl:for-each select="parent::*">
          <xsl:call-template name="genPath">
            <xsl:with-param name="prevPath" select="$currPath"/>
          </xsl:call-template>
        </xsl:for-each>
        <xsl:if test="not(parent::*)">
          <xsl:value-of select="$currPath"/>      
        </xsl:if>
      </xsl:template>
    

    下面的示例将添加 path 所有元素的属性。

    XML输入

    <Doc>
      <Ele1>
        <Ele11>
          <Ele111>
            <foo/>
            <foo/>
            <bar/>
            <foo/>
            <foo/>
            <bar/>
            <bar/>
          </Ele111>
        </Ele11>
      </Ele1>
      <Ele2/>  
    </Doc>
    

    XSLT 1

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output indent="yes"/>
      <xsl:strip-space elements="*"/>
    
      <xsl:template match="text()|@*">
        <xsl:copy>
          <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
      </xsl:template>
    
      <xsl:template match="*">
        <xsl:copy>
          <xsl:attribute name="path">
            <xsl:call-template name="genPath"/>
          </xsl:attribute>
          <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>    
      </xsl:template>
    
      <xsl:template name="genPath">
        <xsl:param name="prevPath"/>
        <xsl:variable name="currPath" select="concat('/',name(),'[',
          count(preceding-sibling::*[name() = name(current())])+1,']',$prevPath)"/>
        <xsl:for-each select="parent::*">
          <xsl:call-template name="genPath">
            <xsl:with-param name="prevPath" select="$currPath"/>
          </xsl:call-template>
        </xsl:for-each>
        <xsl:if test="not(parent::*)">
          <xsl:value-of select="$currPath"/>      
        </xsl:if>
      </xsl:template>
    
    </xsl:stylesheet>
    

    XML输出

    <Doc path="/Doc[1]">
       <Ele1 path="/Doc[1]/Ele1[1]">
          <Ele11 path="/Doc[1]/Ele1[1]/Ele11[1]">
             <Ele111 path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]">
                <foo path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/foo[1]"/>
                <foo path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/foo[2]"/>
                <bar path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/bar[1]"/>
                <foo path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/foo[3]"/>
                <foo path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/foo[4]"/>
                <bar path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/bar[2]"/>
                <bar path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/bar[3]"/>
             </Ele111>
          </Ele11>
       </Ele1>
       <Ele2 path="/Doc[1]/Ele2[1]"/>
    </Doc>
    

    这是另一个版本,它只在需要时输出位置谓词。这个例子也不同,它只是输出路径而不是添加属性。

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output method="text"/>
        <xsl:strip-space elements="*"/>
    
        <xsl:template match="text()"/>
    
        <xsl:template match="*">
            <xsl:for-each select="ancestor-or-self::*">
                <xsl:value-of select="concat('/',local-name())"/>
                <!--Predicate is only output when needed.-->
                <xsl:if test="(preceding-sibling::*|following-sibling::*)[local-name()=local-name(current())]">
                    <xsl:value-of select="concat('[',count(preceding-sibling::*[local-name()=local-name(current())])+1,']')"/>
                </xsl:if>
            </xsl:for-each>
            <xsl:text>&#xA;</xsl:text>
            <xsl:apply-templates select="node()"/>
        </xsl:template>
    
    </xsl:stylesheet>
    

    使用上面的输入,这个样式表输出:

    /Doc
    /Doc/Ele1
    /Doc/Ele1/Ele11
    /Doc/Ele1/Ele11/Ele111
    /Doc/Ele1/Ele11/Ele111/foo[1]
    /Doc/Ele1/Ele11/Ele111/foo[2]
    /Doc/Ele1/Ele11/Ele111/bar[1]
    /Doc/Ele1/Ele11/Ele111/foo[3]
    /Doc/Ele1/Ele11/Ele111/foo[4]
    /Doc/Ele1/Ele11/Ele111/bar[2]
    /Doc/Ele1/Ele11/Ele111/bar[3]
    /Doc/Ele2
    
        3
  •  3
  •   MartinM    14 年前

    我不确定您使用的是哪个XSLT处理器,但是如果它是Saxon,您可以使用扩展函数 path() . 其他处理器可能具有相同的功能。

        4
  •  3
  •   Yojimbo paan    9 年前

    你可以使用 祖先 XPath Axes 带着父母和祖父母一起散步。

    <xsl:for-each select="ancestor::*">...
    
        5
  •  1
  •   Martin Honnen    9 年前

    因为由Saxon 9.8(所有版本)或Saxon 9.7支持的xpath 3.0 version="3.0" 在xslt和xmlprime 4中(使用 --xt30 )以及2017年发行的Altova(使用 版本=“3” 样式表)有内置的 path 功能( https://www.w3.org/TR/xpath-functions-30/#func-path , https://www.w3.org/TR/xpath-functions-31/#func-path )对于一个输入

    <?xml version="1.0" encoding="UTF-8"?>
    <Doc>
        <Ele1>
            <Ele11>
                <Ele111>
                    <foo/>
                    <foo/>
                    <bar/>
                    <foo/>
                    <foo/>
                    <bar/>
                    <bar/>
                </Ele111>
            </Ele11>
        </Ele1>
        <Ele2/>  
    </Doc>
    

    样式表

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:math="http://www.w3.org/2005/xpath-functions/math"
        exclude-result-prefixes="xs math"
        version="3.0">
    
        <xsl:output method="text"/>
    
        <xsl:template match="/">
            <xsl:value-of select="//*/path()" separator="&#10;"/>
        </xsl:template>
    
    </xsl:stylesheet>
    

    给出输出

    /Q{}Doc[1]
    /Q{}Doc[1]/Q{}Ele1[1]
    /Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]
    /Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]
    /Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}foo[1]
    /Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}foo[2]
    /Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}bar[1]
    /Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}foo[3]
    /Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}foo[4]
    /Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}bar[2]
    /Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}bar[3]
    /Q{}Doc[1]/Q{}Ele2[1]
    

    在缺少命名空间的情况下,该输出并不像大多数手工尝试的那样紧凑,但该格式具有允许使用命名空间并获取返回路径的格式的优势(至少给定了xpath 3.0或3.1支持),该路径不需要路径表达式的用户设置任何命名空间绑定来评估它。