2009-10-08 6 views
2

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

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

Тогда мне нужно заменить «хорошо» с «4», чтобы напечатать список целых заказанных товаров по рейтингу с использованием своего рода функцию(). Поскольку я использую 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, но по нескольким причинам я не могу этого сделать в этом случае.

+0

На какой платформе вы работаете? Вы можете использовать функцию расширения, выполняющую замену. –

+0

Я не могу использовать функции расширения, я очень ограничен, мне нужно что-то в родном XSLT 1.0. – 2009-10-08 15:59:42

+0

Думаю, что на самом деле я могу переключиться на XSLT 2.0, но проблема такая же. В XSLT 2.0 я мог бы заменить функцию replace(), но эта функция работает так же, как и мой шаблон замены, всегда печатайте что-то, а не просто меняйте значение. – 2009-10-08 16:02:08

ответ

2

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

<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> 

Для пояснения, see my other answer.;-)


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> 
+0

Ударьте меня! Я думаю, что это проще, чем в другом примере. –

+0

+1, хороший подход, но, поскольку я понял вопрос, в одном документе могут быть как числовые, так и текстовые оценки, поэтому вам также нужно будет рассмотреть числовые оценки. –

+1

Есть решение для всего, держитесь. ;-) – Tomalak

1

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

<?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 - это еще один трюк. Это означает, что результатом выбора выражения является объединение 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> 
+0

Это выглядит сложным, но прекрасным (может быть, слишком сложным для меня), я собираюсь проверить его, и я вам кое-что скажу. Большое спасибо! – 2009-10-08 16:25:10

+0

Это +1 для усилий в одиночку. Хороший подход, иногда вы не можете обойти это так - 'concat()' hackery - это не всегда вариант. ;-) – Tomalak

1

Если вам нужно только заменить хороший рейтинг с 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 для ложного. Затем это используется в подстроке, поэтому только тот, который является истинным, будет подстрокой с длиной 1, остальные будут подстроки с длиной 0. Объединение этих всех вместе оставляет вам значение замены.

+0

Nice мышления. ;-) – Tomalak

+0

Cheers. Получил идею из вашего ответа на мой последний вопрос :) Серьезно, хотя, 7 секунд, я думал, что у меня есть этот в сумке! –

+0

Chris Reynolds: последний вариант ('not (boolean (rating = 'bad') или boolean (...') неверен - вы забыли взять 'string-length()' в учетную запись. ;-) – Tomalak

Смежные вопросы