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, который будет возвращать сумму произведения атрибута показателя и атрибута количества для данного идентификатора представителя. Поэтому для повторения 5 мне нужно ((600 х 0,5) + (200 х 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>
можно заменить одним <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>
В 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
"Атрибуты. Результатом является последовательность, каждый элемент которой является произведением соответствующего"ammount
" а также "rate
".Наконец, сумма этой последовательности производится.
Нет необходимости писать явную рекурсию, а также гарантируется, что закулисная рекурсия используется внутри
f:zipWith()
никогда не падет (для всех практических случаев) с "переполнением стека"
Это должно сработать, вам нужно будет проделать дополнительную работу, чтобы выбрать 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>
<?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.