How to find all numbers in a string with XSLT 1 - numbers

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.

Related

format month of grouped news items in Typo3

I have news items, grouped by year and month. Here the code:
<f:groupedFor each="{paginatedNews}" as="groupedNews1" groupBy="yearOfDatetime" groupKey="year">
<f:groupedFor each="{groupedNews1}" as="groupedNews" groupBy="monthOfDatetime" groupKey="month">
<div style="border:1px solid blue;padding:10px;margin:10px;">
<h1>{month} {year} </h1>
<f:for each="{groupedNews}" as="newsItem">
<f:render partial="List/Item" arguments="{newsItem: newsItem,settings:settings,iterator:iterator}" />
</f:for>
</div>
</f:groupedFor>
</f:groupedFor>
Unfortunately, month and year are displayed in numbers in frontend, e.g.
03 2019
..(some news items)
02 2019
...
01 2019
However, what I need is this:
March 2019
...
February 2019
...
January 2019
How can I achieve this?
On the original EXT:news this method has been used:
Look at the template EXT:news/Resources/Private/Templates/News/DateMenu.html
<f:translate key="month.{month}" />
on the EXT:news/Resources/Private/Language/locallang.xlf you will find:
<trans-unit id="month.01" xml:space="preserve">
<source>January</source>
</trans-unit>
And so forth.

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>

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:

XSLT add new node outside of current node

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>

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.