Xslt 1.0 : Adding hours (integer) to time stamp - datetime

I am struggeling to add 2 hours to a time stamp. It is not a possibility to upgrade to XSLT version 2.0 unformatunately.
08:40:20
Desired output:
10:40:20

Well, you could do simply:
<xsl:template match="Time">
<DesiredTime>
<xsl:value-of select="substring-before(., ':') + 2" />
<xsl:text>:</xsl:text>
<xsl:value-of select="substring-after(., ':')" />
</DesiredTime>
</xsl:template>
If you want it to roll back to 0 after midnight, then do:
<xsl:value-of select="(substring-before(., ':') + 2) mod 24" />
and if you want a leading zero then wrap it in format-number().

Related

Numbering tables where multiple instances count as one table XSLT 3.0

Given this XML:
<preliminaryRqmts>
<!-- Table 1 -->
<reqCondGroup>
<reqCondNoRef>
<reqCond>Lorem ipsum</reqCond>
</reqCondNoRef>
</reqCondGroup>
<!-- Table 2 -->
<reqPersons>
<person man="A">
<personCategory personCategoryCode="Chemical technician"/>
<personSkill skillLevelCode="sk02"/>
<trade>Cleaner</trade>
<estimatedTime unitOfMeasure="h">1,0</estimatedTime>
</person>
</reqPersons>
<reqPersons>
<person man="B">
<personCategory personCategoryCode="Operator"/>
<personSkill skillLevelCode="sk02"/>
<trade>Painter</trade>
<estimatedTime unitOfMeasure="h">1,0</estimatedTime>
</person>
</reqPersons>
<reqPersons>
<person man="B">
<personCategory personCategoryCode="Operator"/>
<personSkill skillLevelCode="sk03"/>
<trade>Rider</trade>
<estimatedTime unitOfMeasure="h">0,8</estimatedTime>
</person>
</reqPersons>
<!-- Table 3 -->
<reqSafety>
<noSafety/>
</reqSafety>
</preliminaryRqmts>
<taskDefinition>
<task>
<taskDescr>
<simplePara>Lorem ipsum</simplePara>
</taskDescr>
</task>
<preliminaryRqmts>
<!-- Table 4 -->
<reqCondGroup>
<noConds/>
</reqCondGroup>
<!-- Table 5 -->
<reqPersons>
<person man="A">
<personCategory personCategoryCode="Basic user"/>
<trade>Operator</trade>
<estimatedTime unitOfMeasure="h">0,3</estimatedTime>
</person>
</reqPersons>
<!-- Table 6 -->
<reqSpares>
<noSpares/>
</reqSpares>
</preliminaryRqmts>
</taskDefinition>
I have to include the Table number when outputting the table title. <reqPersons> may have multiple siblings but they are counted as one table. So in the XML provided, the first three reqPersons are counted as one table, Table 2. reqPersons is not a required element so there could be <preliminaryRqmts> without any <reqPersons>.
I am having trouble getting the correct table numbering for reqPersons when there is more than one preliminaryRqmts with a reqPersons. Originally I had <xsl:value-of select="if(preceding::reqPersons) then 1 else 0"/>. This fails when there are multiple preliminaryRqmts/reqPersons.
Here are the templates for the table numbering and reqPersons. Only the first reqPersons gets a title and table number. Any following-siblings::reqPersons are ignored in the table count. I need help with fixing <xsl:variable name="countPer" select="count(ancestor-or-self::preliminaryRqmts/reqPersons[1])"/>, the rest of the numbering is working properly.
<xsl:template match="reqPersons[1]">
<fo:block>
<xsl:text>Table </xsl:text>
<xsl:call-template name="number-tables"/>
<xsl:text>  Required persons</xsl:text>
</fo:block>
<fo:table>
<xsl:call-template name="reqPersonTableBody"/>
</fo:table>
</fo:block>
</xsl:template>
<xsl:template match="reqPersons">
<fo:block>
<fo:table>
<xsl:call-template name="reqPersonTableBody"/>
</fo:table>
</fo:block>
</xsl:template>
<xsl:template name="reqPersonTableBody">
<fo:table-column column-number="1" column-width="33%"/>
<fo:table-column column-number="2" column-width="33%"/>
<fo:table-column column-number="3" column-width="33%"/>
<fo:table-header>
<fo:table-row>
<fo:table-cell>
<fo:block>Person</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>Category</fo:block>
</fo:table-cell>
<fo:table-cell>
<fo:block>Skill level</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-header>
<fo:table-body>
<xsl:apply-templates select="personnel | person"/>
</fo:table-body>
</xsl:template>
<xsl:template name="number-tables">
<xsl:variable name="countreqCondTables" select="count(preceding::reqCondGroup|preceding::reqSupportEquips|preceding::reqSupplies|preceding::reqSpares|preceding::reqTechInfoGroup)"/>
<xsl:variable name="countPer" select="count(ancestor-or-self::preliminaryRqmts/reqPersons[1])"/>
<xsl:variable name="countSelfPer" select="count(ancestor-or-self::reqPersons[1])"/>
<xsl:variable name="countSelf" select="count(ancestor-or-self::table|ancestor-or-self::reqCondGroup|ancestor-or-self::reqSupportEquips|ancestor-or-self::reqSupplies|
ancestor-or-self::reqSpares|ancestor-or-self::reqTechInfoGroup)"/>
<xsl:value-of select="$countPer+$countSelf+$countSelfPer+$countreqCondTables"/>
</xsl:template>
I would check whether you can use xsl:number count="some pattern matching the elements you want to count" level="any" e.g. in XSLT 3
<xsl:param name="table-count-pattern" static="yes" as="xs:string" select="'preliminaryRqmts/reqCondGroup | preliminaryRqmts/reqPersons[1] | preliminaryRqmts/reqSafety | preliminaryRqmts/reqSpares'"/>
<xsl:template _match="{$table-count-pattern}">
<xsl:comment>computed table <xsl:number _count="{$table-count-pattern}" level="any"/></xsl:comment>
<xsl:next-match/>
</xsl:template>
seems to give the numbers you have in your comments output in another comment. Of course, you don't want to output a comment, you want to output that number in your fo:block but you can obviously adapt the above suggestion easily. If needed, if you have lots of different templates for all the elements that need to output that count, put the above template in a mode with e.g. mode="count", and in your other templates, where you need to number, use e.g. <xsl:apply-templates select="." mode="count"/>. You will probably want to remove the xsl:next-match in that case.

Need to reduce the 20 days prior from the input xml

Need to get the 20 days prior from the input xml element.
Input xml I'm having(year-month-day):
<section>
<Plans>
<Date>2022-01-01</Date>
</Plans>
</section>
<p>20 days back date: <keyword keyref="Cost:Date:date4"/></p>
XSL I'm using
<xsl:template match="keyword[contains(#keyref, 'Cost:Date:date4')]">
<xsl:param name="section" as="element()" tunnel="yes">
<empty/>
</xsl:param>
<keyword keyref="Cost:Date:date4">
<xsl:call-template name="format_variable">
<xsl:with-param name="cur_keyref" select="#keyref"/>
<xsl:with-param name="cur_value"
select="$section//Plans/Date"/>
<xsl:with-param name="cur_format" select="'date4'"/>
</xsl:call-template>
</keyword>
</xsl:template>
<xsl:template name="format_variable">
<xsl:param name="cur_keyref" as="xs:string">MISSING</xsl:param>
<xsl:param name="cur_value" as="xs:string">MISSING</xsl:param>
<xsl:param name="cur_format" as="xs:string">MISSING</xsl:param>
<xsl:choose>
<xsl:when test="$cur_format = 'date4'">
<xsl:variable name="format" select="replace($cur_value, '(.{4})-(.{2})-(.{2})', '$2/$3/$1')"/>
<xsl:value-of select="$format - xs:dateTime('P20D')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of
select="concat('MISSING_FORMAT_', $cur_keyref, '_', $cur_value, '_[', $cur_format, ']')"
/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
When I'm trying the above code its showing the below error:
Invalid dateTime value "P20D" (Non-numeric year component)
Expected output is:
20 days back date: 12/12/2021
FWIW, I believe you are grossly overcomplicating this. The date in the input XML is in YYYY-MM-DD format; all you need to do is subtract a duration of 20 days from the given date - see demo here: https://xsltfiddle.liberty-development.net/6qaCymJ
I think you want to subtract a dayTimeDuration with e.g. <xsl:value-of select="$format - xs:dayTimeDuration('P20D')"/>.

How to calculate year old date range from the current date(EDT) in xslt 1.0

Could some one please suggest me out to calculate 1 year back old date time from the current date time which is in EDT format using XSLT 1.0.
I know using xslt 2.0 there are function like 'yearMonthDuration' but need to perform this in xslt 1.0.
I have this
'''
2021-09-21
11:14:20 EDT
'''
I need to get as 20200921111420 EDT. This is the format I will need to convert as shown YYYYMMDDTTMMSS EDT
Please help in this regard.
Can I extract year column and add 365 to this will that work?
If all you want to do is add 1 year to a given date, you could start by extracting the year component and simply adding 1 to it, leaving the month and day components as they are. However, this will produce an invalid date if the given date is February 29 in a leap year. Still, it's rather simple to correct for that - say something like:
<xsl:value-of select="substring($given-date, 1, 4) + 1"/>
<xsl:variable name="md" select="substring($given-date, 5, 10)" />
<xsl:choose>
<xsl:when test="$md='-02-29'">-02-28</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$md"/>
</xsl:otherwise>
</xsl:choose>
Added:
I have this ''' 2021-09-21 11:14:20 EDT ''' I need to get as 20200921111420 EDT
That's equally simple:
<xsl:value-of select="substring($given-date, 1, 4) - 1"/>
<xsl:variable name="mdt" select="translate(substring($given-date, 6, 14), '-: ', '')" />
<xsl:choose>
<xsl:when test="starts-with($mdt, '0229')">
<xsl:text>0228</xsl:text>
<xsl:value-of select="substring($mdt, 5)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$mdt"/>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="substring($given-date, 20)"/>
EXSLT.Date has some support e.g.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:date="http://exslt.org/dates-and-times"
exclude-result-prefixes="xs date">
<xsl:template match="date">
<xsl:copy>
<xsl:value-of select="concat(., ' - ', date:add(., 'P1Y'))"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
gives <date>2021-09-22 - 2022-09-22</date> for an input of e.g. <date>2021-09-22</date>.
So check whether you use processor supporting that or can use an extension to your XSLT processor, for .NET XslCompiledTransform there is one with https://www.nuget.org/packages/Mvp.Xml.NetStandard/.

Conversion of hh:mm:ss to mm:ss doesn't work within for-each element

I am attempting to transform an XML file to a format that a third party software can read. My issue is in terms of how time is formatted in each of the XML files. The output from the first is formatted as hh:mm:ss however the target software gets confused and reads this as mm:ss. So any event appears to be 60 times shorter than it really is! I have been advised by the developers that I need to convert hours and minutes to minutes.
Early on in the template, whilst converting summary data, I have used the following:
<xsl:attribute name="duration">
<xsl:value-of select="format-number(floor(dive/summary/calculated/#duration div 60), '00')"/>
<xsl:value-of select="format-number(dive/summary/calculated/#duration mod 60, ':00')"/>
</xsl:attribute>
Which neatly converted 1:02:50 into 62:50. So far, so good. When I use the same construct within a for-each element then things don't work so well. The relevant part of my template is as follows:
<xsl:for-each select="dive/data/row">
<sample>
<xsl:attribute name="duration">
<xsl:value-of select="format-number(floor(#duration div 60), '00')"/>
<xsl:value-of select="format-number(#duration mod 60, ':00')"/>
</xsl:attribute>
<xsl:attribute name="depth"><xsl:value-of select="format-number(#depth, '0.00')"/></xsl:attribute>
<xsl:attribute name="temp"><xsl:value-of select="#temp"/></xsl:attribute>
<xsl:attribute name="po2"><xsl:value-of select="format-number(#po2-av, '0.00')"/></xsl:attribute>
</sample>
</xsl:for-each>
The following shows an example of the relevant part of my XML output:
<sample duration="NaNNaN" depth="7.30" temp="18" po2="0.65"/>
<sample duration="NaNNaN" depth="7.50" temp="18" po2="0.56"/>
<sample duration="NaNNaN" depth="7.50" temp="18" po2="0.55"/>
I have also tried to use a variable to format the time-stamps but end up with compilation errors within the stylesheet:
<xsl:variable name="dur" select="#duration" />
<xsl:value-of select="format-number(floor($seconds div 60) mod 60, ':00')"/>
<xsl:value-of select="format-number($seconds mod 60, ':00')"/>
<xsl:attribute name="time"><xsl:value-of select="#dur"/></xsl:attribute>
Firstly can anyone explain why the number-format works in the first instance but not within a for-each element. Secondly I would appreciate thoughts on what XLST code would work.
EDIT: sample input XML now included:
<dive UID="VMS-RED-5051_#-127#4AB4B4675CCE7036_dive_842960947_3084" profile="1" interface-version="vms-2-5" code-version="3.1.9" record-interval="5" serial-number="4AB4B4675CCE7036" records="755" total-hours=" 47" summary-only="false">
<summary time-start=" 842960947" max-depth=" 30.84" time-end=" 842964718" time-offset="-127" time-datum="1991-12-31 00:00:00">
<tissues ... </tissues>
<gasses> ... </gasses>
<calculated duration="3771" start-date="2018-09-16 11:51:14" average-depth="13.277" average-tpm="12.877"/>
</summary>
<data>
<row index="0" depth="1.1" po2-av="0.64" valve="71" temp="19" filter="68" gas-no="1" bat-2="3.96" bat-1="3.96" hp-dil="219" hp-02="175" po2-cell-1="0.64" po2-cell-2="0.65" po2-cell-3="0.65" setpoint="0.60" tissue-ceiling="0" hud="1" sensor_flags="97" co2="0.00" duration="00:00:00">
<tpm .../>
</row>
<row index="1" depth="1.1" po2-av="0.64" valve="0" temp="19" filter="68" gas-no="1" bat-2="3.96" bat-1="3.96" hp-dil="218" hp-02="175" po2-cell-1="0.64" po2-cell-2="0.65" po2-cell-3="0.65" setpoint="0.60" tissue-ceiling="0" hud="1" sensor_flags="97" co2="0.00" duration="00:00:05">
<tpm .../>
</row>
...
<row index="752" depth="1.7" po2-av="0.89" valve="6" temp="14" filter="37" gas-no="1" bat-2="3.95" bat-1="3.95" hp-dil="168" hp-02="117" po2-cell-1="0.89" po2-cell-2="0.88" po2-cell-3="0.90" setpoint="0.92" tissue-ceiling="0" hud="1" sensor_flags="97" co2="1.25" duration="01:02:40">
<tpm .../>
<alarms>
<alarm> ... </alarms>
</row>
<row index="753" depth="0.9" po2-av="0.87" valve="4" temp="14" filter="37" gas-no="1" bat-2="3.95" bat-1="3.95" hp-dil="168" hp-02="117" po2-cell-1="0.87" po2-cell-2="0.86" po2-cell-3="0.88" setpoint="0.86" tissue-ceiling="0" hud="1" sensor_flags="97" co2="1.27" duration="01:02:45">
<tpm .../>
<alarms>
<alarm> ... </alarms>
</row>
<row index="754" depth="0.2" po2-av="0.77" valve="0" temp="14" filter="37" gas-no="1" bat-2="3.95" bat-1="3.95" hp-dil="168" hp-02="116" po2-cell-1="0.77" po2-cell-2="0.77" po2-cell-3="0.78" setpoint="0.80" tissue-ceiling="0" hud="1" sensor_flags="97" co2="1.27" duration="01:02:50">
<tpm .../>
<alarms>
<alarm> ... </alarms>
</row>
</data>
</dive>
On your calculated element, the duration attribute has value "3771" which is a number (of seconds?)
However, on the row element, the duration attribute has value "01:02:40" (for example) which is not a number, and so you cannot perform numeric calculations on it.
However, you can use some string functions to extract the components, and perform numeric functions on that.
For example
<xsl:attribute name="duration">
<xsl:value-of select="format-number(number(substring-before(#duration, ':')) * 60 + number(substring-before(substring-after(#duration, ':'), ':')), '00')"/>
<xsl:value-of select="substring-after(substring-after(#duration, ':'), ':')" />
</xsl:attribute>
However, if you can use XSLT 2.0, you can make use of format-time to make it a bit more readable
<xsl:attribute name="duration">
<xsl:value-of select="format-number(xs:integer(format-time(#duration, '[H1]')) * 60 + xs:integer(format-time(#duration, '[m1]')), '00')" />
<xsl:value-of select="format-time(#duration, '[s01]')" />
</xsl:attribute>
(Where xs: is declared as namespace http://www.w3.org/2001/XMLSchema)
You are doing arithmetic on the #duration attribute:
floor(#duration div 60)
The #duration attribute in your example input is not a number:
duration="00:00:00"
Arithmetic on values that aren't numbers gives you NaN.

Mapping two records to same node, where one is an iteration of a child record of the other

I wish to create a iteration of a node to a node on the source schema. This is easy enough, but the trouble comes when I want to create different iterations on the same node based on a child of the first node.
<cases>
<customer>
<account>
<name>John Smith</name>
<address>hello road 321</address>
<current_balance>100</current_balance>
<current_balance_date>20180712</current_balance_date>
</account>
<invoices>
<invoice>
<amount>231</amount>
<paydate>20183104</paydate>
</invoice>
<invoice>
<amount>2332</amount>
<paydate>20181204</paydate>
</invoice>
</invoices>
</customer>
</cases>
There can be one current_balance per customer, but several invoices, and I need to map them to the same node on the target schema, and have it look like this:
<basis>
<toPay>100</toPay>
<dateToPay>20180712</dateToPay>
</basis>
<basis>
<toPay>231</toPay>
<dateToPay>20183104</dateToPay>
</basis>
<basis>
<toPay>2332</toPay>
<dateToPay>20181204</dateToPay>
</basis>
I have tried table looping, regular looping, conditional looping, and created xslt (which I also am very inexperienced with), and can't seem to make it work. I am only able to make either one of each or two of each.
Edit: I am currently trying an xslt-inline-call:
<xsl:template name="basis">
<!-- balance-parameters -->
<xsl:param name="current_balance" />
<xsl:param name="current_balance_date" />
<!-- invoice-parameters -->
<xsl:param name="amount" />
<xsl:param name="paydate" />
<xsl:element name="basis">
<xsl:element name="toPay"><xsl:value-of select="$current_balance" /></xsl:element>
<xsl:element name="dateToPay"><xsl:value-of select="$current_balance_date" /></xsl:element>
</xsl:element>
<xsl:for-each select="cases/customer/account/invoices/invoice">
<xsl:element name="basis">
<xsl:element name="toPay"><xsl:value-of select="$amount" /></xsl:element>
<xsl:element name="dateToPay"><xsl:value-of select="$paydate" /></xsl:element>
</xsl:element>
</xsl:for-each>
</xsl:template>
The for-each does not output anything at all, I have tried both case/customer/invoices/invoice and case/customer/invoices. I just cannot make this work at all
This one should work. You should loop in /cases/customer/account/invoices/invoice not case/customer/invoices/invoice
<xsl:template name="basis">
<basis>
<toPay>
<xsl:value-of select="/cases/customer/account/current_balance" />
</toPay>
<dateToPay>
<xsl:value-of select="/cases/customer/account/current_balance_date" />
</dateToPay>
</basis>
<xsl:for-each select="/cases/customer/account/invoices/invoice">
<basis>
<toPay>
<xsl:value-of select="amount" />
</toPay>
<dateToPay>
<xsl:value-of select="paydate" />
</dateToPay>
</basis>
</xsl:for-each></xsl:template>
First, make sure the source Schema has basis and set as maxOccurs = unbounded.
This is actually really simple. current_balance should work with just links.
You will need Looping Functoid linking invoice and basis and another Looping Functoid linking current_balance and basis.
That should create two for-each in the xsl which you can view using Validate Map.

Resources