1

I'm having some difficulties with nested grouping in XSLT 1.0. The problem is a few documents have multiple products in their metadata. First, I need to group the results by the products. Finally, grouping by types is expected as well. Actually, I gained the goals using for-each-group instruction from XSLT 2.0. But the thing is, I`m not able to change the version of XSLT in my project.

I would greatly appreciate it if someone could help me to implement this requirements in XSLT 1.0. I`m wondering if it is possible to use the Muenchian method there and what the named keys are look like.

Here is an example of input xml:

<result>
    <document>
        <metadata>
            <title>Academic Program Directors</title>
            <type>typeA</type>
            <product>Product1</product>
            <product>Product2</product>
            <product>Product3</product>
        </metadata>
    </document>
    <document>
        <metadata>
            <title>Administrative Directors</title>
            <type>typeA</type>
            <product>Product2</product>
        </metadata>
    </document>
    <document>
        <metadata>
            <title>Program Managers</title>
            <type>typeB</type>
            <product>Product1</product>
            <product>Product3</product>
        </metadata>
    </document>
</result>

The expect output would be the following:

Product1
    typeA
        Academic Program Directors
    typeB
        Program Managers

Product2
    typeA
        Academic Program Directors
        Administrative Directors

Product3
    typeA
        Academic Program Directors
    typeB
        Program Managers

Here's the XSLT 2.0 solution

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output method="html" indent="yes"/>

    <xsl:template match="/result">
        <xsl:for-each-group select="document/metadata" group-by="product">
            <xsl:sort select="current-grouping-key()"/>
            <p style="font-weight: bold;">
                <xsl:value-of select="current-grouping-key()"/>
            </p>

            <xsl:for-each-group select="current-group()" group-by="type">
                <xsl:sort select="current-grouping-key()"/>
                <p style="padding-left: 2em;">
                    <xsl:value-of select="current-grouping-key()"/>
                </p>

                <xsl:for-each select="current-group()">
                    <p style="padding-left: 4em;">
                        <xsl:value-of select="title"/>
                    </p>
                </xsl:for-each>
            </xsl:for-each-group>
        </xsl:for-each-group>
    </xsl:template>
</xsl:stylesheet>
2
  • So which XSLT 1 processor do you use, any support for e.g. exslt.org/set/functions/distinct/index.html? Commented Nov 8, 2017 at 9:21
  • @MartinHonnen we use Apache Xalan processor and it seems as set:distinct function is supported there. Thank you for your help and guidance! Commented Nov 8, 2017 at 11:56

1 Answer 1

1

A lot of XSLT 1 processors implement set:distinct (http://exslt.org/set/functions/distinct/index.html) so with that you can use the approach in http://xsltransform.net/bEzjRKX which does

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
  xmlns:set="http://exslt.org/sets" exclude-result-prefixes="set">

    <xsl:output method="html" indent="yes"/>

    <xsl:template match="/result">
        <xsl:variable name="metas" select="document/metadata"/>
        <xsl:for-each select="set:distinct($metas/product)">
            <xsl:sort select="."/>
            <p style="font-weight: bold;">
                <xsl:value-of select="."/>
            </p>
            <xsl:variable name="current-group" select="$metas[product = current()]"/>
            <xsl:variable name="types" select="set:distinct($current-group/type)"/>
            <xsl:for-each select="$types">
                <xsl:sort select="."/>
                <p style="padding-left: 2em;">
                    <xsl:value-of select="."/>
                </p>

                <xsl:for-each select="$current-group[type = current()]">
                    <p style="padding-left: 4em;">
                        <xsl:value-of select="title"/>
                    </p>
                </xsl:for-each>
            </xsl:for-each>
        </xsl:for-each>
    </xsl:template>

</xsl:transform>

Using keys and Muenchian grouping I think the problem can be solved with

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
  xmlns:set="http://exslt.org/sets" exclude-result-prefixes="set">

    <xsl:output method="html" indent="yes"/>

    <xsl:template match="/result">
        <xsl:variable name="metas" select="document/metadata"/>
        <xsl:for-each select="set:distinct($metas/product)">
            <xsl:sort select="."/>
            <p style="font-weight: bold;">
                <xsl:value-of select="."/>
            </p>
            <xsl:variable name="current-group" select="$metas[product = current()]"/>
            <xsl:variable name="types" select="set:distinct($current-group/type)"/>
            <xsl:for-each select="$types">
                <xsl:sort select="."/>
                <p style="padding-left: 2em;">
                    <xsl:value-of select="."/>
                </p>

                <xsl:for-each select="$current-group[type = current()]">
                    <p style="padding-left: 4em;">
                        <xsl:value-of select="title"/>
                    </p>
                </xsl:for-each>
            </xsl:for-each>
        </xsl:for-each>
    </xsl:template>

</xsl:transform>

Online at http://xsltransform.net/ehVYZNZ

Sign up to request clarification or add additional context in comments.

1 Comment

thank you a lot for the help! Both solutions are elegant and work fine for me. Especially thanks for the new function set:distinct I found because I`ve never seen it in the templates. Will try to adopt the first solution in my project.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.