Saturday, November 16, 2013

Two-level hierarchy in XSLT

Assume I have this XML

<?xml version="1.0" encoding="utf-8"?>
<Root>
  <Level1>
    <Level1Val level1="Animal" />
    <Level1Val level1="Vegetable" />
    <Level1Val level1="Mineral" />
  </Level1>

  <Level3>
    <Level3Val level1="Animal" level2="Cat" level3="Puma"/>
    <Level3Val level1="Animal" level2="Cat" level3="Tiger"/>
    <Level3Val level1="Animal" level2="Cat" level3="Lion"/>
    <Level3Val level1="Animal" level2="Bear" level3="Black Bear"/>
    <Level3Val level1="Animal" level2="Bear" level3="Brown Bear"/>
    <Level3Val level1="Animal" level2="Bear" level3="Panda Bear"/>

    <Level3Val level1="Vegetable" level2="Fruit" level3="Apple"/>
    <Level3Val level1="Vegetable" level2="Fruit" level3="Peach"/>
    <Level3Val level1="Vegetable" level2="Fruit" level3="Banana"/>
    <Level3Val level1="Vegetable" level2="Green" level3="Celery"/>
    <Level3Val level1="Vegetable" level2="Green" level3="Broccoli"/>
    <Level3Val level1="Vegetable" level2="Green" level3="Kale"/>

    <Level3Val level1="Mineral" level2="Liquid" level3="Oil"/>
    <Level3Val level1="Mineral" level2="Liquid" level3="Kerosene"/>
    <Level3Val level1="Mineral" level2="Liquid" level3="Diesel"/>
    <Level3Val level1="Mineral" level2="Solid" level3="Quartz"/>
    <Level3Val level1="Mineral" level2="Solid" level3="Feldspar"/>
    <Level3Val level1="Mineral" level2="Solid" level3="Garnet"/>
  </Level3>
</Root>

And I want to display the results in a three-level hierarchy. The following XSLT pattern is what I used.

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

  <xsl:output method="html" encoding="UTF-8" omit-xml-declaration="yes" />

  <xsl:key name="Level1PlusLevel2" match="/Root/Level3/Level3Val" use="concat(@level1,'+',level2)" />
  <xsl:key name="Level2Type" match="Level3/Level3Val" use="@level2"/>

  <xsl:variable name="Level1Vals" select="/Root/Level1/Level1Val" />
  <xsl:variable name="Level2Vals" select="Root/Level3/Level3Val[generate-id()=generate-id(key('Level2Type', @level2)[1])]" />

  <xsl:template match="/Root">
    <html>
      <xsl:apply-templates select="$Level1Vals" />
    </html>
  </xsl:template>

  <xsl:template match="Level1Val">
    <!-- output info about Level 1 -->
    <div class="level1">
      <p>level 1: <xsl:value-of select="@level1" /></p>
      <xsl:apply-templates select="$Level2Vals" mode="Level2">
        <xsl:with-param name="level1_id" select="@level1"/>
      </xsl:apply-templates>
    </div>
  </xsl:template>

  <xsl:template match="Level3Val" mode="Level2">
    <xsl:param name="level1_id" />
    <!-- output info about Level 2 -->
    <div class="level2">
      <p>level 2: <xsl:value-of select="@level2" /></p>
      <xsl:apply-templates select="key('Level1PlusLevel2',concat($level1_id,'+',@level2))" />
    </div>
  </xsl:template>

  <xsl:template match="Level3Val">
    <!-- output info about Level 3 -->
    <div class="level3">
      <p>level 3: <xsl:value-of select="@level3" /></p>
    </div>
  </xsl:template>

</xsl:stylesheet>

Note that a more common XML input would probably have a single table of Level2 elements. But in the case I just coded for, there was a separate list of Level1 elements.

I did this sort of from memory and there may be errors. If you see an obvious error please do point it out to me.

No comments:

Post a Comment