How can I optimize this nested grouping? - xslt-1.0

I have an XML as below example, that can contain up to 5000 lines, but I've limited it to 20 to be a bit reasonable.
<PMT NM="rnt-model">
<PV V="L11-L23-L3448-L42375_MODEL1" C="1"></PV>
<PV V="L11-L23-L3448-L448_MODEL2" C="1"></PV>
<PV V="L11-L23-L3448-L448_MODEL3" C="1"></PV>
<PV V="L11-L23-L3448-L448_MODEL4" C="1"></PV>
<PV V="L11-L23-L3448-L448_MODEL5" C="2"></PV>
<PV V="L11-L24-L319-L493_MODEL6" C="1"></PV>
<PV V="L11-L25-L3288-L41931_MODEL7" C="1"></PV>
<PV V="L110-L254-L3217-L41303_MODEL8" C="1"></PV>
<PV V="L110-L254-L3217-L41303_MODEL9" C="1"></PV>
<PV V="L110-L254-L3217-L41303_MODEL10" C="1"></PV>
<PV V="L110-L254-L3217-L41303_MODEL11" C="1"></PV>
<PV V="L110-L254-L3217-L41303_MODEL12" C="1"></PV>
<PV V="L110-L254-L3217-L41303_MODEL13" C="1"></PV>
<PV V="L110-L254-L3217-L41303_MODEL14" C="1"></PV>
<PV V="L110-L254-L3217-L41303_MODEL15" C="1"></PV>
<PV V="L110-L254-L3217-L41303_MODEL16" C="1"></PV>
<PV V="L110-L254-L3217-L41303_MODEL17" C="1"></PV>
<PV V="L110-L254-L3217-L41303_MODEL18" C="1"></PV>
<PV V="L110-L254-L3218-L41307_MODEL19" C="1"></PV>
<PV V="L110-L254-L3218-L41307_MODEL20" C="1"></PV>
</PMT>
The XML has to be transformed from this pseudo flat format to a tree structure based on #V. #V needs to be split in 2 first based on the underscore, and then tokenized on the hyphen.
Or to make it more visible, below is the expected outcome.
<root>
<n id="L11">
<n id="L23">
<n id="L3448">
<n id="L42375">
<n m="MODEL1" c="1"></n>
</n>
<n id="L448">
<n m="MODEL2" c="1"></n>
<n m="MODEL3" c="1"></n>
<n m="MODEL4" c="1"></n>
<n m="MODEL5" c="2"></n>
</n>
</n>
</n>
<!-- rest of rows below -->
I managed to get it working using below XSLT, and it works pretty ok when the amount of rows is small. However, when using real live XML it takes a lot of time to generate the tree so I'm wondering how I can make things more efficient. It's pretty straightforward to do in XSLT2, but I'm stuck with 1.0 for the project I'm working on.
XSLT code used (working but not very efficient)
<?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:key name="level1" use="#L1" match="row"/>
<xsl:key name="level2" use="#L2" match="row"/>
<xsl:key name="level3" use="#L3" match="row"/>
<xsl:key name="level4" use="#L4" match="row"/>
<xsl:template match="//PMT[#NM='rnt-model']">
<root>
<!-- Step 1 : generate a table with all the tokenized attributes -->
<xsl:variable name="theTree">
<xsl:for-each select="PV">
<row>
<xsl:for-each select="#V">
<xsl:call-template name="tokenize_tree">
<xsl:with-param name="list" select="substring-before(.,'_')"/>
<xsl:with-param name="delimiter" select="'-'"/>
</xsl:call-template>
<xsl:attribute name="M"><xsl:value-of select="substring-after(.,'_')"/></xsl:attribute>
</xsl:for-each>
<xsl:attribute name="C"><xsl:value-of select="#C"/></xsl:attribute>
</row>
</xsl:for-each>
</xsl:variable>
<!-- Step 2 : Group all -->
<xsl:for-each select="$theTree//row[generate-id()=generate-id(key('level1',#L1)[1])]">
<xsl:variable name="theType" select="#L1"/>
<n id="{$theType}">
<xsl:for-each select="$theTree/row[#L1=$theType][generate-id()=generate-id(key('level2',#L2)[1])]">
<xsl:variable name="theCat" select="#L2"/>
<n id="{$theCat}">
<xsl:for-each select="$theTree/row[#L2=$theCat][generate-id()=generate-id(key('level3',#L3)[1])]">
<xsl:variable name="theSubCat" select="#L3"/>
<n id="{$theSubCat}">
<xsl:for-each select="$theTree/row[#L3=$theSubCat][generate-id()=generate-id(key('level4',#L4)[1])]">
<xsl:variable name="theSerie" select="#L4"/>
<n id="{$theSerie}">
<xsl:for-each select="$theTree/row[#L4=$theSerie]">
<n m="{#M}" c="{#C}"/>
</xsl:for-each>
</n>
</xsl:for-each>
</n>
</xsl:for-each>
</n>
</xsl:for-each>
</n>
</xsl:for-each>
</root>
</xsl:template>
<xsl:template name="tokenize_tree">
<!--passed template parameter -->
<xsl:param name="list"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<xsl:attribute name="{substring(substring-before($list,$delimiter),1,2)}"><xsl:value-of select="substring-before($list,$delimiter)"/></xsl:attribute>
<!-- get everything in front of the first delimiter -->
<xsl:call-template name="tokenize_tree">
<!-- store anything left in another variable -->
<xsl:with-param name="list" select="substring-after($list,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="$list = ''">
<xsl:text/>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="{substring($list,1,2)}"><xsl:value-of select="$list"/></xsl:attribute>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Any ideas on how to improve efficiency so it works faster with bigger files ?

Efficiency is very much processor-dependent, but perhaps you should try a single-pass approach:
XSLT 1.0
<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:strip-space elements="*"/>
<xsl:key name="level1" match="PV" use="substring-before(#V, '-')" />
<xsl:key name="level2" match="PV" use="substring-before(substring-after(#V, '-'), '-')" />
<xsl:key name="level3" match="PV" use="substring-before(substring-after(substring-after(#V, '-'), '-'), '-')" />
<xsl:key name="level4" match="PV" use="substring-before(substring-after(substring-after(substring-after(#V, '-'), '-'), '-'), '_')" />
<xsl:template match="/PMT">
<root>
<xsl:for-each select="PV[count(. | key('level1', substring-before(#V, '-'))[1]) = 1]">
<xsl:variable name="L1" select="substring-before(#V, '-')" />
<n id="{$L1}">
<xsl:for-each select="key('level1', $L1)[count(. | key('level2', substring-before(substring-after(#V, '-'), '-'))[1]) = 1]">
<xsl:variable name="L2" select="substring-before(substring-after(#V, '-'), '-')" />
<n id="{$L2}">
<xsl:for-each select="key('level2', $L2)[count(. | key('level3', substring-before(substring-after(substring-after(#V, '-'), '-'), '-'))[1]) = 1]">
<xsl:variable name="L3" select="substring-before(substring-after(substring-after(#V, '-'), '-'), '-')" />
<n id="{$L3}">
<xsl:for-each select="key('level3', $L3)[count(. | key('level4', substring-before(substring-after(substring-after(substring-after(#V, '-'), '-'), '-'), '_'))[1]) = 1]">
<xsl:variable name="L4" select="substring-before(substring-after(substring-after(substring-after(#V, '-'), '-'), '-'), '_')" />
<n id="{$L4}">
<xsl:for-each select="key('level4', $L4)">
<n m="{substring-after(substring-after(substring-after(substring-after(#V, '-'), '-'), '-'), '_')}" c="{#C}"/>
</xsl:for-each>
</n>
</xsl:for-each>
</n>
</xsl:for-each>
</n>
</xsl:for-each>
</n>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Note also that an explicit path will usually be more efficient than //.

Related

XSLT 1.0 Select Name field based on evaluation of Date and Time fields

I have XML form data coming in from a vendor app that allows me to select values for fields on the form by using this format.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsl:stylesheet version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output method="text" /><xsl:template match="/"><xsl:apply-templates/></xsl:template>
<xsl:template match="DataSet/diffgr:diffgram/Forms">
<xsl:for-each select="Form">
<xsl:value-of select="FieldName1"/>
<xsl:value-of select="FieldName2"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I can't see the XML, but I can surmise from this that all of the fields are siblings within the <form> and looks something like this:
<form>
<CosignerName1 value="John Doe"/>
<CosignerDate1 value="1/1/1970"/>
<CosignerTime1 value="8:46 PM"/>
<CosignerName2 value="Jane Smith"/>
<CosignerDate2 value="2/2/1972"/>
<CosignerTime2 value="11:46 AM"/>
...
<CosignerName12 value="Will Hunting"/>
<CosignerDate12 value="12/12/1982"/>
<CosignerTime12 value="1:00 AM"/>
</form>
<form>
<CosignerName1 value="Bill Thomas"/>
<CosignerDate1 value="5/5/2020"/>
<CosignerTime1 value="8:46 PM"/>
<CosignerName2 value="Bev Poole"/>
<CosignerDate2 value="6/6/2022"/>
<CosignerTime2 value="11:46 AM"/>
...
<CosignerName12 value="Bob Ross"/>
<CosignerDate12 value="12/12/1982"/>
<CosignerTime12 value="1:00 AM"/>
</form>
Known factors:
Each form has 12 cosigners, each with numbered field names i.e. CosignerName1, CosignerName2, etc.
Each named cosigner also has a CosignerDate and a CosignerTime field associated by number. i.e. CosignerName1 has CosignerDate1 and CosignerTime1 fields that, when combined, show when that user signed the form.
I am using the word 'idnum' in my code and in this question to refer to the number at the end of each label that shows they are part of a set i.e CosignerName1, CosignerDate1, and CosignerTime1 all share idnum=1
Goal: Identify and select only the most recent CosignerName, based on CosignerDate and CosignerTime.
To do this, I figure I need to:
Find the most recent CosignerDate/CosignerTime combination
Read the label of either of the identified fields to extract the idnum from the end
Use the idnum to to identify the correct CosignerName and output it
I have some pieces of the solution, but no way to bring them together:
Create a Timestamp
I can combine a set of CosignerDate[idnum] and CosignerTime[idnum] fields, then translate them into a timestamp. If I could create an array of these timestamps, then I could sort and find the most recent one, but as I understand it XSLT 1.0 doesn't have arrays. So I am not sure what to do with this.
<xsl:variable name="mm" select="substring-before(CosignerDate1,'/')" />
<xsl:variable name="mmyyyy" select="substring-after(CosignerDate1,'/')"/>
<xsl:variable name="dd" select="substring-before($mmyyyy,'/')" />
<xsl:variable name="yyyy" select="substring-after($mmyyyy,'/')" />
<xsl:variable name="ampm" select="substring-after(CosignerTime1,' ')" />
<xsl:variable name="time">
<xsl:choose>
<xsl:when test="$ampm = 'AM'">
<xsl:value-of select="number(translate(substring-before(CosignerTime1,' '),':',''))" />
</xsl:when>
<xsl:when test="$ampm = 'PM'">
<xsl:value-of select="number(translate(substring-before(CosignerTime1,' '),':',''))+number('1200')" />
</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="concat('Timestamp:',$yyyy,$mm,$dd,$time)"/>
The code above outputs something like: Timestamp:202209191128
Get idnums
I can loop through all of the Cosigners and extract the identifying number shared by their labels.
<xsl:for-each select="*[starts-with(local-name(), 'CosignerName')]">
<xsl:variable name="idnum">
<xsl:value-of select="translate(local-name(),'CosignerName','')" />
</xsl:variable>
<xsl:value-of select="$idnum"/>
<xsl:value-of select="','"/>
</xsl:for-each>
This code above outputs something like: 1,4,7,12
But, this is where I get lost.
Failed Attempt
I tried something like the code below. The logic was to loop through each Cosigner name (12 iterations), get the idnum from the label, use that to get the CosignerDate and CosignerTime, create a timestamp, then sort the timestamps, if the timestamp we are looking at is the top one (most recent) then use the idnum to select the correct CosignerName and output it.
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsl:stylesheet version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:output method="text" /><xsl:template match="/"><xsl:apply-templates/></xsl:template>
<xsl:template match="DataSet/diffgr:diffgram/Forms">
<xsl:for-each select="Form">
<xsl:for-each select="*[starts-with(local-name(), 'CosignerName')]">
<xsl:variable name="idnum">
<xsl:value-of select="translate(local-name(),'CosignerName','')" />
</xsl:variable>
<xsl:variable name="mm" select="substring-before(concat(CosignerDate,$idnum),'/')" />
<xsl:variable name="mmyyyy" select="substring-after(concat(CosignerDate,$idnum),'/')"/>
<xsl:variable name="dd" select="substring-before($mmyyyy,'/')" />
<xsl:variable name="yyyy" select="substring-after($mmyyyy,'/')" />
<xsl:variable name="ampm" select="substring-after(concat(CosignerTime,$idnum),' ')" />
<xsl:variable name="time">
<xsl:choose>
<xsl:when test="$ampm = 'AM'">
<xsl:value-of select="number(translate(substring-before(concat(CosignerTime,$idnum),' '),':',''))" />
</xsl:when>
<xsl:when test="$ampm = 'PM'">
<xsl:value-of select="number(translate(substring-before(concat(CosignerTime,$idnum),' '),':',''))+number('1200')" />
</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:variable name="timestamp" select="concat('Timestamp: ',$yyyy,$mm,$dd,$time)"/>
<xsl:sort select="$timestamp" data-type="number" order="descending"/>
<xsl:if test="position() = 1"><xsl:value-of select="."/></xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This of course fails for several reasons:
the concat with the $idnum doesn't actually pull values from fields
the sort has to be the first thing in the for-each
the sort has to happen after all of the timestamps are created
the sorted list of timestamps still needs to reference which idnum they were generated from
Without being able to create an array of timestamps and idnums, I don't know how you can accomplish this.
Does anyone know how can I get at the CosignerName[idnum] that is correlated with the most recent CosignerDate[idnum]/CosignerTime[idnum]?
I believe you could just sort the CosignerNameX nodes by the individual components of their associated dates and times, as shown in the two other answers I linked to:
https://stackoverflow.com/a/59288030/3016153
https://stackoverflow.com/a/30631073/3016153
However, it might be more efficient - and certainly more readable - to do this in two steps:
Construct a dateTime value for each cosigner;
Find the cosigner with the most recent value.
Consider the following example:
XML
<DataSet xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">
<diffgr:diffgram>
<Forms>
<form>
<CosignerName1 value="John Doe"/>
<CosignerDate1 value="1/1/1970"/>
<CosignerTime1 value="8:46 PM"/>
<CosignerName2 value="Jane Smith"/>
<CosignerDate2 value="2/2/1972"/>
<CosignerTime2 value="11:46 AM"/>
<CosignerName12 value="Will Hunting"/>
<CosignerDate12 value="12/12/1982"/>
<CosignerTime12 value="1:00 AM"/>
</form>
<form>
<CosignerName1 value="Bill Thomas"/>
<CosignerDate1 value="5/5/2020"/>
<CosignerTime1 value="8:46 PM"/>
<CosignerName2 value="Bev Poole"/>
<CosignerDate2 value="6/6/2022"/>
<CosignerTime2 value="11:46 AM"/>
<CosignerName12 value="Bob Ross"/>
<CosignerDate12 value="12/12/1982"/>
<CosignerTime12 value="1:00 PM"/>
</form>
</Forms>
</diffgr:diffgram>
</DataSet>
XSLT 1.0 + EXSLT node-set()
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="diffgr exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/DataSet">
<output>
<xsl:for-each select="diffgr:diffgram/Forms/form">
<xsl:variable name="datetimes">
<xsl:for-each select="*[starts-with(local-name(), 'CosignerName')]">
<!-- identify values -->
<xsl:variable name="i" select="substring-after(local-name(), 'CosignerName')" />
<xsl:variable name="date" select="../*[local-name()=concat('CosignerDate', $i)]/#value" />
<xsl:variable name="time" select="../*[local-name()=concat('CosignerTime', $i)]/#value" />
<!-- extract date components -->
<xsl:variable name="year" select="substring-after(substring-after($date, '/'), '/')" />
<xsl:variable name="month" select="substring-before($date, '/')" />
<xsl:variable name="day" select="substring-before(substring-after($date, '/'), '/')" />
<!-- extract time components -->
<xsl:variable name="hour12" select="substring-before($time, ':')" />
<xsl:variable name="minute" select="substring-before(substring-after($time, ':'), ' ')" />
<xsl:variable name="pm" select="contains($time,'PM')" />
<xsl:variable name="hour" select="$hour12 mod 12 + 12*$pm"/>
<!-- construct dateTime -->
<datetime cosigner="{#value}" index="{$i}">
<xsl:value-of select="$year"/>
<xsl:value-of select="format-number($month, '-00')"/>
<xsl:value-of select="format-number($day, '-00')"/>
<xsl:value-of select="format-number($hour, 'T00')"/>
<xsl:value-of select="format-number($minute, ':00')"/>
<xsl:text>:00</xsl:text>
</datetime>
</xsl:for-each>
</xsl:variable>
<!-- output -->
<form>
<xsl:for-each select="exsl:node-set($datetimes)/datetime">
<xsl:sort select="." data-type="text" order="descending"/>
<xsl:if test="position()=1">
<last-cosigner index="{#index}" dateTime="{.}" >
<xsl:value-of select="#cosigner"/>
</last-cosigner>
</xsl:if>
</xsl:for-each>
</form>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<output>
<form>
<last-cosigner index="12" dateTime="1982-12-12T01:00:00">Will Hunting</last-cosigner>
</form>
<form>
<last-cosigner index="2" dateTime="2022-06-06T11:46:00">Bev Poole</last-cosigner>
</form>
</output>
P.S. With a Microsoft processor you may need to change the namespace declaration:
xmlns:exsl="http://exslt.org/common"
to:
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
and then change the exsl prefix in lines #5 and #39 to msxsl.

How to fix 'Errors were reported during stylesheet compilation' in XSLT?

I have this SaxonApiException when I run my XSLT code on https://xslttest.appspot.com/. It return this error :
net.sf.saxon.s9api.SaxonApiException: Errors were reported during stylesheet compilation
I tried on another online tester https://www.freeformatter.com/xsl-transformer.html but I got the same error.
I tried to split my XSLT code. First part with the process of extract ZipCode in Wages and second part with the process of extract ZipCode in Addresses.
Both works when they're separated so I think I made a mistake in the 'choose' element but cannot find it.
Here is my XSLT code...
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/EmployeeUDM_Response/Return/Employee">
<xsl:for-each select="./Wages/Wage">
<xsl:choose>
<xsl:when test="DissimelarZipCode != ''">
<xsl:value-of select="DissimelarZipCode" />
</xsl:when>
<otherwise>
<xsl:for-each select="./Addresses/Address" />
<!-- year -->
<xsl:sort select="substring(StartDate, 1, 4)" order="descending" data-type="number"/>
<!-- month -->
<xsl:sort select="substring(StartDate, 6, 2)" order="descending" data-type="number"/>
<!-- day -->
<xsl:sort select="substring(StartDate, 9, 2)" order="descending" data-type="number"/>
<xsl:if test="position() = 1">
<xsl:value-of select="./ZipCode" />
</xsl:if>
</otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
...and my XML file
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl"?>
<EmployeeUDM_Response xmlns:ns0="http://ESB/Schemas/v2/EmployeeUDM">
<Header Type="Employee" Source="Biztalk ESB" />
<Return>
<Employee>
<Wages>
<Wage>
<StartDate>2019-04-22T00:00:00.0000000+02:00</StartDate>
<EndDate>2019-05-01T00:00:00.0000000+02:00</EndDate>
<DissimelarZipCode>5430 NU</DissimelarZipCode>
</Wage>
</Wages>
<Addresses>
<Address>
<StartDate>2014-01-01T00:00:00.0000000+02:00</StartDate>
<EndDate></EndDate>
<ZipCode>6099 EB</ZipCode>
</Address>
<Address>
<StartDate>2015-01-01T00:00:00.0000000+02:00</StartDate>
<EndDate></EndDate>
<ZipCode>5487 YR</ZipCode>
</Address>
</Addresses>
</Employee>
</Return>
</EmployeeUDM_Response>
I expected the output of the ZipCode in Wage (5430 NU in this case) or, if ZipCode in Wage is empty, the ZipCode in Address with the latest StartDate (5487 YR in this case)
1. There should be <xsl:otherwise> instead of <otherwise>
2. <xsl:sort> should be in <xsl:for-each> .(You have ended the loop in same line)
3. To loop over Address, you will need xpath ../../Addresses/Address. Because at that time <Wage> is being processed. ( ../ will bring you up one level to parent node.)
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/EmployeeUDM_Response/Return/Employee">
<xsl:for-each select="Wages/Wage">
<xsl:choose>
<xsl:when test="DissimelarZipCode != ''">
<xsl:value-of select="DissimelarZipCode" />
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="../../Addresses/Address">
<!-- year -->
<xsl:sort select="substring(StartDate, 1, 4)" order="descending"
data-type="number" />
<!-- month -->
<xsl:sort select="substring(StartDate, 6, 2)" order="descending"
data-type="number" />
<!-- day -->
<xsl:sort select="substring(StartDate, 9, 2)" order="descending"
data-type="number" />
<xsl:if test="position() = 1">
<xsl:value-of select="ZipCode" />
</xsl:if>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/pPzifpP

I want to split the string stored in a variable and store the obtained values in variable using xslt

<xsl:variable name="AAM" select="//AAM"/>
AAM will have string value_1,value_2,value_3,value_4
I then want to split this and store in 4 variables :
seg1,
seg2,
seg3,
seg4
Assuming the below as your input:
<?xml version="1.0" encoding="UTF-8"?>
<body>
<AAM>value_1,value_2,value_3,value_4</AAM>
</body>
An XSLT 2.0 solution to split a comma separated string can be:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:variable name="AAM" select="//AAM" />
<xsl:template match="body">
<body>
<xsl:variable name="separator" select="','" />
<xsl:for-each select="tokenize($AAM,$separator)">
<xsl:element name="seg{position()}">
<xsl:value-of select="normalize-space(.)" />
</xsl:element>
</xsl:for-each>
</body>
</xsl:template>
http://xsltransform.net/6qaFCET/1
Edit: (Based on commented)
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:variable name="AAM" select="//AAM" />
<xsl:template match="body">
<body>
<xsl:variable name="separator" select="','" />
<xsl:for-each select="tokenize($AAM,$separator)">
<xsl:choose>
<xsl:when test="position() = 1">
<xsl:variable name="seg1">
<xsl:value-of select="normalize-space(.)" />
</xsl:variable>
<xsl:copy-of select="$seg1" />
<xsl:text>
</xsl:text>
</xsl:when>
<xsl:when test="position() = 2">
<xsl:variable name="seg2">
<xsl:value-of select="normalize-space(.)" />
</xsl:variable>
<xsl:copy-of select="$seg2" />
<xsl:text>
</xsl:text>
</xsl:when>
<xsl:when test="position() = 3">
<xsl:variable name="seg3">
<xsl:value-of select="normalize-space(.)" />
</xsl:variable>
<xsl:copy-of select="$seg3" />
<xsl:text>
</xsl:text>
</xsl:when>
<xsl:when test="position() = 4">
<xsl:variable name="seg4">
<xsl:value-of select="normalize-space(.)" />
</xsl:variable>
<xsl:copy-of select="$seg4" />
<xsl:text>
</xsl:text>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</body>
</xsl:template>
http://xsltransform.net/6qaFCET/2
Note: following lines can be avoided. It has been added to populate value of variables.
<xsl:copy-of select="$seg2" />
<xsl:text>
</xsl:text>
Assuming you have <AAM>value_1,value_2,value_3,value_4</AAM> and <xsl:variable name="AAM" select="//AAM"/> you can of course use e.g. <xsl:variable name="value-sequence" select="tokenize($AAM, ',')"/> and if then needed access e.g. $value-sequence[1], $value-sequence[2] and so on, thus if you know there are only four values you can declare <xsl:variable name="seq1" select="$value-sequence[1]"/>, <xsl:variable name="seq2" select="$value-sequence[2]"/> and so on. The tokenize function is part of XPath 2 and later so works with XSLT 2 or 3 processors like Saxon 9 or AltovaXML or XmlPrime.
Answering late.
Maybe this is not an exact solution. But this may help you.
This is how I'm able to split a string from an XML node into two variables using XSLT 1.0
<xsl:when test="NodeName">
<xsl:variable name="var" select="NodeName"/>
<xsl:variable name="var1" select="substring-before($var, '=')"/>
<xsl:variable name="var2" select="substring-after($var, '=')"/>
<xsl:value-of select="$var1"/>-
<xsl:value-of select="$var2"/>
</xsl:when>
For Further reference Click Here

How to get only business days between two dates in xslt 1.0

I need help to count only business days (i.e excluding Saturday and Sunday ) between two dates in xslt 1.0
count only business days (i.e excluding Saturday and Sunday ) between
two dates in xslt 1.0
If it can be assumed that the two given dates will not fall on Saturday or Sunday, you could use the method shown in the following example:
XSLT 1.0
<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="/">
<output>
<workdays>
<xsl:call-template name="duration-in-workdays">
<xsl:with-param name="start-date" select="'2015-04-02'" />
<xsl:with-param name="end-date" select="'2015-04-08'" />
</xsl:call-template>
</workdays>
</output>
</xsl:template>
<xsl:template name="duration-in-workdays">
<!-- assumes start-date and end-date are both workdays -->
<xsl:param name="start-date"/>
<xsl:param name="end-date"/>
<xsl:variable name="start">
<xsl:call-template name="JDN">
<xsl:with-param name="date" select="$start-date" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="end">
<xsl:call-template name="JDN">
<xsl:with-param name="date" select="$end-date" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="start-weekday" select="($start + 1) mod 7" />
<xsl:variable name="end-weekday" select="($end + 1) mod 7" />
<xsl:variable name="weeks" select="floor(($end - $start) div 7)" />
<xsl:variable name="days" select="($end - $start) mod 7" />
<xsl:value-of select="5 * $weeks + $days - 2*($start-weekday > $end-weekday)"/>
</xsl:template>
<xsl:template name="JDN">
<xsl:param name="date"/>
<xsl:variable name="year" select="substring($date, 1, 4)"/>
<xsl:variable name="month" select="substring($date, 6, 2)"/>
<xsl:variable name="day" select="substring($date, 9, 2)"/>
<xsl:variable name="a" select="floor((14 - $month) div 12)"/>
<xsl:variable name="y" select="$year + 4800 - $a"/>
<xsl:variable name="m" select="$month + 12*$a - 3"/>
<xsl:value-of select="$day + floor((153*$m + 2) div 5) + 365*$y + floor($y div 4) - floor($y div 100) + floor($y div 400) - 32045" />
</xsl:template>
</xsl:stylesheet>
</xsl:stylesheet>
Caveat: not tested thoroughly.
<xsl:template match="/">
<xsl:value-of select="this:WorkDayDifference(xs:date('2014-05-01'),xs:date('2014-05-12'))"/>
</xsl:template>
<xsl:function name="this:WorkDayDifference">
<xsl:param name="startDate" as="xs:date"/>
<xsl:param name="endDate" as="xs:date"/>
<xsl:variable name="endDateDay" select="this:day-of-week($endDate)"/>
<xsl:variable name="days" select="days-from-duration($endDate - $startDate)"/>
<xsl:variable name="dow1" select="this:day-of-week($startDate)"/>
<xsl:variable name="dow2" select="this:day-of-week($endDate)"/>
<xsl:variable name="weeks" select="xs:integer($days div 7)"/>
<xsl:variable name="offset" select="if($dow2 ge $dow1) then if($endDateDay = 6 or $endDateDay = 0)then(-6)else(-5) else if($endDateDay = 6 or $endDateDay = 0)then(-1)else(0)"/>
<xsl:variable name="wdays" select="sum((0,1,1,1,1,1,0,0,1,1,1,1,1,0)[position() ge $dow1 + 1 and position() le ($dow2 + 7)]) + $weeks * 5 + $offset + 1"/>
<xsl:value-of select="number($wdays)"/>
</xsl:function>
<xsl:function name="this:day-of-week" as="xs:integer?" >
<xsl:param name="date" as="xs:anyAtomicType?"/>
<xsl:sequence select="if (empty($date)) then () else (xs:integer((xs:date($date) - xs:date('1901-01-06')) div xs:dayTimeDuration('P1D')) mod 7)"/>
</xsl:function>

Convert Xslt 1.0 String to a Number

Is necessary for me convert this data:
Longitude 8-55,810 into Longitude 8.93016666
This transformation is obtained :
8 is the integer part and remains untouched;
the second part after '.' is obtained dividend 55,810 by 60
In xslt 1.0 I:
<!-- Declared a variable longitudine es. 8-55,810-->
<xsl:variable name="lon" select='LONGITUDE'/>
<!-- I put in a variable the part after '-' es. 55.810 -->
<xsl:variable name="partemobilelon" select="substring-after($lon,'-')"/>
Now is necessary for me Convert '55.810'(is String) to Number and Divide by 60 (only 8 character after ',').
See if this helps:
<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:variable name="longitude" select="'8-55,810'" />
<xsl:template match="/">
<output>
<xsl:param name="int" select="substring-before($longitude, '-')" />
<xsl:param name="dec" select="translate(substring-after($longitude, '-'), ',', '.')"/>
<xsl:value-of select="$int + $dec div 60" />
</output>
</xsl:template>
</xsl:stylesheet>
Edit:
To round the result, try:
<xsl:template match="/">
<output>
<xsl:param name="int" select="substring-before($longitude, '-')" />
<xsl:param name="dec" select="translate(substring-after($longitude, '-'), ',', '.')"/>
<xsl:param name="num" select="$int + $dec div 60" />
<xsl:value-of select="round($num * 100000000) div 100000000" />
</output>
</xsl:template>
or just:
<xsl:template match="/">
<output>
<xsl:param name="int" select="substring-before($longitude, '-')" />
<xsl:param name="dec" select="translate(substring-after($longitude, '-'), ',', '.')"/>
<xsl:value-of select="format-number($int + $dec div 60, '0.########')" />
</output>
</xsl:template>