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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/953197/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-06 12:33:19  来源:igfitidea点击:

How do you output the current element path in XSLT?

xmlxsltxpath

提问by e-holder

In XSLT, is there a way to determine where you are in an XML document when processing an element?

在 XSLT 中,有没有办法在处理元素时确定您在 XML 文档中的位置?

Example: Given the following XML Doc Fragment...

示例:给定以下 XML 文档片段...

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

In XSLT, if my context is the Element "Ele111", how can I get XSLT to output the full path? I would want it to output: "/Doc/Ele1/Ele11/Ele111".

在 XSLT 中,如果我的上下文是元素“Ele111”,我怎样才能让 XSLT 输出完整路径?我希望它输出:“/Doc/Ele1/Ele11/Ele111”。

The context of this question: I have a very large, very deep document that I want to traverse exhaustively (generically using recursion), and if I find an element with a particular attribute, I want to know where I found it. I suppose I could carry along my current path as I traverse, but I would think XSLT/XPath should know.

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

采纳答案by brabster

Don't think this is built into XPath, you probably need a recursive template, like the one here, which I've based this example on. It will walk every element in an XML document and output the path to that element in a style similar to the one you've described.

不要认为这是内置在 XPath 中的,您可能需要一个递归模板,就像这里的那个,我在这个例子中基于它。它将遍历 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>

There are a few complications; consider this tree:

有一些并发症;考虑这棵树:

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

How do you tell the difference between the two child nodes? So you need some index into your item-sequence, child1and child[2], for example.

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

回答by Daniel Haley

The currently accepted answer will return incorrect paths. For example, the element Ele2in the OP sample XML would return the path /Doc[1]/Ele2[2]. It should be /Doc[1]/Ele2[1].

当前接受的答案将返回错误的路径。例如,Ele2OP 示例 XML 中的元素将返回 path /Doc[1]/Ele2[2]。应该是/Doc[1]/Ele2[1]

Here's a similar XSLT 1.0 template that returns the correct paths:

这是一个类似的 XSLT 1.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>

Here's an example that will add a pathattribute to all elements.

这是一个将path属性添加到所有元素的示例。

XML Input

XML 输入

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

XSLT 1.0

XSLT 1.0

<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 Output

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>

Here's another version that only outputs the positional predicate if it's needed. This example is also different in that it's just outputting the path instead of adding an attribute.

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

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

using the input above, this stylesheet outputs:

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

/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

回答by Mister Lucky

You can use the ancestorXPath Axesto walk all parent, and grandparents.

您可以使用祖先XPath Axes来遍历所有父级和祖父级。

<xsl:for-each select="ancestor::*">...

回答by MartinM

I'm not sure which XSLT processor you're using, but if it is Saxon, you can use extension function path(). Other processors may have same functionality.

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

回答by Martin Honnen

Since XPath 3.0 as supported by Saxon 9.8 (all editions) or Saxon 9.7 with version="3.0"in the XSLT and XmlPrime 4 (using --xt30) as well as 2017 releases of Altova (using version="3.0"stylesheets) there is the built-in pathfunction (https://www.w3.org/TR/xpath-functions-30/#func-path, https://www.w3.org/TR/xpath-functions-31/#func-path) which for an input like

由于 XPath 3.0 由 Saxon 9.8(所有版本)或 Saxon 9.7 支持version="3.0",在 XSLT 和 XmlPrime 4(使用--xt30)以及 Altova 的 2017 版本(使用version="3.0"样式表)中有内置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>

and a stylesheet like

和一个样式表

<?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>

gives the output

给出输出

/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]

That output is not as compact in case of lack of namespaces as most hand-made attempts but the format has the advantage (at least given XPath 3.0 or 3.1 support) to allow for namespaces being used and get a format for the returned path that does not require the user of the path expression to set up any namespace bindings to evaluate it.

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