2

This is a sample of the raw XML I'm working with:

<dsQueryResponse>
  <Rows>
    <Row Title="Animal" Parent="" />
    <Row Title="Mammal" Parent="Animal" />
    <Row Title="Lion" Parent="Mammal" />
    <Row Title="Plant" Parent="" />
    <Row Title="Elephant" Parent="Mammal" />
  </Rows>
</dsQueryResponse>

Using XSLT, how do I get the output to be a nested UL like:

<ul>
  <li>
    Animal
    <ul>
      <li>
        Mammal
        <ul>
          <li>Elephant</li>
          <li>Lion</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>Plant</li>
</ul>

I'm only "okay" with XSLT and can only do simple sorting, and I know I can do this easily through JavaScript/jQuery, but I'd rather use XSLT for this.

3 Answers 3

3

Try this XSLT:

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

  <xsl:template match="/">
    <ul>
      <xsl:apply-templates select="//Row[@Parent = '']"/>
    </ul>
  </xsl:template>

  <xsl:template match="Row">
    <li>
      <xsl:value-of select="@Title"/>

      <xsl:if test="../Row[@Parent = current()/@Title]">
        <ul>
          <xsl:apply-templates select="../Row[@Parent = current()/@Title]"/>
        </ul>
      </xsl:if>
    </li>
  </xsl:template>
</xsl:stylesheet>

Output:

<ul>
  <li>
    Animal<ul>
      <li>
        Mammal<ul>
          <li>Lion</li>
          <li>Elephant</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>Plant</li>
</ul>
Sign up to request clarification or add additional context in comments.

1 Comment

Such an elegant solution!
3

The "elegant" (and efficient!) solution here is to use a key to retrieve "related" records:

XSLT 1.0

<?xml version="1.0" encoding="utf-8"?>
<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:key name="row-by-parent" match="Row" use="@Parent" />

<xsl:template match="/">
    <ul>
        <xsl:apply-templates select="dsQueryResponse/Rows/Row[not(string(@Parent))]"/>
    </ul>
</xsl:template>

<xsl:template match="Row">
    <li>
        <xsl:value-of select="@Title"/>
        <xsl:variable name="child-rows" select="key('row-by-parent', @Title)" />
        <xsl:if test="$child-rows">
            <ul>
                <xsl:apply-templates select="$child-rows"/>
            </ul>
        </xsl:if>
    </li>
</xsl:template>

</xsl:stylesheet> 

Comments

0

Interesting challenge.

This xsl gets an html document which is what I think you want. It's not formatted pretty though. The matching template starts the process searching for nodes with no parent. It then calls a template for that type. That template recursively calls itself to iterate the child types.

I used the command line app msxsl to test it. YMMV

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

  <xsl:template name="CategoryTemplate">
    <xsl:param name="CategoryName"/>

    <ul>
      <xsl:for-each select="/*[local-name()='dsQueryResponse']/*[local-name()='Rows']/*[local-name()='Row' and @Parent=$CategoryName]">
        <li>
          <xsl:value-of select="@Title"/>

          <!--recursively call template with category-->
          <xsl:call-template name="CategoryTemplate">
            <xsl:with-param name="CategoryName" select="@Title"/>
          </xsl:call-template>
        </li>
      </xsl:for-each>
    </ul>

  </xsl:template>

  <xsl:template match="/*[local-name()='dsQueryResponse']/*[local-name()='Rows']">

    <xsl:variable name="CategoryName">
      <xsl:text></xsl:text>
    </xsl:variable>

    <html>
      <body>
        <xsl:call-template name="CategoryTemplate">
          <xsl:with-param name="CategoryName" select="$CategoryName"/>
        </xsl:call-template>
      </body>
    </html>
  </xsl:template>

</xsl:stylesheet>

4 Comments

XSLT has a built-in recursion mechanism with the xsl:apply-templates instruction. There's no need to work so hard to replicate it.
This one has issues where tags aren't closed off in the right places. Tested using xslttest.appspot.com
Interesting that it runs in the msxsl command line app with tags that aren't closed
@michael.hor257k's solution seems far better than this one.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.