Recipe5.3.Selecting Nodes by Context


Recipe 5.3. Selecting Nodes by Context

Problem

You want to select nodes that are bracketed by preceding and following nodes.

Solution

XSLT 1.0

There are several ways to solve this problem in XSLT 1.0, but the easiest to understand computes the position of the nodes that should be selected at each step. If you had the following unstructured document and you desired to select the paragraphs that are bracketed by headings, you can use this technique:

<doc>   <heading>Structure, I don't need any stinkin structure</heading>   <para>1.1</para>   <para>1.2</para>   <para>1.3</para>   <para>1.4</para>   <heading>Confessions of a flat earther</heading>   <para>2.1</para>   <para>2.2</para>   <para>2.3</para>   <heading>Flat hierarchies save trees!</heading>   <para>3.1</para>   <para>3.2</para> </doc>  <xsl:template match="/doc">     <xsl:copy>       <!-- First select the bracketing elements -->       <xsl:apply-templates select="heading"/>     </xsl:copy>  </xsl:template>   <!-- Match on the bracketing elements -->   <xsl:template match="heading">   <!-- Compute how many of the desired elements (para) follow this heading -->   <xsl:variable name="numFollowingPara" select="count(following-sibling::para)"/>      <!-- Compute how many of the desired elements (para) follow the next heading         and subtract from the preceding count to get the position of the last        para in this group-->   <xsl:variable name="lastParaInHeading"        select="$numFollowingPara -              count(following-sibling::heading[1]/following-sibling::para)"/>     <!-- You now can select the desired elements by their position relative to           the current heading -->        <xsl:apply-templates        select="following-sibling::para[position( ) &lt;= $lastParaInHeading]"/>   </xsl:template>

XSLT 2.0

This problem is tailor-made for the for-each-group instruction. Specifically, you would use the group-starting-with attribute:

<xsl:template match="/doc">   <xsl:copy>       <xsl:for-each-group select="*" group-starting-with="heading">         <!--Select the para elements in the group bracketed by heading -->         <xsl:apply-templates select="current-group( )[self::para]"/>        </xsl:for-each-group>                </xsl:copy> </xsl:template>

Discussion

Selecting nodes based on their position relative to other nodes is a common requirement in document-oriented XML transformations where structure is implied rather than literally encoded into the hierarchy of the document. Clearly, if each group consisting of a heading and paragraphs was contained in a separate parent element (for example, a section element), then the problem would be trivial. This is a classic trade-off between ease of use for the document creators vs. ease of use for the document transformers. With the introduction of for-each-group in XSLT 2.0, the trade-off equals out since you can much more easily deal with unstructured documents.

See Also

Recipe 8.8 shows applications of this technique for transforming implicitly structured documents into explicitly structured ones. It also shows other ways the problem can be approached in XSLT 1.0 and 2.0.




XSLT Cookbook
XSLT Cookbook: Solutions and Examples for XML and XSLT Developers, 2nd Edition
ISBN: 0596009747
EAN: 2147483647
Year: 2003
Pages: 208
Authors: Sal Mangano

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net