Re: [xsl] most efficient flat file listing to hierarchical

Subject: Re: [xsl] most efficient flat file listing to hierarchical
From: "Andrew Welch" <andrew.j.welch@xxxxxxxxx>
Date: Thu, 11 Jan 2007 10:26:00 +0000
On 1/11/07, James Fuller <jim.fuller@xxxxxxxxxxxxxx> 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>

cheers
andrew

Current Thread