43

I want to split an address on semicolons (;) into rows separated by <br />:

e.g. if address=123 Elm Street, I want to output 123 Elm Street,

but if address=123 Elm Street;PO Box 222, I want to output

123 Elm Street<br />PO Box 222

and if address=123 Elm Street;PO Box 222;c/o James Jones, I want to output

123 Elm Street<br />PO Box 222<br />c/o James Jones

Is there a way to do this? (probably easy but I'm not that familiar with XSLT)

The plain XSL selector is

<xsl:value-of select="address"/>

and I would like to modify this XSLT fragment to split on semicolon.


update: Apparently the answer involves the use of <xsl:call-template> and the functions substring-before() and substring-after().

But I'm a beginner to XSLT and I could really use some help for how to do this.

Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
Jason S
  • 184,598
  • 164
  • 608
  • 970
  • possible duplicate of [Does XSLT have a Split() function?](http://stackoverflow.com/questions/136500/does-xslt-have-a-split-function) – porges Jan 30 '11 at 21:32
  • maybe it is a near-duplicate, but I can't figure out how to apply the answers to that question, to my problem. – Jason S Jan 30 '11 at 21:35
  • See also http://stackoverflow.com/questions/10750184/looping-through-multiple-sequences-from-strtokenize – Vadzim Dec 09 '16 at 09:27

2 Answers2

72

I. Plain XSLT 1.0 solution:

This transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="text()" name="split">
  <xsl:param name="pText" select="."/>
  <xsl:if test="string-length($pText)">
   <xsl:if test="not($pText=.)">
    <br />
   </xsl:if>
   <xsl:value-of select=
    "substring-before(concat($pText,';'),';')"/>
   <xsl:call-template name="split">
    <xsl:with-param name="pText" select=
     "substring-after($pText, ';')"/>
   </xsl:call-template>
  </xsl:if>
 </xsl:template>
</xsl:stylesheet>

when applied on this XML document:

<t>123 Elm Street;PO Box 222;c/o James Jones</t>

produces the wanted, corrected result:

123 Elm Street<br />PO Box 222<br />c/o James Jones

II. FXSL 1 (for XSLT 1.0):

Here we just use the FXSL template str-map (and do not have to write recursive template for the 999th time):

<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"/>

   <testmap:testmap/>

   <xsl:output omit-xml-declaration="yes" indent="yes"/>

   <xsl:template match="/">
     <xsl:variable name="vTestMap" select="document('')/*/testmap:*[1]"/>
     <xsl:call-template name="str-map">
       <xsl:with-param name="pFun" select="$vTestMap"/>
       <xsl:with-param name="pStr" select=
       "'123 Elm Street;PO Box 222;c/o James Jones'"/>
     </xsl:call-template>
   </xsl:template>

    <xsl:template name="replace" mode="f:FXSL"
         match="*[namespace-uri() = 'testmap']">
      <xsl:param name="arg1"/>

      <xsl:choose>
       <xsl:when test="not($arg1=';')">
        <xsl:value-of select="$arg1"/>
       </xsl:when>
       <xsl:otherwise><br /></xsl:otherwise>
      </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

when this transformation is applied on any XML document (not used), the same, wanted correct result is produced:

123 Elm Street<br/>PO Box 222<br/>c/o James Jones

III. Using XSLT 2.0

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="text()">
  <xsl:for-each select="tokenize(.,';')">
   <xsl:sequence select="."/>
   <xsl:if test="not(position() eq last())"><br /></xsl:if>
  </xsl:for-each>
 </xsl:template>
</xsl:stylesheet>

when this transformation is applied on this XML document:

<t>123 Elm Street;PO Box 222;c/o James Jones</t>

the wanted, correct result is produced:

123 Elm Street<br />PO Box 222<br />c/o James Jones
Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • could you explain the `not($pText=.)` syntax? what is the `.` doing? – Jason S Jan 30 '11 at 22:24
  • 3
    @Jason-S: `.` in XSLT (and in XPath) means the current node (context node). `not($pText=.)` is `true()` if the string value of the parameter `$pText` is not equal to the string value of the current node -- we want to output `
    ` only in this case, otherwise our output will start with `
    `
    – Dimitre Novatchev Jan 30 '11 at 22:29
  • Can I pass an already existing variable to the param name="pText"? – Perdomoff Aug 23 '17 at 01:20
  • @Perdomoff, Yes, in XSLT the arguments supplied in the `select` attribute of `` can be any XPath expression and a variable reference is a valid XPath expression. The same is true for the supplying values to the arguments when calling an ` in XSLT 2.0 and later versions. – Dimitre Novatchev Aug 23 '17 at 02:36
  • This did work, thank you. Can I pass a variable from another xsl file? – Perdomoff Aug 23 '17 at 15:40
  • @Perdomoff, Yes if its global (defined as a child of `` and if this other XSLT stylesheet is imported/included or imports/includes. I would recommend watching my Pluralsight course "XSLT 2.0 and 1.0 Foundations" https://www.pluralsight.com/courses/xslt-foundations-part1 – Dimitre Novatchev Aug 23 '17 at 15:54
5

If your XSLT processor supports EXSLT, you can use str:tokenize, otherwise, the link contains an implementation using functions like substring-before.

Mormegil
  • 7,955
  • 4
  • 42
  • 77
  • how do I find out? My XSLT processor is a web browser, either Firefox 3.6 or Safari 5.0 – Jason S Jan 30 '11 at 21:38
  • 2
    @Jason S - in that case, you cannot depend on that and you need to supply the function yourself; the implementation is available at the linked http://exslt.org/str/functions/tokenize/str.tokenize.template.xsl – Mormegil Jan 30 '11 at 21:40
  • Here is an example: http://stackoverflow.com/questions/10750184/looping-through-multiple-sequences-from-strtokenize – Vadzim Dec 09 '16 at 09:27