tags:

views:

281

answers:

2

I have next xml:

<page>
   <document>
      <id>1001</id>
      <cur>USD</cur>
      <date>01.01.2009</date>
      <amount>10</amount>
   </document>
   <document>
      <id>1001</id>
      <cur>USD</cur>
      <date>02.01.2009</date>
      <amount>15</amount>
   </document>
   <document>
      <id>1001</id>
      <cur>JPY</cur>
      <date>01.01.2009</date>
      <amount>5</amount>
   </document>
   <document>
      <id>1002</id>
      <cur>USD</cur>
      <date>01.01.2009</date>
      <amount>5</amount>
   </document>
   ...
</page>

And I need to transform it into html. Records should be grouped by id and cur. And after each group total amount should be shown. So we want something like this:

Bill: id=1001, cur=USD
      date=01.01.2009   amount=10
      date=02.01.2009   amount=15
      total amount=25
Bill: id=1001, cur=JPY
      date=01.01.2009   amount=5
      total amount=5
Bill: id=1002, cur=USD
      date=01.01.2009   amount=5
      total amount=5
...

How can I achieve this using XSL?

When I tried to find answer in google I found Muenchian method, but it's too complicated when we want to group result by 2 fields. I'm beginner in xsl and it's a bit difficult to me. I also found xslt 2.0 operator for-each-group. Is it supported by major browsers? Is it normally to use it or we should only rely on xslt 1.0?

+1  A: 

You can do this with XSLT 1.0

The method i use here is to create a composite key with the two fields, id and cur. I later apply the templates to the first document in each group. Within the template i then loop through the individual documents and finally i summarize the documents amount field.

<?xml version='1.0'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;

<xsl:key name="idcur" match="document" use="concat(id,cur)"/>

<xsl:template match="/page">
    <xsl:apply-templates select="document[generate-id() = generate-id(key('idcur',concat(id,cur))[1])]"/>
</xsl:template>

<xsl:template match="document">
<xsl:variable name="document" select="key('idcur',concat(id,cur))"/>
Bill: id=<xsl:value-of select="id"/>, cur=<xsl:value-of select="cur"/>
    <xsl:for-each select="$document">
      date=<xsl:value-of select="date"/>   amount=<xsl:value-of select="amount"/>
    </xsl:for-each>
      total amount=<xsl:value-of select="sum($document/amount)"/>
</xsl:template>
</xsl:stylesheet>

Output:

Bill: id=1001, cur=USD
      date=01.01.2009   amount=10
      date=02.01.2009   amount=15
      total amount=25
Bill: id=1001, cur=JPY
      date=01.01.2009   amount=5
      total amount=5
Bill: id=1002, cur=USD
      date=01.01.2009   amount=5
      total amount=5
Peter Lindqvist
For the sake of code legibility, I recommend using `<xsl:text>` over literal strings in the XSL. This way, output format is much more easily controlled, and code format can have correct indents.
Tomalak
Valid point! But I feel that this is not the final solution anyway.
Peter Lindqvist
Thanks a lot! It's not only nice solution but also a useful example for all the theory I've read before about xsl.
Roman
No problem, i find myself struggling with these types of problem a lot, glad i could help. But be sure to check the proposed solution from erlock!
Peter Lindqvist
A: 

Concatening two fields to create a composite key is not safe, unless you are absolutely sure all such created keys are uniques. This method is safer, I think:

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

<xsl:output method="text"/>

<xsl:key name="ids" match="document" use="id"/>
<xsl:key name="currencies" match="document" use="cur"/>

<xsl:template match="/page">
    <xsl:apply-templates select="document[generate-id() = generate-id(key('ids',id)[1])]"/>
</xsl:template>

<xsl:template match="document">
  <xsl:for-each select="key('ids',id)[generate-id() = generate-id(key('currencies', cur)[id=current()/id][1])]">
    <xsl:variable name="bills" select="key('ids', id)[cur = current()/cur]"/>
    <xsl:text>Bill: id=</xsl:text>
    <xsl:value-of select="id"/>
    <xsl:text>, cur=</xsl:text>
    <xsl:value-of select="cur"/>
    <xsl:for-each select="$bills">
      <xsl:text>&#10;date=</xsl:text>
      <xsl:value-of select="date"/>
      <xsl:text>   amount=</xsl:text>
      <xsl:value-of select="amount"/>
    </xsl:for-each>
    <xsl:text>&#10;total amount=</xsl:text>
    <xsl:value-of select="sum($bills/amount)"/>
    <xsl:text>&#10;</xsl:text>
  </xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Erlock