XSLT: заменить перед сортировкой - PullRequest
2 голосов
/ 08 октября 2009

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

<root>
    <item>
        <name>ABC</name>
        <rating>good</rating>
    </item>
    <item>
        <name>BCD</name>
        <rating>3</rating>
    </item>
</root>

Затем Мне нужно заменить 'good' на '4', чтобы распечатать весь список предметов, упорядоченный по рейтингу, используя функцию sort () . Поскольку я использую XSLT 1.0, я использую этот шаблон для замены:

<xsl:template name="string-replace">
  <xsl:param name="subject"     select="''" />
  <xsl:param name="search"      select="''" />
  <xsl:param name="replacement" select="''" />
  <xsl:param name="global"      select="false()" />

  <xsl:choose>
    <xsl:when test="contains($subject, $search)">
      <xsl:value-of select="substring-before($subject, $search)" />
      <xsl:value-of select="$replacement" />
      <xsl:variable name="rest" select="substring-after($subject, $search)" />
      <xsl:choose>
        <xsl:when test="$global">
          <xsl:call-template name="string-replace">
            <xsl:with-param name="subject"     select="$rest" />
            <xsl:with-param name="search"      select="$search" />
            <xsl:with-param name="replacement" select="$replacement" />
            <xsl:with-param name="global"      select="$global" />
          </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="$rest" />
        </xsl:otherwise>
      </xsl:choose>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$subject" />
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

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

Заранее спасибо!

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

Ответы [ 3 ]

2 голосов
/ 08 октября 2009

Вы можете сделать это:

<xsl:stylesheet 
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
  <xsl:output method="xml" encoding="utf-8" />

  <xsl:template match="/root">
    <xsl:for-each select="item">
      <!-- be sure to include every possible value of <rating>! -->
      <xsl:sort select="
        concat(
          substring('4', 1, rating = 'good' ),
          substring('3', 1, rating = 'medioce' ),
          substring('2', 1, rating = 'bad' ),
          substring('1', 1, rating = 'abyssmal' ),
          substring('4', 1, rating = '4' ),
          substring('3', 1, rating = '3' ),
          substring('2', 1, rating = '2' ),
          substring('1', 1, rating = '1' )
        )
      " order="descending" />
      <xsl:copy-of select="." />
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

С входом:

<root>
  <item>
    <name>ABC</name>
    <rating>abyssmal</rating>
  </item>
  <item>
    <name>GEH</name>
    <rating>bad</rating>
  </item>
  <item>
    <name>DEF</name>
    <rating>good</rating>
  </item>
  <item>
    <name>IJK</name>
    <rating>medioce</rating>
  </item>
</root>

Я получаю:

<item>
  <name>DEF</name>
  <rating>good</rating>
</item>
<item>
  <name>IJK</name>
  <rating>medioce</rating>
</item>
<item>
  <name>GEH</name>
  <rating>bad</rating>
</item>
<item>
  <name>ABC</name>
  <rating>abyssmal</rating>
</item>

Для объяснения см. Мой другой ответ . ; -)


EDIT

Изменено решение после этого комментария ОП:

Мне нужно использовать рейтинг (с строки заменены на целые числа), 3 время:

  1. сделать ключ с <xsl:key ... используя рейтинг
  2. Сортировать предметы по рейтингу
  3. Распечатать рейтинг.

На каждом шаге я должен использовать рейтинг ПОСЛЕ замены, (т.е. с использованием целого числа баллы). Я сделал это, повторяя concat(...) код 3 раза, но как вы видно это не слишком круто ... я хотел бы найти способ разместить concat (...) один раз, без необходимости повтори это.

Следующее решение XSLT 1.0 выполняет все эти запросы:

<xsl:stylesheet 
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:tmp="http://tempuri.org/"
  exclude-result-prefixes="tmp"
>
  <xsl:output method="xml" encoding="utf-8" />

  <!-- prepare a list of possible ratings for iteration -->
  <tmp:ratings>
    <tmp:rating num="1" />
    <tmp:rating num="2" />
    <tmp:rating num="3" />
    <tmp:rating num="4" />
  </tmp:ratings>

  <!-- index items by their rating -->
  <xsl:key 
    name="kItemByRating" 
    match="item" 
    use="concat(
      substring('4', 1, rating = 'good' ),
      substring('3', 1, rating = 'medioce' ),
      substring('2', 1, rating = 'bad' ),
      substring('1', 1, rating = 'abyssmal' ),
      substring('4', 1, rating = '4' ),
      substring('3', 1, rating = '3' ),
      substring('2', 1, rating = '2' ),
      substring('1', 1, rating = '1' )
    )
  " />

  <!-- we're going to need that later-on -->
  <xsl:variable name="root" select="/" />

  <xsl:template match="/root">
    <!-- iterate on the prepared list of ratings -->
    <xsl:apply-templates select="document('')/*/tmp:ratings/tmp:rating">
      <xsl:sort select="@num" order="descending" />
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="tmp:rating">
    <xsl:variable name="num" select="@num" />
    <!-- 
      The context node is part of the XSL file now. As a consequence,
      a call to key() would be evaluated within the XSL file.

      The for-each is a means to change the context node back to the 
      XML file, so that the call to key() can return <item> nodes.
    -->
    <xsl:for-each select="$root">
      <!-- now pull out all items with a specific rating -->
      <xsl:apply-templates select="key('kItemByRating', $num)">
        <!-- note that we use the variable here! -->
        <xsl:with-param name="num" select="$num" />
        <xsl:sort select="@name" />
      </xsl:apply-templates>
    </xsl:for-each>
  </xsl:template>

  <xsl:template match="item">
    <xsl:param name="num" select="''" />
    <xsl:copy>
      <!-- print out the numeric rating -->
      <xsl:attribute name="num">
        <xsl:value-of select="$num" />
      </xsl:attribute>
      <xsl:copy-of select="node() | @*" />
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>
1 голос
/ 08 октября 2009

Если вам нужно только заменить хороший рейтинг на 4, попробуйте это. Он заменит хорошую оценку на 4 с целью сортировки и оставит все оценки, которые не являются хорошими, как они есть. Лишние пробелы облегчают чтение / понимание.

<xsl:for-each select="item">
    <xsl:sort select="
        concat(
          substring(
            '4', 
            1, 
            boolean(rating = 'good')
          ),
          substring(
            rating, 
            1, 
            not(boolean(rating = 'good'))
          )
        )
    "/>
</xsl:for-each>

Если вам нужно заменить несколько оценок, но некоторые из них уже числовые, вы можете сделать следующее:

        concat(
          substring(
            '4', 
            1, 
            boolean(rating = 'good')
          ),
          substring(
            '3', 
            1, 
            boolean(rating = 'average')
          ),
          substring(
            '2', 
            1, 
            boolean(rating = 'bad')
          ),
          substring(
            rating, 
            1, 
            not(boolean(rating = 'bad') or boolean(rating = 'average') or boolean(rating = 'good'))
          )
        )

Логическое значение либо преобразуется в 1 для истины, либо в 0 для ложных. Затем он используется в подстроке, поэтому только та, которая имеет значение true, будет подстрокой с длиной 1, остальные будут подстрокой с длиной 0. Конкатенация всех этих вместе оставляет вас со значением замены.

1 голос
/ 08 октября 2009

Если у вас есть только небольшой набор предопределенных замен, вы можете использовать следующий подход:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:myExt="http://www.example.com/myExtension"
    exclude-result-prefixes="myExt">

    <xsl:output method="xml" indent="yes"/>

    <myExt:replacements>
      <item>
        <value>good</value>
        <replacement>4</replacement>
      </item>
      <item>
        <value>very good</value>
        <replacement>5</replacement>
      </item>
    </myExt:replacements>

    <xsl:template match="root">
      <out>
         <xsl:for-each select="item">
           <xsl:sort select="number(document('')/xsl:stylesheet/myExt:replacements/item[value=current()/rating]/replacement | rating)" order="ascending"/>
           <item>
             <name>
               <xsl:value-of select="name"/>
             </name>
             <rating>
               <xsl:value-of select="document('')/xsl:stylesheet/myExt:replacements/item[value=current()/rating]/replacement | rating"/>
             </rating>
           </item>
         </xsl:for-each>
       </out>
    </xsl:template>

Использование document('') - это трюк, позволяющий получить доступ к узлу внутри документа таблицы стилей. В нашем случае это набор узлов, определяющих замены, которые нужно сделать.

Использование | rating в атрибуте select элемента xsl:sort - еще одна хитрость. Это означает, что результатом выражения select является объединение document('')/xsl:stylesheet/myExt:replacements/item[value=current()/rating]/replacement и rating. Когда выражение select оценивается, учитывается только первый элемент результирующего набора узлов. Это приводит к тому, что если замена не определена, будет использоваться значение элемента rating.

Вот как будет выглядеть выходной документ для вас:

<?xml version="1.0" encoding="utf-8"?>
<out>
  <item>
    <name>BCD</name>
    <rating>3</rating>
  </item>
  <item>
    <name>ABC</name>
    <rating>4</rating>
  </item>
</out>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...