Muenchian Method

06 Feb 2009    

Last week I encountered a grouping problem when designing an XLST stylesheet. The application I’m working on, receives an input XML file, that has to be parsed and converted into a PDF document using Apache’s XSLFOP. The data in the XML file is structured in a way that is different from the structure of the PDF document. For example, the XML I’m receiving looks like:

<transport>
     <cars>
        <brand>Audi</brand>
        <model>A4</model>
        <hp>200</hp>
     </cars>
     <cars>
        <brand>BMW</brand>
        <model>3 series</model>
        <hp>180</hp>
     </cars>
...
</transport>

The result in the PDF had to be sorted by brand in order to have a list with all models BMW makes. This means I had to group all the models for all the different brands. Identifying the different models is possible with preceding-siblings, however this method requires a lot of XML processing. Since I had no idea how many cars the input XML will contain, I needed a different solution.

I found that solution in the Muenchian Method, originally developed by Steve Muench. The Muenchian Method uses keys to give you access to the different nodes (brands) through the key value. Since we want to group the cars by their brand, we create the following key:

<xsl:key name="cars-by-brand" match="cars" use="brand" />

In order to obtain all models for a certain brand, we select it from the key:

<xsl:apply-templates select="key('cars-by-brand',brand)" />

Our goal was to make a list of all models grouped for the brands. So every time we find a new brand, we need to start a new list.  In order to find out if a selected brand occurs the first time in the XML file, we use the Muenchian Method:

cars[count(. | key('cars-by-brand', brand)[1]) = 1]

Finally the following XSLT template returns a (sorted) list of models grouped by brand:

<xsl:key name="cars-by-brand" match="cars" use="brand" />
<xsl:template match="transport">
	<xsl:for-each select="cars[count(. | key('cars-by-brand', brand)[1]) = 1]">
		<xsl:sort select="brand" />
		<xsl:value-of select="brand" />
		<xsl:for-each select="key('cars-by-brand', brand)">
			<xsl:sort select="model" />
			<xsl:value-of select="model" />
		</xsl:for-each>
	</xsl:for-each>
</xsl:template>

For additional information on XSLT grouping, I recommend Jeni’s XSLT Pages on grouping.