Подтвердить что ты не робот

Как я могу ускорить мой шаблон "Разделить и побеждать" XSLT, который заменяет определенные символы в строке?

ОБНОВЛЕНИЕ: Я добавил ответ на этот вопрос, который содержит почти все предложения, которые были даны. Исходный шаблон, приведенный в коде ниже, требует 45605ms, чтобы закончить реальный документ ввода (английский текст о программировании script). Пересмотренный шаблон в ответе wiki сообщества привел к тому, что время выполнения сократилось до 605 мс

!

Я использую следующий шаблон 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 script. Заменив приведенный выше шаблон escape-text только

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

делает время выполнения моего XSLT script переходом от 45 секунд до менее чем одной секунды в одном из моих документов.

Отсюда мой вопрос: как я могу ускорить мой шаблон escape-text? Я использую xsltproc, и я предпочел бы чистое решение XSLT 1.0. Решения XSLT 2.0 также приветствуются. Тем не менее, внешние библиотеки могут быть не полезны для этого проекта - я все равно буду интересоваться любыми решениями, использующими их, хотя.

4b9b3361

Ответ 1

Другой (дополнительной) стратегией было бы прекратить рекурсию раньше, прежде чем длина строки будет равна 1, если условие translate($s, $vChars, '') = $s истинно. Это должно обеспечить гораздо более быструю обработку строк, которые вообще не содержат никаких специальных символов, что, вероятно, является большинством из них. Конечно, результаты будут зависеть от того, насколько эффективна реализация xsltproc translate().

Ответ 2

Очень маленькая коррекция улучшила скорость в моих тестах примерно в 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

Вот более улучшенная версия, основанная на ответе @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

Для чего это стоит, моя текущая версия шаблона escape-text, который содержит большинство (отличных!) предложений, которые люди дали в ответ на мой вопрос. Для записи моя оригинальная версия в среднем взяла около 45605 мс на моем примере документа DocBook. После этого время выполнения уменьшилось на несколько этапов:

  • Удаление переменной left и right вместе с вызовом concat() привело к сокращению времени выполнения до 13052мс; эта оптимизация была взята из ответа Томалака.
  • Перемещение общего случая (то есть: данный символ не нуждается в каком-либо специальном экранировании), во-первых, во внутреннем элементе <xsl:choose> доведено до конца времени выполнения до 5812мс. Эта оптимизация была первой предложенной Dimitre.
  • Прервать рекурсию рано, сначала проверив, содержит ли данная строка какой-либо из специальных символов, доведя время до 612 мс. Эта оптимизация была предложена Майклом.
  • Наконец, я не смог устоять перед микро-оптимизацией после прочтения комментария Dimitre в Ответ Tomalak: я заменил вызовы <xsl:value-of select="concat('x', $s, 'y')"/> на <xsl:text>x</xsl:text><xsl:value-of select="$s"/><xsl:text>y</xsl:text>. Это привело к тому, что продолжительность работы составила около 606 мс (примерно на 1%).

В конце концов, функция заняла 606 мс вместо 45605 мс. Впечатляет!

<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

Как насчет использования EXSLT? Строковые функции в EXSLT имеют функцию replace. Я думаю, что это то, что поддерживается множеством реализаций XSLT.

Ответ 6

Обновление: Я исправил это, чтобы на самом деле работать; теперь это не ускорение!

Создание ответа @Wilfred...

После запуска функции EXPLT replace() я решил, что достаточно интересно опубликовать другой ответ, даже если это не полезно для OP. Это может быть полезно для других.

Это интересно из-за алгоритма: вместо основного алгоритма, работающего здесь (выполняя двоичный рекурсивный поиск, разделяя пополам на каждую рекурсию, обрезается всякий раз, когда 2 ^ n-я подстрока не имеет специальных символов в ней, и итерации по выбор специальных символов, когда строка length = 1 содержит специальный символ), алгоритм Jeni Tennison EXSLT ставит итерацию по множеству строк поиска во внешнем цикле. Поэтому во внутренней части цикла он ищет только одну строку за раз и может использовать подстроку-before()/substring-after() для разделения строки вместо слепого разделения пополам.

[Устаревший: я думаю, это достаточно, чтобы ускорить его. Мои тесты показывают ускорение 2.94x ​​ по сравнению с последней версией @Dimitre (средние 230 мс против 676 мс).] Я тестировал с использованием Saxon 6.5.5 в профилировщике Oxygen XML. В качестве входных данных я использовал XML-документ размером 7 Мбайт, который был в основном единственным текстом node, созданным с веб-страниц о javascript, повторяется. Мне кажется, что это отражает задачу, которую ОП пытается оптимизировать. Мне было бы интересно узнать, какие результаты получат другие, с их тестовыми данными и средами.

Зависимости

Здесь используется реализация замены XSLT, которая полагается на exsl: node -set(). Похоже, xsltproc поддерживает эту функцию расширения (возможно, раннюю версию). Так что это может работать из-за-коробки для вас, @Frerich; и для других процессоров, как это было с Saxon.

Однако, если мы хотим 100% чистого XSLT 1.0, я думаю, что было бы не так сложно изменить этот шаблон замены для работы без exsl: node -set(), если 2-й и 3-й параметры передаются в как узлы, а не RTF.

Вот код, который я использовал, который вызывает шаблон замены. Большая часть длины взята с верным способом, который я создал для поиска/замены узлов..., которые, вероятно, могут быть сокращены. (Но вы не можете выполнить поиск или заменить атрибуты узлов, так как в настоящее время набирается шаблон замены. Появится сообщение об попытке поместить атрибуты в элемент документа.)

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

Импортированная таблица стилей изначально была этот.

Однако, как отметил @Frerich, это никогда не давало правильного результата! Это должно научить меня не публиковать показатели производительности, не проверяя правильность!

Я могу видеть в отладчике, где это происходит неправильно, но я не знаю, никогда ли не работал шаблон EXSLT, или если он просто не работает в Saxon 6.5.5... любой вариант был бы удивителен.

В любом случае, EXSLT str: replace() указана для того, чтобы сделать больше, чем нам нужно, поэтому я изменил ее так, чтобы

  • требуют, чтобы входные параметры уже были узлами
  • как следствие, не требуется exsl: node -set()
  • не сортировать строки поиска по длине (они все являются одним символом в этом приложении)
  • не вставлять заменяющую строку между каждой парой символов, когда соответствующая строка поиска пуста

Вот модифицированный шаблон замены:

<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> более компактными и более легкими для чтения IMO.

Производительность:. С помощью этого пересмотренного шаблона задание выполняется примерно в 2,5 с по сравнению с моими 0,68 для моих последних тестов ведущего конкурента, таблицы стилей @Dimitre XSLT 1.0. Так что это не ускорение. Но опять же, у других были очень разные результаты тестов, чем у меня, поэтому я хотел бы услышать, что другие получают с этой таблицей стилей.

Ответ 7

После того, как @Frerich-Raabe опубликовал ответ сообщества wiki, который объединяет предложения до сих пор и достигает (по его данным) ускорения в 76 раз - большое поздравление всем!!!

Я не удержался, чтобы не идти дальше:

<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

ОК, я включу. Хотя это не так интересно, как оптимизация версии XSLT 1.0, вы действительно сказали, что решения XSLT 2.0 приветствуются, поэтому здесь мои.

<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(), чтобы окружать любой из других экранируемых символов кавычками.

В моих тестах это делает хуже, чем последнее предложение XSLT 1.0 от Dimitre, в два раза больше (но я составил свои собственные тестовые данные, а другие условия могут быть идиосинкразированными, поэтому я хотел бы знать, какие результаты получат другие.)

Почему более низкая производительность? Я могу только догадываться, потому что поиск регулярных выражений медленнее, чем поиск фиксированных строк.

Обновление: с помощью аналитической строки

В соответствии с предложением @Alejandro здесь используется аналитическая строка:

<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 секунд для завершения, против 1 - 1,4 с для вышеприведенного шаблона replace(). Назовите это 10-14x.:-( Это говорит мне о том, что разбиение и объединение больших строк на уровне XSLT намного дороже, чем переключение большой строки дважды во встроенную функцию.