Change two elements into single element using XSLT - xslt

I want to get transformation from <mo>(</mo> till <mo>)</mo> to <mfenced>..</mfenced>.
Sample Input is given below:
Sample XML:
<?xml version="1.0" encoding="UTF-8"?>
<chapter xmlns="http://www.w3.org/1998/Math/MathML">
<p>
<math>
<mi>sin</mi>
<mo>(</mo>
<mi>x</mi>
<mi>y</mi>
<mo>)</mo>
<mo>=</mo>
<mi>sin</mi>
<mi>x</mi>
<mi>sin</mi>
<mi>y</mi>
</math>
</p>
</chapter>
XSLT 2.0 tried:
<?xml version='1.0'?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:m="http://www.w3.org/1998/Math/MathML"
xmlns="http://www.w3.org/1998/Math/MathML"
exclude-result-prefixes="m">
<xsl:output method="xml"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="m:mo">
<xsl:if test="(.)='('">
<mfenced><xsl:apply-templates select="following-sibling::*[(.)=')']" mode="copy"/>
</mfenced>
</xsl:if>
<xsl:if test="(.)=')'"></xsl:if>
</xsl:template>
<xsl:template match="m:mo" mode="copy"/>
</xsl:stylesheet>
Output required:
<?xml version="1.0" encoding="UTF-8"?>
<chapter xmlns="http://www.w3.org/1998/Math/MathML">
<p>
<math>
<mi>sin</mi>
<mfenced>
<mi>x</mi>
<mi>y</mi>
</mfenced>
<mo>=</mo>
<mi>sin</mi>
<mi>x</mi>
<mi>sin</mi>
<mi>y</mi>
</math>
</p>
</chapter>

This is the xslt I use for converting brackets in mo to mfenced.
It uses the algorithm of finding matching brackets.
<?xml version='1.0'?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:m="http://www.w3.org/1998/Math/MathML"
xmlns="http://www.w3.org/1998/Math/MathML"
exclude-result-prefixes="m">
<xsl:output method="xml"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="m:mo[(.)='(']">
<xsl:variable name="brcount">
<xsl:value-of select="count(following-sibling::m:mo[(.)='(' or (.)=')'])"/>
</xsl:variable>
<xsl:call-template name="for.oploop">
<xsl:with-param name="i"><xsl:value-of select="'0'"/></xsl:with-param>
<xsl:with-param name="j"><xsl:value-of select="'1'"/></xsl:with-param>
<xsl:with-param name="count"><xsl:value-of select="$brcount"/></xsl:with-param>
<xsl:with-param name="stringop"><xsl:value-of select="'('"/></xsl:with-param>
<xsl:with-param name="stringcl"><xsl:value-of select="')'"/></xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template match="m:mo[(.)=')']">
<xsl:variable name="brcount">
<xsl:value-of select="count(preceding-sibling::m:mo[(.)='(' or (.)=')'])"/>
</xsl:variable>
<xsl:call-template name="for.clloop">
<xsl:with-param name="i"><xsl:value-of select="'0'"/></xsl:with-param>
<xsl:with-param name="j"><xsl:value-of select="'1'"/></xsl:with-param>
<xsl:with-param name="count"><xsl:value-of select="$brcount"/></xsl:with-param>
<xsl:with-param name="stringop"><xsl:value-of select="'('"/></xsl:with-param>
<xsl:with-param name="stringcl"><xsl:value-of select="')'"/></xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template match="m:mo[(.)='[']">
<xsl:variable name="brcount">
<xsl:value-of select="count(following-sibling::m:mo[(.)='[' or (.)=']'])"/>
</xsl:variable>
<xsl:call-template name="for.oploop">
<xsl:with-param name="i"><xsl:value-of select="'0'"/></xsl:with-param>
<xsl:with-param name="j"><xsl:value-of select="'1'"/></xsl:with-param>
<xsl:with-param name="count"><xsl:value-of select="$brcount"/></xsl:with-param>
<xsl:with-param name="stringop"><xsl:value-of select="'['"/></xsl:with-param>
<xsl:with-param name="stringcl"><xsl:value-of select="']'"/></xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template match="m:mo[(.)=']']">
<xsl:variable name="brcount">
<xsl:value-of select="count(preceding-sibling::m:mo[(.)='[' or (.)=']'])"/>
</xsl:variable>
<xsl:call-template name="for.clloop">
<xsl:with-param name="i"><xsl:value-of select="'0'"/></xsl:with-param>
<xsl:with-param name="j"><xsl:value-of select="'1'"/></xsl:with-param>
<xsl:with-param name="count"><xsl:value-of select="$brcount"/></xsl:with-param>
<xsl:with-param name="stringop"><xsl:value-of select="'['"/></xsl:with-param>
<xsl:with-param name="stringcl"><xsl:value-of select="']'"/></xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template name="for.oploop">
<xsl:param name="i" />
<xsl:param name="j" />
<xsl:param name="count" />
<xsl:param name="stringop" />
<xsl:param name="stringcl" />
<xsl:if test="$i = $count">
<xsl:if test="$j > '0'">
<xsl:text disable-output-escaping="yes"><mfenced separators="" open="</xsl:text>
<xsl:value-of select="$stringop"/>
<xsl:text disable-output-escaping="yes">" close=""></mfenced></xsl:text>
</xsl:if>
<xsl:if test="$j = '0'">
<xsl:text disable-output-escaping="yes"><mfenced separators="" open="</xsl:text>
<xsl:value-of select="$stringop"/>
<xsl:text disable-output-escaping="yes">" close="</xsl:text>
<xsl:value-of select="$stringcl"/>
<xsl:text disable-output-escaping="yes">"></xsl:text>
</xsl:if>
</xsl:if>
<xsl:if test="$i < $count">
<xsl:choose>
<xsl:when test="$j = '0'">
<xsl:text disable-output-escaping="yes"><mfenced separators="" open="</xsl:text>
<xsl:value-of select="$stringop"/>
<xsl:text disable-output-escaping="yes">" close="</xsl:text>
<xsl:value-of select="$stringcl"/>
<xsl:text disable-output-escaping="yes">"></xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="following-sibling::m:mo[(.)=$stringop or (.)=$stringcl][$i + 1] = $stringop">
<xsl:call-template name="for.oploop">
<xsl:with-param name="i">
<xsl:value-of select="$i + 1"/>
</xsl:with-param>
<xsl:with-param name="j">
<xsl:value-of select="$j + 1"/>
</xsl:with-param>
<xsl:with-param name="count">
<xsl:value-of select="$count"/>
</xsl:with-param>
<xsl:with-param name="stringop"><xsl:value-of select="$stringop"/></xsl:with-param>
<xsl:with-param name="stringcl"><xsl:value-of select="$stringcl"/></xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:when test="following-sibling::m:mo[(.)=$stringop or (.)=$stringcl][$i + 1] = $stringcl">
<xsl:call-template name="for.oploop">
<xsl:with-param name="i">
<xsl:value-of select="$i + 1"/>
</xsl:with-param>
<xsl:with-param name="j">
<xsl:value-of select="$j - 1"/>
</xsl:with-param>
<xsl:with-param name="count">
<xsl:value-of select="$count"/>
</xsl:with-param>
<xsl:with-param name="stringop"><xsl:value-of select="$stringop"/></xsl:with-param>
<xsl:with-param name="stringcl"><xsl:value-of select="$stringcl"/></xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
<xsl:template name="for.clloop">
<xsl:param name="i" />
<xsl:param name="j" />
<xsl:param name="count" />
<xsl:param name="stringop" />
<xsl:param name="stringcl" />
<xsl:if test="$i = $count">
<xsl:if test="$j > '0'">
<xsl:text disable-output-escaping="yes"><mfenced separators="" open="" close="</xsl:text>
<xsl:value-of select="$stringcl"/>
<xsl:text disable-output-escaping="yes">"></mfenced></xsl:text>
</xsl:if>
<xsl:if test="$j = '0'">
<xsl:text disable-output-escaping="yes"></mfenced></xsl:text>
</xsl:if>
</xsl:if>
<xsl:if test="$i < $count">
<xsl:choose>
<xsl:when test="$j = '0'">
<xsl:text disable-output-escaping="yes"></mfenced></xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="preceding-sibling::m:mo[(.)=$stringop or (.)=$stringcl][$i + 1] = $stringop">
<xsl:call-template name="for.clloop">
<xsl:with-param name="i">
<xsl:value-of select="$i + 1"/>
</xsl:with-param>
<xsl:with-param name="j">
<xsl:value-of select="$j - 1"/>
</xsl:with-param>
<xsl:with-param name="count">
<xsl:value-of select="$count"/>
</xsl:with-param>
<xsl:with-param name="stringop"><xsl:value-of select="$stringop"/></xsl:with-param>
<xsl:with-param name="stringcl"><xsl:value-of select="$stringcl"/></xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:when test="preceding-sibling::m:mo[(.)=$stringop or (.)=$stringcl][$i + 1] = $stringcl">
<xsl:call-template name="for.clloop">
<xsl:with-param name="i">
<xsl:value-of select="$i + 1"/>
</xsl:with-param>
<xsl:with-param name="j">
<xsl:value-of select="$j + 1"/>
</xsl:with-param>
<xsl:with-param name="count">
<xsl:value-of select="$count"/>
</xsl:with-param>
<xsl:with-param name="stringop"><xsl:value-of select="$stringop"/></xsl:with-param>
<xsl:with-param name="stringcl"><xsl:value-of select="$stringcl"/></xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

These Stack Overflow questions may help
XSLT combine multiple nodes into single node
Transform two nodes into one using XSLT
Recursively combine identical sibling elements in XSLT
You can use this template to transform the parent-with-child into the combined element:
<xsl:template match="parent">
<combined>
<xsl:copy-of select="#* | child/#*" />
</combined>
</xsl:template>
What this does is copy all the attributes from the input <parent> element and its <child>, into the output <combined> element.
You'll also want the identity template, in order to pass the <data> element and other nodes through:
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*" />
</xsl:copy>
</xsl:template>

Here a quick and a little dirty solution.
This should also work with nested braces , but will generated not well formed xml if the braces are not matching in input.
Try this:
<
?xml version='1.0'?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:m="http://www.w3.org/1998/Math/MathML"
xmlns="http://www.w3.org/1998/Math/MathML"
exclude-result-prefixes="m">
<xsl:output method="xml"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="m:mo[.=')']" >
<xsl:text disable-output-escaping="yes"><![CDATA[</mfenced>]]></xsl:text>
</xsl:template>
<xsl:template match="m:mo[.='(']" >
<xsl:text disable-output-escaping="yes"><![CDATA[<mfenced>]]></xsl:text>
</xsl:template>
</xsl:stylesheet>

Related

XSLT 2.0 Type checking: I get error "Unknown type xsl:string"

I am working on a complex XSLT transformation. I am using XSLT 2.0. This page, https://www.ibm.com/developerworks/library/x-schemaawarexslt/index.html, explains that you can add type checking to your templates. This does not work with me.
Here is my example XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="columnWidths"/>
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="row">
<xsl:with-param name="columnWidths" select="$columnWidths"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="row">
<xsl:param name="columnWidths"/>
<line>
<xsl:variable name="first">
<xsl:value-of select="field[position() = 1]/text()"/>
</xsl:variable>
<xsl:variable name="second">
<xsl:call-template name="padString">
<xsl:with-param name="value" select="field[position() = 2]/text()"/>
<xsl:with-param name="length" select="subsequence($columnWidths, 2, 1)"/>
<xsl:with-param name="align" select="'left'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="third">
<xsl:call-template name="padString">
<xsl:with-param name="value" select="field[position() = 3]/text()"/>
<xsl:with-param name="length" select="subsequence($columnWidths, 3, 1)"/>
<xsl:with-param name="align" select="'right'"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="string-join(($first, $second, $third), ' ')"/>
</line>
</xsl:template>
<xsl:template name="padString">
<xsl:param name="value"/>
<xsl:param name="length"/>
<xsl:param name="align"/>
<xsl:choose>
<xsl:when test="string-length($value) >= number($length)">
<xsl:value-of select="$value"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="appended">
<xsl:call-template name="generalAppend">
<xsl:with-param name="in" select="$value"/>
</xsl:call-template>
</xsl:variable>
<xsl:call-template name="padString">
<xsl:with-param name="value" select="$appended/append/*[local-name()=$align]/text()"/>
<xsl:with-param name="length" select="$length"/>
<xsl:with-param name="align" select="$align"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="generalAppend">
<xsl:param name="in" as="xsl:string"/>
<append>
<left><xsl:value-of select="concat($in, ' ')"/></left>
<right><xsl:value-of select="concat(' ', $in)"/></right>
</append>
</xsl:template>
</xsl:stylesheet>
My XSLT processor gives the following error:
line [57] column [43]: Unknown type xsl:string
This line reads:
<xsl:param name="in" as="xsl:string"/>
What is going wrong?
You try to get the type "string" from the wrong namespace. The type "string" lives in the XML Schema namespace which differs from the XSLT namespace. Here is the correct version of the above XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:param name="columnWidths"/>
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="row">
<xsl:with-param name="columnWidths" select="$columnWidths"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="row">
<xsl:param name="columnWidths"/>
<line>
<xsl:variable name="first">
<xsl:value-of select="field[position() = 1]/text()"/>
</xsl:variable>
<xsl:variable name="second">
<xsl:call-template name="padString">
<xsl:with-param name="value" select="field[position() = 2]/text()"/>
<xsl:with-param name="length" select="subsequence($columnWidths, 2, 1)"/>
<xsl:with-param name="align" select="'left'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="third">
<xsl:call-template name="padString">
<xsl:with-param name="value" select="field[position() = 3]/text()"/>
<xsl:with-param name="length" select="subsequence($columnWidths, 3, 1)"/>
<xsl:with-param name="align" select="'right'"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="string-join(($first, $second, $third), ' ')"/>
</line>
</xsl:template>
<xsl:template name="padString">
<xsl:param name="value"/>
<xsl:param name="length"/>
<xsl:param name="align"/>
<xsl:choose>
<xsl:when test="string-length($value) >= number($length)">
<xsl:value-of select="$value"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="appended">
<xsl:call-template name="generalAppend">
<xsl:with-param name="in" select="$value"/>
</xsl:call-template>
</xsl:variable>
<xsl:call-template name="padString">
<xsl:with-param name="value" select="$appended/append/*[local-name()=$align]/text()"/>
<xsl:with-param name="length" select="$length"/>
<xsl:with-param name="align" select="$align"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="generalAppend">
<xsl:param name="in" as="xs:string"/>
<append>
<left><xsl:value-of select="concat($in, ' ')"/></left>
<right><xsl:value-of select="concat(' ', $in)"/></right>
</append>
</xsl:template>
</xsl:stylesheet>
A namespace prefix "xs" has been introduced for the XML Schema namespace. The definition of the parameter type then references this prefix:
<xsl:param name="in" as="xs:string"/>

Replace multiple characters in a single element using XSLT

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>

How can I use XSLT 1.0 to right justify plain text output?

I'm working with an XML file that looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="align-test.xsl"?>
<alignTest>
<item name="Some Name" number="3"></item>
<item name="Another Name" number="10"></item>
<item name="A Third One" number="43"></item>
<item name="A Really Long Name" number="100"></item>
</alignTest>
My goal is to output a plain text file with a nicely formatted table in it. I've figured out how to align and pad text columns and a separater using this stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:for-each select="alignTest/item">
<!-- Scroll right. The next line keeps going, but might not look like it due to formatting -->
<xsl:value-of select="substring(concat(#name, ' '), 1, 22)"/>
<xsl:text> | </xsl:text>
<xsl:value-of select="#number"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Which outputs:
Some Name | 3
Another Name | 10
A Third One | 43
A Really Long Name | 100
I'd also like the numeric values to be right justified. Like so:
Some Name | 3
Another Name | 10
A Third One | 43
A Really Long Name | 100
How can I use XSLT 1.0 to right justify plain-text in that way?
Here is a more readable and more efficient version of your answer:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="item">
<xsl:call-template name="padRightSide">
<xsl:with-param name="stringToPad" select="#name"/>
<xsl:with-param name="totalLength" select="22"/>
</xsl:call-template>
<xsl:text>|</xsl:text>
<xsl:call-template name="padLeftSide">
<xsl:with-param name="stringToPad" select="#number"/>
<xsl:with-param name="totalLength" select="5"/>
</xsl:call-template>
<xsl:text>
</xsl:text>
</xsl:template>
<!-- template to pad the left side of strings (and right justificy) -->
<xsl:template name="padLeftSide">
<xsl:param name="stringToPad"/>
<xsl:param name="totalLength"/>
<xsl:param name="padChar" select="' '"/>
<xsl:param name="padBuffer" select=
"concat($padChar,$padChar,$padChar,$padChar,$padChar,
$padChar,$padChar,$padChar,$padChar,$padChar
)"/>
<xsl:variable name="vNewString" select=
"concat($padBuffer, $stringToPad)"/>
<xsl:choose>
<xsl:when test="not(string-length($vNewString) >= $totalLength)">
<xsl:call-template name="padLeftSide">
<xsl:with-param name="stringToPad" select="$vNewString"/>
<xsl:with-param name="totalLength" select="$totalLength"/>
<xsl:with-param name="padChar" select="$padChar"/>
<xsl:with-param name="padBuffer" select="$padBuffer"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select=
"substring($vNewString,
string-length($vNewString) - $totalLength + 1)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- template to pad the right side of strings -->
<xsl:template name="padRightSide">
<xsl:param name="totalLength"/>
<xsl:param name="padChar" select="' '"/>
<xsl:param name="stringToPad"/>
<xsl:param name="padBuffer" select=
"concat($padChar,$padChar,$padChar,$padChar,$padChar,
$padChar,$padChar,$padChar,$padChar,$padChar
)"/>
<xsl:variable name="vNewString" select=
"concat($stringToPad, $padBuffer)"/>
<xsl:choose>
<xsl:when test="not(string-length($vNewString) >= $totalLength)">
<xsl:call-template name="padRightSide">
<xsl:with-param name="stringToPad" select="$vNewString"/>
<xsl:with-param name="totalLength" select="$totalLength"/>
<xsl:with-param name="padChar" select="$padChar"/>
<xsl:with-param name="padBuffer" select="$padBuffer"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($vNewString,1,$totalLength)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Another improvement is to dynamically calculate the maximum string-length and not to have to count it manually:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vNamesMaxLen">
<xsl:call-template name="maxLength">
<xsl:with-param name="pNodes"
select="/*/item/#name"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vNumsMaxLen">
<xsl:call-template name="maxLength">
<xsl:with-param name="pNodes"
select="/*/item/#number"/>
</xsl:call-template>
</xsl:variable>
<xsl:template match="item">
<xsl:call-template name="padRightSide">
<xsl:with-param name="stringToPad"
select="#name"/>
<xsl:with-param name="totalLength"
select="$vNamesMaxLen+1"/>
</xsl:call-template>
<xsl:text>|</xsl:text>
<xsl:call-template name="padLeftSide">
<xsl:with-param name="stringToPad"
select="#number"/>
<xsl:with-param name="totalLength"
select="$vNumsMaxLen+1"/>
</xsl:call-template>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template name="maxLength">
<xsl:param name="pNodes" select="/.."/>
<xsl:for-each select="$pNodes">
<xsl:sort select="string-length()" data-type="number"
order="descending"/>
<xsl:if test="position() = 1">
<xsl:value-of select="string-length()"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
<!-- template to pad the left side of strings (and right justificy) -->
<xsl:template name="padLeftSide">
<xsl:param name="stringToPad"/>
<xsl:param name="totalLength"/>
<xsl:param name="padChar" select="' '"/>
<xsl:param name="padBuffer" select=
"concat($padChar,$padChar,$padChar,$padChar,$padChar,
$padChar,$padChar,$padChar,$padChar,$padChar
)"/>
<xsl:variable name="vNewString" select=
"concat($padBuffer, $stringToPad)"/>
<xsl:choose>
<xsl:when test="not(string-length($vNewString) >= $totalLength)">
<xsl:call-template name="padLeftSide">
<xsl:with-param name="stringToPad" select="$vNewString"/>
<xsl:with-param name="totalLength" select="$totalLength"/>
<xsl:with-param name="padChar" select="$padChar"/>
<xsl:with-param name="padBuffer" select="$padBuffer"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select=
"substring($vNewString,
string-length($vNewString) - $totalLength + 1)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- template to pad the right side of strings -->
<xsl:template name="padRightSide">
<xsl:param name="totalLength"/>
<xsl:param name="padChar" select="' '"/>
<xsl:param name="stringToPad"/>
<xsl:param name="padBuffer" select=
"concat($padChar,$padChar,$padChar,$padChar,$padChar,
$padChar,$padChar,$padChar,$padChar,$padChar
)"/>
<xsl:variable name="vNewString" select=
"concat($stringToPad, $padBuffer)"/>
<xsl:choose>
<xsl:when test="not(string-length($vNewString) >= $totalLength)">
<xsl:call-template name="padRightSide">
<xsl:with-param name="stringToPad" select="$vNewString"/>
<xsl:with-param name="totalLength" select="$totalLength"/>
<xsl:with-param name="padChar" select="$padChar"/>
<xsl:with-param name="padBuffer" select="$padBuffer"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($vNewString,1,$totalLength)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I found a few answers on this page.
The simply way described is to do something like:
<xsl:value-of select="substring(concat(' ', #number), string-length(#number) + 1, 4)"/>
The page also lists a couple of templates for padding both on the left and the right. They call themselves recursively and pad the appropriate amount. (Note that they will also truncate if the requested length is less than that of the string being worked on.) The templates also off a feature of changing the character that is used for padding.
They take more code to implement, but might be easier to maintain. Here's a version of the original stylesheet updated with the two templates to show their usage:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:for-each select="alignTest/item">
<xsl:call-template name="padRightSide">
<xsl:with-param name="stringToPad" select="#name"></xsl:with-param>
<xsl:with-param name="totalLength" select="22"></xsl:with-param>
<xsl:with-param name="padCharacter" select="' '"></xsl:with-param>
</xsl:call-template>
<xsl:text>|</xsl:text>
<xsl:call-template name="padLeftSide">
<xsl:with-param name="stringToPad" select="#number"/>
<xsl:with-param name="totalLength" select="5"/>
<xsl:with-param name="padCharacteracter" select="' '"/>
</xsl:call-template>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
<!-- template to pad the left side of strings (and right justificy) -->
<xsl:template name="padLeftSide">
<xsl:param name="stringToPad"/>
<xsl:param name="totalLength"/>
<xsl:param name="padCharacteracter"/>
<xsl:choose>
<xsl:when test="string-length($stringToPad) < $totalLength">
<xsl:call-template name="padLeftSide">
<xsl:with-param name="stringToPad" select="concat($padCharacteracter,$stringToPad)"/>
<xsl:with-param name="totalLength" select="$totalLength"/>
<xsl:with-param name="padCharacteracter" select="$padCharacteracter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($stringToPad,string-length($stringToPad) - $totalLength + 1)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- template to pad the right side of strings -->
<xsl:template name="padRightSide">
<xsl:param name="padCharacter"/>
<xsl:param name="stringToPad"/>
<xsl:param name="totalLength"/>
<xsl:choose>
<xsl:when test="string-length($stringToPad) < $totalLength">
<xsl:call-template name="padRightSide">
<xsl:with-param name="padCharacter" select="$padCharacter"/>
<xsl:with-param name="stringToPad" select="concat($stringToPad,$padCharacter)"/>
<xsl:with-param name="totalLength" select="$totalLength"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($stringToPad,1,$totalLength)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Of course, you could reduce the size of their footprint a little by hard coding the padding character.

How to unescape Escaped-XML content with the help of XSLT?

My question is How to un-escape xml that has already been escaped.
I tried the code provided by Tomalak in response to How to unescape XML characters with help of XSLT?, but I can't get that to do what I want.
I have SoapMsg Xml. The body contains a few elements one of which is a String. This string
contains Escaped XML. This is often done in RPC SoapMsg because they don't allow complex
types. To Get around this they embed Escaped-Xml inside a String Element, see sXmlParameters in the input below.
Example Input:
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:pan="http://wsdl.somebody.com/Stuff/">
<soap:Header />
<soap:Body>
<pan:SomeCommand>
<first>eefbb52a0fee443cbda838caffbc2654</first>
<second>f26eb2f5dabc457ca045e64585f7b185</second>
<sXmlParameters><PARAMETERS><TIMEOUTDATETIME>2011-03-15
2:09:48.997</TIMEOUTDATETIME></PARAMETERS></sXmlParameters>
</pan:SomeCommand>
</soap:Body>
</soap:Envelope>
I also see this data escaped with <![CDATA[>]]>, I need to un-escape it also.
Converted Output:
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:pan="http://wsdl.somebody.com/Stuff/">
<soap:Header />
<soap:Body>
<pan:SomeCommand>
<first>eefbb52a0fee443cbda838caffbc2654</first>
<second>f26eb2f5dabc457ca045e64585f7b185</second>
<sXmlParameters>
<PARAMETERS>
<TIMEOUTDATETIME>2011-03-15 2:09:48.997</TIMEOUTDATETIME>
</PARAMETERS>
</sXmlParameters>
</pan:SomeCommand>
</soap:Body>
</soap:Envelope>
This will already take care of half of your problem – not the CDATA part:
<?xml version="1.0" encoding="UTF-8"?>
<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="//sXmlParameters">
<xsl:copy>
<xsl:call-template name="unescape">
<xsl:with-param name="escaped" select="string(.)"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="unescape">
<xsl:param name="escaped"/>
<xsl:choose>
<xsl:when test="contains($escaped,'<')">
<xsl:variable name="beforeelem" select="substring-before($escaped,'<')"/>
<xsl:variable name="elemname1" select="substring-before(substring-after($escaped,'<'),' ')"/>
<xsl:variable name="elemname2" select="substring-before(substring-after($escaped,'<'),'>')"/>
<xsl:variable name="elemname3" select="substring-before(substring-after($escaped,'<'),'/>')"/>
<xsl:variable name="hasattributes" select="string-length($elemname1) > 0 and ((string-length($elemname2)=0 or string-length($elemname1) < string-length($elemname2)) and (string-length($elemname3)=0 or string-length($elemname1) < string-length($elemname3)))"/>
<xsl:variable name="elemclosed" select="string-length($elemname3) > 0 and (string-length($elemname2)=0 or string-length($elemname3) < string-length($elemname2))"/>
<xsl:variable name="elemname">
<xsl:choose>
<xsl:when test="$hasattributes">
<xsl:value-of select="$elemname1"/>
</xsl:when>
<xsl:when test="not($elemclosed)">
<xsl:value-of select="$elemname2"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$elemname3"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="elemclosetag" select="concat('</',$elemname,'>')"/>
<xsl:variable name="innercontent">
<xsl:if test="not($elemclosed)">
<xsl:call-template name="skipper-before">
<xsl:with-param name="source" select="substring-after(substring-after($escaped,'<'),'>')"/>
<xsl:with-param name="delimiter" select="$elemclosetag"/>
</xsl:call-template>
</xsl:if>
</xsl:variable>
<xsl:variable name="afterelem">
<xsl:choose>
<xsl:when test="not($elemclosed)">
<xsl:call-template name="skipper-after">
<xsl:with-param name="source" select="substring-after(substring-after($escaped,'<'),'>')"/>
<xsl:with-param name="delimiter" select="$elemclosetag"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring-after(substring-after($escaped,'<'),'/>')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="{$elemname}">
<xsl:if test="$hasattributes">
<xsl:call-template name="unescapeattributes">
<xsl:with-param name="escapedattributes">
<xsl:choose>
<xsl:when test="not($elemclosed)">
<xsl:value-of select="normalize-space(substring-after($elemname2,' '))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="normalize-space(substring-after($elemname3,' '))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:with-param>
</xsl:call-template>
</xsl:if>
<xsl:call-template name="unescape">
<xsl:with-param name="escaped" select="$innercontent"/>
</xsl:call-template>
</xsl:element>
<xsl:call-template name="unescape">
<xsl:with-param name="escaped" select="$afterelem"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="unescapetext">
<xsl:with-param name="escapedtext" select="$escaped"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="unescapeattributes">
<xsl:param name="escapedattributes"/>
<xsl:variable name="attrname" select="substring-before($escapedattributes,'=')"/>
<xsl:variable name="attrquote" select="substring($escapedattributes,string-length($attrname)+2,1)"/>
<xsl:variable name="attrvalue" select="substring-before(substring-after($escapedattributes,$attrquote),$attrquote)"/>
<xsl:variable name="afterattr" select="substring-after(substring-after($escapedattributes,$attrquote),$attrquote)"/>
<xsl:attribute name="{$attrname}">
<xsl:call-template name="unescapetext">
<xsl:with-param name="escapedtext" select="$attrvalue"/>
</xsl:call-template>
</xsl:attribute>
<xsl:if test="contains($afterattr,'=')">
<xsl:call-template name="unescapeattributes">
<xsl:with-param name="escapedattributes" select="normalize-space($afterattr)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="unescapetext">
<xsl:param name="escapedtext"/>
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text">
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text">
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="$escapedtext"/>
<xsl:with-param name="replace">&gt;</xsl:with-param>
<xsl:with-param name="by">></xsl:with-param>
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="replace">&lt;</xsl:with-param>
<xsl:with-param name="by"><</xsl:with-param>
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="replace">&amp;</xsl:with-param>
<xsl:with-param name="by">&</xsl:with-param>
</xsl:call-template>
</xsl:template>
<!-- replaces substrings in strings -->
<xsl:template name="string-replace-all">
<xsl:param name="text"/>
<xsl:param name="replace"/>
<xsl:param name="by"/>
<xsl:choose>
<xsl:when test="contains($text, $replace)">
<xsl:value-of select="substring-before($text,$replace)"/>
<xsl:value-of select="$by"/>
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="substring-after($text,$replace)"/>
<xsl:with-param name="replace" select="$replace"/>
<xsl:with-param name="by" select="$by"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- returns the substring after the last delimiter -->
<xsl:template name="skipper-after">
<xsl:param name="source"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($source,$delimiter)">
<xsl:call-template name="skipper-after">
<xsl:with-param name="source" select="substring-after($source,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$source"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- returns the substring before the last delimiter -->
<xsl:template name="skipper-before">
<xsl:param name="source"/>
<xsl:param name="delimiter"/>
<xsl:param name="result"/>
<xsl:choose>
<xsl:when test="contains($source,$delimiter)">
<xsl:call-template name="skipper-before">
<xsl:with-param name="source" select="substring-after($source,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
<xsl:with-param name="result">
<xsl:if test="result!=''">
<xsl:value-of select="concat($result,$delimiter)"/>
</xsl:if>
<xsl:value-of select="substring-before($source,$delimiter)"/>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$result"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I found that I can use saxon to do this in a much simpler way using the following:
<xsl:template match="SomeCommand">
<sXmlParameters>
<xsl:apply-templates select="saxon:parse(.)" />
</sXmlParameters>
</xsl:template>
there is also saxon:seriralize() that can be used to escape the xml
thanks to all for you input
Wrote a SAX parser for xml-escaped strings in pure xsl 1.0+EXSLT
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:pxml="https://github.com/ilyakharlamov/pure-xsl/parseStringAsXML"
version="1.0">
<xsl:import href="https://raw.githubusercontent.com/ilyakharlamov/pure-xsl/master/parseStringAsXML.xsl"/>
<xsl:template match="/">
<xsl:call-template name="pxml:parseStringAsXML">
<xsl:with-param name="string"><PARAMETERS><TIMEOUTDATETIME>2011-03-152:09:48.997</TIMEOUTDATETIME></PARAMETERS></xsl:with-param>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
Output:
<PARAMETERS>
<TIMEOUTDATETIME>2011-03-152:09:48.997</TIMEOUTDATETIME>
</PARAMETERS>

Ambiguous rule match in replacing style= attributes in XHTML via XSLT

This is a followup to this question.
I have several <span> tags in a document with several semicolon separated style attributes. Right now I have 3 specific style attributes I'm looking for to translate into tags. All works well in the example above as long as the style attribute only contains one of the three style attributes. If a span has more, I get an ambiguous rule match.
The three style attributes I'm looking for are font-style:italic, font-weight:600, and text-decoration:underline which should be removed from the style attribute and transformed into <em>, <strong>, and <u>, respectively.
Here's my current XSLT:
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="span[
contains(translate(#style, ' ', ''), 'font-style:italic')
]">
<xsl:copy>
<xsl:attribute name="style">
<xsl:value-of select="substring-before(#style, ' font-style')"/>
<xsl:value-of select="substring-after(#style, 'italic;')"/>
</xsl:attribute>
<em>
<xsl:apply-templates select="node()"/>
</em>
</xsl:copy>
</xsl:template>
<xsl:template match="span[
contains(translate(#style, ' ', ''), 'font-weight:600')
]">
<xsl:copy>
<xsl:attribute name="style">
<xsl:value-of select="substring-before(#style, ' font-weight')"/>
<xsl:value-of select="substring-after(#style, '600;')"/>
</xsl:attribute>
<strong>
<xsl:apply-templates select="node()"/>
</strong>
</xsl:copy>
</xsl:template>
<xsl:template match="span[
contains(translate(#style, ' ', ''), 'text-decoration:underline')
]">
<xsl:copy>
<xsl:attribute name="style">
<xsl:value-of select="substring-before(#style, ' text-decoration')"/>
<xsl:value-of select="substring-after(#style, 'underline;')"/>
</xsl:attribute>
<u>
<xsl:apply-templates select="node()"/>
</u>
</xsl:copy>
</xsl:template>
Which will generate the ambiguous rule warning doesn't work correctly on some elements that contain more than one of the listed attributes.
An example of the input:
<span style=" text-decoration: underline; font-weight:600; color:#555555">some text</span>
gets transformed to:
<span style=" font-weight:600; color:#555555"><u>some text</u></span>
when the desired result is:
<span style="color:#555555"><b><u>some text</u></b></span>
How can I fix the ambiguous rule match for this?
Thanks in advance
Update:
If i set priorty on each of the templates to descending values and run the XSLT again on the output of the first XSLT run, everything works as expected. There has to be an easier way than running it through the transformation twice. Any ideas?
As Alejandro and Tomalak suggested, replacing the style attributes with a class attribute for CSS classes is an option, too.
EDIT: Just in case real problem gets hide, I have simplified the stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:s="styles"
exclude-result-prefixes="s msxsl">
<s:s prop="font-style:italic" name="em"/>
<s:s prop="font-weight:600" name="strong"/>
<s:s prop="text-decoration:underline" name="u"/>
<xsl:variable name="vStyles" select="document('')/*/s:s"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="span[#style]">
<xsl:variable name="vrtfProp">
<xsl:call-template name="parser"/>
</xsl:variable>
<xsl:variable name="vProp"
select="msxsl:node-set($vrtfProp)/*"/>
<xsl:copy>
<xsl:apply-templates select="#*[name()!='style']"/>
<xsl:attribute name="style">
<xsl:for-each select="$vProp[not(.=$vStyles/#prop)]">
<xsl:value-of select="concat(.,';')"/>
</xsl:for-each>
</xsl:attribute>
<xsl:call-template name="generate">
<xsl:with-param
name="pElements"
select="$vStyles[#prop=$vProp]/#name"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="generate">
<xsl:param name="pElements" select="/.."/>
<xsl:choose>
<xsl:when test="$pElements">
<xsl:element name="{$pElements[1]}">
<xsl:call-template name="generate">
<xsl:with-param
name="pElements"
select="$pElements[position()>1]"/>
</xsl:call-template>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="parser">
<xsl:param name="pString" select="concat(#style,';')"/>
<xsl:if test="contains($pString,';')">
<xsl:variable
name="vProp"
select="substring-before($pString,';')"/>
<prop>
<xsl:value-of
select="concat(
normalize-space(
substring-before($vProp,':')
),
':',
normalize-space(
substring-after($vProp,':')
)
)"/>
</prop>
<xsl:call-template name="parser">
<xsl:with-param
name="pString"
select="substring-after($pString,';')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Output:
<span style="color:#555555;"><strong><u>some text</u></strong></span>
Note: Simpler parsing with space normalization to match properties in an existencial comparison. Generating content without optimization (selecting no match, selecting match). "Stateful" or "stackful" named template for output nested elements. Either way there are two rules (identity and span with #style overwriting it) and two names templates (parser/tokenizer and generator of nested content)
Original stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:s="styles"
xmlns:t="tokenizer"
exclude-result-prefixes="s t msxsl">
<s:s r="font-style" v="italic" e="em"/>
<s:s r="font-weight" v="600" e="strong"/>
<s:s r="text-decoration" v="underline" e="u"/>
<t:t s=";" n="p"/>
<t:t s=":" n="t"/>
<xsl:variable name="vStyles" select="document('')/*/s:s"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="span[#style]">
<xsl:variable name="vrtfStyles">
<xsl:call-template name="tokenizer"/>
</xsl:variable>
<xsl:copy>
<xsl:apply-templates select="#*[name()!='style']"/>
<xsl:call-template name="generate">
<xsl:with-param name="pStyles"
select="msxsl:node-set($vrtfStyles)/*"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="generate">
<xsl:param name="pStyles" select="/.."/>
<xsl:param name="pAttributes" select="/.."/>
<xsl:param name="pElements" select="/.."/>
<xsl:choose>
<xsl:when test="$pStyles">
<xsl:variable name="vMatch"
select="$vStyles[#r=$pStyles[1]/t[1]]
[#v=$pStyles[1]/t[2]]"/>
<xsl:call-template name="generate">
<xsl:with-param name="pStyles"
select="$pStyles[position()>1]"/>
<xsl:with-param name="pAttributes"
select="$pAttributes|
$pStyles[1][not($vMatch)]"/>
<xsl:with-param name="pElements"
select="$pElements|$vMatch"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="$pAttributes">
<xsl:attribute name="style">
<xsl:for-each select="$pAttributes">
<xsl:value-of select="concat(t[1],':',t[2],';')"/>
</xsl:for-each>
</xsl:attribute>
<xsl:call-template name="generate">
<xsl:with-param name="pElements" select="$pElements"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="$pElements">
<xsl:element name="{$pElements[1]/#e}">
<xsl:call-template name="generate">
<xsl:with-param name="pElements"
select="$pElements[position()>1]"/>
</xsl:call-template>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="tokenizer">
<xsl:param name="pTokenizer" select="document('')/*/t:t"/>
<xsl:param name="pString" select="#style"/>
<xsl:choose>
<xsl:when test="not($pTokenizer)">
<xsl:value-of select="normalize-space($pString)"/>
</xsl:when>
<xsl:when test="contains($pString,$pTokenizer[1]/#s)">
<xsl:call-template name="tokenizer">
<xsl:with-param name="pTokenizer" select="$pTokenizer"/>
<xsl:with-param name="pString"
select="substring-before(
$pString,
$pTokenizer[1]/#s
)"/>
</xsl:call-template>
<xsl:call-template name="tokenizer">
<xsl:with-param name="pTokenizer" select="$pTokenizer"/>
<xsl:with-param name="pString"
select="substring-after(
$pString,
$pTokenizer[1]/#s
)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{$pTokenizer[1]/#n}">
<xsl:call-template name="tokenizer">
<xsl:with-param name="pTokenizer"
select="$pTokenizer[position()>1]"/>
<xsl:with-param name="pString" select="$pString"/>
</xsl:call-template>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Note: Recursion paradise. Nested tokenizer for parsing style properties. "Stateful" template for nested content (and performance matching properties, by the way)
Here is an XSLT 1.0 solution using the str-split-to-words template/function of FXSL:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
exclude-result-prefixes="ext"
>
<xsl:import href="strSplit-to-Words.xsl"/>
<xsl:output indent="yes" omit-xml-declaration="yes"/>
<xsl:param name="pStyleReps">
<r s="font-style:italic"><em/></r>
<r s="font-weight:600"><strong/></r>
<r s="text-decoration:underline"><u/></r>
</xsl:param>
<xsl:variable name="vReps" select=
"document('')/*/xsl:param[#name='pStyleReps']/*"/>
<xsl:template match="span">
<xsl:variable name="vrtfStyles">
<xsl:call-template name="str-split-to-words">
<xsl:with-param name="pStr" select="#style"/>
<xsl:with-param name="pDelimiters" select="';'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vStyles" select=
"ext:node-set($vrtfStyles)/*"/>
<xsl:choose>
<xsl:when test=
"not($vReps/#s[contains(current()/#style, .)])">
<xsl:copy-of select="."/>
</xsl:when>
<xsl:otherwise>
<span>
<xsl:copy-of select="#*"/>
<xsl:attribute name="style">
<xsl:for-each select=
"$vStyles[not(translate(.,' ','')=$vReps/#s)]">
<xsl:value-of select="."/>
<xsl:if test="not(position()=last())">;</xsl:if>
</xsl:for-each>
</xsl:attribute>
<xsl:call-template name="styles2markup">
<xsl:with-param name="pStyles" select=
"$vReps/#s
[contains
(translate(current()/#style, ' ', ''),
.
)
]"/>
</xsl:call-template>
</span>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="styles2markup">
<xsl:param name="pResult" select="text()"/>
<xsl:param name="pStyles"/>
<xsl:choose>
<xsl:when test="not($pStyles)">
<xsl:copy-of select="$pResult"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vrtfnewResult">
<xsl:element name="{name($pStyles[1]/../*)}">
<xsl:copy-of select="$pResult"/>
</xsl:element>
</xsl:variable>
<xsl:call-template name="styles2markup">
<xsl:with-param name="pStyles" select=
"$pStyles[position()>1]"/>
<xsl:with-param name="pResult" select=
"ext:node-set($vrtfnewResult)/*"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<span style=" text-decoration: underline; font-weight:600; color:#555555">some text</span>
the wanted, correct result is produced:
<span style=" color:#555555">
<u>
<strong>some text</strong>
</u>
</span>