XSL : Replace value inside tag - xslt

Input:
<Remarks>Random data## B2B## abc,controls,free text ## B2B## random data</Remarks>
The XSL should replace
"## B2B## abc,controls,free text ## B2B##"
in the Remarks tag with
"value1:abc,value2:controls,value3:free text"
Desired output:
<Remarks>Random data,value1:abc,value2:controls,value3:free text,random data</Remarks>
Note : the values inside ## B2B## tags are unknown and change everytime, for now I gave the sample values.

Assuming there will always be exactly one "placeholder" delimited by ## B2B##, and that it will always contain exactly 3 comma-delimited "tokens", you could do this quite simply by:
<xsl:template match="Remarks">
<xsl:copy>
<xsl:value-of select="substring-before(., '## B2B##')" />
<xsl:variable name="placeholder" select="substring-before(substring-after(., '## B2B##'), '## B2B##')" />
<xsl:text> value1: </xsl:text>
<xsl:value-of select="substring-before($placeholder, ',')" />
<xsl:text> value2: </xsl:text>
<xsl:value-of select="substring-before(substring-after($placeholder, ','), ',')" />
<xsl:text> value3: </xsl:text>
<xsl:value-of select="substring-after(substring-after($placeholder, ','), ',')" />
<xsl:value-of select="substring-after(substring-after(., '## B2B##'), '## B2B##')" />
</xsl:copy>
</xsl:template>

This solution is somewhat complex. It uses one template to extract the string between the tags and another to separate this string by the delimiter. What string is replaced by what other string is outsourced to another file containing the mapping (replacement.xml). Because XSLT-1.0 returns RTFs(Resulting Tree Fragments) in variables, the replacement is hardcoded in the template. In XSLT-2.0 you could use a variable for that making the solution more flexible.
Here is the replacement.xml containing the mappings:
<?xml version="1.0" encoding="UTF-8"?>
<replacement>
<r src="abc">value1</r>
<r src="controls">value2</r>
<r src="free text">value3</r>
</replacement>
And the XSLT incorporating these mappings into the string:
<?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="/lines/Remarks">
<xsl:variable name="tagName" select="'## B2B##'" />
<xsl:variable name="subStrToReplace">
<xsl:call-template name="strInTag">
<xsl:with-param name="str" select="text()" />
<xsl:with-param name="tagName" select="$tagName" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="replacementString">
<xsl:call-template name="splitAndReplace">
<xsl:with-param name="pText" select="$subStrToReplace" />
</xsl:call-template>
</xsl:variable>
<Remarks>
<xsl:value-of select="concat(normalize-space(substring-before(text(),$tagName)), ',',$replacementString, normalize-space(substring-after(substring-after(text(),$tagName),$tagName)))" />
</Remarks>
</xsl:template>
<xsl:template name="strInTag">
<xsl:param name="str" />
<xsl:param name="tagName" />
<xsl:value-of select="normalize-space(substring-before(substring-after($str,$tagName),$tagName))" />
</xsl:template>
<xsl:template name="splitAndReplace">
<xsl:param name="pText"/>
<xsl:if test="string-length($pText) > 0">
<xsl:variable name="vNextItem" select="substring-before(concat($pText, ','), ',')"/>
<xsl:value-of select="concat($vNextItem,':',document('replacement.xml')/replacement/r[#src = $vNextItem]/text(),',')"/>
<xsl:call-template name="splitAndReplace">
<xsl:with-param name="pText" select="substring-after($pText, ',')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

Related

XSLT 1.0: escaping double quotes

I have a XML file following this scheme:
<translationData>
<product>
<attributeValue>
<value>1/4"</value>
<value1>1/4"</value1>
<currentValue>aaaa;bbbb</currentValue>
</attributeValue>
</product>
</translationData>
because of the semicolon in "currentValue" i need to escape the semicolon AND the double quotes in "value".
I am able to escape the semicolon by placing all text in qoutes as following:
XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="utf-8" />
<xsl:param name="delim" select="';'" />
<xsl:param name="quote" select="'"'" />
<xsl:param name="break" select="'
'" />
<xsl:template match="/">
<xsl:apply-templates select="translationData/product/attributeValue" />
</xsl:template>
<xsl:template match="attributeValue">
<xsl:apply-templates />
<xsl:if test="following::*">
<xsl:value-of select="$break" />
</xsl:if>
</xsl:template>
<xsl:template match="*">
<!-- remove normalize-space() if you want keep white-space at it is -->
<xsl:value-of select="concat($quote, translate(.,'"','\"'), $quote)" />
<xsl:if test="following-sibling::*">
<xsl:value-of select="$delim" />
</xsl:if>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
but somehow the Output is:
"1/4\";"1/4\";"aaaa;bbbb"
instead of
"1/4\"";"1/4\"";"aaaa;bbbb"
Where am I going wrong?
I am new to XML and XSLT and did not find any question handling this specific case.
XSLT code is from an answer by #Tomalak for another question. see here
The translate() function will only replace each single character with another single character.
To replace a single character " with a two-character string\" you need to use a named recursive template or - if your processor supports it - an extension function such as EXSLT str:replace().
Here's an example of using a recursive template:
...
<xsl:template match="*">
<xsl:text>"</xsl:text>
<xsl:call-template name="replace">
<xsl:with-param name="text" select="."/>
</xsl:call-template>
<xsl:text>"</xsl:text>
<xsl:if test="position()!=last()">
<xsl:text>;</xsl:text>
</xsl:if>
</xsl:template>
...
<xsl:template name="replace">
<xsl:param name="text"/>
<xsl:param name="searchString">"</xsl:param>
<xsl:param name="replaceString">\"</xsl:param>
<xsl:choose>
<xsl:when test="contains($text,$searchString)">
<xsl:value-of select="substring-before($text,$searchString)"/>
<xsl:value-of select="$replaceString"/>
<!-- recursive call -->
<xsl:call-template name="replace">
<xsl:with-param name="text" select="substring-after($text,$searchString)"/>
<xsl:with-param name="searchString" select="$searchString"/>
<xsl:with-param name="replaceString" select="$replaceString"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
...

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

How to replace all special characters in a string with spaces?

I am using XSLT 1.0.
Suppose I have a string similar to "apple-mango%also|there"
I am trying to replace all the non-alphanumeric characters with spaces.
I tried
<xsl:value-of select="translate(., translate(., '0123456789abcdefghijklmnopqrstuvwxysABCDEFGHIJKLMNOPQRSTUVWXYZ', ''), ' ')"/>
but it didn't work.
The trouble is with the outer translate.
As i understand, in a translate() the length of the third string should be same as that of second string or else the missing characters will be taken to be replaced by an empty string ('').
The inner translate works fine since I want to remove all characters with an empty string anyways.
But the outer translate only replaces the first character of the second argument string with a space and replaces rest with an empty string.
Since my list of non-alphanumeric characters in the second argument of the outer translate is dynamic I can't pre-code the third argument.
ex:
My inner translate will return -%|. Which is correct.
Now my outer translate is translate(., '-%|', ' ').
Which returns apple mangoalsothere.
How can it be done short of writing something like this:
translate(., '`~!##$%^&*()-_=+[]{}\|;:'",<.>/?', ' ')
Another way you could look at this is to use the result of the "inner translate" - i.e the string containing all the unwanted characters - as a parameter in a named recursive template that would replace them, one-by-one, by a space:
XML
<input>alpha-bravo/charlie#delta...echo?foxtrot%golf|hotel india-juliet</input>
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/">
<output>
<xsl:call-template name="tokenize">
<xsl:with-param name="string" select="input"/>
<xsl:with-param name="delimiters" select="translate(input, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', '')"/>
</xsl:call-template>
</output>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="string"/>
<xsl:param name="delimiters"/>
<xsl:choose>
<xsl:when test="$delimiters">
<xsl:variable name="delimiter" select="substring($delimiters, 1, 1)" />
<xsl:value-of select="substring-before($string, $delimiter)" />
<xsl:text> </xsl:text>
<!-- recursive call -->
<xsl:call-template name="tokenize">
<xsl:with-param name="string" select="substring-after($string, $delimiter)"/>
<xsl:with-param name="delimiters" select="substring($delimiters, 2)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="utf-8"?>
<output>alpha bravo charlie delta echo foxtrot golf hotel india juliet</output>
One way to do this would be to create a recursive template to create a string of nothing but spaces for a given length
<xsl:template name="AllSpaces">
<xsl:param name="spaces" />
<xsl:if test="$spaces > 0">
<xsl:text> </xsl:text>
<xsl:call-template name="AllSpaces">
<xsl:with-param name="spaces" select="$spaces - 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
Then, you can generate a string with the number of spaces equal to the length of the string you are working with.
<xsl:variable name="specialchars" select="translate(., '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', '')" />
<xsl:variable name="spaces">
<xsl:call-template name="AllSpaces">
<xsl:with-param name="spaces" select="string-length($specialchars)" />
</xsl:call-template>
</xsl:variable>
You can then use this spaces variable in your translate. For example, try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text" />
<xsl:template match="data">
<xsl:variable name="specialchars" select="translate(., '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', '')" />
<xsl:variable name="spaces">
<xsl:call-template name="AllSpaces">
<xsl:with-param name="spaces" select="string-length($specialchars)" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="translate(., $specialchars, $spaces)"/>
</xsl:template>
<xsl:template name="AllSpaces">
<xsl:param name="spaces" />
<xsl:if test="$spaces > 0">
<xsl:text> </xsl:text>
<xsl:call-template name="AllSpaces">
<xsl:with-param name="spaces" select="$spaces - 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Now, if you had multiple strings you wanted to replace in your XML, you could slightly improve things by having a global variable for spaces that was equal to the length of the longest string. This would give you more spaces than you needed, but that would not be a problem.
Try this XSLT too
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text" />
<xsl:variable name="spaces">
<xsl:for-each select="//data">
<xsl:sort select="string-length(.)" order="descending" />
<xsl:if test="position() = 1">
<xsl:call-template name="AllSpaces">
<xsl:with-param name="spaces" select="string-length(.)" />
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:template match="data">
<xsl:value-of select="translate(., translate(., '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', ''), $spaces)"/>
</xsl:template>
<xsl:template name="AllSpaces">
<xsl:param name="spaces" />
<xsl:if test="$spaces > 0">
<xsl:text> </xsl:text>
<xsl:call-template name="AllSpaces">
<xsl:with-param name="spaces" select="$spaces - 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When applied to this XML
<test>
<data>apple-mango%also|there</data>
<data>apple-mango%also|there!test</data>
</test>
The following is output
apple mango also there
apple mango also there test

How to remove particular characters from a string using XSLT?

I need to check if a particular string contains a a particular word for example to check if,
SultansOfSwing contains the word Swing.
Let me also mention that the value of the string in question is unknown. As in it can be any word so we do not know the length et cetera.
I understand I can do this by using the contains keyword.
But once I know that this word contains the Swing keyword I want to display the string without this "Swing" word.. thus effectively displaying only "SultansOf".
I have been trying to explore how I can achieve this but not getting any break through.
Could somebody please advise which keyword or function will provide this facility ? How can I remove a particular word from within a string.
Given this for input:
<root>
<song>SultansOfSwing</song>
<song>SwingOfSultans</song>
<song>SultansSwingOf</song>
</root>
The output of this:
<?xml version='1.0' ?>
<root>
<swing-less-long>SultansOf</swing-less-long>
<swing-less-long>OfSultans</swing-less-long>
<swing-less-long>SultansOf</swing-less-long>
</root>
Can be gotten from this. Note the use of substring-before and substring-after.
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="/">
<root>
<xsl:apply-templates select="root/song"/>
</root>
</xsl:template>
<xsl:template match="song">
<swing-less-long>
<xsl:if test="contains(., 'Swing')">
<xsl:call-template name="remove">
<xsl:with-param name="value" select="."/>
</xsl:call-template>
</xsl:if>
</swing-less-long>
</xsl:template>
<xsl:template name="remove">
<xsl:param name="value"/>
<xsl:value-of select="concat(substring-before($value, 'Swing'), substring-after($value, 'Swing'))"/>
</xsl:template>
</xsl:stylesheet>
I think this string replacement function is quite exhaustive:
EDIT - needed to change $string to $string2. Should work now
<xsl:template name="string-replace">
<xsl:param name="string1" select="''" />
<xsl:param name="string2" select="''" />
<xsl:param name="replacement" select="''" />
<xsl:param name="global" select="true()" />
<xsl:choose>
<xsl:when test="contains($string1, $string2)">
<xsl:value-of select="substring-before($string1, $string2)" />
<xsl:value-of select="$replacement" />
<xsl:variable name="rest" select="substring-after($string1, $string2)" />
<xsl:choose>
<xsl:when test="$global">
<xsl:call-template name="string-replace">
<xsl:with-param name="string1" select="$rest" />
<xsl:with-param name="string2" select="$string2" />
<xsl:with-param name="replacement" select="$replacement" />
<xsl:with-param name="global" select="$global" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$rest" />
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string1" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
It's case-sensitive, mind you. In your case:
<xsl:call-template name="string-replace">
<xsl:with-param name="string1" select="'SultansOfSwing'" />
<xsl:with-param name="string2" select="'Swing'" />
<xsl:with-param name="replacement" select="''" />
</xsl:call-template>