I need to create HTML unordered lists from a flat XML structure using XSLT 1.0. The input XML consists of a series of nodes to be transformed into list items. However, this series may be interrupted by non-list nodes of different types:
<input>
<paragraph>abc</paragraph>
<paragraph>def</paragraph>
<listable>123</listable>
<listable>456</listable>
<other-block>
<other-text>Foo</other-text>
</other-block>
<listable>789</listable>
<listable>012</listable>
</input>
My objective is:
<div class="output">
<p>abc</p>
<p>def</p>
<ul>
<li>123</li>
<li>456</li>
</ul>
<div class="my-block">
<p class="other">Foo</p>
</div>
<ul>
<li>789</li>
<li>012</li>
</ul>
</div>
I found a old thread with a solution that almost works for me (last solution on the page, by Dimitre Novatchev) and adapted it. This is a minimal stylesheet based on that solution:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="utf-8" indent="yes" />
<xsl:strip-space elements="*" />
<!-- IDENTITY TRANSFORM: -->
<xsl:template match="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*" />
</xsl:copy>
</xsl:template>
<!-- NON-LIST ITEMS: -->
<xsl:template match="input">
<div class="output">
<xsl:apply-templates />
</div>
</xsl:template>
<xsl:template match="paragraph">
<p>
<xsl:apply-templates />
</p>
</xsl:template>
<xsl:template match="other-block">
<div class="my-block">
<xsl:apply-templates select="descendant::other-text" />
</div>
</xsl:template>
<xsl:template match="other-text">
<p class="other">
<xsl:copy-of select="text()" />
</p>
</xsl:template>
<!-- LIST HANDLING: -->
<xsl:key name="kFollowingUL" match="listable"
use="generate-id(preceding-sibling::*[not(self::listable)][1])"/>
<xsl:template match="*[not(self::listable) and following-sibling::*[1][self::listable]]">
<xsl:call-template name="identity" />
<xsl:variable name="vFolUL"
select="key('kFollowingUL',generate-id())"/>
<xsl:if test="$vFolUL">
<ul>
<xsl:apply-templates mode="copy"
select="key('kFollowingUL',generate-id())" />
</ul>
</xsl:if>
</xsl:template>
<xsl:template match="listable" mode="copy">
<li>
<xsl:value-of select="normalize-space()" />
</li>
</xsl:template>
<xsl:template match="listable" />
</xsl:stylesheet>
The problem with this approach is that it does not apply transformations to the last non-listable node before each list. The <paragraph>
and <other-block>
nodes in the input are copied directly to the output, although templates are applied to descendants of <other-block>
:
<div class="output">
<p>abc</p>
<paragraph>def</paragraph>
<ul>
<li>123</li>
<li>456</li>
</ul>
<other-block>
<p class="other">Foo</p>
</other-block>
<ul>
<li>789</li>
<li>012</li>
</ul>
</div>
Can anyone suggest a way to modify the earlier XSLT 1.0 solution and add transformation of the last nodes before the listable groups?