Hope someone can help. I am trying to compare 2 dates inside an XML file and using a XSLT to do some calculation:
For example I have 2 dates in XML: 2011-05-23 and 2011-04-29. I want to do calculation inside XSLT like below:
('2011-05-23'-'2011-04-29')*30 = 24*30= 720
Can anyone shed any light?
An XSLT 2.0 solution
<?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="days-from-duration(
xs:date('2011-05-23')
- xs:date(xs:date('2011-04-29'))
)*30"/>
</xsl:template>
</xsl:stylesheet>
Yields: 720
xs:date() function evaluates the dates, which can be used to perform date operations
subtracting the second date from the first yields the xdt:dayTimeDuration P24D (24 days)
days-from-duration() extracts the days component from the xdt:dayTimeDuration (24)
then you can use that number to perform normal arethmatic (e.g. 24*30=720)
It might be worth looking at the EXSLT - date:difference solution. I believe that should do what you want, and there's even a straight XSLT implementation available.
Be aware though that the returned value is in duration format as specified in the XML Schema Part 2: Datatypes Second Edition, so it's likely you will need to process the result to derive the unit you wish to use in calculation (for instance in your example above you're expecting a result detailing the number of days difference - so you would need to pull out the relevant unit you want to work with, the result from date:difference in that case would most likely be "P24D").
Here's two templates I sometimes use for date calculations:
<xsl:template name="calcseconds">
<xsl:param name="date" />
<xsl:value-of select="(((substring($date,1,4) - 1970) * 365)+floor((substring($date,1,4) - 1970) div 4)+substring('000,031,059,090,120,151,181,212,243,273,304,334,365',substring($date,6,2)*4-3,3)+(substring($date,9,2)-1)+(1-floor(((substring($date,1,4) mod 4) + 2) div 3))*floor((substring($date,6,2)+17) div 20))*86400+(substring($date,12,2)*3600)+(substring($date,15,2)*60)+substring($date,18,2)" />
</xsl:template>
<xsl:template name="calcdays">
<xsl:param name="date" />
<xsl:value-of select="(((substring($date,1,4) - 1970) * 365)+floor((substring($date,1,4) - 1970) div 4)+substring('000,031,059,090,120,151,181,212,243,273,304,334,365',substring($date,6,2)*4-3,3)+(substring($date,9,2)-1)+(1-floor(((substring($date,1,4) mod 4) + 2) div 3))*floor((substring($date,6,2)+17) div 20))" />
</xsl:template>
They're a bit of a mouthful, but they'll calculate the number of seconds/days since midnight 1st January 1970 as an integer, which you can then do straight arithmetic on. They rely on the date format being yyyy-mm-dd hh:mm:ss, but manipulation of the parameters of the substring calls should allow you to process dates in any format you need.
Related
I am looking to find a way to control both start and end numbering that are added as suffix number to text "period". It seems the logic of my code makes for-each count 3 search matches (index 1-3), and due to using if to find se:Bank it reduces printout to 2 prints.
I know I can use position() -2 to force the numbering to start at 0, but that will only work if I have exact same amount of data. As soon as data growths the position() -2 will assume to step 2 positions minus and will not return zero as start.
I do understand that XSLT does what I actually ask for and returns presumable correct answer.
The interval of number I will be using is between 0 and 3. Most of the time in sequence.
It will be unknown in advance exact interval amounts.
I know there is a start-at using <xsl:number> but it did not solve my problem. Using start-at prints out same number twice.
The JSON file is aligned with a certain standard so I am not allowed to change the structure of the JSON file.
Suspected problem:
The "foreach" spans over more loops than wanted. In this case I need just for the system to loop twice for "se:Bank" and therefor return "period0" and "period1".
Observation:
I suspect that it probably would be better that I learn to extract when xbrl:concept = se:Bank. That would reduce the planned loop down to 2 search, thus being able iterate over them.
Here you find the xsltfiddle.
Below you find same code as in xsltfiddle:
JSON data:
<data>
{
"report": {
"facts": [
{
"xbrl:concept": "se:CompanyName",
"value": "Great Company Ltd"
},
{
"xbrl:concept": "se:Bank",
"numericValue": 1000
},
{
"xbrl:concept": "se:Bank",
"numericValue": 3000
}
]
}
}
</data>
XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0">
<xsl:output method="xhtml" indent="yes" html-version="5"/>
<xsl:mode on-no-match="shallow-skip"/>
<!-- Parse JSON to XML -->
<xsl:template match="data">
<xsl:apply-templates select="json-to-xml(.)/*"/>
</xsl:template>
<!-- Printout periods -->
<xsl:template match="//*[#key='facts']">
<xsl:for-each select="//*[#key='xbrl:concept']">
<xsl:if test=". = 'se:Bank'">
period<xsl:number value="position()"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Result:
<?xml version="1.0" encoding="UTF-8"?>period2period3
Expected result:
<?xml version="1.0" encoding="UTF-8"?>period0period1
If you want to output position() or position() - 1 then <xsl:value-of select="position()"/> or <xsl:value-of select="position() -1 "/>, respectively, suffice, there is no need to feed position() to xsl:number.
Furthermore, I am not sure I understand your requirements correctly, but using a predicate <xsl:for-each select=".//*[#key='xbrl:concept'][. = 'se:Bank']"> instead of the of the nested xsl:if should help to get the result you want, namely to process the two elements in the input sample meeting the condition in the predicate: https://xsltfiddle.liberty-development.net/93wniUS/1
I am trying to pad an output in XSLT with variables being multiplied in XSLT 1.0. I cannot use the format-number because it will auto-round the numbers but I need to get the decimals in place. I can get this to work when I use column formatting within the XML, but since this has multiple column formats within the value-of-select, it does not seem to work here and obey the 7 in the code below:
<xsl:value-of select="substring(E_BaseRate * E_NormalHours * 2, 1, 7)" disable-output-escaping="yes"/>
The output I am getting is: 1601.93 translated to 160193 but I need to get the result of 0160193. Is there a way to do this?
I broke this out to give you an idea. You could use as is or put it all together into one select...
<xsl:variable name="numToString" select="string(160193)"/>
<xsl:variable name="numLen" select="string-length($numToString)"/>
<xsl:variable name="value" select="concat(substring('0000000', 1, 7 - $numLen), $numToString)"/>
I need to convert the date from 12 hour format to 24 hour format.
Input:
01/27/2016 07:01:36 PM
Expected output:
201601271901(YYYYMMDDHHMM)
I have used format-dateTime() function in my code ,I am getting error
<xsl:value-of select="format-dateTime(part_need/promised_dt,'[Y0001][M01][D01][H01][m01]')"/>
Error:
Description: FORG0001: Invalid dateTime value "01/27/2016 07:01:36 PM" (Non-numeric year component)
Please help on this issue
Your input is not a valid ISO 8601 date/time, so you cannot use the built-in date/time functions on it.
Try instead something like (XSLT 2.0):
<xsl:template match="inputdate">
<xsl:copy>
<xsl:variable name="dte" select="tokenize(.,'/|\s|:')" />
<xsl:value-of select="$dte[3]" />
<xsl:value-of select="$dte[1]" />
<xsl:value-of select="$dte[2]" />
<xsl:variable name="h24" select="xs:integer($dte[4]) mod 12 + 12 * xs:integer($dte[7]='PM')" />
<xsl:value-of select="format-number($h24, '00')" />
<xsl:value-of select="$dte[5]" />
</xsl:copy>
</xsl:template>
Note that this assumes your days are zero-padded to two digits (as are your months).
If you need to use this in several places, consider turning it into a function.
format-dateTime takes an xs:dateTime? as the first parameter. The part_needed/promised_dt is a node.
If you have the date-time in the standard ISO format (e.g. "2006-01-27T19:01:36"), you can use xs:dateTime(part_needed/promised_dt).
Saxon does not have a non-standard date-time parser helper, so you would need to use the xs:dateTime(xs:date(year,month,day), xs:time(hours, minutes, seconds)) constructor and use something like substring(part_needed/promised_dt,1,2) to get each date/time part.
I have a fairly convoluted XML file and I need to do a weighted average of a few values within it using XSL. I am able to complete a sum of the weights OR of the values, but I cannot get the multiplication to work. I get an error:
XPTY0004: A sequence of more than one item is not allowed as the first
operand of '*'
I am not able to share the XML, but I have simplified the XML to the following example (assume there are a large number of foos):
<group>
<fooList>
<foo>
<attributeList>
<Attribute ID="1" Weight="0.5">
<otherParams />
</Attribute>
</attributeList>
<Properties>
<PhysicalProperties>
<Volume Average="125" Unknown="50" />
</PhysicalProperties>
</Properties>
</foo>
</fooList>
</group>
My current attempt to get the weighted average is the following:
<xsl:variable name="WeightedVolume" select="sum(/group/fooList/foo[attributeList/Attribute/[#ID=$test_id]]/attributeList/Attribute/#Weight * /group/fooList/foo[attributeList/Attribute/[#ID=$test_id]]/Properties/PhysicalProperties/Volume/#Average)"/>
I know there are similar questions available - but most of them deal with something like summing and multiplying foo
<foo>
<Weight>0.5</Weight>
<VolumeAverage>125</VolumeAverage>
</foo>
The answer on this StackOverflow Question appeals to me, but I cannot seem to make it work.
I'm using Saxon-HE 9.5.1.1N from Saxonica, with Visual Studio 2013.
Edited
I was able to get something to work for XSL 2, but need to have a fall-back for XSL1.
<xsl:variable name="WeightedVolume" select="sum(for $i in /group/FooList/foo[attributeList/Attribute[#ID=$test_id] return $i/AttributeList/Attribute/#Weight * $i/Properties/PhysicalProperties/Volume/#Average)"/>
To follow the example in that question you linked to, you would use this in XSLT 2.0/XPath 2.0:
<xsl:variable name="FoosToCalculate"
select="/group/fooList/foo[attributeList/Attribute/#ID = $test_id]" />
<xsl:variable name="WeightedVolume"
select="sum($FoosToCalculate/(attributeList/Attribute/#Weight *
Properties/PhysicalProperties/Volume/#Average)
)"/>
Doing this summing in XSLT 1.0 is considerably more involved and typically involves either using recursive templates or some manifestation of the node-set() function. Here is an example of the latter:
<xsl:stylesheet version="1.0"
xmlns:ex="http://exslt.org/common"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<!-- determine $test_id however you need to -->
<xsl:variable name="products">
<xsl:for-each
select="/group/fooList/foo[attributeList/Attribute/#ID = $test_id]">
<product>
<xsl:value-of select="attributeList/Attribute/#Weight *
Properties/PhysicalProperties/Volume/#Average" />
</product>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="sum(ex:node-set($products)/product)"/>
</xsl:template>
</xsl:stylesheet>
For completeness, if you want to sum over a computed quantity in XSLT 1.0, there are three ways of doing it:
(a) recursion: write a recursive template that processes the items in the sequence one by one, computing the total as it goes.
(b) create an XML tree in which the computed quantities are node values, and then process this tree using the sum() function. To do this in a single stylesheet you will need the exslt:node-set() extension function.
(c) use an extension function provided by the XSLT vendor, or user-written using the facilities provided by the vendor for calling external functions.
In XSLT 2.0, it can always be done using the construct
sum(for $x in node-set return f($x))
where f is a function that computes the quantity.
Lets say I have the following xml.
<root>
<data>
<a>ATTITUDE_ANNOYED</a>
<b>ATTITUDE_CAUTIOUS</b>
<c>25</c>
<d>30</d>
</data>
</root>
Ignoring the schema of my output, I want my output to present A as "Cautious" (one level up from annoyed), B as "
Pleased" (one level up from Cautious") and I want to perform some maths on C and D to convert the value into something slightly different.
I've had a look at a bunch of similar questions here (and I'm new to XSLT so maybe I don't quite get it) but a lot of the solutions appear to be "in-line", i.e. you modify the result as you transform it. This is okay but in my real example there are a lot of these values and I don't want to be performing the exact same conversion in multiple places (DRY). I just want to more or less pre-process the entire document and convert a bunch of values into other values (using just a few formulas) before I start the transformation.
What would be the best way to achieve this? I'm not particularly interested in performance so is there a way that I can run a prior transformation to easily transform specific values without modifying the structure?
UPDATE: (DevNull asked for my output desires) The output isn't exactly finalised. I'm trying to help a group at CivFanatics produce a guide on the differences between the AIs whose values derive from an xml file. There are a ton of leaders and a ton of values that need converting, by hand its taking them 2 hours per leader at the moment and the final formatting hasn't been decided on so I thought we'd all save time by using something like XLST.
Here is a rough example of a demo I'm working on.
<xsl:template match="data">
<h3>Attitude Thresholds</h3>
<table border="1">
<tr><td>Will open borders</td><td><xsl:value-of select="a"/></td></tr>
<tr><td>Will trade techs</td><td><xsl:value-of select="b"/></td></tr>
</table>
</xsl:template>
To clarify A and B are OpenBordersRefuseAttitudeThreshold and TechRefuseAttitudeThreshold. The guide is more readable if these are WillOpenBordersAt and WillTradeTechAt as opposed to the original values in the xml file so I need to nudge them up a value for the final output.
There are additional conversions that have been discussed:
iWonderConstructRand -> Builds Wonders
0 -> 0/10
5 -> 1/10
10 -> 2/10
15 -> 3/10
20 -> 4/10
25 -> 5/10
30 -> 6/10
35 -> 7/10
40 -> 8/10
45 -> 9/10
50 -> 10/10
So that's values such as C or D. They range between 0-50 and should be put into a 0-10 format for readability. There are a fair few of these values too.
Also there are conversions like "GoodieBaddie" which vary from 0 to 10. Which we wish to convert to something like:
0-3 -> Bad(x)
4-6 -> Neutral(x)
7-10 -> Good(x)
Where (x) is the original value.
Am I even using the right tool for the job here or is it borderline? I figured XLST would be a good choice to enable other contributors to not have to rely on the devs to make changes to the formatting/layout (as xlst is easier to edit then say C# or Python).
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<my:attitudes>
<a val="1">ANNOYED</a>
<a val="2">CAUTIOUS</a>
<a val="3">PLEASED</a>
</my:attitudes>
<xsl:variable name="vAttitudes" select="document('')/*/my:attitudes/*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[starts-with(., 'ATTITUDE_')]/text()">
<xsl:variable name="vVal" select=
"$vAttitudes[. = substring-after(current(), '_')]/#val"/>
<xsl:value-of select="concat('ATTITUDE_', $vAttitudes[#val = $vVal+1])"/>
</xsl:template>
<xsl:template match="*[floor(.) = .]/text()">
<xsl:value-of select="concat(round(. div 5), '/10')"/>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<root>
<data>
<a>ATTITUDE_ANNOYED</a>
<b>ATTITUDE_CAUTIOUS</b>
<c>25</c>
<d>30</d>
</data>
</root>
produces the wanted, correct result:
<root>
<data>
<a>ATTITUDE_CAUTIOUS</a>
<b>ATTITUDE_PLEASED</b>
<c>5/10</c>
<d>6/10</d>
</data>
</root>