Compare to current date - xslt

I have a xml file with rows like this :
<Data>
<a>something</a>
<b>20120601</b>
<c>something else</c>
</Data>
the value in is a date formatted YYYYMMDD
I have a small template that processes each row, and which is working fine (for it doesn't do much)
Now my question :
How can I process a row only if the date in B is greater (later) than 60 days before today (current date) ?
So if the date is recent -> process the row, else don't.

Here is a complete, short and simple XSLT 2.0 solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pPeriod" select="'P60D'"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"Data
[current-date()
-
xs:date(concat(substring(b,1,4),
'-',
substring(b,5,2),
'-',
substring(b,7)
)
)
gt xs:dayTimeDuration($pPeriod)
]"/>
</xsl:stylesheet>
When applied on this XML document:
<t>
<Data>
<a>something</a>
<b>20120601</b>
<c>something else</c>
</Data>
<Data>
<a>something</a>
<b>20120420</b>
<c>something else</c>
</Data>
<Data>
<a>something</a>
<b>20120301</b>
<c>something else</c>
</Data>
</t>
the wanted, correct result (last Data element "deleted") is produced:
<t>
<Data>
<a>something</a>
<b>20120601</b>
<c>something else</c>
</Data>
<Data>
<a>something</a>
<b>20120420</b>
<c>something else</c>
</Data>
</t>

As the commenter said, XSL cannot do this by itself. You would need to first work out what the date was 60 days ago, and pass this in to your XSL as a parameter.
In PHP, you could work out the day in the following way:
$date_60_days_ago = date('Y-m-d H:m:s', strtotime("-60 days"));
As for the XSL, see this XMLPlayground session (I pass the date in manually for the purpose of this demo)
http://www.xmlplayground.com/sK82Q4

As MiMo already stated there's no built in function to get the current date in xslt 1.0. You can pass params into your xslt stylesheet but this depends on the xslt processor you use.
If you pass the back date (curret date minus 60 days) as a param, your test can be as simple as that:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="backdate" />
<xsl:template match="Data">
<xsl:if test="(./b >= $backdate)">
<xsl:apply-templates />
</xsl:if>
</xsl:template>
</xsl:stylesheet>
In xslt 2.0 you can use the built in current-date() function
<xsl:variable name="backdate" select="current-date() -60*xs:dayTimeDuration('P1D')" />
... but you'll need to cast your <b> date value to accordingly - or format the backdate variable to fit your date value.
Adopted from Dimitre's answer on subtracting days.

Related

XSL to Parse and Re-order XML Nodes

I need assistance with creating a XSL stylesheet to parse data and re-order based on values within certain nodes. My original XML is being exported by a roster program in a undesirable structure which is causing issues when converting to JSON.
This is a Fire Department roster that will be converted into JSON to be processed by Station Status Boards. I'm looking to format the XML so that when converted into JSON each Station has a crew list. I've attempted to create a XSL without success. I have zero background in XSL (Fire Fighter).
Section of Original XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Data>
<Date>2019-05-07-07:00</Date>
<Headers></Headers>
<Records>
<Record>
<RscPayrollIDCh>12345678</RscPayrollIDCh>
<RscEmployeeIDCh>12345678</RscEmployeeIDCh>
<RscMasterNameCh>Smith, Mike A.</RscMasterNameCh>
<InstitutionAbrvCh>SPL</InstitutionAbrvCh>
<AgencyAbrvCh>SPFD</AgencyAbrvCh>
<RegionAbrvCh>OPS</RegionAbrvCh>
<StationAbrvCh>B19</StationAbrvCh>
<PUnitAbrvCh>BAT19</PUnitAbrvCh>
<PosJobAbrvCh>BC-S</PosJobAbrvCh>
</Record>
<Record>
<RscPayrollIDCh>12345</RscPayrollIDCh>
<RscEmployeeIDCh>12345</RscEmployeeIDCh>
<RscMasterNameCh>Smith, John A.</RscMasterNameCh>
<InstitutionAbrvCh>SPL</InstitutionAbrvCh>
<AgencyAbrvCh>SPFD</AgencyAbrvCh>
<RegionAbrvCh>OPS</RegionAbrvCh>
<StationAbrvCh>S15</StationAbrvCh>
<PUnitAbrvCh>E15</PUnitAbrvCh>
<PosJobAbrvCh>CAPT</PosJobAbrvCh>
</Record>
<Record>
<RscPayrollIDCh>123456</RscPayrollIDCh>
<RscEmployeeIDCh>123456</RscEmployeeIDCh>
<RscMasterNameCh>Smith, Bob R.</RscMasterNameCh>
<InstitutionAbrvCh>SPL</InstitutionAbrvCh>
<AgencyAbrvCh>SPFD</AgencyAbrvCh>
<RegionAbrvCh>OPS</RegionAbrvCh>
<StationAbrvCh>S15</StationAbrvCh>
<PUnitAbrvCh>E15</PUnitAbrvCh>
<PosJobAbrvCh>ENG</PosJobAbrvCh>
</Record>
</Records>
</Data>
I would like to format the XML so that it looks something like this:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Data>
<Date>2019-05-07-07:00</Date>
<Headers></Headers>
<Records>
<Record>
<StationAbrvCh>B19</StationAbrvCh>
<RscMasterNameCh>Smith, Mike A.</RscMasterNameCh>
</Record>
<Record>
<StationAbrvCh>S15</StationAbrvCh>
<RscMasterNameCh>Smith, John A.</RscMasterNameCh>
<RscMasterNameCh>Smith, Bob R.</RscMasterNameCh>
</Record>
</Records>
I would like my roster to list each crew member under the Station they are assigned to for the day.
If you are using XSLT 1.0, Muenchian grouping is the best approach to achieve it as below:
<?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" indent="yes" />
<xsl:key name="groups" match="/Data/Records/Record" use="StationAbrvCh" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="/Data/Records">
<xsl:copy>
<xsl:for-each select="Record[generate-id() = generate-id(key('groups', StationAbrvCh)[1])]">
<xsl:copy>
<StationAbrvCh><xsl:value-of select="StationAbrvCh" /></StationAbrvCh>
<xsl:for-each select="key('groups', StationAbrvCh)">
<RscMasterNameCh><xsl:value-of select="RscMasterNameCh" /></RscMasterNameCh>
</xsl:for-each>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
See demo here: https://xsltfiddle.liberty-development.net/pPzifpM
Using XSLT 2.0 it is quite easy.
In the template maching Records, you should use for-each-group
selecting Record elements and grouping them by StationAbrvCh.
Within each group you should:
Generate StationAbrvCh element, filled with the current grouping key
(also StationAbrvCh).
Run a for-each loop for the current group, copying to the output the
current RscMasterNameCh.
The script should contain also the identity template.
Below you have an example script:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Records">
<xsl:copy>
<xsl:for-each-group select="Record" group-by="StationAbrvCh">
<xsl:copy>
<StationAbrvCh><xsl:value-of select="current-grouping-key()"/></StationAbrvCh>
<xsl:for-each select="current-group()">
<xsl:sequence select="RscMasterNameCh"/>
</xsl:for-each>
</xsl:copy>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
</xsl:stylesheet>
Maybe, to fully understand each detail of the above solution,
you should search the Web for description of for-each-group
and related functions (current-grouping-key() and current-group).

XSL for-each filter with subcontext from other main-node

i've already a new problem.
I have to merge lists via xsl, problem on this is that the key in the lookup-list must be concatinated by 2 values.
The lists can be relative large with thousands,ten-thousands in some cases even more of entries in both lists. In advance of large sizes of this lists, i have to look on performance and memory. It could be that this will later implemented in an web-service-client, so it must run quick and resource-saving.
Merging the exisiting Elements in List1 and List2 is done and was not complicated, but now i have to check both lists on non-exisiting elements in other list.
I tried to negate the for-each select statement but failed and it is presumably the wrong way.
InputXML-example
<ROOT>
<getObjectListResponse>
<item>
<Key>1111111:aaaa</Key>
<someOhterData>Text</someOhterData>
</item>
<item>
<Key>2222222:bbbb</Key>
<someOhterData>Text</someOhterData>
</item>
<item>
<Key>3333333:aaaa</Key>
<someOhterData>Text</someOhterData>
</item>
</getObjectListResponse>
<LookupList>
<DATA>
<KeyPart1>1111111</KeyPart1>
<KeyPart2>aaaa</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>1111111</KeyPart1>
<KeyPart2>bbbb</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>2222222</KeyPart1>
<KeyPart2>aaaa</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>2222222</KeyPart1>
<KeyPart2>bbbb</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>3333333</KeyPart1>
<KeyPart2>aaaa</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>3333333</KeyPart1>
<KeyPart2>bbbb</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
</LookupList>
</ROOT>
The first part, find the existing parts in both lists is already done.
The second part is to find non-existing parts in List 1 to List 2 and List 2 to List 1.
I wanna like to do this in for-each, so you get only non-exisiting entries from List1 which does not exists in List2.
My Problem ist to lookup in for-each context with an concatinated key, from all DATA in LookupList.
<xsl:for-each select="/*/getObjectListResponse/item[Key/text() != /*/LookupList/DATA/*[concat(KeyPart1,'/',KeyPart2)]]">
<xsl:copy-of select="."/>
</xsl:for-each>
<xsl:for-each select="/*/getObjectListResponse/item[Key/text() != /*/LookupList/DATA/[concat(KeyPart1,'/',KeyPart2)]]">
<xsl:copy-of select="."/>
</xsl:for-each>
But everything i tried fails, with no results or wrong result.
How can this be done?
I tried this and some others, but nothing will work.
Thanks in advance
I would use keys for the cross-reference, here is an XSLT 3.0 (as supported by Saxon 9.8 all editions or Altova XMLSpy/Raptor) stylesheet as obviously one sample is a good use case for a composite key:
<?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="3.0">
<xsl:output indent="yes"/>
<xsl:key name="data" match="DATA" composite="true" use="KeyPart1, KeyPart2"/>
<xsl:key name="item" match="item" use="Key"/>
<xsl:template match="ROOT">
<xsl:copy>
<items-not-in-data>
<xsl:copy-of select="getObjectListResponse/item[not(key('data', (substring-before(Key, ':'), substring-after(Key, ':'))))]"/>
</items-not-in-data>
<data-not-in-items>
<xsl:copy-of select="LookupList/DATA[not(key('item', concat(KeyPart1, ':', KeyPart2)))]"/>
</data-not-in-items>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
For your sample data I get
<ROOT>
<items-not-in-data/>
<data-not-in-items>
<DATA>
<KeyPart1>1111111</KeyPart1>
<KeyPart2>bbbb</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>2222222</KeyPart1>
<KeyPart2>aaaa</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>3333333</KeyPart1>
<KeyPart2>bbbb</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
</data-not-in-items>
</ROOT>
Of course XSLT 3.0 and a composite key is not mandatory, you could as well use XSLT 2.0 and use a single key value concat(KeyPart1, KeyPart2).

Using Saxon, increment or decrement the global variables in XSLT

I want to use a counter to increment and decrement the value in XSLT. I am able to achieve this using Apache Xalan, but now I want to achieve the same using Saxon.
My XSLT script for Xalan looks like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lxslt="http://xml.apache.org/xslt"
xmlns:result="http://www.example.com/results"
extension-element-prefixes="result">
<lxslt:component prefix="result" functions="incr dcr">
<lxslt:script lang="javascript">
var count = 0;
function incr() {
count++;
return count;
}
function dcr() {
count--;
return count;
}
</lxslt:script>
</lxslt:component>
<xsl:template match="/">
a)<xsl:value-of select="result:incr()"/>
b)<xsl:value-of select="result:incr()"/>
c)<xsl:value-of select="result:dcr()"/>
</xsl:template>
</xsl:stylesheet>
Expected output is:
a)1
b)2
c)1
------------------------- my use case using saxon -------------------
This is my sample data.xml file. I want to transform this to html file.
<entity>
<record>10</record>
<record>15</record>
<record>19</record>
<record>7</record>
<record>4</record>
<record>14</record>
<record>24</record>
<entity>
I want to implement a counter for line-number and want to increment the counter and print the line-number
my expected outputs:
case 1: If record values > 14, then my output should have line-number with value as.
line-num value
1 15
2 19
3 24
case 2 : If record values < 14, then my output should have line-number with value as.
line-num value
1 10
2 7
3 4
My other use case :
<entity>
<record>10</record>
<record>15</record>
<record>19</record>
<record>7</record>
<record>20</record>
<record>14</record>
<record>24</record>
<entity>
<record>30</record>
<record>3</record>
</entity>
</entity>
<entity>
<record>5</record>
<record>17</record>
<record>19</record>
<record>6</record>
<record>70</record>
<record>9</record>
<record>35</record>
<entity>
<record>15</record>
<record>2</record>
</entity>
</entity>
This is my other use case, first <entity> record value > 15 and in second <entity> record value < 10, and my <entity> can grow bigger where i have to show only some record based on condition.
line-num value
1 19
2 20
3 24
4 30
5 5
6 6
7 2
There is no way to use Javascript in Saxon as far as I know.
If you want to use an assignable and changeable variable in Saxon 9 (EE or PE) then you can use
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:saxon="http://saxon.sf.net/"
xmlns:mf="http://example.com/mf"
extension-element-prefixes="saxon"
exclude-result-prefixes="xs saxon mf"
version="2.0">
<xsl:variable name="counter" as="xs:integer" select="0" saxon:assignable="yes"/>
<xsl:function name="mf:incr" as="xs:integer">
<saxon:assign name="counter" select="$counter + 1"/>
<xsl:sequence select="$counter"/>
</xsl:function>
<xsl:function name="mf:decr" as="xs:integer">
<saxon:assign name="counter" select="$counter - 1"/>
<xsl:sequence select="$counter"/>
</xsl:function>
<xsl:template match="/" name="main">
a)<xsl:value-of select="mf:incr()"/>
b)<xsl:value-of select="mf:incr()"/>
c)<xsl:value-of select="mf:decr()"/>
</xsl:template>
</xsl:stylesheet>
See the warnings and notes in http://saxonica.com/html/documentation/extensions/instructions/assign.html however on attempts to introduce side effects. You might want to edit your question to explain your XML input and the desired output and why you think you need such a counter variable, hopefully we can then show a pure XSLT 2.0 solution instead of using an assignable variable.
As for your requirement to process and number certain input elements, the stylesheet
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="entity">
<root>
<xsl:apply-templates select="record[. > 14]"/>
<xsl:apply-templates select="record[. < 14]"/>
</root>
</xsl:template>
<xsl:template match="record">
<xsl:copy>
<line-num>
<xsl:value-of select="position()"/>
</line-num>
<value>
<xsl:value-of select="."/>
</value>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
when run against the input
<entity>
<record>10</record>
<record>15</record>
<record>19</record>
<record>7</record>
<record>4</record>
<record>14</record>
<record>24</record>
</entity>
outputs
<root>
<record>
<line-num>1</line-num>
<value>15</value>
</record>
<record>
<line-num>2</line-num>
<value>19</value>
</record>
<record>
<line-num>3</line-num>
<value>24</value>
</record>
<record>
<line-num>1</line-num>
<value>10</value>
</record>
<record>
<line-num>2</line-num>
<value>7</value>
</record>
<record>
<line-num>3</line-num>
<value>4</value>
</record>
</root>
Other options are using e.g. xsl:number count="entity[. < 14]".
As for your latest requirement, I think you can still handle that using position() as long as you select the elements you want to process, e.g. the stylesheet
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:apply-templates select="/root/entity[1]//record[. > 15], /root/entity[2]//record[. < 10]"/>
</xsl:template>
<xsl:template match="record">
<xsl:value-of select="concat(position(), ' ', ., '
')"/>
</xsl:template>
</xsl:stylesheet>
when applied to the input
<root>
<entity>
<record>10</record>
<record>15</record>
<record>19</record>
<record>7</record>
<record>20</record>
<record>14</record>
<record>24</record>
<entity>
<record>30</record>
<record>3</record>
</entity>
</entity>
<entity>
<record>5</record>
<record>17</record>
<record>19</record>
<record>6</record>
<record>70</record>
<record>9</record>
<record>35</record>
<entity>
<record>15</record>
<record>2</record>
</entity>
</entity>
</root>
outputs (with an XSLT 2.0 processor like Saxon 9)
1 19
2 20
3 24
4 30
5 5
6 6
7 9
8 2
(Responding to the expanded description of the problem)
The key thing to appreciate is that this problem requires sequential processing of the record elements, in document order. This means it can't be achieved in a processor-independent way with xsl:for-each or xsl:apply-templates because these are not guaranteed to process the elements one at a time in order (recent versions of Saxon, for example, will process elements in parallel using multi-threading, and the spec allows this; this makes any attempt to update global variables ineffective).
The classic way to process elements sequentially is using head-tail recursion. Write a template or function that takes the sequence of record elements as a parameter, and the current line number as a second parameter. If the list of records is non-empty, then (a) process the first record in the list, and (b) call yourself recursively to process the rest of the list, passing the new value of the line-number.
It can be a bit mind-blowing to write code in this way the first time you try it, but it soon comes naturally.
In XSLT 3.0 there's a new construct xsl:iterate that looks more like a conventional loop: when you enter the xsl:iterate body you pass initial values for its parameters, and each time you continue to the next iteration of the loop you can set new values for the parameters.
Without javascript, using saxon extension instructions.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:saxon="http://saxon.sf.net/"
extension-element-prefixes="saxon"
exclude-result-prefixes="saxon">
<xsl:variable name="i" select="0" saxon:assignable="yes"/>
<xsl:template match="/">
a) <xsl:value-of select="$i"/>
<saxon:assign name="i" select="$i+1"/>
b) <xsl:value-of select="$i"/>
<saxon:assign name="i" select="$i+2"/>
c) <xsl:value-of select="$i"/>
<saxon:assign name="i" select="$i+3"/>
d) <xsl:value-of select="$i"/>
</xsl:template>
</xsl:stylesheet>

Filter and Group in XSLT

I am trying to filter data and then group using XSLT. Here is my XML
<?xml version="1.0" encoding="UTF-8"?>
<AccumulatedOutput>
<root>
<Header>
<Add>true</Add>
<Name>Subscriber</Name>
<Value>SAC</Value>
</Header>
</root>
<root>
<Header>
<Add>true</Add>
<Name>System</Name>
<Value>CBP</Value>
</Header>
</root>
<root>
<Header>
<Add>false</Add>
<Name>Subscriber</Name>
<Value>SAC</Value>
</Header>
</root>
</AccumulatedOutput>
What I want to do is that group based on Header/Name and but remove the group in which Header/Add is false.
So in above example I there will be two groups created (one for Name=Subscriber and other for Name=System) but since the first group(with Name=Subscriber) contains Add=false , I want to ignore that and my output should only have one node in it , like below
<?xml version = "1.0" encoding = "UTF-8"?>
<root>
<Header>
<Name>System</Name>
<Value>CBP</Value>
<Add>true</Add>
</Header>
</root>
I tried using group by method but I can't figure out a way to filter it.
It will be a great help if someone can give me some pointers
Thanks
In XSLT 2.0, you could do:
<xsl:template match="/AccumulatedOutput">
<root>
<xsl:for-each-group select="root/Header" group-by="Name">
<xsl:if test="not(current-group()/Add='false')">
<xsl:copy-of select="current-group()"/>
</xsl:if>
</xsl:for-each-group>
</root>
</xsl:template>
No explicit XSLT conditional instructions:
<xsl:template match="/*">
<root>
<xsl:for-each-group group-by="Name" select=
"root/Header[for $n in string(Name)
return every $h in /*/root/Header[Name eq $n]
satisfies not($h/Add eq 'false')]">
<xsl:sequence select="current-group()"/>
</xsl:for-each-group>
</root>
</xsl:template>

Help with XSLT Transformation

I was hoping I could get a little assistance with an XSLT transform. I can't seem to get it right.
Here is a sample of the source xml document:
<?xml version="1.0" encoding="UTF-8"?>
<Locations>
<header>
<location>Location Field</location>
<job_function>Job Function Field</job_function>
<count>Count</count>
</header>
<data>
<location>2177</location>
<job_function>ADM</job_function>
<count>1</count>
</data>
<data>
<location>2177</location>
<job_function>OPS</job_function>
<count>1</count>
</data>
<data>
<location>2177</location>
<job_function>SLS</job_function>
<count>5</count>
</data>
<data>
<location>2179</location>
<job_function>ADM</job_function>
<count>1</count>
</data>
<data>
<location>2179</location>
<job_function>SEC</job_function>
<count>1</count>
</data>
</Locations>
I want to transform it into the following format:
<Locations>
<data>
<PeopleSoftID>2177</PeopleSoftID>
<ADM>1</ADM>
<OPS>1</OPS>
<SLS>5</SLS>
<TotalCount>7</TotalCount>
</data>
<data>
<PeopleSoftID>2179</PeopleSoftID>
<ADM>1</ADM>
<SEC>1</SEC>
<TotalCount>2</TotalCount>
</data>
</Locations>
So basically, as you can see in the sample source document there are multiple elements that have the same value. In the destination document, there should now only be one record (<PeopleSoftID> element) per <location> element value in the source document. Since there were 3 <location> elements with the value of 2177, the destination document now has just 1 <PeopleSoftID> element that contains that value. The value of the <job_function> element in the source document becomes an element in the destination document. The value of that new element ends up being the sibling value of the <count> element from the source document. The <TotalCount> element in the destination document is the SUM of the values of all the new elements that are generated from the source <job_function> element.
I hope that explanation did not confuse anybody =).
I am a little new to XSLTs still so I am having trouble getting the logic right on this.
I can only use XSLT 1.0 too.
If I did not provide enough information let me know, and I will try to provide more as soon as I am able.
Thanks guys!
Read up on xsl:key and grouping with the Muenchian Method
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" />
<!--Group the data elements by their location values -->
<xsl:key name="data-by-location" match="data" use="location" />
<xsl:template match="Locations">
<xsl:copy>
<!--Get a distinct list of location values,
using the Muenchian Method -->
<xsl:for-each
select="data[generate-id() =
generate-id(key('data-by-location', location)[1])]">
<xsl:copy>
<PeopleSoftID>
<xsl:value-of select="location"/>
</PeopleSoftID>
<!--For every data element matching this location... -->
<xsl:for-each select="key('data-by-location',location)">
<!--Create an element using the job_function
as the element name -->
<xsl:element name="{job_function}">
<!--The value of the count element
as the value of the generated element-->
<xsl:value-of select="count"/>
</xsl:element>
</xsl:for-each>
<TotalCount>
<!--calculate the sum of all the count element values
for this location -->
<xsl:value-of select="sum(key('data-by-location',
location)/count)"/>
</TotalCount>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>