PDA

View Full Version : XSL: sorting and summing by attribues


duma
07-02-2004, 10:37 AM
Hi!

I have a problem where I need to sort XML entries by their attributes and sum them up. The XML is in this form:

<date date_time="2004-06-14">
<entry number_ordered="1" type="A"/>
<entry number_ordered="4" type="A"/>
<entry number_ordered="2" type="B"/>
<entry number_ordered="9" type="D"/>
</date>
<date date_time="2004-06-14">
<entry number_ordered="3" type="A"/>
<entry number_ordered="2" type="C"/>
<entry number_ordered="1" type="A"/>
</date>

... and so forth.

The result I am looking for is a list (XML or HTML) displaying
Entry A total of 9 ordered
Entry B total of 2 ordered
Entry C total of 2 ordered
Entry D total of 9 ordered

The number and names of the type attribute is not known (i.e. could be A, B, C, D or Cow, Horese, etc)

Any clues on how to sort and sum the entries based on the type attribute?

Christoffer

neofibril
07-04-2004, 01:04 AM
I played around with the xslt syntax for a while, but fell short of anything spectacular.
Well, not to fail completely, here is an example of how sorting could be done via js/DOM (IE-only, for expedience).


<body>
<xml id="msXML">
<?xml version="1.0"?>
<stuff>
<date date_time="2004-06-14">
<entry number_ordered="1" type="A"/>
<entry number_ordered="4" type="A"/>
<entry number_ordered="2" type="B"/>
<entry number_ordered="9" type="D"/>
</date>
<date date_time="2004-06-14">
<entry number_ordered="3" type="A"/>
<entry number_ordered="2" type="C"/>
<entry number_ordered="1" type="A"/>
</date>
</stuff>
</xml>
<script type="text/javascript">
function output(txt)
{
var nodes = msXML.getElementsByTagName("entry"),
arr = [],
ln = nodes.length,
i = -1;
while(++i < ln)
arr.push([nodes[i].getAttribute("type"), nodes[i].getAttribute("number_ordered")]);
arr = arr.sort().toString().split(",");
var y,
n = 0,
x = arr.shift();
while(arr.length)
{
n += +arr.shift();
y = arr.shift();
if(y == x)
continue;
txt.value += "Entry " + x + " total of " + n + " ordered\n\n";
n = 0;
x = y;
}
}
</script>
<form>
<fieldset style="text-align:center;padding-bottom:1em">
<legend>
<input type="button" value="process" onclick="output(txt)">
</legend>
<textarea name="txt" cols="75" rows="25"></textarea>
</fieldset>
</form>
</body>

duma
07-05-2004, 12:27 PM
Thanks for the input!

However, the XSL transformation is done server side by Sablotron, so Javascript transformation is unfortunately not applicable.

The solution is coded in PHP, which outputs XML. This is transformed to HTML by Sablotron and XSL templates.

I have considered doing the logic in PHP and outputting the totals in XML instead. It annoys me that this seemingly simpel sorting routine is not possible to do in XSL (or at least, that I cannot find a way). Furthermore, it seems most "correct" to do the sorting and summing in XSL.

Any suggestions in pure XSL to solve the problem?

Thanks in advance... :)

neofibril
07-05-2004, 10:31 PM
xslt/xpath don't appear to have methods that would facilitate operations of such an arbitrary nature.

Given that a combination of techniques would allow for this, these may end up being unnecessarily extensive/intensive, compared to DOM enumeration.

duma
07-06-2004, 11:27 AM
I found a way to solve the problem. With some inspiration from http://www.jenitennison.com/xslt/grouping/muenchian.html this code solves the problem (I have added a root element encapsulating the XML).


<xsl:key name="by-type" match="entry" use="@type" />
<xsl:template match="root">
<xsl:for-each select="date/entry[generate-id() = generate-id(key('by-type', @type)[1])]">
<xsl:sort select="@type"/>
Entry <xsl:value-of select="@type" /> total of
<xsl:value-of select="sum(../../date/entry[@type=current()/@type]/@number_ordered)" /> ordered.<br/>
</xsl:for-each>
</xsl:template>

I hope this is usefull. Thanks for the help :)

neofibril
07-07-2004, 05:34 AM
Useful, and less complicated than I imagined. Thanks for sharing the result.