Умножьте 2 числа, а затем суммируйте

У меня трудное время, пытаясь сделать что-то похожее на то, что это должно быть очень легко сделать. Я в основном хочу умножить 2 числа в node, а затем суммировать общее число этих чисел для всех узлов. Вот код XSLT, который я пробовал.

<xsl:value-of select="sum(Parts/Part/Quantity * Parts/Part/Rate)"/>

Этот код приводит к ошибке, которая говорит: "Аргумент 1 суммы функции не может быть преобразован в набор node".

Кто-нибудь имеет представление о том, что не так, или как я могу выполнить то, что я пытаюсь сделать?

Ответ 1

Вот три возможных решения:

Решение1 XSLT2:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>

    <xsl:template match="/">
      <xsl:sequence select="sum(/*/*/(rate * quantity))"/>
    </xsl:template>
</xsl:stylesheet>

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

<parts>
  <part>
        <rate>0.37</rate>
    <quantity>10</quantity>
  </part>
  <part>
        <rate>0.03</rate>
    <quantity>10</quantity>
  </part>
</parts>

Полученный результат получается:

4

Решение XSLT 2.0 использует тот факт, что в XPath 2.0 разрешено, что правильный аргумент последнего оператора "/" может быть выражением или вообще функцией. Это выражение/функция применяется для каждого из выбранных до сих пор узлов, действующих как контекст node, и каждое приложение функции производит один результат.

Solution2 XSLT 1.0:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>

    <xsl:template match="/">
      <xsl:call-template name="sumProducts">
        <xsl:with-param name="pList" select="*/*"/>
      </xsl:call-template>
    </xsl:template>

    <xsl:template name="sumProducts">
        <xsl:param name="pList"/>
        <xsl:param name="pAccum" select="0"/>

        <xsl:choose>
          <xsl:when test="$pList">
            <xsl:variable name="vHead" select="$pList[1]"/>

            <xsl:call-template name="sumProducts">
              <xsl:with-param name="pList" select="$pList[position() > 1]"/>
              <xsl:with-param name="pAccum"
               select="$pAccum + $vHead/rate * $vHead/quantity"/>
            </xsl:call-template>
          </xsl:when>
          <xsl:otherwise>
            <xsl:value-of select="$pAccum"/>
          </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

При применении к вышеуказанному XML-документу создается правильный результат:

4

Это типичное рекурсивное решение XSLT 1.0. Обратите внимание, как шаблон sumProducts вызывает себя рекурсивно, пока не будет обработан весь список ввода, переданный в параметре $pList.

Решение3 FXSL (XSLT 1.0):

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
xmlns:test-map-product="test-map-product"
exclude-result-prefixes="xsl ext test-map-product"
>
   <xsl:import href="sum.xsl"/>
   <xsl:import href="map.xsl"/>
   <xsl:import href="product.xsl"/>

   <!-- This transformation is to be applied on:
        salesMap.xml

        It contains the code of the "sum of products" from the 
        article "The Functional Programming Language XSLT"
     -->

   <test-map-product:test-map-product/>

   <xsl:output method="text"/>

   <xsl:template match="/">
     <!-- Get: map product /sales/sale -->
     <xsl:variable name="vSalesTotals">
         <xsl:variable name="vTestMap" select="document('')/*/test-map-product:*[1]"/>
         <xsl:call-template name="map">
           <xsl:with-param name="pFun" select="$vTestMap"/>
           <xsl:with-param name="pList1" select="/sales/sale"/>
         </xsl:call-template>
     </xsl:variable>

     <!-- Get sum map product /sales/sale -->
      <xsl:call-template name="sum">
        <xsl:with-param name="pList" select="ext:node-set($vSalesTotals)/*"/>
      </xsl:call-template>
   </xsl:template>

    <xsl:template name="makeproduct" match="*[namespace-uri() = 'test-map-product']">
      <xsl:param name="arg1"/>

      <xsl:call-template name="product">
        <xsl:with-param name="pList" select="$arg1/*"/>
      </xsl:call-template>
    </xsl:template>
</xsl:stylesheet>

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

<sales>
  <sale>
    <price>3.5</price>
    <quantity>2</quantity>
    <Discount>0.75</Discount>
    <Discount>0.80</Discount>
    <Discount>0.90</Discount>
  </sale>
  <sale>
    <price>3.5</price>
    <quantity>2</quantity>
    <Discount>0.75</Discount>
    <Discount>0.80</Discount>
    <Discount>0.90</Discount>
  </sale>
</sales>

Производится правильный результат:

7,5600000000000005

В последнем случае для каждого sale мы вычисляем произведение price, quantity и все доступные (переменные числа) discount -s.

FXSL - это чистая реализация функций более высокого порядка XSLT. В этом примере функция f:map() более высокого порядка используется для отображения функции f:product() в списке дочерних элементов каждого элемента sale. Затем результаты суммируются для получения конечного результата.

Ответ 2

Все решения Dimitre работают, и он прав, что вам не нужно использовать функции расширения, но иногда это облегчает жизнь. Это не слишком вредно, особенно когда вы используете расширения exslt, которые поддерживаются несколькими XSLT-процессорами. Кроме того, причина, по которой вы получаете ошибки последовательности, вероятно, связана с тем, что вы используете процессор XSLT 1.

Если вы хотите сохранить выбранное решение, вам понадобится использовать Saxon или другой XSLT-процессор, который поддерживает XSLT 2.

В противном случае, здесь альтернативный способ сделать это в XSLT 1. Это будет работать в большинстве XSLT-процессоров, и некоторым людям может быть проще найти, чем рекурсивная версия. Лично я предпочитаю рекурсивную версию (третье предложение Dimitre), потому что она более портативна.

<xsl:stylesheet version="1.0"
                xmlns:ex="http://exslt.org/common"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text"/>

  <xsl:template name="GetProducts">
    <xsl:param name="left"/>
    <xsl:param name="right"/>

    <xsl:for-each select="$left/text()">
      <product>
        <xsl:value-of select="number(.) * number($right[position()])"/>
      </product>
    </xsl:for-each>
  </xsl:template>

  <xsl:template match="/">
    <xsl:variable name="products">
      <xsl:call-template name="GetProducts">
        <xsl:with-param name="left" select="Parts/Part/Rate"/>
        <xsl:with-param name="right" select="Parts/Part/Quantity"/>
      </xsl:call-template>
    </xsl:variable>

    <xsl:value-of select="sum(ex:node-set($products)/product)"/>
  </xsl:template>
</xsl:stylesheet>