This is a followup to this question.
I have several tags in a document with several semicolon separated style attributes. Right now I have
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>
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)