Функция множественного поиска и замены XSL

Я пытаюсь использовать функцию XSL translate() для создания чего-то вроде функции поиска и замены следующим образом:

<xsl:template name="create-id">
    <xsl:param name="id" />
    <xsl:call-template name="search-and-replace">
        <xsl:with-param name="str" select="$id" />
        <xsl:with-param name="search">0123456789</xsl:with-param>
        <xsl:with-param name="replace">abcdefghij</xsl:with-param>
    </xsl:call-template>
</xsl:template>

<xsl:template name="search-and-replace">
    <xsl:param name="str" />
    <xsl:param name="search" />
    <xsl:param name="replace" />
    <xsl:variable name="newstr" select="translate($str, $search,
    $replace)" />
    <xsl:choose>
        <xsl:when test="contains($newstr, $search)">
            <xsl:call-template name="search-and-replace">
                <xsl:with-param name="str" select="$newstr" />
                <xsl:with-param name="search" select="$search" />
                <xsl:with-param name="replace" select="$replace" />
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$newstr" />
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

Однако что-то в моей логике здесь не так, поскольку она, похоже, удаляет последний символ в возвращаемой строке. Я предполагаю, что translate() заменяет только первый экземпляр каждого символа в строке и не является действительно рекурсивным.

Любые мысли или вход будут оценены.


person John    schedule 12.03.2011    source источник
comment
Опубликуйте несколько примеров ввода и вывода, которые вы видите, а также то, что вы ожидаете увидеть. Translate() может не делать того, что вы думаете, но без примера мы не можем сказать, что это такое.   -  person Jim Garrison    schedule 12.03.2011
comment
translate() не делает то, что вы думаете. Не стоит делать предположения о том, что делает функция, глядя на ее имя: сначала прочитайте спецификацию.   -  person Michael Kay    schedule 12.03.2011
comment
Хороший вопрос, +1. См. мой ответ для полного решения XSLT 1.0 проблемы с множественной заменой. Также дается подробное объяснение.   -  person Dimitre Novatchev    schedule 12.03.2011


Ответы (4)


Функция translate() может заменить только один символ другим одиночным символом (или пустым символом (удалить)). Таким образом, он не может решить проблему замены строки.

Вот полное решение XSLT 1.0 для проблемы множественной замены:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:my="my:my">
    <xsl:output omit-xml-declaration="yes"/>
    <xsl:strip-space elements="*"/>

    <my:params xml:space="preserve">
        <pattern>
            <old>&#xA;</old>
            <new><br/></new>
        </pattern>
        <pattern>
            <old>quick</old>
            <new>slow</new>
        </pattern>
        <pattern>
            <old>fox</old>
            <new>elephant</new>
        </pattern>
        <pattern>
            <old>brown</old>
            <new>white</new>
        </pattern>
    </my:params>

    <xsl:variable name="vPats"
         select="document('')/*/my:params/*"/>

    <xsl:template match="text()" name="multiReplace">
        <xsl:param name="pText" select="."/>
        <xsl:param name="pPatterns" select="$vPats"/>

        <xsl:if test="string-length($pText) >0">
            <xsl:variable name="vPat" select=
            "$vPats[starts-with($pText, old)][1]"/>

            <xsl:choose>
                <xsl:when test="not($vPat)">
                    <xsl:copy-of select="substring($pText,1,1)"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:copy-of select="$vPat/new/node()"/>
                </xsl:otherwise>
            </xsl:choose>

            <xsl:call-template name="multiReplace">
                <xsl:with-param name="pText" select=
                "substring($pText, 1 + not($vPat) + string-length($vPat/old/node()))"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

когда это преобразование применяется к следующему XML-документу:

<t>The quick
brown fox</t>

получен желаемый правильный результат:

The slow<br />white elephant

Пояснение:

  1. Используется именованный шаблон, который рекурсивно вызывает сам себя.

  2. Все множественные пары шаблонов замены --> замещающих пар предоставляются в одном внешнем параметре, который для удобства здесь указывается встроенным как элемент глобального уровня <my:params> .

  3. Рекурсия берет каждый символ в исходной строке (слева направо) и находит первый шаблон, который начинается с этого символа в этой позиции строки.

  4. Заменой может быть не только строка, но и любой узел. В этом конкретном случае мы заменяем каждый символ NL элементом <br/>.

person Dimitre Novatchev    schedule 12.03.2011
comment
Спасибо за вклад и объяснение. Я посмотрю, сработает ли это решение в моем случае. - person John; 14.03.2011
comment
@dimitre-novatchev, похоже, это сработает для меня. Однако мне нужно определить по крайней мере два набора параметров для данной таблицы стилей. Поскольку мой опыт работы с пространствами имен XSL несколько ограничен, как можно изменить код, чтобы передать определение и передать два разных набора параметров в вызов multiReplace? - person John; 14.03.2011
comment
@John: дерево <my:params> должно быть передано в качестве параметра или может быть в отдельном файле XML, а имя файла должно быть передано в качестве параметра преобразования. Теперь передача внешних параметров преобразованию зависит от конкретного используемого XSLT-процессора — нужно читать его документацию. Я думаю, что ваш лучший путь - принять этот ответ, а затем задать дополнительный вопрос, указав процессор XSLT, который вы используете, и спросив, как следует передавать внешние параметры для этого процессора XSLT. Я или многие другие могу дать хорошие ответы. - person Dimitre Novatchev; 14.03.2011

Определение функции translate($arg, $mapString, $transString):

Возвращает значение $arg, измененное таким образом, что каждый символ в значении $arg, который встречается в некоторой позиции N в значении $mapString, был заменен символом, который встречается в позиции N в значении $transString.

То есть он не заменяет подстроку другой строкой, а сопоставляет символы с другими символами. Для замены подстроки используйте что-то вроде

<xsl:template name="search-and-replace">
  <xsl:param name="str"/>
  <xsl:param name="search"/>
  <xsl:param name="replace"/>
  <xsl:choose>
    <xsl:when test="contains($str, $search)">
      <xsl:value-of select="substring-before($str, $search)"/>
      <xsl:value-of select="$replace"/>
      <xsl:call-template name="search-and-replace">
        <xsl:with-param name="str" select="substring-after($str, $search)"/>
        <xsl:with-param name="search" select="$search"/>
        <xsl:with-param name="replace" select="$replace"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$str"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>
person jelovirt    schedule 12.03.2011
comment
На самом деле я ищу больше сопоставления символов с несколькими экземплярами в строке, чем поиск и замену. Извините, если мой исходный пост был неясен. - person John; 14.03.2011

Похоже, ваше никогда не будет правдой, поскольку вы уже заменили все символы в $search на $replace в

<xsl:variable name="newstr" select="translate($str, $search,
    $replace)" />

заранее.

person Quincy    schedule 12.03.2011

str:replace from exslt. Он делает именно то, что вам нужно, и уже написан и протестирован некоторыми гуру xslt. Исходный код xslt также доступен здесь.

person earlNameless    schedule 12.03.2011
comment
Спасибо. Я не уверен, что наша система будет поддерживать exslt, но я посмотрю на это. - person John; 14.03.2011
comment
Предоставленный xsd все еще должен работать, так как он не использует ничего особенного. - person earlNameless; 15.03.2011