xslt recursive template on parent-child data

倾然丶 夕夏残阳落幕 提交于 2020-01-03 17:44:28

问题


I'm trying to wrap my mind around xslt. A number of questions here on stackoverflow help ( XSLT templates and recursion and XSLT for-each loop, filter based on variable ) but I'm still kinda puzzled. I guess I'm "thinking of template as functions" ( https://stackoverflow.com/questions/506348/how-do-i-know-my-xsl-is-efficient-and-beautiful )

Anyway...my data is

<Entities>
    <Entity ID="8" SortValue="0" Name="test" ParentID="0" />
    <Entity ID="14" SortValue="2" Name="test2" ParentID="8" />
    <Entity ID="16" SortValue="1" Name="test3" ParentID="8" />
    <Entity ID="17" SortValue="3" Name="test4" ParentID="14" />
    <Entity ID="18" SortValue="3" Name="test5" ParentID="0" />
</Entities>

What I'd like as output is basically a "treeview"

<ul>
    <li id="entity8">
        test
        <ul>
            <li id="entity16">
                test3
            </li>
            <li id="entity14">
                test2
                <ul>
                    <li id="entity17">
                        test4
                    </li>
                </ul>
            </li>
        </ul>
    </li>
    <li id="entity18">
        test5
    </li>
</ul>

The XSLT I have so far is wrong in that it definitely "thinks of templates as functions" and also throws a StackOverflowException (:-)) on execution

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
    <xsl:output method="html" indent="yes"/>

    <xsl:template match="Entities">
        <ul>
            <li>
                <xsl:value-of select="local-name()"/>
                <xsl:apply-templates/>
            </li>
        </ul>
    </xsl:template>

    <xsl:template match="//Entities/Entity[@ParentID=0]">
        <xsl:call-template name="recursive">
            <xsl:with-param name="parentEntityID" select="0"></xsl:with-param>
        </xsl:call-template>
     </xsl:template>

    <xsl:template name="recursive">
        <xsl:param name="parentEntityID"></xsl:param>
        <xsl:variable name="counter" select="//Entities/Entity[@ParentID=$parentEntityID]"></xsl:variable>

        <xsl:if test="count($counter) > 0">
            <xsl:if test="$parentEntityID > 0">
            </xsl:if>
                <li>
                    <xsl:variable name="entityID" select="@ID"></xsl:variable>
                    <xsl:variable name="sortValue" select="@SortValue"></xsl:variable>
                    <xsl:variable name="name" select="@Name"></xsl:variable>
                    <xsl:variable name="parentID" select="@ParentID"></xsl:variable>                    

                    <a href=?ID={$entityID}&amp;ParentEntityID={$parentID}">
                        <xsl:value-of select="$name"/>
                    </a>

                    <xsl:call-template name="recursive">
                        <xsl:with-param name="parentEntityID" select="$entityID"></xsl:with-param>
                    </xsl:call-template>

                </li>           
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

I know how to do this by code, no problem. This time, though, I'm looking for a solution in xslt and for that any help would be greatly appreciated.


回答1:


Although call-template and named templates are a very useful feature of the language, if you find yourself preferring them to apply-templates it may be a sign that you are still thinking in functions rather than templates. This is particularly true if the first thing you do in a named template is select a nodeset on which to operate.

Here is a simple version of what you are trying to do.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
    <xsl:template match="/">
        <ul>
           <xsl:apply-templates select="Entities/Entity[@ParentID=0]" />
        </ul>
    </xsl:template>

    <xsl:template match="Entity">
        <li>
           <xsl:value-of select="@Name" />
           <xsl:apply-templates select="../Entity[@ParentID=current()/@ID]" />
        </li>
    </xsl:template>
</xsl:stylesheet>

Note that there is no need for the counter, as the value of the "parent" provides the necessary context.

Also note that all Entities behave in the same way, regardless of where they are in the tree, they contain their @Name value, and apply the template to any Entity objects whose @ParentID matches the @ID of the current level.




回答2:


A solution that is correct and efficient (the currently accepted answer doesn't produce the wanted nested ul elements:

<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:key name="kChildren" match="Entity" use="@ParentID"/>

 <xsl:template match="/*[Entity]">
     <ul>
       <xsl:apply-templates select="key('kChildren', '0')">
            <xsl:sort select="@SortValue" data-type="number"/>
       </xsl:apply-templates>
     </ul>
 </xsl:template>

 <xsl:template match="Entity">
   <li id="entity{@ID}">
      <xsl:value-of select="concat('&#xA;               ', @Name, '&#xA;')"/>
      <xsl:if test="key('kChildren', @ID)">
          <ul>
            <xsl:apply-templates select="key('kChildren', @ID)">
              <xsl:sort select="@SortValue" data-type="number"/>
            </xsl:apply-templates>
          </ul>
      </xsl:if>
   </li>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the provided XML document:

<Entities>
    <Entity ID="8" SortValue="0" Name="test" ParentID="0" />
    <Entity ID="14" SortValue="2" Name="test2" ParentID="8" />
    <Entity ID="16" SortValue="1" Name="test3" ParentID="8" />
    <Entity ID="17" SortValue="3" Name="test4" ParentID="14" />
    <Entity ID="18" SortValue="3" Name="test5" ParentID="0" />
</Entities>

the wanted, correct result is produced:

<ul>
   <li id="entity8">
               test
<ul>
         <li id="entity16">
               test3
</li>
         <li id="entity14">
               test2
<ul>
               <li id="entity17">
               test4
</li>
            </ul>
         </li>
      </ul>
   </li>
   <li id="entity18">
               test5
</li>
</ul>


来源:https://stackoverflow.com/questions/13644680/xslt-recursive-template-on-parent-child-data

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!