I have xml like
<tr>
<td class="x">1</td>
<td class="x">2</td>
<td>3</td>
<td class="x">4</td>
<td class="x">5</td>
<td class="x">6</td>
<td class="x">7</td>
</tr>
and I want the result to be using xsl is:
\cont{1-2}
\cont{4-7}
can I do this?
This transformation is both short (25 lines) and efficient (uses keys):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kFollowing" match="td[@class='x']"
use="concat(generate-id(..),
'+',generate-id(preceding-sibling::*
[not(self::td and @class='x')][1])
)"/>
<xsl:template match="/*">
<xsl:variable name="vGroup"
select="key('kFollowing', concat(generate-id(),'+'))"/>
<xsl:value-of select=
"concat('\cont{{',$vGroup[1],'-',$vGroup[last()],'}}','
')"/>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="*/*">
<xsl:variable name="vGroup" select=
"key('kFollowing', concat(generate-id(..),'+', generate-id()))"/>
<xsl:value-of select=
"concat('\cont{{',$vGroup[1],'-',$vGroup[last()],'}}','
')"/>
</xsl:template>
<xsl:template match="td[@class='x']|text()"/>
</xsl:stylesheet>
When applied on the provided XML document:
<tr>
<td class="x">1</td>
<td class="x">2</td>
<td>3</td>
<td class="x">4</td>
<td class="x">5</td>
<td class="x">6</td>
<td class="x">7</td>
</tr>
the wanted, correct result is produced:
\cont{1-2}
\cont{4-7}
There are nice ways of doing it, and there are nasty ways of doing it. I haven't got enough time to think of a nice way, how does this grab you?:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ext="http://exslt.org/common">
<xsl:output method="text" indent="no"/>
<xsl:template match="/tr">
<xsl:for-each select="td[@class='x']">
<xsl:variable name="currentNum" select="number(text())"/>
<xsl:variable name="prevNum" select="number(preceding-sibling::td[@class='x'][1]/text())"/>
<xsl:if test="($currentNum - 1) != $prevNum and $currentNum > 0 and following-sibling::td[@class='x']">
<xsl:variable name="following" select="following-sibling::td[@class='x']"/>
<xsl:if test="number($following[1]/text()) = ($currentNum + 1)">
\cont{<xsl:value-of select="$currentNum"/>-<xsl:call-template name="findend">
<xsl:with-param name="start" select="$currentNum"/>
<xsl:with-param name="nodes" select="$following"/>
</xsl:call-template>}
</xsl:if>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="findend">
<xsl:param name="nodes"/>
<xsl:param name="start"/>
<xsl:variable name="current" select="number($nodes[1]/text())"/>
<xsl:choose>
<xsl:when test="count($nodes) < 2">
<xsl:value-of select="$current"/>
</xsl:when>
<xsl:when test="number($nodes[2]/text()) != ($current + 1)">
<xsl:value-of select="$current"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="findend">
<xsl:with-param name="nodes" select="$nodes[position() > 1]"/>
<xsl:with-param name="start" select="$current"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
output is
\cont{1-2}
\cont{4-7}
I made the assumption that if you only had one number you didn't want a continue, so you don't get \cont{1-1}, and that they had to be in sequence and that they have to be in class='x'.
With a bit more time, you could come up with something prettier!
As an explanation though, it goes through looking for a td[@class='x'] whos number doesn't equal the previous one + 1 (ie, there was no number or it wasn't contiguous). If it finds one, it checks the next number is this number + 1, to avoid the case of the cont{1-1}, then it calls a recursive template that looks for and prints the end
Here is some XSLT 1.0 approach using a key and modes (but those mainly to avoid complex template match patterns):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="k1"
match="td[@class = 'x'][preceding-sibling::*[1][self::td[@class = 'x']]]"
use="generate-id(
preceding-sibling::td[
@class = 'x' and
not(preceding-sibling::*[1][self::td[@class = 'x']])
][1]
)"/>
<xsl:template match="tr">
<xsl:apply-templates select="td[@class = 'x' and not(preceding-sibling::*[1][self::td[@class = 'x']])]" mode="start"/>
</xsl:template>
<xsl:template match="td[@class = 'x']" mode="start">
<xsl:text>\cont{</xsl:text>
<xsl:value-of select="."/>
<xsl:text>-</xsl:text>
<xsl:apply-templates select="key('k1', generate-id())[last()]" mode="end"/>
</xsl:template>
<xsl:template match="td[@class = 'x']" mode="end">
<xsl:value-of select="."/>
<xsl:text>} </xsl:text>
</xsl:template>
</xsl:stylesheet>
That stylesheet, when applied on
<tr>
<td class="x">1</td>
<td class="x">2</td>
<td>3</td>
<td class="x">4</td>
<td class="x">5</td>
<td class="x">6</td>
<td class="x">7</td>
</tr>
outputs
\cont{1-2}
\cont{4-7}
The simplest stylesheet I could come up with that solves your problem:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="td[not(preceding-sibling::*[1][self::td][@class='x'])]">
<xsl:text>\cont{</xsl:text>
<xsl:value-of select="."/>
<xsl:text>-</xsl:text>
<xsl:value-of select="following-sibling::td[
not(following-sibling::*[1][self::td][@class='x'])][1]"/>
<xsl:text>} </xsl:text>
</xsl:template>
<xsl:template match="td"/>
</xsl:stylesheet>
Explanation: First, we match every td
whose immediately preceding sibling is not itself a td
with a class
attribute equal to x
. These are the start nodes, so we use their values as the start of each sequence.
For the end of the sequence we use the next td
that does not have as its immediately following sibling another td
whose class
is x
.
All other nodes are ignored.