I have the following XML:
<cars>
<car type='toyota'>
<model>Yaris</model>
<year>1998</year>
<company>TOYOTA</company>
</car>
<car type='kia'>
<model>Optima</model>
<year>2002</year>
<company>KIA</company>
</car>
<car type='kia'>
<model>CERATO</model>
<year>2009</year>
<company>KIA</company>
</car>
<car type='bmw'>
<model>M3</model>
<year>2016</year>
<company>BMW</company>
</car>
<car type='bmw'>
<model>X5</model>
<year>2010</year>
</car>
<car type='bmw'>
<model>335i</model>
<year>2010</year>
<company>BMW</company>
</car>
</cars>
I want to group the cars by company element with sorting (alpha, ascending) on the same element. the output should be something like:
BMW: M3, X5, 335i
KIA: Optima, CERATO
TOYOTA: Yaris
The thing is that the car element might not contain a company node, In this case the car/@type value must be used to add the element to the correct group. How can I map the value of the @type attribute to the correct group that is based on the company value?
A little know feature of XSLT (at least in version 2.0) is
that when you create a list, e.g. (xx, yy, zz)
, this list
actually containts only existing values.
If e.g. xx
value was empty, it would not be a part of the
result list.
So if you write [1]
after it, you actually get the first
non-empty element from the expression list between parentheses.
In your comment, as of 5.08, you asked for a solution that other
type
shoud be treated as TOYOTA
.
It is possible, using if ... then ... else ...
, in this case:
if (@type = 'other') then 'TOYOTA' else @type
So you can write the script the following way:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="cars">
<xsl:for-each-group select="car" group-by="upper-case(
(company, if (@type = 'other') then 'TOYOTA' else @type)[1])">
<xsl:sort select="current-grouping-key()"/>
<xsl:value-of select="current-grouping-key()"/>
<xsl:text>: </xsl:text>
<xsl:value-of select="string-join(current-group()/model, ', ')"/>
<xsl:text>
</xsl:text>
</xsl:for-each-group>
</xsl:template>
</xsl:transform>
As you can see:
(company, if (@type = 'other') then 'TOYOTA' else @type)
is the source list (argument of upper-case),[1]
takes first element from what has been created.I moved the call to upper-case
to the "outer level",
assuming that model
can also be written in lower case.