Re: [xsl] processing following-siblings to a point

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
On 9 Jul 2009, at 12:59 , charlieo0@xxxxxxxxxxx wrote:

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]".

- In 'move' mode, ignore all subtitle and para elements.

  - 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?)

HTH

Michael Sperberg-McQueen


-- **************************************************************** * C. M. Sperberg-McQueen, Black Mesa Technologies LLC * http://www.blackmesatech.com * http://cmsmcq.com/mib * http://balisage.net ****************************************************************

Current Thread