0
votes

I have xml like follows,

<doc>
    <section>
        <p id="main">aa</p>
        <p id="main">bb</p>
        <p id="main">cc</p>
        <p id="para1">dd</p>
        <p id="st_main">ee</p>
        <p id="st_main">ff</p>
        <p id="xy_main">cc</p>
        <p id="xy_main">cc</p>
        <p id="main">gg</p>
        <p id="para2">hh</p>
        <p id="main">ii</p>
        <p id="st_main">jj</p>
        <p id="xy_main">cc</p>
        <p id="main">cc</p>
        <p id="para1">xx</p>
        <p id="main">yy</p>
        <p id="xy_main">zz</p>
        <p id="main">cc</p>
    </section>
</doc>

My requirements are

1) grouping <p> by para attribute and add separate section to each <p> group.

2) identify <p> node groups which id attribute starting from st put <st_start> and <st_end> at the start and the end on the group

3) identify <p> node groups which id attribute starting from xy put <xy_start> and <xy_end> at the start and the end on the group

SO the expected output is,

<doc>
    <section>
        <p id="main">aa</p>
        <p id="main">bb</p>
        <p id="main">cc</p>
    </section>
    <section type="para1">
        <p id="para1">dd</p>
        <ss_start/>
        <p id="st_main">ee</p>
        <p id="st_main">ff</p>
        <ss_end/>
        <xy_start/>
        <p id="xy_main">cc</p>
        <p id="xy_main">cc</p>
        <xy_end/>
        <p id="main">gg</p>
    </section>
    <section type="para2">
        <p id="para2">hh</p>
        <p id="main">ii</p>
        <ss_start/>
        <p id="st_main">jj</p>
        <ss_end/>
        <xy_start/>
        <p id="xy_main">cc</p>
        <xy_end/>
        <p id="main">cc</p>
    </section>
    <section type="para1">
        <p id="para1">xx</p>
        <p id="main">yy</p>
        <xy_start/>
        <p id="xy_main">zz</p>
        <xy_end/>
        <p id="main">cc</p>
    </section>
</doc>

I have following xsl to to this task

<xsl:template match="section">
        <xsl:for-each-group select="p" group-starting-with="p[starts-with(@id, 'para')]">
            <section>
                <xsl:for-each-group select="current-group()" group-adjacent="starts-with(@id, 'st')">
                    <xsl:if test="current-grouping-key()">
                        <ss_start/>
                    </xsl:if>
                    <xsl:apply-templates select="current-group()"/>
                    <xsl:if test="current-grouping-key()">
                        <ss_end/>
                    </xsl:if>
                </xsl:for-each-group>
                <xsl:for-each-group select="current-group()" group-adjacent="starts-with(@id, 'xy')">
                    <xsl:if test="current-grouping-key()">
                        <xy_start/>
                    </xsl:if>
                    <xsl:apply-templates select="current-group()"/>
                    <xsl:if test="current-grouping-key()">
                        <xy_end/>
                    </xsl:if>
                </xsl:for-each-group>
            </section>
        </xsl:for-each-group>
    </xsl:template>

But is doubled up the content ? How can I modify my code to get the expected output?

1

1 Answers

1
votes

I think you only need one nested xsl:for-each-group here, when you group adjacent nodes by their id attribute. You can then test by grouping key being equal to st_main or xy_main, and if so add the extra nodes.

Try this template

<xsl:template match="section">
    <xsl:for-each-group select="p" group-starting-with="p[starts-with(@id, 'para')]">
        <section>
            <xsl:for-each-group select="current-group()" group-adjacent="@id">
                <xsl:if test="current-grouping-key() = 'st_main'">
                    <ss_start/>
                </xsl:if>
                <xsl:if test="current-grouping-key() = 'xy_main'">
                    <xy_start/>
                </xsl:if>
                <xsl:apply-templates select="current-group()"/>
                <xsl:if test="current-grouping-key() = 'st_main'">
                    <ss_end/>
                </xsl:if>
                <xsl:if test="current-grouping-key() = 'xy_main'">
                    <xy_end/>
                </xsl:if>
            </xsl:for-each-group>
        </section>
    </xsl:for-each-group>
</xsl:template>

Alternatively, to avoid too much hard-coding, you could just check for the id attribute ending in "_main" and create dynamic element names. Try this too:

<xsl:template match="section">
    <xsl:for-each-group select="p" group-starting-with="p[starts-with(@id, 'para')]">
        <section>
            <xsl:for-each-group select="current-group()" group-adjacent="@id">
                <xsl:if test="ends-with(current-grouping-key(), '_main')">
                    <xsl:element name="{substring-before(current-grouping-key(), '_')}_start" />
                </xsl:if>
                <xsl:apply-templates select="current-group()"/>
                <xsl:if test="ends-with(current-grouping-key(), '_main')">
                    <xsl:element name="{substring-before(current-grouping-key(), '_')}_end" />
                </xsl:if>
            </xsl:for-each-group>
        </section>
    </xsl:for-each-group>
</xsl:template>