Given a source XML instance as follows:
source.xml
<?xml version="1.0" encoding="UTF-8"?>
<data>
<elements>
<element path="a." name="Aname"/>
<element path="a.b." parent="a." name="Bname"/>
<element path="a.c." parent="a." name="Cname"/>
<element path="a.c.d." parent="a.c." name="Dname"/>
<element path="a.c.d.e." parent="a.c.d." name="Ename"/>
<element path="a.f." parent="a." name="Fname"/>
<element path="a.f.g." parent="a.f." name="Gname"/>
<element path="a.f.g.h." parent="a.f.g." name="Hname"/>
<element path="a.f.g.h.i." parent="a.f.g.h." name="Iname"/>
<element path="a.f.g.h.i.j." parent="a.f.g.h.i." name="Jname"/>
<element path="a.f.g.k." parent="a.f.g." name="Kname"/>
<element path="a.f.g.l." parent="a.f.g." name="Lname"/>
<element path="a.f.m." parent="a.f." name="Mname"/>
</elements>
</data>
How can the following resultant XML be achieved via an XSLT transformation:
result.xml
<?xml version="1.0" encoding="UTF-8"?>
<map>
<node TEXT="Aname">
<node TEXT="Bname"/>
<node TEXT="Cname">
<node TEXT="Dname">
<node TEXT="Ename"/>
</node>
</node>
<node TEXT="Fname">
<node TEXT="Gname">
<node TEXT="Hname">
<node TEXT="Iname">
<node TEXT="Jname"/>
</node>
</node>
<node TEXT="Kname"/>
<node TEXT="Lname"/>
</node>
<node TEXT="Mname"/>
</node>
</node>
</map>
As you can see in the source XML every element
XML element has an associated path
attribute with a dot separated string value.
Every element
in the source XML also has a parent
attribute with a dot separated string value.
Can the values of both these attributes (path
and parent
) somehow be utilized to achieve the desired nesting of node
elements as shown in the resultant XML ?
Below is just one example of my many failed XSLT attempts.
All attempts I've tried fail to achieve the desired nesting of node
elements - the resultant XML always remains flat.
XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="data">
<map>
<xsl:apply-templates select="@*|node()"/>
</map>
</xsl:template>
<xsl:template match="element">
<xsl:for-each select=".">
<node>
<xsl:attribute name="TEXT" select="@name"/>
</node>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This can be very easy it you use the correct tool for resolving cross-references:
XSLT 1.0
<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="child" match="element" use="@parent" />
<xsl:template match="/data">
<map>
<xsl:apply-templates select="elements/element[not(@parent)]"/>
</map>
</xsl:template>
<xsl:template match="element">
<node TEXT="{@name}">
<xsl:apply-templates select="key('child', @path)"/>
</node>
</xsl:template>
</xsl:stylesheet>