代码之家  ›  专栏  ›  技术社区  ›  Frerich Raabe

如何加快我的“分而治之”XSLT模板(它替换字符串中的某些字符)?

  •  13
  • Frerich Raabe  · 技术社区  · 15 年前

    更新: answer to this question 它包含了几乎所有已经给出的建议。下面代码中给出的原始模板是必需的 45605毫秒 完成一个真实世界的输入文档(关于脚本编程的英文文本)。修改后的模板 community wiki answer 带来了运行时 低至605ms !

    我使用下面的XSLT模板将字符串中的一些特殊字符替换为它们的转义变体;它使用分而治之策略递归地调用自己,最终查看给定字符串中的每个字符。然后决定是否应按原样打印字符,或是否需要任何形式的转义:

    <xsl:template name="escape-text">
    <xsl:param name="s" select="."/>
    <xsl:param name="len" select="string-length($s)"/>
    <xsl:choose>
        <xsl:when test="$len >= 2">
            <xsl:variable name="halflen" select="round($len div 2)"/>
            <xsl:variable name="left">
                <xsl:call-template name="escape-text">
                    <xsl:with-param name="s" select="substring($s, 1, $halflen)"/>
                    <xsl:with-param name="len" select="$halflen"/>
                </xsl:call-template>
            </xsl:variable>
            <xsl:variable name="right">
                <xsl:call-template name="escape-text">
                    <xsl:with-param name="s" select="substring($s, $halflen + 1)"/>
                    <xsl:with-param name="len" select="$halflen"/>
                </xsl:call-template>
            </xsl:variable>
            <xsl:value-of select="concat($left, $right)"/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:choose>
                <xsl:when test="$s = '&quot;'">
                    <xsl:text>&quot;\&quot;&quot;</xsl:text>
                </xsl:when>
                <xsl:when test="$s = '@'">
                    <xsl:text>&quot;@&quot;</xsl:text>
                </xsl:when>
                <xsl:when test="$s = '|'">
                    <xsl:text>&quot;|&quot;</xsl:text>
                </xsl:when>
                <xsl:when test="$s = '#'">
                    <xsl:text>&quot;#&quot;</xsl:text>
                </xsl:when>
                <xsl:when test="$s = '\'">
                    <xsl:text>&quot;\\&quot;</xsl:text>
                </xsl:when>
                <xsl:when test="$s = '}'">
                    <xsl:text>&quot;}&quot;</xsl:text>
                </xsl:when>
                <xsl:when test="$s = '&amp;'">
                    <xsl:text>&quot;&amp;&quot;</xsl:text>
                </xsl:when>
                <xsl:when test="$s = '^'">
                    <xsl:text>&quot;^&quot;</xsl:text>
                </xsl:when>
                <xsl:when test="$s = '~'">
                    <xsl:text>&quot;~&quot;</xsl:text>
                </xsl:when>
                <xsl:when test="$s = '/'">
                    <xsl:text>&quot;/&quot;</xsl:text>
                </xsl:when>
                <xsl:when test="$s = '{'">
                    <xsl:text>&quot;{&quot;</xsl:text>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="$s"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:otherwise>
    </xsl:choose>
    </xsl:template>
    

    这个模板占据了XSLT脚本所需的大部分运行时。更换上述部件 escape-text 模板仅包含

    <xsl:template name="escape-text">
        <xsl:param name="s" select="."/>
        <xsl:value-of select="$s"/>
    </xsl:template>
    

    在我的一个文档上,使XSLT脚本的运行时间从45秒变为不到1秒。

    因此我的问题是:我怎样才能加快我的速度 转义文本 模板?我在用 xsltproc 我更喜欢纯XSLT1.0解决方案。XSLT2.0解决方案也将受到欢迎。但是,外部库可能对这个项目没有用处-我仍然对使用它们的任何解决方案感兴趣。

    8 回复  |  直到 9 年前
        1
  •  16
  •   Mads Hansen    15 年前

    另一个(补充)策略是,如果条件允许,在字符串长度降到1之前提前终止递归 translate($s, $vChars, '') = $s 这是真的。这将使不包含任何特殊字符的字符串的处理速度大大加快,这些字符可能占大多数。当然,结果将取决于xsltproc实现 translate() 是。

        2
  •  7
  •   Dimitre Novatchev    15 年前

    一个很小的修正使我的测试速度提高了17倍 .

    有额外的改进,但我想这就足够了。。。:)

    <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns:my="my:my">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
     <xsl:strip-space elements="*"/>
    
     <xsl:variable name="vChars">"@|#\}&amp;^~/{</xsl:variable>
    
     <xsl:template match="node()|@*">
      <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
     </xsl:template>
    
     <xsl:template match="text()" name="escape-text">
      <xsl:param name="s" select="."/>
      <xsl:param name="len" select="string-length($s)"/>
    
      <xsl:choose>
        <xsl:when test="$len >= 2">
            <xsl:variable name="halflen" select="round($len div 2)"/>
            <xsl:variable name="left">
                <xsl:call-template name="escape-text">
                    <xsl:with-param name="s" select="substring($s, 1, $halflen)"/>
                    <xsl:with-param name="len" select="$halflen"/>
                </xsl:call-template>
            </xsl:variable>
            <xsl:variable name="right">
                <xsl:call-template name="escape-text">
                    <xsl:with-param name="s" select="substring($s, $halflen + 1)"/>
                    <xsl:with-param name="len" select="$halflen"/>
                </xsl:call-template>
            </xsl:variable>
            <xsl:value-of select="concat($left, $right)"/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:choose>
                <xsl:when test="not(contains($vChars, $s))">
                 <xsl:value-of select="$s"/>
                </xsl:when>
                <xsl:when test="$s = '&quot;'">
                    <xsl:text>&quot;\&quot;&quot;</xsl:text>
                </xsl:when>
                <xsl:when test="$s = '@'">
                    <xsl:text>&quot;@&quot;</xsl:text>
                </xsl:when>
                <xsl:when test="$s = '|'">
                    <xsl:text>&quot;|&quot;</xsl:text>
                </xsl:when>
                <xsl:when test="$s = '#'">
                    <xsl:text>&quot;#&quot;</xsl:text>
                </xsl:when>
                <xsl:when test="$s = '\'">
                    <xsl:text>&quot;\\&quot;</xsl:text>
                </xsl:when>
                <xsl:when test="$s = '}'">
                    <xsl:text>&quot;}&quot;</xsl:text>
                </xsl:when>
                <xsl:when test="$s = '&amp;'">
                    <xsl:text>&quot;&amp;&quot;</xsl:text>
                </xsl:when>
                <xsl:when test="$s = '^'">
                    <xsl:text>&quot;^&quot;</xsl:text>
                </xsl:when>
                <xsl:when test="$s = '~'">
                    <xsl:text>&quot;~&quot;</xsl:text>
                </xsl:when>
                <xsl:when test="$s = '/'">
                    <xsl:text>&quot;/&quot;</xsl:text>
                </xsl:when>
                <xsl:when test="$s = '{'">
                    <xsl:text>&quot;{&quot;</xsl:text>
                </xsl:when>
            </xsl:choose>
        </xsl:otherwise>
    </xsl:choose>
    </xsl:template>
    </xsl:stylesheet>
    
        3
  •  4
  •   Tomalak    15 年前

    根据@Dimitre的回答,这里有一个更完善的版本:

      <xsl:template match="text()" name="escape-text">
        <xsl:param name="s" select="."/>
        <xsl:param name="len" select="string-length($s)"/>
    
        <xsl:choose>
          <xsl:when test="$len &gt; 1">
            <xsl:variable name="halflen" select="round($len div 2)"/>
            <!-- no "left" and "right" variables necessary! -->
            <xsl:call-template name="escape-text">
              <xsl:with-param name="s" select="substring($s, 1, $halflen)"/>
            </xsl:call-template>
            <xsl:call-template name="escape-text">
              <xsl:with-param name="s" select="substring($s, $halflen + 1)"/>
            </xsl:call-template>
          </xsl:when>
          <xsl:otherwise>
            <xsl:choose>
              <xsl:when test="not(contains($vChars, $s))">
                <xsl:value-of select="$s"/>
              </xsl:when>
              <xsl:when test="contains('\&quot;', $s)">
                <xsl:value-of select="concat('&quot;\', $s, '&quot;')" />
              </xsl:when>
              <!-- all other cases can be collapsed, this saves some time -->
              <xsl:otherwise>
                <xsl:value-of select="concat('&quot;', $s, '&quot;')" />
              </xsl:otherwise>
            </xsl:choose>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:template>
    

    应该再快一点,但我还没有做基准测试。不管怎样,都要短一点。;-)

        4
  •  3
  •   Frerich Raabe    9 年前

    值得一提的是,这是我目前的版本 escape-text 模板,其中包含了大多数(优秀!)人们对我的问题提出的建议。作为记录,我的原始版本在我的DocBook示例文档上平均花费了大约45605ms。之后,运行时间分多个步骤减少:

    • left right 变量与 concat() 调用将运行时降低到13052ms;此优化来自 Tomalak's answer .
    • 首先在内部移动普通大小写(即:给定的字符不需要任何特殊转义) <xsl:choose> 元素将运行时间进一步降低到5812ms suggested by Dimitre
    • 首先测试给定的字符串是否包含任何特殊字符,从而提前中止递归,使运行时间降到612ms suggested by Michael .
    • 最后,我忍不住在读了Dimitre的评论后做了一个微优化 :我替换了 <xsl:value-of select="concat('x', $s, 'y')"/> <xsl:text>x</xsl:text><xsl:value-of select="$s"/><xsl:text>y</xsl:text>

    <xsl:variable name="specialLoutChars">"@|#\}&amp;^~/{</xsl:variable>
    
    <xsl:template name="escape-text">
        <xsl:param name="s" select="."/>
        <xsl:param name="len" select="string-length($s)"/>
        <xsl:choose>
            <!-- Common case optimization: 
                 no need to recurse if there are no special characters -->
            <xsl:when test="translate($s, $specialLoutChars, '') = $s">
                <xsl:value-of select="$s"/>
            </xsl:when>
            <!-- String length greater than 1, use DVC pattern -->
            <xsl:when test="$len > 1">
                <xsl:variable name="halflen" select="round($len div 2)"/>
                <xsl:call-template name="escape-text">
                    <xsl:with-param name="s" select="substring($s, 1, $halflen)"/>
                    <xsl:with-param name="len" select="$halflen"/>
                </xsl:call-template>
                <xsl:call-template name="escape-text">
                    <xsl:with-param name="s" select="substring($s, $halflen + 1)"/>
                    <xsl:with-param name="len" select="$len - $halflen"/>
                </xsl:call-template>
            </xsl:when>
            <!-- Special character -->
            <xsl:otherwise>
                <xsl:text>&quot;</xsl:text>
                <!-- Backslash and quot need backslash escape -->
                <xsl:if test="$s = '&quot;' or $s = '\'">
                    <xsl:text>\</xsl:text>
                </xsl:if>
                <xsl:value-of select="$s"/>
                <xsl:text>&quot;</xsl:text>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    
        5
  •  1
  •   Wilfred Springer    15 年前

    使用EXSLT怎么样?字符串在中起作用 EXSLT 有一个函数叫做 replace . 我认为很多XSLT实现都支持它。

        6
  •  1
  •   LarsH    15 年前

    我修正了这一点,以实际工作;现在,这不是一个加速!

    基于@Wilfred的答案。。。

    [已弃用: 我想这足以大大加快速度。我的测试显示速度加快了 2.94倍 超过@Dimitre最近的一次(平均230ms对676ms)。 web pages about javascript ,重复。在我看来,这似乎是代表的任务,操作试图优化。我很想看看其他人得到了什么结果,他们的测试数据和环境。

    other processors ,就像撒克逊一样。

    但是,如果我们想要100%纯xslt1.0,我认为修改这个替换模板以使其在没有exsl:节点集(),只要第2个和第3个参数作为节点集而不是RTF传入。

    下面是我使用的代码,它调用replace模板。我创建搜索/替换节点集的冗长方式占用了大部分篇幅。。。可能会缩短。(但您不能进行搜索或替换节点

    <xsl:stylesheet version="1.0" xmlns:str="http://exslt.org/strings"
        xmlns:foo="http://www.foo.net/something" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:import href="lars.replace.template.xsl"/>
    
        <foo:replacements>
            <replacement>
                <search>"</search>
                <replace>"\""</replace>
            </replacement>
            <replacement>
                <search>\</search>
                <replace>"\\"</replace>
            </replacement>
            <replacement>
                <search>@</search>
                <replace>"["</replace>
            </replacement>
            <replacement>
                <search>|</search>
                <replace>"["</replace>
            </replacement>
            <replacement>
                <search>#</search>
                <replace>"["</replace>
            </replacement>
            <replacement>
                <search>}</search>
                <replace>"}"</replace>
            </replacement>
            <replacement>
                <search>&amp;</search>
                <replace>"&amp;"</replace>
            </replacement>
            <replacement>
                <search>^</search>
                <replace>"^"</replace>
            </replacement>
            <replacement>
                <search>~</search>
                <replace>"~"</replace>
            </replacement>
            <replacement>
                <search>/</search>
                <replace>"/"</replace>
            </replacement>
            <replacement>
                <search>{</search>
                <replace>"{"</replace>
            </replacement>
        </foo:replacements>
    
        <xsl:template name="escape-text" match="text()" priority="2">
            <xsl:call-template name="str:replace">
                <xsl:with-param name="string" select="."/>
                <xsl:with-param name="search"
                    select="document('')/*/foo:replacements/replacement/search/text()"/>
                <xsl:with-param name="replace"
                    select="document('')/*/foo:replacements/replacement/replace/text()"/>
            </xsl:call-template>
        </xsl:template>
    
        <xsl:template match="node()|@*">
            <xsl:copy>
                <xsl:apply-templates select="node()|@*"/>
            </xsl:copy>
        </xsl:template>
    </xsl:stylesheet>
    

    导入的样式表最初是 this one .

    然而,正如@Frerich所指出的,这从来没有给出正确的输出! 这应该教会我不要在没有检查正确性的情况下发布绩效数据!

    不管怎样,EXSLT的str:替换()被指定做比我们需要的更多的事情,所以我修改了它以便

    • 要求输入参数已经是节点集
    • 因此,不需要exsl:节点集()
    • 不按长度对搜索字符串排序(在本应用程序中,它们都是一个字符)

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
       xmlns:str="http://exslt.org/strings">
       <!-- By Lars Huttar
        based on implementation of EXSL str:replace() by Jenni Tennison.
        http://www.exslt.org/str/functions/replace/str.replace.template.xsl
        Modified by Lars not to need exsl:node-set(), not to bother sorting
        search strings by length (in our application, all the search strings are of
        length 1), and not to put replacements between every other character
        when a search string is length zero.
        Search and replace parameters must both be nodesets.
        -->
    
       <xsl:template name="str:replace">
          <xsl:param name="string" select="''" />
          <xsl:param name="search" select="/.." />
          <xsl:param name="replace" select="/.." />
          <xsl:choose>
             <xsl:when test="not($string)" />
             <xsl:when test="not($search)">
                <xsl:value-of select="$string" />
             </xsl:when>
             <xsl:otherwise>
                <xsl:variable name="search1" select="$search[1]" />
                <xsl:variable name="replace1" select="$replace[1]" />
    
                <xsl:choose>
                   <xsl:when test="contains($string, $search1)">
                      <xsl:call-template name="str:replace">
                         <xsl:with-param name="string"
                            select="substring-before($string, $search1)" />
                         <xsl:with-param name="search"
                            select="$search[position() > 1]" />
                         <xsl:with-param name="replace"
                            select="$replace[position() > 1]" />
                      </xsl:call-template>
                      <xsl:value-of select="$replace1" />
                      <xsl:call-template name="str:replace">
                         <xsl:with-param name="string"
                            select="substring-after($string, $search)" />
                         <xsl:with-param name="search" select="$search" />
                         <xsl:with-param name="replace" select="$replace" />
                      </xsl:call-template>
                   </xsl:when>
                   <xsl:otherwise>
                      <xsl:call-template name="str:replace">
                         <xsl:with-param name="string" select="$string" />
                         <xsl:with-param name="search"
                            select="$search[position() > 1]" />
                         <xsl:with-param name="replace"
                            select="$replace[position() > 1]" />
                      </xsl:call-template>
                   </xsl:otherwise>
                </xsl:choose>
             </xsl:otherwise>
          </xsl:choose>
       </xsl:template>
    
    </xsl:stylesheet>
    

    这个简单模板的一个附带好处是,现在可以使用搜索节点的属性并替换参数。这将使 <foo:replacements> 数据更紧凑,更容易阅读。

    性能: 使用这个修改过的模板,这项工作只需2.5秒就可以完成,而我最近对主要竞争对手@Dimitre的xslt1.0样式表的测试只需0.68秒。所以这不是加速。但是,其他人的测试结果和我的非常不同,所以我想听听其他人对这个样式表的看法。

        7
  •  0
  •   Dimitre Novatchev    15 年前

    在@Frerich Raabe发布了一个社区wiki答案之后

    我忍不住不说:

    <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:variable name="specialLoutChars">"@|#\}&amp;^~/{</xsl:variable>
    
     <xsl:key name="kTextBySpecChars" match="text()"
      use="string-length(translate(., '&quot;@|#\}&amp;^~/', '') = string-length(.))"/>
    
     <xsl:template match="node()|@*">
      <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
     </xsl:template>
    
     <xsl:template match="text()[key('kTextBySpecChars', 'true')]" name="escape-text">
      <xsl:param name="s" select="."/>
      <xsl:param name="len" select="string-length($s)"/>
    
      <xsl:choose>
        <xsl:when test="$len >= 2">
            <xsl:variable name="halflen" select="round($len div 2)"/>
            <xsl:call-template name="escape-text">
                <xsl:with-param name="s" select="substring($s, 1, $halflen)"/>
                <xsl:with-param name="len" select="$halflen"/>
            </xsl:call-template>
            <xsl:call-template name="escape-text">
                <xsl:with-param name="s" select="substring($s, $halflen + 1)"/>
                <xsl:with-param name="len" select="$len - $halflen"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:when test="$len = 1">
            <xsl:choose>
                <!-- Common case: the character at hand needs no escaping at all -->
                <xsl:when test="not(contains($specialLoutChars, $s))">
                    <xsl:value-of select="$s"/>
                </xsl:when>
                <xsl:when test="$s = '&quot;' or $s = '\'">
                    <xsl:text>&quot;\</xsl:text>
                    <xsl:value-of select="$s"/>
                    <xsl:text>&quot;</xsl:text>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:text>&quot;</xsl:text>
                    <xsl:value-of select="$s"/>
                    <xsl:text>&quot;</xsl:text>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:when>
      </xsl:choose>
     </xsl:template>
    </xsl:stylesheet>
    

    这个转换(在我的数据上)实现了1.5倍的进一步加速。所以总的加速比应该超过100倍。

        8
  •  0
  •   LarsH    15 年前

    <xsl:stylesheet version="2.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    
        <xsl:template name="escape-text" match="text()" priority="2">
            <xsl:variable name="regex1">[@|#}&amp;^~/{]</xsl:variable>
            <xsl:variable name="replace1">"$0"</xsl:variable>
            <xsl:variable name="regex2">["\\]</xsl:variable>
            <xsl:variable name="replace2">"\\$0"</xsl:variable>
            <xsl:value-of select='replace(replace(., $regex2, $replace2),
                                  $regex1, $replace1)'/>
        </xsl:template>
    
        <xsl:template match="node()|@*">
            <xsl:copy>
                <xsl:apply-templates select="node()|@*"/>
            </xsl:copy>
        </xsl:template>
    
    </xsl:stylesheet>
    

    这只是使用regexp replace()分别用“\”或“\”替换\或“或”;由另一个regexp replace()组成,用引号将任何其他可转义字符括起来。

    更糟的

    为什么表现比较慢?我只能猜测这是因为搜索正则表达式比搜索固定字符串慢。

    使用分析字符串

    根据@Alejandro的建议,这里使用的是analyze string:

    <xsl:template name="escape-text" match="text()" priority="2">
        <xsl:analyze-string select="." regex='([@|#}}&amp;^~/{{])|(["\\])'>
            <xsl:matching-substring>
                <xsl:choose>
                    <xsl:when test="regex-group(1)">"<xsl:value-of select="."/>"</xsl:when>
                    <xsl:otherwise>"\<xsl:value-of select="."/>"</xsl:otherwise>
                </xsl:choose>
            </xsl:matching-substring>
            <xsl:non-matching-substring><xsl:value-of select="."/></xsl:non-matching-substring>
        </xsl:analyze-string>
    </xsl:template>
    

    虽然这似乎是一个好主意,但不幸的是,它并没有给我们带来性能上的胜利:在我的设置中,始终需要大约14秒才能完成,而上面的replace()模板需要1-1.4秒。那叫一个

    推荐文章