XSLT: Select following-sibling until reaching a specified tag
The accepted answer has a bad side effect and it is kinda wrong.
Further in this post, i will explain the real compare of the following essential statement and why it can and will fail.
recap/analyse situation while being in template <xsl:template match="h1">
:
- Current context node is any
h1
from the matching<xsl:template>
. - variable named
header
contains a duplicate of my current context node.
The essential statement which is bad/wrong:
following-sibling::p[preceding-sibling::h1[1] = $header]
- select all following siblings
p
of my context node |following-sibling::p
- filter these
p
where the first (closest) preceding-sibling namedh1
"is" the same as the variable$header
|...[preceding-sibling::h1[1] = $header]
.
!! In XSLT 1.0 the compare of a node with a node will be done by its value !!
See it in an example. Lets pretend the input xml is like this [<h1>
contain twice the same value Test
]:
<html>
<h1>Test</h1>
<p>Test: p 1</p>
<p>Test: p 2</p>
<h1>Test</h1>
<p>Test2: p 1</p>
<p>Test2: p 2</p>
<p>Test2: p 3</p>
</html>
A !WRONG! result will be created:
<content>
<section>
<sectionHeading>Test</sectionHeading>
<sectionContent>
<paragraph>Test: p 1</paragraph>
<paragraph>Test: p 2</paragraph>
<paragraph>Test2: p 1</paragraph> <-- should be only in 2. section
<paragraph>Test2: p 2</paragraph> <-- should be only in 2. section
<paragraph>Test2: p 3</paragraph> <-- should be only in 2. section
</sectionContent>
</section>
<section>
<sectionHeading>Test</sectionHeading>
<sectionContent>
<paragraph>Test2: p 1</paragraph>
<paragraph>Test2: p 2</paragraph>
<paragraph>Test2: p 3</paragraph>
</sectionContent>
</section>
</content>
Correct compare
...
<xsl:template match="h1">
<xsl:variable name="header" select="generate-id(.)"/>
<section>
<sectionHeading>
<xsl:apply-templates/>
</sectionHeading>
<sectionContent>
<xsl:for-each select="following-sibling::p[generate-id(preceding-sibling::h1[1]) = $header]">
<paragraph>
<xsl:value-of select="."/>
</paragraph>
</xsl:for-each>
</sectionContent>
</section>
</xsl:template>
...
Use the function generate-id() to get the unique (at least in the current document) ID of a node and compare now node vs node! Even if you use this technique with <xsl:key>
, you have to use generate-id()
.
Try this: (Instead of asking for all the p's we ask for all the p's whose most recently preceding h1 is current.)
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<content>
<xsl:apply-templates/>
</content>
</xsl:template>
<xsl:template match="h1">
<xsl:variable name="header-id" select="generate-id(.)"/>
<section>
<sectionHeading>
<xsl:apply-templates/>
</sectionHeading>
<sectionContent>
<xsl:for-each select="following-sibling::p[generate-id(preceding-sibling::h1[1]) = $header-id]">
<paragraph>
<xsl:value-of select="."/>
</paragraph>
</xsl:for-each>
</sectionContent>
</section>
</xsl:template>
<xsl:template match="p"/>
</xsl:stylesheet>