[xsl] Re: Transforming non structured XML into structured XML with XSLT

Subject: [xsl] Re: Transforming non structured XML into structured XML with XSLT
From: Dimitre Novatchev <dnovatchev@xxxxxxxxx>
Date: Wed, 8 Aug 2001 08:13:26 -0700 (PDT)
bruno.o.faure@xxxxxxxxxxxxxx wrote:

> I'm trying to write a XSLT style sheet that transforms a non-structured xml
> document to a structured xml document and am in troubles :
> 
> My xml documents looks like a flat modelization of a treeview :
> 
> <row lib="data1" folder1="A" folder2="C" />
> <row lib="data2" folder1="A" folder2="C" />
> <row lib="data3" folder1="A" folder2="C" />
> <row lib="data1" folder1="A" folder2="D" />
> <row lib="data2" folder1="A" folder2="D" />
> <row lib="data3" folder1="A" folder2="D" />
> <row lib="data1" folder1="B" folder2="E" />
> <row lib="data2" folder1="B" folder2="E" />
> <row lib="data3" folder1="B" folder2="E" />
> <row lib="data1" folder1="B" folder2="F" />
> <row lib="data2" folder1="B" folder2="F" />
> <row lib="data3" folder1="B" folder2="F" />
> <row lib="data1" folder1="B" folder2="G" />
> <row lib="data2" folder1="B" folder2="G" />
> <row lib="data3" folder1="B" folder2="G" />
> <row lib="data1" folder1="B" folder2="H" />
> <row lib="data2" folder1="B" folder2="H" />
> <row lib="data3" folder1="B" folder2="H" />
> 
> Basically the first line means that the folder A contains the folder C  which 
> contains data1
> And so on for the others
> I would need to transform it in the following structured XML document :
> 
> <A>
>      <C>
>      <data1/>
>      <data2/>
>      <data3/>
>      </C>
>      <D>
>      <data1/>
>      <data2/>
>      <data3/>
>      </D>
> </A>
> <B>
>      <E>
>      <data1/>
>      <data2/>
>      <data3/>
>      </E>
>      <F>
>      <data1/>
>      <data2/>
>      <data3/>
>      </F>
>      [...]
> </B>
> 
> The XML file is dynamically generated so folder names and folder depth are not 
> know in advance.
> I can't see how to do that, and I don't event know if it is possible.

Hi Bruno,

Here's the solution:

xml source:
----------
<rows>
    <row lib="data1" folder1="A" folder2="C" />
    <row lib="data2" folder1="A" folder2="C" />
    <row lib="data3" folder1="A" folder2="C" />
    <row lib="data1" folder1="A" folder2="D" />
    <row lib="data2" folder1="A" folder2="D" />
    <row lib="data3" folder1="A" folder2="D" />
    <row lib="data1" folder1="B" folder2="E" />
    <row lib="data2" folder1="B" folder2="E" />
    <row lib="data3" folder1="B" folder2="E" />
    <row lib="data1" folder1="B" folder2="F" />
    <row lib="data2" folder1="B" folder2="F" />
    <row lib="data3" folder1="B" folder2="F" />
    <row lib="data1" folder1="B" folder2="G" />
    <row lib="data2" folder1="B" folder2="G" />
    <row lib="data3" folder1="B" folder2="G" />
    <row lib="data1" folder1="B" folder2="H" />
    <row lib="data2" folder1="B" folder2="H" />
    <row lib="data3" folder1="B" folder2="H" />
</rows>


xsl stylesheet:
--------------
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>
  <xsl:output indent="yes" omit-xml-declaration="yes"/>
  <xsl:key name="kRow_f1_f2" match="row" use="concat(@folder1, '~', @folder2)"/>
  <xsl:key name="kRow_f1" match="row" use="@folder1"/>
  <xsl:key name="kRow_f2" match="row" use="@folder2"/>
  <xsl:variable name="manySpaces" select="'                              '"/>
  <xsl:variable name="linkList" select="/rows/row" />

  <xsl:template match="/">
    <xsl:call-template name="constructHierarchy">
      <xsl:with-param name="linkList" select="$linkList" />
    </xsl:call-template>
  </xsl:template>

  <xsl:template name="constructHierarchy">
    <xsl:param name="linkList" select="/.." />

       <xsl:for-each select="$linkList[not(key('kRow_f2', @folder1))]">
         <xsl:if test="count(. | key('kRow_f1', @folder1)[1])=1">
           <xsl:call-template name="makeElement">
             <xsl:with-param name="pElement" select="@folder1"/>
             <xsl:with-param name="indent" select="0"/>
           </xsl:call-template>
        </xsl:if>
      </xsl:for-each>
  </xsl:template>

  <xsl:template name="makeElement">
    <xsl:param name="pElement" select="'ErrorUnknown'"/>
    <xsl:param name="indent" select="0"/>

    <xsl:text>&#xA;</xsl:text>
    <xsl:value-of select="substring($manySpaces, 1, $indent)"/>
    <xsl:element name="{$pElement}">
      <xsl:for-each select="key('kRow_f1', $pElement)
                                    [
                                      count(. | key('kRow_f1_f2', 
                                                     concat(@folder1, 
                                                            '~', 
                                                            @folder2
                                                            )
                                                    )[1]
                                           ) = 1
                                     ]">
           <xsl:call-template name="makeElement">
             <xsl:with-param name="pElement" select="@folder2"/>
             <xsl:with-param name="indent" select="$indent + 2"/>
           </xsl:call-template>
      </xsl:for-each>
      
      <xsl:for-each select="key('kRow_f2', $pElement)">
        <xsl:element name="{@lib}"/>
      </xsl:for-each>
         
    </xsl:element>
  </xsl:template>
</xsl:stylesheet>

Result:
------

<A>
  <C>
<data1 />
<data2 />
<data3 />
</C>
  <D>
<data1 />
<data2 />
<data3 />
</D>
</A>
<B>
  <E>
<data1 />
<data2 />
<data3 />
</E>
  <F>
<data1 />
<data2 />
<data3 />
</F>
  <G>
<data1 />
<data2 />
<data3 />
</G>
  <H>
<data1 />
<data2 />
<data3 />
</H>
</B>

As you can see, a minor thing remainds -- the indentation (if needed at all) could
be improved.

Hope this helped.

Cheers,
Dimitre Novatchev.




__________________________________________________
Do You Yahoo!?
Make international calls for as low as $.04/minute with Yahoo! Messenger
http://phonecard.yahoo.com/

 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread