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>
--~--