Умножьте 2 числа и затем сложите - PullRequest
10 голосов
/ 13 января 2009

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

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

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

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

Ответы [ 2 ]

37 голосов
/ 13 января 2009

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

Solution1 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 допускается, что правильный аргумент последнего "/ Оператор может быть выражением или вообще функцией. Это выражение / функция применяется для каждого из выбранных узлов, выступающих в качестве узла контекста, и каждое приложение-функция выдает один результат.

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.

Solution3 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. Затем результаты суммируются для получения окончательного результата.

1 голос
/ 15 января 2009

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

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

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

<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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...