Subject: Re: [xsl] most efficient flat file listing to hierarchical From: "Dimitre Novatchev" <dnovatchev@xxxxxxxxx> Date: Thu, 11 Jan 2007 08:26:01 -0800 |
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).
Here's my take on this problem. Trying to adhere to the KISS principle, I'm using a two-step approach naturally expressed as functional composition.
Notice that not only is this a litlle easier to understand, but that the main "powerhorse" (the function f:makeTree() ) is just 17 lines of code:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:f="http://fxsl.sf.net/" exclude-result-prefixes="f xs">
<xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:template match="/*"> <xsl:sequence select="f:makeTree(f:tokenizePaths(/*/*), 1)"/> </xsl:template>
<xsl:function name="f:tokenizePaths"> <xsl:param name="pfilePaths" as="element()+"/> <xsl:for-each select="$pfilePaths"> <item> <xsl:for-each select="tokenize(.,'/')"> <q><xsl:value-of select="."/></q> </xsl:for-each> </item> </xsl:for-each> </xsl:function>
<xsl:function name="f:makeTree" as="element()*"> <xsl:param name="pItems" as="element()*"/> <xsl:param name="pqLevel" as="xs:integer"/>
<xsl:for-each-group select="$pItems" group-by="q[$pqLevel]"> <xsl:choose> <xsl:when test="count(current-group()) = 1"> <file name="{q[$pqLevel]}"/> </xsl:when> <xsl:otherwise> <dir name="{q[$pqLevel]}"> <xsl:sequence select="f:makeTree(current-group(), $pqLevel+1)"/> </dir> </xsl:otherwise> </xsl:choose> </xsl:for-each-group> </xsl:function> </xsl:stylesheet>
-- Cheers, Dimitre Novatchev --------------------------------------- Truly great madness cannot be achieved without significant intelligence. --------------------------------------- To invent, you need a good imagination and a pile of junk ------------------------------------- You've achieved success in your field when you don't know whether what you're doing is work or play
Andrew Welch wrote:
> 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>
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
Current Thread |
---|
|
<- Previous | Index | Next -> |
---|---|---|
Re: [xsl] most efficient flat file , Andrew Welch | Thread | Re: [xsl] most efficient flat file , James Fuller |
Re: [xsl] most efficient flat file , James Fuller | Date | Re: [xsl] most efficient flat file , Andrew Welch |
Month |