xslt: create a schedule - xslt

can someone tell me, how can I create a list of dates using xslt using a start- and end date and a parameter for the calculation period, so sth like the input
<root>
<startdate>2014/01/01</startdate>
<enddate>2015/02/20</enddate>
<period>daily</period>
</root>
gives me a list
<root>
<date>2014/01/01</date>
<date>2014/01/02</date>
...
<date>2014/02/20</date>
</root>
My node period can have values in
daily
weekly
fortnightly
monthly
So the last 3 would give me lists like
<root>
<date>2014/01/01</date>
<date>2014/01/08</date>
...
<date>2015/02/18</date>
</root>
<root>
<date>2014/01/01</date>
<date>2014/01/15</date>
...
<date>2015/02/11</date>
</root>
<root>
<date>2014/01/01</date>
<date>2014/02/01</date>
...
<date>2015/02/01</date>
</root>
with the last date smaller or equal the enddate. The date formats I would use are YYYYMMDD and DD/MM/YYYY, but I would probably be able to adapt any other date format.
Someone knows how to do this?
Thanks very much!

First, you must use YYYY-MM-DD format if you want your dates to be recognized as such.
Now, here's a quick-and-dirty way to achieve the requested result:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/root">
<result>
<xsl:call-template name="enumerate-dates">
<xsl:with-param name="startdate" select="startdate"/>
<xsl:with-param name="enddate" select="enddate"/>
<xsl:with-param name="period" select="period"/>
</xsl:call-template>
</result>
</xsl:template>
<xsl:template name="enumerate-dates">
<xsl:param name="startdate" as="xs:date"/>
<xsl:param name="enddate" as="xs:date"/>
<xsl:param name="period"/>
<xsl:if test="$startdate le $enddate">
<date><xsl:value-of select="$startdate" /></date>
<xsl:call-template name="enumerate-dates">
<xsl:with-param name="startdate">
<xsl:choose>
<xsl:when test="$period='daily'">
<xsl:value-of select="$startdate + xs:dayTimeDuration('P1D')" />
</xsl:when>
<xsl:when test="$period='weekly'">
<xsl:value-of select="$startdate + xs:dayTimeDuration('P7D')" />
</xsl:when>
<xsl:when test="$period='fortnightly'">
<xsl:value-of select="$startdate + xs:dayTimeDuration('P14D')" />
</xsl:when>
<xsl:when test="$period='monthly'">
<xsl:value-of select="$startdate + xs:yearMonthDuration('P1M')" />
</xsl:when>
</xsl:choose>
</xsl:with-param>
<xsl:with-param name="enddate" select="$enddate"/>
<xsl:with-param name="period" select="$period"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Applied to an example input of:
<root>
<startdate>2013-12-15</startdate>
<enddate>2014-03-08</enddate>
<period>fortnightly</period>
</root>
the result is:
<?xml version="1.0" encoding="utf-8"?>
<result>
<date>2013-12-15</date>
<date>2013-12-29</date>
<date>2014-01-12</date>
<date>2014-01-26</date>
<date>2014-02-09</date>
<date>2014-02-23</date>
</result>

Related

String Split to new Elements using XSL 1.0

Can any guide me to split the given xml element values into multiple child elements based on a token. Here is my sample input xml and desired output. I have a limitation to use xsl 1.0. Thank you.
Input XML:
<?xml version='1.0' encoding='UTF-8'?>
<SQLResults>
<SQLResult>
<ACTION1>Action1</ACTION1>
<ACTION2>Action2</ACTION2>
<Encrypt>Program=GPG;Code=23FCS;</Encrypt>
<SENDER>Program=WebPost;Protocol=WS;Path=/home/Inbound</SENDER>
</SQLResult>
</SQLResults>
Output XML:
<?xml version='1.0' encoding='UTF-8'?>
<SQLResults>
<SQLResult>
<ACTION1>Action1</ACTION1>
<ACTION2>Action2</ACTION2>
<Encrypt>
<Program>GPG</Program>
<Code>23FCS</Code>
</Encrypt>
<SENDER>
<Program>Action4</Program>
<Protocol>WS</Protocol>
<Path>/home/Inbound</Path>
</SENDER>
</SQLResult>
</SQLResults>
In XSLT 2 it would be easy, just with the following template:
<xsl:template match="Encrypt|SENDER">
<xsl:copy>
<xsl:analyze-string select="." regex="(\w+)=([\w/]+);?">
<xsl:matching-substring>
<element name="{regex-group(1)}">
<xsl:value-of select="regex-group(2)"/>
</element>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:copy>
</xsl:template>
Because you want to do it in XSLT 1, you have to express it another way.
Instead of analyze-string you have to:
Tokenize the content into non-empty tokens contained between ; chars.
You have to add tokenize template.
Each such token divide into 2 substrings, before and after = char.
Create an element with the name equal to the first substring.
Write the content of this element - the second substring.
XSLT 1 has also such limitation that the result of the tokenize template
is a result tree fragment (RTF) not the node set and thus it cannot be
used in XPath expressions.
To circumvent this limitation, you must use exsl:node-set function.
So the whole script looks like below:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="Encrypt|SENDER">
<xsl:copy>
<xsl:variable name="tokens">
<xsl:call-template name="tokenize">
<xsl:with-param name="txt" select="."/>
<xsl:with-param name="delim" select="';'"/>
</xsl:call-template>
</xsl:variable>
<xsl:for-each select="exsl:node-set($tokens)/token">
<xsl:variable name="t1" select="substring-before(., '=')"/>
<xsl:variable name="t2" select="substring-after(., '=')"/>
<xsl:element name="{$t1}">
<xsl:value-of select="$t2" />
</xsl:element>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="txt" />
<xsl:param name="delim" select="' '" />
<xsl:choose>
<xsl:when test="$delim and contains($txt, $delim)">
<token>
<xsl:value-of select="substring-before($txt, $delim)" />
</token>
<xsl:call-template name="tokenize">
<xsl:with-param name="txt" select="substring-after($txt, $delim)" />
<xsl:with-param name="delim" select="$delim" />
</xsl:call-template>
</xsl:when>
<xsl:when test="$txt">
<token><xsl:value-of select="$txt" /></token>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
</xsl:transform>

How to pattern match in XSLT and add values

As part of an XSLT, I need to add all the values of the "Duration" element and display the value. Now, the below XML is a part of the larger XML I'm working on. In the below XML, I need to match
a/TimesheetDuration/Day*/Duration, add the values and display them. I dont want to store all the values in variables and add them. Is there any other clean way of doing this?
<?xml version="1.0" ?>
<a>
<TimesheetDuration>
<Day1>
<BusinessDate>6/12/2013</BusinessDate>
<Duration>03:00</Duration>
</Day1>
<Day2>
<BusinessDate>6/13/2013</BusinessDate>
<Duration>04:00</Duration>
</Day2>
<Day3>
<BusinessDate>6/14/2013</BusinessDate>
<Duration>05:00</Duration>
</Day3>
</TimesheetDuration>
</a>
An XPath 2.0 solution, assuming the durations are in the form HH:MM, would be
sum(for $d in a//Duration
return xs:dayTimeDuration(replace($d, '(..):(..)', 'PT$1H$2M')))
In xslt 1.0 you could do it for example with following stylesheet
<?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" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<Durations>
<xsl:apply-templates select="a/TimesheetDuration/node()[starts-with(name(),'Day')]" />
<xsl:variable name="hours">
<xsl:call-template name="sumHours">
<xsl:with-param name="Day" select="a/TimesheetDuration/node()[starts-with(name(),'Day')][1]" />
</xsl:call-template>
</xsl:variable>
<SumOfHours>
<xsl:value-of select="$hours" />
</SumOfHours>
<!-- Sum of minutes would be calculated similarly -->
</Durations>
</xsl:template>
<xsl:template match="node()[starts-with(name(),'Day')]">
<xsl:copy-of select="Duration" />
</xsl:template>
<xsl:template name="sumHours">
<xsl:param name="tmpSum" select="0" />
<xsl:param name="Day" />
<xsl:variable name="newTmpSum" select="$tmpSum + substring-before($Day/Duration, ':')" />
<xsl:choose>
<xsl:when test="$Day/following-sibling::node()[starts-with(name(),'Day')]">
<xsl:call-template name="sumHours">
<xsl:with-param name="tmpSum" select="$newTmpSum" />
<xsl:with-param name="Day" select="$Day/following-sibling::node()[starts-with(name(),'Day')]" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$newTmpSum" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
It produces output
<?xml version="1.0" encoding="UTF-8"?>
<Durations>
<Duration>03:00</Duration>
<Duration>04:00</Duration>
<Duration>01:00</Duration>
<SumOfHours>8</SumOfHours>
</Durations>

how to get required name from a string in xslt?

e.g i have following strings:
xoc.coe.hw.ZSBALAJI
hw.cor.exp.nt.ZSSHIVA
i have to get only last string (i.e. ZSBALAJI from first and ZSSHIVA from second). How can I do it in xslt.
Thanks in advance.
Here is an XSLT-1.0 solution to your problem:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="//string">
<xsl:call-template name="skipper">
<xsl:with-param name="source" select="."/>
<xsl:with-param name="delimiter" select="'.'"/>
</xsl:call-template>
</xsl:template>
<!-- returns the substring after the last delimiter -->
<xsl:template name="skipper">
<xsl:param name="source"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($source,$delimiter)">
<xsl:call-template name="skipper">
<xsl:with-param name="source" select="substring-after($source,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$source"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
When applied to this document:
<?xml version="1.0" encoding="UTF-8"?>
<strings>
<string>xoc.coe.hw.ZSBALAJI</string>
<string>hw.cor.exp.nt.ZSSHIVA</string>
</strings>
It produces the following result:
ZSBALAJI
ZSSHIVA
Let's assume that you have the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<a>xoc.coe.hw.ZSBALAJI</a>
<a>hw.cor.exp.nt.ZSSHIVA</a>
</root>
Then the following XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="text"/>
<xsl:template match="//a">
<xsl:variable name="parts" select="tokenize(node(), '\.')"/>
<xsl:variable name="count" select="count($parts)"/>
<xsl:for-each select="$parts">
<xsl:if test="position() = $count">
<xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
will ouput
ZSBALAJI
ZSSHIVA
Essentially, you can use XPath tokenize function and then take the last token.
You can try and use EXSLT tokenize(string, string?) function to split by '.' on the relevant node, see this for additional info.

xslt - subtracting days

Is it possible with xslt to take a date field and subtract N number of days from it? If so, can you please provide me an example?
Here is a demonstration how to do this in XSLT 2.0:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:variable name="vToday" select="current-date()"/>
Today is: <xsl:sequence select="$vToday"/>
30 days ago it was: <xsl:sequence select=
"$vToday -30*xs:dayTimeDuration('P1D')"/>
365 days ago it was: <xsl:sequence select=
"$vToday -365*xs:dayTimeDuration('P1D')"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on any XML document (not used), the wanted, correct result is produced:
Today is: 2010-10-07-07:00
30 days ago it was: 2010-09-07-07:00
365 days ago it was: 2009-10-07-07:00
Yes, with XSLT 2.0 it is possible, and very easy to do.
There are a lot of Date/Time/Duration functions in XPATH 2.0, which are part of XSLT 2.0.
This example subtracts 1 day from the date 2010-01-01 to produce 2009-12-31:
<?xml version="1.0"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:template match="/">
<xsl:value-of select="xs:date('2010-01-01') - xs:dayTimeDuration('P1D')" />
</xsl:template>
</xsl:stylesheet>
Well, XSLT can split strings and parse numbers, so yes, it would be "possible".
However, it would be much easier and efficient if you could use extension functions and implement that in another language. If and how that works depends on the XSLT engine used, however.
EXSLT may have everything you need: http://www.exslt.org/date/functions/add/index.html
I can see that all the mentioned solutions are for XSLT 2.0. I have similar solution for XSLT 1.0 using EXSLT date:add
Example: Consider that the number of days to be subtracted is 365 and we need it as the default start date. In this case, we have to provide the duration 365 days in xs:dayTimeDuration format i.e. '-P365D'.
Please find below the code.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:date="http://exslt.org/dates-and-times"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
extension-element-prefixes="date xs"
exclude-result-prefixes="date xs" version="1.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" />
<xsl:template match="/">
<xsl:variable name="vCurrDate" select="date:date-time()"/>
<xsl:variable name="vDefaultStDate" select="(date:add($vCurrDate, '-P365D'))"/>
</xsl:template>
</xsl:stylesheet>
<xsl:template name="dim" >
<xsl:param name="y" />
<xsl:param name="m"/>
<xsl:choose>
<xsl:when test="($m=1 or $m=3 or $m=5 or $m=7 or $m=8 or $m=10 or $m=12)" >
31
</xsl:when>
<xsl:when test="$m>2">
30
</xsl:when>
<xsl:when test="($m=2) and (not($y mod 4=0)) or ($y mod 100=0) and (not($y mod 400=0))">
28
</xsl:when>
<xsl:otherwise>
29
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="minusdays">
<xsl:param name="year" />
<xsl:param name="month" />
<xsl:param name="day"/>
<xsl:param name="days"/>
<xsl:variable name="lm" select="number($month)-1+number($month=1)*12"/>
<xsl:variable name="lmdays">
<xsl:call-template name="dim">
<xsl:with-param name="y" select="$year"/>
<xsl:with-param name="m" select="$lm"/>
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<xsl:when test="$days<$day">
<xsl:value-of select="$year"/>
<xsl:if test="number($month)<10">0</xsl:if>
<xsl:value-of select="$month"/>
<xsl:if test="($day - $days)<10">0</xsl:if>
<xsl:value-of select="$day - $days"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="minusdays">
<xsl:with-param name="year"
select="number($year)-number($month=1)"/>
<xsl:with-param name="month" select="$lm" />
<xsl:with-param name="day" select="$lmdays"/>
<xsl:with-param name="days" select="$days - $day" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

Can I perform elementwise division in XSLT?

I have a data structure which looks something like:
<resultSet>
<result>
<numCorrect>1</numCorrect>
<truthCorrect>4</truthCorrect>
</result>
<result>
<numCorrect>2</numCorrect>
<truthCorrect>4</truthCorrect>
</result>
<result>
<numCorrect>3</numCorrect>
<truthCorrect>5</truthCorrect>
</result>
<result>
<numCorrect>5</numCorrect>
<truthCorrect>6</truthCorrect>
</result>
</resultSet>
I'd like to compute avg((result/numCorrect) div (result/truthCorrect)), but XSLT doesn't seem to allow element-wise division, how do I compute this average?
If it helps, I'm using Saxon as an XSLT 2.0 processor.
The following will do what you want ..
It uses recursion and serially adds all paired calculation and at the end divides by the number of results..
<xsl:template match = "/resultSet" >
<xsl:variable name="TotalSum">
<xsl:call-template name="calculator">
<xsl:with-param name="currSum">0</xsl:with-param>
<xsl:with-param name="count"><xsl:value-of select="count(result)"/></xsl:with-param>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$TotalSum div count(result)"/>
</xsl:template>
<xsl:template name="calculator">
<xsl:param name="currSum"/>
<xsl:param name="count"/>
<xsl:variable name="actual" select="number(result[number($count)]/numCorrect)"/>
<xsl:variable name="truth" select="number(result[number($count)]/truthCorrect)"/>
<xsl:variable name="singleResult" select="number($actual div $truth)"/>
<xsl:variable name="CycleSum" select="number($currSum + $singleResult)"/>
<xsl:choose>
<xsl:when test="number($count - 1) > 0 ">
<xsl:call-template name="calculator">
<xsl:with-param name="currSum"><xsl:value-of select="$CycleSum"/></xsl:with-param>
<xsl:with-param name="count"><xsl:value-of select="number($count - 1)"/></xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise><xsl:value-of select="$CycleSum"/></xsl:otherwise>
</xsl:choose>
</xsl:template>
Let me know if there are parts you do not understand..
A short-form solution that works seems to be this:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exslt="http://exslt.org/common">
<xsl:template match="/resultSet">
<xsl:variable name="foo">
<xsl:for-each select="result">
<n><xsl:value-of select="numCorrect div truthCorrect" /></n>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="avg(exslt:node-set($foo)/n)" />
</xsl:template>
</xsl:stylesheet>
where <xsl:value-of select="avg(exslt:node-set($foo)/n)" /> may be replaced with
<xsl:value-of select="sum(exslt:node-set($foo)/n) div count(result)" />
if you're using an XSLT engine that supports exslt extensions but doesn't have the non-standard avg function.