2009-09-18

Grouping XML nodes using XSL 2.0

Man, that was painful!!! Curse those XML Committee morons that should have designed a transformation language based on Perl, rather than come with the atrocity that is XSL!

So, let's say you have the following XML (This is a list of cross references between 8051 banks, custom generated by a popular disassembler), which you already sorted using the other XSL I posted today:
<banks>
<B0_000E/>
<B0_000E/>
<B0_000E/>
<B0_000E/>
<B0_49F8/>
<B0_49F8/>
<B0_49F8/>
<B0_49F8/>
<B0_49F8/>
<B0_49F8/>
<B0_520A/>
<B0_520A>
<BD_2E9D/>
<BD_C4B5/>
</B0_520A>
<B0_520A/>
<B0_54F9/>
<B0_54F9/>
<B0_54F9>
<BB_E851/>
</B0_54F9>
<B0_54F9/>
<B0_59EC>
<B0_012F/>
</B0_59EC>
<B0_59EC>
<BE_012F/>
</B0_59EC>
<B0_59EC>
<BF_012F/>
</B0_59EC>
<B0_CC5E/>
<B0_CC5E>
<BC_DAA6/>
<BC_DF2E/>
</B0_CC5E>
<B0_CC5E>
<BB_DA1C/>
</B0_CC5E>
<B0_CC5E/>
<B0_E022/>
<B0_E022>
<BE_E439/>
<BE_E458/>
<BE_EE05/>
<BE_EE28/>
<BE_F15B/>
<BE_F17A/>
<BE_F8E5/>
<BE_F8F9/>
</B0_E022>
<B0_E022>
<BF_3BF0/>
<BF_6C6B/>
<BF_7F7E/>
<BF_7F9D/>
<BF_7FE0/>
<BF_7FFF/>
<BF_CD2C/>
<BF_D103/>
<BF_D126/>
<BF_D146/>
<BF_D169/>
</B0_E022>
<B0_E022/>
<B0_E022>
<BC_CE5A/>
</B0_E022>
<B0_E022/>
<B0_E022/>
<B0_E201/>
<B0_E201/>
</banks>
Obviously now, you would like to move all the sub nodes into a single one and remove all the duplicates to obtain a file like this:
<banks>
<B0_000E/>
<B0_49F8/>
<B0_520A>
<BD_2E9D/>
<BD_C4B5/>
</B0_520A>
<B0_54F9>
<BB_E851/>
</B0_54F9>
<B0_59EC>
<B0_012F/>
<BE_012F/>
<BF_012F/>
</B0_59EC>
<B0_CC5E>
<BC_DAA6/>
<BC_DF2E/>
<BB_DA1C/>
</B0_CC5E>
<B0_E022>
<BE_E439/>
<BE_E458/>
<BE_EE05/>
<BE_EE28/>
<BE_F15B/>
<BE_F17A/>
<BE_F8E5/>
<BE_F8F9/>
<BF_3BF0/>
<BF_6C6B/>
<BF_7F7E/>
<BF_7F9D/>
<BF_7FE0/>
<BF_7FFF/>
<BF_CD2C/>
<BF_D103/>
<BF_D126/>
<BF_D146/>
<BF_D169/>
<BC_CE5A/>
</B0_E022>
<B0_E201/>
</banks>

Well, using the XSL (2.0) below, now you can!
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>

<xsl:template match="/">
<banks> <!-- Our XML root element. Modify according to yours! -->
<!-- Select all nodes that are immediate children of our root
element ("/*/*") and group them according to their node
names ("local-name()") -->
<xsl:for-each-group select="/*/*" group-by="local-name()">
<!-- Output a (single) node for each of these groups -->
<xsl:element name="{local-name()}">
<!-- NB: To remove empty nodes, move the <xsl:element>
section inside the <xsl:for-each-group> below, and use
{local-name(parent::*)} instead of {local-name()} -->
<!-- Select all the children nodes from the above group
("current-group()/*) - Don't bother to group them. -->
<xsl:for-each-group select="current-group()/*" group-by=".">
<!-- Here we copy the whole (group) branch as is -->
<xsl:copy-of select="current-group()"/>
</xsl:for-each-group>
</xsl:element>
</xsl:for-each-group>
</banks> <!-- Close our root -->
</xsl:template>
</xsl:stylesheet>

No comments:

Post a Comment