How do you summarize the result of a call pattern in XSLT?
I have XML like this:
<risk>
<driver driverId="2">
<vehicleUse>M</vehicleUse>
</driver>
<driver driverId="3">
<vehicleUse>F</vehicleUse>
</driver>
<driver driverId="4">
<vehicleUse>I</vehicleUse>
</driver>
</risk>
I am using XSLT (v1.0 implementation, .NET) to translate each car into a number and then get the total of those numbers. Vehicles translate as M = 3, F = 2 and i = 1. An additional complication is that for driver ID 3, these values are multiplied by 10, and for driver 4, by 100. Thus, in the above example, the total the number will be 3 + 20 + 100 = 123.
I have defined a template in my XSLT file like this:
<xsl:template name="getVehicleUseScore">
<xsl:param name="driverId" />
<xsl:param name="vehicleUse" />
<!-- Implementation left out for brevity -->
</xsl:template>
The rest of the XSLT file calls the template;
<xsl:template match="risk">
<vehicleUseScore>
<xsl:for-each select="driver">
<xsl:call-template name="getVehicleUseScore">
<xsl:with-param name="driverId" select="@driverId" />
<xsl:with-param name="vehicleUse" select="vehicleUse" />
</xsl:call-template>
</xsl:for-each>
</vehicleUseScore>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
As a result, I get the text "320100" which concatenates only 3, 20 and 100, which at least proves that the getVehicleUseScore pattern works.
I would like to pass the results of getVehicleUseScore to the sum () function, but I don’t know how. I tried the following:
<xsl:value-of select="sum(getVehicleUseScore(@driverId, vehicleUse))" />
But the XSLT compiler states that "getVehicleUseScore () is an unknown XSLT function".
Is there a way to do this?
source to share
Below is a short XSLT 1.0 way :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="risk">
<vehicleUseScore>
<xsl:variable name="vrtfResult">
<xsl:for-each select="driver">
<xsl:call-template name="getVehicleUseScore">
<xsl:with-param name="pdriverId" select="@driverId" />
<xsl:with-param name="pvehicleUse" select="vehicleUse" />
</xsl:call-template>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="sum(msxsl:node-set($vrtfResult)/*)"/>
</vehicleUseScore>
</xsl:template>
<xsl:template name="getVehicleUseScore">
<xsl:param name="pdriverId" />
<xsl:param name="pvehicleUse" />
<score>
<xsl:variable name="vValue" select=
"(($pvehicleUse='M')*3 + ($pvehicleUse='F')*2 + ($pvehicleUse='I')*1)"/>
<xsl:variable name="vFactor" select=
"1 +(9*($pdriverId=3)) + (99*($pdriverId=4))"/>
<xsl:value-of select="$vValue*$vFactor"/>
</score>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied to the provided XML document:
<risk>
<driver driverId="2">
<vehicleUse>M</vehicleUse>
</driver>
<driver driverId="3">
<vehicleUse>F</vehicleUse>
</driver>
<driver driverId="4">
<vehicleUse>I</vehicleUse>
</driver>
</risk>
produces the desired, correct result:
<vehicleUseScore>123</vehicleUseScore>
Explanation
When capturing template output in a variable, this variable is of type RTF (Result Tree Fragment), and if the content does not contain a node (other than the text node), its content cannot be moved by the XPath expression.
To do this, you need to call this vendor-specific variable xxx:node-set()
to convert the RTF to a regular tree.
Here we also use the fact that whenever a boolean expression occurs in an arithmetic expression, it is converted to a number and by definition:
number(true()) = 1
and
number(false()) = 0
source to share
Another approach, which is slightly longer, but avoids using a function node-set()
(I like to avoid proprietary functions whenever possible, although it node-set()
is only semi-panned), is to use recursion:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="risk">
<vehicleUseScore>
<xsl:call-template name="sumDriverScores">
<xsl:with-param name="drivers" select="driver" />
</xsl:call-template>
</vehicleUseScore>
</xsl:template>
<xsl:template name="sumDriverScores">
<xsl:param name="drivers" />
<xsl:if test="not($drivers)">
<xsl:text>0</xsl:text>
</xsl:if>
<xsl:if test="$drivers">
<xsl:variable name="currentValue">
<xsl:call-template name="getVehicleUseScore">
<xsl:with-param name="driverId" select="$drivers[1]/@driverId" />
<xsl:with-param name="vehicleUse" select="$drivers[1]/vehicleUse" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="sumRemainder">
<xsl:call-template name="sumDriverScores">
<xsl:with-param name="drivers" select="$drivers[position() > 1]" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$currentValue + $sumRemainder"/>
</xsl:if>
</xsl:template>
<xsl:template name="getVehicleUseScore">
<xsl:param name="driverId" />
<xsl:param name="vehicleUse" />
<xsl:variable name="useFactor" select="translate($vehicleUse, 'MFI', '321')"/>
<xsl:variable name="idFactor"
select="1 + 9 * ($driverId = 3) + 99 * ($driverId = 4)"/>
<xsl:value-of select="$useFactor * $idFactor"/>
</xsl:template>
</xsl:stylesheet>
Output:
<vehicleUseScore>123</vehicleUseScore>
source to share