Проблема с использованием exslt str:replace с набором узлов

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

Вот мой файл:

<!DOCTYPE xsl:stylesheet  [
    <!ENTITY nbsp   "&#160;">
    <!ENTITY yen    "&#165;">
    <!ENTITY circle  "&#9679;">
    <!ENTITY raquo "&#187;">
]>
<xsl:stylesheet 
    version="1.0" 
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:msxsl="urn:schemas-microsoft-com:xslt"
    xmlns:str="http://exslt.org/strings"
    xmlns:exsl="http://exslt.org/common"
    xmlns:regexp="http://exslt.org/regular-expressions"
    extension-element-prefixes="msxsl str exsl regexp">
<xsl:output 
    method="html" 
    indent="yes" 
    encoding="utf-8" 
    doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN" 
    doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" 
/>

<!-- Start of the main template -->
<xsl:template match="/Top">

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:fb="http://www.facebook.com/2008/fbml">
    <body>
        <xsl:variable name="text">
            -[this]- This is a test string -[that]-
            -[this]- This is another test string -[that]-
        </xsl:variable>
        text: <xsl:value-of select="$text" disable-output-escaping="yes" />

        <xsl:variable name="searches" xmlns="">
            <replacements xmlns="">
                <searches>
                    <search>-[this]-</search>
                    <search>-[that]-</search>
                </searches>
                <replaces>
                    <replace>**[this]**</replace>
                    <replace>**[that]**</replace>
                </replaces>
            </replacements>
        </xsl:variable>

        <xsl:variable name="search_set" select="exsl:node-set($searches)/replacements/searches/search" />
        <xsl:variable name="replace_set" select="exsl:node-set($searches)/replacements/replaces/replace" />
        search_set: <xsl:copy-of select="$search_set" />
        replace_set: <xsl:copy-of select="$replace_set" />
        <xsl:if test="$search_set">
            replaced via function:
            <xsl:value-of select="str:replace($text, $search_set, $replace_set)" disable-output-escaping="yes" />
            replaced via template:
            <xsl:variable name="replaced_tpl">
                <xsl:call-template name="str:replace">
                    <xsl:with-param name="string" select="$text"/>
                    <xsl:with-param name="search" select="$search_set"/>
                    <xsl:with-param name="replace" select="$replace_set"/>
                </xsl:call-template>
            </xsl:variable>
            <xsl:value-of select="$replaced_tpl" disable-output-escaping="yes" />
        </xsl:if>

    </body>
</html>

</xsl:template><!-- / end main template -->

<xsl:template name="str:replace" xmlns="">
   <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:when test="function-available('exsl:node-set')">
<!--  this converts the search and replace arguments to node sets
              if they are one of the other XPath types  -->
         <xsl:variable name="search-nodes-rtf">
            <xsl:copy-of select="$search" />
         </xsl:variable>
         <xsl:variable name="replace-nodes-rtf">
            <xsl:copy-of select="$replace" />
         </xsl:variable>
         <xsl:variable name="replacements-rtf">
            <xsl:for-each select="exsl:node-set($search-nodes-rtf)/node()">
               <xsl:variable name="pos"
                             select="position()" />
               <replace search="{.}">
                  <xsl:copy-of select="exsl:node-set($replace-nodes-rtf)/node()[$pos]" />
               </replace>
            </xsl:for-each>
         </xsl:variable>
         <xsl:variable name="sorted-replacements-rtf">
            <xsl:for-each select="exsl:node-set($replacements-rtf)/replace">
               <xsl:sort select="string-length(@search)"
                         data-type="number"
                         order="descending" />
               <xsl:copy-of select="." />
            </xsl:for-each>
         </xsl:variable>
         <xsl:call-template name="str:_replace">
            <xsl:with-param name="string"
                            select="$string" />
            <xsl:with-param name="replacements"
                            select="exsl:node-set($sorted-replacements-rtf)/replace" />
         </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
         <xsl:message terminate="yes">
            ERROR: template implementation of str:replace relies on exsl:node-set().
         </xsl:message>
      </xsl:otherwise>
   </xsl:choose>
</xsl:template>

<xsl:template name="str:_replace">
   <xsl:param name="string"
              select="''" />
   <xsl:param name="replacements"
              select="/.." />
   <xsl:choose>
      <xsl:when test="not($string)" />
      <xsl:when test="not($replacements)">
         <xsl:value-of select="$string" />
      </xsl:when>
      <xsl:otherwise>
         <xsl:variable name="replacement"
                       select="$replacements[1]" />
         <xsl:variable name="search"
                       select="$replacement/@search" />
         <xsl:choose>
            <xsl:when test="not(string($search))">
               <xsl:value-of select="substring($string, 1, 1)" />
               <xsl:copy-of select="$replacement/node()" />
               <xsl:call-template name="str:_replace">
                  <xsl:with-param name="string"
                                  select="substring($string, 2)" />
                  <xsl:with-param name="replacements"
                                  select="$replacements" />
               </xsl:call-template>
            </xsl:when>
            <xsl:when test="contains($string, $search)">
               <xsl:call-template name="str:_replace">
                  <xsl:with-param name="string"
                                  select="substring-before($string, $search)" />
                  <xsl:with-param name="replacements"
                                  select="$replacements[position() > 1]" />
               </xsl:call-template>
               <xsl:copy-of select="$replacement/node()" />
               <xsl:call-template name="str:_replace">
                  <xsl:with-param name="string"
                                  select="substring-after($string, $search)" />
                  <xsl:with-param name="replacements"
                                  select="$replacements" />
               </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
               <xsl:call-template name="str:_replace">
                  <xsl:with-param name="string"
                                  select="$string" />
                  <xsl:with-param name="replacements"
                                  select="$replacements[position() > 1]" />
               </xsl:call-template>
            </xsl:otherwise>
         </xsl:choose>
      </xsl:otherwise>
   </xsl:choose>
</xsl:template>


</xsl:stylesheet>

И вот результат:

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:fb="http://www.facebook.com/2008/fbml"><head><style type="text/css"></style></head><body>
        text: 
            -[this]- This is a test string -[that]-
            -[this]- This is another test string -[that]-

        search_set: <search xmlns="">-[this]-</search><search xmlns="">-[that]-</search>
        replace_set: <replace xmlns="">**[this]**</replace><replace xmlns="">**[that]**</replace>
            replaced via function:

            **[this]** This is a test string -[that]-
            **[this]** This is another test string -[that]-

            replaced via template:


            **[this]** This is a test string **[that]**
            **[this]** This is another test string **[that]**

</body></html>

Как вы можете видеть на выходе. При использовании функции заменяется только строка первого узла. Второй нет. Как видите, я скопировал код шаблона с exslt.org в файл, и сначала он не работал, пока я не добавил xmlns="" в шаблон str:replace следующим образом:

<xsl:template name="str:replace" xmlns="">

В этот момент форма шаблона работает, что наводит меня на мысль, что это проблема пространства имен. Я считаю, что в функции, когда она сортирует узлы и создает свои собственные узлы replace, например:

<replace search="{.}">
    <xsl:copy-of select="exsl:node-set($replace-nodes-rtf)/node()[$pos]" />
</replace>

Этот узел может оказаться в другом пространстве имен, поэтому последующий цикл не может их адресовать. Добавление атрибута xmlns к str:replace помещает все узлы, созданные в нем, в то же нулевое пространство имен, что и узлы, которые я передаю, и тогда это работает. Однако, что бы я ни пытался, я не могу заставить работать версию функции. Я даже удалил все пространства имен из файла и созданного набора узлов xml, и все равно это не работает. Честно говоря, все эти пространства имен меня немного сбивают с толку. Может быть, проблема даже вовсе не в этом.

Любая помощь будет принята с благодарностью, спасибо!


person ragamufin    schedule 26.09.2014    source источник
comment
Вы уверены, что ваш процессор поддерживает функцию? -- П.С. Было бы очень полезно свести пример только к тому, что необходимо для демонстрации проблемы. Если проблема связана с функцией, то шаблон совершенно не имеет значения, и наоборот.   -  person michael.hor257k    schedule 26.09.2014
comment
Я считаю, что это так, поскольку я могу обычно использовать функцию для замены строк, передавая только строки для 2-го и 3-го параметров. Кроме того, с набором узлов он работает, но заменяет только первую строку в наборе узлов. Я только позже поместил туда шаблон, чтобы попытаться отладить происходящее, и это помогло мне понять, что проблема заключается в коде функции, когда она создает свои собственные узлы replace, кажется, помещает их в пространство имен, которое он не может позже читать.   -  person ragamufin    schedule 26.09.2014


Ответы (1)


Обычно я могу использовать эту функцию для замены строк, передавая только строки для 2-го и 3-го параметров.

Проблема может заключаться в том, как эта функция реализована в вашем процессоре. Я предлагаю вам устранить все другие возможные причины сбоя и попробовать применить следующую таблицу стилей:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
xmlns:str="http://exslt.org/strings"
extension-element-prefixes="str">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>

<xsl:template match="/input">
    <output>
        <xsl:choose>
            <xsl:when test="function-available('str:replace')">
                <xsl:value-of select="str:replace(string, search, replace)" />  
            </xsl:when>
            <xsl:otherwise>
                <xsl:text>function str:replace() is not supported</xsl:text>
            </xsl:otherwise>
        </xsl:choose>
    </output>
</xsl:template>

</xsl:stylesheet>

к этому вводу:

<input>
    <string>Mary had a little lamb, its fleece was white as snow. 
And everywhere that Mary went, the lamb was sure to go.</string>
    <search>Mary</search>
    <search>lamb</search>
    <search>fleece</search>
    <replace>John</replace>
    <replace>dog</replace>
    <replace>fur</replace>
</input>

и сообщить о результатах.


Продолжение:

Пытаясь, я получаю <output>John had a little lamb, its fleece was white as snow. And everywhere that John went, the lamb was sure to go.</output>.

Ну тогда очевидно функция реализована не по спецификации. Это не так уж и плохо, поскольку большинство процессоров вообще не реализуют функцию str:replace(). Все, что вам нужно, это заполнить недостающую часть, вызвав именованный шаблон перед вызовом функции, например:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
xmlns:exsl="http://exslt.org/common"
xmlns:str="http://exslt.org/strings"
extension-element-prefixes="exsl str">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>

<xsl:variable name="dictionary"> 
    <search>Mary</search>
    <search>lamb</search>
    <search>fleece</search>
    <replace>John</replace>
    <replace>dog</replace>
    <replace>fur</replace>
</xsl:variable>

<xsl:template match="/input">
    <output>
        <xsl:call-template name="multi-replace">
            <xsl:with-param name="string" select="string"/>
            <xsl:with-param name="search-strings" select="exsl:node-set($dictionary)/search"/>
            <xsl:with-param name="replace-strings" select="exsl:node-set($dictionary)/replace"/>
        </xsl:call-template>
    </output>
</xsl:template>

<xsl:template name="multi-replace">
    <xsl:param name="string"/>
    <xsl:param name="search-strings"/>
    <xsl:param name="replace-strings"/>
    <xsl:choose>
        <xsl:when test="$search-strings">
            <xsl:call-template name="multi-replace">
                <xsl:with-param name="string" select="str:replace($string, $search-strings[1], $replace-strings[1])"/> 
                <xsl:with-param name="search-strings" select="$search-strings[position() > 1]"/>
                <xsl:with-param name="replace-strings" select="$replace-strings[position() > 1]"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$string"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

</xsl:stylesheet>

Применяется к следующему тестовому вводу:

<input>
    <string>Mary had a little lamb, its fleece was white as snow. And everywhere that Mary went, the lamb was sure to go.</string>
</input>

вы должны увидеть следующий результат:

<?xml version="1.0" encoding="utf-8"?>
<output>John had a little dog, its fur was white as snow. And everywhere that John went, the dog was sure to go.</output>

(Я не могу проверить это сам, так как ни один из моих процессоров не поддерживает эту функцию).

Это должно приблизить вас к указанному поведению, за исключением одного: в спецификации указано, что "Сначала заменяются самые длинные строки поиска". Если вы хотите реализовать и это, вы должны сначала отсортировать строки словаря, а если вы хотите, чтобы реализация была простой, вы должны ввести словарную строку парами.

person michael.hor257k    schedule 26.09.2014
comment
Правильно, у меня то же самое работает со строками для search и replace, однако это не работает, когда я передаю наборы узлов для этих параметров в соответствии с документацией. - person ragamufin; 27.09.2014
comment
@ragamufin Извините, я не понимаю, что вы говорите. В приведенном выше примере последние два аргумента функции str:replace() являются наборами узлов. Каков ваш результат? - person michael.hor257k; 27.09.2014
comment
ах, по какой-то причине я не видел ваш входной xml внизу. Пытаясь, я получаю <output>John had a little lamb, its fleece was white as snow. And everywhere that John went, the lamb was sure to go.</output>. Таким образом, даже в этом случае кажется, что заменен только первый узел. - person ragamufin; 29.09.2014
comment
@ragamufin Хорошо, теперь мы знаем, с чем имеем дело, и вопрос в том, как вы хотите действовать дальше. Я добавил к своему ответу то, что считаю самым простым способом. - person michael.hor257k; 29.09.2014
comment
Спасибо. Я сделал все, что мог в вопросе, чтобы объяснить вещи, но я уверен, что это было немного запутанно. Однако ваш подход здесь имеет смысл, используя код шаблона с exslt.org, я также могу заставить его работать, просто добавив xmlns="" в шаблон в соответствии с моим кодом выше. На самом деле у меня уже есть код, аналогичный тому, что вы разместили здесь, для замены нескольких строк. Когда я понял, что str:replace может принимать наборы узлов, я надеялся сделать свой код более компактным, используя синтаксис функции... - person ragamufin; 29.09.2014
comment
Извините, какую часть вы получаете и где я вас теряю? - person ragamufin; 29.09.2014
comment
@ragamufin Как мы сейчас об этом говорим? Есть ли ответ на этот вопрос - или еще есть моменты, которые нужно изучить? Если эти вопросы касаются использования пространств имен с шаблонами (в отличие от функций расширения), я предлагаю вам опубликовать отдельный вопрос. - person michael.hor257k; 29.09.2014
comment
Я ценю усилия, которые вы приложили, пытаясь ответить на вопрос, и, возможно, это связано с моим запутанным объяснением, но вопрос никогда не касался использования пространств имен с шаблоном. Как вы можете видеть в коде, который я отправил с вопросом, он уже работал, используя шаблон, скопированный с exslt.org. Вопрос заключался в том, как заставить версию функции работать с набором узлов, поскольку она всегда заменяет только первое значение в наборе узлов, а не последующие. - person ragamufin; 29.09.2014
comment
@ragamufin Итак, на этот вопрос не было ответа? Я считаю, что предложенный мной тест ясно показывает, что функция, реализованная вашим процессором, не работает с наборами узлов. Я не вижу, что вы можете с этим что-то сделать (кроме как отправить отчет об ошибке). - person michael.hor257k; 29.09.2014
comment
Я слышу тебя. Просто вы сделали почти то же самое, что и я, см. вопрос. Код, который я разместил, был просто для того, чтобы показать различные вещи, которые я пытался дать как можно больше информации. Я подожду немного, чтобы увидеть, действительно ли кто-то предоставляет способ использования функции с работающими узлами. Если никто этого не сделает, я отмечу ваш ответ как правильный. Как это? - person ragamufin; 30.09.2014
comment
@ragamufin Я просто спросил, закончили ли мы здесь. Я не вижу возможности получить от вас внятный ответ. Я предполагаю, что да, если вы не отредактируете свой вопрос и не добавите запрос на дополнительные разъяснения. - person michael.hor257k; 30.09.2014
comment
Если вы не можете предоставить мне способ заставить работать версию функции, то да, на этом мы закончили. - person ragamufin; 30.09.2014