Subject: Re: [xsl] Calculating cumulative values - another call for help From: "Abel Braaksma (online)" <abel.online@xxxxxxxxx> Date: Wed, 29 Aug 2007 17:30:38 +0200 (CEST) |
Hi Simon, My response started out with a rather easy stylesheet, which was supposed to show an algorithm to do a treewalk and keep a record of the totals. The idea was simple, and the processing time would yield O(n). Unfortunately, I missed a couple of things on the first read and I had to restart. I hoped to do it with walking the following:: axis and keeping record with tunneling parameters, but unfortunately, a tunneled parameter is erased when it goes "out of scope" (i.e., when the next selected element, in this case a 'set' would come into focus, it does not hold the tunneled params of children). With tunneled parameters it would've been real easy (and I may have overlooked something, but I'm done for today ;) . Now I had to make an alternative and I chose Andrew's suggestion with a two-phase pass. I wanted to eliminate sum() completely, and to eliminate any look-back or look-ahead that needs to walk the whole tree. What I've come up with is far from perfect and it shows some bad programming style. I.e., I approached your problem partially imperatively, but i think for this particular case, it is a defensible approach. Anyway, 'nough talk, here's the thing (hope the mailer keeps the formatting a bit): <?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"> <xsl:output indent="yes" /> <xsl:template match="/"> <!-- mini pipeline: put points into a variable and process them --> <xsl:variable name="points"> <xsl:apply-templates select="root/set[1]/point[1]" mode="aggregate"> <xsl:with-param name="calc" tunnel="yes"> <for x="1" y2="2" y3="2"/> </xsl:with-param> </xsl:apply-templates> </xsl:variable> <!-- apply set with pre-processed points --> <xsl:apply-templates select="root/set"> <xsl:with-param name="points" select="$points" /> </xsl:apply-templates> </xsl:template> <!-- get calculated values --> <xsl:template match="point" use-when="1"> <xsl:copy> <xsl:copy-of select="@x | @y1 | @y2 | @y3" /> </xsl:copy> </xsl:template> <!-- apply the calculated points belonging to the current set --> <xsl:template match="set"> <xsl:param name="points" /> <xsl:copy> <xsl:copy-of select="@*" /> <xsl:apply-templates select="$points/point[@parent-id = generate-id(current())]" /> </xsl:copy> </xsl:template> <!-- calculation of the points (needs some cleaning) --> <xsl:template match="point" mode="aggregate"> <xsl:param name="calc" tunnel="yes" /> <!-- helper variables --> <xsl:variable name="next-point" select="following::point[1]" /> <xsl:variable name="next-x" select="$next-point/@x" /> <xsl:variable name="next-y" select="($next-point/@y1, 0)[1]" as="xs:integer" /> <!-- the $calc block contains the current calculation, index is based on the @x value (i.e., one calc block per @x value --> <xsl:variable name="current-calc" select="$calc/for[@x = current()/@x]" /> <xsl:variable name="next-calc" select="$calc/for[@x = $next-x]" /> <xsl:variable name="next-y3" select="xs:integer(($next-calc/@y3, 0)[1])" as="xs:integer" /> <!-- holds 1 in normal situation, 2 when a new x value arrives --> <xsl:variable name="new-x-value" select="number(@x = $next-x) + 1" /> <xsl:copy> <xsl:copy-of select="@*" /> <xsl:attribute name="y2" select="$current-calc/@y2" /> <xsl:attribute name="y3" select="$current-calc/@y3" /> <xsl:attribute name="parent-id" select="generate-id(parent::node())" /> <!-- copy is for debugging purposes, remove to prevent bloating --> <xsl:copy-of select="$calc" /> </xsl:copy> <xsl:variable name="var"> </xsl:variable> <xsl:apply-templates select="following::point[1]" mode="#current" > <xsl:with-param name="calc" tunnel="yes"> <xsl:variable name="new-for"> <xsl:variable name="source-y2" select="(0, $next-point/@y1)[$new-x-value]" as="xs:integer" /> <xsl:variable name="prev-y2" select="($next-point/@y1, $current-calc/@y2)[$new-x-value]" as="xs:integer" /> <xsl:variable name="sum-y3" select="$next-y3 + $next-y" /> <for x="{$next-point/@x}" y2="{$source-y2 + $prev-y2}" y3="{$sum-y3}" /> </xsl:variable> <!-- select the new one only for the current x --> <xsl:for-each select="distinct-values(($calc/for/@x, $next-x))"> <xsl:copy-of select=" ($new-for/for[@x = current()], $calc/for[@x = current()])[1]" /> </xsl:for-each> </xsl:with-param> </xsl:apply-templates> </xsl:template> </xsl:stylesheet> Hope it helps a bit. Have fun with it! Cheers, -- Abel Braaksma > Dear Experts, > > I've been aggregating numbers in XSLT 1.0 using the preceding-sibling:: axis > for nodes with the same parent. I now need to aggregate all the values that > precede the context node, even if they have different parents. For this I > added another attribute using the preceding:: axis (see XSLT 1.0 Stylesheet > below). Unfortunately, the transform is starting to groan under the weight > of all these O(n^2) operations. I have revisited an earlier solution > suggested by Dimitre Novatchev (see XSLT 2.0 Stylesheet below) that uses > FXSL. However, I'm not clear how I can adapt it to meet the new > requirements. Specifically, I would like to: > > a) generate attribute y3 that is a cumulative value based on all preceding > <point> elements > b) copy y1 from the input to the output
Current Thread |
---|
|
<- Previous | Index | Next -> |
---|---|---|
Re: [xsl] Calculating cumulative va, Andrew Welch | Thread | RE: [xsl] Calculating cumulative va, Simon Shutter |
Re: [xsl] Apply Templates, when to , John Smith | Date | Re: [xsl] xsl:choose and xsl:when, Abel Braaksma (onlin |
Month |