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>
<SHAPE>SQUARE</SHAPE>
</REFLECTOR>
<REFLECTOR>
<REFLECTOR_NUM>2</REFLECTOR_NUM>
<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/>
<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/>
<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>
<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>
<SHAPE>SQUARE</SHAPE>
</BICYCLE>
<BICYCLE>
<COLOR>BLUE</COLOR>
<WHEEL_TYPE>FRONT</WHEEL_TYPE>
<FLAT>NO</FLAT>
<REFLECTOR_NUM>2</REFLECTOR_NUM>
<SHAPE>ROUND</SHAPE>
</BICYCLE>
<BICYCLE>
<COLOR>BLUE</COLOR>
<WHEEL_TYPE>REAR</WHEEL_TYPE>
<FLAT>NO</FLAT>
<REFLECTOR_NUM/>
<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.