I\'m writing an XSLT transformation (for XSL-FO), and need to repeat something for each letter in a string value, for example:
If string is stored in MyData/My
you could use a call template and pass parameters, then use recursion to call the template untill there are no characters left.
example added below.
on this xml
<?xml version="1.0" encoding="utf-8"?>
<data>
<node>something</node>
</data>
and this xslt
<?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="data/node">
<xsl:call-template name="for-each-character">
<xsl:with-param name="data" select="."/>
</xsl:call-template>
</xsl:template>
<xsl:template name="for-each-character">
<xsl:param name="data"/>
<xsl:if test="string-length($data) > 0">
<someTags>
<xsl:value-of select="substring($data,1,1)"/>
</someTags>
<xsl:call-template name="for-each-character">
<xsl:with-param name="data" select="substring($data,2)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
you will then be able to manipulate in the if statement to do further stuff!
You can use recursion:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:call-template name="get-letters">
<xsl:with-param name="input">something</xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template name="get-letters">
<xsl:param name="input"/>
<xsl:if test="string-length($input)">
<xsl:value-of select="substring($input, 1, 1)"/>
<xsl:text> </xsl:text>
<xsl:call-template name="get-letters">
<xsl:with-param name="input" select="substring($input, 2)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Output:
s o m e t h i n g
Several XSLT 1.0 solutions have been posted (since that is what the original poster needed). For comparison, though, here is how this could be done in XSLT 2.0 using xsl:analyze-string:
<xsl:analyze-string select="MyData/MyValue" regex=".">
<xsl:matching-substring>
<someTags>
<xsl:value-of select="."/>
</someTags>
</xsl:matching-substring>
</xsl:analyze-string>
I'm not sure about the feasibility of the iteration. You can use recursion obsviously as shown in other answers. This is my proposal (not much different from the others apart the fact I'm using template match patterns and not named templates):
<xsl:template match="MyData/MyValue">
<xsl:param name="sub" select="."/>
<xsl:variable name="subsub" select="substring($sub,1,1)"/>
<xsl:if test="boolean($subsub)">
<someTags>
<xsl:value-of select="$subsub"/>
</someTags>
<xsl:apply-templates select="self::node()">
<xsl:with-param name="sub" select="substring($sub,2)"/>
</xsl:apply-templates>
</xsl:if>
</xsl:template>
You can try this dirt-ugly hack that has proven to work time and again:
<xsl:for-each select="//*[position() <= string-length(MyData/MyValue)]">
<someTags>
<xsl:value-of select="substring(MyData/MyValue, position(), 1)"/>
</someTags>
</xsl:for-each>
This will work if //*
matches more nodes than the number of characters in your string... Of course, this would also deserve the odd line of comment for the poor fellow reading your code afterwards... ;-)
Note: I know there are XSLT purists out there. But when you need to get the job done and don't care much about the hyper-verbosity of XSLT, then sometimes these tricks are awesome! IMO
Note also: I have raised a performance question here, to see if iteration or recursion performs better: XSLT iteration or recursion performance
Good question, +1.
This is what the template/function str-map from FXSL is for:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/" xmlns:testmap="testmap"
exclude-result-prefixes="xsl f testmap">
<xsl:import href="str-dvc-map.xsl"/>
<!-- to be applied on any xml source -->
<testmap:testmap/>
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:variable name="vFunTripple" select="document('')/*/testmap:*[1]"/>
<xsl:call-template name="str-map">
<xsl:with-param name="pFun" select="$vFunTripple"/>
<xsl:with-param name="pStr" select="'something'"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="trippleChar" match="testmap:*" mode="f:FXSL">
<xsl:param name="arg1"/>
<xsl:value-of select="concat($arg1,$arg1,$arg1)"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on any XML document (not used), the wanted result is produced:
sssooommmeeettthhhiiinnnggg