## With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

No credit card required

# Processing Nodes by Position

## Problem

You want to process nodes in a sequence that is a function of their position in a document or node set.

## Solution

Use `xsl:sort` with the select set to the `position( )` or `last( )` functions. The most trivial application of this example processes nodes in reverse document order:

```<xsl:apply-templates>
<xsl:sort select="position(  )" order="descending" data-type="number"/>
</xsl:apply-templates>```

or:

```<xsl:for-each select="*">
<xsl:sort select="position(  )" order="descending" data-type="number"/>
<!-- ... -->
</xsl:for-each>```

Another common version of this example traverses a node set as if it were a matrix of a specified number of columns. Here, you process all nodes in the first column, then the second, and then the third:

```<xsl:for-each select="*">
<xsl:sort select="(position(  ) - 1)  mod 3" />
<!-- ... -->
</xsl:for-each>```

Or, perhaps more cleanly with:

```<xsl:for-each select="*[position(  ) mod 3 = 1]">
<xsl:apply-templates
select=". | following-sibling::*[position(  ) &lt; 3]" />
</xsl:for-each>```

Sometimes you need to use `position( )` to separate the first node in a node set from the remaining nodes. Doing so lets you perform complex aggregation operations on a document using recursion. I call this example `recursive-aggregation`. The abstract form of this example follows:

```<xsl:template name="aggregation">
<xsl:param name="node-set"/>
<xsl:choose>
<xsl:when test="\$node-set">
<!--We compute some function of the first element that produces
a value that we want to aggregate. The function may depend on
the type of the element (i.e. it can be polymorphic)-->
<xsl:variable name="first">
<xsl:apply-templates select="\$node-set[1]" mode="calc"/>
</xsl:variable>
<!--We recursivly process the remaining nodes using position(  ) -->
<xsl:variable name="rest">
<xsl:call-template name="aggregation">
<xsl:with-param name="node-set"
select="\$node-set[position(  )!=1]"/>
</xsl:call-template>
</xsl:variable>
<!-- We perform some aggragation operation. This might not require
a call to a template. For example, this might be
\$first + \$rest           or
\$first * \$rest           or
concat(\$first,\$rest)      etc. -->
<xsl:call-template name="aggregate-func">
<xsl:with-param name="a" select="\$first"/>
<xsl:with-param name="b" select="\$rest"/>
</xsl:call-template>
</xsl:when>
<!-- Here `IDENTITY-VALUE` should be replaced with the identity
under the aggragate-func. For example, 0 is the identity
for addition, 1 is the identity for subtarction, "" is the
identity for concatentation, etc. -->
<xsl:otherwise>`IDENTITY-VALUE`</xsl:otherwise>
</xsl:template>```

## Discussion

XSLT’s natural tendency is to process nodes in document order. This is equivalent to saying that nodes are processed in order of their position. Thus, the following two XSLT fragments are equivalent (the sort is redundant):

```<xsl:for-each select="*">
<xsl:sort select="position(  )"/>
<!-- ... -->
</xsl:for-each>

<xsl:for-each select="*">
<!-- ... -->
</xsl:for-each>```

You can format our organizations chart into a two-column report using a variation of this idea, shown in Example 4-30 and Example 4-31.

Example 4-30. 2-columns-orgchat.xslt stylesheet

```<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output method="text" />
<xsl:strip-space elements="*"/>

<xsl:template match="employee[employee]">
<xsl:value-of select="@name"/>
<xsl:text>&#xA;</xsl:text>
<xsl:call-template name="dup">
<xsl:with-param name="input" select=" '-' "/>
<xsl:with-param name="count" select="80"/>
</xsl:call-template>
<xsl:text>&#xA;</xsl:text>
<xsl:for-each select="employee[(position(  ) - 1) mod 2 = 0]">
<xsl:value-of select="@name"/>
<xsl:call-template name="dup">
<xsl:with-param name="input" select=" ' ' "/>
<xsl:with-param name="count" select="40 - string-length(@name)"/>
</xsl:call-template>
<xsl:value-of select="following-sibling::*[1]/@name"/>
<xsl:text>&#xA;</xsl:text>
</xsl:for-each>
<xsl:text>&#xA;</xsl:text>
<xsl:apply-templates/>
</xsl:template>

<xsl:template name="dup">
<xsl:param name="input"/>
<xsl:param name="count" select="1"/>
<xsl:choose>
<xsl:when test="not(\$count) or not(\$input)"/>
<xsl:when test="\$count = 1">
<xsl:value-of select="\$input"/>
</xsl:when>
<xsl:otherwise>
<xsl:if test="\$count mod 2">
<xsl:value-of select="\$input"/>
</xsl:if>
<xsl:call-template name="dup">
<xsl:with-param name="input"
select="concat(\$input,\$input)"/>
<xsl:with-param name="count"
select="floor(\$count div 2)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

</xsl:stylesheet>```

Example 4-31. Output

```Jil Michel
------------------------------------------------------------
Nancy Pratt                   Jane Doe
Mike Rosenbaum

Nancy Pratt
------------------------------------------------------------
Phill McKraken                Ima Little

Ima Little
------------------------------------------------------------
Betsy Ross

Jane Doe
------------------------------------------------------------
Walter H. Potter              Wendy B.K. McDonald

Wendy B.K. McDonald
------------------------------------------------------------
Craig F. Frye                 Hardy Hamburg
Rich Shaker

Mike Rosenbaum
------------------------------------------------------------
Cindy Post-Kellog             Oscar A. Winner

Cindy Post-Kellog
------------------------------------------------------------
Allen Bran                    Frank N. Berry
Jack Apple

Oscar A. Winner
------------------------------------------------------------
Jack Nickolas                 Tom Hanks
Susan Sarandon

Jack Nickolas
------------------------------------------------------------
R.P. McMurphy

Tom Hanks
------------------------------------------------------------
Forest Gump                   Andrew Beckett

Susan Sarandon
------------------------------------------------------------
Helen Prejean```

One example of recursive-aggregation is a stylesheet that computes the total commission paid to salespeople whose commission is a function of their total sales over all products, shown in Example 4-32 and Example 4-33.

Example 4-32. Total-commission.xslt stylesheet

```<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>

<xsl:template match="salesBySalesperson">
<xsl:text>Total commision = </xsl:text>
<xsl:call-template name="total-commision">
<xsl:with-param name="salespeople" select="*"/>
</xsl:call-template>
</xsl:template>

<!-- By default salespeople get 2% commsison and no base salary -->
<xsl:template match="salesperson" mode="commision">
<xsl:value-of select="0.02 * sum(product/@totalSales)"/>
</xsl:template>

<!-- salespeople with seniority > 4 get \$10000.00 base + 0.5% commsison -->
<xsl:template match="salesperson[@seniority > 4]" mode="commision" priority="1">
<xsl:value-of select="10000.00 + 0.05 * sum(product/@totalSales)"/>
</xsl:template>

<!-- salespeople with seniority > 8 get (seniority * \$2000.00) base + 0.8% commsison -->
<xsl:template match="salesperson[@seniority > 8]" mode="commision" priority="2">
<xsl:value-of select="@seniority * 2000.00 + 0.08 *
sum(product/@totalSales)"/>
</xsl:template>

<xsl:template name="total-commision">
<xsl:param name="salespeople"/>
<xsl:choose>
<xsl:when test="\$salespeople">
<xsl:variable name="first">
<xsl:apply-templates select="\$salespeople[1]" mode="commision"/>
</xsl:variable>
<xsl:variable name="rest">
<xsl:call-template name="total-commision">
<xsl:with-param name="salespeople"
select="\$salespeople[position(  )!=1]"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="\$first + \$rest"/>
</xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:template>

</xsl:stylesheet>```

Example 4-33. Output

`Total commision = 471315`