xsl: Sort Multiple attributes - xslt

I have the below data, where I have multiple attributes:
I need to sort either by Datetime or by Timezone in ascending order.
If input contains different Datetime and same Timezone it should output ascending per Datetime.
If input contains same Datetime and different Timezone it should output ascending per Timezone.
Current Logic:
<xsl:sort select="#Datetime" order="ascending"/>
<xsl:sort select="#Timezone" order="ascending"/>
SAMPLE 1st scenario - output is as expected:
<MSG>
<DOC>
<Parent>
<Input Id="1234567890" Srvce="RRR" Cd="D1" Datetime="2016-06-16 20:42:30" Timezone="+02:00" EvtRmk="DUMMY 1">
</Input>
<Input Id="1234567890" Srvce="RRR" Cd="D1" Datetime="2016-06-15 20:43:15" Timezone="+04:00" EvtRmk="DUMMY 1">
</Input>
Current output:
<Output>2016-06-15 20:43:15"</Output>
<Output>2016-06-16 20:42:30</Output>
SAMPLE 2nd scenario - Different Timezone/Same Datetime; output not sorted per Timezone:
<MSG>
</DOC>
<Parent>
<Input Id="1234567890" Srvce="RRR" Cd="D1" Datetime="2016-06-15 20:42:30" Timezone="+04:00" EvtRmk="DUMMY 1">
</Input>
<Input Id="1234567890" Srvce="RRR" Cd="D1" Datetime="2016-06-15 20:43:15" Timezone="+00:00" EvtRmk="DUMMY 1">
</Input>
Current Output:
<Output>2016-06-15 20:42:30</Output>
<Output>2016-06-15 20:43:15</Output>
Expected output:
<Output>2016-06-15 20:43:15</Output>
<Output>2016-06-15 20:42:30</Output>

In XSLT 1.0, you will have to do this in two passes: first, equalize the input dateTimes to a common base such as UTC, then sort the results by this value:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="Parent">
<!-- first-pass -->
<xsl:variable name="inputs">
<xsl:for-each select="Input">
<input>
<xsl:copy-of select="#*"/>
<xsl:call-template name="dateTime-to-seconds">
<xsl:with-param name="dateTime" select="#Datetime" />
<xsl:with-param name="offset" select="#Timezone" />
</xsl:call-template>
</input>
</xsl:for-each>
</xsl:variable>
<!-- output -->
<xsl:copy>
<xsl:for-each select="exsl:node-set($inputs)/input">
<xsl:sort select="." data-type="number" order="ascending"/>
<Input>
<xsl:copy-of select="#*"/>
</Input>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template name="dateTime-to-seconds">
<xsl:param name="dateTime"/>
<xsl:param name="offset"/>
<xsl:variable name="date" select="substring-before($dateTime, ' ')" />
<xsl:variable name="local-time" select="substring-after($dateTime, ' ')" />
<xsl:variable name="year" select="substring($date, 1, 4)" />
<xsl:variable name="month" select="substring($date, 6, 2)" />
<xsl:variable name="day" select="substring($date, 9, 2)" />
<xsl:variable name="hour" select="substring($local-time, 1, 2)" />
<xsl:variable name="minute" select="substring($local-time, 4, 2)" />
<xsl:variable name="second" select="substring($local-time, 7)" />
<xsl:variable name="offset-sign" select="1 - 2 * starts-with($offset, '-')" />
<xsl:variable name="offset-hour" select="substring($offset, 2, 2) * $offset-sign" />
<xsl:variable name="offset-minute" select="substring($offset, 5, 2) * $offset-sign" />
<xsl:variable name="a" select="floor((14 - $month) div 12)"/>
<xsl:variable name="y" select="$year + 4800 - $a"/>
<xsl:variable name="m" select="$month + 12*$a - 3"/>
<xsl:variable name="jd" select="$day + floor((153*$m + 2) div 5) + 365*$y + floor($y div 4) - floor($y div 100) + floor($y div 400) - 32045" />
<xsl:value-of select="86400*$jd + 3600*$hour + 60*$minute + $second - 3600*$offset-hour - 60*$offset-minute" />
</xsl:template>
</xsl:stylesheet>
Applied to the following test input:
XML
<Parent>
<Input Id="001" Datetime="2016-06-15 19:44:30" Timezone="-02:30"/>
<Input Id="002" Datetime="2016-06-15 20:42:30" Timezone="+04:00"/>
<Input Id="003" Datetime="2016-06-15 21:43:15" Timezone="+00:00"/>
</Parent>
the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<Parent>
<Input Id="002" Datetime="2016-06-15 20:42:30" Timezone="+04:00"/>
<Input Id="003" Datetime="2016-06-15 21:43:15" Timezone="+00:00"/>
<Input Id="001" Datetime="2016-06-15 19:44:30" Timezone="-02:30"/>
</Parent>

Related

XSLT 1.0 Date Duration (Years)

I'm using XSLT 1.0 to output the duration between two date fields. The following code (from https://stackoverflow.com/a/38615456/3016153) outputs the Duration in Days, Hours, Minutes, and Seconds as expected, but I need to update it to output Years as well. Does anyone have any suggestions on how I can update this code to output the Year duration? Alternative code is welcomed as well, but it must use XSLT 1.0
Any assistance is greatly appreciated.
<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:strip-space elements="*"/>
<xsl:template match="event">
<xsl:variable name="start">
<xsl:call-template name="dateTime-to-seconds">
<xsl:with-param name="dateTime" select="start/#time" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="end">
<xsl:call-template name="dateTime-to-seconds">
<xsl:with-param name="dateTime" select="end/#time" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="duration" select="$end - $start" />
<xsl:variable name="d" select="floor($duration div 86400)"/>
<xsl:variable name="t" select="$duration mod 86400"/>
<xsl:variable name="h" select="floor($t div 3600)"/>
<xsl:variable name="r" select="$t mod 3600"/>
<xsl:variable name="m" select="floor($r div 60)"/>
<xsl:variable name="s" select="$r mod 60"/>
<xsl:copy>
<xsl:copy-of select="name"/>
<duration>
<xsl:value-of select="$d"/>
<xsl:text> days, </xsl:text>
<xsl:value-of select="$h"/>
<xsl:text> hours, </xsl:text>
<xsl:value-of select="$m"/>
<xsl:text> minutes and </xsl:text>
<xsl:value-of select="$s"/>
<xsl:text> seconds</xsl:text>
</duration>
</xsl:copy>
</xsl:template>
<xsl:template name="dateTime-to-seconds">
<xsl:param name="dateTime"/>
<xsl:variable name="date" select="substring-before($dateTime, 'T')" />
<xsl:variable name="time" select="substring-after($dateTime, 'T')" />
<xsl:variable name="local-time" select="substring($time, 1, string-length($time) - 6)" />
<xsl:variable name="offset" select="substring-after($time, $local-time)" />
<xsl:variable name="year" select="substring($date, 1, 4)" />
<xsl:variable name="month" select="substring($date, 6, 2)" />
<xsl:variable name="day" select="substring($date, 9, 2)" />
<xsl:variable name="hour" select="substring($local-time, 1, 2)" />
<xsl:variable name="minute" select="substring($local-time, 4, 2)" />
<xsl:variable name="second" select="substring($local-time, 7)" />
<xsl:variable name="offset-sign" select="1 - 2 * starts-with($offset, '-')" />
<xsl:variable name="offset-hour" select="substring($offset, 2, 2) * $offset-sign" />
<xsl:variable name="offset-minute" select="substring($offset, 5, 2) * $offset-sign" />
<xsl:variable name="a" select="floor((14 - $month) div 12)"/>
<xsl:variable name="y" select="$year + 4800 - $a"/>
<xsl:variable name="m" select="$month + 12*$a - 3"/>
<xsl:variable name="jd" select="$day + floor((153*$m + 2) div 5) + 365*$y + floor($y div 4) - floor($y div 100) + floor($y div 400) - 32045" />
<xsl:value-of select="86400*$jd + 3600*$hour + 60*$minute + $second - 3600*$offset-hour - 60*$offset-minute" />
</xsl:template>
</xsl:stylesheet>
Sample XML:
<events>
<event>
<name>Alpha</name>
<start time="2015-02-21T00:59:06+02:00"/>
<end time="2018-02-22T02:24:38+02:00"/>
</event>
<event>
<name>Bravo</name>
<start time="2016-02-21T00:59:06+02:00"/>
<end time="2020-03-05T02:24:38+02:00"/>
</event>
<event>
<name>Charlie</name>
<start time="2016-02-21T00:59:06+02:00"/>
<end time="2020-02-21T00:59:06+01:00"/>
</event>
enter code here
As stated in the comments, if you are willing to assume that every 365 days constitute a year, then all you need to do is change this part:
<duration>
<xsl:value-of select="$d"/>
<xsl:text> days, </xsl:text>
<xsl:value-of select="$h"/>
<xsl:text> hours, </xsl:text>
<xsl:value-of select="$m"/>
<xsl:text> minutes and </xsl:text>
<xsl:value-of select="$s"/>
<xsl:text> seconds</xsl:text>
</duration>
to:
<duration>
<xsl:value-of select="floor($d div 365)"/>
<xsl:text> years, </xsl:text>
<xsl:value-of select="$d mod 365"/>
<xsl:text> days, </xsl:text>
<xsl:value-of select="$h"/>
<xsl:text> hours, </xsl:text>
<xsl:value-of select="$m"/>
<xsl:text> minutes and </xsl:text>
<xsl:value-of select="$s"/>
<xsl:text> seconds</xsl:text>
</duration>
As also stated in the comments, it is rather rather strange that you would be willing to accept an error of approximately 1 day per 4 years in a calculation that calculates to seconds precision. If you only need precision of days, then you should save processor time and electricity by removing the parts that calculate the hours, minutes and seconds.

Count the total of preceding siblings and comments

I have many files that resemble the following a.xml file, although they are much larger:
<?xml version="1.0" encoding="UTF-8"?>
<a version="3.0">
<b bb="P1">
<!--============== b:P1 c:1 ==============-->
<c cc="1">
<d dd="61">d1
</d>
</c>
<!--============== b:P1 c:2 ==============-->
<c cc="2">
<d dd="17">d2
</d>
</c>
</b>
</a>
For each c there is only one preceding comment.
I want to output a file with the same structure of the following a.csv file:
1|1|a|0| |0| |0|
2|1|a|1|b|0| |0|
3|1|a|1|b|1|!|0|
3|1|a|1|b|2|c|0|
4|1|a|1|b|2|c|1|d
3|1|a|1|b|3|!|0|
3|1|a|1|b|4|c|0|
4|1|a|1|b|4|c|1|d
It represents the hierarchical tree for a.xml:
Field 1 is the hierarchical level. For instance a has level 1, b has level 2, etc.
Fields 2, 4, 6 and 8 are equal to:
if the current node's level is less than the current field's level then 0
else the total number of preceding siblings and comments plus one
Field 3, 5, 7 and 9 are equal to:
if the current node's level is less than the current field's level then " "
else either "!" if the current node is preceded by a comment or the node's name
In this example level 3 contains comments.
I cannot find a good way to do a for-each that includes both nodes and comments. When I use <xsl:for-each select="*"> I only loop through the nodes.
Because of that, I've come out with the following xslt, that checks if the current node is preceded by a comment:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<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:for-each select="*">
<xsl:variable name="elm01" select="local-name()" />
<xsl:text>1</xsl:text>
<xsl:text>|</xsl:text><xsl:value-of select="count(preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm01" />
<xsl:text>|0| |0| |0|</xsl:text>
<xsl:text>
</xsl:text>
<xsl:for-each select="*">
<xsl:variable name="elm02" select="local-name()" />
<xsl:text>2</xsl:text>
<xsl:text>|</xsl:text><xsl:value-of select="count(../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm01" />
<xsl:text>|</xsl:text><xsl:value-of select="count(preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm02" />
<xsl:text>|0| |0|</xsl:text>
<xsl:text>
</xsl:text>
<xsl:for-each select="*">
<xsl:variable name="elm03" select="local-name()" />
<xsl:if test="preceding-sibling::comment()[1]">
<xsl:text>3</xsl:text>
<xsl:text>|</xsl:text><xsl:value-of select="count(../../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm01" />
<xsl:text>|</xsl:text><xsl:value-of select="count(../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm02" />
<xsl:text>|</xsl:text><xsl:value-of select="count(preceding-sibling::*)+1"/>
<xsl:text>|!</xsl:text>
<xsl:text>|0|</xsl:text>
<xsl:text>
</xsl:text>
</xsl:if>
<xsl:text>3</xsl:text>
<xsl:text>|</xsl:text><xsl:value-of select="count(../../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm01" />
<xsl:text>|</xsl:text><xsl:value-of select="count(../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm02" />
<!-- TODO: I want to count the total of preceding siblings and comments -->
<xsl:text>|</xsl:text><xsl:value-of select="count(preceding-sibling::*|comment())+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm03" />
<xsl:text>|0|</xsl:text>
<xsl:text>
</xsl:text>
<xsl:for-each select="*">
<xsl:variable name="elm04" select="local-name()" />
<xsl:text>4</xsl:text>
<xsl:text>|</xsl:text><xsl:value-of select="count(../../../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm01" />
<xsl:text>|</xsl:text><xsl:value-of select="count(../../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm02" />
<xsl:text>|</xsl:text><xsl:value-of select="count(../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm03" />
<xsl:text>|</xsl:text><xsl:value-of select="count(preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm04" />
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
However when I run the following command:
xsltproc a.xslt a.xml > a.csv
I get the following a.csv file:
1|1|a|0| |0| |0|
2|1|a|1|b|0| |0|
3|1|a|1|b|1|!|0|
3|1|a|1|b|1|c|0|
4|1|a|1|b|1|c|1|d
3|1|a|1|b|2|!|0|
3|1|a|1|b|2|c|0|
4|1|a|1|b|2|c|1|d
Please notice that field 6 is incorrect:
it is equal to 1 both for the 1st comment and the 1st node c and its children
it is equal to 2 both for the 2nd comment and the 2nd node c and its children
Do you have any solutions to suggest?
SOLUTION (by Tim)
I can now get the correct output by using the following xslt file:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<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:for-each select="*">
<xsl:variable name="elm01" select="local-name()" />
<xsl:text>1</xsl:text>
<xsl:text>|</xsl:text><xsl:value-of select="count(preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm01" />
<xsl:text>|0| |0| |0|</xsl:text>
<xsl:text>
</xsl:text>
<xsl:for-each select="*">
<xsl:variable name="elm02" select="local-name()" />
<xsl:text>2</xsl:text>
<xsl:text>|</xsl:text><xsl:value-of select="count(../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm01" />
<xsl:text>|</xsl:text><xsl:value-of select="count(preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm02" />
<xsl:text>|0| |0|</xsl:text>
<xsl:text>
</xsl:text>
<xsl:for-each select="*">
<xsl:variable name="elm03" select="local-name()" />
<xsl:if test="preceding-sibling::comment()[1]">
<xsl:text>3</xsl:text>
<xsl:text>|</xsl:text><xsl:value-of select="count(../../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm01" />
<xsl:text>|</xsl:text><xsl:value-of select="count(../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm02" />
<xsl:text>|</xsl:text><xsl:value-of select="count(preceding-sibling::*|preceding-sibling::comment())"/>
<xsl:text>|!</xsl:text>
<xsl:text>|0|</xsl:text>
<xsl:text>
</xsl:text>
</xsl:if>
<xsl:text>3</xsl:text>
<xsl:text>|</xsl:text><xsl:value-of select="count(../../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm01" />
<xsl:text>|</xsl:text><xsl:value-of select="count(../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm02" />
<xsl:text>|</xsl:text><xsl:value-of select="count(preceding-sibling::*|preceding-sibling::comment())+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm03" />
<xsl:text>|0|</xsl:text>
<xsl:text>
</xsl:text>
<xsl:for-each select="*">
<xsl:variable name="elm04" select="local-name()" />
<xsl:text>4</xsl:text>
<xsl:text>|</xsl:text><xsl:value-of select="count(../../../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm01" />
<xsl:text>|</xsl:text><xsl:value-of select="count(../../preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm02" />
<xsl:text>|</xsl:text><xsl:value-of select="count(../preceding-sibling::*|../preceding-sibling::comment())+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm03" />
<xsl:text>|</xsl:text><xsl:value-of select="count(preceding-sibling::*)+1"/>
<xsl:text>|</xsl:text><xsl:copy-of select="$elm04" />
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Alternatively you can also use the xslt in Tim's reply, that gets rid of repetitions.
There expression you want is this...
<xsl:value-of select="count(preceding-sibling::*|preceding-sibling::comment()) + 1" />
Or this would work too...
<xsl:value-of select="count(preceding-sibling::node()[self::*|self::comment()]) + 1" />
But you can also use xsl:number
<xsl:number count="*|comment()" />
Your stylesheet does seem a bit over-complicated though, with much repetition. Try this more generic one instead. This recursively calls each level, passing in the constructed line each call to save having to build it each time.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text" />
<xsl:param name="maxLevel" select="4" />
<xsl:template match="*|comment()">
<xsl:param name="level" select="1" />
<xsl:param name="prev" />
<xsl:variable name="new">
<xsl:value-of select="$prev" />
<xsl:text>|</xsl:text>
<xsl:number count="*|comment()" />
<xsl:text>|</xsl:text>
<xsl:choose>
<xsl:when test="self::*">
<xsl:value-of select="local-name()" />
</xsl:when>
<xsl:otherwise>
<xsl:text>!</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="$level" />
<xsl:value-of select="$new" />
<xsl:call-template name="pad">
<xsl:with-param name="levels" select="$maxLevel - $level" />
</xsl:call-template>
<xsl:text>
</xsl:text>
<xsl:apply-templates select="*|comment()">
<xsl:with-param name="level" select="$level + 1" />
<xsl:with-param name="prev" select="$new" />
</xsl:apply-templates>
</xsl:template>
<xsl:template name="pad">
<xsl:param name="levels" />
<xsl:if test="$levels > 0">
<xsl:text>|0| </xsl:text>
<xsl:call-template name="pad">
<xsl:with-param name="levels" select="$levels - 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
See it in action at http://xsltfiddle.liberty-development.net/jyRYYiy

XSLT : compare date timezone

i am new to XSLT
How to compare the date strings in XSLT and output the result
below is my input xml
<?xml version="1.0" encoding="UTF-8"?>
<dates>
<date1>2003-09-15T16:53:22.000-07:00</date1>
<date2>2003-09-15T16:53:23.000-07:00</date2>
</dates>
below is my XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="date1" select="/dates/date1"/>
<xsl:variable name="date2" select="/dates/date2"/>
<xsl:template match="/">
<xsl:if test="$date1 > $date2">
date1 is greater
</xsl:if>
<xsl:if test="$date1 = $date2">
both dates are equal
</xsl:if>
<xsl:if test="$date1 <= $date2">
date1 is lesser than date2
</xsl:if>
</xsl:template>
</xsl:stylesheet>
now in XSLT i want to compare the above dates, so is it possible in XSLT 1.0 to compare (greater,lesser, equals) the dates
i believe in xslt 1.0 its not possible, if possible please share the information.
if it can be done in XSLT 2.0 please help me how i can fix this,
in my current ongoing study project, i have used XSLT 1.0, so please suggest answer in XSLT 1.0 thanks
To do this in XSLT 1.0, you must first convert the given dateTimes to numerical values and equalize them to a common time zone.
In the following stylesheet, each dateTime is converted to the number of seconds elapsed since noon UTC of November 24, 4714 BC - see: https://en.wikipedia.org/wiki/Julian_day
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:strip-space elements="*"/>
<xsl:template match="dates">
<xsl:variable name="date1">
<xsl:call-template name="dateTime-to-seconds">
<xsl:with-param name="dateTime" select="date1" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="date2">
<xsl:call-template name="dateTime-to-seconds">
<xsl:with-param name="dateTime" select="date2" />
</xsl:call-template>
</xsl:variable>
<result>
<xsl:choose>
<xsl:when test="$date1 < $date2">date1 occurs earlier than date2</xsl:when>
<xsl:when test="$date1 = $date2">the two dates are concurrent</xsl:when>
<xsl:when test="$date1 > $date2">date1 occurs later than date2</xsl:when>
</xsl:choose>
</result>
</xsl:template>
<xsl:template name="dateTime-to-seconds">
<xsl:param name="dateTime"/>
<xsl:variable name="date" select="substring-before($dateTime, 'T')" />
<xsl:variable name="time" select="substring-after($dateTime, 'T')" />
<xsl:variable name="local-time" select="substring($time, 1, string-length($time) - 6)" />
<xsl:variable name="offset" select="substring-after($time, $local-time)" />
<xsl:variable name="year" select="substring($date, 1, 4)" />
<xsl:variable name="month" select="substring($date, 6, 2)" />
<xsl:variable name="day" select="substring($date, 9, 2)" />
<xsl:variable name="hour" select="substring($local-time, 1, 2)" />
<xsl:variable name="minute" select="substring($local-time, 4, 2)" />
<xsl:variable name="second" select="substring($local-time, 7)" />
<xsl:variable name="offset-sign" select="1 - 2 * starts-with($offset, '-')" />
<xsl:variable name="offset-hour" select="substring($offset, 2, 2) * $offset-sign" />
<xsl:variable name="offset-minute" select="substring($offset, 5, 2) * $offset-sign" />
<xsl:variable name="a" select="floor((14 - $month) div 12)"/>
<xsl:variable name="y" select="$year + 4800 - $a"/>
<xsl:variable name="m" select="$month + 12*$a - 3"/>
<xsl:variable name="jd" select="$day + floor((153*$m + 2) div 5) + 365*$y + floor($y div 4) - floor($y div 100) + floor($y div 400) - 32045" />
<xsl:value-of select="86400*$jd + 3600*$hour + 60*$minute + $second - 3600*$offset-hour - 60*$offset-minute" />
</xsl:template>
</xsl:stylesheet>
Applied to the following test input:
XML
<dates>
<date1>2003-09-15T16:53:22.000-07:00</date1>
<date2>2003-09-15T17:53:22.000-06:00</date2>
</dates>
the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<result>the two dates are concurrent</result>

XSLT - Looping through an array of dates

I need to know all weekends from two dates. I actually got a template which returns the day number (0-6) when passing a date as parameter, however, the problem is i need to loop through the dates between the two dates and calculate just business days.
Is there a way to store all these dates in an array and loop through that array?
Do i need to use C# embedded?
I'm using XSLT V 1.0 with SharePoint Designer.
Thanks.
This is not exactly trivial to do in pure XSLT 1.0.
Weekend dates in a given range:
<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:param name="startDate" select="'2014-05-01'"/>
<xsl:param name="endDate" select="'2014-05-31'"/>
<xsl:template match="/">
<weekends>
<xsl:call-template name="weekends">
<xsl:with-param name="startJDN">
<xsl:call-template name="JDN">
<xsl:with-param name="date" select="$startDate" />
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="endJDN">
<xsl:call-template name="JDN">
<xsl:with-param name="date" select="$endDate" />
</xsl:call-template>
</xsl:with-param>
</xsl:call-template>
</weekends>
</xsl:template>
<xsl:template name="weekends">
<xsl:param name="startJDN"/>
<xsl:param name="endJDN"/>
<xsl:if test="$startJDN mod 7 > 4">
<date>
<xsl:call-template name="GD">
<xsl:with-param name="JDN" select="$startJDN" />
</xsl:call-template>
</date>
</xsl:if>
<xsl:if test="$startJDN < $endJDN">
<xsl:call-template name="weekends">
<xsl:with-param name="startJDN" select="$startJDN + 1"/>
<xsl:with-param name="endJDN" select="$endJDN"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="JDN">
<xsl:param name="date"/>
<xsl:param name="year" select="substring($date, 1, 4)"/>
<xsl:param name="month" select="substring($date, 6, 2)"/>
<xsl:param name="day" select="substring($date, 9, 2)"/>
<xsl:param name="a" select="floor((14 - $month) div 12)"/>
<xsl:param name="y" select="$year + 4800 - $a"/>
<xsl:param name="m" select="$month + 12*$a - 3"/>
<xsl:value-of select="$day + floor((153*$m + 2) div 5) + 365*$y + floor($y div 4) - floor($y div 100) + floor($y div 400) - 32045" />
</xsl:template>
<xsl:template name="GD">
<xsl:param name="JDN"/>
<xsl:param name="f" select="$JDN + 1401 + floor((floor((4 * $JDN + 274277) div 146097) * 3) div 4) - 38"/>
<xsl:param name="e" select="4*$f + 3"/>
<xsl:param name="g" select="floor(($e mod 1461) div 4)"/>
<xsl:param name="h" select="5*$g + 2"/>
<xsl:param name="D" select="floor(($h mod 153) div 5 ) + 1"/>
<xsl:param name="M" select="(floor($h div 153) + 2) mod 12 + 1"/>
<xsl:param name="Y" select="floor($e div 1461) - 4716 + floor((14 - $M) div 12)"/>
<xsl:value-of select="concat($Y, '-', format-number($M, '00'), '-', format-number($D, '00'))" />
</xsl:template>
</xsl:stylesheet>
The result of the above is a list of all Saturdays and Sundays in the month of May 2014:
<?xml version="1.0" encoding="UTF-8"?>
<weekends>
<date>2014-05-03</date>
<date>2014-05-04</date>
<date>2014-05-10</date>
<date>2014-05-11</date>
<date>2014-05-17</date>
<date>2014-05-18</date>
<date>2014-05-24</date>
<date>2014-05-25</date>
<date>2014-05-31</date>
</weekends>

xsl grouping sort problem

I have the following xsl template that I'm using to group my xsl. The problem I have is that I need to uppercase the #Title as currently my grouping is seeing upper and lowercase as seperate groups.
<xsl:key name="rows-by-title" match="Row" use="substring(#Title,1,1)" />
<xsl:template name="Meunchian" match="/dsQueryResponse/Rows">
<xsl:for-each select="Row[count(. | key('rows-by-title', substring(#Title,1,1))[1]) = 1]">
<xsl:sort select="substring(#Title,1,1)" />
<p></p><xsl:value-of select="substring(#Title,1,1)" /><br />
<xsl:for-each select="key('rows-by-title', substring(#Title,1,1))">
<xsl:value-of select="#Title" /><br/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
I tried to use call-template and set a variable but xsl does not seem to like this:
<xsl:key name="rows-by-title" match="Row" use="substring(#Title,1,1)" />
<xsl:template name="Meunchian" match="/dsQueryResponse/Rows">
<xsl:for-each select="Row[count(. | key('rows-by-title', substring(#Title,1,1))[1]) = 1]">
<xsl:variable name="myTitle">
<xsl:call-template name="to-upper">
<xsl:with-param name="text">
<xsl:value-of select="#Title"/>
</xsl:with-param>
</xsl:call-template>
</xsl:variable>
<p></p><xsl:value-of select="$myTitle" /><br />
<xsl:for-each select="key('rows-by-title', substring(#Title,1,1))">
<xsl:value-of select="#Title" /><br/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
What I am trying to achieve is meunchian grouping but without case sensitivity - hope this makes Sense!
Kieran
The way to convert lower-case letters to upper is to use the XPath translate() function.
Using it, one way to express the desired transformation is the following:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:variable name="vLower" select=
"'abcdefghijklmnopqrstuvwxyz'"
/>
<xsl:variable name="vUpper" select=
"'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"
/>
<xsl:key name="rows-by-title" match="Row" use=
"translate(substring(#Title,1,1),
'abcdefghijklmnopqrstuvwxyz',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
)" />
<xsl:template match="/">
<html>
<xsl:apply-templates select="*/*"/>
</html>
</xsl:template>
<xsl:template name="Meunchian" match="/dsQueryResponse/Rows">
<xsl:for-each select=
"Row[generate-id()
=
generate-id(key('rows-by-title',
translate(substring(#Title,1,1),
$vLower,
$vUpper)
)[1]
)
]">
<xsl:sort select="translate(substring(#Title,1,1),
$vLower,
$vUpper)" />
<p></p>
<xsl:value-of select="translate(substring(#Title,1,1),
$vLower,
$vUpper)" />
<br />
<xsl:for-each select=
"key('rows-by-title',
translate(substring(#Title,1,1),
$vLower,
$vUpper)">
<xsl:value-of select="#Title" />
<br/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When applied on the following XML document:
<dsQueryResponse>
<Rows>
<Row Title="Agenda" />
<Row Title="Policy" />
<Row Title="policy" />
<Row Title="Report" />
<Row Title="report" />
<Row Title="Test2" />
<Row Title="test1" />
<Row Title="Boo" />
<Row Title="foo" />
</Rows>
</dsQueryResponse>
it produces the desired result:
<html>
<p/>A
<br/>Agenda
<br/>
<p/>B
<br/>Boo
<br/>
<p/>F
<br/>foo
<br/>
<p/>P
<br/>Policy
<br/>policy
<br/>
<p/>R
<br/>Report
<br/>report
<br/>
<p/>T
<br/>Test2
<br/>test1
<br/>
</html>
In XPath 2.0 one will use the upper-case() function to convert lower case to upper case.
Also, grouping in XSLT 2.0 can be better expressed using the <xsl:for-each-group>
instruction.