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

This is a continuation of this issue .

I have several <span> tags in a document with several style attributes separated by a semicolon. Right now I have 3 specific style attributes that I am looking for to translate into tags. Everything works well in the example above if the style attribute contains only one of the three style attributes. If span has more, I get an ambiguous match for the rules.

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 converted to <em> , <strong> and <u> respectively.

Here is 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> 

What will generate an ambiguous rule warning does not work correctly for some elements that contain more than one of the listed attributes.

Input Example:

 <span style=" text-decoration: underline; font-weight:600; color:#555555">some text</span> 

converted to:

 <span style=" font-weight:600; color:#555555"><u>some text</u></span> 

when the desired result:

 <span style="color:#555555"><b><u>some text</u></b></span> 

How can I fix an ambiguous rule match for this?

Thank you in advance


Update:

If I set priorty on each of the templates to descending values ​​and run XSLT again on the output of the first XSLT run, everything works as expected. There should be an easier way than running it through the conversion twice. Any ideas?


As suggested by Alejandro and Tomalak, replacing style attributes with a class attribute for CSS classes is also an option.

+3
source share
2 answers

EDIT : actually the real problem is getting hidden, I 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 Simplified parsing with space normalization to match properties compared to existing ones. Content creation without optimization (choice of coincidence, choice of correspondence). "Stateful" or "stackful" named template to display nested elements . In any case, there are two rules (identifier and span with @style replacement) and two name patterns (parser / tokenizer and nested content generator)

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="st msxsl"> <s:sr="font-style" v="italic" e="em"/> <s:sr="font-weight" v="600" e="strong"/> <s:sr="text-decoration" v="underline" e="u"/> <t:ts=";" n="p"/> <t:ts=":" 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 : paradise for recursion. Nested tokenizer for parsing style properties. "Stateful" template for embedded content (and, by the way, performance matching properties)

+2
source

Here is an XSLT 1.0 solution using the str-split-to-words FXSL template / function :

 <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"> <rs="font-style:italic"><em/></r> <rs="font-weight:600"><strong/></r> <rs="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 conversion is applied to the provided XML document:

 <span style=" text-decoration: underline; font-weight:600; color:#555555">some text</span> 

required, the correct result is obtained :

 <span style=" color:#555555"> <u> <strong>some text</strong> </u> </span> 
+1
source

Source: https://habr.com/ru/post/1338353/


All Articles