Reusing XSL template to be invoked with different relative XPaths

Posted by meomaxy on Stack Overflow See other posts from Stack Overflow or by meomaxy
Published on 2010-06-18T15:46:25Z Indexed on 2010/06/18 15:53 UTC
Read the original article Hit count: 436

Filed under:
|

Here is my contrived example that illustrates what I am attempting to accomplish. I have an input XML file that I wish to flatten for further processing.

Input file:

<BICYCLES>
    <BICYCLE>
        <COLOR>BLUE</COLOR>
        <WHEELS>
            <WHEEL>
                <WHEEL_TYPE>FRONT</WHEEL_TYPE>
                <FLAT>NO</FLAT>
                <REFLECTORS>
                    <REFLECTOR>
                        <REFLECTOR_NUM>1</REFLECTOR_NUM>
                        <COLOR>RED</COLOR>
                        <SHAPE>SQUARE</SHAPE>
                    </REFLECTOR>
                    <REFLECTOR>
                        <REFLECTOR_NUM>2</REFLECTOR_NUM>
                        <COLOR>WHITE</COLOR>
                        <SHAPE>ROUND</SHAPE>
                    </REFLECTOR>
                </REFLECTORS>
            </WHEEL>
            <WHEEL>
                <WHEEL_TYPE>REAR</WHEEL_TYPE>
                <FLAT>NO</FLAT>
            </WHEEL>
        </WHEELS>
    </BICYCLE>
</BICYCLES>

The input is a list of <BICYCLE> nodes. Each <BICYCLE> has a <COLOR> and optionally has <WHEELS>.

<WHEELS> is a list of <WHEEL> nodes, each of which has a few attributes, and optionally has <REFLECTORS>.

<REFLECTORS> is a list of <REFLECTOR> nodes, each of which has a few attributes.

The goal is to flatten this XML. This is the XSL I'm using:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output method="xml" encoding="UTF-8" indent="yes" omit-xml-declaration="yes" xml:space="preserve"/> 

<xsl:template match="/">
<BICYCLES>
<xsl:apply-templates/>
</BICYCLES>
</xsl:template>

<xsl:template match="BICYCLE">
<xsl:choose>
    <xsl:when test="WHEELS">
        <xsl:apply-templates select="WHEELS"/>
    </xsl:when>
    <xsl:otherwise>
        <BICYCLE>
            <COLOR><xsl:value-of select="COLOR"/></COLOR>
            <WHEEL_TYPE/>
            <FLAT/>
            <REFLECTOR_NUM/>
            <COLOR/>
            <SHAPE/>
        </BICYCLE>
    </xsl:otherwise>
</xsl:choose>
</xsl:template>

<xsl:template match="WHEELS">
<xsl:apply-templates select="WHEEL"/>
</xsl:template>

<xsl:template match="WHEEL">
    <xsl:choose>
        <xsl:when test="REFLECTORS">
            <xsl:apply-templates select="REFLECTORS"/>
        </xsl:when>
        <xsl:otherwise>
            <BICYCLE>
                <COLOR><xsl:value-of select="../../COLOR"/></COLOR>
                <WHEEL_TYPE><xsl:value-of select="WHEEL_TYPE"/></WHEEL_TYPE>
                <FLAT><xsl:value-of select="FLAT"/></FLAT>
                <REFLECTOR_NUM/>
                <COLOR/>
                <SHAPE/>
            </BICYCLE>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template match="REFLECTORS">
    <xsl:apply-templates select="REFLECTOR"/>
</xsl:template>

<xsl:template match="REFLECTOR"> 
    <BICYCLE>
        <COLOR><xsl:value-of select="../../../../COLOR"/></COLOR>
        <WHEEL_TYPE><xsl:value-of select="../../WHEEL_TYPE"/></WHEEL_TYPE>
        <FLAT><xsl:value-of select="../../FLAT"/></FLAT>
        <REFLECTOR_NUM><xsl:value-of select="REFLECTOR_NUM"/></REFLECTOR_NUM>
        <COLOR><xsl:value-of select="COLOR"/></COLOR>
        <SHAPE><xsl:value-of select="SHAPE"/></SHAPE>
    </BICYCLE>
</xsl:template>

</xsl:stylesheet>

The output is:

<BICYCLES xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <BICYCLE>
        <COLOR>BLUE</COLOR>
        <WHEEL_TYPE>FRONT</WHEEL_TYPE>
        <FLAT>NO</FLAT>
        <REFLECTOR_NUM>1</REFLECTOR_NUM>
        <COLOR>RED</COLOR>
        <SHAPE>SQUARE</SHAPE>
    </BICYCLE>
    <BICYCLE>
        <COLOR>BLUE</COLOR>
        <WHEEL_TYPE>FRONT</WHEEL_TYPE>
        <FLAT>NO</FLAT>
        <REFLECTOR_NUM>2</REFLECTOR_NUM>
        <COLOR>WHITE</COLOR>
        <SHAPE>ROUND</SHAPE>
    </BICYCLE>
    <BICYCLE>
        <COLOR>BLUE</COLOR>
        <WHEEL_TYPE>REAR</WHEEL_TYPE>
        <FLAT>NO</FLAT>
        <REFLECTOR_NUM/>
        <COLOR/>
        <SHAPE/>
    </BICYCLE>
</BICYCLES>

What I don't like about this is that I'm outputting the color attribute in several forms:

<COLOR><xsl:value-of select="../../../../COLOR"/></COLOR>
<COLOR><xsl:value-of select="../../COLOR"/></COLOR>
<COLOR><xsl:value-of select="COLOR"/></COLOR>
<COLOR/>

It seems like there ought to be a way to make a named template and invoke it from the various places where it is needed and pass some parameter that represents the path back to the <BICYCLE> node to which it refers.

Is there a way to clean this up, say with a named template for bicycle fields, for wheel fields and for reflector fields?

In the real world example this is based on, there are many more attributes to a "bicycle" than just color, and I want to make this XSL easy to change to include or exclude fields without having to change the XSL in multiple places.

© Stack Overflow or respective owner

Related posts about Xml

Related posts about xslt