XSLT 2.0 and XPath 2.0 Programmer's Reference, 4th Edition (170 page)

BOOK: XSLT 2.0 and XPath 2.0 Programmer's Reference, 4th Edition
2.51Mb size Format: txt, pdf, ePub

Sorting the Groups

If there are any

elements as children of the

, these affect the order in which the groups are processed. They do not affect the order of the items within each group, nor which item in a group is considered to be the initial item.

The
select
expression in an

element calculates a sort key that affects the group as a whole, but it is always evaluated with respect to the initial item in the group. The initial item in a group is the item within the group that was first in population order. The
select
expression is evaluated with this item as the context item, with the position of this item relative to the initial items of other groups as the context position, and with the number of groups as the context size.

If any of the attributes of the

elements are attribute value templates, then the XPath expressions in these attribute value templates are evaluated with the same context item, position, and size as the
select
expression of the containing

element.

If there are no

elements, or in cases where the initial items in two groups have the same values for their sort keys, the groups are processed in order of first appearance; that is, if the initial item of group G appeared in the population before the initial item of group H, then group G is processed before group H.

“Processed before” does not refer to the actual order of execution; the system can process the groups in any order, or in parallel. What it means is that the results of processing group G appear in the final result sequence ahead of the results of processing group H.

In practice, if the groups are sorted at all then they are nearly always sorted by the value of the grouping key:
group the addresses in each city, sorting the groups by city
. This can be conveniently coded as:


    

    

       

    


The functions
current-group()
and
current-grouping-key()
are described on page 739 in Chapter 13.

An

element within

is used to sort the groups. To sort the items within each group, use an

element within the inner

, or write:


The

instruction is described on page 437.

You can also use the

instruction to sort the population before grouping starts.

Usage and Examples

The following sections give a number of examples of how

can be used to solve grouping problems. They are organized according to the four ways of defining the grouping criteria:
group-by
,
group-adjacent
,
group-starting-with
, and
group-ending-with
.

Using group-by

This is by far the most common kind of grouping. We'll start with a simple case.

Example: Single-Level Grouping by Value

This example groups a set of employees according to the department in which they work.

Source

We'll start with the following simple data file (
staff.xml
):


  

  

  

  

  


Output

The requirement is to output an HTML document in which the staff are listed by department:

sales department

John Jones


Maria Gomez


personnel department

Barbara Jenkins


Wesley Thomas


transport department

Cormac O'Donovan


Stylesheet

This output is simple to achieve. The full stylesheet is in
group-by-dept.xsl
:


  

    

            department

    

      


    

  


A number of variations are possible on this theme. To sort the deparments, use an

element within the

. To sort the employees within each department, use an

element within the

. The solution then becomes (
sorted-depts.xsl
):

  


  

    

            department

    

      

      


    

  


This general design pattern, where

is used at the outer level to iterate over the groups, and an inner

is used to iterate over the items within a group, is typical. But there are a number of useful variations:

  • The inner loop is sometimes better done using

    , especially if the group includes elements of different types.
  • Sometimes the entire inner loop can be written as

    , especially when generating XML output.
  • Sometimes the requirement is not to display the items in each group, but to calculate some aggregate function for these items: for example to list for each department, the name of the department, the number of employees, and the maximum salary. In this case, the inner loop might not be explicit. The number of employees in the group is easily computed as
    count(current-group())
    . Our sample data doesn't show salary, but if this was available as an extra attribute on the

    element, then you could easily calculate the maximum salary as
    max(current-group()/@salary)
    .
  • If the requirement is to eliminate duplicates rather than to group all the items (in our example, to output a list of departments), then the inner loop can be omitted entirely. But in this case it may be simpler to use the
    distinct-values()
    function described on page 749.
  • If you need to number the groups, or test whether you are processing the last group, then within the

    element you can use
    position()
    and
    last()
    in the usual way. At this level, the context item is the initial item of the group being processed, and
    position()
    and
    last()
    refer to the position of this item in a list that contains the initial item of each group, in processing order.

The example above was expressed as a grouping problem (“list the employees grouped by department”), so it is easy to see that

can be used in the solution. Sometimes grouping problems are not so easy to recognize. This might be the case if the example above were expressed as “for each department, list the name of the department and the number of employees.”

Example: Multilevel Grouping by Value

Sometimes there is a need to do multilevel grouping. For example, you might want to group the employees by department, and the departments by location. Assume that the

element now has a location attribute as well as a department attribute. It doesn't really matter whether departments can span locations, the code will work either way.

Source

The data is now like this (
staff-locations.xml
):


  

            department=“sales”

            location=“New York”/>

  

            department=“personnel”

            location=“Los Angeles”/>

  

            department=“transport”

            location=“New York”/>

  

            department=“personnel”

            location=“Los Angeles”/>

  

            department=“sales”

            location=“Seattle”/>


Output

You might want the output presented like this:

Location: Los Angeles

    Department: Personnel

        Barbara Jenkins

        Wesley Thomas

Location: New York

    Department: Sales

        John Jones

    Department: Transport

        Cormac O'Donovan

Location: Seattle

    Department: Sales

        Maria Gomez

Stylesheet

Assume that the indentation is achieved using CSS styles, so you can concentrate on getting the structure of the information right. To do this multilevel grouping, just use two levels of

elements (
multi-level.xsl
):


  

    

    

       Location:

       

    


    

      

      

         Department:

         

      


      

        

        

           Location 

           

        


      

    

  


A similar requirement is where there is a composite grouping key (“group employees that have the same department and the same location”). There are two ways of handling this. You can either treat it as a single level of grouping, using the concatenation of the two values as the grouping key, or you can treat it as two nested groupings in which the outer level does nothing (
composite.xsl
):


  

    .  .  .

  


The two techniques are not completely identical. For example, with a single-level grouping using a concatenated key, the value of
position()
while processing a department will run continuously from 1 up to the total number of groups, but with a two-level grouping,
position()
will start again at 1 for each location.

Other books

Special Delivery by Ann M. Martin
The Five Gold Bands by Jack Vance
Parallel Fire by Deidre Knight
The Donut Diaries by Dermot Milligan
The Rings of Haven by Brown, Ryk
Bad Friends by Claire Seeber