XSLT для суммирования произведения двух атрибутов
У меня есть следующая структура источника XML:
<turnovers>
<turnover repid="1" amount="500" rate="0.1"/>
<turnover repid="5" amount="600" rate="0.5"/>
<turnover repid="4" amount="400" rate="0.2"/>
<turnover repid="1" amount="700" rate="0.05"/>
<turnover repid="2" amount="100" rate="0.15"/>
<turnover repid="1" amount="900" rate="0.25"/>
<turnover repid="2" amount="1000" rate="0.18"/>
<turnover repid="5" amount="200" rate="0.55"/>
<turnover repid="9" amount="700" rate="0.40"/>
</turnovers>
Мне нужен оператор xsl: value-of select, который вернет сумму произведения атрибута rate и атрибута amount для данного идентификатора rep. Поэтому для rep 5 мне нужно ((600 x 0.5) + (200 x 0.55)).
6 ответов
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:template match="/turnovers">
<val>
<!-- call the sum function (with the relevant nodes) -->
<xsl:call-template name="sum">
<xsl:with-param name="nodes" select="turnover[@repid='5']" />
</xsl:call-template>
</val>
</xsl:template>
<xsl:template name="sum">
<xsl:param name="nodes" />
<xsl:param name="sum" select="0" />
<xsl:variable name="curr" select="$nodes[1]" />
<!-- if we have a node, calculate & recurse -->
<xsl:if test="$curr">
<xsl:variable name="runningsum" select="
$sum + $curr/@amount * $curr/@rate
" />
<xsl:call-template name="sum">
<xsl:with-param name="nodes" select="$nodes[position() > 1]" />
<xsl:with-param name="sum" select="$runningsum" />
</xsl:call-template>
</xsl:if>
<!-- if we don't have a node (last recursive step), return sum -->
<xsl:if test="not($curr)">
<xsl:value-of select="$sum" />
</xsl:if>
</xsl:template>
</xsl:stylesheet>
выдает:
<val>410</val>
два <xsl:if>
s можно заменить одним <xsl:choose>
. Это означало бы одну проверку меньше во время рекурсии, но это также означает две дополнительные строки кода.
в обычном XSLT 1.0 вам нужен рекурсивный шаблон для этого, например:
<xsl:template match="turnovers">
<xsl:variable name="selectedId" select="5" />
<xsl:call-template name="sum_turnover">
<xsl:with-param name="turnovers" select="turnover[@repid=$selectedId]" />
</xsl:call-template>
</xsl:template>
<xsl:template name="sum_turnover">
<xsl:param name="total" select="0" />
<xsl:param name="turnovers" />
<xsl:variable name="head" select="$turnovers[1]" />
<xsl:variable name="tail" select="$turnovers[position()>1]" />
<xsl:variable name="calc" select="$head/@amount * $head/@rate" />
<xsl:choose>
<xsl:when test="not($tail)">
<xsl:value-of select="$total + $calc" />
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="sum_turnover">
<xsl:with-param name="total" select="$total + $calc" />
<xsl:with-param name="turnovers" select="$tail" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Это должно сделать трюк, вам нужно будет сделать некоторую дополнительную работу, чтобы выбрать distinct repid
' s
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:variable name="totals">
<product>
<xsl:for-each select="turnovers/turnover">
<repid repid="{@repid}">
<value><xsl:value-of select="@amount * @rate"/></value>
</repid>
</xsl:for-each>
</product>
</xsl:variable>
<totals>
<total repid="5" value="{sum($totals/product/repid[@repid='5']/value)}"/>
</totals>
</xsl:template>
</xsl:stylesheet>
в XSLT 1.0 использование FXSL делает такие проблемы легко решить:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
xmlns:ext="http://exslt.org/common"
exclude-result-prefixes="xsl f ext"
>
<xsl:import href="zipWith.xsl"/>
<xsl:output method="text"/>
<xsl:variable name="vMultFun" select="document('')/*/f:mult-func[1]"/>
<xsl:template match="/">
<xsl:call-template name="profitForId"/>
</xsl:template>
<xsl:template name="profitForId">
<xsl:param name="pId" select="1"/>
<xsl:variable name="vrtfProducts">
<xsl:call-template name="zipWith">
<xsl:with-param name="pFun" select="$vMultFun"/>
<xsl:with-param name="pList1" select="/*/*[@repid = $pId]/@amount"/>
<xsl:with-param name="pList2" select="/*/*[@repid = $pId]/@rate"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="sum(ext:node-set($vrtfProducts)/*)"/>
</xsl:template>
<f:mult-func/>
<xsl:template match="f:mult-func" mode="f:FXSL">
<xsl:param name="pArg1"/>
<xsl:param name="pArg2"/>
<xsl:value-of select="$pArg1 * $pArg2"/>
</xsl:template>
</xsl:stylesheet>
когда это преобразование применяется к первоначально разнесенному исходному XML-документу, получается правильный результат:
310
в XSLT 2.0 то же решение, используя FXSL 2.0 может быть выражено XPath однострочным:
sum(f:zipWith(f:multiply(),
/*/*[xs:decimal(@repid) eq 1]/@amount/xs:decimal(.),
/*/*[xs:decimal(@repid) eq 1]/@rate/xs:decimal(.)
)
)
весь трансформация:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:f="http://fxsl.sf.net/"
exclude-result-prefixes="f xs"
>
<xsl:import href="../f/func-zipWithDVC.xsl"/>
<xsl:import href="../f/func-Operators.xsl"/>
<!-- To be applied on testFunc-zipWith4.xml -->
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:value-of select=
"sum(f:zipWith(f:multiply(),
/*/*[xs:decimal(@repid) eq 1]/@amount/xs:decimal(.),
/*/*[xs:decimal(@repid) eq 1]/@rate/xs:decimal(.)
)
)
"/>
</xsl:template>
</xsl:stylesheet>
опять же, это преобразование дает правильный ответ:
310
обратите внимание на следующее:
на
f:zipWith()
функция принимает в качестве аргументов функцииfun()
(из двух аргументов) и два списка элементов, имеющих одинаковую длину. Он создает новый список той же длины, элементы которого являются результатом пары применениеfun()
соответствующегоk
- й пункты двух списков.f:zipWith()
как в выражении принимает функциюf:multiply()
и две последовательности, соответствующей "ammount
" и "rate
" атрибутами. В sesult-это последовательность, каждый элемент которой равен произведению соответствующего "ammount
" и "rate
".наконец, sum этого производится последовательность.
нет необходимости писать явную рекурсию, и также гарантируется, что закулисная рекурсия, используемая в
f:zipWith()
никогда не будет сбой (для всех практических случаев) с "переполнением стека"
<?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"
exclude-result-prefixes="xs"
version="2.0">
<xsl:variable name="repid" select="5" />
<xsl:template match="/">
<xsl:value-of select=
"sum(for $x in /turnovers/turnover[@repid=$repid] return $x/@amount * $x/@rate)"/>
</xsl:template>
</xsl:stylesheet>
вы можете сделать это, если вам просто нужно значение, а не xml.
самый простой способ сделать это в XSLT, вероятно, использовать привязки языка программирования, чтобы вы могли определить свои собственные функции XPath.