Solved

Attribute based grouping with XSL

Posted on 2004-04-02
4
301 Views
Last Modified: 2013-11-19
Hi All,

I'm facing a prob lem and can't figure it out. The XML and wanted result is shown below

My XML:
<Results>
      <date/>
      <GROUP Name="G1">
            <ELEMENT name="A">
                  <Value depth="10">5ppm</Value>
                  <Value depth="20">15ppm</Value>
                  <Value depth="30">25ppm</Value>
            </ELEMENT>
            <ELEMENT name="B">
                  <Value depth="10">5ppm</Value>
                  <Value depth="20">15ppm</Value>
                  <Value depth="30">25ppm</Value>
            </ELEMENT>
      </GROUP>
      <GROUP Name="G2">
            <ELEMENT name="C">
                  <Value depth="11">5ppm</Value>
                  <Value depth="21">15ppm</Value>
                  <Value depth="31">25ppm</Value>
            </ELEMENT>
            <ELEMENT name="D">
                  <Value depth="11">5ppm</Value>
                  <Value depth="21">15ppm</Value>
                  <Value depth="31">25ppm</Value>
            </ELEMENT>
      </GROUP>
</Results>

Wanted result:

Table:
Group G1
              A           B
Depth
10          5            5
20          15         15
30          25         25

Group G2
              C           D
Depth
11          5            5
21          15         15
31          25         25

....
0
Comment
Question by:krisnackaerts
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 2
4 Comments
 
LVL 7

Expert Comment

by:culshaja
ID: 10740151
Hi,

I got this from the XSL FAQ on http://www.dpawson.co.uk:

Here's a stylesheet that produces the cross-product you're looking for. It uses an <xsl:key> like functional indexes to speed up the iteration of unique regions.

Assuming the input is:

<demo>
  <salesman id="0001" name="Rick Peterson">
    <account id="act001" region="Midwest">Johnosn's Laundry</account>
    <account id="act002" region="Canada">Franks's Laundry</account>
    <account id="act003" region="Alaska">Mary's Laundry</account>
    <account id="act004" region="New Jersey">Bill's Laundry</account>
    <account id="act005" region="Midwest">Hammond's Laundry</account>
  </salesman>
  <salesman id="0003" name="Ty Coon">
    <account id="act006" region="Canada">Franks's Diner</account>
    <account id="act007" region="Midwest">Johnosn's Diner</account>
    <account id="act008" region="Canada">Hammond's Diner</account>
    <account id="act009" region="Alaska">Mary's Diner</account>
    <account id="act010" region="Alaska">Bill's Diner</account>
  </salesman>
</demo>

The following stylesheet produces an HTML table crosstab with salespeople down the left (ordered by name) and regions across the top (ordered by name), with a grid cell for each crosstab, and &nsbp; for empty cells.

<!-- Example of a "CrossTab" stylesheet
       using <xsl:key>'s for grouping -->
<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:key name="b" match="@region" use="."/>
  <xsl:template match="/">
    <html>  
      <body>
        <table border="1" cellspacing="0">
          <!-- Generate the row of table header cells -->
          <tr>
            <th>Salesperson</th>
            <!--
             | Generate a header cell for each unique region name
             |
             | See Chapter 9 of "Building Oracle XML Applications"
                                 from O'Reilly
             | for a detailed explanation of how this
                  <xsl:key> based technique works
             +-->
            <xsl:for-each
        select="//@region[generate-id(.)=generate-id(key('b',.)[1])]">
              <!-- Sort by the region name
                   (the value of the current @region attribute -->
              <xsl:sort select="."/>
              <th>
                <xsl:value-of select="."/>
              </th>
            </xsl:for-each>
          </tr>
        <!-- Generate a row for each salesman -->
        <xsl:for-each select="demo/salesman">
          <!-- Sort by salesman name -->
          <xsl:sort select="@name"/>
          <!-- Keep the current salesman in a variable for later -->
          <xsl:variable name="cur" select="."/>
          <tr>
            <!-- First cell has the salesman's name -->
            <td bgcolor="yellow">
                 <xsl:value-of select="@name"/></td>
            <!-- Generate a cell for each unique region -->
            <xsl:for-each
             select="//@region[generate-id(.)=
                    generate-id(key('b',.)[1])]">
              <td>
                <!-- If no accts for current salesman in current region,
                   do &nbsp; -->
                <xsl:if
                  test="not($cur/account[@region=current()])">
                        &#160;</xsl:if>
               
<!-- List matching accounts for current salesman in current region -->
                <xsl:for-each select="$cur/account[@region=current()]">
                  <xsl:value-of select="."/>
                  <xsl:if test="position() != last()"><br/></xsl:if>
                </xsl:for-each>
              </td>
            </xsl:for-each>
          </tr>
        </xsl:for-each>
      </table>
    </body>
  </html>
  </xsl:template>
</xsl:stylesheet>


James :-)
0
 

Author Comment

by:krisnackaerts
ID: 10740724
Thank's for the info, though,

I've been experimenting with xsl:key and use it actually in a similar way. The problem here is that the key is calculated for the whole document, and I need it only to be applied at a specific node. When I apply code similar to that above, I would get for depth:
10
11
20
21
30
31

I hope I made my problem clear, I was thinking more of using variables now...?

Kris
0
 
LVL 5

Accepted Solution

by:
conorj earned 250 total points
ID: 10742294
Is this what you want??

..................
<xsl:template match="/">
    <table>
        <xsl:apply-templates select="Results/GROUP" />
    </table>
</xsl:template>

<xsl:template match="GROUP">
    <tr>
        <th align="left" colspan="3">Group <xsl:value-of select="@Name" /></th>
    </tr>
    <tr>
        <th>Depth</th>
        <th><xsl:value-of select="ELEMENT[1]/@name" /></th>
        <th><xsl:value-of select="ELEMENT[2]/@name" /></th>
    </tr>
    <xsl:apply-templates select="ELEMENT[1]/Value" />
</xsl:template>

<xsl:template match="Value">
    <tr>
        <td>&#160;</td>
        <td><xsl:value-of select="." /></td>
        <td><xsl:value-of select="../following-sibling::ELEMENT/Value[@depth = current()/@depth]" /></td>
    </tr>
</xsl:template>
...........

HTH.

rgds,
Conor.
0
 

Author Comment

by:krisnackaerts
ID: 10926311
Many thank's,
The last solution was partially correct but helped me a lot.

<xsl:template match="XXXX">
        <xsl:apply-templates select="Results/GROUP"/>
</xsl:template>

<xsl:template match="GROUP">
        <DIV CLASS='GROUP'><xsl:value-of select="@name"/></DIV>
        <TABLE CLASS='GROUP'>
                <TR>
                        <TD CLASS='HEADERx'>depth</TD>
                        <xsl:for-each select="ELEMENT">
                                <TD CLASS='HEADERx'><xsl:value-of select="@name" /></TD>
                        </xsl:for-each>
                </TR>
                <xsl:apply-templates select="ELEMENT[1]/Value" />
        </TABLE>
</xsl:template>


<xsl:template match="Value">
        <TR>
                <TD class="Datax"><xsl:value-of select="./@depth"/></TD>
                <TD class="Datax"><xsl:value-of select="."/>x</TD>
                <xsl:for-each select="../following-sibling::ELEMENT/Value[@depth= current()/@depth]">
                        <TD class="Datax"><xsl:value-of select="."/></TD>
                </xsl:for-each>
        </TR>
</xsl:template>
0

Featured Post

DevOps Toolchain Recommendations

Read this Gartner Research Note and discover how your IT organization can automate and optimize DevOps processes using a toolchain architecture.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Title # Comments Views Activity
ebay devID, appID, certID, userToken 2 110
XML extra information 8 44
xslt 1.0 - How to split value 8 48
Retreiving SOAP FAULT messages using classical ASP 14 43
I found this questions asking how to do this in many different forums, so I will describe here how to implement a solution using PHP and AJAX. The logical flow for the problem should be: Write an event handler for the first drop down box to get …
Many times as a report developer I've been asked to display normalized data such as three rows with values Jack, Joe, and Bob as a single comma-separated string such as 'Jack, Joe, Bob', and vice versa.  Here's how to do it. 
Viewers will learn about the regular for loop in Java and how to use it. Definition: Break the for loop down into 3 parts: Syntax when using for loops: Example using a for loop:
The viewer will learn how to look for a specific file type in a local or remote server directory using PHP.

739 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question