Dharam Thakkar
Dharam Thakkar

Reputation: 89

xml to xml using xslt : Recursive matching & creating hierarchy like tree

I am completely new to xslt. Please help me to write style sheet.I have input xml like this

Input XML:

    <elements>
     <e1>
       <pid>1</pid>
       <cid>2</cid>
     </e1>

     <e1>
      <pid>1</pid>
      <cid>3</cid>
     </e1>

     <e1>
      <pid>2</pid>
      <cid>4</cid>
    </e1>
    </elements>

Desired XML:

    <tree>
      <unit id="1">
        <unit id="2">
           <unit id="4">
             <data></data>
           </unit>
           <data></data>
        </unit>

        <unit id="3">
           <data></data>
        </unit>

        <data></data>

      </unit>
    </tree>

I feel this should be really easy but I'm struggling to find information about how to do this. My XSLT knowledge isn't great.

Upvotes: 1

Views: 1527

Answers (2)

Michael Kay
Michael Kay

Reputation: 163458

Ah, after reading JLRishe I think I get it: "pid" means "parent ID", "cid" means "child ID", and e1 represents a parent-child relationship. Brilliant detective work, I would never have worked that out for myself.

The basic model is that when you are positioned on a parent element you do apply-templates to its children. This applies just as well if the parent/child relationships are represented by primary/foreign keys as when they are represented using the XML hierarchy. So the essence is:

<xsl:template match="e1">
  <unit id="{pid}">
    <xsl:apply-templates select="//e1[pid=current()/cid]"/>
    <data/>
  </unit>
</xsl:template>

which is essentially JLRishe's solution except he has added an optimization using keys.

Upvotes: 0

JLRishe
JLRishe

Reputation: 101730

I'm not 100% sure how you want the XSLT to determine from that input that the top id is 1 (is it because it's the only pid value with no corresponding cid values, or is it always 1?). Nonetheless, this should do the job:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>
  <xsl:key name="kItemsByC" match="e1" use="cid" />
  <xsl:key name="kItemsByP" match="e1" use="pid" />

  <xsl:template match="/">
    <tree>
      <xsl:call-template name="Unit">
        <!-- This will be the value of the <pid> that has no <cid> references to
             it (assuming there is only one top-level <pid>) -->
        <xsl:with-param name="id" 
                        select="string(/elements/e1/pid[not(key('kItemsByC', .))])" />
      </xsl:call-template>
    </tree>
  </xsl:template>

  <xsl:template match="e1" name="Unit">
    <xsl:param name="id" select="cid" />

    <unit id="{$id}">
      <xsl:apply-templates select="key('kItemsByP', $id)" />
      <data />
    </unit>
  </xsl:template>
</xsl:stylesheet>

When this is run on your sample input, this produces:

<tree>
  <unit id="1">
    <unit id="2">
      <unit id="4">
        <data />
      </unit>
      <data />
    </unit>
    <unit id="3">
      <data />
    </unit>
    <data />
  </unit>
</tree>

Note: The above XSLT has logic to attempt to dynamically locate the top-level ID. If it can be assumed that the top-level unit will always have ID 1, then one key and the above XSLT's (somewhat) complicated formula can be eliminated:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" indent="yes"/>
  <xsl:key name="kItemsByP" match="e1" use="pid" />

  <xsl:template match="/">
    <tree>
      <xsl:call-template name="Unit">
        <xsl:with-param name="id" select="1" />
      </xsl:call-template>
    </tree>
  </xsl:template>

  <xsl:template match="e1" name="Unit">
    <xsl:param name="id" select="cid" />

    <unit id="{$id}">
      <xsl:apply-templates select="key('kItemsByP', $id)" />
      <data />
    </unit>
  </xsl:template>
</xsl:stylesheet>

This also produces the requested output when run on your sample input.

Upvotes: 2

Related Questions