Skip to content

Instantly share code, notes, and snippets.

@gimsieke
Created May 3, 2020 13:42
Show Gist options
  • Save gimsieke/56311eee455bf43b2f685e9cfa699c37 to your computer and use it in GitHub Desktop.
Save gimsieke/56311eee455bf43b2f685e9cfa699c37 to your computer and use it in GitHub Desktop.
XSLT problem raised by Michael Müller-Hillebrand on Mulberry’s xsl-list on 2020-05-03
<?xml version="1.0" encoding="UTF-8"?>
<div>
<h2 id="E2">Item with content to be joined follows div to collect</h2>
<div>
<ol data-meta="listlevel=start">
<li>
<p>1st item</p>
</li>
</ol>
<div class="box" data-meta="collect">
<p>Hint</p>
</div>
<ol data-meta="listlevel=continue">
<li data-meta="listitem=continue">
<p>Para ff</p>
</li>
<li>
<p>2nd item</p>
</li>
</ol>
<p>Other arbitrary content</p>
</div>
</div>
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0"
xmlns:my="http://localhost/"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="my xs">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="*[ol[@data-meta = 'listlevel=start']]">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<!-- named template: then you can call it recursively for lower list levels
(not used here) -->
<xsl:call-template name="collect">
<!-- We are grouping a) the list *items* in both special list types and
b) any other children of the current div. These a) and b) nodes are
returned by my:atomic-items() in document order: -->
<xsl:with-param name="nodes" select="my:atomic-items(.)"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:function name="my:atomic-items" as="node()*">
<xsl:param name="context" as="element()"/>
<xsl:variable name="special-lists" as="element(ol)*"
select="$context/(ol[@data-meta = ('listlevel=start', 'listlevel=continue')])" />
<xsl:sequence
select="$context/* except $special-lists | $special-lists/li"/>
</xsl:function>
<xsl:template name="collect">
<xsl:param name="nodes" as="node()*"/>
<!-- First grouping: Start with
ol[@data-meta = 'listlevel=start']/li[1] -->
<xsl:for-each-group select="$nodes"
group-starting-with="li[parent::ol[@data-meta = 'listlevel=start']]
[. is ../*[1]]">
<xsl:choose>
<!-- The first group can, in principle, start with any other node
that precedes the actual first list starting item. Such an
uninteresting initial group will be processed in otherwise. -->
<xsl:when test="parent::ol[@data-meta = 'listlevel=start']">
<!-- This is a grouping that may result in at most two groups:
The current list and anything non-collected and non-continuing
that might come after it (<p>Other arbitrary content</p>): -->
<xsl:for-each-group select="current-group()"
group-adjacent="exists(self::li)
or
@data-meta = 'collect'">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<!-- We re-create the surrounding ol: -->
<ol>
<!-- The context element is the first li
in an ol[@data-meta = 'listlevel=start'] -->
<xsl:copy-of select="../@data-meta"/>
<xsl:for-each-group select="current-group()"
group-starting-with="li[not(@data-meta =
'listitem=continue')]">
<!-- Create a new li: -->
<xsl:copy>
<xsl:copy-of select="@data-meta"/>
<xsl:apply-templates
select=" current-group()/self::li/node()
| current-group()[empty(self::li)]"/>
</xsl:copy>
</xsl:for-each-group>
</ol>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
<?xml version="1.0" encoding="UTF-8"?>
<div>
<h2 id="E2">Item with content to be joined follows div to collect</h2>
<div>
<ol data-meta="listlevel=start">
<li>
<p>1st item</p>
<div class="box" data-meta="collect">
<p>Hint</p>
</div>
<p>Para ff</p>
</li>
<li>
<p>2nd item</p>
</li>
</ol>
<p>Other arbitrary content</p>
</div>
</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment