Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XSLT for each letter in a string

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/MyValue string (e.g. MyData.MyValue = "something"), I need an for-each like this one:

<xsl:for-each select="MyData/MyValue"> <!-- What goes here to iterate through letters? -->
  <someTags>
    <xsl:value-of select="Letter" /> <!-- What goes here to output current letter? -->
  </someTags>
</xsl:for-each>

Any ideas?

like image 322
veljkoz Avatar asked Jul 13 '11 14:07

veljkoz


4 Answers

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>
like image 138
Jukka Matilainen Avatar answered Oct 03 '22 01:10

Jukka Matilainen


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) &gt; 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!

like image 27
Treemonkey Avatar answered Nov 06 '22 09:11

Treemonkey


You can try this dirt-ugly hack that has proven to work time and again:

<xsl:for-each select="//*[position() &lt;= 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

like image 7
Lukas Eder Avatar answered Nov 06 '22 08:11

Lukas Eder


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>
like image 4
Emiliano Poggi Avatar answered Nov 06 '22 08:11

Emiliano Poggi