通过XSLT替换XHTML中的style =属性时出现模糊规则匹配

时间:2011-02-04 23:16:15

标签: xslt xhtml transform

这是this question的后续内容。

我在文档中有几个<span>标记,其中包含多个以分号分隔的样式属性。现在我有3个特定的样式属性,我正在寻找转换成标签。只要style属性只包含三个样式属性中的一个,所有这些在上面的示例中都很有效。如果跨度更大,我会得到一个模糊的规则匹配。

我正在寻找的三个样式属性是font-style:italicfont-weight:600text-decoration:underline,它们应从样式属性中删除并转换为<em>,{{分别为1}}和<strong>

这是我目前的XSLT:

<u>

哪些会生成模糊规则警告,但在包含多个列出属性的某些元素上无法正常工作。

输入的一个例子:

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

转变为:

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

当所需结果为:

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

如何修复模糊规则匹配呢?

提前致谢


更新

如果我将每个模板上的<span style="color:#555555"><b><u>some text</u></b></span> 设置为降序值并在第一个XSLT运行的输出上再次运行XSLT,那么一切都按预期工作。必须有一种比通过转换运行两次更简单的方法。有什么想法吗?


正如Alejandro和Tomalak所建议的那样,用CSS类的priorty属性替换style属性也是一种选择。

2 个答案:

答案 0 :(得分:2)

编辑:为了防止真正的问题被隐藏,我简化了样式表:

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

输出:

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

注意:使用空间规范化进行更简单的解析,以匹配存在性比较中的属性。生成内容而不进行优化(选择不匹配,选择匹配)。输出嵌套元素的“有状态”或“堆叠”命名模板。无论哪种方式,有两个规则(标识和span@style覆盖它)和两个名称模板(解析器/标记器和嵌套内容的生成器)

原始样式表:

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

注意:递归天堂。嵌套的标记生成器,用于解析样式属性。嵌套内容的“有状态”模板(顺便说一下,性能匹配属性)

答案 1 :(得分:1)

以下是使用FXSL str-split-to-words模板/函数的XSLT 1.0解决方案:

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

在提供的XML文档上应用此转换时:

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

产生了想要的正确结果

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