简体   繁体   中英

XSL 1.0 Creating html ul-li tree from xpaths, where data is in xml attributes as text delimited by '/'

I'm trying to produce a tree of data that looks like this:

  • root
    • 1stGenChild1
      • 2ndGenChild1
      • 2ndGenChild2
    • 1stGenChild2

and so produce the code as follows:

<ul>
  <li>root</li>
  <ul>
    <li>1stGenChild1</li>
  <ul>
    <li>2ndGenChild1</li>
    <li>2ndGenChild2</li>
  </ul>
  <li>1stGenChild2</li>
</ul>

where my data is in the form:

<XML_FILTER>
  <XPATH @xpath="root/1stGenChild1" />
  <XPATH @xpath="root/1stGenChild1/2ndGenChild1" />
  <XPATH @xpath="root/1stGenChild1/2ndGenChild2" />
  <XPATH @xpath="root/1stGenChild2" />
</XML_FILTER>

It would be relatively simple to produce this in XSLT2 with tokenise, but I cannot use XSLT2 for this as I am restricted to using only MSXML 6.0 by the system in use.

The biggest problem I've found is that the normal methods of doing this can't deal with the root never being explicitly stated in its own attribute, but I still need this node in the output.

How can I produce a tree for data which may have more child node levels? - ie. many more lists within lists than the example shown above.

Also does anyone know if there is a limit to the number of lists within lists before indentation is not rendered by browsers, as this will make the view useless.

Many thanks.

Here is a simple method for nesting the nodes according to their hierarchy:

XSLT 1.0

<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:strip-space elements="*"/>

<xsl:template match="/XML_FILTER">
    <ul>
        <xsl:apply-templates select="XPATH[not(contains(@xpath, '/'))]"/>
    </ul>
</xsl:template>

<xsl:template match="XPATH">
    <xsl:variable name="dir" select="concat(@xpath, '/')" />
    <li>
        <xsl:value-of select="@xpath"/>
    </li>   
    <xsl:variable name="child" select="../XPATH[starts-with(@xpath, $dir) and not(contains(substring-after(@xpath, $dir), '/'))]" />
    <xsl:if test="$child">
        <ul>
            <xsl:apply-templates select="$child"/>
        </ul>           
    </xsl:if>
</xsl:template>

</xsl:stylesheet> 

Applied to your example input (after removing the illegal @ characters from the attribute's name!), the result will be:

<?xml version="1.0" encoding="UTF-8"?>
<ul>
  <li>root</li>
  <ul>
    <li>root/1stGenChild1</li>
    <ul>
      <li>root/1stGenChild1/2ndGenChild1</li>
      <li>root/1stGenChild1/2ndGenChild2</li>
    </ul>
    <li>root/1stGenChild2</li>
  </ul>
</ul>

Now you only need to replace the:

<xsl:value-of select="@xpath"/>

instruction with a call to a named template that returns the last token - see: https://stackoverflow.com/a/41625340/3016153


Or do this instead:

XSLT 1.0

<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:strip-space elements="*"/>

<xsl:template match="/XML_FILTER">
    <ul>
        <xsl:apply-templates select="XPATH[not(contains(@xpath, '/'))]"/>
    </ul>
</xsl:template>

<xsl:template match="XPATH">
    <xsl:param name="parent"/>
    <xsl:variable name="dir" select="concat(@xpath, '/')" />
    <li>
        <xsl:value-of select="substring-after(concat('/', @xpath), concat($parent, '/'))"/>
    </li>   
    <xsl:variable name="child" select="../XPATH[starts-with(@xpath, $dir) and not(contains(substring-after(@xpath, $dir), '/'))]" />
    <xsl:if test="$child">
        <ul>
            <xsl:apply-templates select="$child">
                <xsl:with-param name="parent" select="concat('/', @xpath)"/>
            </xsl:apply-templates>
        </ul>           
    </xsl:if>
</xsl:template>

</xsl:stylesheet> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM