xslt命名模板:如何编写模板,创建表示给定节点的xpath的字符串

时间:2014-06-05 22:12:38

标签: xslt xpath

我正在尝试编写一个递归命名模板,它将显示给定节点的路径:

<?xml version="1.0"?>

<testfile>
    <section>
        <title>My Section</title>
        <para>Trying to write a recursive function that will return a basic xpath of a given node; in the case of this node, I would want to return testfile/section/para, I don't need /testfile/section[1]/para[1] or anything like that. The issue I'm having is that in the case of a named template, I don't know how to select a different node and apply it to the named template.</para>

    </section>

</testfile>

我正在尝试这个模板:

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

<!-- stylesheet to test a named template trying to build an xpath for a given node -->
<xsl:output method="xml"/>
<xsl:template match="/">
    <result>
        <xsl:apply-templates/>
    </result>
</xsl:template>

<xsl:template match="*">
    <xsl:variable name="xpath">
        <xsl:call-template name="getXpath">
            <xsl:with-param name="pathText" select="''"/>
        </xsl:call-template>
    </xsl:variable>

    <element>element name : <xsl:value-of select="name()"/> path : <xsl:value-of select="$xpath"/></element>
    <xsl:apply-templates/>

</xsl:template>



<xsl:template name="getXpath">
<xsl:param name="pathText"/>

<xsl:message>top of get xpath func path text : <xsl:value-of select="$pathText"/>    </xsl:message>
    <xsl:choose>
        <xsl:when test="ancestor::*">
            <xsl:message><xsl:value-of select="name()"/> has a parent</xsl:message>
            <xsl:call-template name="getXpath">
                <xsl:with-param name="pathText">
                    <xsl:value-of select="name()"/>    <xsl:text>/</xsl:text><xsl:value-of select="$pathText"/>
                    <!-- how to recursively call template with parent node? -->
                </xsl:with-param>   
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:message><xsl:value-of select="name()"/> has no parent!</xsl:message>
            <xsl:value-of select="$pathText"/>
        </xsl:otherwise>
    </xsl:choose>   

</xsl:template>


</xsl:stylesheet>

根据评论,我不确定如何将上下文节点以外的节点应用于命名模板。我尝试的另一个策略是将节点作为参数发送到模板,但我不知道如何(或者如果可以)将轴应用于参数,如

$thisNode../*

等。

我确信这很简单,我很想念......谢谢。

4 个答案:

答案 0 :(得分:2)

您确实可以将节点作为参数传递给模板....

<xsl:template name="getXpath">
<xsl:param name="pathText"/>
<xsl:param name="node" select="." />

要对其应用轴,例如测试祖先,你可以这样做....

<xsl:when test="$node/ancestor::*">

要在递归调用它时将其父元素传递给模板,请执行以下操作:

<xsl:with-param name="node" select="$node/parent::*" />

试试这个XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:template match="/">
    <result>
        <xsl:apply-templates/>
    </result>
</xsl:template>

<xsl:template match="*">
    <xsl:variable name="xpath">
        <xsl:call-template name="getXpath">
            <xsl:with-param name="pathText" select="''"/>
        </xsl:call-template>
    </xsl:variable>

    <element>element name : <xsl:value-of select="name()"/> path : <xsl:value-of select="$xpath"/></element>
    <xsl:apply-templates/>
</xsl:template>

<xsl:template name="getXpath">
<xsl:param name="pathText"/>
<xsl:param name="node" select="." />

<xsl:message>top of get xpath func path text : <xsl:value-of select="$pathText"/>    </xsl:message>
    <xsl:choose>
        <xsl:when test="$node/ancestor::*">
            <xsl:message><xsl:value-of select="name($node)"/> has a parent</xsl:message>
            <xsl:call-template name="getXpath">
                <xsl:with-param name="pathText">
                    <xsl:value-of select="name($node)"/>    <xsl:text>/</xsl:text><xsl:value-of select="$pathText"/>
                </xsl:with-param>   
                <xsl:with-param name="node" select="$node/parent::*" />
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:message><xsl:value-of select="name($node)"/> has no parent!</xsl:message>
            <xsl:value-of select="$pathText"/>
        </xsl:otherwise>
    </xsl:choose>   
</xsl:template>
</xsl:stylesheet>

另一种方法是使用 xsl:apply-templates ,但使用模式参数可以将其与其他模板匹配分开。试试这个XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:template match="/">
    <result>
        <xsl:apply-templates/>
    </result>
</xsl:template>

<xsl:template match="*">
    <xsl:variable name="xpath">
        <xsl:apply-templates select="." mode="getXpath">
            <xsl:with-param name="pathText" select="''"/>
        </xsl:apply-templates>
    </xsl:variable>

    <element>element name : <xsl:value-of select="name()"/> path : <xsl:value-of select="$xpath"/></element>
    <xsl:apply-templates/>
</xsl:template>

<xsl:template match="*" mode="getXpath">
<xsl:param name="pathText"/>
<xsl:message>top of get xpath func path text : <xsl:value-of select="$pathText"/>    </xsl:message>
    <xsl:choose>
        <xsl:when test="ancestor::*">
            <xsl:message><xsl:value-of select="name()"/> has a parent</xsl:message>
            <xsl:apply-templates select=".." mode="getXpath">
                <xsl:with-param name="pathText">
                    <xsl:value-of select="name()"/>    <xsl:text>/</xsl:text><xsl:value-of select="$pathText"/>
                </xsl:with-param>   
            </xsl:apply-templates>
        </xsl:when>
        <xsl:otherwise>
            <xsl:message><xsl:value-of select="name()"/> has no parent!</xsl:message>
            <xsl:value-of select="$pathText"/>
        </xsl:otherwise>
    </xsl:choose>   
</xsl:template>
</xsl:stylesheet>

答案 1 :(得分:1)

如果只执行xsl:for-each,则不必将节点作为参数传递。

这是您的XSLT的修改示例。 (请注意,只有在需要时才会在路径中输出位置谓词。)

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

    <!-- stylesheet to test a named template trying to build an xpath for a given node -->
    <xsl:output method="xml" indent="yes"/>
    <xsl:template match="/">
        <result>
            <xsl:apply-templates/>
        </result>
    </xsl:template>

    <xsl:template match="*">
        <xsl:variable name="xpath">
            <xsl:call-template name="getXpath"/>            
        </xsl:variable>        
        <element>element name : <xsl:value-of select="name()"/> path : <xsl:value-of select="$xpath"/></element>
        <xsl:apply-templates/>        
    </xsl:template>    

    <xsl:template name="getXpath"> 
        <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:template>

    <xsl:template match="text()"/>

</xsl:stylesheet>

输出(使用问题中的输入)

<result>
   <element>element name : testfile path : /testfile</element>
   <element>element name : section path : /testfile/section</element>
   <element>element name : title path : /testfile/section/title</element>
   <element>element name : para path : /testfile/section/para</element>
</result>

答案 2 :(得分:1)

对于它的价值,我在大约十年前写了一个简单的XPath生成模板,在my "styling stylesheets" article on DeveloperWorks的第2部分:

Listing 4. Template that generates a Pseudo XPath in XSLT

<xsl:template name="pseudo-xpath-to-current-node">
   <!-- Special-case for the root node, which otherwise
       wouldn't generate any path at all. A bit of a kluge,
       but it's simple and efficient. -->
   <xsl:if test="not(parent::node())">
            <xsl:text>/</xsl:text>
   </xsl:if>

   <xsl:for-each select="ancestor-or-self::node()">
     <xsl:choose>
       <xsl:when test="not(parent::node())">
            <!-- This clause recognizes the root node, which doesn't need
                 to be explicitly represented in the XPath. -->
       </xsl:when>
       <xsl:when test="self::text()">
            <xsl:text>/text()[</xsl:text>
            <xsl:number level="single"/>
            <xsl:text>]</xsl:text>
       </xsl:when>
       <xsl:when test="self::comment()">
            <xsl:text>/comment()[</xsl:text>
            <xsl:number level="single"/>
            <xsl:text>]</xsl:text>
       </xsl:when>
       <xsl:when test="self::processing-instruction()">
            <xsl:text>/processing-instruction()[</xsl:text>
            <xsl:number level="single"/>
            <xsl:text>]</xsl:text>
       </xsl:when>
       <xsl:when test="self::*">
            <!-- This test for Elements works because the Principal
                 Node Type of the self:: axis happens to be Element.
                         -->
            <xsl:text>/</xsl:text>
            <xsl:value-of select="name(.)"/>
            <xsl:text>[</xsl:text>
            <xsl:number level="single"/>
            <xsl:text>]</xsl:text>
       </xsl:when>
       <xsl:when test="self::node()[name()='xmlns' | starts-with(name(),'xmlns:')]">
            <!-- This recognizes namespace nodes, though it's a bit
                 ugly.  XSLT 1.0 doesn't seem to have a more elegant
                 test. XSLT 2.0 is expected to deprecate the whole
                 concept of namespace nodes, so it may become a moot
                 point.

                 NS nodes are unique; a count isn't required.  -->
            <xsl:text>/namespace::</xsl:text>
            <xsl:value-of select="local-name(.)"/>
       </xsl:when>
       <xsl:otherwise>
            <!-- If I've reached this clause, the node must be an
                 attribute.  Attributes are unique; a count is not
                 required.  -->
            <xsl:text>/@</xsl:text>
            <xsl:value-of select="name(.)"/>
       </xsl:otherwise>
     </xsl:choose>
   </xsl:for-each>
</xsl:template>

这是一个XSLT 1.0解决方案,结构清晰。它可能是简化它,特别是如果你使用的是XSLT和XPath 2.0。

正如我在那里解释的那样,这个“伪XPath”版本忽略了命名空间问题,因为我不需要它用于概念验证工具,因为它是用于人类可读的消息而不是用于执行。通过将其更改为写出指定节点类型的路径并使用显式测试localname和namespace URI的谓词,可以更正正确地管理命名空间。由此产生的路径将变得更加庞大,人类难以处理。如果您愿意,请为读者练习。

你也可以用更具表现力的东西取代位置指数......但是知道什么是有意义的并不容易。

希望有所帮助。玩得开心。

(哦,差点忘了:如果XSLT FAQ网站上有其他解决方案,我不会感到惊讶。)

答案 3 :(得分:0)

我想你想要这样的东西:

<xsl:variable name="get.path">
    <xsl:text> /</xsl:text>
    <xsl:for-each select="ancestor-or-self::*">
        <xsl:variable name="get.current.node" select="name(.)"/>
        <xsl:value-of select="name()"/>
        <xsl:text>[</xsl:text>
        <xsl:value-of select="count(preceding-sibling::*[name(.) = $get.current.node]) + 1"/>
        <xsl:text>]</xsl:text>
        <xsl:if test="position() != last()">
            <xsl:text>/</xsl:text>
        </xsl:if>
    </xsl:for-each>
</xsl:variable>