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

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

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

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

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

10
задан Dimitre Novatchev 30 June 2019 в 20:32
поделиться

2 ответа

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

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

38
ответ дан 3 December 2019 в 13:56
поделиться

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

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

Иначе вот альтернативный метод выполнения его в XSLT 1. Это будет работать в большинстве процессоров XSLT, и некоторый peope мог бы найти это легче к grok, чем рекурсивная версия. Лично, я предпочитаю рекурсивную версию (3-е предложение 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>
1
ответ дан 3 December 2019 в 13:56
поделиться
Другие вопросы по тегам:

Похожие вопросы: