Как ускорить мой шаблон XSLT «разделяй и властвуй», который заменяет определенные символы в строке? - PullRequest
13 голосов
/ 24 августа 2010

ОБНОВЛЕНИЕ: Я добавил ответ на этот вопрос , который включает почти все предложения, которые были даны.Исходный шаблон, приведенный в приведенном ниже коде, требовал 45605мс для завершения ввода исходного текста (текст на английском языке о программировании сценариев).Пересмотренный шаблон в вики-ответе сообщества уменьшил время выполнения до 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.Замена вышеуказанного escape-text шаблона просто

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

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

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

Ответы [ 8 ]

16 голосов
/ 25 августа 2010

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

7 голосов
/ 24 августа 2010

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

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

Должно быть еще чуть-чуть быстрее, но я не тестировал его. В любом случае, это короче. ; -)

3 голосов
/ 25 августа 2010

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

  • Удаление переменной left и right вместе с вызовом concat() привело к уменьшению времени выполнения до 13052 мс;эта оптимизация была взята из ответа Томалака .
  • Перемещение общего регистра (то есть: данный символ не нуждается в специальном экранировании) сначала во внутреннем элементе <xsl:choose>, приведшем к выполнениюдалее до 5812 мс.Эта оптимизация была впервые , предложенной Димитром .
  • . Прекращение рекурсии на ранних этапах путем первого тестирования, содержит ли данная строка какой-либо из специальных символов вообще, уменьшило время выполнения до 612 мс.Эта оптимизация была предложена Майклом .
  • Наконец, я не удержался от микрооптимизации после прочтения комментария Димитра в ответ Томалака : я заменил <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>
1 голос
/ 26 августа 2010

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

Построение ответа @ Уилфреда ...

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

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

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

Зависимость

При этом используется реализация XSLT replace, которая использует 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 ... любой из этих вариантов был бы удивительным.

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

  • требует, чтобы входные параметры уже были наборами узлов
  • как следствие, не требует 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 с - для моих недавних испытаний ведущего конкурента, таблицы стилей XSLT 1.0 @ Dimitre. Так что это не ускорение. Но, опять же, у других были результаты тестирования, отличные от моих, поэтому я хотел бы услышать, что другие получают с этой таблицей стилей.

1 голос
/ 24 августа 2010

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

0 голосов
/ 25 августа 2010

Хорошо, я добавлю. Хотя это не так интересно, как оптимизация версии 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>

Это просто использует регулярное выражение replace () для замены \ или "на" \ "или" \ "" соответственно; состоит из другого регулярного выражения replace (), заключающего в кавычки любые другие экранируемые символы.

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

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

Обновление: с использованием строки анализа

Согласно предложению @ 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 намного дороже, чем обход большой строки дважды во встроенной функции.

0 голосов
/ 25 августа 2010

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

<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 раз.

...