...

...

...

...

...

...

Grouping only some items in a series

49 Views Asked by At

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?

2

There are 2 best solutions below

3
Daniel Haley On BEST ANSWER

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>
0
michael.hor257k On

I can't do for-each-group select="p" because then the title element is not processed at all.

You could do simply:

<xsl:template match="section">
    <xsl:copy>
        <xsl:copy-of select="title"/>
        <xsl:for-each-group select="p" group-adjacent="@style">
        ...

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.

Try:

    <xsl:for-each-group select="p" group-adjacent="@style">
        <xsl:choose>
            <xsl:when test="current-grouping-key()=('Warning', 'Note', 'Caution') 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>