Find max node value - xslt

I have a xml given below:
<root title="الصفحة الرئيسة">
<item title="الصفحة الرئيسة" itemuri="tcm:8-29-4" ShowInNav="True" type="sg" pageuri="tcm:8-10592-64" sLink="/ara/index.aspx">
<item title="من نحن" itemuri="tcm:8-779-4" ShowInNav="True" type="sg" pageuri="tcm:8-9934-64" navorder="00500" sLink="/ara/about/index.aspx">
</item>
<item title="برامجنا" itemuri="tcm:8-817-4" ShowInNav="True" type="sg" pageuri="tcm:8-10112-64" navorder="00100" sLink="/ara/courses/language-study-abroad.aspx">
</item>
<item title="مدارسنا" itemuri="tcm:8-824-4" ShowInNav="True" type="sg" pageuri="tcm:8-10162-64" navorder="00300" sLink="/ara/schools/english-language.aspx">
</item>
</item>
</root>
Now I want to the value of maximum navorder, so that I can use that value further in "if" condition.

Below is the XSLT 1.0 code of two possible solutions.
A third solution is to use EXSLT.
A fourth solution is to use the maximum template of FXSL.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:variable name="vMax1" select=
"*/*/*/#navorder[not(. < ../../*/#navorder)][3]"/>
$vMax1: <xsl:value-of select="$vMax1"/>
<xsl:variable name="vMax2">
<xsl:call-template name="max">
<xsl:with-param name="pSeq"
select="*/*/*/#navorder"/>
</xsl:call-template>
</xsl:variable>
$vMax2: <xsl:value-of select="$vMax2"/>
</xsl:template>
<xsl:template name="max">
<xsl:param name="pSeq"/>
<xsl:variable name="vLen" select="count($pSeq)"/>
<xsl:if test="$vLen > 0">
<xsl:choose>
<xsl:when test="$vLen = 1">
<xsl:value-of select="$pSeq[1]"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vHalf"
select="floor($vLen div 2)"/>
<xsl:variable name="vMax1">
<xsl:call-template name="max">
<xsl:with-param name="pSeq"
select="$pSeq[not(position() > $vHalf)]"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vMax2">
<xsl:call-template name="max">
<xsl:with-param name="pSeq"
select="$pSeq[position() > $vHalf]"/>
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<xsl:when test="$vMax1 >= $vMax2">
<xsl:value-of select="$vMax1"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$vMax2"/>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When the above transformatio is applied on the original XML document:
<root title="الصفحة الرئيسة">
<item title="الصفحة الرئيسة" itemuri="tcm:8-29-4"
ShowInNav="True" type="sg"
pageuri="tcm:8-10592-64"
sLink="/ara/index.aspx">
<item title="من نحن" itemuri="tcm:8-779-4"
ShowInNav="True" type="sg"
pageuri="tcm:8-9934-64"
navorder="00500"
sLink="/ara/about/index.aspx"/>
<item title="برامجنا" itemuri="tcm:8-817-4"
ShowInNav="True" type="sg"
pageuri="tcm:8-10112-64"
navorder="00100"
sLink="/ara/courses/language-study-abroad.aspx"/>
<item title="مدارسنا" itemuri="tcm:8-824-4"
ShowInNav="True" type="sg"
pageuri="tcm:8-10162-64"
navorder="00300"
sLink="/ara/schools/english-language.aspx"/>
</item>
</root>
the wanted result is produced:
$vMax1: 00500
$vMax2: 00500

the following is a solution using XPath 1.0:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="maximum">
<xsl:value-of select="/root/item//item[not(preceding-sibling::item/#navorder > #navorder or following-sibling::item/#navorder > #navorder)]/#navorder"/>
</xsl:variable>
<max>
<xsl:value-of select="$maximum"/>
</max>
</xsl:template>
</xsl:stylesheet>
The expression
/root/item//item[not(preceding-sibling::item/#navorder > #navorder or following-sibling::item/#navorder > #navorder)]/#navorder
filters all item nodes so that there is no preceding or following node that has a higher navorder value on the same level.

I found this forum post as one potential solution.
Of course, if you have access to XPath 2.0, there's a more direct solution: fn:max(arg, arg, …)

Couple of ways: with xpath 2 you can use the max() function, but if you're in xslt 1 you'll have to use sort in a for-each and then put the item at position 1 in your variable.

Have a look at this. Then use it in your xpath
/root/item[#navorder = math:max(/root/item/#navorder)]
Or something similar :)
NOTE This requires the use of EXSLT.
NOTE 2: No it doesn't. Just copy and paste the code.

Related

How to define attribute name dynamically in XSLT?

I have this XML:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl">
<xsl:param name="navigation-xml">
<item id="home" title-en="Services" title-de="Leistungen" />
<item id="company" title-en="Company" title-de="Unternehmen" />
<item id="references" title-en="References" title-de="Referenzen" />
</xsl:param>
<xsl:param name="navigation" select="exsl:node-set($navigation-xml)/*" />
<xsl:param name="navigation-id" />
<xsl:template name="title">
<xsl:apply-templates select="$navigation" mode="title" />
</xsl:template>
<xsl:template match="item" mode="title">
<xsl:if test="$navigation-id = #id">
<xsl:choose>
<xsl:when test="$current-language = 'de'">
<xsl:value-of select="#title-de" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="#title-en" />
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
How can I refactor the last 12 lines, so that the attribute name (either #title-de or #title-en) gets determined dynamically rather than in the (silly) way I did it in?
Thanks for any help.
You could write
<xsl:template name="title">
<xsl:apply-templates select="$navigation" mode="title" />
</xsl:template>
<xsl:template match="item" mode="title">
<xsl:if test="$navigation-id = #id">
<xsl:choose>
<xsl:when test="$current-language = 'de'">
<xsl:value-of select="#title-de" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="#title-en" />
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
as
<xsl:template name="title">
<xsl:apply-templates select="$navigation[$navigation-id = #id]" mode="title" />
</xsl:template>
<xsl:template match="item" mode="title">
<xsl:value-of select="#*[local-name() = concat('title-', $current-language)]" />
</xsl:template>
IMHO, your problem starts much earlier. If you define your navigation-xml parameter as:
<xsl:param name="navigation-xml">
<item id="home">
<title lang="en">Services</title>
<title lang="de">Leistungen</title>
</item>
<item id="company">
<title lang="en">Company</title>
<title lang="de">Unternehmen</title>
</item>
<item id="references">
<title lang="en">References</title>
<title lang="de">Referenzen</title>
</item>
</xsl:param>
you will be able to address its individual nodes much more conveniently and elegantly.

xslt: create a schedule

can someone tell me, how can I create a list of dates using xslt using a start- and end date and a parameter for the calculation period, so sth like the input
<root>
<startdate>2014/01/01</startdate>
<enddate>2015/02/20</enddate>
<period>daily</period>
</root>
gives me a list
<root>
<date>2014/01/01</date>
<date>2014/01/02</date>
...
<date>2014/02/20</date>
</root>
My node period can have values in
daily
weekly
fortnightly
monthly
So the last 3 would give me lists like
<root>
<date>2014/01/01</date>
<date>2014/01/08</date>
...
<date>2015/02/18</date>
</root>
<root>
<date>2014/01/01</date>
<date>2014/01/15</date>
...
<date>2015/02/11</date>
</root>
<root>
<date>2014/01/01</date>
<date>2014/02/01</date>
...
<date>2015/02/01</date>
</root>
with the last date smaller or equal the enddate. The date formats I would use are YYYYMMDD and DD/MM/YYYY, but I would probably be able to adapt any other date format.
Someone knows how to do this?
Thanks very much!
First, you must use YYYY-MM-DD format if you want your dates to be recognized as such.
Now, here's a quick-and-dirty way to achieve the requested result:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/root">
<result>
<xsl:call-template name="enumerate-dates">
<xsl:with-param name="startdate" select="startdate"/>
<xsl:with-param name="enddate" select="enddate"/>
<xsl:with-param name="period" select="period"/>
</xsl:call-template>
</result>
</xsl:template>
<xsl:template name="enumerate-dates">
<xsl:param name="startdate" as="xs:date"/>
<xsl:param name="enddate" as="xs:date"/>
<xsl:param name="period"/>
<xsl:if test="$startdate le $enddate">
<date><xsl:value-of select="$startdate" /></date>
<xsl:call-template name="enumerate-dates">
<xsl:with-param name="startdate">
<xsl:choose>
<xsl:when test="$period='daily'">
<xsl:value-of select="$startdate + xs:dayTimeDuration('P1D')" />
</xsl:when>
<xsl:when test="$period='weekly'">
<xsl:value-of select="$startdate + xs:dayTimeDuration('P7D')" />
</xsl:when>
<xsl:when test="$period='fortnightly'">
<xsl:value-of select="$startdate + xs:dayTimeDuration('P14D')" />
</xsl:when>
<xsl:when test="$period='monthly'">
<xsl:value-of select="$startdate + xs:yearMonthDuration('P1M')" />
</xsl:when>
</xsl:choose>
</xsl:with-param>
<xsl:with-param name="enddate" select="$enddate"/>
<xsl:with-param name="period" select="$period"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Applied to an example input of:
<root>
<startdate>2013-12-15</startdate>
<enddate>2014-03-08</enddate>
<period>fortnightly</period>
</root>
the result is:
<?xml version="1.0" encoding="utf-8"?>
<result>
<date>2013-12-15</date>
<date>2013-12-29</date>
<date>2014-01-12</date>
<date>2014-01-26</date>
<date>2014-02-09</date>
<date>2014-02-23</date>
</result>

how to sum subdocument properties

There is list
<items>
<item parentid='1'>
<amount>3</amount>
</item>
<item parentid='2'>
<amount>1</amount>
</item>
</items>
and document:
<udata id='1'>
<price>10</price>
</udata>
<udata id='1'>
<price>20</price>
</udata>
How to sum price's all document's?
To sum count I use'd:
<xsl:value-of select="sum(items/item/amount)"/>
I'd use:
<xsl:apply-templates select="udata/items/item" mode='price2' />
<xsl:template mode='price2' match='item'>
<xsl:apply-templates select="document(concat('upage://', page/#parentId))" mode='price'>
<xsl:with-param select='amount' name='count'/>
</xsl:apply-templates>
</xsl:template>
<xsl:template mode='price' match='/'>
<xsl:param name='count'/>
<xsl:value-of select="$count * /udata/page/properties/group[#name='price_prop']/property[#name='price']/value"/>
</xsl:template>
In result i had:
3020
I need 50. How to do this?
Here is a sample assuming XSLT 2.0 (e.g. as possible with Saxon 9 or AltovaXML):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:param name="data-url" select="'test2012050103.xml'"/>
<xsl:variable name="data-doc" select="document($data-url)"/>
<xsl:key name="k1" match="udata" use="#id"/>
<xsl:template match="items">
<xsl:value-of select="sum(for $item in item return $item/amount * key('k1', $item/#parentid, $data-doc)/price)"/>
</xsl:template>
</xsl:stylesheet>
Example documents are
<items>
<item parentid='1'>
<amount>3</amount>
</item>
<item parentid='2'>
<amount>1</amount>
</item>
</items>
and
<root>
<udata id='1'>
<price>10</price>
</udata>
<udata id='2'>
<price>20</price>
</udata>
</root>
Output is 50.
[edit]Here is an XSLT 1.0 stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:param name="data-url" select="'test2012050103.xml'"/>
<xsl:variable name="data-doc" select="document($data-url)"/>
<xsl:key name="k1" match="udata" use="#id"/>
<xsl:template match="items">
<xsl:call-template name="sum">
<xsl:with-param name="items" select="item"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="sum">
<xsl:param name="items" select="/.."/>
<xsl:param name="total" select="0"/>
<xsl:choose>
<xsl:when test="not($items)">
<xsl:value-of select="$total"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="price">
<xsl:for-each select="$data-doc">
<xsl:value-of select="$items[1]/amount * key('k1', $items[1]/#parentid)/price"/>
</xsl:for-each>
</xsl:variable>
<xsl:call-template name="sum">
<xsl:with-param name="items" select="$items[position() > 1]"/>
<xsl:with-param name="total" select="$total + $price"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Here is a shorter solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:variable name="vPrices" select=
"document('file:///c:/temp/delete/priceData.xml')/*/*"/>
<xsl:template match="/*">
<xsl:apply-templates select="item[1]"/>
</xsl:template>
<xsl:template match="item" name="sumProducts">
<xsl:param name="pAccum" select="0"/>
<xsl:variable name="vNewAccum" select=
"$pAccum +amount * $vPrices[#id = current()/#parentid]/price"/>
<xsl:if test="not(following-sibling::*)">
<xsl:value-of select="$vNewAccum"/>
</xsl:if>
<xsl:apply-templates select="following-sibling::*">
<xsl:with-param name="pAccum" select="$vNewAccum"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
When applied on the following XML document:
<items>
<item parentid='1'>
<amount>3</amount>
</item>
<item parentid='2'>
<amount>1</amount>
</item>
</items>
and having the file c:\temp\delete\priceData.xml contain:
<root>
<udata id='1'>
<price>10</price>
</udata>
<udata id='2'>
<price>20</price>
</udata>
</root>
then the wanted, correct result is produced:
50

Proper use of for-each-group in XSLT

I have the following xml sample:
<items>
<item>
<item_id>1</item_id>
<item_name>item 1</item_name>
<group_id>1</group_id>
<group_name>group 1</group_name>
</item>
<item>
<item_id>2</item_id>
<item_name>item 2</item_name>
<group_id>1</group_id>
<group_name>group 1</group_name>
</item>
<item>
<item_id>3</item_id>
<item_name>item 3</item_name>
<group_id>2</group_id>
<group_name>group 2</group_name>
</item>
</items>
which I need transformed into the following csv format:
1,item 1
2,item 2
3,item 3
1,group 1
2,group 2
In the xml, item_id will always be followed by item_name. The item name will not always be concat('item_',#). It could be an description such as 'toothpaste'. Items will never be repeated in the file but occasionally the group_id->group_name pairing will not always be 1-1. In this case, it is preferred to take the first pairing.
I am accomplishing this using a 'for-each-group' statement but it seems a bit hacky. Are there any downsides to this approach? What are some better ways to accomplish this?
<xsl:template match="/">
<xsl:call-template name="list_format">
<xsl:with-param name="list" select="'item'"/>
</xsl:call-template>
<xsl:call-template name="list_format">
<xsl:with-param name="list" select="'group'"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="list_format">
<xsl:param name="list"/>
<xsl:variable name="linefeed" select="'
'"></xsl:variable>
<xsl:variable name="list_id" select="concat($list,'_id')"></xsl:variable>
<xsl:variable name="list_name" select="concat($list,'_name')"></xsl:variable>
<xsl:for-each-group select="/items/item" group-by="*[name()=$list_id]">
<xsl:sort select="*[name()=$list_id]"/>
<xsl:value-of select="*[name()=$list_id]"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="*[name()=$list_name]"/>
<xsl:value-of select="$linefeed"/>
</xsl:for-each-group>
</xsl:template>
hmmm .. not really a downside but it doesn't work in XSLT 1.0 as it doesn't support for-each-group :-/
I don't think you would need XSLT 1.0 solution .. but still am posting my work :) Check this out!
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/items">
<xsl:call-template name="items_grouping">
<xsl:with-param name="list" select="'item'"/>
</xsl:call-template>
<xsl:call-template name="items_grouping">
<xsl:with-param name="list" select="'group'"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="items_grouping">
<xsl:param name="list"/>
<xsl:variable name="linefeed" select="'
'"></xsl:variable>
<xsl:variable name="list_id" select="concat($list,'_id')"></xsl:variable>
<xsl:variable name="list_name" select="concat($list,'_name')"></xsl:variable>
<xsl:for-each select="item">
<xsl:for-each select="*[name()=$list_id and not(.= ../preceding-sibling::item/*[name()=$list_id]/.)]">
<xsl:value-of select="concat(.,',')"/>
<xsl:value-of select="../*[name()=$list_name]"/>
<xsl:value-of select="$linefeed"/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
Used same methodology of yours .. only change is 'preceding-sibling'
and also for this code, order of child-nodes of <item> doesn't matter
Here is a simple and short XSLT 2.0 (as requested) solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates/>
<xsl:for-each-group select="/*/*/group_id" group-by=".">
<xsl:value-of separator="," select="../*[starts-with(name(), 'group_')]"/>
<xsl:text>
</xsl:text>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="item">
<xsl:value-of separator="," select="*[starts-with(name(), 'item_')]"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<items>
<item>
<item_id>1</item_id>
<item_name>item 1</item_name>
<group_id>1</group_id>
<group_name>group 1</group_name>
</item>
<item>
<item_id>2</item_id>
<item_name>item 2</item_name>
<group_id>1</group_id>
<group_name>group 1</group_name>
</item>
<item>
<item_id>3</item_id>
<item_name>item 3</item_name>
<group_id>2</group_id>
<group_name>group 2</group_name>
</item>
</items>
the wanted, correct result is produced:
1,item 1
2,item 2
3,item 3
1,group 1
2,group 2

XSLT 1.0 to iterate over several XML elements with comma delimited values

I have an XML document structured as follows
<items>
<item>
<name>item1</name>
<attributes>a,b,c,d</attributes>
</item>
<item>
<name>item2</name>
<attributes>c,d,e</attributes>
</item>
</items>
For each unique attribute value (delimited by commas) I need to list all item names associated with that value like so:
a : item1
b : item1
c : item1, item2
d : item1, item2
e : item2
My initial plan was to use a template to parse the attributes into Attribute nodes, surrounding each with appropriate tags, and then separating out the unique values with an XPATH expression like
Attribute[not(.=following::Attribute)]
but since the result of the template isn't a node-set that ever goes through an XML parser, I can't traverse it. I also tried exslt's node-set() function only to realize it does not allow me to traverse the individual Attribute nodes either.
At this point I'm at a loss for a simple way to do this and would really appreciate any help or ideas on how to proceed. Thanks!
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:ext="http://exslt.org/common">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kAtrByVal" match="attr" use="."/>
<xsl:template match="/">
<xsl:variable name="vrtfPass1">
<groups>
<xsl:apply-templates/>
</groups>
</xsl:variable>
<xsl:variable name="vPass1"
select="ext:node-set($vrtfPass1)"/>
<xsl:apply-templates select="$vPass1/*"/>
</xsl:template>
<xsl:template match="item">
<group name="{name}">
<xsl:apply-templates select="attributes"/>
</group>
</xsl:template>
<xsl:template match="attributes" name="tokenize">
<xsl:param name="pText" select="."/>
<xsl:if test="string-length($pText)">
<xsl:variable name="vText" select=
"concat($pText,',')"/>
<attr>
<xsl:value-of select="substring-before($vText,',')"/>
</attr>
<xsl:call-template name="tokenize">
<xsl:with-param name="pText" select=
"substring-after($pText,',')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match=
"attr[generate-id()
=
generate-id(key('kAtrByVal',.)[1])
]
">
<xsl:value-of select="concat('
',.,': ')"/>
<xsl:for-each select="key('kAtrByVal',.)">
<xsl:value-of select="../#name"/>
<xsl:if test="not(position()=last())">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
when applied on the provided XML document:
<items>
<item>
<name>item1</name>
<attributes>a,b,c,d</attributes>
</item>
<item>
<name>item2</name>
<attributes>c,d,e</attributes>
</item>
</items>
produces the wanted, correct result:
a: item1
b: item1
c: item1, item2
d: item1, item2
e: item2
Explanation:
Pass1: tokenization and end result:
<groups>
<group name="item1">
<attr>a</attr>
<attr>b</attr>
<attr>c</attr>
<attr>d</attr>
</group>
<group name="item2">
<attr>c</attr>
<attr>d</attr>
<attr>e</attr>
</group>
</groups>
.2. Pass2 takes the result of Pass1 (converted to a nodeset using the extension function ext:node-set()) as input, performs Muenchian grouping and produces the final, wanted result.
My first thought is to make two passes. First, tokenize the attributes elements using a (slightly) modified version of #Alejandro's answer to this previous question:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<items>
<xsl:apply-templates/>
</items>
</xsl:template>
<xsl:template match="item">
<item name="{name}">
<xsl:apply-templates select="attributes"/>
</item>
</xsl:template>
<xsl:template match="attributes" name="tokenize">
<xsl:param name="text" select="."/>
<xsl:param name="separator" select="','"/>
<xsl:choose>
<xsl:when test="not(contains($text, $separator))">
<val>
<xsl:value-of select="normalize-space($text)"/>
</val>
</xsl:when>
<xsl:otherwise>
<val>
<xsl:value-of select="normalize-space(
substring-before($text, $separator))"/>
</val>
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring-after(
$text, $separator)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Which produces:
<items>
<item name="item1">
<val>a</val>
<val>b</val>
<val>c</val>
<val>d</val>
</item>
<item name="item2">
<val>c</val>
<val>d</val>
<val>e</val>
</item>
</items>
Then apply the following stylesheet to that output:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:key name="byVal" match="val" use="." />
<xsl:template match="val[generate-id() =
generate-id(key('byVal', .)[1])]">
<xsl:value-of select="." />
<xsl:text> : </xsl:text>
<xsl:apply-templates select="key('byVal', .)" mode="group" />
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="val" mode="group">
<xsl:value-of select="../#name" />
<xsl:if test="position() != last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:template>
<xsl:template match="val" />
</xsl:stylesheet>
Producing:
a : item1
b : item1
c : item1, item2
d : item1, item2
e : item2
Doing this in one stylesheet would require more thought (or an extension function).