BizTalk Utilities CV ,   Jobs ,   Code library
 
Home Page


Add/Edit your code items
Search the code library
Browse for the code library


Uncategorized
Parsing XML tag elements with VB string functions
Padding with spaces to a fixed-length string
Re: Swapping values in two integer variables without using a temporary variable
Swapping values in two integer variables without using a temporary variable
Comparing Multiple fields from the subquery
Re: Filtering unique values
Re: Grouping with totals/subtotals -- nested Muenchian method
Grouping with totals/subtotals -- nested Muenchian method
WebCube Report - Browser-based Multidimensional Data Reporting
Re: Missing constant definition
swapping two integer values without using temporary
Debugging script
What is BEEP (BXXP)?
Meta-Patterns: Design Patterns Explained
XML Helper functions in Visual Basic
nodeset of child templates using document()
Converting INI files to XML
Fullxml 2
Sorting the results and use the Axis
TreeMaker


 
 

<< System.XMLWCF, WS, SOAP >>


By Dimitre Novatchev
First Posted 03/05/2001
Times viewed 2258

Positional grouping by number of items


Summary Discussed is a way to group adjacent elements that fulfill certain criteria. There are two main different types of criteria -- by number of adjacent elements (usually constant) in each group, or by the type of adjacent elements allowed in each group. The first type of positional grouping -- by number -- is discussed here.

Another grouping FAQ is about the ways to group adjacent elements that fulfil certain criteria. There are two main different types of criteria -- by number of adjacent elements (usually some constant) in each group, or by the type of adjacent elements allowed in each group.   

Now I'll discuss the first type of positional grouping -- by number.   

Here's a recent posting from the microsoft.public.xsl:   Given XML in the format;
<rs>
  <
z date=2000-09-03
eggs=1 bacon=4/>
  <
z date=2000-09-04 eggs=2 bacon=3/>
  <
z date=2000-09-05 eggs=3 bacon=2/>
  <
z date=2000-09-06 eggs=4 bacon=1/>
  <
z date=2000-09-07 eggs=5 bacon=0/>
</
rs>

Where the number of days could continue to to x...

Is it possible to transfer to HTML tables (below) with 4 columns - Breakfast type, then 3 day columns - repeated until the end of the node set is reached?  I'm guessing I'd need to use the mod of the position()?

<TABLE>
    <TR>
        <TD>
        Breakfast Type
        </TD>
        <TD>
        2000-09-03
        </TD>
        <TD>
        2000-09-04
        </TD>
        <TD>
        2000-09-05
        </TD>
        <TD>
   </TR>
    <TR>
        <TD>
        eggs
        </TD>
        <TD>
        1
        </TD>
        <TD>
        2
        </TD>
        <TD>
        3
        </TD>
    </TR>
    <TR>
        <TD>
        bacon
       </TD>
        <TD>
        4
      </TD>
        <TD>
        3
        </TD>
        <TD>
        2
      </TD>
    </TR>
</TABLE>
<TABLE>
    <TR>
        <TD>
        Breakfast Type
        </TD>
        <TD>
        2000-09-06
       </TD>
        <TD>
        2000-09-07
       </TD>
   </TR>
    <TR>
        <TD>
        eggs
        </TD>
        <TD>
        4
       </TD>
        <TD>
        5
       </TD>
    </TR>
    <TR>
        <TD>
        bacon
       </TD>
        <TD>
        1
     </TD>
        <TD>
        0
       </TD>
    </TR>
</TABLE>

Let's analyse this problem. The result must contain every group of three adjacent z records, formatted in a separate <table>. We rely on the fact that every z element's date is unique and that the z elements in the source XML document are sorted by ascending date. Were this not true, then we'd have to use a combination of the Muenchian method plus simple date sorting (just use two other snippets :o)) ) as a preparatory step.

So, we must be able to identify the first z element of each group of three adjacent elements. Here's when the position() function and the mod operator come handy -- an expression like the following is just what we need:
z[position() mod 3 = 1]

This will be the match expression of a template we'll use for generating each group. What must this template do?

Yes, it will generate a table element. But do note -- an element, not just a start tag!  This has been a typical problem for many people -- in case you specify just the starting tag -- in another template, which generates the last element of the group I'll output the ending tag..., you'll be unpleasantly surprised to find out that this is not well - formed XML (the current xsl:template element and the table element are not properly nested, but do overlap).

Now that we've made this important observation, the code of the template becomes much more obvious:
<table>
   <
xsl:apply-templates select=
$restOfGroup/>
<table>

However, we have a requirement for a special header, so we'll have a separate template for the header row. Also, every row will display a different type of breakfast, so we're going to pass this as a parameter.   How do we know which nodes constitute exactly the elements of a group? Expressing this from the first element of a group (, which we already have located) is easy:  
.|following::z[position() &lt; 3]

We now can write the first template:
<xsl:template match=z[position() mod 3 = 1]>
  <
xsl:variable name=thisGroup
 
select=.|following::z[position() &lt; 3] />
   <
table>
    <
tr>
      <
xsl:apply-templates select=$thisGroup mode=header />
    </
tr>
   <
xsl:for-each select=@*[name() != 'date']>
      <
tr>
        <
xsl:apply-templates select=$thisGroup mode=row>
          <
xsl:with-param name=breakfType select=name() />
        </
xsl:apply-templates>
      </
tr>
    </
xsl:for-each>
  </
table>
</
xsl:template>

An important thing to note: we use the mode attribute to apply these templates. This makes the code cleaner and also ensures that exactly the intended templates will be selected and they will not compete (and be ignored by chance) for selection with other templates.   The other two templates are straightforward:

<xsl:template match=z mode=header>
  <
xsl:if test=position()=1>
    <
td>Breakfast Type</td>
  </
xsl:if>

  <
td>
    <
xsl:value-of select=@date />
  </
td>
</
xsl:template>

<
xsl:template match=z mode=row>
  <
xsl:param name=breakfType />

  <
xsl:if test=position()=1>
    <
td>
      <
xsl:value-of select=$breakfType />
    </
td>
  </
xsl:if>

  <
td>
    <
xsl:value-of select=@*[name()=$breakfType] />
  </
td>
</
xsl:template>

Now, together with the template that matches the root node / we have the complete stylesheet:

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

  <
xsl:template match=/>
    <
xsl:apply-templates select=/rs/z />
  </
xsl:template>

  <
xsl:template match=z[position() mod 3 = 1]>
    <
xsl:variable name=thisGroup
   
select=.|following::z[position() &lt; 3] />

    <
table>
      <
tr>
        <
xsl:apply-templates select=$thisGroup mode=header />
      </
tr>

      <
xsl:for-each select=@*[name() != 'date']>
        <
tr>
          <
xsl:apply-templates select=$thisGroup mode=row>
            <
xsl:with-param name=breakfType select=name() />
          </
xsl:apply-templates>
        </
tr>
      </
xsl:for-each>
    </
table>
  </
xsl:template>

  <
xsl:template match=z mode=header>
    <
xsl:if test=position()=1>
      <
td>Breakfast Type</td>
    </
xsl:if>

    <
td>
      <
xsl:value-of select=@date />
    </
td>
  </
xsl:template>

  <
xsl:template match=z mode=row>
    <
xsl:param name=breakfType />

    <
xsl:if test=position()=1>
      <
td>
        <
xsl:value-of select=$breakfType />
      </
td>
    </
xsl:if>

    <
td>
      <
xsl:value-of select=@*[name()=$breakfType] />
    </
td>
  </
xsl:template>
</
xsl:stylesheet>

And the result (as displayed by the browser) is:

Breakfast Type

2000-09-03

2000-09-04

2000-09-05

eggs 1 2 3
bacon 4 3 2
Breakfast Type 2000-09-06 2000-09-07
eggs 4 5
bacon 1 0

Additional information

Further additional information


Rate this article on a scale of 1 to 10 (2 votes, average 5)

Your vote :  

<< System.XMLWCF, WS, SOAP >>





Leave a comment for this article
Your name
Your email (optional)
Your comment
Optional: Upload an attachment
Enter the code shown:

 
 

    Email TopXML