xsltxslt-grouping

Grouping only some items in a series


I have an XML document that has a series of paragraphs:

<section>
    <title>Section title</title>
    <p style="Body">...</p>
    <p style="Body">...</p>
    <p style="Note">...</p>
    <p style="Body">...</p>
    <p style="Body">...</p>
    <p style="Warning">...</p>
    <p style="Warning">...</p>
    <p style="Warning">...</p>
    <p style="Body">...</p>
    <p style="Note">...</p>
    <p style="Note">...</p>
    <p style="Warning">...</p>
    <p style="Body">...</p>
</section>

I want to group some paragraphs so I can apply formatting to the entire group, e.g. put them inside a div so I can put a border around them. The order of the elements has to be preserved. The paragraphs I want to group have the @style 'Note','Warning' or 'Caution'. Specifically, I want to group them only if two or more paragraphs of the same @ style are adjacent to each other.

<section>
    <title>Section title</title>
    <p style="Body">...</p>
    <p style="Body">...</p>
    <p style="Note">...</p>
    <p style="Body">...</p>
    <p style="Body">...</p>
    <div>
        <p style="Warning">...</p>
        <p style="Warning">...</p>
        <p style="Warning">...</p>
    </div>
    <p style="Body">...</p>
    <div>
        <p style="Note">...</p>
        <p style="Note">...</p>
    </div>
    <p style="Warning">...</p>
    <p style="Body">...</p>
</section>

I'm having trouble with the for-each-group instruction:

<xsl:for-each-group select="*" group-adjacent="@stylename">
 (wrap in div here)

This gives me an error because the title element does not have a @stylename attribute. I can't do for-each-group select="p" because then the title element is not processed at all.

How do I process all elements in the section, but group only the paragraphs by their @stylename?


Solution

  • What error are you getting? I'm assuming it's something like:

    An empty sequence is not allowed as the @group-adjacent attribute of xsl:for-each-group
    

    This is because (from the spec):

    [ERR XTTE1100] It is a type error if the result of evaluating the group-adjacent expression is an empty sequence or a sequence containing more than one item, unless composite="yes" is specified.

    Try changing:

    group-adjacent="@style"
    

    to:

    group-adjacent="normalize-space(@style)"
    

    Example:

    <xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" expand-text="yes">
        <xsl:output indent="yes"/>
        <xsl:strip-space elements="*"/>
        
        <xsl:mode on-no-match="shallow-copy"/>
        
        <xsl:template match="section">
            <xsl:copy>
                <xsl:apply-templates select="@*"/>
                <xsl:for-each-group select="*" group-adjacent="normalize-space(@style)">
                    <xsl:choose>
                        <xsl:when test="current-grouping-key()=('Warning','Caution','Note') and count(current-group()) > 1">
                            <div>
                                <xsl:apply-templates select="current-group()"/>
                            </div>
                        </xsl:when>
                        <xsl:otherwise>
                            <xsl:apply-templates select="current-group()"/>
                        </xsl:otherwise>
                    </xsl:choose>
                </xsl:for-each-group>
            </xsl:copy>
        </xsl:template>
        
    </xsl:stylesheet>