replacing copyright symbol and other symbols with elements in xslt - xslt

Input:
<text>
Please see the registered mark® .
Please see the copy right ©.
Please see the Trade mark™.
</text>
Output:
<text>
Please see the registered mark<registeredTrademark></registeredTrademark>.
Please see the copy right <copyright></copyright>.
Please see the Trade mark <trademark></trademark>.
</text>
I need to replace all special symbols with the elements as shown above
Can any one help.
Thanks

As this is XSLT 1.0, you are going to have to use a recursive named template to check each character in turn.
Firstly, it may be more flexible to create a sort of 'look-up' in your XSLT where you can specify a list of symbols and the required element name to replace them with
<lookup:codes>
<code symbol="®">registeredTrademark</code>
<code symbol="©">copyright</code>
<code symbol="™">trademark</code>
</lookup:codes>
(The 'lookup' namespace could actually be named anything, just as long as it is declard in the XSLT).
Then, to access this, you could define a variable to access this look-up
<xsl:variable name="lookup" select="document('')/*/lookup:codes"/>
And, to look-up an actually code based on a symbol would do something like this (where $text) is a variable that contains the text you are checking.
<xsl:variable name="char" select="substring($text, 1, 1)"/>
<xsl:variable name="code" select="$lookup/code[#symbol = $char]"/>
All the named template would do, is check the first character of the text, replacing it with an element if it exists in the lookup, and then recursively call the template with the remaining part of the text.
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:lookup="lookup" exclude-result-prefixes="lookup">
<xsl:output method="xml" indent="no"/>
<lookup:codes>
<code symbol="®">registeredTrademark</code>
<code symbol="©">copyright</code>
<code symbol="™">trademark</code>
</lookup:codes>
<xsl:variable name="lookup" select="document('')/*/lookup:codes"/>
<xsl:template match="text[text()]">
<text>
<xsl:call-template name="text"/>
</text>
</xsl:template>
<xsl:template name="text">
<xsl:param name="text" select="text()"/>
<xsl:variable name="char" select="substring($text, 1, 1)"/>
<xsl:variable name="code" select="$lookup/code[#symbol = $char]"/>
<xsl:choose>
<xsl:when test="$code"><xsl:element name="{$code}" /></xsl:when>
<xsl:otherwise>
<xsl:value-of select="$char"/>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="string-length($text) > 1">
<xsl:call-template name="text">
<xsl:with-param name="text" select="substring($text, 2, string-length($text) - 1)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output
<text>
Please see the registered mark<registeredTrademark /> .
Please see the copy right <copyright />.
Please see the Trade mark<trademark />.
</text>

This transformation is more efficient by avoiding char-by-char recursion and using "biggest-possible-step" recursion:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<my:reps>
<r char="®">registeredTrademark</r>
<r char="©">copyright</r>
<r char="™">trademark</r>
</my:reps>
<xsl:variable name="vReps" select="document('')/*/my:reps/*"/>
<xsl:template match="text()" name="multReplace">
<xsl:param name="pText" select="."/>
<xsl:param name="pReps" select="$vReps"/>
<xsl:if test="$pText">
<xsl:variable name="vTarget" select="$pReps[1]/#char"/>
<xsl:choose>
<xsl:when test="not($vTarget)">
<xsl:value-of select="$pText"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vReplacement" select="$pReps[1]"/>
<xsl:call-template name="multReplace">
<xsl:with-param name="pText" select=
"substring-before(concat($pText, $vTarget), $vTarget)"/>
<xsl:with-param name="pReps" select="$pReps[position() >1]"/>
</xsl:call-template>
<xsl:if test="contains($pText, $vTarget)">
<xsl:element name="{$vReplacement}"/>
<xsl:call-template name="multReplace">
<xsl:with-param name="pText" select="substring-after($pText, $vTarget)"/>
<xsl:with-param name="pReps" select="$pReps"/>
</xsl:call-template>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When applied to the provided XML document:
<text>
Please see the registered mark® .
Please see the copy right ©.
Please see the Trade mark™.
</text>
the correctly-replaced text is produced:
Please see the registered mark<registeredTrademark/> .
Please see the copy right <copyright/>.
Please see the Trade mark<trademark/>.

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

Using a Map in XSL for expanding abbreviations

I saw a similar question on creating a Map.
That answer has this code:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:template match="/">
<xsl:variable name="map">
<map>
<entry key="key-1">value1</entry>
<entry key="key-2">value2</entry>
<entry key="key-3">value3</entry>
</map>
</xsl:variable>
<output>
<xsl:value-of select="msxsl:node-set($map)/map/entry[#key='key-1']"/>
</output>
</xsl:template>
I would like to replace the output command to use a value in my XML to see if it is a key in the map and then replace it with the value.
Is the best way to do a for-each select on the map and compare with contains?
Here is a snippet of the XML:
<document>
<content name="PART_DESC_SHORT" type="text" vse-streams="2" u="22" action="cluster" weight="1">
SCREW - ADJUST
</content>
</document>
The content node value may have a string containing an abbreviation that I want to replace with the full value.
Thanks,
Paul
No need to use a for-each - this XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:output indent="yes" method="xml" />
<xsl:variable name="abbreviations">
<abbreviation key="Brkt Pivot R">Bracket Pivot R</abbreviation>
<abbreviation key="Foo">Expanded Foo</abbreviation>
<abbreviation key="Bar">Expanded Bar</abbreviation>
</xsl:variable>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<xsl:variable name="text" select="."/>
<xsl:variable name="abbreviation" select="msxsl:node-set($abbreviations)/*[#key=$text]"/>
<xsl:choose>
<xsl:when test="$abbreviation">
<xsl:value-of select="$abbreviation"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
will convert any XML in an exact copy expanding all the text matching an abbreviation defined in the abbreviations variable at the top.
If you want to expand the abbreviations only within specific elements you can modify the second template match="..." rule.
On the other hand if you want to expand ANY occurance of all the abbreviations within the text you need loops - that means recursion in XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:output indent="yes" method="xml" />
<xsl:variable name="abbreviations">
<abbreviation key="Brkt">Bracket</abbreviation>
<abbreviation key="As">Assembly</abbreviation>
<abbreviation key="Foo">Expanded Foo</abbreviation>
<abbreviation key="Bar">Expanded Bar</abbreviation>
</xsl:variable>
<!-- Replaces all occurrences of a string with another within a text -->
<xsl:template name="replace">
<xsl:param name="text"/>
<xsl:param name="from"/>
<xsl:param name="to"/>
<xsl:choose>
<xsl:when test="contains($text,$from)">
<xsl:value-of select="concat(substring-before($text,$from),$to)"/>
<xsl:call-template name="replace">
<xsl:with-param name="text" select="substring-after($text,$from)"/>
<xsl:with-param name="from" select="$from"/>
<xsl:with-param name="to" select="$to"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Replace all occurences of a list of abbreviation with their expanded version -->
<xsl:template name="replaceAbbreviations">
<xsl:param name="text"/>
<xsl:param name="abbreviations"/>
<xsl:choose>
<xsl:when test="count($abbreviations)>0">
<xsl:call-template name="replaceAbbreviations">
<xsl:with-param name="text">
<xsl:call-template name="replace">
<xsl:with-param name="text" select="$text"/>
<xsl:with-param name="from" select="$abbreviations[1]/#key"/>
<xsl:with-param name="to" select="$abbreviations[1]"/>
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="abbreviations" select="$abbreviations[position()>1]"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<xsl:call-template name="replaceAbbreviations">
<xsl:with-param name="text" select="."/>
<xsl:with-param name="abbreviations" select="msxsl:node-set($abbreviations)/*"/>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
Applying this second XSLT to
<document>
<content name="PART_DESC_SHORT" type="text" vse-streams="2" u="22" action="cluster" weight="1">
Brkt Pivot R
</content>
</document>
produces
<document>
<content name="PART_DESC_SHORT" type="text" vse-streams="2" u="22" action="cluster" weight="1">
Bracket Pivot R
</content>
</document>
Note that:
this solution assumes that no abbreviation ovelap (e.g. two separate abbreviations Brk and Brkt)
it uses XSLT 1.0 - a better solution is probably possible with XSLT 2.0
this kind of heavy string processing is likely quite inefficient in XSLT, it is probably better to write an extension function in some other language and call it from the XSLT.

XSLT multiple string replacement with recursion

I have been attempting to perform multiple (different) string replacement with recursion and I have hit a roadblock. I have sucessfully gotten the first replacement to work, but the subsequent replacements never fire. I know this has to do with the recursion and how the with-param string is passed back into the call-template. I see my error and why the next xsl:when never fires, but I just cant seem to figure out exactly how to pass the complete modified string from the first xsl:when to the second xsl:when. Any help is greatly appreciated.
<xsl:template name="replace">
<xsl:param name="string" select="." />
<xsl:choose>
<xsl:when test="contains($string, '
')">
<xsl:value-of select="substring-before($string, '
')" />
<br/>
<xsl:call-template name="replace">
<xsl:with-param name="string" select="substring-after($string, '
')"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="contains($string, 'TXT')">
<xsl:value-of select="substring-before($string, '
TXT')" />
<xsl:call-template name="replace">
<xsl:with-param name="string" select="substring-after($string, '
')" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
This transformation is fully parameterized and doesn't need any tricks with default namespaces:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<my:params xml:space="preserve">
<pattern>
<old>
</old>
<new><br/></new>
</pattern>
<pattern>
<old>quick</old>
<new>slow</new>
</pattern>
<pattern>
<old>fox</old>
<new>elephant</new>
</pattern>
<pattern>
<old>brown</old>
<new>white</new>
</pattern>
</my:params>
<xsl:variable name="vPats"
select="document('')/*/my:params/*"/>
<xsl:template match="text()" name="multiReplace">
<xsl:param name="pText" select="."/>
<xsl:param name="pPatterns" select="$vPats"/>
<xsl:if test=
"string-length($pText) >0">
<xsl:variable name="vPat" select=
"$vPats[starts-with($pText, old)][1]"/>
<xsl:choose>
<xsl:when test="not($vPat)">
<xsl:copy-of select="substring($pText,1,1)"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$vPat/new/node()"/>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="multiReplace">
<xsl:with-param name="pText" select=
"substring($pText, 1 + not($vPat) + string-length($vPat/old/node()))"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when it is applied on this XML document:
<t>The quick
brown fox</t>
the wanted, correct result is produced:
The slow<br/>white elephant
Explanation:
The text is scanned from left to right and at any position, if the remaining string starts with one of the specified patterns, then the starting substring is replaced by the replacement specified for the firat matching patterns.
Do note: If we have search patterns:
"relation" --> "mapping"
"corelation" --> "similarity"
in the above order, and text:
"corelation"
then this solution produces the more correct result:
"similarity"
and the currently accepted solution by #Alejandro) produces:
"comapping"
Edit: With a small update we get another improvement: If at a given location more than one replace is possible, we perform the longest replace.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<my:params xml:space="preserve">
<pattern>
<old>
</old>
<new><br/></new>
</pattern>
<pattern>
<old>quick</old>
<new>slow</new>
</pattern>
<pattern>
<old>fox</old>
<new>elephant</new>
</pattern>
<pattern>
<old>brown</old>
<new>white</new>
</pattern>
</my:params>
<xsl:variable name="vrtfPats">
<xsl:for-each select="document('')/*/my:params/*">
<xsl:sort select="string-length(old)"
data-type="number" order="descending"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="vPats" select=
"ext:node-set($vrtfPats)/*"/>
<xsl:template match="text()" name="multiReplace">
<xsl:param name="pText" select="."/>
<xsl:param name="pPatterns" select="$vPats"/>
<xsl:if test= "string-length($pText) >0">
<xsl:variable name="vPat" select=
"$vPats[starts-with($pText, old)][1]"/>
<xsl:choose>
<xsl:when test="not($vPat)">
<xsl:copy-of select="substring($pText,1,1)"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$vPat/new/node()"/>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="multiReplace">
<xsl:with-param name="pText" select=
"substring($pText,
1 + not($vPat) + string-length($vPat/old/node())
)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Thus, if we have two reps such as "core" --> "kernel" and "corelation" --> "similarity", The second would be used for a text containing the word "corelation", regardless of how the reps are ordered.
This stylesheet shows a verbose solution just for you to learn the pattern:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()" name="replace">
<xsl:param name="pString" select="string()"/>
<xsl:param name="pSearch" select="'THIS'"/>
<xsl:param name="pReplace" select="'THAT'"/>
<xsl:choose>
<xsl:when test="contains($pString, '
')">
<xsl:call-template name="replace">
<xsl:with-param
name="pString"
select="substring-before($pString, '
')"/>
</xsl:call-template>
<br/>
<xsl:call-template name="replace">
<xsl:with-param
name="pString"
select="substring-after($pString, '
')"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="contains($pString, $pSearch)">
<xsl:call-template name="replace">
<xsl:with-param
name="pString"
select="substring-before($pString, $pSearch)"/>
</xsl:call-template>
<xsl:value-of select="$pReplace"/>
<xsl:call-template name="replace">
<xsl:with-param
name="pString"
select="substring-after($pString, $pSearch)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$pString"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
With this input:
<t>THIS is a test.
But THAT is not.
THIS is also a test.</t>
Output:
<t>THAT is a test.<br />But THAT is not.<br />THAT is also a test.</t>
EDIT: Full parameterized solution.
<stylesheet version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform">
<param name="pMap">
<s t="
" xmlns=""><br/></s>
<s t="THIS" xmlns="">THAT</s>
</param>
<template match="node()|#*">
<copy>
<apply-templates select="node()|#*"/>
</copy>
</template>
<template match="text()" name="replace">
<param name="pString" select="string()"/>
<param name="pSearches"
select="document('')/*/*[#name='pMap']/s"/>
<param name="vMatch" select="$pSearches[contains($pString,#t)][1]"/>
<choose>
<when test="$vMatch">
<call-template name="replace">
<with-param
name="pString"
select="substring-before($pString, $vMatch/#t)"/>
</call-template>
<copy-of select="$vMatch/node()"/>
<call-template name="replace">
<with-param
name="pString"
select="substring-after($pString, $vMatch/#t)"/>
</call-template>
</when>
<otherwise>
<value-of select="$pString"/>
</otherwise>
</choose>
</template>
</stylesheet>
Output:
<t>THAT is a test.<br/>But THAT is not.<br/>THAT is also a test.</t>
Note: There is a problem when using inline data in XML 1.0: you can't reset prefixed namespace declaration as in XML 1.1. The solution is to use a not common but valid notation: declare XSLT namespace as default namespace.
The problem may stem from differences in the encoding of newline, causing the XSLT processor not to recognize the CRLF in your match strings. I suggest testing with using comma in place of newline. The following will give you the expected result when called with the parameter "abc,def,ghi":
<xsl:template name="replace">
<xsl:param name="string" select="." />
<xsl:choose>
<xsl:when test="contains($string, ',')">
<xsl:value-of select="substring-before($string, ',')" />
<br/>
<xsl:call-template name="replace">
<xsl:with-param name="string" select="substring-after($string, ',')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
I modified the Dimitrie answer to put his solution in a template and using an exsl extension. Please check it, may be can be useful for someone.
<?xml version='1.0' ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
xmlns:a="http://www.tralix.com/cfd/2"
extension-element-prefixes="exsl">
<xsl:output indent="yes"/>
<xsl:template match="/*">
<xsl:variable name="replacesList">
<replaces>
<replace><old>01</old><new>01 - Efectivo</new></replace>
<replace><old>02</old><new>02 - Cheque nominativo</new></replace>
<replace><old>03</old><new>03 - Transferencia electrónica de fondos</new></replace>
<replace><old>04</old><new>04 - Tarjeta de Crédito</new></replace>
<replace><old>05</old><new>05 - Monedero Electrónico</new></replace>
<replace><old>06</old><new>06 - Dinero electrónico</new></replace>
<replace><old>08</old><new>08 - Vales de despensa</new></replace>
<replace><old>28</old><new>28 - Tarjeta de Débito</new></replace>
<replace><old>29</old><new>29 - Tarjeta de Servicio</new></replace>
<replace><old>99</old><new>99 - Otros</new></replace>
</replaces>
</xsl:variable>
<descripcionMetodoDePago>
<xsl:call-template name="replaces">
<xsl:with-param name="text" select="text"/>
<xsl:with-param name="replaces">
<xsl:copy-of select="exsl:node-set($replacesList/*/*)"/>
</xsl:with-param>
</xsl:call-template>
</descripcionMetodoDePago>
</xsl:template>
<xsl:template name="replaces">
<xsl:param name="text"/>
<xsl:param name="replaces"/>
<xsl:if test="$text!=''">
<xsl:variable name="replace" select="$replaces/*[starts-with($text, old)][1]"/>
<xsl:choose>
<xsl:when test="not($replace)">
<xsl:copy-of select="substring($text,1,1)"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$replace/new/node()"/>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="replaces">
<xsl:with-param name="text" select=
"substring($text, 1 + not($replace) + string-length($replace/old/node()))"/>
<xsl:with-param name="replaces" select="$replaces"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Although this question was asked (and answered) several years ago, neither this answer nor the (many!) other variants I found while searching the 'net over the last couple of days were able to do what I needed: replace multiple strings in nodes which may contain several kb of text.
Dimitre's version works well when nodes contain very little text, but when I tried to use it I almost immediately fell foul of the dreaded stack overflow (recursive calls, remember!) The problem with Dimitre's solution is that it tries to match the search patterns to the beginning of the text. This means that many (recursive) calls are made, each call using the right-most n-1 characters of the original text. For a 1k text that means over 1000 recursive calls!
After digging around for alternatives I came across an example by Ibrahim Naji (http://thinknook.com/xslt-replace-multiple-strings-2010-09-07/) which uses the more conventional substring-before/substring-after combination to perform the replacement. However, that code is limited to a single replacement string for any number of search strings.
I decided, therefore, that it was time to actually get my hands dirty (and learn XSLT at the same time!) The result is the following code which performs multiple string replacements (specified via an internal template, but that could easily be replaced with an external file, for example), and which (so far in my tests) doesn't suffer from excessive recursive calls.
It should be noted that the replacements are very basic (as are most other existing implementations) meaning that no attempts are made to only match entire words, for example. I hope the comments are enough to explain the way it works, particularly for other XSLT beginners (like myself).
And now the code...
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
xmlns:dps="dps:dps">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<!--
The original version of this code was published by Ibrahim Naji (http://thinknook.com/xslt-replace-multiple-strings-2010-09-07/).
It works but suffered the limitation of only being able to supply a single replacement text. An alternative implementation, which
did allow find/replace pairs to be specified, was published by Dimitre Novatchev
(https://stackoverflow.com/questions/5213644/xslt-multiple-string-replacement-with-recursion).
However, that implementation suffers from stack overflow problems if the node contains more than a few hundred bytes of text (and
in my case I needed to process nodes which could include several kb of data). Hence this version which combines the best features
of both implementations.
John Cullen, 14 July 2017.
-->
<!-- IdentityTransform, copy the input to the output -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- Process all text nodes. -->
<xsl:template match="text()">
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="."/>
</xsl:call-template>
</xsl:template>
<!-- Table of replacement patterns -->
<xsl:variable name="vPatterns">
<dps:patterns>
<pattern>
<old><i></old>
<new><em></new>
</pattern>
<pattern>
<old></i></old>
<new></em></new>
</pattern>
<pattern>
<old><b></old>
<new><strong></new>
</pattern>
<pattern>
<old></b></old>
<new></strong></new>
</pattern>
</dps:patterns>
</xsl:variable>
<!--
Convert the internal table into a node-set. This could also be done via a call to document()
for example select="document('')/*/myns:params/*" with a suitable namespace declaration, but
in my case that was not possible because the code is being used in with a StreamSource.
-->
<xsl:variable name="vPats" select="exsl:node-set($vPatterns)/dps:patterns/*"/>
<!-- This template matches all text() nodes, and calls itself recursively to performs the actual replacements. -->
<xsl:template name="string-replace-all">
<xsl:param name="text"/>
<xsl:param name="pos" select="1"/>
<xsl:variable name="replace" select="$vPats[$pos]/old"/>
<xsl:variable name="by" select="$vPats[$pos]/new"/>
<xsl:choose>
<!-- Ignore empty strings -->
<xsl:when test="string-length(translate(normalize-space($text), ' ', '')) = 0">
<xsl:value-of select="$text"/>
</xsl:when>
<!-- Return the unchanged text if the replacement is larger than the input (so no match possible) -->
<xsl:when test="string-length($replace) > string-length($text)">
<xsl:value-of select="$text"/>
</xsl:when>
<!-- If the current text contains the next pattern -->
<xsl:when test="contains($text, $replace)">
<!-- Perform a recursive call, each time replacing the next occurrence of the current pattern -->
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="concat(substring-before($text,$replace),$by,substring-after($text,$replace))"/>
<xsl:with-param name="pos" select="$pos"/>
</xsl:call-template>
</xsl:when>
<!-- No (more) matches found -->
<xsl:otherwise>
<!-- Bump the counter to pick up the next pattern we want to search for -->
<xsl:variable name="next" select="$pos+1"/>
<xsl:choose>
<!-- If we haven't finished yet, perform a recursive call to process the next pattern in the list. -->
<xsl:when test="boolean($vPats[$next])">
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="$text"/>
<xsl:with-param name="pos" select="$next"/>
</xsl:call-template>
</xsl:when>
<!-- No more patterns, we're done. Return the fully processed text. -->
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

xslt convert xml string in xml elements

here's a tricky one.
I have the following XML
<test>
<testElement SomeAttribute="<otherxml><otherElement>test test</otherElement></otherxml>">
</testElement>
</test>
Using XSLT, I want to transform this XML to have the following result.
<test>
<testElement>
<SomeAttributeTransformedToElement>
<otherxml>
<otherElement>test test</otherElement>
</otherxml>
</SomeAttributeTransformedToElement>
</testElement>
</test>
Basically, some text in an attribute must be transformed to actual elements in the final XML
Any ideas how to achieve that in XSLT?
Alex
You can achieve that by disabling output escaping. However, note that your input document is not a valid XML document (< is illegal in attribute values and needs escaping). I therefore changed your input document as follows:
Input document
<?xml version="1.0" encoding="utf-8"?>
<test>
<testElement SomeAttribute="<otherxml><otherElement>test test</otherElement></otherxml>">
</testElement>
</test>
XSLT
<?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="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#SomeAttribute">
<SomeAttributeTransformedToElement>
<xsl:value-of select="." disable-output-escaping="yes"/>
</SomeAttributeTransformedToElement>
</xsl:template>
</xsl:stylesheet>
Be aware that with disable-output-escaping="yes" there is no longer a guarantee that the produced output document is a well-formed XML document.
I had the same need, and I finally managed to build something working.
However, it's far from perfect. But as this solution works for me (and is very useful in my case), I give it them to share with anyone who could be interrested too.
I hope that XSL-purists will forgive me for this :
This XSLT named-template takes a text variable as input, and generates XML elements on the output.
Warning : There are some limitations :
The XML must not have self-contained elements (<a><b><a>...</a></b></a> is forbidden, as the 'a' element contains another 'a')
The XML must be normalized (a single space between the element name and the attributes, so <elmt attr1="1" attr2="2"> is allowed, but not <elmt attr1="1" attr2="2"> (this can be fixed)
&ltelement /> style is not handled yet (sorry, I didn't need that, and I'm kinda busy on y current project), but this can easily be fixed.
So you've been warned : don't put this in production before having tested it and being sure that it's illegible to your case. Also, if you happen to fix or improve this script, please let me know.
Here are the templates :
<xsl:template name="t-convert">
<xsl:param name="TEXT"/>
<xsl:choose>
<xsl:when test="starts-with($TEXT,'<?')">
<xsl:call-template name="t-convert">
<xsl:with-param name="TEXT" select="substring-after($TEXT,'?>')"/>
</xsl:call-template>
</xsl:when>
<!-- Si le texte contient encore des elements -->
<xsl:when test="contains($TEXT,'<')">
<xsl:variable name="before-first-open" select="substring-before($TEXT,'<')"/>
<xsl:variable name="after-first-open" select="substring-after($TEXT,'<')"/>
<!-- On ecrit le texte qui précéde l'élément -->
<xsl:value-of select="$before-first-open"/>
<!-- Le nom de l'élément -->
<xsl:variable name="TRAD" select="translate($after-first-open,'>',' ')"/>
<!-- TODO : Gere le cas <ELEMENT /> -->
<xsl:variable name="ELEMENT" select="substring-before($TRAD,' ')"/>
<xsl:variable name="suite" select="substring-after($after-first-open,$ELEMENT)"/>
<xsl:variable name="DEFINITION" select="substring-before($suite,'>')"/>
<xsl:variable name="CONTENT" select="substring-after(substring-before($suite,concat('</',$ELEMENT)),concat($DEFINITION,'>'))"/>
<xsl:variable name="FOLLOWING">
<xsl:choose>
<xsl:when test="substring($DEFINITION,string-length($DEFINITION))='/'"><!-- ends-with($DEFINITION,'/') not compatible with all XSLT version -->
<xsl:value-of select="substring-after($suite,'/>')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring-after(substring-after($suite,concat('</',$ELEMENT)),'>')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="{$ELEMENT}">
<xsl:call-template name="t-attribs">
<xsl:with-param name="TEXT" select="$DEFINITION"/>
</xsl:call-template>
<xsl:call-template name="t-convert">
<xsl:with-param name="TEXT" select="$CONTENT"/>
</xsl:call-template>
</xsl:element>
<xsl:call-template name="t-convert">
<xsl:with-param name="TEXT" select="$FOLLOWING"/>
</xsl:call-template>
</xsl:when>
<!-- no more element -->
<xsl:otherwise>
<xsl:value-of select="$TEXT"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="t-attribs">
<xsl:param name="TEXT"/>
<xsl:if test="contains($TEXT,'=')">
<!-- Assert TEXT=' NAME="VALUE".*' -->
<xsl:variable name="NAME" select="substring-after(substring-before($TEXT,'='),' ')"/>
<xsl:variable name="afterName" select="substring-after($TEXT,'="')"/>
<xsl:variable name="VALUE" select="substring-before($afterName,'"')"/>
<xsl:variable name="FOLLOWING" select="substring-after($afterName,'"')"/>
<xsl:attribute name="{$NAME}">
<xsl:value-of select="$VALUE"/>
</xsl:attribute>
<xsl:call-template name="t-attribs">
<xsl:with-param name="TEXT" select="FOLLOWING"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
And it's called with :
<xsl:call-template name="t-convert">
<xsl:with-param name="TEXT" select="//content"/>
</xsl:call-template>
I hope this will be helful to at least one perso in the world (it was for me !)