Reputation: 71
I am trying to output a travel itinerary read from a database into a PDF. Retreiving etc etc is all good, however i would like to order the itinerary by date.
Currently the xml structure is as follows
<flights>
<flDate>13OCT</flDate>
<flFrom>Glasgow International </flFrom>
<flTo>Philadelphia, PA - International </flTo>
<flNumber>US769</flNumber>
<depTime>1020</depTime>
<arrTime>1300</arrTime>
</flights>
<flights>
<flDate>22OCT</flDate>
<flFrom>Philadelphia, PA - International </flFrom>
<flTo>Glasgow International </flTo>
<flNumber>US768</flNumber>
<depTime>1855</depTime>
<arrTime>1830</arrTime>
</flights>
<accommodation>
<accDate>14OCT</accDate>
<accName>Hotel Los Jameos</accName>
<duration>10</duration>
<roomType>Type B Rooms</roomType>
<boardBasis>H/B</boardBasis>
</accommodation>
Output at the moment is
13OCT Glasgow to Phili
..........
22OCT Phili to Glasgow
..........
14OCT Hotel Los Jameos
..........
Obviously this isn't ideal and leads to confusion. Ideally i would like the accommodation (and any other itinerary items) to be in the correct date order.
How would I go about that? I'm thinking it maybe a change instructure to the database or the XML.
Current XSL below.
<xsl:for-each select="iOverview/itinLeg">
<xsl:sort select="flights/flDate" order="descending"/>
<xsl:sort select="accommodation/accDate" order="descending"/>
<xsl:apply-templates select="flights[count(.|key('fDate', flDate)[1])=1]"/>
<xsl:apply-templates select="accommodation"/>
</xsl:for-each>
<xsl:template match="flights">
<fo:block margin-top="2mm">
<xsl:for-each select="key('fDate', flDate)">
<xsl:sort select="flDate"/>
<fo:block>
<fo:inline font-weight="bold"><xsl:value-of select="flDate"/></fo:inline>
<fo:inline padding-left="4mm"><xsl:value-of select="flFrom"/></fo:inline>
<fo:inline padding-left="1mm">to</fo:inline>
<fo:inline padding-left="2mm"><xsl:value-of select="flTo"/></fo:inline>
</fo:block>
.......
</xsl:for-each>
</fo:block>
</xsl:template>
Accommodation xsl similar just different elements names really.
Thank you for any suggestions.
Upvotes: 0
Views: 54
Reputation: 117003
Let us reduce the example to the minimum necessary to deal with the problem of sorting nodes by date in the given DMMM format (at least that's how it seems).
Given the following input:
XML
<itinerary>
<flight>
<flDate>25OCT</flDate>
</flight>
<flight>
<flDate>13NOV</flDate>
</flight>
<flight>
<flDate>9SEP</flDate>
</flight>
<flight>
<flDate>13OCT</flDate>
</flight>
<flight>
<flDate>28AUG</flDate>
</flight>
<flight>
<flDate>5OCT</flDate>
</flight>
</itinerary>
the following stylesheet:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:variable name="months" select="'JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC'" />
<xsl:template match="/itinerary">
<xsl:copy>
<xsl:for-each select="flight">
<xsl:sort select="string-length(substring-before($months, translate(flDate, '0123456789', '')))" data-type="text" order="ascending"/>
<xsl:sort select="translate(flDate, 'ABCDEFGJLMNOPRSTUVY', '')" data-type="number" order="ascending"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
will return:
<?xml version="1.0" encoding="UTF-8"?>
<itinerary>
<flight>
<flDate>28AUG</flDate>
</flight>
<flight>
<flDate>9SEP</flDate>
</flight>
<flight>
<flDate>5OCT</flDate>
</flight>
<flight>
<flDate>13OCT</flDate>
</flight>
<flight>
<flDate>25OCT</flDate>
</flight>
<flight>
<flDate>13NOV</flDate>
</flight>
</itinerary>
Note: This will produce incorrect results when your itinerary spans a year boundary: events in January of the next year will be sorted before events in December of this year.
Upvotes: 0
Reputation: 29022
Your MCVE is not so complete, so I added an <xsl:key name="fDate" match="flights" use="flDate" />
to complete it. I also inferred the surrounding tags from your XSLT. Sorting the output can be done be merging the search expression to flDate | accDate
. I omitted the XSL:FO blocks for clarity, you would probably like to readd them to your final solution.
The resulting XSLT-1.0 stylesheet looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/2001/XMLSchema">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="fDate" match="flights" use="flDate" />
<xsl:template match="/">
<xsl:for-each select="iOverview/itinLeg">
<xsl:for-each select="flights | accommodation">
<xsl:sort select="flDate | accDate" order="descending"/>
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
<xsl:template match="flights[count(.|key('fDate', flDate)[1]) = 1]">
<xsl:for-each select="key('fDate', flDate)">
<xsl:value-of select="flDate"/><xsl:text> </xsl:text><xsl:value-of select="normalize-space(flFrom)"/> to <xsl:value-of select="flTo"/><xsl:value-of select="' '" />
</xsl:for-each>
</xsl:template>
<xsl:template match="flights[count(.|key('fDate', flDate)[1]) > 1]" /> <!-- filter out any dates with more than one matches -->
<xsl:template match="accommodation">
<xsl:value-of select="accDate"/><xsl:text> </xsl:text><xsl:value-of select="accName"/><xsl:value-of select="' '" />
</xsl:template>
</xsl:stylesheet>
With your input data the output is as follows:
22OCT Philadelphia, PA - International to Glasgow International
14OCT Hotel Los Jameos
13OCT Glasgow International to Philadelphia, PA - International
If you consider the order of times to be wrong, just replace "descending" with "ascending" in the `".
Upvotes: 0
Reputation: 163352
First, you're going to have trouble sorting with this date format. Change it to something sane like 2016-10-05, and then you can sort dates as if they were strings.
Your next challenge is that the expression to compute the sort key for flights is different from that for hotels, but you want to sort into a single sequence. But that is easily overcome; just use a union expression in the xsl:sort/@select
:
<xsl:apply-templates select="*">
<xsl:sort select="self::flights/flDate | self::accomodation/accDate"/>
</xsl:apply-templates>
Upvotes: 1