Andrew Welch wrote:
On 1/11/07, James Fuller <jim(_dot_)fuller(_at_)ruminate(_dot_)co(_dot_)uk>
wrote:
Hello All,
Can anyone propose a pure xslt (1 or 2) solution to transforming the
following flat xml structure of directory paths into a hierarchical
(nested) xml.
<?xml version='1.0'?>
<listing>
<item>cn/test.xml</item>
<item>en</item>
<item>en/test.html</item>
<item>en/test1.html</item>
<item>en/resource</item>
<item>en/resource/style</item>
<item>en/resource/style/test.css</item>
<item>favicon.ico</item>
<item>cn</item>
</listing>
to
<dir>
<file name="favicon.ico"/>
<dir name="cn">
<file name="test.xml"/>
</dir>
<dir name="en">
<file name="test.html"/>
<file name="test1.html"/>
<dir name="resource">
<dir name="style">
<file name="test.css"/>
</dir>
</dir>
</dir>
</dir>
...trickier than it first looks :)
This should do the job:
<xsl:stylesheet version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
exclude-result-prefixes="xs">
<xsl:template match="listing">
<dir>
<xsl:call-template name="process">
<xsl:with-param name="depth" select="1" as="xs:integer"/>
<xsl:with-param name="seq" select="item"/>
</xsl:call-template>
</dir>
</xsl:template>
<xsl:template name="process">
<xsl:param name="depth" as="xs:integer"/>
<xsl:param name="seq"/>
<xsl:for-each-group select="$seq" group-by="tokenize(.,
'/')[$depth]">
<xsl:variable name="part" select="tokenize(., '/')[$depth]"/>
<xsl:choose>
<xsl:when test="contains($part, '.')">
<file name="{$part}"/>
</xsl:when>
<xsl:otherwise>
<dir name="{$part}">
<xsl:call-template name="process">
<xsl:with-param name="depth" select="$depth +
1"/>
<xsl:with-param name="seq"
select="$seq[tokenize(., '/')[$depth]
= $part]"/>
</xsl:call-template>
</dir>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
The output is:
<dir>
<dir name="cn">
<file name="test.xml"/>
</dir>
<dir name="en">
<file name="test.html"/>
<file name="test1.html"/>
<dir name="resource">
<dir name="style">
<file name="test.css"/>
</dir>
</dir>
</dir>
<file name="favicon.ico"/>
</dir>
nice....the recursion route seems to be the general approach, just about
scratched it out myself (w/o the part bit)
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:template match="data">
<dir name="root">
<xsl:call-template name="pdir">
<xsl:with-param name="n" select="1"/>
<xsl:with-param name="in" select="//item"/>
</xsl:call-template>
</dir>
</xsl:template>
<xsl:template name="pdir">
<xsl:param name="n"/>
<xsl:param name="in"/>
<xsl:for-each-group select="$in" group-by="tokenize(.,'/')[$n]">
<dir src="{.}">
<xsl:call-template name="pdir">
<xsl:with-param name="n" select="$n+1"/>
<xsl:with-param name="in" select="current-group()"/>
</xsl:call-template> </dir>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
this is an elegant template, really shows the power and sweetspot hit by
XSLT 2.0 (anyone doing this in XSLT 1.0, barring FXSL, this becomes a
bit of a nightmare)....any alternate approaches which makes things more
efficient (i dont think its possible to optimise the MKay recursive
approach).
was thinking there might be an approach using analyze-string
instruction...but still working on it.
(IMHO, DaveP should include this in XSLT FAQ, its a keeper and common
processing scenario)
ta, J
--~------------------------------------------------------------------
XSL-List info and archive: http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: <mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--