Reputation: 221210
I can't think of a simple and elegant way to find "previous" and "next" elements in an XML document using XPath or a simple XSLT template. Here's a sample XML document (in a real document, the @id
's wouldn't be ordered so simply)
<manual>
<section id="1">
<section id="2">
<section id="3"/>
<section id="4"/>
<section id="5"/>
</section>
<section id="6">
<section id="7"/>
<section id="8"/>
<section id="9"/>
</section>
</section>
<section id="10"/>
<section id="11">
<section id="12"/>
</section>
</manual>
And here's what I mean by previous / subsequent in document order
+------------------+------------------+--------------+
| Selected section | Previous section | Next section |
+------------------+------------------+--------------+
| 1 | none | 2 |
| 2 | 1 | 3 |
| 3 | 2 | 4 |
| 4 | 3 | 5 |
| 5 | 4 | 6 |
| ... | ... | ... |
| 10 | 9 | 11 |
| 11 | 10 | 12 |
| 12 | 11 | none |
+------------------+------------------+--------------+
The problem with the preceding::
axis is that ancestors are excluded, i.e. section[id=2]
is not a preceding node of section[id=3]
.
In the same way, the following::
axis excludes descendants, i.e. section[id=3]
is not a following node for section[id=2]
.
So how could I produce "previous" and "next" elements e.g. from these templates:
<xsl:template match="section" mode="prev">
<xsl:value-of select="... what to put here ..."/>
</xsl:template>
<xsl:template match="section" mode="next">
<xsl:value-of select="... what to put here ..."/>
</xsl:template>
Note, this is a similar but not the same question here: XPath 1.0 closest preceding and/or ancestor node with an attribute in a XML Tree. These XPath constructions are really over my head, sometimes...
Upvotes: 1
Views: 6731
Reputation: 221210
A "not so elegant" implementation would be this (selecting @id
and not the whole node)
<xsl:template match="section" mode="prev-id">
<xsl:variable name="id" select="@id"/>
<xsl:variable name="position">
<xsl:for-each select="//section">
<xsl:if test="@id = $id">
<xsl:value-of select="position()"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="//section">
<xsl:if test="position() = $position - 1">
<xsl:value-of select="@id"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template match="section" mode="next-id">
<xsl:variable name="id" select="@id"/>
<xsl:variable name="position">
<xsl:for-each select="//section">
<xsl:if test="@id = $id">
<xsl:value-of select="position()"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="//section">
<xsl:if test="position() = $position + 1">
<xsl:value-of select="@id"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
Upvotes: 0
Reputation: 167716
Here is a stylesheet that incorporates Nicholas suggestion with the union, then taking the last()
for the previous item and the first for the next item:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="html" indent="yes"/>
<xsl:template match="manual">
<table>
<thead>
<tr>
<th>Selected section</th>
<th>Previous section</th>
<th>Next section</th>
</tr>
</thead>
<tbody>
<xsl:apply-templates select="descendant::section"/>
</tbody>
</table>
</xsl:template>
<xsl:template match="section">
<tr>
<td>
<xsl:value-of select="@id"/>
</td>
<xsl:apply-templates select="." mode="prev"/>
<xsl:apply-templates select="." mode="next"/>
</tr>
</xsl:template>
<xsl:template match="section" mode="prev">
<td>
<xsl:value-of select="(preceding::section | ancestor::section)[last()]/@id"/>
</td>
</xsl:template>
<xsl:template match="section" mode="next">
<td>
<xsl:value-of select="(following::section | descendant::section)[1]/@id"/>
</td>
</xsl:template>
</xsl:stylesheet>
With your sample input Saxon 6.5.5 outputs
<table>
<thead>
<tr>
<th>Selected section</th>
<th>Previous section</th>
<th>Next section</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td></td>
<td>2</td>
</tr>
<tr>
<td>2</td>
<td>1</td>
<td>3</td>
</tr>
<tr>
<td>3</td>
<td>1</td>
<td>4</td>
</tr>
<tr>
<td>4</td>
<td>1</td>
<td>5</td>
</tr>
<tr>
<td>5</td>
<td>1</td>
<td>6</td>
</tr>
<tr>
<td>6</td>
<td>1</td>
<td>7</td>
</tr>
<tr>
<td>7</td>
<td>1</td>
<td>8</td>
</tr>
<tr>
<td>8</td>
<td>1</td>
<td>9</td>
</tr>
<tr>
<td>9</td>
<td>1</td>
<td>10</td>
</tr>
<tr>
<td>10</td>
<td>1</td>
<td>11</td>
</tr>
<tr>
<td>11</td>
<td>1</td>
<td>12</td>
</tr>
<tr>
<td>12</td>
<td>1</td>
<td></td>
</tr>
</tbody>
</table>
Upvotes: 1