I\'m trying to take XML data and sort elements by their data attribute. Unfortunately the dates come over in mm/dd/yyyy format and are not static lengths. (Jan = 1 instead of
Here is one way to do this sorting using a 2-pass transformation (it is possible to do this in a one-pass transformation, but the code would be too-complicated):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="vrtfPass1">
<xsl:apply-templates/>
</xsl:variable>
<xsl:apply-templates mode="pass2" select=
"ext:node-set($vrtfPass1)/*"/>
</xsl:template>
<xsl:template match="@startdate">
<xsl:variable name="vDate" select="substring-before(.,' ')"/>
<xsl:variable name="vYear" select=
"substring($vDate, string-length($vDate) -3)"/>
<xsl:variable name="vDayMonth" select=
"substring-before($vDate, concat('/',$vYear))"/>
<xsl:variable name="vMonth"
select="format-number(substring-before($vDayMonth, '/'), '00')"/>
<xsl:variable name="vDay"
select="format-number(substring-after($vDayMonth, '/'), '00')"/>
<xsl:attribute name="startdate">
<xsl:value-of select="concat($vYear,$vMonth,$vDay)"/>
</xsl:attribute>
</xsl:template>
<xsl:template match="node()|@*" mode="pass2">
<xsl:copy>
<xsl:apply-templates mode="pass2" select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template mode="pass2" match="collection">
<xsl:copy>
<xsl:apply-templates mode="pass2" select="@*"/>
<xsl:apply-templates mode="pass2">
<xsl:sort select="@startdate"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<content date="1/13/2011 1:21:00 PM">
<collection vo="promotion">
<data vo="promotion" promotionid="64526" code="101P031" startdate="1/7/2011 12:00:00 AM"/>
<data vo="promotion" promotionid="64646" code="101P026" startdate="2/19/2011 12:00:00 AM"/>
<data vo="promotion" promotionid="64636" code="101P046" startdate="1/9/2011 12:00:00 AM"/>
</collection>
</content>
the wanted, correct result is produced:
<content date="1/13/2011 1:21:00 PM">
<collection vo="promotion">
<data vo="promotion" promotionid="64526" code="101P031" startdate="20110107"/>
<data vo="promotion" promotionid="64636" code="101P046" startdate="20110109"/>
<data vo="promotion" promotionid="64646" code="101P026" startdate="20110219"/>
</collection>
</content>
Do note:
Multipass transformations in XSLT 1.0 require the use of the vendor-specific xxx:node-set()
function to convert the result of a pass from its RTF (Result Transformation Fragment) type to a regular tree (document).
The xxx:node-set()
function used in this solution is the ext:node-set()
function of EXSLT, which is implemented on most XSLT processors.
You can use multiple xsl:sort
instructions like in this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="collection">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:apply-templates select="data">
<xsl:sort select="substring-after(
substring-after(
substring-before(
@startdate,
' '),
'/'),
'/')" data-type="number"/>
<xsl:sort select="substring-before(
@startdate,
'/')" data-type="number"/>
<xsl:sort select="substring-before(
substring-after(
@startdate,
'/'),
'/')" data-type="number"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output:
<content date="1/13/2011 1:21:00 PM">
<collection vo="promotion">
<data vo="promotion" promotionid="64526" code="101P031"
startdate="1/7/2011 12:00:00 AM"></data>
<data vo="promotion" promotionid="64636" code="101P046"
startdate="1/9/2011 12:00:00 AM"></data>
<data vo="promotion" promotionid="64646" code="101P026"
startdate="2/19/2011 12:00:00 AM"></data>
</collection>
</content>
Update from comments
What I'm trying to do is in that stylesheet perform the sort as you've done and then export out the promotionid of the item with startdate '
2/19/2011
' in this case. I assumed it would be something like<xsl:value-of select="data[last()]/@promotionid"/>
but I either am using it in the wrong place or have the statement wrong
Update 3: Now with new selecting data conditions
Use the "standard" maximum idiom. This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="collection">
<xsl:variable name="vData" select="data[@type='base']"/>
<xsl:for-each select="data[not($vData)]|$vData">
<xsl:sort select="substring-after(
substring-after(
substring-before(
@startdate,
' '),
'/'),
'/')" data-type="number"/>
<xsl:sort select="substring-before(
@startdate,
'/')" data-type="number"/>
<xsl:sort select="substring-before(
substring-after(
@startdate,
'/'),
'/')" data-type="number"/>
<xsl:if test="position()=last()">
<xsl:value-of select="@promotionid"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output:
64636