Abel,
Thank you so much for the preamble and the formatted and documented
solution. I look forward to trying it out.
Simon
-----Original Message-----
From: Abel Braaksma (online) [mailto:abel(_dot_)online(_at_)xs4all(_dot_)nl]
Sent: August 29, 2007 8:31 AM
To: xsl-list(_at_)lists(_dot_)mulberrytech(_dot_)com
Subject: Re: [xsl] Calculating cumulative values - another call for help
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[(_at_)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[(_at_)x =
current()/@x]" />
<xsl:variable name="next-calc" select="$calc/for[(_at_)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[(_at_)x = current()],
$calc/for[(_at_)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
--~------------------------------------------------------------------
XSL-List info and archive: http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: <mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--
--~------------------------------------------------------------------
XSL-List info and archive: http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: <mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--