XSLT Expand input XML using a for-loop type operation -
i'm new xslt, forgive ignorance, if have input xml this:
<doc> <stuff> <for var="i" from="1" to="2"> <item>$(i)></item> <for var="j" from="2" to="4"> <inneritem>$(j)</inneritem> </for> </for> </stuff> </doc> i want use transform have output xml expanded this:
<doc> <stuff> <item>1</item> <inneritem>2</inneritem> <inneritem>3</inneritem> <inneritem>4</inneritem> <item>2</item> <inneritem>2</inneritem> <inneritem>3</inneritem> <inneritem>4</inneritem> </stuff> </doc> all i've got this: next?
<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/xsl/transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl" > <xsl:output method="xml" indent="yes"/> <xsl:template match="@* | node()"> <xsl:copy> <xsl:apply-templates select="@* | node()"/> </xsl:copy> </xsl:template> <xsl:template match="for"> <xsl:variable name="from" select="@from" /> <xsl:variable name="to" select="@to" /> <xsl:copy> <xsl:apply-templates select="@* | node()"/> </xsl:copy> </xsl:template> </xsl:stylesheet>
here simpler (no explicit conditional instructions, no user-defined functions, no modes) , shorter, working transformation:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/xsl/transform"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:strip-space elements="*"/> <xsl:template match="node()|@*"> <xsl:param name="pvars" as="element()*"/> <xsl:copy> <xsl:apply-templates select="node()|@*"> <xsl:with-param name="pvars" select="$pvars"/> </xsl:apply-templates> </xsl:copy> </xsl:template> <xsl:template match="for"> <xsl:param name="pvars" as="element()*"/> <xsl:variable name="vcurrentfor" select="."/> <xsl:for-each select="@from @to"> <xsl:variable name="vnewvars"> <xsl:sequence select="$pvars"/> <var name="{$vcurrentfor/@var}" value="{current()}"/> </xsl:variable> <xsl:apply-templates select="$vcurrentfor/node()"> <xsl:with-param name="pvars" select="$vnewvars/*"/> </xsl:apply-templates> </xsl:for-each> </xsl:template> <xsl:template match="text()[contains(., '$(')]"> <xsl:param name="pvars" as="element()*"/> <xsl:analyze-string select="." regex="\$\((.+?)\)"> <xsl:non-matching-substring> <xsl:value-of select="."/> </xsl:non-matching-substring> <xsl:matching-substring> <xsl:variable name="vname" select="regex-group(1)"/> <xsl:variable name="vreplacement" select= "$pvars[@name eq $vname][last()]/@value"/> <xsl:sequence select="string($vreplacement)"/> </xsl:matching-substring> </xsl:analyze-string> </xsl:template> </xsl:stylesheet> when transformation applied on provided xml document:
<doc> <stuff> <for var="i" from="1" to="2"> <item>$(i)</item> <for var="j" from="2" to="4"> <inneritem>$(j)</inneritem> </for> </for> </stuff> </doc> the wanted, correct result produced:
<doc> <stuff> <item>1</item> <inneritem>2</inneritem> <inneritem>3</inneritem> <inneritem>4</inneritem> <item>2</item> <inneritem>2</inneritem> <inneritem>3</inneritem> <inneritem>4</inneritem> </stuff> </doc> it possible perform more complicated processing:
using variables differnt levels:
<doc> <stuff> <for var="x" from="1" to="2"> <item>$(x)</item> <for var="y" from="2" to="4"> <inneritem>$(x).$(y)</inneritem> </for> </for> </stuff> </doc> the result on document is:
<doc> <stuff> <item>1</item> <inneritem>1.2</inneritem> <inneritem>1.3</inneritem> <inneritem>1.4</inneritem> <item>2</item> <inneritem>2.2</inneritem> <inneritem>2.3</inneritem> <inneritem>2.4</inneritem> </stuff> </doc> or xml document:
<doc> <stuff> <for var="x" from="1" to="2"> <item> <value>$(x)</value> <for var="y" from="2" to="4"> <inneritem> <value>$(x).$(y)</value> <for var="z" from="3" to="5"> <inner-most-item>$(x).$(y).$(z)</inner-most-item> </for> </inneritem> </for> </item> </for> </stuff> </doc> the result is:
<doc> <stuff> <item> <value>1</value> <inneritem> <value>1.2</value> <inner-most-item>1.2.3</inner-most-item> <inner-most-item>1.2.4</inner-most-item> <inner-most-item>1.2.5</inner-most-item> </inneritem> <inneritem> <value>1.3</value> <inner-most-item>1.3.3</inner-most-item> <inner-most-item>1.3.4</inner-most-item> <inner-most-item>1.3.5</inner-most-item> </inneritem> <inneritem> <value>1.4</value> <inner-most-item>1.4.3</inner-most-item> <inner-most-item>1.4.4</inner-most-item> <inner-most-item>1.4.5</inner-most-item> </inneritem> </item> <item> <value>2</value> <inneritem> <value>2.2</value> <inner-most-item>2.2.3</inner-most-item> <inner-most-item>2.2.4</inner-most-item> <inner-most-item>2.2.5</inner-most-item> </inneritem> <inneritem> <value>2.3</value> <inner-most-item>2.3.3</inner-most-item> <inner-most-item>2.3.4</inner-most-item> <inner-most-item>2.3.5</inner-most-item> </inneritem> <inneritem> <value>2.4</value> <inner-most-item>2.4.3</inner-most-item> <inner-most-item>2.4.4</inner-most-item> <inner-most-item>2.4.5</inner-most-item> </inneritem> </item> </stuff> </doc> i stop here, given design of language, possibilities limitless.
update: op has asked:
"is there way allow expansion inside attributes? example:
<inner-most-item id="$(i)">"
yes, quite easy -- adding new template matching attributes , refactoring code:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/xsl/transform" xmlns:my="my:my"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:strip-space elements="*"/> <xsl:template match="node()|@*"> <xsl:param name="pvars" as="element()*"/> <xsl:copy> <xsl:apply-templates select="node()|@*"> <xsl:with-param name="pvars" select="$pvars"/> </xsl:apply-templates> </xsl:copy> </xsl:template> <xsl:template match="for"> <xsl:param name="pvars" as="element()*"/> <xsl:variable name="vcurrentfor" select="."/> <xsl:for-each select="@from @to"> <xsl:variable name="vnewvars"> <xsl:sequence select="$pvars"/> <var name="{$vcurrentfor/@var}" value="{current()}"/> </xsl:variable> <xsl:apply-templates select="$vcurrentfor/node()"> <xsl:with-param name="pvars" select="$vnewvars/*"/> </xsl:apply-templates> </xsl:for-each> </xsl:template> <xsl:template match="text()[contains(., '$(')]"> <xsl:param name="pvars" as="element()*"/> <xsl:value-of select="my:evaltext($pvars, .)"/> </xsl:template> <xsl:template match="@*[contains(., '$(')]"> <xsl:param name="pvars" as="element()*"/> <xsl:attribute name="{name()}"> <xsl:value-of select="my:evaltext($pvars, .)"/> </xsl:attribute> </xsl:template> <xsl:function name="my:evaltext"> <xsl:param name="pvars" as="element()*"/> <xsl:param name="ptext"/> <xsl:analyze-string select="$ptext" regex="\$\((.+?)\)"> <xsl:non-matching-substring> <xsl:value-of select="."/> </xsl:non-matching-substring> <xsl:matching-substring> <xsl:variable name="vname" select="regex-group(1)"/> <xsl:variable name="vreplacement" select= "$pvars[@name eq $vname][last()]/@value"/> <xsl:value-of select="string($vreplacement)"/> </xsl:matching-substring> </xsl:analyze-string> </xsl:function> </xsl:stylesheet> when transformation applied on following xml document:
<doc> <stuff> <for var="i" from="1" to="2"> <item name="x$(i)">$(i)</item> <for var="j" from="2" to="4"> <inneritem name="x$(i).$(j)">$(j)</inneritem> </for> </for> </stuff> </doc> the wanted, correct result produced:
<doc> <stuff> <item name="x1">1</item> <inneritem name="x1.2">2</inneritem> <inneritem name="x1.3">3</inneritem> <inneritem name="x1.4">4</inneritem> <item name="x2">2</item> <inneritem name="x2.2">2</inneritem> <inneritem name="x2.3">3</inneritem> <inneritem name="x2.4">4</inneritem> </stuff> </doc>
Comments
Post a Comment