Reputation: 76
I want to use my own <xsl:function>
as a grouping key in XSLT 3.0. My function returns xs:boolean
but in the output of the grouping I want to map true
and false
to meaningful string values, in my case true = 'Live album'
and false = 'Studio album'
.
My first attempts with using an if..else
-expression or outsorucing current-grouping-key()
to a local variable all failed syntactically.
This is my stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:my="https://my-function.library.org" exclude-result-prefixes="#all">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:function name="my:group-key" as="xs:boolean">
<xsl:param name="recording"/>
<xsl:sequence select="contains($recording/title, 'Live')"/>
</xsl:function>
<xsl:template match="/catalog">
<groups>
<xsl:for-each-group select="recording" group-by="my:group-key(.)">
<group key="{current-grouping-key()}">
<xsl:for-each select="current-group()">
<titel><xsl:value-of select="title"/></titel>
</xsl:for-each>
</group>
</xsl:for-each-group>
</groups>
</xsl:template>
</xsl:stylesheet>
and this is the input XML (excerpt):
<?xml version="1.0" encoding="ISO-8859-1"?>
<catalog>
<recording medium="LP" state="2" no="1">
<title>The Years 1960-1970</title>
(...)
</recording>
<recording medium="tape" state="6" no="2">
<title>Live at the Village Vanguard</title>
(...)
</recording>
<recording medium="CD" state="8" no="3">
<title>Classic Tunes</title>
(...)
</recording>
</catalog>
The desired output:
<?xml version="1.0" encoding="UTF-8"?>
<groups>
<group key="Studio album">
<titel>The Years 1960-1970</titel>
<titel>Classic Tunes</titel>
</group>
<group key="Live album">
<titel>Live at the Village Vanguard</titel>
</group>
</groups>
Ideally I want to have a general solution that would allow for more mappings than just for true
and false
. So an if..else
-based solution would not even be ideal.
I was also wondering if it would make sense to outsource this mapping to a separate function. Any opinions about best practices here are also welcome.
Upvotes: 1
Views: 34
Reputation: 76
For anybody looking for runnable code ready for copy&pasting, this is my final solution:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:my="https://my-function.library.org" exclude-result-prefixes="#all">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:function name="my:group-key" as="xs:string">
<xsl:param name="recording"/>
<xsl:variable name="my-map" as="map(xs:boolean, xs:string)" select="map { true() : 'Live album', false() : 'Studio album' }"/>
<xsl:sequence select="$my-map(contains($recording/title, 'Live'))"/>
</xsl:function>
<xsl:template match="/catalog">
<groups>
<xsl:for-each-group select="recording" group-by="my:group-key(.)">
<group key="{current-grouping-key()}">
<xsl:for-each select="current-group()">
<titel><xsl:value-of select="title"/></titel>
</xsl:for-each>
</group>
</xsl:for-each-group>
</groups>
</xsl:template>
</xsl:stylesheet>
Thanx @MartinHonnen for the inspiration!
Upvotes: 1
Reputation: 163595
I don't see why you need the boolean, why not map the input directly to the output form?
<xsl:function name="my:group-key" as="xs:string">
<xsl:param name="recording"/>
<xsl:sequence select="if (contains($recording/title, 'Live'))
then 'Live album'
else 'Studio album'"/>
</xsl:function>
Upvotes: 3