RE: [xsl] How do I "merge" nodes based on a common key?

Subject: RE: [xsl] How do I "merge" nodes based on a common key?
From: "M. David Peterson" <m.david@xxxxxxxxxx>
Date: Fri, 26 Mar 2004 17:29:24 -0700
I can't tell from your thread or your source if the http_load, httpload,
and fetch-curl are the only sibling elements that you will have in your
source that will contain the title attribute.  Actually, in this case it
doesn't matter because if there are 2 or more elements with different
localnames but  are processed the same way, this is the approach I will
always use.  No need to create a template that matches every element
name if they're all processed the same way. However, if that's not the
case then you will need to create a template that matches each element
and processes it appropriately. 

Note: in the first template you will find <xsl:copy-of
select="$groupTitles"/>.  This is simply there to make a copy of the
final grouping and merging of the source XML that took place and feed it
to the output stream.  You will now need to take this variable and
apply-templates to it to transform it the way you want it layed out.
Keep in mind that your processor is going to see this variable as a
Result Tree Fragment (Because, technically, it is, even though it is
well formed) and as such you will need to convert it to a nodeset using
whatever method the processor you are using has invoked.  Xalan uses the
xalan:nodeset(), msxml uses msxml:node-set(), Saxon uses
exsl:node-set(), and... (fill in the list of XSLT processors here)...
Remember to declare the proper namespace in your xsl:stylesheet element
:)

Moving forward >>> :D

This XSL...

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
version="1.0">
  <xsl:key name="distinctTitle" match="@title" use="."/>
  <xsl:output method="xml" indent="yes"/>
  <xsl:template match="/">
    <xsl:variable name="groupTitles">
      <xsl:element name="groupTitles">
        <xsl:apply-templates select="run/child::*[generate-id(@title) =
generate-id(key('distinctTitle', @title))]" mode="groupDistinct">
          <xsl:with-param name="allElements" select="*"/>
        </xsl:apply-templates>
      </xsl:element>
    </xsl:variable>
    <xsl:copy-of select="$groupTitles"/>
  </xsl:template>
  <xsl:template match="*" mode="groupDistinct">
    <xsl:param name="allElements"/>
    <xsl:variable name="title" select="@title"/>
    <xsl:element name="group">
      <xsl:attribute name="name">
        <xsl:value-of select="@title"/>
      </xsl:attribute>
      <xsl:copy-of select="$allElements/child::*[@title = $title]"/>
    </xsl:element>
  </xsl:template>
</xsl:stylesheet>

Groups and transforms this XML... (deleted a bunch of elements and
attributes to save space and added a second layer of elements with
title="Y" to show the grouping effect.  By using xsl:copy-of to copy the
matching elements for the group it doesn't matter how many elements or
attributes there are... its copy's them all.)

<?xml version="1.0"?>
<run>
  <http_load title="X">
    <url>http://delenn-g:8080/lps/sample-apps/calendar/calendar.lzo?
lzt=swf</url>
    <rr>delenn-g</rr>
    <fetches>94851</fetches>
  </http_load>
  <httpload gzip="true"
      title="X"
      seconds="180"/>
  <fetch-curl title="X">
    <url>http://delenn-g:8080/lps/sample-apps/calendar/calendar.lzo?
lzt=swf</url>
    <status>HTTP/1.1 404 Not Found</status>
  </fetch-curl>
  <http_load title="Y">
    <url>http://delenn-g:8080/lps/sample-apps/calendar/calendar.lzo?
lzt=swf</url>
    <rr>delenn-g</rr>
    <fetches>94851</fetches>
  </http_load>
  <httpload gzip="true"
      title="Y"
      seconds="180"/>
  <fetch-curl title="Y">
    <url>http://delenn-g:8080/lps/sample-apps/calendar/calendar.lzo?
lzt=swf</url>
    <status>HTTP/1.1 404 Not Found</status>
  </fetch-curl>
</run>

Into this...

<?xml version="1.0" encoding="UTF-8"?>
<groupTitles>
  <group name="X">
    <http_load title="X">
    <url>http://delenn-g:8080/lps/sample-apps/calendar/calendar.lzo?
lzt=swf</url>
    <rr>delenn-g</rr>
    <fetches>94851</fetches>
  </http_load>
    <httpload gzip="true" title="X" seconds="180"/>
    <fetch-curl title="X">
    <url>http://delenn-g:8080/lps/sample-apps/calendar/calendar.lzo?
lzt=swf</url>
    <status>HTTP/1.1 404 Not Found</status>
  </fetch-curl>
  </group>
  <group name="Y">
    <http_load title="Y">
    <url>http://delenn-g:8080/lps/sample-apps/calendar/calendar.lzo?
lzt=swf</url>
    <rr>delenn-g</rr>
    <fetches>94851</fetches>
  </http_load>
    <httpload gzip="true" title="Y" seconds="180"/>
    <fetch-curl title="Y">
    <url>http://delenn-g:8080/lps/sample-apps/calendar/calendar.lzo?
lzt=swf</url>
    <status>HTTP/1.1 404 Not Found</status>
  </fetch-curl>
  </group>
</groupTitles>

Best of luck!

<M:D/>

-----Original Message-----
From: Joseph Silverman [mailto:yossie@xxxxxxxxxxxxxxxxx] 
Sent: Friday, March 26, 2004 3:22 PM
To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
Subject: Re: [xsl] How do I "merge" nodes based on a common key?

that allows me to group stuff, but how do I "merge" them once grouped?

to rephrase, I still get a line for EACH node in the original data, 
rather than one line for all nodes with any given matching @title.

Thanks for your help! - Yossie

---

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

<xsl:output method="html" encoding="UTF-8" indent="yes" />
<xsl:key name="title" match="http_load | httpload" use="@title" />
<xsl:template match="run">
<table border="1" cellpadding="6">
<xsl:for-each select="httpload[count(. | key('title', @title)[1]) = 1]">
     <xsl:for-each select="key('title',@title)">
<tr>
<td><xsl:value-of select="@title"/></td>
<td><xsl:value-of select="@cache"/></td>
<td><xsl:value-of select="@gzip"/></td>
<td><xsl:value-of select="fetches/text()"/></td>
<td><xsl:value-of select="max_parallel/text()"/></td>
<td><xsl:value-of select="mbytes_sec/text()"/></td>
</tr>
     </xsl:for-each>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>

On Mar 26, 2004, at 2:52 AM, Brian Chrisman wrote:

> A grouping question everybody asks... a good 'grouping' solution:
> http://www.jenitennison.com/xslt/grouping/muenchian.html
>
>
> Joseph Silverman wrote:
>
>> I have a xml file I would like to process into a html table.  I
figure
>> that xslt is the way to go.  The file contains various nodes (results
>> from load testing) that all share a single attribute, title, that
>> identifies and links them in groups.  See snippet below.
>>
>> So, I can't figure out some code to process this file and "merge" the
>> nodes based on the title attribute so I can print each "merged" node 
>> as
>> a single line in a table. sub-nodes AS well as attributes (though 
>> there
>> is no overlap in the latter!) should be merged.  Then I can use a
>> <xsl:for-each ..> to process the merged list.
>>
>> I tried some stuff on my own and it is getting me nowhere (see
below).
>>
>> Any ideas?
>>
>> Thanks - Yossie
>>
>> <run>
>> <http_load title="X">
>>
>> <url>http://delenn-g:8080/lps/sample-apps/calendar/calendar.lzo?
>> lzt=swf</url>
>>          <rr>delenn-g</rr>
>>          <fetches>94851</fetches>
>>          <max_parallel>50</max_parallel>
>>          <mbytes>111.443</mbytes>
>>          <elapsed>180.406</elapsed>
>>          <mean_kbytes_connection>1.20312</mean_kbytes_connection>
>>          <fetches_sec>525.763</fetches_sec>
>>          <mbytes_sec>0.617733</mbytes_sec>
>>          <msec_connect_mean>0.400405</msec_connect_mean>
>>          <msec_connect_max>66.577</msec_connect_max>
>>          <msec_connect_min>0.211</msec_connect_min>
>>          <msec_first_mean>94.5393</msec_first_mean>
>>          <msec_first_max>3768.62</msec_first_max>
>>          <msec_first_min>2.111</msec_first_min>
>>          <http_result>
>>                  <code>404</code>
>>                  <count>94851</count>
>>          </http_result>
>> </http_load>
>> <httpload       gzip="true"
>>          title="X"
>>          seconds="180"
>>          path="/sample-apps/calendar/calendar.lzo?lzt=swf"
>>          parallel="50"
>>          cache="false"
>>          lps="lps"
>>          datapath=""
>>          backend="" />
>> <fetch-curl title="X">
>>
>> <url>http://delenn-g:8080/lps/sample-apps/calendar/calendar.lzo?
>> lzt=swf</url>
>>          <status>HTTP/1.1 404 Not Found</status>
>>          <size>1232</size>
>>          <http-headers>
>>                  <Date>Wed, 24 Mar 2004 19:04:12 GMT</Date>
>>                  <Server>Jetty/4.2.17 (Linux/2.4.18-26.8.0 i386
>> java/1.4.1_02)</Server>
>>                  <Content-Type>text/html</Content-Type>
>>                  <Content-Length>1232</Content-Length>
>>          </http-headers>
>> </fetch-curl>
>>   <!--
>> ...
>>     ... more httpload, http_load, fetch-curl nodes with different 
>> title
>> attributes, no more than one of each with a given title.
>> ...
>> -->
>> </run>
>>
>> ---
>>
>> <xsl:stylesheet version = '1.0'
>> xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
>>
>> <xsl:output method="html" encoding="UTF-8" indent="yes" />
>> <xsl:template match="/run">
>> <xsl:key name="bleh" match="http_load | httpload | fetch_curl"
>> use="@title" />
>> <html>
>> <head>
>> <title>
>> Title
>> </title>
>> </head>
>> <body>
>> <table border="1" cellpadding="6">
>> <tr>
>> <th>Title</th>
>> <th>Cache</th>
>> <th>Gzip</th>
>> <th>Fetches</th>
>> <th>Parallel</th>
>> <th>Mbytes/Sec</th>
>> </tr>
>> <xsl:for-each select="key('bleh',.)">
>> <tr>
>> <td><xsl:value-of select="@title"/></td>
>> <td><xsl:value-of select="@cache"/></td>
>> <td><xsl:value-of select="@gzip"/></td>
>> <td><xsl:value-of select="fetches/text()"/></td>
>> <td><xsl:value-of select="max_parallel/text()"/></td>
>> <td><xsl:value-of select="mbytes_sec/text()"/></td>
>> </tr>
>> </xsl:for-each>
>> </table>
>> </body>
>> </html>
>> </xsl:template>
>> </xsl:stylesheet>
>
Yossie Silverman - ENTP                   "Leave the bearded one for me"
I'NET: yossie@xxxxxxxxxxxxxx            - Flesh Gordon
HTTP: http://www.blacksteel.com/~yossie/  B3 f- t dc g++ k++ s++ p m e+

Current Thread