开发者

Listing or counting paths from current node to each leaf node that have some property

开发者 https://www.devze.com 2023-02-22 15:43 出处:网络
Is here some way using XSLT to list or count paths from current node to each one leaf node based on some criteria.

Is here some way using XSLT to list or count paths from current node to each one leaf node based on some criteria. for e.g. in specific case here suppose current nod开发者_Go百科e is "t" and paths from current node to each leaf node that do not have "trg" attribute. In below e.g.

<root>
  <t>
    <a1>
     <b1 trg="rr">
       <c1></c1>
     </b1>
     <b2>
       <c2></c2>
     </b2>
    </a1>
    <a2>
      <b3>
        <c3></c3>
      </b3>
    </a2>
  </t>
</root>

here is only path with this property t/a1/b2/c2 and t/a2/b3/c3

but not t/a1/b1/c1


The easiest approach would be:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="text()"/>
    <xsl:template match="text()" mode="search"/>
    <xsl:template match="t">
        <xsl:apply-templates mode="search">
            <xsl:with-param name="pPath" select="name()"/>
        </xsl:apply-templates>
    </xsl:template>
    <xsl:template match="*" mode="search">
        <xsl:param name="pPath"/>
        <xsl:apply-templates mode="search">
            <xsl:with-param name="pPath" select="concat($pPath,'/',name())"/>
        </xsl:apply-templates>
    </xsl:template>
    <xsl:template match="*[not(*)]" mode="search">
        <xsl:param name="pPath"/>
        <xsl:value-of select="concat($pPath,'/',name(),'&#xA;')"/>
    </xsl:template>
    <xsl:template match="*[@trg]" mode="search" priority="1"/>
</xsl:stylesheet>

Output:

t/a1/b2/c2
t/a2/b3/c3

Note: Complete pull style. A rule for path search beginning (t patttern). A rule for tunneling parameter (* pattern). A rule for outputing leafs' path (*[not(*)] pattern). A rule for recursion breaking condition (*[@trg] pattern).


I can think of a way to achieve this, but it doesn't look nice and so hopefully someone will come up with a better way...

Firstly, I defined a variable with the level of the current node

<xsl:variable name="level">
   <xsl:value-of select="count(ancestor::*)"/>
</xsl:variable>

Next, match all descendents or the current node that happen to be leaf nodes, and which don't have any ancestors (including itself, but only as far as the current level) with the attribute @trg = 'rr' as specified.

<xsl:apply-templates select=".//*[not(node())][not(ancestor-or-self::*[count(ancestor::*) &gt; $level][@trg='rr'])]">
   <xsl:with-param name="currentLevel" select="$level"/>
</xsl:apply-templates>

Having matched the leaf nodes, you should be able to get the path of parent nodes back up to the current node.

Here is the complete stylesheet

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

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

   <xsl:template match="t">

      <!-- The level of the current node -->
      <xsl:variable name="level">
         <xsl:value-of select="count(ancestor::*)"/>
      </xsl:variable>

      <paths>
         <!-- Match all leaf nodes where there is not an ancestor (up to the current node) with the attribute @trg = 'rr' -->
         <xsl:apply-templates select=".//*[not(node())][not(ancestor-or-self::*[count(ancestor::*) &gt; $level][@trg=&apos;rr&apos;])]">
            <xsl:with-param name="currentLevel" select="$level"/>
         </xsl:apply-templates>
      </paths>
   </xsl:template>

   <!-- For leaf nodes, write out the ancestor path to a specified level -->
   <xsl:template match="*[not(node())]">
      <xsl:param name="currentLevel"/>
      <path>
         <xsl:for-each select="ancestor::*[count(ancestor::*) &gt;= $currentLevel]">
            <xsl:value-of select="name()"/>
            <xsl:text>/</xsl:text>
         </xsl:for-each>
         <xsl:value-of select="name()"/>
      </path>
   </xsl:template>

</xsl:stylesheet>

When applied on the given XML, the following results are returned

<paths>
   <path>t/a1/b2/c2</path>
   <path>t/a2/b3/c3</path>
</paths>


This problem is best solved with XSLT 2.0 / XPath 2.0

A very initial XSLT 1.0 attempt:

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

 <xsl:template match="*[not(*)]">
   <xsl:call-template name="buildPath">
    <xsl:with-param name="pstartNode" select="/*/t"/>
   </xsl:call-template>
 </xsl:template>

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

 <xsl:template name="buildPath">
  <xsl:param name="pstartNode" select="."/>
  <xsl:param name="pendNode" select="."/>

  <xsl:choose>
   <xsl:when test=
    "not(count($pstartNode | $pendNode/ancestor-or-self::node())
        =
         count($pendNode/ancestor-or-self::node())
         )
    ">
   </xsl:when>
   <xsl:otherwise>
    <xsl:variable name="vPath" select=
      "ancestor::*
          [count(.|$pstartNode/descendant-or-self::*)
          =
           count($pstartNode/descendant-or-self::*)
          ]
      "/>
     <xsl:if test="not($vPath[@trg])">
      <xsl:for-each select=
        "ancestor::*
          [count(.|$pstartNode/descendant-or-self::*)
          =
           count($pstartNode/descendant-or-self::*)
          ]">
        <xsl:value-of select="concat(name(),'/')"/>
      </xsl:for-each>
      <xsl:value-of select="concat(name(), '&#xA;')"/>
     </xsl:if>
   </xsl:otherwise>
  </xsl:choose>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML document:

<root>
    <t>
        <a1>
            <b1 trg="rr">
                <c1></c1>
            </b1>
            <b2>
                <c2></c2>
            </b2>
        </a1>
        <a2>
            <b3>
                <c3></c3>
            </b3>
        </a2>
    </t>
</root>

produces the wanted, correct result:

t/a1/b2/c2
t/a2/b3/c3


Here is the stylesheet:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <paths>
            <xsl:apply-templates select="root/t">
                <xsl:with-param name="path"/>
            </xsl:apply-templates>
        </paths>
    </xsl:template>

    <xsl:template name="path">
        <xsl:param name="path"/>
        <xsl:choose>
            <xsl:when test="string-length($path)">
                <xsl:value-of select="$path"/>/<xsl:value-of select="name()"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="name()"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

    <xsl:template match="*">
        <xsl:param name="path"/>
        <xsl:if test="not(@trg)">
            <xsl:if test="not(count(*))">
                <path>
                    <xsl:call-template name="path">
                        <xsl:with-param name="path">
                            <xsl:value-of select="$path"/>
                        </xsl:with-param>
                    </xsl:call-template>
                </path>
            </xsl:if>
            <xsl:apply-templates select="*">
                <xsl:with-param name="path">
                    <xsl:call-template name="path">
                        <xsl:with-param name="path">
                            <xsl:value-of select="$path"/>
                        </xsl:with-param>
                    </xsl:call-template>
                </xsl:with-param>
            </xsl:apply-templates>
        </xsl:if>
    </xsl:template>

</xsl:stylesheet>

The idea is very simple - the main processing template (math="*") is the recursive function applicable to every node. It has one parameter - path which is the actual path to this node from the search root node (t). Once node doesn't have children - it could be output.

Another template named path is a simple helper function to generate the path properly. If you could register an XPath function in your XSLT processor I'd rather do that and get rid of verbose call-template constructions. The whole call-template ... call could be the simple <xsl:value-of select="myfn:path($path)"/> which would greatly increase the readability and that's very important for the XSLT ;)

Anyway, I feel that there are too many path words in my explanation :), so don't hesitate to ask if something is not clear.

0

精彩评论

暂无评论...
验证码 换一张
取 消