XSLT 1.0: Sorting by concating portions of date string
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 01) So I believe the string will have to be parsed into three components and the month padded. The newly concated value (yyyymmdd) then sorted descending.
Problem is I have no idea how to do this. Here is an example of the data
<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" type="base"/>
<data vo="promotion" promotionid="64646" code="101P026" startdate="2/19/2011 12:00:00 AM" type=""/>
<data vo="promotion" promotionid="64636" code="101P046" startdate="1/9/2011 12:00:00 AM" type="base"/>
</collection>
</content>
Also can anyone please recommend a good book on learning XSLT?
Thanks!
Update 1
I really wish I had a better understanding of this LOL Anyways, I used the code you provided and added the 'value-of' code that worked in the related code you provide in another question and am seeing no results. Ideally once this has been sorted I would then need to reference multiple other attributes from the most recent data element.
<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:template match="content/collection/data">
<xsl:if test="position()=1">
<xsl:value-of select="@promotionid"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Update 2
Hmm, well I tried updating it like you said
...
</xsl:template>
<xsl:template mode="pass2" match="content/collection/data">
<xsl:value-of select="@promotionid"/>
</xsl:template>
</xsl:stylesheet>
And I still don't get any output. I googled around a bit and also tried messing with this declaration xmlns:ext="http://exslt.org/common"
and tried different values based on an article I looked at. I tried
- http://exslt.org/common
- urn:schemas-microsoft-com:xslt
- http://xml.apache.org/xalan (changed the node-set to nodeset)
And nothing provided output. So I wonder if I have something wrong or if my xslt processor doesn't support it.
Update 3
Okay apparently we've been given, repeatedly, bad information. I've update the sample XML with another attribute which changes what needs to be done.
What needs to happen is the data be sorted by date like we've already done and then pull the promotionid of the data node that is the most recent AND has the type='base'. If no data node has type='base' than we just reference the most recent data node like we've already have working.
Hope that makes sense. 开发者_运维知识库 And once again thanks so much.
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
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 theext:node-set()
function of EXSLT, which is implemented on most XSLT processors.
精彩评论