XSLT add new node outside of current node - date

I'm trying to add a new element based on a date comparison. If the parent DATE is within the last 7 days, I want to add a new element. I wrote the code to do the date comparison but I'm having a hard time figuring out where to put it. Currently, it is in the template that reformats the parent DATE but this results in my new element inside the date element. Is there a way to create a new element outside of the current element? Thanks.
My Input
<?xml version="1.0" encoding="UTF-8"?>
<NOTICES>
<PRESOL>
<DATE>03012013</DATE>
<AGENCY><![CDATA[Department of the Interior]]></AGENCY>
<OFFICE><![CDATA[Fish and Wildlife Service]]></OFFICE>
<LOCATION><![CDATA[CGS-WO]]></LOCATION>
<ZIP>97232</ZIP>
<CHANGES>
<MOD>
<DATE>01112013</DATE>
<COUNTRY>US</COUNTRY>
</MOD>
</CHANGES>
</PRESOL>
<COMBINE>
<DATE>03012013</DATE>
<AGENCY><![CDATA[Department of the Air Force]]></AGENCY>
<OFFICE><![CDATA[Air Force Materiel Command]]></OFFICE>
<LOCATION><![CDATA[Tinker OC-ALC - (Central Contracting)]]></LOCATION>
<ZIP>73145-3015</ZIP>
</COMBINE>
<COMBINE>
<DATE>03052013</DATE>
<AGENCY><![CDATA[Department of the Navy]]></AGENCY>
<OFFICE><![CDATA[Military Sealift Command]]></OFFICE>
<LOCATION><![CDATA[MSC Norfolk]]></LOCATION>
</COMBINE>
<COMBINE>
<DATE>03292013</DATE>
<AGENCY><![CDATA[Department of Veterans Affairs]]></AGENCY>
<OFFICE><![CDATA[Grand Junction VAMC)]]></OFFICE>
<LOCATION><![CDATA[Veterans Affairs Medical Center]]></LOCATION>
</COMBINE>
<PRESOL>
<DATE>03302013</DATE>
<AGENCY><![CDATA[Department of the Air Force]]></AGENCY>
<OFFICE><![CDATA[Pacific Air Forces]]></OFFICE>
<LOCATION><![CDATA[354 CONS - Eielson]]></LOCATION>
<CHANGES>
<MOD>
<DATE>01112013</DATE>
<COUNTRY>US</COUNTRY>
</MOD>
<MOD>
<DATE>01112013</DATE>
<COUNTRY>UK</COUNTRY>
</MOD>
<MOD>
<DATE>01142013</DATE>
<COUNTRY>JAPAN</COUNTRY>
</MOD>
</CHANGES>
</PRESOL>
<FAIROPP>
<DATE>04012013</DATE>
<AGENCY><![CDATA[Department of the Navy]]></AGENCY>
<OFFICE><![CDATA[Bureau of Medicine and Surgery]]></OFFICE>
<LOCATION><![CDATA[NH Camp Pendleton]]></LOCATION>
<ZIP>92055</ZIP>
<CHANGES>
<MOD>
<DATE>02122011</DATE>
<COUNTRY>JAPAN</COUNTRY>
</MOD>
</CHANGES>
</FAIROPP>
</NOTICES>
Desired Output:
<?xml version="1.0" encoding="UTF-8"?>
<NOTICES>
<PRESOL>
<DATE>03012013</DATE>
<AGENCY><![CDATA[Department of the Interior]]></AGENCY>
<OFFICE><![CDATA[Fish and Wildlife Service]]></OFFICE>
<LOCATION><![CDATA[CGS-WO]]></LOCATION>
<ZIP>97232</ZIP>
<CHANGES>
<MOD>
<DATE>01112013</DATE>
<COUNTRY>US</COUNTRY>
</MOD>
</CHANGES>
</PRESOL>
<COMBINE>
<DATE>03012013</DATE>
<AGENCY><![CDATA[Department of the Air Force]]></AGENCY>
<OFFICE><![CDATA[Air Force Materiel Command]]></OFFICE>
<LOCATION><![CDATA[Tinker OC-ALC - (Central Contracting)]]></LOCATION>
<ZIP>73145-3015</ZIP>
</COMBINE>
<COMBINE>
<DATE>03052013</DATE>
<AGENCY><![CDATA[Department of the Navy]]></AGENCY>
<OFFICE><![CDATA[Military Sealift Command]]></OFFICE>
<LOCATION><![CDATA[MSC Norfolk]]></LOCATION>
</COMBINE>
<COMBINE>
<DATE>03292013</DATE>
**<mostrecent>YES</mostrecent>**
<AGENCY><![CDATA[Department of Veterans Affairs]]></AGENCY>
<OFFICE><![CDATA[Grand Junction VAMC)]]></OFFICE>
<LOCATION><![CDATA[Veterans Affairs Medical Center]]></LOCATION>
</COMBINE>
<PRESOL>
<DATE>03302013</DATE>
**<mostrecent>YES</mostrecent>**
<AGENCY><![CDATA[Department of the Air Force]]></AGENCY>
<OFFICE><![CDATA[Pacific Air Forces]]></OFFICE>
<LOCATION><![CDATA[354 CONS - Eielson]]></LOCATION>
<CHANGES>
<MOD>
<DATE>01112013</DATE>
<COUNTRY>US</COUNTRY>
</MOD>
<MOD>
<DATE>01112013</DATE>
<COUNTRY>UK</COUNTRY>
</MOD>
<MOD>
<DATE>01142013</DATE>
<COUNTRY>JAPAN</COUNTRY>
</MOD>
</CHANGES>
</PRESOL>
<FAIROPP>
<DATE>04012013</DATE>
**<mostrecent>YES</mostrecent>**
<AGENCY><![CDATA[Department of the Navy]]></AGENCY>
<OFFICE><![CDATA[Bureau of Medicine and Surgery]]></OFFICE>
<LOCATION><![CDATA[NH Camp Pendleton]]></LOCATION>
<ZIP>92055</ZIP>
<CHANGES>
<MOD>
<DATE>02122011</DATE>
<COUNTRY>JAPAN</COUNTRY>
</MOD>
</CHANGES>
</FAIROPP>
</NOTICES>
My XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output omit-xml-declaration="yes" indent="yes" cdata-section-elements="AGENCY DESC CLASSCOD CONTACT DATE NAICS LINK OFFADD OFFICE SUBJECT ZIP AGENCY ZIP"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="backdate1" select="current-date() -7*xs:dayTimeDuration('P1D')"/>
<xsl:variable name="backdate" select="xs:date(substring($backdate1, 1, 10))"/>
<!-- copy all nodes -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="DATE/text()">
<!-- format DATE mm/dd/yyyy -->
<xsl:value-of select="concat(substring(., 1, 2), '/', substring(., 3, 2), '/', substring(., 5, 4))"/>
**<!-- add new node mostrecent if date is within the last 7 days -->
<xsl:variable name="subtract_date" select="days-from-duration(xs:date(concat(substring(., 5, 4), '-', substring(., 1, 2), '-', substring(., 3, 2))) - xs:date(substring($backdate1, 1, 10)))"/>
<xsl:if test="$subtract_date >= 0">
<xsl:text disable-output-escaping="yes"><</xsl:text>
<xsl:text disable-output-escaping="yes">mostrecent</xsl:text>
<xsl:text disable-output-escaping="yes">></xsl:text>
<xsl:text disable-output-escaping="yes"></</xsl:text>
<xsl:text disable-output-escaping="yes">mostrecent</xsl:text>
<xsl:text disable-output-escaping="yes">></xsl:text>
</xsl:if>**
</xsl:template>
<!-- keep only the parent date node and delete all children date nodes -->
<xsl:template match="DATE[../ancestor::*/DATE]"/>
<!-- add new node type to each child node -->
<xsl:template match="NOTICES/child::node()">
<xsl:text disable-output-escaping="yes"><</xsl:text><xsl:value-of select="name(.)"/><xsl:text disable-output-escaping="yes">></xsl:text>
<xsl:apply-templates select="#*|node()"/>
<type>
<xsl:value-of select ="name(.)"/>
</type>
<xsl:text disable-output-escaping="yes"></</xsl:text><xsl:value-of select="name(.)"/><xsl:text disable-output-escaping="yes">></xsl:text>
</xsl:template>
</xsl:stylesheet>

You can do this if you match on the DATE element instead of its text node:
<xsl:template match="DATE">
<xsl:variable name="dStr" select="string(.)" />
<xsl:variable name="bdStr" select="string($backdate1)" />
<!-- format DATE mm/dd/yyyy -->
<xsl:copy>
<xsl:value-of select="concat(substring($dStr, 1, 2), '/',
substring($dStr, 3, 2), '/',
substring($dStr, 5, 4))"/>
</xsl:copy>
<!-- add new node mostrecent if date is within the last 7 days -->
<xsl:variable name="subtract_date"
select="days-from-duration(xs:date(
concat(substring($dStr, 5, 4), '-',
substring($dStr, 1, 2), '-',
substring($dStr, 3, 2))) -
xs:date(substring($bdStr, 1, 10)))"/>
<xsl:if test="$subtract_date >= 0">
<mostrecent>YES</mostrecent>
</xsl:if>
</xsl:template>

Related

Update the counter by +1 when another record with two similar values exists in xslt 2.0

I have the below xml:
<EmployeeLeaveDataUpsertRequest>
<Row>
<Emp_id>11</Emp_id>
<Pay_slip_no>1</Pay_slip_no>
<Pay_comp>AU_0299</Pay_comp>
<Hours>136</Hours>
<Date_from_ec>20170401</Date_from_ec>
<Date_to_ec>20170429</Date_to_ec>
<Date_ped> </Date_ped>
<No_of_period>1</No_of_period>
<Ma_ind>M</Ma_ind>
<Fa_ind>N</Fa_ind>
<Counter>1</Counter>
</Row>
<Row>
<Emp_id>12</Emp_id>
<Pay_slip_no>1</Pay_slip_no>
<Pay_comp>AU_0900</Pay_comp>
<Hours>40</Hours>
<Date_from_ec>20170206</Date_from_ec>
<Date_to_ec>20170210</Date_to_ec>
<Date_ped> </Date_ped>
<No_of_period>1</No_of_period>
<Ma_ind>M</Ma_ind>
<Fa_ind>N</Fa_ind>
<Counter>1</Counter>
</Row>
<Row>
<Emp_id>11</Emp_id>
<Pay_slip_no>1</Pay_slip_no>
<Pay_comp>AU_0299</Pay_comp>
<Hours>8</Hours>
<Date_from_ec>20170111</Date_from_ec>
<Date_to_ec>20170115</Date_to_ec>
<Date_ped> </Date_ped>
<No_of_period>1</No_of_period>
<Ma_ind>M</Ma_ind>
<Fa_ind>N</Fa_ind>
<Counter>1</Counter>
</Row>
In the above xml you can see that every record has an element counter with default value set as 1.
In an event for same Emp_id and Pay_comp then I need to set the counter as 1 for first record , 2 for second record and so on.
Like in the above xml you can see two records where the Emp_id is 11 and Pay_comp is AU_0299 -- so for the first one set the counter as 1 and for the next one a 2.
output xml:
<EmployeeLeaveDataUpsertRequest>
<Row>
<Emp_id>11</Emp_id>
<Pay_slip_no>1</Pay_slip_no>
<Pay_comp>AU_0299</Pay_comp>
<Hours>136</Hours>
<Date_from_ec>20170401</Date_from_ec>
<Date_to_ec>20170429</Date_to_ec>
<Date_ped> </Date_ped>
<No_of_period>1</No_of_period>
<Ma_ind>M</Ma_ind>
<Fa_ind>N</Fa_ind>
<Counter>1</Counter>
</Row>
<Row>
<Emp_id>11</Emp_id>
<Pay_slip_no>1</Pay_slip_no>
<Pay_comp>AU_0299</Pay_comp>
<Hours>8</Hours>
<Date_from_ec>20170111</Date_from_ec>
<Date_to_ec>20170115</Date_to_ec>
<Date_ped> </Date_ped>
<No_of_period>1</No_of_period>
<Ma_ind>M</Ma_ind>
<Fa_ind>N</Fa_ind>
<Counter>2</Counter>
</Row>
<Row>
<Emp_id>12</Emp_id>
<Pay_slip_no>1</Pay_slip_no>
<Pay_comp>AU_0900</Pay_comp>
<Hours>40</Hours>
<Date_from_ec>20170206</Date_from_ec>
<Date_to_ec>20170210</Date_to_ec>
<Date_ped> </Date_ped>
<No_of_period>1</No_of_period>
<Ma_ind>M</Ma_ind>
<Fa_ind>N</Fa_ind>
<Counter>1</Counter>
</Row>
I have tried for loop but couldn't succeed. Need your inputs on the XSLT code which could achieve it
You could use a key to identify duplicates, with XSLT 3.0 (as now supported by Saxon 9.8 or current versions of Altova XMLSpy and Raptor) it is as easy as:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
exclude-result-prefixes="xs math"
expand-text="yes"
version="3.0">
<xsl:key name="group" match="Row" use="Emp_id , Pay_comp" composite="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="Row[not(. is key('group', (Emp_id , Pay_comp))[1])]/Counter">
<xsl:copy>{index-of(key('group', (../Emp_id , ../Pay_comp)), ..)}</xsl:copy>
</xsl:template>
</xsl:stylesheet>
With XSLT 2.0 you can translate the above to
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
exclude-result-prefixes="xs math"
version="2.0">
<xsl:key name="group" match="Row" use="concat(Emp_id, '|', Pay_comp)"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Row[not(. is key('group', concat(Emp_id, '|', Pay_comp))[1])]/Counter">
<xsl:copy>
<xsl:value-of select="index-of(key('group', concat(../Emp_id, '|', ../Pay_comp)), ..)"/>
</xsl:copy>
</xsl:template>

How to find all numbers in a string with XSLT 1

There are some nice solutions to How to find all numbers in a string for XSLT 2 and even 3. How can I accomplish the exact same thing within the limits of XSLT 1 (withe the possible help of EXSLT)?
Here’s an example:
<data>
<sig>NL Mellin 1-1 36</sig>
<sig>NL Mellin 1-1 38</sig>
<sig>NL Mellin 1-10 02</sig>
<sig>NL Mellin 1-10 04</sig>
<sig>NL Mellin 1-10 09</sig>
</data>
The desired output would be:
1 1 36
1 1 38
1 10 02
1 10 04
1 10 09
Try it this way:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="utf-8" />
<xsl:template match="/">
<xsl:for-each select="data/sig">
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="translate(., '-', ' ')"/>
</xsl:call-template>
<xsl:if test="position()!=last()">
<xsl:text>
</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="text"/>
<xsl:param name="delimiter" select="' '"/>
<xsl:variable name="token" select="substring-before(concat($text, $delimiter), $delimiter)" />
<xsl:if test="$token = translate($token, translate($token, '0123456789', ''), '')">
<xsl:value-of select="$token"/>
<xsl:text> </xsl:text>
</xsl:if>
<xsl:if test="contains($text, $delimiter)">
<!-- recursive call -->
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring-after($text, $delimiter)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Note:
If you have multiple delimiters, you need to translate them to a common character (space in my example);
I didn't bother to remove the trailing space in each line;
If your processor supports the EXSLT str:tokenize() function, this could be simpler.

Defining time dimension in Mondrain Schema?

I am trying to define time dimension in Mondrian schema. In Mondrian time dimension it require 3 level-type must be Years, Quarters, Months.
But my table contain only one date field. So how it is possible?
Can I use postgreSQL query in Mondrian? So I can use 3 query to select Years, Quarters and Month from single date field.
Create simple table with only one date column:
create table tmp_cube as (select generate_series('2011-01-01'::date, '2012-01-01'::date, '1 day')::date gs);
Create dummy cube:
<Schema name="New Schema1">
<Cube name="Test" visible="true" cache="true" enabled="true">
<Table name="tmp_cube" schema="public" alias="">
</Table>
<Dimension type="TimeDimension" visible="true" foreignKey="gs" name="Time Dimension">
<Hierarchy name="New Hierarchy 0" visible="true" hasAll="true" primaryKey="gs">
<View alias="test_view">
<SQL dialect="generic">SELECT gs, extract(year from gs) as year, extract(quarter from gs) as quarter, extract(month from gs) as month FROM tmp_cube</SQL>
</View>
<Level name="Year" visible="true" column="year" type="Integer" internalType="int" uniqueMembers="false" levelType="TimeYears">
</Level>
<Level name="Quarter" visible="true" column="quarter" type="Integer" uniqueMembers="false" levelType="TimeQuarters">
</Level>
<Level name="Month" visible="true" column="month" type="Integer" uniqueMembers="false" levelType="TimeMonths">
</Level>
</Hierarchy>
</Dimension>
<Measure name="Count Rows" column="gs" aggregator="count" visible="true">
</Measure>
</Cube>
</Schema>
Now I see in Saiku:

CRM 2011 Chart: count of grouped dates for other field

I have an entity expressing employee absences split per half day (AM and PM). the entity has 4 relevant fields: the employee name (another custom entity), a reason for absence (holiday, overtime reimbursement, paid vacation, allowed leave,... 11 in total), the date of absence and whether it's an AM or PM absence (dropdown using 2 values). If an employee is absent the entire day, 2 records are made, 1 for AM and 1 for PM.
I now have to make a graph which shows the following:
Group all absences per employee (5 absent employees is 5 groups);
group all absences for a specific employee per reason for absence (an employee with 2 different absences gets 2 adjacent columns in his group);
The data shown is a count of all the absences, grouped by date (2 absences, 1 AM and 1 PM, on the same date should count as 1 absence, not 2). In effect, I need a count of unique dates.
I have managed to make a basic graph which has the employee and absence reason groups completed. However, I cannot figure out how to count unique dates.
What i've got so far is:
<visualization>
<visualizationid>{CA31385D-FE63-E311-A895-005056A03018}</visualizationid>
<name>Datum afwezigheid bij personeelsfiche en reden van afwezigheid</name>
<primaryentitytypecode>acm_tijdindeling</primaryentitytypecode>
<datadescription>
<datadefinition>
<fetchcollection>
<fetch mapping="logical" aggregate="true">
<entity name="acm_tijdindeling">
<attribute groupby="true" alias="group_personeelsfiche" name="acm_personeelsfiche" />
<attribute alias="count_datumafwezigheid" name="acm_datumafwezigheid" aggregate="count" />
<attribute groupby="true" alias="group_redenvanafwezigheid" name="acm_redenvanafwezigheid" />
</entity>
</fetch>
</fetchcollection>
<categorycollection>
<category alias="group_personeelsfiche">
<measurecollection>
<measure alias="count_datumafwezigheid" />
</measurecollection>
</category>
</categorycollection>
</datadefinition>
</datadescription>
<presentationdescription>
<Chart Palette="None" PaletteCustomColors="55,118,193; 197,56,52; 149,189,66; 117,82,160; 49,171,204; 255,136,35; 97,142,206; 209,98,96; 168,203,104; 142,116,178; 93,186,215; 255,155,83">
<Series>
<Series ChartType="Column" IsValueShownAsLabel="True" Font="{0}, 9.5px" LabelForeColor="59, 59, 59" CustomProperties="PointWidth=0.75, MaxPixelPointWidth=40"></Series>
</Series>
<ChartAreas>
<ChartArea BorderColor="White" BorderDashStyle="Solid">
<AxisY LabelAutoFitMinFontSize="8" TitleForeColor="59, 59, 59" TitleFont="{0}, 10.5px" LineColor="165, 172, 181" IntervalAutoMode="VariableCount">
<MajorGrid LineColor="239, 242, 246" />
<MajorTickMark LineColor="165, 172, 181" />
<LabelStyle Font="{0}, 10.5px" ForeColor="59, 59, 59" />
</AxisY>
<AxisX LabelAutoFitMinFontSize="8" TitleForeColor="59, 59, 59" TitleFont="{0}, 10.5px" LineColor="165, 172, 181" IntervalAutoMode="VariableCount">
<MajorTickMark LineColor="165, 172, 181" />
<MajorGrid LineColor="Transparent" />
<LabelStyle Font="{0}, 10.5px" ForeColor="59, 59, 59" />
</AxisX>
</ChartArea>
</ChartAreas>
<Titles>
<Title Alignment="TopLeft" DockingOffset="-3" Font="{0}, 13px" ForeColor="59, 59, 59"></Title>
</Titles>
<Legends>
<Legend Alignment="Center" LegendStyle="Table" Docking="right" IsEquallySpacedItems="True" Font="{0}, 11px" ShadowColor="0, 0, 0, 0" ForeColor="59, 59, 59" />
</Legends>
</Chart>
</presentationdescription>
<isdefault>false</isdefault>
</visualization>
I've tried adding groupby="true" dategrouping="day" to the count_datumafwezigheid aggregate, but then I get an "invalid XML" error.
Here's a link on how to count unique items http://crmchartguy.wordpress.com/2013/12/12/count-distinct-or-unique-items-in-ms-crm-charts/
I had a similar problem once - im not sure its possible to aggregate DateTime - See Thread:
Fetch DateTime CRM 2011

XSL Transform extracting min and max dates

I'm trying to extract min and max dates from an XML source. I'm getting a nodeset into my variables and I need the actual date value within the nodes and can't find how to get it.
Source XML:
<Info dataSource="source">
<Detail>
<StartDate>20121211</StartDate>
<EndDate>20130112</EndDate>
</Detail>
<Detail>
<StartDate>20121211</StartDate>
<EndDate>20130112</EndDate>
</Detail>
<Detail>
<StartDate>20121211</StartDate>
<EndDate>20130112</EndDate>
</Detail>
<Detail>
<StartDate>20121218</StartDate>
<EndDate>20130114</EndDate>
</Detail>
</Info>
The XSL code:
<xsl:if test="//StartDate != '' and //EndDate != ''">
<xsl:variable name ="startDate">
<xsl:for-each select="//StartDate">
<xsl:sort select="StartDate" data-type="text" order="ascending"/>
<xsl:if test="position() = 1">
<xsl:value-of select="." />
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:variable name ="endDate">
<xsl:for-each select="//EndDate">
<xsl:sort select="EndDate" data-type="text" order="descending"/>
<xsl:if test="position() = 1">
<xsl:value-of select="." />
</xsl:if>
</xsl:for-each>
</xsl:variable>
</xsl:if>
The dates are formatted correctly to support the sorts and retrieval, the issue is once the variables are populated I can't find how to access their values:
If you're using XSLT 2.0 you have min and max functions which do the work for you. XSLT 1.0 doesn't have these functions, but you can cheat by doing this (may be rather inefficient for large inputs, but it works):
<xsl:variable name="startDate" select="string(//StartDate[not(. > //StartDate)])" />
and similarly for the endDate. The trick here is that you're looking for the StartDate d for which there is no other StartDate that d is greater than.