Reputation: 9150
Suppose I have the following sequence of elements:
<outer>
<e>…</e> <!-- Adjacent <e> should be grouped if they aren’t yet. -->
<e>…</e>
<group>
<e>…</e>
<e>…</e>
</group>
<e>…</e>
<e>…</e>
</outer>
And I’d like to consolidate those elements <e>
that have not yet been grouped, i.e. the output would be
<outer>
<group-foo> <!-- Grouped elements. -->
<e>…</e>
<e>…</e>
</group-foo>
<group-bar>
<e>…</e>
<e>…</e>
</group-bar>
<group-foo>
<e>…</e>
<e>…</e>
</group-foo>
</outer>
I just can’t quite figure out how to select a group of adjacent elements (a node set); closest idea was to select //e[name(parent::*) = 'outer']
or some such but that assumes a certain parent element and it returns a single node set, whereas I’d need two.
Upvotes: 1
Views: 111
Reputation: 243599
No recursion. Using keys (Muenchian grouping):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kE-ByPrecedingNonE" match="e[not(name(..) = 'group')]"
use="generate-id(preceding-sibling::*[not(self::e)][1])"/>
<xsl:template match="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="e[not(name(..) = 'group')]"/>
<xsl:template match=
"e[generate-id()
= generate-id(key('kE-ByPrecedingNonE',
generate-id(preceding-sibling::*[not(self::e)][1])
)[1]
)]">
<group-generated>
<xsl:apply-templates select=
"key('kE-ByPrecedingNonE',
generate-id(preceding-sibling::*[not(self::e)][1])
)" mode="inGroup"/>
</group-generated>
</xsl:template>
<xsl:template match="node()" mode="inGroup">
<xsl:call-template name="identity"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided source-xml document:
<outer>
<e>…</e>
<e>…</e>
<group>
<e>…</e>
<e>…</e>
</group>
<e>…</e>
<e>…</e>
</outer>
the wanted correct result is produced:
<outer>
<group-generated>
<e>…</e>
<e>…</e>
</group-generated>
<group>
<e>…</e>
<e>…</e>
</group>
<group-generated>
<e>…</e>
<e>…</e>
</group-generated>
</outer>
Explanation:
The identity rule (template) copies to the output every node as is. We also give it a name so that we can not only use it when applying templates, but also call it directly -- as we do in step 4. below.
A template matching any <e>
element that is not already inside a <group>
. This has no body -- we want to only handle such elements in our next templates -- not when the identity template selects it for execution.
A template that matches any <e>
element that is the first in a "non-covered" group. This uses the idea behind the Muenchian grouping method -- if you are unfamiliar with it, learn it well -- you won't be sorry. This template generates a wrapper element (called "group-generated") for the whole group and inside this wrapper applies templates to all <e>
s in the group in mode "inGroup" -- where these are simply copied.
The template in mode "inGroup" simply delegates to the identity rule to copy the selected node. Thus any matched node is copied as-is. Here, if we decide, we can perform any other required "inGroup" processing.
Upvotes: 0
Reputation: 117165
One way to solve this is to use a so-called sibling recursion:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/outer">
<xsl:copy>
<xsl:apply-templates select="e[not(preceding-sibling::*[1][self::e])] | group"/>
</xsl:copy>
</xsl:template>
<xsl:template match="e">
<group-foo>
<xsl:copy-of select="."/>
<!-- immediate sibling in the same group -->
<xsl:apply-templates select="following-sibling::*[1][self::e]" mode="collect" />
</group-foo>
</xsl:template>
<xsl:template match="e" mode="collect">
<xsl:copy-of select="."/>
<!-- immediate sibling in the same group -->
<xsl:apply-templates select="following-sibling::*[1][self::e]" mode="collect" />
</xsl:template>
<xsl:template match="group">
<group-bar>
<xsl:copy-of select="*"/>
</group-bar>
</xsl:template>
</xsl:stylesheet>
Upvotes: 1