Главная » XSLT » Замена текста

0

Задача

Требуется заменить все вхождения заданной подстроки другой строкой.

Решение XSLT 1.0

Следующий рекурсивный шаблон заменяет все вхождения искомой строки на строку замены.

<xsl:template name="search-and-replace"> <xsl:param name="input"/> <xsl:param name="search-string"/> <xsl:param name="replace-string"/> <xsl:choose>

<!– Смотрим, содержит ли входная строка искомую –> <xsl:when test="$search-string and

contains($input,$search-string)"> <!– Если да, конкатенируем подстроку, предшествующую искомой, со строкой замены, и со строкой, являющейся результатом рекурсивного применения шаблона к оставшейся подстроке –> <xsl:value-of

select="substring-before($input,$search-string)"/> <xsl:value-of select="$replace-string"/> <xsl:call-template name="search-and-replace"> <xsl:with-param name="input"

select="substring-after($input,$search-string)"/> <xsl:with-param name="search-string" select="$search-string"/> <xsl:with-param name="replace-string" select="$replace-string"/> </xsl:call-template> </xsl:when> <xsl:otherwise>

<!– Больше вхождений искомой строки нет, поэтому возвращаем текущую входную строку –> <xsl:value-of select="$input"/> </xsl:otherwise> </xsl:choose> </xsl:template>

Если вы хотите заменять только слова целиком, то следует проверять, что не­посредственно до и после искомой строки находятся символы, принадлежащие классу разделителей слов. Мы будем считать, что разделителями являются сим­волы, хранящиеся в переменной $punc, а также все символы пропуска.

<xsl:template name="search-and-replace-whole-words-only"> <xsl:param name="input"/> <xsl:param name="search-string"/> <xsl:param name="replace-string"/>

<xsl:variable name="punc" select="concat(‘.,;:()[]!?$@&amp;&quot;’,&quot;&apos;&quot;)"/> <xsl:choose>

<!— Смотрим, содержит ли входная строка искомую —> <xsl:when test="contains($input,$search-string)"> <!– Если да, проверяем, что до и после нее находятся разделители слов –>

<xsl:variable name="before"

select="substring-before($input,$search-string)"/> <xsl:variable name="before-char"

select="substring(concat(‘ ‘,$before),

string-length($before) + 1,1)"/> <xsl:variable name="after"

select="substring-after($input,$search-string)"/> <xsl:variable name="after-char"

select="substring($after,1,1)"/> <xsl:value-of select="$before"/> <xsl:choose>

<xsl:when test="(not(normalize-space($before-char)) or contains($punc,$before-char)) and (not(normalize-space($after-char)) or contains($punc,$after-char))"> <xsl:value-of select="$replace-string"/> </xsl:when> <xsl:otherwise>

<xsl:value-of select="$search-string"/> </xsl:otherwise> </xsl:choose>

<xsl:call-template name="search-and-replace-whole-words-only"> <xsl:with-param name="input" select="$after"/> <xsl:with-param name="search-string" select="$search-string"/> <xsl:with-param name="replace-string" select="$replace-string"/> </xsl:call-template> </xsl:when> <xsl:otherwise>

<!– Больше вхождений искомой строки нет, поэтому возвращаем текущую входную строку —>

<xsl:value-of select="$input"/> </xsl:otherwise> </xsl:choose> </xsl:template>

Обратите внимание на то, как переменная $punc строится с помощью функ­ции concat(), чтобы в нее вошли символы одиночной и двойной кавычек.

Никак по-другому это сделать невозможно, поскольку ни XPath, ни XSLT, в отличие от языка C, не позволяют экранировать специальные символы с помощью обратной косой черты (\). В XPath 2.0 кавычку можно ввести в текст программы, записав ее два раза подряд.

XSLT 2.0

Функциональность шаблона search-and-replace в версии 2.0 встроена в функцию replace(). Функциональность шаблона search-and-replace- whole-words-only можно имитировать с помощью регулярных выражений для сопоставления со словами:

<xsl:function name="ckbk:search-and-replace-whole-words-only"> <xsl:param name="input" as="xs:string"/> <xsl:param name="search-string" as="xs:string"/> <xsl:param name="replace-string" as="xs:string"/> <xsl:sequence select="replace($input, concat(‘(A|\W)’,

$search-string,'(\W|$),),concat(,$1′,$replace-string,,$2,))"/>

</xsl:function>

Во многих реализациях регулярных выражений для сопоставления с границей слова предусмотрен метасимвол \b, но в XPath 2.0 он не поддерживается.

Здесь мы строим регулярное выражение, окружая строку $search-string конструкциями (Л!Ш) и (\W|$), где \W означает «не \w» или «не символ, входящий в состав слова». Метасимволы Л и $ учитывают случай, когда слово нахо­дится в начале или в конце строки. Мы должны также вернуть сопоставленный сим­вол назад в текст, воспользовавшись ссылками на запомненные группы $1 и $2.

Функция replace() позволяет больше, чем в решении для XPath 1.0, так как она пользуется регулярными выражениями и может запоминать отдельные сопо­ставленные части и подставлять их в строку замены с помощью псевдоперемен­ных $1, $2 и т.д. Мы изучим функцию replace() более детально в рецепте 2.10.

Обсуждение

Поиск и замена – типичная задача обработки текста. Представленное выше решение – это самая прямолинейная реализация, написанная на чистом XSLT. У читателя может возникнуть мысль, что производительность такого решения не­достаточна. Ведь для каждого выхождения искомой строки вызываются функции contains(), substring-before() и substring-after(). Вполне вероят­но, что каждая из этих функций повторно просматривает всю входную строку в поисках искомой. И, стало быть, при таком подходе выполняется на два поиска больше, чем необходимо. Немного поразмыслив, вы можете найти решения, пока­занные в примерах 2.4 и 2.5, которые на первый взгляд представляются более эф­фективными.

Пример 2.4. Использование временной строки в неудачной попытке улуч­шить производительность поиска и замены

<xsl:template name="search-and-replace"> <xsl:param name="input"/> <xsl:param name="search-string"/> <xsl:param name="replace-string"/>

<!– Найти подстроку, предшествующую искомой строке, и сохранить ее в переменной –> <xsl:variable name="temp"

select="substring-before($input,$search-string)"/> <xsl:choose>

<!– Если $temp не пуста или входная строка начинается с искомой подстроки, то необходимо произвести замену. Тем самым мы избегаем вызова функции contains(). –>

<xsl:when test="$temp or starts-with($input,$search-string)"> <xsl:value-of select="concat($temp,$replace-string)"/> <xsl:call-template name="search-and-replace">

<!– Вызова substring-after избегаем за счет использования длины temp и искомой строки для извлечения остатка строки в рекурсивном вызове. —> <xsl:with-param name="input"

select="substring($input,string-length($temp)+

string-length($search-string)+1)"/> <xsl:with-param name="search-string"

select="$search-string"/> <xsl:with-param name="replace-string" select="$replace-string"/> </xsl:call-template> </xsl:when> <xsl:otherwise>

<xsl:value-of select="$input"/> </xsl:otherwise> </xsl:choose> </xsl:template>

Пример 2.5. Использование временного целого в неудачной попытке улуч­шить производительность поиска и замены

<xsl:template name="search-and-replace"> <xsl:param name="input"/> <xsl:param name="search-string"/> <xsl:param name="replace-string"/>

<!– Найти длину подстроки, предшествующей искомой строке, и сохранить ее в переменной –>

<xsl:variable name="temp"

select="string-length(substring-before($input,$search-string))"/> <xsl:choose>

<!– Если $temp не равно 0 или входная строка начинается с искомой подстроки, то необходимо произвести замену. Тем самым мы избегаем вызова функции contains(). –>

<xsl:when test="$temp or starts-with($input,$search-string)"> <xsl:value-of select="substring($input,1,$temp)"/> <xsl:value-of select="$replace-string"/>

<!– Вызова substring-after избегаем за счет использования temp и длины искомой строки для извлечения остатка строки в рекурсивном вызове. –> <xsl:call-template name="search-and-replace"> <xsl:with-param name="input"

select="substring($input,$temp+

string-length($search-string)+1)"/> <xsl:with-param name="search-string"

select="$search-string"/> <xsl:with-param name="replace-string"

select="$replace-string"/> </xsl:call-template> </xsl:when> <xsl:otherwise>

<xsl:value-of select="$input"/> </xsl:otherwise> </xsl:choose> </xsl:template>

Идея обоих вариантов одна и та же: если запомнить, где функция substring- before() нашла соответствие, то можно воспользоваться этой информацией для того, чтобы не вызывать функции contains() и substring-after(). Но мы вынуждены обращаться к функции starts-with(), чтобы выделить случай, когда substring-before() возвращает пустую строку; такое может случиться, если искомая строка отсутствует или исходная строка начинается с искомой. Впрочем, starts-with(), вероятно, работает быстрее, чем contains(), посколь­ку ей не нужно просматривать больше символов, чем содержится в искомой строке. Второй вариант отличается от первого предположением, что сохранение целочислен­ного смещения может оказаться эффективнее сохранения подстроки целиком.

Увы, ни одна из этих оптимизаций не дает никакого выигрыша при использова­нии процессора XSLT Xalan. Более того, при некоторых входных данных реализации Saxon и XT показывают на порядок большее время работы! Столкнувшись с этим па­радоксальным результатом, я сначала предположил, что использование переменной $temp в рекурсивном вызове как-то препятствует оптимизации хвостовой рекурсии в Saxon (см. рецепт 2.6). Однако, экспериментируя с длинными входными строка­ми, в которых искомая строка встречается много раз, я не сумел вызвать переполнение стека. Тогда я заподозрил, что по какой-то причине функция substring() в XSLT работает медленнее, чем substring-before() и substring-after(). Майкл Кэй, автор реализации Saxon, указал, что substring() действительно работа­ет медленно из-за сложных правил, которые приходится поддерживать, в том числе округления аргументов с плавающей точкой, обработки особых случаев, когда на­чальная или конечная точки оказываются за границами строки, и вопросов, свя­занных с суррогатными парами Unicode. Напротив, функции substring-before() и substring-after() гораздо лучше транслируются на язык Java.

Отсюда следует извлечь урок: оптимизация – дело непростое, особенно в XSLT, когда имеются существенные различия между реализациями, а в новых версиях авторы стараются применить дополнительные оптимизации. Если вы не готовы часто профилировать программу, то лучше ограничиться простыми решениями. К числу достоинств простых решений можно отнести и то, что, скорее всего, они будут вести себя одинаково в разных реализациях XSLT.

Мангано Сэл  XSLT. Сборник рецептов. – М.: ДМК Пресс, СПБ.: БХВ-Петербург, 2008. – 864 с.: ил.

По теме:

  • Комментарии