Reputation: 1913
I'd like to optimize the XSL transform below. I've included sample input data and the sample output is creates. The input data is from a database with a Record element for each row, and child elements for each column that was selected. There could be any number of columns in any order with any name. I don't know any of that ahead of time (except for the Id element). I need to turn that data into a grid XML format that defines the columns used, then includes row elements for each record with cell elements for each column. The input data includes a Columns element whose child elements define the columns that need to appear in the output. I don't know what any of the columns might be ahead of time. All of the elements under Columns needs to appear in the output. The order of the columns/cells in the output is determined by the order attribute in the Columns child elements. The Record child elements that match the names of the Columns child elements supply the data for the corresponding cell element in the output. Record child elements that have no matching Columns child element are ignored. Not all Columns child elements will have corresponding Record child elements; for them we need to output an empty cell element.
Can I make this XSL work any better? I know that for-each is "bad". Can I template-ize this any more? Thanks!
XML Input:
<?xml version="1.0" encoding="utf-8" ?>
<!-- The root element could have any name -->
<Data>
<!-- There could be any number of Record elements -->
<Record>
<!-- Elements here may have any name and be in any order and may not be included in Columns -->
<!-- For a particular XML file, all Record elements have the same child elements in the same order -->
<Id>234542</Id>
<Name>Tom Winter</Name>
<SSN>XXX-XX-3317</SSN>
<Facility>East Coast Hospital</Facility>
<Status>AC</Status>
</Record>
<Record>
<Id>345223</Id>
<Name>John Doe</Name>
<SSN>XXX-XX-2344</SSN>
<Facility>St. Joseph West</Facility>
<Status>DE</Status>
</Record>
<Columns>
<!-- There may be any number of element here and they can be in any order -->
<Name label="Patient Name" display="yes" order="2"/>
<MRN label="MRN #" display="yes" order="1"/>
<BirthDate label="Birth Date" align="right" display="yes" order="3"/>
<SSN label="SSN" display="yes" order="9" notSortable="yes"/>
<DischargeDate label="Discharge Date" align="right" display="no" order="7"/>
<Address label="Address" display="yes" order="8"/>
<Facility label="Facility" display="yes" order="4"/>
</Columns>
</Data>
XSL:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="utf-8"/>
<xsl:key name="recordId" match="Record" use="generate-id(.)"/>
<xsl:template match="/*">
<grid name="exampleGrid">
<xsl:for-each select="//Columns/*">
<xsl:sort select="@order" order="ascending" data-type="number"/>
<xsl:variable name="name" select="local-name(.)"/>
<column label="{@label}" name="{$name}" align="{@align}"/>
</xsl:for-each>
<xsl:for-each select="Record">
<xsl:call-template name="Record"/>
</xsl:for-each>
</grid>
</xsl:template>
<xsl:template name="Record">
<xsl:variable name="recordId" select="generate-id(.)"/>
<row key="{Id}">
<xsl:for-each select="//Columns/*">
<xsl:sort select="@order" order="ascending" data-type="number"/>
<xsl:variable name="name" select="local-name(.)"/>
<cell align="{@align}">
<xsl:value-of select="key('recordId', $recordId)/*[local-name(.) = $name]"/>
</cell>
</xsl:for-each>
</row>
</xsl:template>
</xsl:stylesheet>
XML Output:
<grid name="exampleGrid">
<column label="MRN #" name="MRN" align="" />
<column label="Patient Name" name="Name" align="" />
<column label="Birth Date" name="BirthDate" align="right" />
<column label="Facility" name="Facility" align="" />
<column label="Discharge Date" name="DischargeDate" align="right" />
<column label="Address" name="Address" align="" />
<column label="SSN" name="SSN" align="" />
<row key="234542">
<cell align=""></cell>
<cell align="">Tom Winter</cell>
<cell align="right"></cell>
<cell align="">East Coast Hospital</cell>
<cell align="right"></cell>
<cell align=""></cell>
<cell align="">XXX-XX-3317</cell>
</row>
<row key="345223">
<cell align=""></cell>
<cell align="">John Doe</cell>
<cell align="right"></cell>
<cell align="">St. Joseph West</cell>
<cell align="right"></cell>
<cell align=""></cell>
<cell align="">XXX-XX-2344</cell>
</row>
</grid>
Upvotes: 0
Views: 760
Reputation:
This is how I would do this task:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="vColumns" select="/*/Columns/*"/>
<xsl:template match="/*">
<grid name="exampleGrid">
<xsl:apply-templates select="$vColumns">
<xsl:sort select="@order" data-type="number"/>
</xsl:apply-templates>
<xsl:apply-templates select="Record"/>
</grid>
</xsl:template>
<xsl:template match="Columns/*">
<column label="{@label}" name="{local-name()}" align="{@align}"/>
</xsl:template>
<xsl:template match="Record">
<xsl:variable name="vCurrent" select="."/>
<row key="{Id}">
<xsl:for-each select="$vColumns">
<xsl:sort select="@order" data-type="number"/>
<cell align="{@align}">
<xsl:value-of
select="$vCurrent/*[local-name()
= local-name(current())]"/>
</cell>
</xsl:for-each>
</row>
</xsl:template>
</xsl:stylesheet>
Output:
<grid name="exampleGrid">
<column label="MRN #" name="MRN" align="" />
<column label="Patient Name" name="Name" align="" />
<column label="Birth Date" name="BirthDate" align="right" />
<column label="Facility" name="Facility" align="" />
<column label="Discharge Date" name="DischargeDate" align="right" />
<column label="Address" name="Address" align="" />
<column label="SSN" name="SSN" align="" />
<row key="234542">
<cell align=""></cell>
<cell align="">Tom Winter</cell>
<cell align="right"></cell>
<cell align="">East Coast Hospital</cell>
<cell align="right"></cell>
<cell align=""></cell>
<cell align="">XXX-XX-3317</cell>
</row>
<row key="345223">
<cell align=""></cell>
<cell align="">John Doe</cell>
<cell align="right"></cell>
<cell align="">St. Joseph West</cell>
<cell align="right"></cell>
<cell align=""></cell>
<cell align="">XXX-XX-2344</cell>
</row>
</grid>
With only one sort performed as suggested by Martin Honnen:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="msxsl">
<xsl:variable name="vRTFColumns">
<xsl:for-each select="/*/Columns/*">
<xsl:sort select="@order" data-type="number"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="vColumns"
select="msxsl:node-set($vRTFColumns)/*"/>
<xsl:template match="/*">
<grid name="exampleGrid">
<xsl:for-each select="$vColumns">
<column label="{@label}"
name="{local-name()}"
align="{@align}"/>
</xsl:for-each>
<xsl:apply-templates select="Record"/>
</grid>
</xsl:template>
<xsl:template match="Record">
<xsl:variable name="vCurrent" select="."/>
<row key="{Id}">
<xsl:for-each select="$vColumns">
<cell align="{@align}">
<xsl:value-of
select="$vCurrent/*[local-name()
= local-name(current())]"/>
</cell>
</xsl:for-each>
</row>
</xsl:template>
</xsl:stylesheet>
EDIT: Adding an XSLT 2.0 solution because is the perfect example for xsl:perform-sort
instruction.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="vColumns" as="element()*">
<xsl:perform-sort select="/*/Columns/*">
<xsl:sort select="@order" data-type="number"/>
</xsl:perform-sort>
</xsl:variable>
<xsl:template match="/*">
<grid name="exampleGrid">
<xsl:apply-templates select="$vColumns"/>
<xsl:apply-templates select="Record"/>
</grid>
</xsl:template>
<xsl:template match="Columns/*">
<column label="{@label}" name="{local-name()}" align="{@align}"/>
</xsl:template>
<xsl:template match="Record">
<xsl:variable name="vCurrent" select="."/>
<row key="{Id}">
<xsl:for-each select="$vColumns">
<cell align="{@align}">
<xsl:value-of
select="$vCurrent/*[local-name()
= local-name(current())]"/>
</cell>
</xsl:for-each>
</row>
</xsl:template>
</xsl:stylesheet>
Note: Nodes in $vColumns
sequence retain their identity, and now we can use pattern matching with them.
Upvotes: 3