Reputation: 1323
Given the XML:
<root>
<txto>Header text<txto>
<txt>more paragraph text</txt>
<txt>even more paragraph text</txt>
<txto>Another header<txto>
<txt> … </txt>
<txt> … </txt>
</root>
The desired output is having the first header define a section, while preserving the second:
<root>
<n1>
<o>Header text</o>
<txt>more paragraph text</txt>
<txt>even more paragraph text</txt>
</n1>
<txto>Another header<txto>
<txt> … </txt>
<txt> … </txt>
</root>
Using the XSL:
<xsl:template match=“//txto[starts-with(., ‘Header’)]”>
<n1>
<o><xsl:value-of select="."/></o>
<xsl:apply-templates select="following-sibling::txt" />
</n1>
</xsl:template>
However, the result is 'duplication' of the txt elements:
<root>
<n1>
<o>Header text</o>
<txt>more paragraph text</txt>
<txt>even more paragraph text</txt>
</n1>
<txt>more paragraph text</txt> <-- 'duplicated'
<txt>even more paragraph text</txt> <-- 'duplicated'
<txto>Another header<txto>
<txt> … </txt>
<txt> … </txt>
</root>
Which probably has to do with the Identity transform already having copied all txt nodes to the result tree:
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
How do I avoid copying the two 'duplicated' txt nodes to the result tree? I only want them inside the n1 node and not immediately after.
Upvotes: 1
Views: 48
Reputation: 116992
In XSLT 1.0 you could do something like:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="following-txt" match="txt" use="generate-id(preceding-sibling::txto[1])" />
<xsl:template match="/root">
<xsl:copy>
<xsl:apply-templates select="txto"/>
</xsl:copy>
</xsl:template>
<xsl:template match="txto[starts-with(., 'Header')]">
<n1>
<o>
<xsl:value-of select="." />
</o>
<xsl:copy-of select="key('following-txt', generate-id())"/>
</n1>
</xsl:template>
<xsl:template match="txto">
<xsl:copy-of select=". | key('following-txt', generate-id())"/>
</xsl:template>
</xsl:stylesheet>
Upvotes: 1
Reputation: 167516
In XSLT 2 or 3:
<xsl:template match="root">
<xsl:copy>
<xsl:for-each-group select="*" group-starting-with="txto[starts-with(., 'Header')]">
<xsl:choose>
<xsl:when test="position() = 1">
<n1>
<xsl:apply-templates select="current-group()"/>
</n1>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="txto[starts-with(., 'Header')][1]">
<o>
<xsl:apply-templates/>
</o>
</xsl:template>
That assumes there are several txto
elements starting with Header
, it is not quite sure why your code checks for that but your sample for the "second" header has an element <txto>Another header<txto>
that does not meet that condition.
Based on your comment, although I still think your requirement is clear, you might simply want a pattern group-starting-with="txto"
for the for-each-group
and a pattern match="txto[1]"
for the transformation template below.
In XSLT 1 an approach like
<xsl:key name="sibling-group"
match="root/*[not(self::txto)]"
use="generate-id(preceding-sibling::txto[1])"/>
<xsl:template match="@* | node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="root">
<xsl:copy>
<xsl:apply-templates select="txto"/>
</xsl:copy>
</xsl:template>
<xsl:template match="txto[1]">
<n1>
<o>
<xsl:apply-templates/>
</o>
<xsl:apply-templates select="key('sibling-group', generate-id())"/>
</n1>
</xsl:template>
<xsl:template match="txto[not(position() = 1)]">
<xsl:call-template name="identity"/>
<xsl:apply-templates select="key('sibling-group', generate-id())"/>
</xsl:template>
could work, again, the conditions might be adapted or enhanced to precisely select what a "header" is.
Upvotes: 2