RE: [xsl] Calculating cumulative values - another call for help

Subject: RE: [xsl] Calculating cumulative values - another call for help
From: "Simon Shutter" <simon@xxxxxxxxxxx>
Date: Wed, 29 Aug 2007 08:45:28 -0700
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.online@xxxxxxxxx] 
Sent: August 29, 2007 8:31 AM
To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
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[@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