wrap sibling nodes based on on attribute
Using XSLT, how can I wrap siblings that share the same value for an attribute.
Lets say I need to wrap a one or more <amendment/>
with the <chapter/>
they belong too.
From this:
<section>
<heading>some heading text</heading>
<amendment num='1' chapter='1'>
<foo/>
</amendment>
<amendment num='2' chapter='1'>
<bar/>
</amendment>
<amendment num='3' chapter='2'>
<baz/>
</amendment>
<heading>some heading text</heading>
<amendment num='4' chapter='3'&开发者_如何学JAVAgt;
<baz/>
</amendment>
</section>
into this:
<section>
<heading>some heading text</heading>
<chapter num="1">
<amendment num='1'>
<foo/>
</amendment>
<amendment num='2'>
<bar/>
</amendment>
</chapter>
<chapter num="2">
<amendment num='3'>
<baz/>
</amendment>
</chapter>
<heading>some heading text</heading>
<chapter num="3">
<amendment num='4'>
<baz/>
</amendment>
</chapter>
</section>
Note 1: Amendments are always listed sorted by chapter in the source XML.
Note 2: Im using PHP5 with XSLT 1.0
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="kAmendementByChapter" match="amendment" use="@chapter"/>
<xsl:template match="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="amendment[count(.|key('kAmendementByChapter',
@chapter)[1])=1]">
<chapter num="{@chapter}">
<xsl:apply-templates select="key('kAmendementByChapter',@chapter)"
mode="copy"/>
</chapter>
</xsl:template>
<xsl:template match="amendment"/>
<xsl:template match="amendment" mode="copy">
<xsl:call-template name="identity"/>
</xsl:template>
<xsl:template match="@chapter"/>
</xsl:stylesheet>
Output:
<section>
<heading>some heading text</heading>
<chapter num="1">
<amendment num="1">
<foo></foo>
</amendment>
<amendment num="2">
<bar></bar>
</amendment>
</chapter>
<chapter num="2">
<amendment num="3">
<baz></baz>
</amendment>
</chapter>
<heading>some heading text</heading>
<chapter num="3">
<amendment num="4">
<baz></baz>
</amendment>
</chapter>
</section>
Note: Copy all (indentity rule), grouping on @chapter.
If you're using XSLT 1, you can use the Muenchian grouping method like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="chapter" use="@chapter" match="amendment" />
<xsl:template match="section">
<xsl:copy>
<xsl:apply-templates select="heading | amendment[generate-id() = generate-id(key('chapter',@chapter)[1])]" />
</xsl:copy>
</xsl:template>
<xsl:template match="amendment">
<xsl:element name="chapter">
<xsl:attribute name="num">
<xsl:value-of select="@chapter" />
</xsl:attribute>
<xsl:apply-templates select="key('chapter', @chapter)" mode="withoutchapter"/>
</xsl:element>
</xsl:template>
<xsl:template match="amendment" mode="withoutchapter">
<xsl:copy>
<xsl:apply-templates select="@*[(name() != 'chapter')] | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
There's two 'amendment' templates here- the first one (without the mode) is only invoked by the section template on amendments that are the first occurrence of an amended with that chapter. It creates a chapter element, and within it invokes the second template on each amendment
tag with that chapter.
Two caveats here; First, any amendments without a chapter will be dropped from the output.
Secondly, where there is a heading in between two amendment tags, the amendment tags will still be grouped, and the heading will appear after the group.
So, if you do (abbreviated for clarity):
<amendment num='1' chapter='1' />
<heading>heading text</heading>
<amendment num='2' chapter='1' />
It'll output:
<chapter num='1'>
<amendment num='1' />
<amendment num='2' />
</chapter>
<heading>heading text</heading>
Because your amendments are sorted by chapter, you can get away with not using a key, an instead just looking at the immediately following and preceding elements. The following should work:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="amendment[not(@chapter = preceding-sibling::amendment[1]/@chapter)]">
<chapter num="{@chapter}">
<xsl:variable name="chapter" select="@chapter"/>
<amendment num="{@num}">
<xsl:apply-templates/>
</amendment>
<xsl:apply-templates select="following-sibling::amendment[1][@chapter = $chapter]">
<xsl:with-param name="chapter" select="@chapter"/>
</xsl:apply-templates>
</chapter>
</xsl:template>
<xsl:template match="amendment">
<xsl:param name="chapter"/>
<xsl:if test="$chapter">
<amendment num="{@num}">
<xsl:apply-templates/>
</amendment>
<xsl:apply-templates select="following-sibling::amendment[1][@chapter = $chapter]">
<xsl:with-param name="chapter" select="$chapter"/>
</xsl:apply-templates>
</xsl:if>
</xsl:template>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Thanks to @Alejandro and @Flynn1179 for noticing an error in the initial version of this solution -- now corrected!
This transformation:
<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="kbyChapter" match="amendment"
use="@chapter"/>
<xsl:template match="node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:apply-templates select="node()[1]"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="amendment"/>
<xsl:template match=
"amendment[not(@chapter=preceding-sibling::amendment[1]/@chapter)]">
<chapter num="{@chapter}">
<xsl:apply-templates select="key('kbyChapter',@chapter)" mode="copy"/>
</chapter>
<xsl:apply-templates select=
"key('kbyChapter',@chapter)[last()]/following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="*" mode="copy">
<xsl:copy>
<xsl:apply-templates select="@*|node()[1]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="@*">
<xsl:copy-of select="."/>
</xsl:template>
<xsl:template match="@chapter"/>
</xsl:stylesheet>
when applied on the provided XML document (with a correction on the last amendment
):
<section>
<heading>some heading text</heading>
<amendment num='1' chapter='1'>
<foo/>
</amendment>
<amendment num='2' chapter='1'>
<bar/>
</amendment>
<amendment num='3' chapter='2'>
<baz/>
</amendment>
<heading>some heading text</heading>
<amendment num='4' chapter='3'>
<baz/>
</amendment>
</section>
produces the wanted, correct result:
<section>
<heading>some heading text</heading>
<chapter num="1">
<amendment num="1">
<foo/>
</amendment>
<amendment num="2">
<bar/>
</amendment>
</chapter>
<chapter num="2">
<amendment num="3">
<baz/>
</amendment>
</chapter>
<heading>some heading text</heading>
<chapter num="3">
<amendment num="4">
<baz/>
</amendment>
</chapter>
</section>
精彩评论