Reputation: 23
I am working on an XSLT report and need to find totals for components in a Bill of Materials. The input XML consists of Items and Links. For each Item, I need to get the quantity for each Link where it is used. To complicate matters, I need to go up the hierarchy and multiply the quantities by the parent quantities. The example only shows two levels, but it could be deeper than that. For example, Item assy1 uses 5 assy2 and 1 screw Item. Each Item assy2 uses 2 screws. So there would be a total of 5 assy2 and 11 screws (1 used by assy1 and each assy2 uses 2 (2X5)). I have figured out how to total the item quantities, but not how to multiply them up the hierarchy.
Here is the source XML:
<items>
<item id="93516">
<attrs><attr name="FILE_ID">assy1</attr></attrs>
</item>
<item id="93515">
<attrs><attr name="FILE_ID">assy2</attr></attrs>
</item>
<item id="93514">
<attrs><attr name="FILE_ID">screw</attr></attrs>
</item>
</items>
<links>
<link source="93516" destination="93514">
<attrs><attr name="QUANTITY">5</attr></attrs>
</link>
<link source="93516" destination="93515">
<attrs><attr name="QUANTITY">1</attr></attrs>
</link>
<link source="93515" destination="93514">
<attrs><attr name="QUANTITY">2</attr></attrs>
</link>
</links>
Here is some code I found and adapted to sum the quantities, but it doesn't multiply by the parent quantities:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<xsl:apply-templates select="/items/item"/>
</xsl:template>
<xsl:template match="item">
<xsl:value-of select="attrs/attr[@name='FILE_ID']"/> -
<xsl:variable name="item_id" select="@id"/>
<xsl:call-template name="bomQty">
<xsl:with-param name="itemLinks" select="/links/link[@destination=$item_id]"/>
</xsl:call-template> -
</xsl:template>
<xsl:template name="bomQty">
<xsl:param name="itemLinks"/>
<xsl:choose>
<xsl:when test="$itemLinks">
<xsl:variable name="recursive_result">
<xsl:call-template name="bomQty">
<xsl:with-param name="itemLinks" select="$itemLinks[position() > 1]"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="number($itemLinks[1]/attrs/attr[@name='QUANTITY']) + $recursive_result"/>
</xsl:when>
<xsl:otherwise><xsl:value-of select="0"/></xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Any help would be appreciated.
Upvotes: 2
Views: 267
Reputation: 243529
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kItemById" match="item" use="@id"/>
<xsl:key name="kUses" match="@destination"
use="../@source"/>
<xsl:key name="kLink" match="link"
use="concat(@source, '+', @destination)"/>
<xsl:key name="kelemByName" match="*/*" use="name()"/>
<xsl:template match="/">
Bill of Materials for the production of one <xsl:text/>
<xsl:text/> "<xsl:value-of select="/*/items/item[1]/*/*"/>":
<xsl:variable name="vrtfPass1">
<xsl:call-template name="generate">
<xsl:with-param name="pId"
select="/*/items/item[1]/@id"/>
</xsl:call-template>
</xsl:variable>
<xsl:apply-templates mode="pass2" select=
"ext:node-set($vrtfPass1)/*"/>
</xsl:template>
<xsl:template name="generate">
<xsl:param name="pId"/>
<xsl:param name="pQty" select="1"/>
<xsl:element name="{string(key('kItemById', $pId))}">
<xsl:attribute name="qty">
<xsl:value-of select="$pQty"/>
</xsl:attribute>
<xsl:for-each select="key('kUses', $pId)">
<xsl:call-template name="generate">
<xsl:with-param name="pId" select="."/>
<xsl:with-param name="pQty" select=
"key('kLink', concat($pId, '+', .))
/attrs/attr[@name='QUANTITY']"/>
</xsl:call-template>
</xsl:for-each>
</xsl:element>
</xsl:template>
<xsl:template match="/*" mode="pass2">
<xsl:for-each select=
"descendant::*
[generate-id()
=
generate-id(key('kelemByName', name())[1])
]">
<xsl:value-of select=
"concat(name(), ': ')"/>
<xsl:variable name="vrtfQuantities">
<xsl:for-each select="key('kelemByName', name())">
<xsl:call-template name="totalQuantity"/>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select=
"concat(sum(ext:node-set($vrtfQuantities)/total), '
')"/>
</xsl:for-each>
</xsl:template>
<xsl:template name="totalQuantity">
<xsl:param name="pNode" select="."/>
<xsl:choose>
<xsl:when test="not($pNode/parent::*)">
<total><xsl:value-of select="$pNode/@qty"/></total>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vAncTotal">
<xsl:call-template name="totalQuantity">
<xsl:with-param name="pNode" select="$pNode/.."/>
</xsl:call-template>
</xsl:variable>
<total><xsl:value-of select="$pNode/@qty * $vAncTotal"/></total>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<bm>
<items>
<item id="93516">
<attrs>
<attr name="FILE_ID">assy1</attr>
</attrs>
</item>
<item id="93514">
<attrs>
<attr name="FILE_ID">assy2</attr>
</attrs>
</item>
<item id="93515">
<attrs>
<attr name="FILE_ID">screw</attr>
</attrs>
</item>
</items>
<links>
<link source="93516" destination="93514">
<attrs>
<attr name="QUANTITY">5</attr>
</attrs>
</link>
<link source="93516" destination="93515">
<attrs>
<attr name="QUANTITY">1</attr>
</attrs>
</link>
<link source="93514" destination="93515">
<attrs>
<attr name="QUANTITY">2</attr>
</attrs>
</link>
</links>
</bm>
produces the wanted, correct result:
Bill of Materials for the production of one "assy1":
assy2: 5
screw: 11
Upvotes: 1