I am attempting to write XSLT that will select immediate-siblings of a certain type, but stop when a different tag is reached.
Here's the Source XML:
<?xml version="1.0"?>
<body>
<proc>Test</proc>
<alert>Test1: alert 1</alert>
<alert>Test1: alert 2</p>
<para>Test para 1</para>
<alert>Test2: alert 1</alert>
<alert>Test2: alert 2</alert>
<alert>Test2: alert 3</alert>
<proc>Test</proc>
<alert>Test3: alert 1</alert>
<alert>Test3: alert 2</alert>
<alert>Test3: alert 3</alert>
</html>
Here's the expected result:
<?xml version="1.0" encoding="UTF-8"?>
<body>
<proc>
<alert>Test1: alert 1</alert>
<alert>Test1: alert 2</alert>
</proc>
<para>Test para 1</para>
<alert>Test2: alert 1</alert>
<alert>Test2: alert 2</alert>
<alert>Test2: alert 3</alert>
<proc>
<alert>Test3: alert 1</alert>
<alert>Test3: alert 2</alert>
<alert>Test3: alert 3</alert>
</proc>
</body>
is this even possible?
Here's my current xsl which isn't doing the trick:
<xsl:template match="proc">
<xsl:variable name="procedure" select="."/>
<xsl:apply-templates/>
<xsl:for-each
select="following-sibling::alert[preceding-sibling::proc[1] = $procedure]">
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:template>
<xsl:template match="c:hhtAlert">...</xsl:template>
<xsl:template match="c:hhtPara">...</xsl:template>
Here is an efficient and quite short XSLT 1.0 solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kFollowing" match="alert"
use="generate-id(preceding-sibling::*
[
self::proc
or
self::para
]
[1]
[self::proc]
)"/>
<xsl:template match="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="proc">
<proc>
<xsl:apply-templates select="key('kFollowing', generate-id())" mode="copy"/>
</proc>
</xsl:template>
<xsl:template match="alert" mode="copy">
<xsl:call-template name="identity"/>
</xsl:template>
<xsl:template match="alert[preceding-sibling::*
[
self::proc
or
self::para
]
[1]
[self::proc]
]"
/>
</xsl:stylesheet>
when this transformation is applied on the provided XML document (corrected to be well-formed):
<body>
<proc>Test</proc>
<alert>Test1: alert 1</alert>
<alert>Test1: alert 2</alert>
<para>Test para 1</para>
<alert>Test2: alert 1</alert>
<alert>Test2: alert 2</alert>
<alert>Test2: alert 3</alert>
<proc>Test</proc>
<alert>Test3: alert 1</alert>
<alert>Test3: alert 2</alert>
<alert>Test3: alert 3</alert>
</body>
the wanted result is produced:
<body>
<proc>
<alert>Test1: alert 1</alert>
<alert>Test1: alert 2</alert>
</proc>
<para>Test para 1</para>
<alert>Test2: alert 1</alert>
<alert>Test2: alert 2</alert>
<alert>Test2: alert 3</alert>
<proc>
<alert>Test3: alert 1</alert>
<alert>Test3: alert 2</alert>
<alert>Test3: alert 3</alert>
</proc>
</body>
Do note: The use of keys makes this transformation many times faster (in case of many alert
siblings) than using the preceding-sibling::
or following-sibling::
axes.
If you are using XSLT 2 something like the following should work:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="body">
<xsl:copy>
<xsl:for-each-group select="*" group-starting-with="para | proc">
<xsl:choose>
<xsl:when test="self::para">
<xsl:apply-templates select="current-group()"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="current-group()[position() > 1]"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Besides Dimitre's good answer (probably the clasic solution for this kind of problems), this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()[1]|@*"/>
</xsl:copy>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="proc">
<proc>
<xsl:apply-templates select="following-sibling::node()[1]"
mode="open"/>
</proc>
<xsl:apply-templates select="following-sibling::node()
[not(self::alert)][1]"/>
</xsl:template>
<xsl:template match="node()" mode="open"/>
<xsl:template match="alert" mode="open">
<xsl:copy-of select="."/>
<xsl:apply-templates select="following-sibling::node()[1]"
mode="open"/>
</xsl:template>
</xsl:stylesheet>
Output:
<body>
<proc>
<alert>Test1: alert 1</alert>
<alert>Test1: alert 2</alert>
</proc>
<para>Test para 1</para>
<alert>Test2: alert 1</alert>
<alert>Test2: alert 2</alert>
<alert>Test2: alert 3</alert>
<proc>
<alert>Test3: alert 1</alert>
<alert>Test3: alert 2</alert>
<alert>Test3: alert 3</alert>
</proc>
</body>
Note: This use fine grained trasversal.
Edit: Better pattern matching with modes.