Jeni Tennison
With this structure:
<firstitem>Item 1</firstitem>
<item>Second Text</item>
<item>blah blah</item>
<item>tee hee</item>
<firstitem>List 2, Item 1</firstitem>
<item>more text</item>
<firstitem>more lists</firstitem>
<item>test</item> into something like this (indenting is purely decorative):
<list>
<item>Item 1</item>
<item>Second Text</item>
<item>blah blah</item>
<item>tee hee</item>
</list>
<list>
<item>List 2, Item 1</item>
<item>more text</item>
</list>
<list>
<item>more lists</item>
<item>test</item>
</list>
> It seems to me that I can process each <firstitem> element
> and then apply templates to all of the following-siblings
> that have their first <firstitem>
> preceding-sibling equal to the current <firstitem>.
> But, a) I can't seem to get
> the syntax right for this solution,
> b) this seems like an odd way to solve the problem,
> and c) somebody probably knows an easier way. There are definitely other approaches (like you could use an xsl:for-each
rather than applying templates), but your approach is an eminently
reasonable one, and the biggest problem, the XPath expression, applies in
both, so let's see if we can get the syntax working. I don't know how far
you've got with it, so forgive me if I go through everything just in case! First you want to "process each <firstitem>
element". You do that with a template matching that kind of
element, so:
<xsl:template match="firstitem">
...
</xsl:template>
Second, and importantly, you *only* want to process those firstitem
elements. That means that the template that matches whatever element your
firstitems and items are children of has to only be applying templates to
the firstitem. I'll assume that they're children of an 'items' element: <xsl:template match="items">
<xsl:apply-templates select="firstitem" />
</xsl:template> Third, you want to "apply templates to all of the
following-siblings that have their first <firstitem>
preceding-sibling equal to the current <firstitem>".
Applying templates to a node list is easy: <xsl:apply-templates select="..." /> The thing that's difficult is generating the node
list! Let's take it a step at a time: 1. "all of the following-siblings..." Actually, you're not interested in *all* of the
following-sibling nodes, just in the 'item' elements. The
XPath for that is: following-sibling::item In David Carlisle's human-speak, this is "all
items that are following siblings". 2. "...that have..." Here you're narrowing the list you've got by
putting an extra condition on it, so you use a
predicate: following-sibling::item[...] 3. "...their first <firstitem> preceding-sibling..." or 'the first preceding-sibling that is a firstitem': preceding-sibling::firstitem[1] 4. "...the current <firstitem>..." This could be a little tricky because when we're
in the predicate that we're currently in, the *context node*
is an item. We want the *current node*, which we know is a
firstitem (because that's what this template is matching on)
and that we get using: current()
5. "...equal to..." Now we get into a bit of a murky area that I got confused by myself
recently. We want to make sure that the *node* that is 'the first
preceding-sibling that is a firstitem' is the same as the *node* that is
'the current node'. There are two ways of doing that: a. generate a unique identifier for the two nodes and compare them: generate-id(preceding-sibling::firstitem[1]) = generate-id(current()) b. make a set that is the union of the two nodes
and see how many elements are in it - if they're the same
node, then the answer is 1: count(preceding-sibling::firstitem[1] | current()) = 1 I'm going to use the first because it makes more
sense to me. We have our XPath expression:
following-sibling::item[generate-id(preceding-sibling::firstitem[1])
= generate-id(current())] So we slot that into our xsl:apply-templates, and put that into our
firstitem-matching template: <xsl:template match="firstitem">
...
<xsl:apply-templates
select="following-sibling::item[generate-id(preceding-sibling::firstitem[1])
= generate-id(current())]" />
...
</xsl:template>
What have we forgotten? Oh yes, the output! For each 'firstitem', we want
to have a 'list', with the first 'item' having the content of the
'firstitem' and the other 'item's being copies of the original ones.
<xsl:template match="firstitem">
<list>
<item><xsl:value-of select="." /></item>
<xsl:apply-templates
select="following-sibling::item[generate-id(preceding-sibling::firstitem[1])
= generate-id(current())]" />
</list>
</xsl:template>
<xsl:template match="item">
<xsl:copy-of select="." />
</xsl:template>
I've tested the full stylesheet (below) in SAXON and it works.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" />
<xsl:template match="items">
<xsl:apply-templates select="firstitem" />
</xsl:template>
<xsl:template match="firstitem">
<list>
<item><xsl:value-of select="." /></item>
<xsl:apply-templates
select="following-sibling::item
[generate-id(preceding-sibling::firstitem[1])
= generate-id(current())]" />
</list>
</xsl:template>
<xsl:template match="item">
<xsl:copy-of select="." />
</xsl:template>
</xsl:stylesheet>
|