I need to split two tags with comma separated string into a list of parent-child tags as shown below.
For example, the input will be :-
<UserID>162,163</UserID>
<UserName>Stacy,Stephen</UserName>
Expected output :-
Expected Output
Please help to achieve this result using xslt code
I tried the following format which I got from another query, but its generating a nested pattern instead of the list :-
<xsl:template name="tokenize">
<xsl:param name="textID" select="."/>
<xsl:param name="textName" select="."/>
<xsl:param name="separator" select="','"/>
<User>
<xsl:choose>
<xsl:when test="not(contains($textID, $separator))">
<ID>
<xsl:value-of select="normalize-space($textID)"/>
</ID>
</xsl:when>
<xsl:otherwise>
<ID>
<xsl:value-of select="normalize-space(substring-before($textID, $separator))"/>
</ID>
<xsl:call-template name="tokenize">
<xsl:with-param name="textID" select="substring-after($textID, $separator)"/>
<xsl:with-param name="textName" select="substring-after($textName, $separator)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
<xsl:choose>
<xsl:when test="not(contains($textName, $separator))">
<Name>
<xsl:value-of select="normalize-space($textName)"/>
</Name>
</xsl:when>
<xsl:otherwise>
<Name>
<xsl:value-of select="normalize-space(substring-before($textName, $separator))"/>
</Name>
<xsl:call-template name="tokenize">
<xsl:with-param name="textID" select="substring-after($textID, $separator)"/>
<xsl:with-param name="textName" select="substring-after($textName, $separator)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</User>
</xsl:template>
There are few assumptions before simplifying the tokenize template shared in the XSLT code.
The count of comma separated values in <UserID> and <UserName> is always equal.
There is a 1-1 correspondence on the indexes of the values i.e. 162 <-> Stacy and 163 <-> Stephen.
XSLT version is 1.0
A parent node <UserList> has been added as a root node for the shared input XML.
XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*" />
<xsl:template match="UserList">
<xsl:copy>
<xsl:call-template name="tokenize">
<xsl:with-param name="textID" select="normalize-space(UserID)" />
<xsl:with-param name="textName" select="normalize-space(UserName)" />
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="textID" />
<xsl:param name="textName" />
<xsl:param name="separator" select="','" />
<xsl:choose>
<xsl:when test="not(contains($textID, $separator) and contains($textName, $separator)) ">
<User>
<ID><xsl:value-of select="$textID" /></ID>
<Name><xsl:value-of select="$textName" /></Name>
</User>
</xsl:when>
<xsl:otherwise>
<User>
<ID><xsl:value-of select="substring-before($textID, $separator)" /></ID>
<Name><xsl:value-of select="substring-before($textName, $separator)" /></Name>
</User>
<xsl:call-template name="tokenize">
<xsl:with-param name="textID" select="normalize-space(substring-after($textID, $separator))" />
<xsl:with-param name="textName" select="normalize-space(substring-after($textName, $separator))" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Output
<UserList>
<User>
<ID>162</ID>
<Name>Stacy</Name>
</User>
<User>
<ID>163</ID>
<Name>Stephen</Name>
</User>
</UserList>
Related
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
I have a string of single values and ranges, and I want to split them and put them in order of single values together (comma separated) and ranges together (comma separated), using XSLT1.0. I don't want the result in form of XML nodes, I do want the result on the same line. I have found that existing solutions here on Stack Exchange works, but it puts them on different lines as XML nodes.
The string is:
Input: <mark>9, 11, 345-900, 100-200, 1023, 200-400, 99</mark>
Desired Output: 9, 11, 1023, 99, 345-900, 100-200, 200-400
Code:
<?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" omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="mark">
<xsl:call-template name="tokenizeStringWithoutHyphen">
<xsl:with-param name="list" select="."/>
<xsl:with-param name="delimiter" select="','"/>
</xsl:call-template>
<xsl:call-template name="tokenizeStringWithHyphen">
<xsl:with-param name="list" select="."/>
<xsl:with-param name="delimiter" select="','"/>
</xsl:call-template>
</xsl:template>
<!--############################################################-->
<!--## Template to tokenize strings ##-->
<!--############################################################-->
<xsl:template name="separatestrings">
<xsl:param name="list"/>
<xsl:param name="delimiter"/>
</xsl:template>
<xsl:template name="tokenizeStringWithHyphen">
<!--passed template parameter -->
<xsl:param name="list"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<xsl:variable name="stringContainsHyphen" select="contains(substring-before($list,$delimiter),'-')"/>
<!--<test><xsl:value-of select="$stringContainsHyphen"/></test>-->
<xsl:choose>
<xsl:when test="$stringContainsHyphen = true()">
<!-- get everything in front of the first delimiter -->
<xsl:value-of select="normalize-space(substring-before($list,$delimiter))"/>
</xsl:when>
</xsl:choose>
<xsl:call-template name="tokenizeStringWithHyphen">
<!-- 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>
<xsl:value-of select="$list"/>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="tokenizeStringWithoutHyphen">
<!--passed template parameter -->
<xsl:param name="list"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<xsl:variable name="stringContainsHyphen" select="contains(substring-before($list,$delimiter),'-')"/>
<!--<test><xsl:value-of select="$stringContainsHyphen"/></test>-->
<xsl:choose>
<xsl:when test="$stringContainsHyphen = false()">
<!-- get everything in front of the first delimiter -->
<xsl:value-of select="normalize-space(substring-before($list,$delimiter))"/>
</xsl:when>
</xsl:choose>
<xsl:call-template name="tokenizeStringWithoutHyphen">
<!-- 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:choose>
</xsl:template>
</xsl:stylesheet>
I don't want the result in form of XML nodes, I do want the result on
the same line.
If you want to sort the values, you must tokenize them into nodes first. Then sort the nodes and output them back as text values in a single line:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="mark">
<xsl:variable name="tokens">
<xsl:call-template name="tokenize"/>
</xsl:variable>
<xsl:for-each select="exsl:node-set($tokens)/token">
<xsl:sort select="number(contains(., '-'))" data-type="number" order="ascending"/>
<xsl:value-of select="." />
<xsl:if test="position()!=last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="text" select="."/>
<xsl:param name="delimiter" select="', '"/>
<xsl:choose>
<xsl:when test="contains($text, $delimiter)">
<token>
<xsl:value-of select="substring-before($text, $delimiter)"/>
</token>
<!-- recursive call -->
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring-after($text, $delimiter)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<token>
<xsl:value-of select="$text"/>
</token>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I am in need to find and replace a entities and characters to strings. For example, should be replaced by ' ' empty space, $ should be replaced by |doll|, % should be replaced by |perc|. I can use XSLT 1.0.
XML document:
<?xml version="1.0"?>
<chapter>
<math>
<mtext>This is $400, 300%to500%.</mtext>
</math>
</chapter>
XSLT 1.0 transformation tried:
<?xml version='1.0'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="mtext">
<mtext>
<xsl:choose>
<xsl:when test="contains(.,' ') or contains(.,'$') or contains(.,'%')">
<xsl:if test="contains(.,' ')"><xsl:call-template name="replace-string"><xsl:with-param name="text" select="."/><xsl:with-param name="from"> </xsl:with-param><xsl:with-param name="to" select="' '"/></xsl:call-template><xsl:variable name="mtext_text" select="."/></xsl:if>
<xsl:variable name="mtext_text" select="."/>
<xsl:if test="contains(.,'$')"><xsl:call-template name="replace-string"><xsl:with-param name="text" select="mtext_text"/><xsl:with-param name="from">$</xsl:with-param><xsl:with-param name="to" select="'|doll|'"/></xsl:call-template><xsl:variable name="mtext_text" select="."/></xsl:if>
<xsl:variable name="mtext_text" select="."/>
<xsl:if test="contains(.,'%')"><xsl:call-template name="replace-string"><xsl:with-param name="text" select="mtext_text"/><xsl:with-param name="from">%</xsl:with-param><xsl:with-param name="to" select="'|perc|'"/></xsl:call-template></xsl:if>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates/>
</xsl:otherwise>
</xsl:choose>
</mtext>
</xsl:template>
<xsl:template name="replace-string">
<xsl:param name="text"/>
<xsl:param name="from"/>
<xsl:param name="to"/>
<xsl:choose>
<xsl:when test="contains($text, $from)">
<xsl:variable name="before" select="substring-before($text, $from)"/>
<xsl:variable name="after" select="substring-after($text, $from)"/>
<xsl:variable name="prefix" select="concat($before, $to)"/>
<xsl:copy-of select="$before"/>
<xsl:value-of select="$to" disable-output-escaping="yes"/>
<xsl:call-template name="replace-string">
<xsl:with-param name="text" select="$after"/>
<xsl:with-param name="from" select="$from"/>
<xsl:with-param name="to" select="$to"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Required output:
<?xml version='1.0' ?>
<mtext>This is |doll|400, 300|perc|to500|perc|.</mtext>
This XSLT 1.0 transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my" extension-element-prefixes="xsl">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<my:reps>
<rep>
<old> </old>
<new xml:space="preserve"> </new>
</rep>
<rep>
<old>$</old>
<new>|doll|</new>
</rep>
<rep>
<old>%</old>
<new>|perc|</new>
</rep>
</my:reps>
<xsl:variable name="vReps" select="document('')/*/my:reps/*"/>
<xsl:template match="mtext">
<xsl:copy>
<xsl:call-template name="multiReplace"/>
</xsl:copy>
</xsl:template>
<xsl:template name="multiReplace">
<xsl:param name="pText" select="."/>
<xsl:param name="pRep" select="$vReps[1]"/>
<xsl:choose>
<xsl:when test="not($pRep)"><xsl:value-of select="$pText"/></xsl:when>
<xsl:otherwise>
<xsl:variable name="vReplaced">
<xsl:call-template name="replace">
<xsl:with-param name="pText" select="$pText"/>
<xsl:with-param name="pOld" select="$pRep/old"/>
<xsl:with-param name="pNew" select="$pRep/new"/>
</xsl:call-template>
</xsl:variable>
<xsl:call-template name="multiReplace">
<xsl:with-param name="pText" select="$vReplaced"/>
<xsl:with-param name="pRep" select="$pRep/following-sibling::*[1]"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="replace">
<xsl:param name="pText"/>
<xsl:param name="pOld"/>
<xsl:param name="pNew"/>
<xsl:if test="$pText">
<xsl:value-of select="substring-before(concat($pText,$pOld), $pOld)"/>
<xsl:if test="contains($pText, $pOld)">
<xsl:value-of select="$pNew"/>
<xsl:call-template name="replace">
<xsl:with-param name="pText" select="substring-after($pText, $pOld)"/>
<xsl:with-param name="pOld" select="$pOld"/>
<xsl:with-param name="pNew" select="$pNew"/>
</xsl:call-template>
</xsl:if>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<chapter>
<math>
<mtext>This is $400, 300%to500%.</mtext>
</math>
</chapter>
produces the wanted, correct result:
<mtext>This is |doll|400, 300|perc|to500|perc|.</mtext>
I have a XML element with a value similar to the following.
<?xml version='1.0' encoding='UTF-8'?>
<Report_Data>
<Report_Entry>
<Address>1234 Address Line 1
Pleasanton, CA 94588
United States of America</Address>
</Report_Entry>
<Report_Entry>
<Address>1234 Address Line 1
5678 Address Line 2
Pleasanton, CA 94588
United States of America</Address>
</Report_Entry>
</Report_Data>
I am trying to count the # of occurences of the following value.
<xsl:variable name="String1" select="'
'"/>
What I am hoping to have in my output, is to create a new variable that is 2 for the first record and 3 for the second record.
Note that I would be running from a For-Each Report_Entry loop.
The template which you are looking is GetNoOfOccurance
<xsl:template name="GetNoOfOccurance">
<xsl:param name="String"/>
<xsl:param name="SubString"/>
<xsl:param name="Counter" select="0"/>
<xsl:variable name="sa" select="substring-after($String, $SubString)"/>
<xsl:choose>
<xsl:when test="$sa != '' or contains($String, $SubString)">
<xsl:call-template name="GetNoOfOccurance">
<xsl:with-param name="String" select="$sa"/>
<xsl:with-param name="SubString" select="$SubString"/>
<xsl:with-param name="Counter" select="$Counter + 1"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$Counter"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Call the template in the mentioned way:-
<xsl:variable name="searchStr" select="'
'"/>
<xsl:call-template name="GetNoOfOccurance">
<xsl:with-param name="String" select="text()"/>
<xsl:with-param name="SubString" select="$searchStr"/>
</xsl:call-template>
XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:variable name="searchStr" select="'
'"/>
<xsl:for-each select="//Address">
<xsl:call-template name="GetNoOfOccurance">
<xsl:with-param name="String" select="text()"/>
<xsl:with-param name="SubString" select="$searchStr"/>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="GetNoOfOccurance">
<xsl:param name="String"/>
<xsl:param name="SubString"/>
<xsl:param name="Counter" select="0"/>
<xsl:variable name="sa" select="substring-after($String, $SubString)"/>
<xsl:choose>
<xsl:when test="$sa != '' or contains($String, $SubString)">
<xsl:call-template name="GetNoOfOccurance">
<xsl:with-param name="String" select="$sa"/>
<xsl:with-param name="SubString" select="$SubString"/>
<xsl:with-param name="Counter" select="$Counter + 1"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$Counter"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
The template GetNoOfOccurance is taken from #Tomalak answer
You forgot to mention to XSLT version.
If you are using XSLT 2.0, the simplest way is to use the tokenize() function and subtract one, like so...
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:variable name="String1" select="'
'"/>
<xsl:template match="/*">
<xsl:for-each select="Report_Entry/Address">There are <xsl:value-of select="count(tokenize(concat(' ',.,' '),$String1)) - 1" /> occurrences.
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
...which gives this output for the sample intput...
There are 2 occurrences.
There are 3 occurrences.
There is XML document:
<data>how;many;i;can;tell;you</data>
Need to get XML using XSLT version 1:
<manydata>
<onedata>how</onedata>
<onedata>many</onedata>
<onedata>i</onedata>
<onedata>can</onedata>
<onedata>tell</onedata>
<onedata>you</onedata>
</manydata>
How I can do it?
UPDATE:
Output format must be XML.
This recursive solution is probably one of the shortest possible:
<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="data">
<manydata><xsl:apply-templates/></manydata>
</xsl:template>
<xsl:template match="text()" name="tokenize">
<xsl:param name="pText" select="."/>
<xsl:if test="string-length($pText)">
<onedata>
<xsl:value-of select=
"substring-before(concat($pText,';'),';')"/>
</onedata>
<xsl:call-template name="tokenize">
<xsl:with-param name="pText" select=
"substring-after($pText,';')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document;
<data>how;many;i;can;tell;you</data>
the wanted, correct result is produced:
<manydata>
<onedata>how</onedata>
<onedata>many</onedata>
<onedata>i</onedata>
<onedata>can</onedata>
<onedata>tell</onedata>
<onedata>you</onedata>
</manydata>
<xsl:template match="data">
<manydata>
<!--
empty <manydata/> will be generated,
if <data/> without child text()
-->
<xsl:apply-templates select="text()"/>
</manydata>
</xsl:template>
<xsl:template match="data/text()">
<!-- start point for recursion -->
<xsl:call-template name="tokenize-string">
<xsl:with-param name="separator" select="';'"/>
<xsl:with-param name="string" select="text()"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="tokenize-string">
<xsl:param name="separator"/>
<xsl:param name="string"/>
<xsl:variable name="string-before-separator"
select="substring-before( $string, $separator )"/>
<onedata>
<xsl:choose>
<!-- if $separator presents in $string take first piece -->
<xsl:when test="$string-before-separator">
<xsl:value-of select="$string-before-separator"/>
</xsl:when>
<!-- take whole $string, if no $separator in the $string -->
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</onedata>
<!-- recursive call, if separator was found -->
<xsl:if test="$string-before-separator">
<xsl:call-template name="tokenize-string">
<xsl:with-param name="separator" select="$separator"/>
<xsl:with-param name="string"
select="substring-after( $string, $separator )"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
Try this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="data">
<xsl:element name="manydata">
<xsl:call-template name="splitsemicolons">
<xsl:with-param name="text" select="text()" />
</xsl:call-template>
</xsl:element>
</xsl:template>
<xsl:template name="splitsemicolons">
<xsl:param name="text" />
<xsl:choose>
<xsl:when test="contains($text,';')">
<xsl:element name="onedata">
<xsl:value-of select="substring-before($text,';')" />
</xsl:element>
<xsl:call-template name="splitsemicolons">
<xsl:with-param name="text" select="substring-after($text,';')" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:element name="onedata">
<xsl:value-of select="$text" />
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
This uses a named template that is called recursively, each time outputting what's before the first ;, and calling itself with everything after the first ;. If there isn't a ;, it just outputs whatever's left as-is.
You can use the XSLT extension library to get the tokenize function. Here is how the final code will look like:
<xsl:template match="/">
<manydata>
<xsl:for-each select="str:tokenize(data, ';')">
<xsl:value-of select="." />
</xsl:for-each>
</manydata>
</xsl:template>
</xsl:stylesheet>
Please note you will have to import the extension library into you XSLT using:
<xsl:import href="path/str.xsl" />
before you use the library functions.