Subject: Re: [xsl] processing following-siblings to a point From: "C. M. Sperberg-McQueen" <cmsmcq@xxxxxxxxxxxxxxxxx> Date: Thu, 9 Jul 2009 15:24:45 -0600 |
Hello all,...
I've run into a structure that I must transform that I just haven't found a solution for. XSL 1.0 only. (I just don't have the proficiency for 2.0 yet).
I'm transforming what was a rather flat SGML structure to an XML structure. Here's the input structure:
Resulting structure tree: ...
My template (or at least the important part).
<xsl:template match="gen/title" mode="move">
<para0>
<title>
<xsl:value-of select="."/>
</title>
<subpara1>
<title><xsl:value-of select="following- sibling::subtitle[1]"/></title>
<xsl:apply-templates select="following-sibling::para"/>
</subpara1>
</para0>
My problem is with the <xsl:apply-templates select="following- sibling::para"/> line. Of course, all of the para elements are following siblings of all the gen/title elements. How can I express the <apply-templates> to process only the para element siblings BEFORE the next following title sibling. (This is difficult because EVERYTHING is a sibling of everything else).
As Ken Holman has already suggested, this is the kind of thing that many users will use to push themselves over the threshold of using XSLT 2.0. Don't let me discourage you from that.
On the other hand, this is a simple instance of a more general situation, which is: you want a non-standard flow of control / a non-standard order for processing the elements of the input. And there is a general pattern for solutions which is worth learning.
There may be clever predicates you can write, to keep the structure of your template as it is. You want all the paragraphs which have the same first right-sibling title as the title you are processing. So you might try something like
<!--* NOT tested! *--> <xsl:variable name="next_title" select="generate-id(./following-sibling::title)"/> <xsl:apply-templates select="following-sibling::para [generate-id(./following-sibling::title) = $next_title]"/>
When I face problems like this, I typically spend a little while trying to make the select expression work, and handle the corner cases (what if there is no following title because this is the last one? Oh, yeah, wrap it in a choose ...).
If after a while I haven't made it work, I move to the manual option, which is well worth learning. All you want to do is
- In 'move' mode, for each title, open the 'para0' element and make the 'title' element. Then process the following sibling (which will become the first virtual child of the new wrapper) in a new mode which I'll call virtual-child mode. So your 'apply-templates' will have select="following-sibling::*[1]".
- For subtitles in 'virtual-child' mode, open the 'subpara1' element, emit the 'title' element, call apply-templates on the first following sibling in virtual-child mode, and then close 'subpara1'.
- For para elements in virtual-child mode, emit the appropriate output 'para' element, then call apply-templates on the first following sibling. If that sibling is a para, it will produce yet another child of 'subpara1'.
- For title elements in virtual-child mode, do nothing. In particular, do not process children or following siblings. You get to a title element in virtual-child mode only by being called recursively from the preceding sibling, seeking further virtual children for the most recently opened subpara1. But when the next title is found, that subpara1 is *over*. So do nothing. This ends the recursion, and control returns eventually to the template for the subtitle, and then to the template for the (preceding) title. And then the title you just visited without doing anything will be handled in 'move' mode by the template described above.
I'd provide a full solution in XSLT, but that would deprive you of the pleasure of working out the pattern for yourself.
For many people used to XSLT, calling apply-templates with a select that matches exactly one element feels remarkably like suddenly dropping down into assembly language: it feels procedural and a bit suspect. I'm not sure that it really is capital-P procedural (and if it is, I'll blame the designer of the input format! Unnatural input formats produce unnatural XSLT. So your hands are clean!) -- but the ability to traverse the tree with more or less complete control over what comes next can make this style of control flow very handy. (If you're like me, you may find it helpful to write a few procedures in Lisp to remind yourself that what seems clunky and unusual here is just the explicit instruction about what to do next; in Lisp, it feels perfectly normal -- why should we feel guilty about doing it in XSLT?)
-- **************************************************************** * C. M. Sperberg-McQueen, Black Mesa Technologies LLC * http://www.blackmesatech.com * http://cmsmcq.com/mib * http://balisage.net ****************************************************************
Current Thread |
---|
|
<- Previous | Index | Next -> |
---|---|---|
Re: [xsl] processing following-sibl, G. Ken Holman | Thread | RE: [xsl] processing following-sibl, Michael Kay |
Re: [xsl] Elegant way to create an , Stuart A. Yeates | Date | Re: [xsl] Difference between xmlns:, C. M. Sperberg-McQue |
Month |