Group contiguous siblings of a type - xslt

Given siblings, some of which are <row> elements and some not, like this,
<h />
<row id='v' />
<a />
<b />
<row id='w' />
<d />
<row id='x' />
<row id='y' />
<f />
<r />
<row id='z' />
using xslt 1.0, I need to process them in order but to group the non-row ones together as I go, like this,
<notRow>
<h />
</notRow>
<row id='v' />
<notRow>
<a />
<b />
</notRow>
<row id='w' />
<notRow>
<d />
<row id='x' />
<row id='y' />
<notRow>
<f />
<r />
</notRow>
<row id='z' />
The first and last may or may not be <row> elements.
How?

It can be as short and simple as this (no need of calling templates several times, xsl:for-each, xsl:if). Here is the complete transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kFollowing" match="*/*[not(self::row)]"
use="concat(generate-id(..), '+',
generate-id(preceding-sibling::row[1])
)"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template priority="2" match=
"*/*[not(self::row)
and
(preceding-sibling::*[1][self::row]
or not(preceding-sibling::*)
)]">
<notRow>
<xsl:copy-of select=
"key('kFollowing', concat(generate-id(..), '+',
generate-id(preceding-sibling::row[1])
))"/>
</notRow>
</xsl:template>
<xsl:template match="*/*[not(self::row)]"/>
</xsl:stylesheet>
When this transformation is applied on the provided XML (wrapped into a single top element to make it well-formed):
<t>
<h />
<row id='v' />
<a />
<b />
<row id='w' />
<d />
<row id='x' />
<row id='y' />
<f />
<r />
<row id='z' />
</t>
the wanted, correct result is produced:
<t>
<notRow>
<h/>
</notRow>
<row id="v"/>
<notRow>
<a/>
<b/>
</notRow>
<row id="w"/>
<notRow>
<d/>
</notRow>
<row id="x"/>
<row id="y"/>
<notRow>
<f/>
<r/>
</notRow>
<row id="z"/>
</t>
Update:
The OP has expressed an additional requirement that nodes need be processed by matching templates -- not just copied.
This requires only minimal change:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kFollowing" match="*/*[not(self::row)]"
use="concat(generate-id(..), '+',
generate-id(preceding-sibling::row[1])
)"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template priority="2" match=
"*/*[not(self::row)
and
(preceding-sibling::*[1][self::row]
or not(preceding-sibling::*)
)]">
<notRow>
<xsl:apply-templates mode="group" select=
"key('kFollowing', concat(generate-id(..), '+',
generate-id(preceding-sibling::row[1])
))"/>
</notRow>
</xsl:template>
<!-- This template can be replaced with whatever processing needed -->
<xsl:template match="*" mode="group">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="*/*[not(self::row)]"/>
</xsl:stylesheet>
The template that operates in mode "group" should be substituted with template(s) that implement the exact wanted processing. In this case it copies the matched element -- but in the real application any wanted processing would go here.

You might be able to do a trick with a key to group each non-row element by its preceding row (if there is one), or its parent element if not:
<xsl:key name="elementsFollowingRow"
match="*[not(self::row)]"
use="generate-id( (.. | preceding-sibling::row )[last()])" />
and define a named template to put in a notRow if the current element has any associated elements according to the key
<xsl:template name="addNotRow">
<xsl:if test="key('elementsFollowingRow', generate-id())">
<notRow>
<xsl:copy-of select="key('elementsFollowingRow', generate-id())" />
</notRow>
</xsl:if>
</xsl:template>
Then in the template where you're matching the parent element (the one that contains all these row and non-row elements you can do
<xsl:call-template name="addNotRow" />
<xsl:for-each select="row">
<xsl:copy-of select="." />
<xsl:call-template name="addNotRow" />
</xsl:for-each>
The first call-template outside the for-each will deal with any notRow that is required before the first row, and the call inside the for-each will put in any notRow required after the row in question.

This isn't pretty, but it works.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="t">
<xsl:if test="row[1]/preceding-sibling::*">
<notRow>
<xsl:for-each select="row[1]/preceding-sibling::*" >
<xsl:copy />
</xsl:for-each>
</notRow>
</xsl:if>
<xsl:for-each select="row">
<xsl:copy-of select="."/>
<xsl:if test="following-sibling::row[1]/preceding-sibling::*[generate-id(preceding-sibling::row[1])=generate-id(current())]">
<notRow>
<xsl:for-each select="following-sibling::row[1]/preceding-sibling::*[generate-id(preceding-sibling::row[1])=generate-id(current())]">
<xsl:copy />
</xsl:for-each>
</notRow>
</xsl:if>
</xsl:for-each>
<xsl:if test="row[last()]/following-sibling::*">
<notRow>
<xsl:for-each select="row[last()]/following-sibling::*" >
<xsl:copy />
</xsl:for-each>
</notRow>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
On this XML source
<t>
<h />
<i />
<row id='v' />
<a />
<b />
<row id='w' />
<d />
<row id='x' />
<row id='y' />
<f />
<r />
<row id='z' />
<i />
</t>
it returns the correct result:
<notRow>
<h/>
<i/>
</notRow>
<row id="v"/>
<notRow>
<a/>
<b/>
</notRow>
<row id="w"/>
<notRow>
<d/>
</notRow>
<row id="x"/>
<row id="y"/>
<notRow>
<f/>
<r/>
</notRow>
<row id="z"/>
<notRow>
<i/>
</notRow>
but it does seem there should be something simpler.

Related

Grouping dynamically

I'm trying to group the data together by summing a node adjacently. Example
<root>
<row id="AAA" val="2"/>
<row id="BBB" val="3"/>
<row id="CCC" val="1"/>
<row id="DDD" val="4"/>
<row id="EEE" val="6"/>
<row id="FFF" val="3"/>
<row id="GGG" val="6"/>
<row id="HHH" val="8"/>
<row id="III" val="3"/>
<row id="JJJ" val="4"/>
<row id="KKK" val="2"/>
<row id="LLL" val="1"/>
</root>
Let's say I have a parameter of 10 then, then every time the values sum to 10 or less than 10, they should be grouped together.
And the result should be
<root>
<grouped>
<row id="AAA" val="2"/>
<row id="BBB" val="3"/>
<row id="CCC" val="1"/>
<row id="DDD" val="4"/>
</grouped>
<grouped>
<row id="EEE" val="6"/>
<row id="FFF" val="3"/>
</grouped>
<grouped>
<row id="GGG" val="6"/>
</grouped>
<grouped>
<row id="HHH" val="8"/>
</grouped>
<grouped>
<row id="III" val="3"/>
<row id="JJJ" val="4"/>
<row id="KKK" val="2"/>
<row id="LLL" val="1"/>
</grouped>
</root>
I tried with group-adjacent with sum(current/#val + following-sibling::row/#val le 10) then tried group-by(sum(#val)) but I can see my basic approach is incorrect. Now I'm wondering, is this even possible. So I thought I'd ask the experts!
Thanks!
In XSLT 1 you could use sibling recursion, in XSLT 3 it is easier but a bit verbose to use xsl:iterate:
<xsl:template match="root">
<xsl:copy>
<xsl:iterate select="row">
<xsl:param name="sum" as="xs:integer" select="0"/>
<xsl:param name="group" as="element(row)*" select="()"/>
<xsl:on-completion>
<xsl:if test="$group">
<group>
<xsl:copy-of select="$group"/>
</group>
</xsl:if>
</xsl:on-completion>
<xsl:variable name="current-sum" select="$sum + xs:integer(#val)"/>
<xsl:if test="$current-sum > 10">
<group>
<xsl:copy-of select="$group"/>
</group>
</xsl:if>
<xsl:next-iteration>
<xsl:with-param name="sum" select="if ($current-sum > 10) then xs:integer(#val) else $current-sum"/>
<xsl:with-param name="group" select="if ($current-sum > 10) then . else ($group, .)"/>
</xsl:next-iteration>
</xsl:iterate>
</xsl:copy>
</xsl:template>
https://xsltfiddle.liberty-development.net/6pS2B6o
As an alternative, you could use an accumulator that sums the #val values and "remembers" when a "group" has been established, then in the grouping you can use group-starting-with to check the accumulator:
<xsl:param name="max" as="xs:integer" select="10"/>
<xsl:mode on-no-match="shallow-copy" use-accumulators="#all"/>
<xsl:output method="xml" indent="yes"/>
<xsl:accumulator name="window" as="item()*" initial-value="()">
<xsl:accumulator-rule match="root" select="(0, true())"/>
<xsl:accumulator-rule match="root/row"
select="let $val := xs:integer(#val),
$sum := $value[1],
$window-start := $value[2],
$current-sum := $sum + $val
return
if ($current-sum gt $max)
then ($val, true())
else ($current-sum, false())"/>
</xsl:accumulator>
<xsl:template match="root">
<xsl:copy>
<xsl:for-each-group select="row" group-starting-with="*[accumulator-before('window')[2]]">
<grouped>
<xsl:apply-templates select="current-group()"/>
</grouped>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
https://xsltfiddle.liberty-development.net/6pS2B6o/1
You can even make that streamable (with the help of Michael Kay):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="#all" version="3.0">
<xsl:param name="max" as="xs:integer" select="10"/>
<xsl:mode on-no-match="shallow-copy" use-accumulators="#all" streamable="yes"/>
<xsl:output method="xml" indent="yes"/>
<xsl:accumulator name="window" as="item()*" initial-value="()" streamable="yes">
<xsl:accumulator-rule match="root" select="(0, true())"/>
<xsl:accumulator-rule match="root/row"
select="
let $val := xs:integer(#val),
$sum := $value[1],
$window-start := $value[2],
$current-sum := $sum + $val
return
if ($current-sum gt $max)
then
($val, true())
else
($current-sum, false())"
/>
</xsl:accumulator>
<xsl:template match="root">
<xsl:copy>
<xsl:for-each-group select="row"
group-starting-with="*[boolean(accumulator-before('window')[2])]">
<grouped>
<xsl:apply-templates select="current-group()"/>
</grouped>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The xsl:for-each-group instruction can't handle this requirement.
As well as Martin's suggestions, another 3.0 approach is fold-left:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:array="http://www.w3.org/2005/xpath-functions/array"
exclude-result-prefixes="#all"
version="3.0">
<xsl:param name="max" as="xs:integer" select="10"/>
<xsl:template match="root">
<xsl:copy>
<xsl:variable name="groups" select="
fold-left(row, ([]), function($groups, $next) {
if (sum(head($groups)?*/#val) + $next/#val le $max)
then (array:append(head($groups), $next), tail($groups))
else ([$next], $groups)
}) => reverse()"/>
<xsl:for-each select="$groups">
<grouped>
<xsl:copy-of select="?*"/>
</grouped>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This builds the groups as a sequence of arrays, one array per group, initially in reverse order: the callback function is executed once for each row, and it adds the row to the first (i.e. last) group if the total is within your threshold, otherwise it starts a new group.
(Why in reverse order? Largely because head() and tail() are convenient, and there's no equivalent for getting the last item and "all except the last").

Distinct-values in XSL 1.0 inside of for each

I would like to get the distinct values inside of a for loop, or within some group. Since the xsl:key can only be declared at the top level, how would I be able to make a xsl:key for each group? In the example below, the group would be the most outer fruit tags. Note that there's also a xsl:sort. If there is a way to accomplish this by just xpaths (preceding-sibling), I would love to know this solution as well. I'm not sure if I would need to use the Muenchian method to accomplish this, but this is what I have:
Input.xml
<root>
<fruits>
<fruit>
<fruit id="2">
<banana><taste>Yummy</taste></banana>
<banana><taste>Disgusting</taste></banana>
</fruit>
<fruit id="1">
<banana><taste>Eh</taste></banana>
<banana><taste>Disgusting</taste></banana>
</fruit>
</fruit>
<fruit>
<fruit id="2">
<banana><taste>Yummy</taste></banana>
<banana><taste>Disgusting</taste></banana>
</fruit>
<fruit id="1">
<banana><taste>Amazing</taste></banana>
<banana><taste>Disgusting</taste></banana>
</fruit>
</fruit>
</fruits>
</root>
Transform.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:key name="taste" use="." match="taste" />
<xsl:template match="root">
<xsl:apply-templates select="fruits" />
</xsl:template>
<xsl:template match="fruits">
<xsl:element name="newFruits">
<xsl:call-template name="test" />
</xsl:element>
</xsl:template>
<xsl:template name="test">
<xsl:for-each select="fruit">
<xsl:sort select="fruit/#id" />
<xsl:element name="newFruit">
<!-- xsl:for-each select="fruit/banana/taste[not(.=preceding::taste)]/.." /> -->
<xsl:for-each select="fruit/banana/taste[generate-id() = generate-id(key('taste',.)[1])]/..">
<xsl:element name="fruit">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output (comments in the output is the desired tags that should appear)
<?xml version="1.0" encoding="UTF-8"?>
<newFruits>
<newFruit>
<fruit>Yummy</fruit>
<fruit>Disgusting</fruit>
<fruit>Eh</fruit>
</newFruit>
<newFruit>
<!-- <fruit>Yummy</fruit> -->
<!-- <fruit>Disgusting</fruit> -->
<fruit>Amazing</fruit>
</newFruit>
</newFruits>
The issue is that you want your taste elements to be distinct per each top-level fruit element. Your current grouping is getting the distinct elements for the whole document.
If you can't update to XSLT 2.0 then shed a tear, as you have to then use a concatenated key in XSLT 1.0, to include a unique identifier for the relevant fruit element, which can be achieved by using generate-id()
<xsl:key name="taste" use="concat(generate-id(../../..), '|', .)" match="taste" />
Then, in your "test" template, define a variable to hold the id for the relevant fruit...
<xsl:variable name="id" select="generate-id()" />
And your expression to get the distinct tastes becomes this...
<xsl:for-each select="fruit/banana/taste[generate-id() = generate-id(key('taste', concat($id, '|', .))[1])]">
Try this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="xml" indent="yes" />
<xsl:key name="taste" use="concat(generate-id(../../..), '|', .)" match="taste" />
<xsl:template match="root">
<xsl:apply-templates select="fruits" />
</xsl:template>
<xsl:template match="fruits">
<newFruits>
<xsl:call-template name="test" />
</newFruits>
</xsl:template>
<xsl:template name="test">
<xsl:for-each select="fruit">
<xsl:variable name="id" select="generate-id()" />
<newFruit>
<!-- xsl:for-each select="fruit/banana/taste[not(.=preceding::taste)]/.." /> -->
<xsl:for-each select="fruit/banana/taste[generate-id() = generate-id(key('taste', concat($id, '|', .))[1])]">
<fruit>
<xsl:value-of select="."/>
</fruit>
</xsl:for-each>
</newFruit>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Note, you don't really need the first template, and I can't see the point of a named template, so you can simplify the above XSLT to this...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="xml" indent="yes" />
<xsl:key name="taste" use="concat(generate-id(../../..), '|', .)" match="taste" />
<xsl:template match="fruits">
<newFruits>
<xsl:apply-templates select="fruit" />
</newFruits>
</xsl:template>
<xsl:template match="fruit">
<xsl:variable name="id" select="generate-id()" />
<newFruit>
<!-- xsl:for-each select="fruit/banana/taste[not(.=preceding::taste)]/.." /> -->
<xsl:for-each select="fruit/banana/taste[generate-id() = generate-id(key('taste', concat($id, '|', .))[1])]">
<fruit>
<xsl:value-of select="."/>
</fruit>
</xsl:for-each>
</newFruit>
</xsl:template>
</xsl:stylesheet>

Extreme XSLT XML Flattening

I have the following input XML file:
<root>
<a>
<b>1</b>
</a>
<c>
<d>
<e>2</e>
<f>3</f> or <e>3</e>
</d>
<g h="4"/>
<i>
<j>
<k>
<l m="5" n="6" o="7" />
<l m="8" n="9" o="0" />
</k>
</j>
</i>
</c>
</root>
I would like to use XSLT to transform it into the follow outputs:
OUTPUT 1
<root>
<row b="1" e="2" f="3" h="4" m="5" n="6" o="7" />
<row b="1" e="2" f="3" h="4" m="8" n="9" o="0" />
<root>
OUTPUT 2
<root>
<row b="1" e="2" h="4" m="5" n="6" o="7" />
<row b="1" e="2" h="4" m="8" n="9" o="0" />
<row b="1" e="3" h="4" m="5" n="6" o="7" />
<row b="1" e="3" h="4" m="8" n="9" o="0" />
<root>
Can anyone help my XSLT isn't very strong. Thanks.
It will be easier if you let the occurrence of <e> determine the outer loop constructing your <row>s and have a inner loop iterating over all <l>s.
Try something like this:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="root">
<root>
<xsl:apply-templates />
</root>
</xsl:template>
<xsl:template match="e">
<!-- store values of 'e' and 'f' in params -->
<xsl:param name="value_of_e" select="." />
<xsl:param name="value_of_f" select="ancestor::d[1]/f" />
<!-- iterate over all 'l's -->
<xsl:for-each select="//l">
<xsl:element name="row">
<xsl:attribute name="b">
<xsl:value-of select="//b" />
</xsl:attribute>
<xsl:attribute name="e">
<xsl:value-of select="$value_of_e" />
</xsl:attribute>
<!-- only include 'f' if it has a value -->
<xsl:if test="$value_of_f != ''">
<xsl:attribute name="f">
<xsl:value-of select="$value_of_f" />
</xsl:attribute>
</xsl:if>
<xsl:attribute name="h">
<xsl:value-of select="ancestor::c[1]/g/#h" />
</xsl:attribute>
<xsl:attribute name="m">
<xsl:value-of select="./#m" />
</xsl:attribute>
<xsl:attribute name="n">
<xsl:value-of select="./#n" />
</xsl:attribute>
<xsl:attribute name="o">
<xsl:value-of select="./#o" />
</xsl:attribute>
</xsl:element>
</xsl:for-each>
</xsl:template>
<xsl:template match="b" />
<xsl:template match="f" />
<xsl:template match="g" />
</xsl:stylesheet>

limit records display using xslt

I have a problem. I get the data from xml then transform it with xslt.
Let us say I have a xml file:
<?xml version="1.0"?>
<root>
<row id="1" fname="Dan" lname="Wahlin">
<address type="home">
<street>1234 Anywhere St.</street>
<city>AnyTown</city>
<zip>85789</zip>
</address>
<address type="business">
<street>1234 LottaWork Ave.</street>
<city>AnyTown</city>
<zip>85786</zip>
</address>
</row>
<row id="2" fname="Elaine" lname="Wahlin">
<address type="home">
<street>1234 Anywhere St.</street>
<city>AnyTown</city>
<zip>85789</zip>
</address>
<address type="business">
<street>1233 Books Way</street>
<city>AnyTown</city>
<zip>85784</zip>
</address>
</row>
</root>
And this stylesheet:
<?xml version="1.0" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes" encoding="utf-8" omit-xml-declaration="no"/>
<xsl:template match="/">
<root>
<xsl:apply-templates/>
</root>
</xsl:template>
<xsl:template match="row">
<row>
<xsl:attribute name="id">
<xsl:value-of select="id"/>
</xsl:attribute>
<xsl:attribute name="fname">
<xsl:value-of select="name/fname"/>
</xsl:attribute>
<xsl:attribute name="lname">
<xsl:value-of select="name/lname"/>
</xsl:attribute>
<xsl:for-each select="address">
<xsl:copy-of select="."/>
</xsl:for-each> </row>
</xsl:template>
</xsl:stylesheet
How can limit this to 3 records, then after 3 records it create a tr tag?
For example:
<table>
<tr>
<td>Address1</td>
<td>Address2</td>
<td>Address3</td>
</tr>
<tr>
<td>Address4</td>
<td>Address5</td>
<td>Address6</td>
</tr>
</table
try something like
<xsl:for-each select="PATH">
<xsl:variable name="pos" select="position() mod 3" />
</xsl:for-each>
then you can work with
<xsl:if test="$pos = 0">
</xsl:if>
and
<xsl:if test="$pos != 0">
</xsl:if>
if $pos = 0 means that you reached the 3rd row
Here are some good resources to learn more about XSLT and XPath
http://w3schools.com/xsl/default.asp
http://w3schools.com/xpath/default.asp

xsl grouping sort problem

I have the following xsl template that I'm using to group my xsl. The problem I have is that I need to uppercase the #Title as currently my grouping is seeing upper and lowercase as seperate groups.
<xsl:key name="rows-by-title" match="Row" use="substring(#Title,1,1)" />
<xsl:template name="Meunchian" match="/dsQueryResponse/Rows">
<xsl:for-each select="Row[count(. | key('rows-by-title', substring(#Title,1,1))[1]) = 1]">
<xsl:sort select="substring(#Title,1,1)" />
<p></p><xsl:value-of select="substring(#Title,1,1)" /><br />
<xsl:for-each select="key('rows-by-title', substring(#Title,1,1))">
<xsl:value-of select="#Title" /><br/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
I tried to use call-template and set a variable but xsl does not seem to like this:
<xsl:key name="rows-by-title" match="Row" use="substring(#Title,1,1)" />
<xsl:template name="Meunchian" match="/dsQueryResponse/Rows">
<xsl:for-each select="Row[count(. | key('rows-by-title', substring(#Title,1,1))[1]) = 1]">
<xsl:variable name="myTitle">
<xsl:call-template name="to-upper">
<xsl:with-param name="text">
<xsl:value-of select="#Title"/>
</xsl:with-param>
</xsl:call-template>
</xsl:variable>
<p></p><xsl:value-of select="$myTitle" /><br />
<xsl:for-each select="key('rows-by-title', substring(#Title,1,1))">
<xsl:value-of select="#Title" /><br/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
What I am trying to achieve is meunchian grouping but without case sensitivity - hope this makes Sense!
Kieran
The way to convert lower-case letters to upper is to use the XPath translate() function.
Using it, one way to express the desired transformation is the following:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:variable name="vLower" select=
"'abcdefghijklmnopqrstuvwxyz'"
/>
<xsl:variable name="vUpper" select=
"'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"
/>
<xsl:key name="rows-by-title" match="Row" use=
"translate(substring(#Title,1,1),
'abcdefghijklmnopqrstuvwxyz',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
)" />
<xsl:template match="/">
<html>
<xsl:apply-templates select="*/*"/>
</html>
</xsl:template>
<xsl:template name="Meunchian" match="/dsQueryResponse/Rows">
<xsl:for-each select=
"Row[generate-id()
=
generate-id(key('rows-by-title',
translate(substring(#Title,1,1),
$vLower,
$vUpper)
)[1]
)
]">
<xsl:sort select="translate(substring(#Title,1,1),
$vLower,
$vUpper)" />
<p></p>
<xsl:value-of select="translate(substring(#Title,1,1),
$vLower,
$vUpper)" />
<br />
<xsl:for-each select=
"key('rows-by-title',
translate(substring(#Title,1,1),
$vLower,
$vUpper)">
<xsl:value-of select="#Title" />
<br/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When applied on the following XML document:
<dsQueryResponse>
<Rows>
<Row Title="Agenda" />
<Row Title="Policy" />
<Row Title="policy" />
<Row Title="Report" />
<Row Title="report" />
<Row Title="Test2" />
<Row Title="test1" />
<Row Title="Boo" />
<Row Title="foo" />
</Rows>
</dsQueryResponse>
it produces the desired result:
<html>
<p/>A
<br/>Agenda
<br/>
<p/>B
<br/>Boo
<br/>
<p/>F
<br/>foo
<br/>
<p/>P
<br/>Policy
<br/>policy
<br/>
<p/>R
<br/>Report
<br/>report
<br/>
<p/>T
<br/>Test2
<br/>test1
<br/>
</html>
In XPath 2.0 one will use the upper-case() function to convert lower case to upper case.
Also, grouping in XSLT 2.0 can be better expressed using the <xsl:for-each-group>
instruction.