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

后端 未结 2 889
隐瞒了意图╮
隐瞒了意图╮ 2021-01-20 19:18

This is a followup to this question.

I have several tags in a document with several semicolon separated style attributes. Right now I have

相关标签:
2条回答
  • 2021-01-20 19:46

    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>
    
    0 讨论(0)
  • 2021-01-20 19:52

    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)

    0 讨论(0)
提交回复
热议问题