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

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