chris457
chris457

Reputation: 76

XSLT 3.0 Grouping by <xsl:function as="xs:boolean">: How to map result to meaningful text attributes for output

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

Answers (2)

chris457
chris457

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

Michael Kay
Michael Kay

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

Related Questions