Deepening an XML hierarchy

J

Juho Jussila

Hi

I have a problem while trying to deepen a flat XML document.
The problem is that there are no links between elements. The only
way to determine which element should be a child of another
is to check its position.

Input document is:
<?xml version="1.0" encoding="iso-8859-1"?>
<Root>
<A1/>
<B1/>
<B1/>
<C1/>
<C1/>
<D1/>
<B2/>
<C2/>
<Root>

Element can have 0-n sub-elements:
A1: B1,B2
B1: C1
C1: D1
B2: C2

And I'd like to have this kind of result document:
<A1>
<B1/>
<B1>
<C1/>
<C1>
<D1/>
</C1>
</B1>
<B2>
<C2/>
</B2>
</A1>

After reading FAQ
(http://www.dpawson.co.uk/xsl/sect2/N4486.html#d252e1102)
I managed to write this:

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

<xsl:template match="/Root">
<xsl:apply-templates select="A1"/>
</xsl:template

<xsl:template match="A1">
<xsl:copy>
<xsl:copy-of select="*"/>
<xsl:apply-templates
select="following-sibling::*[1][name()='B1' or name()='B2']" />
</xsl:copy>
</xsl:template>

<xsl:template match="B1|B2">
<xsl:copy-of select="."/>
<xsl:apply-templates
select="following-sibling::*[1][name()='B1' or name()='B2']"/>
</xsl:template>

</xsl:stylesheet>

But now I don't have any idea how to travel next levels.
If I add apply-templates select="C1" inside the last template,
"iterator" position is lost. For example in sequence A1 B1 C1 B1 C1
the last B1 is not matched.

Is there any way to do this with xslt?

Thanks in advance.
 
D

David Carlisle

$ saxon flat1.xml flat1.xsl
<?xml version="1.0" encoding="utf-8"?>
<root>
<A1>
<B1/>
<B1>
<C1/>
<C1>
<D1/>
<B2>
<C2/>
</B2>
</C1>
</B1>
</A1>
</root>



<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">

<xsl:eek:utput indent="yes"/>
<xsl:variable name="s" select="'ABCDEFGHIJK'"/>

<xsl:template match="Root">
<root>
<xsl:apply-templates select="*[1]"/>
</root>
</xsl:template>

<xsl:template match="*">
<xsl:variable name="f" select="substring(name(),1,1)"/>
<xsl:variable name="c" select="string-length(substring-before($s,$f))"/>
<xsl:variable name="n" select="following-sibling::*[1]"/>
<xsl:variable name="nf" select="substring(name($n),1,1)"/>
<xsl:variable name="nc" select="string-length(substring-before($s,$nf))"/>
<xsl:copy>
<xsl:apply-templates select="$n[$nc &gt;$c]"/>
</xsl:copy>
<xsl:apply-templates select="$n[$nc &lt;=$c]"/>
</xsl:template>

</xsl:stylesheet>
 
J

Juho Jussila

David Carlisle said:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">

<xsl:eek:utput indent="yes"/>
<xsl:variable name="s" select="'ABCDEFGHIJK'"/>

<xsl:template match="Root">
<root>
<xsl:apply-templates select="*[1]"/>
</root>
</xsl:template>

<xsl:template match="*">
<xsl:variable name="f" select="substring(name(),1,1)"/>
<xsl:variable name="c" select="string-length(substring-before($s,$f))"/>
<xsl:variable name="n" select="following-sibling::*[1]"/>
<xsl:variable name="nf" select="substring(name($n),1,1)"/>
<xsl:variable name="nc" select="string-length(substring-before($s,$nf))"/>
<xsl:copy>
<xsl:apply-templates select="$n[$nc &gt;$c]"/>
</xsl:copy>
<xsl:apply-templates select="$n[$nc &lt;=$c]"/>
</xsl:template>

</xsl:stylesheet>

Thanks for your quick reply. XSLT really is magic :)

Too bad, I wrote too simplified example and mislead you. Sorry.
Actual input is (well, only part of it):

<?xml version="1.0" encoding="iso-8859-1"?>
<Root>
<H01/>
<H04/>
<H02/>
<H03/>
<H05/>
<H02/>
<H02/>
<I01/>
<I02/>
<I02/>
<I05/>
<I05/>
<Root>

Structure is documented in textual form:
H01
H04
H02
H05
H03
I01
I02
I05
VJU09
VJU02
VJU12
VJU02
....

So simple element name doesn't tell where it should be placed.
Is there still hope for XSLT based solution?
 
D

David Carlisle

Too bad, I wrote too simplified example and mislead you. Sorry.

well I suspected that your real case didn't just use the first letter,
but...
Structure is documented in textual form:
I don't understand this.
Is there still hope for XSLT based solution?
If there is some procedure that says when an element should be nested
and when it should not, then xslt can encode that.
However my understanding of your description is that you just give a
complete description of the final answer. Clearly if you know what the
answer has to be, XSLT can just copy the required answer and ignore its
input, but I suspect that isn't quite what you want either:)

although looking again, perhaps I can make one guess at what you mean...



flat2.xml
<Root>
<H01/>
<H04/>
<H02/>
<H03/>
<H05/>
<H02/>
<H02/>
<I01/>
<I02/>
<I02/>
<I05/>
<I05/>
</Root>

flat2a.xml
<x>
<x name="H01">
<x name="H04"/>
<x name="H02">
<x name="H05"/>
<x name="H03"/>
</x>
</x>
<x name="I01">
<x name="I02">
<x name="I05"/>
</x>
</x>
<x name="VJU09">
<x name="VJU02"/>
<x name="VJU12">
<x name="VJU02"/>
</x>
</x>
</x>


flat2.xsl
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">

<xsl:eek:utput indent="yes"/>
<xsl:variable name="s" select="document('flat2a.xml')"/>
<xsl:template match="Root">
<root>
<xsl:apply-templates select="*[1]"/>
</root>
</xsl:template>

<xsl:template match="*">
<xsl:variable name="t" select="name()"/>
<xsl:variable name="n" select="$s//*[@name=$t]/../*/@name"/>
<xsl:variable name="c" select="$s//*[@name=$t]/*/@name"/>
<xsl:copy>
<xsl:apply-templates select="following-sibling::*[1][name()=$c]"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::*[name()=$n][1]"/>
</xsl:template>

</xsl:stylesheet>


$ saxon flat2.xml flat2.xsl
<?xml version="1.0" encoding="utf-8"?>
<root>
<H01>
<H04/>
<H02>
<H03/>
<H05/>
</H02>
<H02/>
<H02/>
</H01>
<I01>
<I02/>
<I02>
<I05/>
<I05/>
</I02>
</I01>
</root>
 
M

Mukul Gandhi

I am not sure whether this will help, but here is my attempt..

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

<xsl:eek:utput method="xml" indent="yes" />

<xsl:template match="/Root">
<xsl:call-template name="generateHierarchy">
<xsl:with-param name="x" select="*[1]" />
<xsl:with-param name="nodeset" select="*[position() &gt; 1]" />

</xsl:call-template>
</xsl:template>

<xsl:template name="generateHierarchy">
<xsl:param name="x" />
<xsl:param name="nodeset" />

<xsl:choose>
<xsl:when test="(name($x) = name($nodeset[1])) and (name($x) !=
'') and (name($nodeset[1]) != '')">
<xsl:element name="{name($x)}" />
<xsl:element name="{name($nodeset[1])}">
<xsl:call-template name="generateHierarchy">
<xsl:with-param name="x" select="$nodeset[1]" />
<xsl:with-param name="nodeset" select="$nodeset[position()
&gt; 1]" />
</xsl:call-template>
</xsl:element>
</xsl:when>
<xsl:eek:therwise>
<xsl:if test="name($x) != ''">
<xsl:element name="{name($x)}">
<xsl:call-template name="generateHierarchy">
<xsl:with-param name="x" select="$nodeset[1]" />
<xsl:with-param name="nodeset" select="$nodeset[position() &gt;
1]" />
</xsl:call-template>
</xsl:element>
</xsl:if>
</xsl:eek:therwise>
</xsl:choose>

</xsl:template>

</xsl:stylesheet>

I get output -
<?xml version="1.0" encoding="UTF-8"?>
<H01>
<H04>
<H02>
<H03>
<H05>
<H02/>
<H02>
<H02>
<I01>
<I02/>
<I02>
<I02>
<I05/>
<I05>
<I05/>
</I05>
</I02>
</I02>
</I01>
</H02>
</H02>
</H05>
</H03>
</H02>
</H04>
</H01>

I am using Saxon 8.4.

Regards,
Mukul
 
J

Juho Jussila

David Carlisle said:
I don't understand this.

Sorry, my English is quite poor.. I was trying to say that the XML
hierarchy is documented in Word document.

[...]
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">

<xsl:eek:utput indent="yes"/>
<xsl:variable name="s" select="document('flat2a.xml')"/>
<xsl:template match="Root">
<root>
<xsl:apply-templates select="*[1]"/>
</root>
</xsl:template>

<xsl:template match="*">
<xsl:variable name="t" select="name()"/>
<xsl:variable name="n" select="$s//*[@name=$t]/../*/@name"/>
<xsl:variable name="c" select="$s//*[@name=$t]/*/@name"/>
<xsl:copy>
<xsl:apply-templates select="following-sibling::*[1][name()=$c]"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::*[name()=$n][1]"/>
</xsl:template>

</xsl:stylesheet>

Wow, this is exactly what I need and IMHO _very_ elegant solution. The
idea of putting XML structure in separate file make this generic.

Thank you very much, I really appreciate your help.
 
J

Juho Jussila

Mukul Gandhi said:
I am not sure whether this will help, but here is my attempt..

Thanks, nice solution also. Maybe I express my intention unclearly,
but your XSLT gives a bit too deep hierarchy.

Hierarchy is specified:
H01 may have child elements: H04, H02
H02 may have child elements: H05, H03
I01 may have child elements: I02
I02 may have child elements: I05
and so on..

It would be easy to flatten any kind of XML hierarchy performing a
preorder traversal. Problem is that the input document is flat and I
need to do the other way.

[...]
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,983
Messages
2,570,187
Members
46,747
Latest member
jojoBizaroo

Latest Threads

Top