2010-08-26 5 views
5

Я новичок в преобразованиях xsl, и у меня есть вопрос. Я перекручивание через XML, как это:xsl трансформация

<PO> 
<Items> 
    <Item> 
    <Price>2</Price> 
    <Quantity>5</Quantity> 
    </Item> 
    <Item> 
    <Price>3</Price> 
    <Quantity>2</Quantity> 
    </Item>  
</Items> 
<QuantityTotal></QuantityTotal> 
</PO> 

Теперь я хочу, чтобы вставить значение в узле QuantityTotal:
Значение является суммой цен * количество всех элементов, в данном случае (2 * 5) + (3 * 2) = 16 Как это сделать, я пробовал это с помощью цикла и переменных, но переменные неизменяемы, поэтому я не знаю, как я могу это достичь.

Thx для вашей помощи

+1

+1 хороший вопрос –

+0

Хороший вопрос (+1). См. Мой ответ для решений как в XSLT 1.0 (не требуется никаких расширений), так и XSLT 2.0 –

+0

См. Также http://stackoverflow.com/questions/436998/multiply-2-numbers-and-then-sum-with-xslt и http ://переполнение стека.com/questions/1333558/xslt-to-sum-product-of-two-attributes – harpo

ответ

4

Вот XSLT решение - нет функции расширения не требуется:

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

<xsl:template match="node()|@*"> 
    <xsl:copy> 
     <xsl:apply-templates select="node()|@*"/> 
    </xsl:copy> 
</xsl:template> 

<xsl:template match="QuantityTotal"> 
    <xsl:copy> 
    <xsl:call-template name="sumProducts"> 
    <xsl:with-param name="pNodes" select="../Items/Item"/> 
    </xsl:call-template> 
    </xsl:copy> 
</xsl:template> 

<xsl:template name="sumProducts"> 
    <xsl:param name="pNodes"/> 
    <xsl:param name="pSum" select="0"/> 
    <xsl:param name="pEname1" select="'Price'"/> 
    <xsl:param name="pEname2" select="'Quantity'"/> 

    <xsl:choose> 
    <xsl:when test="not($pNodes)"> 
    <xsl:value-of select="$pSum"/> 
    </xsl:when> 
    <xsl:otherwise> 
    <xsl:call-template name="sumProducts"> 
     <xsl:with-param name="pNodes" select= 
     "$pNodes[position() > 1]"/> 
     <xsl:with-param name="pSum" select= 
     "$pSum 
     + 
     $pNodes[1]/*[name()=$pEname1] 
     * 
     $pNodes[1]/*[name()=$pEname2] 
     "/> 
    </xsl:call-template> 
    </xsl:otherwise> 
    </xsl:choose> 
</xsl:template> 
</xsl:stylesheet> 

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

<PO> 
    <Items> 
     <Item> 
      <Price>2</Price> 
      <Quantity>5</Quantity> 
     </Item> 
     <Item> 
      <Price>3</Price> 
      <Quantity>2</Quantity> 
     </Item> 
    </Items> 
    <QuantityTotal></QuantityTotal> 
</PO> 

разыскиваемый результат получается:

<PO> 
    <Items> 
     <Item> 
     <Price>2</Price> 
     <Quantity>5</Quantity> 
     </Item> 
     <Item> 
     <Price>3</Price> 
     <Quantity>2</Quantity> 
     </Item> 
    </Items> 
    <QuantityTotal>16</QuantityTotal> 
</PO> 
+0

+1 для хорошего решения XSLT 1.0 и XSLT 2.0! – 2010-08-27 20:16:06

1

Вот решение с использованием XSLT2, в котором множество узлов являются объектами первого класса. В XSLT1 вам нужно будет использовать расширение узла.

Объяснение ниже:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0"> 

    <xsl:template match="@*|node()"> 
     <xsl:copy> 
      <xsl:apply-templates select="@*|node()"/> 
     </xsl:copy> 
    </xsl:template> 

    <xsl:variable name="extendedItems" as="xs:integer*"> 
     <xsl:for-each select="//Item"> 
      <xsl:value-of select="./Price * ./Quantity"/> 
     </xsl:for-each> 
    </xsl:variable> 

    <xsl:variable name="total"> 
     <xsl:value-of select="sum($extendedItems)"/> 
    </xsl:variable> 

    <xsl:template match="//QuantityTotal"> 
     <xsl:copy> 
      <xsl:apply-templates select="@*"/> 
      <xsl:value-of select="$total"/> 
     </xsl:copy> 
    </xsl:template> 

</xsl:stylesheet> 

подход здесь заключается в использовании «тождественное преобразование», чтобы скопировать документ, при выполнении расчетов и вставить результат в выходной шаблон QuantityTotal. Первый шаблон копирует вход в выходной файл, но переопределяется более конкретным шаблоном для QuantityTotal внизу. Первое объявление переменной создает список расширенных затрат, а второе определение переменной суммирует затраты для получения общей суммы. Затем сумма вставляется в узел NumberTotal.

Ключом к пониманию XSL является то, что он носит декларативный характер. Наиболее распространенная концептуальная ошибка, допущенная почти всеми новичками, заключается в том, что таблица стилей является последовательной программой, обрабатывающей входной XML-документ. На самом деле все наоборот. XSL-движок читает XML-документ. и для каждого нового тега, с которым он сталкивается, он просматривает таблицу стилей для «лучшего» соответствия, выполняя этот шаблон.

EDIT:

Вот версия xslt1.1, которая работает с Saxon 6,5

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    xmlns:ex="http://exslt.org/common" 
    extension-element-prefixes="ex" 
    version="1.1"> 
    <xsl:template match="@*|node()"> 
     <xsl:copy> 
      <xsl:apply-templates select="@*|node()"/> 
     </xsl:copy> 
    </xsl:template> 
    <xsl:variable name="extendedItems"> 
     <xsl:for-each select="//Item"> 
      <extended> 
      <xsl:value-of select="./Price * ./Quantity"/> 
      </extended> 
      </xsl:for-each> 
    </xsl:variable> 
    <xsl:variable name="total"> 
     <xsl:value-of select="sum(ex:node-set($extendedItems/extended))"/> 
    </xsl:variable> 
    <xsl:template match="//QuantityTotal"> 
     <xsl:copy> 
      <xsl:apply-templates select="@*"/> 
      <xsl:value-of select="$total"/> 
     </xsl:copy> 
    </xsl:template> 
</xsl:stylesheet> 
+0

благодарим вас за ответ, но он по-прежнему не работает, как предполагается. Результат, который я получаю сейчас, равен 106, то есть он вычисляет первый элемент (результат = 10) и второй элемент (результат = 6), и поэтому переменная extendedItems его значение становится «106». Также, когда я использую функцию sum() для общего числа, она выдает ошибку, которую я должен использовать node-set(), поэтому мой код для total будет выглядеть следующим образом: sum (msxsl: node-set ($ extendedItems)). Что я делаю не так? Thx заранее –

+0

Вы используете это с XSLT1. У вас есть трансформатор XSLT2? –

+0

Какой XSL-процессор вы используете? –

2

Помимо превосходного ответа Dimitre, эта таблица стилей принимает другой подход:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:template match="node()|@*"> 
     <xsl:copy> 
      <xsl:apply-templates select="node()|@*"/> 
     </xsl:copy> 
    </xsl:template> 
    <xsl:template match="QuantityTotal"> 
     <xsl:copy> 
      <xsl:apply-templates select="../Items/Item[1]" mode="sum"/> 
     </xsl:copy> 
    </xsl:template> 
    <xsl:template match="Item" mode="sum"> 
     <xsl:param name="pSum" select="0"/> 
     <xsl:variable name="vNext" select="following-sibling::Item[1]"/> 
     <xsl:variable name="vSum" select="$pSum + Price * Quantity"/> 
     <xsl:apply-templates select="$vNext" mode="sum"> 
      <xsl:with-param name="pSum" select="$vSum"/> 
     </xsl:apply-templates> 
     <xsl:if test="not($vNext)"> 
      <xsl:value-of select="$vSum"/> 
     </xsl:if> 
    </xsl:template> 
</xsl:stylesheet> 

Выход:

<PO> 
    <Items> 
     <Item> 
      <Price>2</Price> 
      <Quantity>5</Quantity> 
     </Item> 
     <Item> 
      <Price>3</Price> 
      <Quantity>2</Quantity> 
     </Item> 
    </Items> 
    <QuantityTotal>16</QuantityTotal> 
</PO> 
+0

@Alejandro: Хороший ответ (+1). –

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