This was on [www-xsl-fo(_at_)w3(_dot_)org], somebody suggested I put it on this
list. Enjoy. (xsl:fo is used, but not an issue to the problem.)
Here's a (hopefully) interesting problem: (I've been editing this
email all day. Every time I can't figure out how to do something, I
come up with an alternate plan, assisted by a few people in #xml on
irc.freenode.net. (Thank you Bebabo). I figured at this point I'd
share my experiences. If you can think of better, faster, cleaner ways
to do this, I'd love to hear from you.)
source xml:
<document>
<field>
<title>a</title>
</field>
<field>
<title>b</title>
<value/>
<field>
<title>c</title>
<value/>
</field>
<field>
<title>d</title>
</field>
<field>
<title>e</title>
<value/>
</field>
</document>
What I want to happen is basically, for-each select="field", if
<field> has a <value/> and the next <field> also has a value, have 4
cells in a row: field[n]/title, text , field[n+1]/title, 'text'. If
<field> does not have a <value/> or if the next <field> does not have
a <value/>, then have 2 cells: field[n]/title, and 'other text' with
number-columns-spanned=3.
My first thought was this:
/-----------------------------------------------------------------------------------------\
. . .
<xsl:variable name="col" select="''"/>
<xsl:table-body>
<xsl:for-each select="field">
<xsl:choose>
<xsl:when test="value and not($col)">
<fo:table-row>
<fo:table-cell><xsl:value-of select="title"/></fo:table-cell>
<fo:table-cell>text</fo:table-cell>
<xsl:variable name="col" select="'true'"/>
</xsl:when>
<xsl:when test="value and $col">
<fo:table-cell><xsl:value-of select="title"/></fo:table-cell>
<fo:table-cell>text</fo:table-cell>
</fo:table-row>
<xsl:variable name="col" select="''"/>
</xsl:when>
<xsl:when test="not(value) and not($col)">
<fo:table-row>
<fo:table-cell><xsl:value-of select="title"/></fo:table-cell>
<fo:table-cell>other text</fo:table-cell>
<fo:table-cell number-columns-spanned="2"/>
</fo:table-row>
</xsl:when>
<xsl:when test="not(value) and $col">
<fo:table-cell number-columns-spanned="2"/>
</fo:table-row>
<fo:table-row>
<fo:table-cell><xsl:value-of select="title"/></fo:table-cell>
<fo:table-cell>other text</fo:table-cell>
<fo:table-cell number-columns-spanned="2"/>
</fo:table-row>
<xsl:variable name="col" select="''"/>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</fo:table-body>
\---------------------------------------------------------------------------------------/
This obviously will not work, because XSLT does not allow variables to
be reassigned. After reading extensively from O'Reilly's XSLT book
and the w3c specs for XSLT and XPath, I decided I could achive my goal
with recursive template matches. Here is what I arrived at:
/----------------------------------------------------------------------------------------\
<xsl:stylesheet . . .>
<xsl:template match="document">
. . .
<fo:table-body>
<xsl:apply-templates select="field[1]">
</fo:table-body>
. . .
</xsl:template> <!-- end template match="document" -->
<xsl:template match="field">
<xsl:choose>
<!-- When current <field> has <value/> and next <field>
has <value/> -->
<xsl:when test="value and following-sibling::field[1]/value">
<fo:table-row>
<fo:table-cell>
<!-- current title -->
<fo:block><xsl:value-of select="title"/></fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>text</fo:block>
</fo:table-cell>
<fo:table-cell>
<!-- next title -->
<fo:block><xsl:value-of
select="following-sibling::field[1]/title"/></fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>text</fo:block>
</fo:table-cell>
</fo:table-row>
<!-- use this template for the next element (after the
one previously referred
to as 'next'. omitting the final '[1]' will
cause this template to be processed
remaining <field>, which results in very long
files with lots of stuff i don't
want. -->
<xsl:apply-templates
select="following-sibling::field[position() > 1][1]"/>
</xsl:when>
<!-- if not. . . -->
<xsl:otherwise>
<fo:table-row>
<fo:table-cell>
<fo:block><xsl:value-of select="title"/></fo:block>
</fo:table-cell>
<fo:table-cell number-columns-spanned="3">
<fo:block>other text</fo:block>
</fo:table-cell>
</fo:table-row>
<!-- use this template for the next element. again,
don't omit '[1]'. -->
<xsl:apply-templates select="following-sibling::field[1]"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
\------------------------------------------------------------------------------------/
This works great. Based on my understanding of the XSLT parsers, this
is pretty memory intensive, as applying "following-sibling::field[1]"
actually passes the remaining <field> nodes in <document> every time,
leaving a lot of stuff in the stack. Is that correct?
At any rate, any ideas on a better way to do this? (Other than a SAX
parsing of the XML with a few variables and leaving an extra node in
select <field>s.)
--~------------------------------------------------------------------
XSL-List info and archive: http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: <mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--