Comma separated string parsing XSLT to for-each node - xslt

I have an input string which has csv values. Eg., 1,2,3
I would need to separate each values and assign to target node in for-each loop.
I got this below template that splits the input string based on delimiter. How can I assign each of the delimited values to the target element in for-each loop.
<xsl:template name="output-tokens">
<xsl:param name="list"/>
<xsl:param name="delimiter"/>
<xsl:variable name="newlist">
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<xsl:value-of select="normalize-space($list)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(normalize-space($list), $delimiter)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="first" select="substring-before($newlist, $delimiter)"/>
<xsl:variable name="remaining"
select="substring-after($newlist, $delimiter)"/>
<xsl:variable name="count" select="position()"/>
<num>
<xsl:value-of select="$first"/>
</num>
<xsl:if test="$remaining">
<xsl:call-template name="output-tokens">
<xsl:with-param name="list" select="$remaining"/>
<xsl:with-param name="delimiter">
<xsl:value-of select="$delimiter"/>
</xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:template>
Input xml:
<out1:AvailableDates>
<out1:AvailableDate>15/12/2011,16/12/2011,19/12/2011,20/12/2011,21/12/2011</out1:AvailableDate>
</out1:AvailableDates>
Expected Output:
<tns:AvailableDates>
<tns:AvailableDate>15/12/2011</tns:AvailableDate>
<tns:AvailableDate>16/12/2011</tns:AvailableDate>
<tns:AvailableDate>120/12/2011</tns:AvailableDate>
</tns:AvailableDates>

Here is a complete and short, true XSLT 1.0 solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:out1="undefined" xmlns:tns="tns:tns"
exclude-result-prefixes="out1 tns">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="out1:AvailableDate">
<tns:AvailableDates>
<xsl:apply-templates/>
</tns:AvailableDates>
</xsl:template>
<xsl:template match="text()" name="split">
<xsl:param name="pText" select="."/>
<xsl:param name="pItemElementName" select="'tns:AvailableDate'"/>
<xsl:param name="pItemElementNamespace" select="'tns:tns'"/>
<xsl:if test="string-length($pText) > 0">
<xsl:variable name="vNextItem" select=
"substring-before(concat($pText, ','), ',')"/>
<xsl:element name="{$pItemElementName}"
namespace="{$pItemElementNamespace}">
<xsl:value-of select="$vNextItem"/>
</xsl:element>
<xsl:call-template name="split">
<xsl:with-param name="pText" select=
"substring-after($pText, ',')"/>
<xsl:with-param name="pItemElementName" select="$pItemElementName"/>
<xsl:with-param name="pItemElementNamespace" select="$pItemElementNamespace"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document (corrected to be made well-formed):
<out1:AvailableDates xmlns:out1="undefined">
<out1:AvailableDate>15/12/2011,16/12/2011,19/12/2011,20/12/2011,21/12/2011</out1:AvailableDate>
</out1:AvailableDates>
the wanted, correct result is produced:
<tns:AvailableDates xmlns:tns="tns:tns">
<tns:AvailableDate>15/12/2011</tns:AvailableDate>
<tns:AvailableDate>16/12/2011</tns:AvailableDate>
<tns:AvailableDate>19/12/2011</tns:AvailableDate>
<tns:AvailableDate>20/12/2011</tns:AvailableDate>
<tns:AvailableDate>21/12/2011</tns:AvailableDate>
</tns:AvailableDates>

With XSLT 2.0 you can use tokenize(string, separator) function instead of named template.
And this xsl:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:tns="http://tnsnamespace">
<xsl:template match="AvailableDate">
<tns:AvailableDates>
<xsl:for-each select="tokenize(current(), ',')">
<tns:AvailableDate>
<xsl:value-of select="."/>
</tns:AvailableDate>
</xsl:for-each>
</tns:AvailableDates>
</xsl:template>
</xsl:stylesheet>
gives following result:
<?xml version="1.0" encoding="UTF-8"?>
<tns:AvailableDates xmlns:tns="http://tnsnamespace">
<tns:AvailableDate>15/12/2011</tns:AvailableDate>
<tns:AvailableDate>16/12/2011</tns:AvailableDate>
<tns:AvailableDate>19/12/2011</tns:AvailableDate>
<tns:AvailableDate>20/12/2011</tns:AvailableDate>
<tns:AvailableDate>21/12/2011</tns:AvailableDate>
</tns:AvailableDates>
Update:
With Xslt 2.0 processor under backward compatibility mode following template gives the same result:
<xsl:template match="AvailableDate">
<tns:AvailableDates>
<xsl:variable name="myValue">
<xsl:call-template name="output-tokens">
<xsl:with-param name="list" select="."/>
<xsl:with-param name="delimiter" select="','"/>
</xsl:call-template>
</xsl:variable>
<xsl:for-each select="$myValue/node()">
<tns:AvailableDate>
<xsl:value-of select="."/>
</tns:AvailableDate>
</xsl:for-each>
</tns:AvailableDates>
</xsl:template>
For Xslt 1.0 - it is not possible simple (with standard functions) access to nodes via variable - see #Dimitre Novatchev answer XSLT 1.0 - Create node set and pass as a parameter
For this purpose XSLT 1.0 processors contains extension function: node-set(...)
For Saxon 6.5 node-set() function is defined in http://icl.com/saxon namespace
So in the case of XSLT 1.0 processors solution would be:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exslt="http://exslt.org/common"
xmlns:out1="http://out1namespace"
xmlns:tns="http://tnsnamespace"
exclude-result-prefixes="out1 exslt">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="out1:AvailableDate">
<tns:AvailableDates>
<xsl:variable name="myValue">
<xsl:call-template name="output-tokens">
<xsl:with-param name="list" select="."/>
<xsl:with-param name="delimiter" select="','"/>
</xsl:call-template>
</xsl:variable>
<xsl:for-each select="exslt:node-set($myValue)/node()">
<tns:AvailableDate>
<xsl:value-of select="."/>
</tns:AvailableDate>
</xsl:for-each>
</tns:AvailableDates>
</xsl:template>
<xsl:template name="output-tokens">
<xsl:param name="list"/>
<xsl:param name="delimiter"/>
<xsl:variable name="newlist">
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<xsl:value-of select="normalize-space($list)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(normalize-space($list), $delimiter)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="first" select="substring-before($newlist, $delimiter)"/>
<xsl:variable name="remaining"
select="substring-after($newlist, $delimiter)"/>
<xsl:variable name="count" select="position()"/>
<num>
<xsl:value-of select="$first"/>
</num>
<xsl:if test="$remaining">
<xsl:call-template name="output-tokens">
<xsl:with-param name="list" select="$remaining"/>
<xsl:with-param name="delimiter">
<xsl:value-of select="$delimiter"/>
</xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Thanks #Dimitre Novatchev to correct me and his answer about accessing node sets from variable.

Personally, I prefer this variant based on custom extension functions. The method is compact and clean, and works fine in XSLT 1.0 (at least with XALAN 2.7 as embedded in any recent JVM).
1) declare a class with a static method returning a org.w3c.dom.Node
package com.reverseXSL.util;
import org.w3c.dom.*;
import java.util.regex.*;
import javax.xml.parsers.DocumentBuilderFactory;
public class XslTools {
public static Node splitToNodes(String input, String regex) throws Exception {
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
Element item, list = doc.createElement("List");
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(input);
while (m.find()) {
item = doc.createElement("Item");
StringBuffer sb = new StringBuffer();
for (int i=1; i<=m.groupCount(); ++i) if (m.start(i)>=0) sb.append(m.group(i));
Text txt = doc.createTextNode(sb.toString());
item.appendChild(txt);
list.appendChild(item);
}
return list;
}
}
This function splits an input string on a regex pattern and creates a document fragment of the kind <list><Item>A</Item><Item>B</Item><Item>C</Item></List>.
The regex is matched in sequence, each match yielding an Item element whose value is composed from the capturing groups (some possibly empty) inside each regex match. This allows to get rid from delimiters and other syntax chars.
For instance, to split a comma-separated list like " A, B ,, C", skip empty values, and trim extra spaces (hence get the above Node list), use a regex like '\s*([^,]+?)\s*(?:,|$)' - a mind twisting one! If instead you want to split the input text by a fixed size (here 10 chars) with the last Item taking whatever remains, use a regex like '(.{10}|.+)' - love it!
You can then use the function in XSLT 1.0 as follows (quite compact!):
<xsl:stylesheet version="1.0" xmlns:var="com.reverseXSL.util.XslTools" extension-element-prefixes="var" ...
...
<xsl:template ...
...
<xsl:for-each select="var:splitToNodes(Detail/CsvText,'\s*([^,]+?)\s*(?:,|$)')/Item">
<Loop><xsl:value-of select="."/></Loop>
</xsl:for-each>
...
Executed on a template match yielding the input fragment <Detail><CsvText>a, b ,c </CsvText></Detail> you'll generate <Loop>a</Loop><Loop>b</Loop><Loop>c</Loop>
The trick is not forgetting to follow the function call that generates the Node/Item by the XPath "/Item" (or "/*") as you shall note, so that a Node sequence is returned into the for-each loop.

Related

XSLT 1.0 eliminate duplicates and split comma separated string [duplicate]

I would like to ask if there is a function that can be use to to remove a duplicate value inside a string separated by | simplest possible way. I have below example of the string
1111-1|1111-1|1111-3|1111-4|1111-5|1111-3
the output that I'm expecting is:
1111-1|1111-3|1111-4|1111-5
Thanks in advance.
All presented XSLT 1.0 solutions so far produce the wrong result:
1111-1|1111-4|1111-5|1111-3
whereas the wanted, correct result is:
1111-1|1111-3|1111-4|1111-5
Now, the following transformation (no extensions, pure XSLT 1.0):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="text()" name="distinctSubstrings">
<xsl:param name="pText" select="."/>
<xsl:param name="poutDelim"/>
<xsl:param name="pFoundDistinctSubs" select="'|'"/>
<xsl:param name="pCountDistinct" select="0"/>
<xsl:if test="$pText">
<xsl:variable name="vnextSub" select="substring-before(concat($pText, '|'), '|')"/>
<xsl:variable name="vIsNewDistinct" select=
"not(contains(concat($pFoundDistinctSubs, '|'), concat('|', $vnextSub, '|')))"/>
<xsl:variable name="vnextDistinct" select=
"substring(concat($poutDelim,$vnextSub), 1 div $vIsNewDistinct)"/>
<xsl:value-of select="$vnextDistinct"/>
<xsl:variable name="vNewFoundDistinctSubs"
select="concat($pFoundDistinctSubs, $vnextDistinct)"/>
<xsl:variable name="vnextOutDelim"
select="substring('|', 2 - ($pCountDistinct > 0))"/>
<xsl:call-template name="distinctSubstrings">
<xsl:with-param name="pText" select="substring-after($pText, '|')"/>
<xsl:with-param name="pFoundDistinctSubs" select="$vNewFoundDistinctSubs"/>
<xsl:with-param name="pCountDistinct" select="$pCountDistinct + $vIsNewDistinct"/>
<xsl:with-param name="poutDelim" select="$vnextOutDelim"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document (with string value the provided string in the question):
<t>1111-1|1111-1|1111-3|1111-4|1111-5|1111-3</t>
produces the wanted, correct result:
1111-1|1111-3|1111-4|1111-5
Explanation:
All found distinct substrings are concatenated in the parameter $pFoundDistinctSubs -- whenever we get the next substring from the delimited input, we compare it to the distinct substrings passed in this parameter. This ensures that the first in order distinct substring will be output -- not the last as in the other two solutions.
We use conditionless value determination, based on the fact that XSLT 1.0 implicitly converts a Boolean false() to 0 and true() to 1 whenever it is used in a context that requires a numeric value. In particular, substring($x, 1 div true()) is equivalent to substring($x, 1 div 1) that is: substring($x, 1) and this is the entire string $x. On the other side, substring($x, 1 div false()) is equivalent to substring($x, 1 div 0) -- that is: substring($x, Infinity) and this is the empty string.
To know why avoiding conditionals is important: watch this Pluralsight course:
Tactical Design Patterns in .NET: Control Flow, by Zoran Horvat
To do this in pure XSLT 1.0, with no extension functions, you will need to use a recursive named template:
<xsl:template name="distinct-values-from-list">
<xsl:param name="list"/>
<xsl:param name="delimiter" select="'|'"/>
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<xsl:variable name="token" select="substring-before($list, $delimiter)" />
<xsl:variable name="next-list" select="substring-after($list, $delimiter)" />
<!-- output token if it is unique -->
<xsl:if test="not(contains(concat($delimiter, $next-list, $delimiter), concat($delimiter, $token, $delimiter)))">
<xsl:value-of select="concat($token, $delimiter)"/>
</xsl:if>
<!-- recursive call -->
<xsl:call-template name="distinct-values-from-list">
<xsl:with-param name="list" select="$next-list"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$list"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Full demo: http://xsltransform.net/ncdD7mM
Added:
The above method outputs the last occurrence of each value in the list, because that's the simplest way to remove the duplicates.
The side effect of this is that the original order of the values is not preserved. Or - more correctly - it is the reverse order that is being preserved.
I would not think preserving the original forward order is of any importance here. But in case you do need it, it could be done this way (which I believe is much easier to follow than the suggested alternative):
<xsl:template name="distinct-values-from-list">
<xsl:param name="list"/>
<xsl:param name="delimiter" select="'|'"/>
<xsl:param name="result"/>
<xsl:choose>
<xsl:when test="$list">
<xsl:variable name="token" select="substring-before(concat($list, $delimiter), $delimiter)" />
<!-- recursive call -->
<xsl:call-template name="distinct-values-from-list">
<xsl:with-param name="list" select="substring-after($list, $delimiter)"/>
<xsl:with-param name="result">
<xsl:value-of select="$result"/>
<!-- add token if this is its first occurrence -->
<xsl:if test="not(contains(concat($delimiter, $result, $delimiter), concat($delimiter, $token, $delimiter)))">
<xsl:value-of select="concat($delimiter, $token)"/>
</xsl:if>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($result, 2)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Assuming that you can use XSLT 2.0, and assuming that the input looks like
<?xml version="1.0" encoding="UTF-8"?>
<root>1111-1|1111-1|1111-3|1111-4|1111-5|1111-3</root>
you could use the distinct-values and tokenize functions:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="/root">
<result>
<xsl:value-of separator="|" select="distinct-values(tokenize(.,'\|'))"/>
</result>
</xsl:template>
</xsl:transform>
And the result will be
<?xml version="1.0" encoding="UTF-8"?>
<result>1111-1|1111-3|1111-4|1111-5</result>
I have adapted a stylesheet below from (XSLT 1.0 How to get distinct values)
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output omit-xml-declaration="yes"/>
<xsl:template match="/">
<output>
<xsl:call-template name="distinctvalues">
<xsl:with-param name="values" select="root"/>
</xsl:call-template>
</output>
</xsl:template>
<xsl:template name="distinctvalues">
<xsl:param name="values"/>
<xsl:variable name="firstvalue" select="substring-before($values, '|')"/>
<xsl:variable name="restofvalue" select="substring-after($values, '|')"/>
<xsl:if test="not(contains($values, '|'))">
<xsl:value-of select="$values"/>
</xsl:if>
<xsl:if test="contains($restofvalue, $firstvalue) = false">
<xsl:value-of select="$firstvalue"/>
<xsl:text>|</xsl:text>
</xsl:if>
<xsl:if test="$restofvalue != ''">
<xsl:call-template name="distinctvalues">
<xsl:with-param name="values" select="$restofvalue" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
with a sample input of:
<root>1111-1|1111-1|1111-3|1111-4|1111-5|1111-3</root>
and the output is
<output>1111-1|1111-4|1111-5|1111-3</output>
**** EDIT ****
per Michael's comment below, here is the revised stylesheet which uses a saxon extension:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:saxon="http://icl.com/saxon"
exclude-result-prefixes="saxon"
version="1.1">
<xsl:output omit-xml-declaration="yes"/>
<xsl:variable name="aaa">
<xsl:call-template name="tokenizeString">
<xsl:with-param name="list" select="root"/>
<xsl:with-param name="delimiter" select="'|'"/>
</xsl:call-template>
</xsl:variable>
<xsl:template match="/">
<xsl:for-each select="saxon:node-set($aaa)/token[not(preceding::token/. = .)]">
<xsl:if test="position() > 1">
<xsl:text>|</xsl:text>
</xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
<xsl:template name="tokenizeString">
<!--passed template parameter -->
<xsl:param name="list"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<token>
<!-- get everything in front of the first delimiter -->
<xsl:value-of select="substring-before($list,$delimiter)"/>
</token>
<xsl:call-template name="tokenizeString">
<!-- store anything left in another variable -->
<xsl:with-param name="list" select="substring-after($list,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="$list = ''">
<xsl:text/>
</xsl:when>
<xsl:otherwise>
<token>
<xsl:value-of select="$list"/>
</token>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
given an input of:
<root>cat|cat|catalog|catalog|red|red|wired|wired</root>
it outputs
cat|catalog|red|wired
and with this input:
<root>1111-1|1111-1|1111-3|1111-4|1111-5|1111-3</root>
the output is
1111-1|1111-3|1111-4|1111-5

Function that can be use to omit duplicate value on a string

I would like to ask if there is a function that can be use to to remove a duplicate value inside a string separated by | simplest possible way. I have below example of the string
1111-1|1111-1|1111-3|1111-4|1111-5|1111-3
the output that I'm expecting is:
1111-1|1111-3|1111-4|1111-5
Thanks in advance.
All presented XSLT 1.0 solutions so far produce the wrong result:
1111-1|1111-4|1111-5|1111-3
whereas the wanted, correct result is:
1111-1|1111-3|1111-4|1111-5
Now, the following transformation (no extensions, pure XSLT 1.0):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="text()" name="distinctSubstrings">
<xsl:param name="pText" select="."/>
<xsl:param name="poutDelim"/>
<xsl:param name="pFoundDistinctSubs" select="'|'"/>
<xsl:param name="pCountDistinct" select="0"/>
<xsl:if test="$pText">
<xsl:variable name="vnextSub" select="substring-before(concat($pText, '|'), '|')"/>
<xsl:variable name="vIsNewDistinct" select=
"not(contains(concat($pFoundDistinctSubs, '|'), concat('|', $vnextSub, '|')))"/>
<xsl:variable name="vnextDistinct" select=
"substring(concat($poutDelim,$vnextSub), 1 div $vIsNewDistinct)"/>
<xsl:value-of select="$vnextDistinct"/>
<xsl:variable name="vNewFoundDistinctSubs"
select="concat($pFoundDistinctSubs, $vnextDistinct)"/>
<xsl:variable name="vnextOutDelim"
select="substring('|', 2 - ($pCountDistinct > 0))"/>
<xsl:call-template name="distinctSubstrings">
<xsl:with-param name="pText" select="substring-after($pText, '|')"/>
<xsl:with-param name="pFoundDistinctSubs" select="$vNewFoundDistinctSubs"/>
<xsl:with-param name="pCountDistinct" select="$pCountDistinct + $vIsNewDistinct"/>
<xsl:with-param name="poutDelim" select="$vnextOutDelim"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document (with string value the provided string in the question):
<t>1111-1|1111-1|1111-3|1111-4|1111-5|1111-3</t>
produces the wanted, correct result:
1111-1|1111-3|1111-4|1111-5
Explanation:
All found distinct substrings are concatenated in the parameter $pFoundDistinctSubs -- whenever we get the next substring from the delimited input, we compare it to the distinct substrings passed in this parameter. This ensures that the first in order distinct substring will be output -- not the last as in the other two solutions.
We use conditionless value determination, based on the fact that XSLT 1.0 implicitly converts a Boolean false() to 0 and true() to 1 whenever it is used in a context that requires a numeric value. In particular, substring($x, 1 div true()) is equivalent to substring($x, 1 div 1) that is: substring($x, 1) and this is the entire string $x. On the other side, substring($x, 1 div false()) is equivalent to substring($x, 1 div 0) -- that is: substring($x, Infinity) and this is the empty string.
To know why avoiding conditionals is important: watch this Pluralsight course:
Tactical Design Patterns in .NET: Control Flow, by Zoran Horvat
To do this in pure XSLT 1.0, with no extension functions, you will need to use a recursive named template:
<xsl:template name="distinct-values-from-list">
<xsl:param name="list"/>
<xsl:param name="delimiter" select="'|'"/>
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<xsl:variable name="token" select="substring-before($list, $delimiter)" />
<xsl:variable name="next-list" select="substring-after($list, $delimiter)" />
<!-- output token if it is unique -->
<xsl:if test="not(contains(concat($delimiter, $next-list, $delimiter), concat($delimiter, $token, $delimiter)))">
<xsl:value-of select="concat($token, $delimiter)"/>
</xsl:if>
<!-- recursive call -->
<xsl:call-template name="distinct-values-from-list">
<xsl:with-param name="list" select="$next-list"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$list"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Full demo: http://xsltransform.net/ncdD7mM
Added:
The above method outputs the last occurrence of each value in the list, because that's the simplest way to remove the duplicates.
The side effect of this is that the original order of the values is not preserved. Or - more correctly - it is the reverse order that is being preserved.
I would not think preserving the original forward order is of any importance here. But in case you do need it, it could be done this way (which I believe is much easier to follow than the suggested alternative):
<xsl:template name="distinct-values-from-list">
<xsl:param name="list"/>
<xsl:param name="delimiter" select="'|'"/>
<xsl:param name="result"/>
<xsl:choose>
<xsl:when test="$list">
<xsl:variable name="token" select="substring-before(concat($list, $delimiter), $delimiter)" />
<!-- recursive call -->
<xsl:call-template name="distinct-values-from-list">
<xsl:with-param name="list" select="substring-after($list, $delimiter)"/>
<xsl:with-param name="result">
<xsl:value-of select="$result"/>
<!-- add token if this is its first occurrence -->
<xsl:if test="not(contains(concat($delimiter, $result, $delimiter), concat($delimiter, $token, $delimiter)))">
<xsl:value-of select="concat($delimiter, $token)"/>
</xsl:if>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($result, 2)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Assuming that you can use XSLT 2.0, and assuming that the input looks like
<?xml version="1.0" encoding="UTF-8"?>
<root>1111-1|1111-1|1111-3|1111-4|1111-5|1111-3</root>
you could use the distinct-values and tokenize functions:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="/root">
<result>
<xsl:value-of separator="|" select="distinct-values(tokenize(.,'\|'))"/>
</result>
</xsl:template>
</xsl:transform>
And the result will be
<?xml version="1.0" encoding="UTF-8"?>
<result>1111-1|1111-3|1111-4|1111-5</result>
I have adapted a stylesheet below from (XSLT 1.0 How to get distinct values)
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output omit-xml-declaration="yes"/>
<xsl:template match="/">
<output>
<xsl:call-template name="distinctvalues">
<xsl:with-param name="values" select="root"/>
</xsl:call-template>
</output>
</xsl:template>
<xsl:template name="distinctvalues">
<xsl:param name="values"/>
<xsl:variable name="firstvalue" select="substring-before($values, '|')"/>
<xsl:variable name="restofvalue" select="substring-after($values, '|')"/>
<xsl:if test="not(contains($values, '|'))">
<xsl:value-of select="$values"/>
</xsl:if>
<xsl:if test="contains($restofvalue, $firstvalue) = false">
<xsl:value-of select="$firstvalue"/>
<xsl:text>|</xsl:text>
</xsl:if>
<xsl:if test="$restofvalue != ''">
<xsl:call-template name="distinctvalues">
<xsl:with-param name="values" select="$restofvalue" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
with a sample input of:
<root>1111-1|1111-1|1111-3|1111-4|1111-5|1111-3</root>
and the output is
<output>1111-1|1111-4|1111-5|1111-3</output>
**** EDIT ****
per Michael's comment below, here is the revised stylesheet which uses a saxon extension:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:saxon="http://icl.com/saxon"
exclude-result-prefixes="saxon"
version="1.1">
<xsl:output omit-xml-declaration="yes"/>
<xsl:variable name="aaa">
<xsl:call-template name="tokenizeString">
<xsl:with-param name="list" select="root"/>
<xsl:with-param name="delimiter" select="'|'"/>
</xsl:call-template>
</xsl:variable>
<xsl:template match="/">
<xsl:for-each select="saxon:node-set($aaa)/token[not(preceding::token/. = .)]">
<xsl:if test="position() > 1">
<xsl:text>|</xsl:text>
</xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
<xsl:template name="tokenizeString">
<!--passed template parameter -->
<xsl:param name="list"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<token>
<!-- get everything in front of the first delimiter -->
<xsl:value-of select="substring-before($list,$delimiter)"/>
</token>
<xsl:call-template name="tokenizeString">
<!-- store anything left in another variable -->
<xsl:with-param name="list" select="substring-after($list,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="$list = ''">
<xsl:text/>
</xsl:when>
<xsl:otherwise>
<token>
<xsl:value-of select="$list"/>
</token>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
given an input of:
<root>cat|cat|catalog|catalog|red|red|wired|wired</root>
it outputs
cat|catalog|red|wired
and with this input:
<root>1111-1|1111-1|1111-3|1111-4|1111-5|1111-3</root>
the output is
1111-1|1111-3|1111-4|1111-5

xslt named template : how to write a template that creates a string representing the xpath of a given node

I'm trying to write a recursive named template that will show the path of a given node:
<?xml version="1.0"?>
<testfile>
<section>
<title>My Section</title>
<para>Trying to write a recursive function that will return a basic xpath of a given node; in the case of this node, I would want to return testfile/section/para, I don't need /testfile/section[1]/para[1] or anything like that. The issue I'm having is that in the case of a named template, I don't know how to select a different node and apply it to the named template.</para>
</section>
</testfile>
I'm trying this template :
<?xml version='1.0'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- stylesheet to test a named template trying to build an xpath for a given node -->
<xsl:output method="xml"/>
<xsl:template match="/">
<result>
<xsl:apply-templates/>
</result>
</xsl:template>
<xsl:template match="*">
<xsl:variable name="xpath">
<xsl:call-template name="getXpath">
<xsl:with-param name="pathText" select="''"/>
</xsl:call-template>
</xsl:variable>
<element>element name : <xsl:value-of select="name()"/> path : <xsl:value-of select="$xpath"/></element>
<xsl:apply-templates/>
</xsl:template>
<xsl:template name="getXpath">
<xsl:param name="pathText"/>
<xsl:message>top of get xpath func path text : <xsl:value-of select="$pathText"/> </xsl:message>
<xsl:choose>
<xsl:when test="ancestor::*">
<xsl:message><xsl:value-of select="name()"/> has a parent</xsl:message>
<xsl:call-template name="getXpath">
<xsl:with-param name="pathText">
<xsl:value-of select="name()"/> <xsl:text>/</xsl:text><xsl:value-of select="$pathText"/>
<!-- how to recursively call template with parent node? -->
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:message><xsl:value-of select="name()"/> has no parent!</xsl:message>
<xsl:value-of select="$pathText"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
As per the comment, I'm not sure how to apply a node other than the context node to the named template. The other strategy I tried was to send the node to the template as a param, but I don't know how(or if you can) apply an axis to a param, as in
$thisNode../*
etc.
I'm sure it's something simple that I'm missing...thanks.
You can indeed pass in the node as a param to the template....
<xsl:template name="getXpath">
<xsl:param name="pathText"/>
<xsl:param name="node" select="." />
To apply an axis to it, for example to test for ancestors, you would do this....
<xsl:when test="$node/ancestor::*">
And to pass its parent element to the template when you recursively call it, do this:
<xsl:with-param name="node" select="$node/parent::*" />
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:template match="/">
<result>
<xsl:apply-templates/>
</result>
</xsl:template>
<xsl:template match="*">
<xsl:variable name="xpath">
<xsl:call-template name="getXpath">
<xsl:with-param name="pathText" select="''"/>
</xsl:call-template>
</xsl:variable>
<element>element name : <xsl:value-of select="name()"/> path : <xsl:value-of select="$xpath"/></element>
<xsl:apply-templates/>
</xsl:template>
<xsl:template name="getXpath">
<xsl:param name="pathText"/>
<xsl:param name="node" select="." />
<xsl:message>top of get xpath func path text : <xsl:value-of select="$pathText"/> </xsl:message>
<xsl:choose>
<xsl:when test="$node/ancestor::*">
<xsl:message><xsl:value-of select="name($node)"/> has a parent</xsl:message>
<xsl:call-template name="getXpath">
<xsl:with-param name="pathText">
<xsl:value-of select="name($node)"/> <xsl:text>/</xsl:text><xsl:value-of select="$pathText"/>
</xsl:with-param>
<xsl:with-param name="node" select="$node/parent::*" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:message><xsl:value-of select="name($node)"/> has no parent!</xsl:message>
<xsl:value-of select="$pathText"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
An alternate approach is to use xsl:apply-templates, but with the mode parameter to keep it separate from other template matches. Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:template match="/">
<result>
<xsl:apply-templates/>
</result>
</xsl:template>
<xsl:template match="*">
<xsl:variable name="xpath">
<xsl:apply-templates select="." mode="getXpath">
<xsl:with-param name="pathText" select="''"/>
</xsl:apply-templates>
</xsl:variable>
<element>element name : <xsl:value-of select="name()"/> path : <xsl:value-of select="$xpath"/></element>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="*" mode="getXpath">
<xsl:param name="pathText"/>
<xsl:message>top of get xpath func path text : <xsl:value-of select="$pathText"/> </xsl:message>
<xsl:choose>
<xsl:when test="ancestor::*">
<xsl:message><xsl:value-of select="name()"/> has a parent</xsl:message>
<xsl:apply-templates select=".." mode="getXpath">
<xsl:with-param name="pathText">
<xsl:value-of select="name()"/> <xsl:text>/</xsl:text><xsl:value-of select="$pathText"/>
</xsl:with-param>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:message><xsl:value-of select="name()"/> has no parent!</xsl:message>
<xsl:value-of select="$pathText"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
You shouldn't have to pass a node as a param if you just do an xsl:for-each.
Here's a modified example of your XSLT. (Notice that the positional predicates are only output in the path if they are needed.)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- stylesheet to test a named template trying to build an xpath for a given node -->
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<result>
<xsl:apply-templates/>
</result>
</xsl:template>
<xsl:template match="*">
<xsl:variable name="xpath">
<xsl:call-template name="getXpath"/>
</xsl:variable>
<element>element name : <xsl:value-of select="name()"/> path : <xsl:value-of select="$xpath"/></element>
<xsl:apply-templates/>
</xsl:template>
<xsl:template name="getXpath">
<xsl:for-each select="ancestor-or-self::*">
<xsl:value-of select="concat('/',local-name())"/>
<!--Predicate is only output when needed.-->
<xsl:if test="(preceding-sibling::*|following-sibling::*)[local-name()=local-name(current())]">
<xsl:value-of select="concat('[',count(preceding-sibling::*[local-name()=local-name(current())])+1,']')"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
Output (using the input from the question)
<result>
<element>element name : testfile path : /testfile</element>
<element>element name : section path : /testfile/section</element>
<element>element name : title path : /testfile/section/title</element>
<element>element name : para path : /testfile/section/para</element>
</result>
For what it's worth, I wrote a simple XPath generation template about a decade ago, in part 2 of my "styling stylesheets" article on DeveloperWorks:
Listing 4. Template that generates a Pseudo XPath in XSLT
<xsl:template name="pseudo-xpath-to-current-node">
<!-- Special-case for the root node, which otherwise
wouldn't generate any path at all. A bit of a kluge,
but it's simple and efficient. -->
<xsl:if test="not(parent::node())">
<xsl:text>/</xsl:text>
</xsl:if>
<xsl:for-each select="ancestor-or-self::node()">
<xsl:choose>
<xsl:when test="not(parent::node())">
<!-- This clause recognizes the root node, which doesn't need
to be explicitly represented in the XPath. -->
</xsl:when>
<xsl:when test="self::text()">
<xsl:text>/text()[</xsl:text>
<xsl:number level="single"/>
<xsl:text>]</xsl:text>
</xsl:when>
<xsl:when test="self::comment()">
<xsl:text>/comment()[</xsl:text>
<xsl:number level="single"/>
<xsl:text>]</xsl:text>
</xsl:when>
<xsl:when test="self::processing-instruction()">
<xsl:text>/processing-instruction()[</xsl:text>
<xsl:number level="single"/>
<xsl:text>]</xsl:text>
</xsl:when>
<xsl:when test="self::*">
<!-- This test for Elements works because the Principal
Node Type of the self:: axis happens to be Element.
-->
<xsl:text>/</xsl:text>
<xsl:value-of select="name(.)"/>
<xsl:text>[</xsl:text>
<xsl:number level="single"/>
<xsl:text>]</xsl:text>
</xsl:when>
<xsl:when test="self::node()[name()='xmlns' | starts-with(name(),'xmlns:')]">
<!-- This recognizes namespace nodes, though it's a bit
ugly. XSLT 1.0 doesn't seem to have a more elegant
test. XSLT 2.0 is expected to deprecate the whole
concept of namespace nodes, so it may become a moot
point.
NS nodes are unique; a count isn't required. -->
<xsl:text>/namespace::</xsl:text>
<xsl:value-of select="local-name(.)"/>
</xsl:when>
<xsl:otherwise>
<!-- If I've reached this clause, the node must be an
attribute. Attributes are unique; a count is not
required. -->
<xsl:text>/#</xsl:text>
<xsl:value-of select="name(.)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
That was an XSLT 1.0 solution, structured for clarity. It's probably possible to simplify it, especially if you're using XSLT and XPath 2.0.
As I explained there, this "pseudo-XPath" version ignores the namespace issue, since I didn't need it for that proof-of-concept tool and since it was intended for human-readable messages rather than for execution. It could be corrected to manage namespaces properly by changing it to write out paths that specify node type with a predicate explicitly testing localname and namespace URI. The resulting paths would be bulkier and harder for humans to process. Exercise for the reader, if you're so inclined.
You might also be able to replace the positional index with something more expressive... but knowing what's going to be meaningful is not easy.
Hope that helps. Have fun.
(Oh, almost forgot: I wouldn't be surprised if there are other solutions on the XSLT FAQ site.)
I think you want something like this:
<xsl:variable name="get.path">
<xsl:text> /</xsl:text>
<xsl:for-each select="ancestor-or-self::*">
<xsl:variable name="get.current.node" select="name(.)"/>
<xsl:value-of select="name()"/>
<xsl:text>[</xsl:text>
<xsl:value-of select="count(preceding-sibling::*[name(.) = $get.current.node]) + 1"/>
<xsl:text>]</xsl:text>
<xsl:if test="position() != last()">
<xsl:text>/</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:variable>

Adding numbers in a string using XSLT

I have a string (in a variable) that has a list of numbers separated by space or comma.
I need to sum the numbers in the string.
example string "1,2,5,12,3"
or "1 2 5 12 3"
Is there a way to add the numbers within the string and return the total?
This much shorter transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="text()" name="sumStringList">
<xsl:param name="pText" select="."/>
<xsl:param name="pSum" select="0"/>
<xsl:param name="pDelim" select="','"/>
<xsl:choose>
<xsl:when test="not(string-length($pText) >0)">
<xsl:value-of select="$pSum"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vnewList"
select="concat($pText,$pDelim)"/>
<xsl:variable name="vHead" select=
"substring-before($vnewList, $pDelim)"/>
<xsl:call-template name="sumStringList">
<xsl:with-param name="pText" select=
"substring-after($pText, $pDelim)"/>
<xsl:with-param name="pSum" select="$pSum+$vHead"/>
<xsl:with-param name="pDelim" select="$pDelim"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when applied on the following XML document:
<t>1,2,5,12,3</t>
produces the wanted, correct result:
23
Explanation: Recursively called named template that also matches a text node. A sentinel (appended comma) is added to speed up and streamline processing.
II. XSLT 2.0 solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:param name="pDelim" select="','"/>
<xsl:template match="text()">
<xsl:sequence select=
"sum(for $s in tokenize(.,$pDelim)
return number($s)
)
"/>
</xsl:template>
</xsl:stylesheet>
When applied on the same XML document (above), this transformation produces the same wanted, correct answer:
23
Here we use the standard XPath 2.0 function tokenize() and we must convert every resulting token to number (using the number() function) before finally applying the standard XPath function sum().
I don't know XSLT, but generally you would split the string using spaces and commas as separators.
After a quick search I found that you can use tokenize(string, separator) as the split function if you are using XSLT 2.0. This page has an example on how to use tokenize.
Here is an XSLT 1.0 solution
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<xsl:variable name="listOfValues" select="'1,2,5,12,3'" />
<xsl:call-template name="splitAndAdd">
<xsl:with-param name="list" select="$listOfValues"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="splitAndAdd">
<xsl:param name="list" />
<xsl:param name="delimiter" select="','"/>
<xsl:param name="total" select="0" />
<xsl:variable name="newList">
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<xsl:value-of select="normalize-space($list)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(normalize-space($list),$delimiter)" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="token"
select="substring-before($newList, $delimiter)" />
<xsl:variable name="remaining"
select="normalize-space(substring-after($newList, $delimiter))" />
<xsl:variable name="newTotal" select="$total + number($token)" />
<xsl:choose>
<xsl:when test="$remaining">
<xsl:call-template name="splitAndAdd">
<xsl:with-param name="delimiter" select="$delimiter"/>
<xsl:with-param name="list" select="$remaining"/>
<xsl:with-param name="total" select="$newTotal" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$newTotal" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

Is it possible to slice off the end of a URL with XSLT 1.0?

Using only XSLT 1.0's string functions, how would I go about slicing off the end of a url?
So from
http://stackoverflow.com/questions/2981175/is-it-possible-to-slice-the-end-of-a-url-with-xslt-1-0
I would like to extract
is-it-possible-to-slice-the-end-of-a-url-with-xslt-1-0
Is this possible?
Unfortunately there is no substring-after-last function in XSLT/XPath 1.0. So to get the last part of an URL you would have to write a recursive template as explained by Jeni Tenisson:
<xsl:template name="substring-after-last">
<xsl:param name="string" />
<xsl:param name="delimiter" />
<xsl:choose>
<xsl:when test="contains($string, $delimiter)">
<xsl:call-template name="substring-after-last">
<xsl:with-param name="string"
select="substring-after($string, $delimiter)" />
<xsl:with-param name="delimiter" select="$delimiter" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise><xsl:value-of select="$string" /></xsl:otherwise>
</xsl:choose>
</xsl:template>
This template would be called e.g. like this:
<xsl:call-template name="substring-after-last">
<xsl:with-param name="string" select="$url" />
<xsl:with-param name="delimiter" select="'/'" />
</xsl:call-template>
I. Using a recursively called named template:
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:call-template name="eatAllSlashes">
<xsl:with-param name="pText" select="."/>
</xsl:call-template>
</xsl:template>
<xsl:template name="eatAllSlashes">
<xsl:param name="pText"/>
<xsl:choose>
<xsl:when test="not(contains($pText,'/'))">
<xsl:value-of select="$pText"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="eatAllSlashes">
<xsl:with-param name="pText"
select="substring-after($pText, '/')"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<t>http://stackoverflow.com/questions/2981175/is-it-possible-to-slice-the-end-of-a-url-with-xslt-1-0</t>
produces the wanted, correct output:
is-it-possible-to-slice-the-end-of-a-url-with-xslt-1-0
II. Using the FXSL library:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my" exclude-result-prefixes="xsl my">
<xsl:import href="iter.xsl"/>
<xsl:output method="text"/>
<my:condition/>
<my:skipSlash/>
<xsl:variable name="vfunCondition"
select="document('')/*/my:condition"/>
<xsl:variable name="vfunSkipSlash"
select="document('')/*/my:skipSlash"/>
<xsl:template match="/">
<xsl:call-template name="iterUntil">
<xsl:with-param name="pCond" select="$vfunCondition"/>
<xsl:with-param name="pFun" select="$vfunSkipSlash"/>
<xsl:with-param name="arg1" select="string(/)"/>
</xsl:call-template>
</xsl:template>
<xsl:template match="my:condition">
<xsl:param name="arg1"/>
<xsl:value-of select="number(not(contains($arg1, '/')))"/>
</xsl:template>
<xsl:template match="my:skipSlash">
<xsl:param name="arg1"/>
<xsl:value-of select="substring-after($arg1, '/')"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on this XML document:
<t>http://stackoverflow.com/questions/2981175/is-it-possible-to-slice-the-end-of-a-url-with-xslt-1-0</t>
the wanted result is produced:
is-it-possible-to-slice-the-end-of-a-url-with-xslt-1-0
Do note:
The template iterUntil has three parameters:
-- pCond -- a function (template reference) that checks a condition on the current result and potentially issues a "stop signal" (1).
-- pFun -- a function (template reference) that is used to produce the next current result from the current result (or initially from the input argument $arg1).
-- arg1 -- the input argument on which the pFun function is initially applied.
Our pCond function is the template that matches my:condition. It issues the "stop signal" (outputs 1) only if the string passed as $arg1 does not contain any '/' characters.
Our pFun function is the template that matches my:skipSlash. It discards all characters up to and including the first '/' in the string $arg1
The initial input argument is defined in $arg1 and is the string value from which only the text after the last '/' must be produced.
The main advantage of using FXSL is that this avoids the need to code explicit recursion and the possibilities for errors doing this. Also, the template/functions are very generic and powerful and can be re-used for solving huge classes of similar problems.