Sorting XML using XSLT, while keeping multiple comments
I'm trying to sort an XML document using XSLT, and want to keep the comments. So far so good, since there are some answers to this question already (see related..). But! none of these (excellent) answers relate to an XML that looks like this:
<xml>
<beatles>
<!-- comment(1): john is actually my favourite -->
<!-- comment(2): John died tragically in 1980 -->
<beatle name="John"/>
<beatle name="Ringo"/>
<beatle name="George"/>
<!-- comment(1): Paul still does live concerts to this day -->
<!-- comment(2): contrary to common folklore, Paul is NOT dead! -->
<beatle name="Paul"/>
</beatles>
</xml>
What happens now? I want to sort the Beatles (God bless them) by name, and also keep ALL the comments of each Beatle in place, in order to get this result:
<xml>
<beatles>
<beatle name="George"/>
<!-- comment(1): john is actually my favourite -->
<!-- comment(2): John died tragically in 1980 -->
<beatle name="John"/>
<!-- comment(1): Paul still does live concerts to this day -->
<!-- comment(2): contrary to common folklore, Paul is NOT dead! -->
<beatle name="Paul"/>
<beatle name="Ringo"/>
</beatles>
</xml>
Good old preceding-sibling::comment()[1] won't work here. In regular code I'd just do a reverse loop over all the preceding comments, and stop when I hit a non-comment node; but 开发者_JS百科as we all know, XSLT's for-each cannot be escaped.
Any thoughts?
TIA!
DF.
I think this can be achieved by means of a key which lists all comments for a given 'beatle' element.
<xsl:key name="comments" match="comment()" use="following-sibling::beatle[1]/@name" />
So, for each comment, it is indexed by the first successive beatle element.
You can then use this as follows to list all comments for any beatle element.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml"/>
<xsl:key name="comments" match="comment()" use="following-sibling::beatle[1]/@name" />
<xsl:template match="/xml/beatles">
<beatles>
<xsl:for-each select="beatle">
<xsl:sort select="@name" />
<!-- Loop through all comments for the beatle element -->
<xsl:for-each select="key('comments', @name)">
<xsl:comment>
<xsl:value-of select="." />
</xsl:comment>
</xsl:for-each>
<!-- Copy the beatle element -->
<xsl:copy>
<xsl:copy-of select="@*" />
</xsl:copy>
</xsl:for-each>
</beatles>
</xsl:template>
</xsl:stylesheet>
When copying corresponding beatle
node you should apply its comments also. That's all you need to do.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="beatles">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:apply-templates select="beatle">
<xsl:sort select="@name" data-type="text"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="beatle">
<xsl:variable name="current" select="."/>
<xsl:apply-templates
select="preceding-sibling::comment()[generate-id(following-sibling::beatle[1]) = generate-id($current)]"/>
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output:
<?xml version="1.0" encoding="windows-1251"?>
<xml>
<beatles>
<beatle name="George"/>
<!-- comment(1): john is actually my favourite -->
<!-- comment(2): John died tragically in 1980 -->
<beatle name="John"/>
<!-- comment(1): Paul still does live concerts to this day -->
<!-- comment(2): contrary to common folklore, Paul is NOT dead! -->
<beatle name="Paul"/>
<beatle name="Ringo"/>
</beatles>
</xml>
精彩评论