I have the following xml :
<?xml version="1.0" encoding="UTF-8"?>
<persons>
<person>
<phone type='home'>203-555-1212</phone>
<phone type='fax'>203-555-1212</phone>
<address type='home'>
<street>12 Main Street</street>
<city>Southbury</city>
<state>CT</state>
<zip>06488</zip>
</address>
<firstName>Charles</firstName>
<lastName>Smithington</lastName>
</person>
<person1>
<phone type='home'>58-99-44-999</phone>
<phone type='fax'>5788-9987-3365</phone>
<address type='home'>
<street>12 Main Street</street>
<city>Park Avenue</city>
<state>NY</state>
<zip>10025</zip>
</address>
<firstName>Mike</firstName>
<lastName>Shinoda</lastName>
</person1>
</persons>
Here i have to exclude the address field of the person whose state is CT whereas to include the address of the person whose state is NY.
<xsl:template match="persons">
<persons>
<xsl:apply-templates select="person"/>
<xsl:apply-templates select="person1"/>
</persons>
</xsl:template>
<xsl:template match="person">
<person>
<xsl:copy-of select="*[name() != 'address']"/>
<xsl:apply-templates select="address"/>
</person>
</xsl:template><xsl:template match="person1">
<person1>
<xsl:copy-of select="*[name() != 'address']"/>
<xsl:apply-templates select="address"/>
</person1>
</xsl:template>
<xsl:template match="address">
<xsl:if test="not(state='CT')">
<xsl:copy-of select="../address"/>
</xsl:if>
</xsl:template>
As you apply above transformation on provided xml then it will produce following result xml.
<person>
<phone type="home">203-555-1212</phone>
<phone type="fax">203-555-1212</phone>
<firstName>Charles</firstName>
<lastName>Smithington</lastName>
</person>
<person1>
<phone type="home">58-99-44-999</phone>
<phone type="fax">5788-9987-3365</phone>
<firstName>Mike</firstName>
<lastName>Shinoda</lastName>
<address type="home">
<street>12 Main Street</street>
<city>Park Avenue</city>
<state>NY</state>
<zip>10025</zip>
</address>
</person1>
Related
I am struggling with XSLT and have been for days. I'm in the process of modifying a previous coworkers python code that transforms many different JSON files into xml and finally into kml. I'm within a hairs breadth of wrapping this up and, of course, the one part I can't get my head around is what's left.
I have an xml file with this structure:
<?xml version="1.0" ?>
<document xmlns="http://ws.wso2.org/dataservice">
<group>
<display_name>Housing</display_name>
<id>Housing</id>
<item>
<id>5063</id>
<image_url>images/5063.jpg</image_url>
<latitude>40.354007</latitude>
<longitude>-74.666675</longitude>
<name>Stanworth Apartments</name>
</item>
.
. (Many items omitted)
.
</group>
<group>
<display_name>Buildings</display_name>
<id>Building</id>
<item>
<id>5025</id>
<image_url>images/5025.jpg</image_url>
<latitude>40.350066</latitude>
<longitude>-74.603464</longitude>
<name>Lyman Spitzer Building</name>
<name_alt>LSB</name_alt>
<organization_id>ORG418</organization_id>
</item>
.
. (Many items omitted)
.
</group>
<group>
.
. (Many groups omitted)
.
</group>
<group>
<display_name>Accessible Features</display_name>
<id>Entryway</id>
<item>
<description>Accessible entryway</description>
<id>E028</id>
<latitude>40.349159</latitude>
<longitude>-74.658629</longitude>
<name>E028</name>
</item>
<item>
<description>Accessible entryway</description>
<id>E029</id>
<latitude>40.349398</latitude>
<longitude>-74.658517</longitude>
<name>E029</name>
</item>
</group>
<group>
<display_name>Accessible Features</display_name>
<id>Route</id>
<item>
<description>Accessible pathway</description>
<id>R054</id>
<name>R054</name>
<steps>-74.66032495749012,40.3489269473544</steps>
<steps>-74.6602836233495,40.34888813533125</steps>
</item>
<item>
<description>Accessible pathway</description>
<id>R055</id>
<name>R055</name>
<steps>-74.66023036637355,40.34884827131961</steps>
<steps>-74.66018651597699,40.34881015960344</steps>
</item>
<item>
<description>Accessible pathway</description>
<id>R072</id>
<name>R072</name>
<steps>-74.66101885775542,40.34737535360176</steps>
<steps>-74.6610915120654,40.34740600913134</steps>
<steps>-74.66187000551304,40.34717392492537</steps>
</item>
</group>
</document>
Each "group" is transformed into a Folder in the final KML file.
<Folder id="Housing">
<name>Housing</name>
<Placemark id="_0288">
.
. (Many lines omitted)
.
The goal is to create one Folder "id='Accessible" with the contents of two groups. The group with id='Entryway' and the group with id='Route. The desired output would be:
<Folder id="Accessible">
<name>Accessible Features</name>
<Placemark id="_E001">
<name>E001</name>
<description><![CDATA[<div><p>Accessible entryway</p></div>]]></description>
<styleUrl>#entryway</styleUrl>
<Point>
<coordinates>-74.663266, 40.348289,0</coordinates>
</Point>
</Placemark>
<Placemark id="_E002">
<name>E002</name>
<description><![CDATA[<div><p>Accessible entryway</p></div>]]></description>
<styleUrl>#entryway</styleUrl>
<Point>
<coordinates>-74.662252, 40.348057,0</coordinates>
</Point>
</Placemark>
.
. then have the items from the group with id='Route'
.
<Placemark id="_R002">
<name>Accessible Routes</name>
<description><![CDATA[<div><p>Accessible pathway</p></div>]]></description>
<styleUrl/>
<Style>
<LineStyle>
<color>FFFF0000</color>
<width>4</width>
</LineStyle>
</Style>
<LineString>
<coordinates>
-74.65135187837255,40.34699608960065
-74.65134698312161,40.34698651192196
</coordinates>
</LineString>
</Placemark>
<Placemark id="_R003">
<name>Accessible Routes</name>
<description><![CDATA[<div><p>Accessible pathway</p></div>]]></description>
<styleUrl/>
<Style>
<LineStyle>
<color>FFFF0000</color>
<width>4</width>
</LineStyle>
</Style>
<LineString>
<coordinates>
-74.65135184561255,40.34699603789065
-74.65134698312256 44.34698634192100
</coordinates>
</LineString>
</Placemark>
.
. more than 66,000 lines omitted
.
</Folder>
I've written the XSLT to transform the XML into these KML Folders and the only thing left to do is get them under the same folder.
What I've been trying to do is move all of the items from the group with id='Route' into the group with id='Entryway.
In my xslt file is an apply-templates at the group nodes.
<xsl:apply-templates select="ds:group">
<xsl:sort select="ds:display_name"/>
</xsl:apply-templates>
This is picked up by the template match for each group.
<xsl:template match="ds:group">
At which point I'm lost. I'll post my code but it is only going to confuse and depress you.
<xsl:template match="ds:group">
<xsl:choose>
<xsl:when test="not(ds:id = 'Route') and not(ds:id = 'Entryway')">
<Folder id="{ds:id}">
<name>
<xsl:value-of select="ds:display_name"/>
</name>
<xsl:apply-templates select="ds:item">
<xsl:sort select="ds:name"/>
</xsl:apply-templates>
</Folder>
</xsl:when>
<xsl:when test="ds:id = 'Entryway'">
<Folder id='Accessible'>
<name>
<xsl:value-of select="ds:display_name"/>
</name>
<xsl:apply-templates select="ds:item">
<xsl:sort select="ds:name"/>
</xsl:apply-templates>
</Folder>
</xsl:when>
<xsl:when test="ds:id = 'Route'">
<!-- Copy all of current node to Entryway node -->
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
I don't think I'm going about this the right way. By the time the XSLT process gets to the group with id='Route' the KML Folder for Entryway has already been written. I'm at a dead end. Can I union two groups together based on the value of "id"? Conceptually the idea would be: <xsl:template match="ds:id='Route' | ds:id='Entryway'"> But that doesn't even compile.
Can I copy all of the elements of the group (id='Route') to the group (id='Entryway') after the first group has been processed?
Thank you in advance for your time and attention.
George
I think you need to "step" in at the document level and do e.g.
<xsl:template match="ds:document">
<Folder id='Accessible'>
<xsl:apply-templates select="ds:group[ds:id = 'Entryway' or ds:id = 'Route']"/>
</Folder>
<!-- process other ds:group here as well e.g.
<xsl:apply-templates select="ds:group[not(ds:id = 'Entryway' or ds:id = 'Route')]"/>
-->
</xsl:template>
AFAICT you want to do something like:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://ws.wso2.org/dataservice"
exclude-result-prefixes="ns0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/ns0:document">
<Folders>
<xsl:variable name="accessible" select="ns0:group[ns0:id='Entryway' or ns0:id ='Route']" />
<xsl:if test="$accessible">
<Folder id="Accessible">
<name>Accessible Features</name>
<xsl:apply-templates select="$accessible/ns0:item"/>
</Folder>
</xsl:if>
<xsl:apply-templates select="ns0:group[not(ns0:id='Entryway' or ns0:id ='Route')]"/>
</Folders>
</xsl:template>
<xsl:template match="ns0:group">
<Folder id="{ns0:id}">
<name>
<xsl:value-of select="display_name"/>
</name>
<xsl:apply-templates select="ns0:item"/>
</Folder>
</xsl:template>
<xsl:template match="ns0:item">
<Placemark id="{ns0:id}">
<name>
<xsl:value-of select="ns0:name"/>
</name>
<!-- more here -->
</Placemark>
</xsl:template>
</xsl:stylesheet>
<xsl:template match="/ds:document">
<kml>
<Document>
<name>Campus Map</name>
<xsl:apply-templates select="ds:group[ds:id != 'Entryway' and ds:id != 'Route']">
<xsl:sort select="ds:display_name"/>
</xsl:apply-templates>
<Folder id='Accessible'>
<name>Accessible Feature</name>
<xsl:apply-templates select="ds:group[ds:id = 'Entryway' or ds:id = 'Route']"/>
</Folder>
</Document>
</kml>
</xsl:template>
<xsl:template match="ds:group[ds:id = 'Entryway' or ds:id = 'Route']">
<xsl:apply-templates select="ds:item"/>
</xsl:template>
<xsl:template match="ds:group[ds:id != 'Entryway' and ds:id != 'Route']">
<Folder id="{ds:id}">
<name>
<xsl:value-of select="ds:display_name"/>
</name>
<xsl:apply-templates select="ds:item">
<xsl:sort select="ds:name"/>
</xsl:apply-templates>
</Folder>
</xsl:template>
Here is my in-data:
<Results>
<Result>
<Id>1</Id>
</Result>
<Result>
<Id>2</Id>
</Result>
</Results>
<Results>
<RefId>1</RefId>
<Text>One</Text>
</Results>
<Results>
<RefId>2</RefId>
<Text>Two</Text>
</Results>
How the output should be:
<OBR></OBR>
<OBX>One</OBX>
<OBR></OBR>
<OBX>Two</OBX>
My xslt-code
<xsl:key name="test" match="Results/Result" use="Id"/>
<xsl:template match="Results/Result">
<OBR></OBR>
<xsl:for-each select="Results[key('test', RefId)/RefId]">
<OBX><xsl:value-of select="Text" /></OBX>
</xsl:for-each>
</xsl:template>
It does not work. My result is:
<OBR></OBR>
<OBX>One</OBX>
<OBX>Two</OBX>
<OBR></OBR>
<OBX>One</OBX>
<OBX>Two</OBX>
I assume that the problem is with the for-each in my template.. It´s looping twice every time the template runs. Any suggestions?
I complicated it unnecessarily much with they key-function. I solved with just creating a variable named ID and it´s based on the Id-field. Then in the for-each I just tested if the variable and the RefId-element matched and it works perfectly.
<xsl:template match="Results/Result">
<xsl:variable name="ID" select="Id"></xsl:variable>
<OBR></OBR>
<xsl:for-each select="Results[(RefId = $ID)]">
<OBX><xsl:value-of select="Text" /></OBX>
</xsl:for-each>
</xsl:template>
I'm struggling to copy my parent node data when streaming using template match inside an iterate loop with a path from another xml I access via map.
What I'm getting is this:
<?xml version='1.0' encoding='utf-8'?>
<root>
<row>
<Record>1</Record>
<Employee-ID>12345</Employee-ID>
<Authorization-ID>133746</Authorization-ID>
<Date>2021-06-22</Date>
<Quantity>2</Quantity>
<Task-ID>PRJTASK0011134</Task-ID>
<Project-Plan-ID>133746-GP OM Internal Labor-1</Project-Plan-ID>
<Comments/>
<Status>Error: Invalid ID value. '' is not a valid ID value for type =
'Custom_Worktag_13_ID'. Error: Invalid ID value. '' is not a valid ID value for type =
'Custom_Organization_Reference_ID'. Error: Invalid ID value. '133746-GP OM Internal
Labor-1' is not a valid ID value for type = 'Project_Plan_ID'. Error: Invalid ID value.
'12345' is not a valid ID value for type = 'Employee_ID'.</Status>
</row>
</root>
But the output I'm looking for is:
<?xml version='1.0' encoding='utf-8'?>
<root>
<row>
<Record>1</Record>
<Employee-ID>12345</Employee-ID>
<Authorization-ID>133746</Authorization-ID>
<Date>2021-06-22</Date>
<Quantity>2</Quantity>
<Task-ID>PRJTASK0011134</Task-ID>
<Project-Plan-ID>133746-GP OM Internal Labor-1</Project-Plan-ID>
<Comments/>
<Status>Error: Invalid ID value. '' is not a valid ID value for type =
'Custom_Worktag_13_ID'.</Status>
</row>
<row>
<Record>1</Record>
<Employee-ID>12345</Employee-ID>
<Authorization-ID>133746</Authorization-ID>
<Date>2021-06-22</Date>
<Quantity>2</Quantity>
<Task-ID>PRJTASK0011134</Task-ID>
<Project-Plan-ID>133746-GP OM Internal Labor-1</Project-Plan-ID>
<Comments/>
<Status>Error: Invalid ID value. '' is not a valid ID value for type =
'Custom_Organization_Reference_ID'.</Status>
</row>
<row>
<Record>1</Record>
<Employee-ID>12345</Employee-ID>
<Authorization-ID>133746</Authorization-ID>
<Date>2021-06-22</Date>
<Quantity>2</Quantity>
<Task-ID>PRJTASK0011134</Task-ID>
<Project-Plan-ID>133746-GP OM Internal Labor-1</Project-Plan-ID>
<Comments/>
<Status>Error: Invalid ID value. '133746-GP OM Internal Labor-1' is not a valid ID value for
type = 'Project_Plan_ID'.</Status>
</row>
<row>
<Record>1</Record>
<Employee-ID>12345</Employee-ID>
<Authorization-ID>133746</Authorization-ID>
<Date>2021-06-22</Date>
<Quantity>2</Quantity>
<Task-ID>PRJTASK0011134</Task-ID>
<Project-Plan-ID>133746-GP OM Internal Labor-1</Project-Plan-ID>
<Comments/>
<Status>Error: Invalid ID value. '12345' is not a valid ID value for type =
'Employee_ID'.</Status>
</row>
</root>
XSLT Input XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<row>
<Record>1</Record>
<Employee-ID>12345</Employee-ID>
<Authorization-ID>133746</Authorization-ID>
<Date>2021-06-22</Date>
<Quantity>2</Quantity>
<Task-ID>PRJTASK0011134</Task-ID>
<Project-Plan-ID>133746-GP OM Internal Labor-1</Project-Plan-ID>
<Comments/>
</row>
</root>
Error Variable Data:
<?xml version="1.0" encoding="UTF-8"?>
<errors>
<error>
<lineNumber>1</lineNumber>
<errorGroup>
<errorRow>
<severity>Error</severity>
<message>Invalid ID value. '' is not a valid ID value for type =
'Custom_Worktag_13_ID'</message>
</errorRow>
<errorRow>
<severity>Error</severity>
<message>Invalid ID value. '' is not a valid ID value for type =
'Custom_Organization_Reference_ID'</message>
</errorRow>
<errorRow>
<severity>Error</severity>
<message>Invalid ID value. '133746-GP OM Internal Labor-1' is not a valid ID value
for type = 'Project_Plan_ID'</message>
</errorRow>
<errorRow>
<severity>Error</severity>
<message>Invalid ID value. '12345' is not a valid ID value for type =
'Employee_ID'</message>
</errorRow>
</errorGroup>
</error>
</errors>
Current XSLT 3 Code:
<?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:map="http://www.w3.org/2005/xpath-functions/map" version="3.0">
<xsl:output method="xml" indent="yes"/>
<xsl:mode name="streaming" streamable="yes" on-no-match="shallow-skip"/>
<xsl:mode name="in-memory" streamable="no"/>
<xsl:variable name="lineKey" as="map(xs:string, element())">
<xsl:map>
<xsl:call-template name="generateErrorFileMap"/>
</xsl:map>
</xsl:variable>
<xsl:template match="root">
<root>
<xsl:apply-templates select="row/copy-of()" mode="in-memory"/>
</root>
</xsl:template>
<xsl:template match="row" mode="in-memory">
<xsl:choose>
<xsl:when test="map:contains($lineKey, Record)">
<xsl:iterate select="map:get($lineKey, Record)/errorGroup/errorRow">
<row>
<!-- Copy Nodes -->
<xsl:apply-templates/>
<Status>
<xsl:value-of select="concat(severity, ': ', message, '.')"/>
</Status>
</row>
</xsl:iterate>
</xsl:when>
<xsl:otherwise>
<row>
<!-- Copy Nodes -->
<xsl:apply-templates/>
<status>
<xsl:value-of select="'Successfully loaded.'"/>
</status>
</row>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- standard copy template -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template name="generateErrorFileMap">
<xsl:source-document href="mctx:vars/errorFile" streamable="yes">
<xsl:for-each select="/errors/error/copy-of()">
<xsl:map-entry key="lineNumber => string()">
<map>
<xsl:apply-templates select="errorGroup"/>
</map>
</xsl:map-entry>
</xsl:for-each>
</xsl:source-document>
</xsl:template>
</xsl:stylesheet>
What would be the best approach to achieve my desired output in an optimized way?
I think this is a task for xsl:merge (at least if the rows and errors are sorted by the lineNumber and Record integer value):
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:output method="xml" indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template name="xsl:initial-template">
<root>
<xsl:merge>
<xsl:merge-source name="record" for-each-source="'record-list.xml'" streamable="yes" select="root/row">
<xsl:merge-key select="xs:integer(Record)"/>
</xsl:merge-source>
<xsl:merge-source name="error" for-each-source="'error-list.xml'" streamable="yes" select="errors/error">
<xsl:merge-key select="xs:integer(lineNumber)"/>
</xsl:merge-source>
<xsl:merge-action>
<xsl:choose>
<xsl:when test="not(current-merge-group('error'))">
<xsl:apply-templates select="current-merge-group('record')">
<xsl:with-param name="status" select="'Successfully loaded.'"/>
</xsl:apply-templates>
</xsl:when>
<xsl:when test="current-merge-group('record') and current-merge-group('error')">
<xsl:for-each select="current-merge-group('error')/errorGroup/errorRow">
<xsl:apply-templates select="current-merge-group('record')">
<xsl:with-param name="status" select="severity || ':' || message"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:when>
</xsl:choose>
</xsl:merge-action>
</xsl:merge>
</root>
<xsl:comment xmlns:saxon="http://saxon.sf.net/">Run with {system-property('xsl:product-name')} {system-property('xsl:product-version')} {system-property('Q{http://saxon.sf.net/}platform')}</xsl:comment>
</xsl:template>
<xsl:template match="row/*[last()]">
<xsl:param name="status"/>
<xsl:next-match/>
<status>{$status}</status>
</xsl:template>
</xsl:stylesheet>
Run Saxon EE with the -it option instead of the -s source option.
To fix the original code, I think instead of
<xsl:when test="map:contains($lineKey, Record)">
<xsl:iterate select="map:get($lineKey, Record)/errorGroup/errorRow">
<row>
<!-- Copy Nodes -->
<xsl:apply-templates/>
<Status>
<xsl:value-of select="concat(severity, ': ', message, '.')"/>
</Status>
</row>
</xsl:iterate>
</xsl:when>
you want e.g.
<xsl:variable name="record" select="."/>
<xsl:when test="map:contains($lineKey, Record)">
<xsl:iterate select="map:get($lineKey, Record)/errorGroup/errorRow">
<xsl:apply-templates select="$record">
<xsl:with-param name="status" select="severity || ': ' || message || '.'"/>
</xsl:apply-templates>
</xsl:iterate>
</xsl:when>
plus a template (requires expand-text="yes" in scope)
<xsl:template match="row/*[last()]">
<xsl:param name="status"/>
<xsl:next-match/>
<status>{$status}</status>
</xsl:template>
I think that the result of
<xsl:apply-templates select="errorGroup"/>
is going to be an errorGroup element, which means that the result of map:get($lineKey, Record) will be an errorGroup element, which means that map:get($lineKey, Record)/errorGroup will select nothing (since an errorGroup element does not have an errorGroup child). Try changing the type declaration of the map to as="map(xs:string, element(errorGroup))" to strengthen the type checking.
That's the only thing I notice from a quick code check, but you haven't told us how it's failing; it's difficult to diagnose an invisible problem.
I have two large xml files, one of which has the following format:
<Persons>
<Person>
<ID>1</ID>
<LAST_NAME>London</LAST_NAME>
</Person>
<Person>
<ID>2</ID>
<LAST_NAME>Twain</LAST_NAME>
</Person>
<Person>
<ID>3</ID>
<LAST_NAME>Dikkens</LAST_NAME>
</Person>
</Persons>
The second file has the following format:
<SalesPersons>
<SalesPerson>
<ID>2</ID>
<LAST_NAME>London</LAST_NAME>
</SalesPerson>
<SalesPerson>
<ID>3</ID>
<LAST_NAME>Dikkens</LAST_NAME>
</SalesPerson>
</SalesPersons>
I need to find those records from file 1, which does not exist in file 2. Although I have it done using for-each loop, such an approach is taking a substantial amount of time. Is it possible to somehow make it run faster using a different approach?
Using a key can help to improve performance on lookups:
<xsl:key name="sales-person" match="SalesPerson" use="concat(ID, '|', LAST_NAME)"/>
<xsl:template match="/">
<xsl:for-each select="Persons/Person">
<xsl:variable name="person" select="."/>
<!-- need to change context document for key function use -->
<xsl:for-each select="$doc2">
<xsl:if test="not(key('sales-person', concat($person/ID, '|', $person/LAST_NAME)))">
<xsl:copy-of select="$person"/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
That assumes you have bound doc2 as a variable or parameter with e.g. <xsl:param name="doc2" select="document('sales-persons.xml')"/>.
All-
I am trying to understand the root cause for 2 nodes getting merged.
The input XML
<?xml version='1.0' encoding='UTF-8'?>
<Employees>
<Employee>
<Employee_ID>E00001</Employee_ID>
<Legal_Name Descriptor="John Doe" />
<lastName>Doe</lastName>
<firstName>John</firstName>
<P_From_Date>2015-04-01-08:00</P_From_Date>
<P_End_Date>2015-12-31-08:00</P_End_Date>
<Transaction>
<Plan Descriptor="Plan A" />
<effective_date>2015-03-22-08:00</effective_date>
<end_date>2015-10-22</end_date>
<Annual_Cost>6000</Annual_Cost>
</Transaction>
<Transaction>
<Plan Descriptor="Plan A" />
<effective_date>2015-02-03-08:00</effective_date>
<Annual_Cost>4000</Annual_Cost>
</Transaction>
<Transaction>
<Plan Descriptor="Plan A" />
<effective_date>2013-02-03-08:00</effective_date>
<Annual_Cost>3000</Annual_Cost>
</Transaction>
<Transaction>
<Plan Descriptor="Plan B" />
<effective_date>2014-12-03-08:00</effective_date>
<Annual_Cost>12000</Annual_Cost>
</Transaction>
<Transaction>
<Plan Descriptor="Plan B" />
<effective_date>2014-10-03-08:00</effective_date>
<Annual_Cost>1000</Annual_Cost>
</Transaction>
</Employee>
<Employee>
<Employee_ID>E00002</Employee_ID>
<Legal_Name Descriptor="John Doe" />
<lastName>Test</lastName>
<firstName>Jane</firstName>
<P_From_Date>2015-01-01-08:00</P_From_Date>
<Transaction>
<Plan Descriptor="Plan D" />
<effective_date>2015-05-22-08:00</effective_date>
<Annual_Cost>12000</Annual_Cost>
</Transaction>
<Transaction>
<Plan Descriptor="Plan D" />
<effective_date>2014-03-01-08:00</effective_date>
<Annual_Cost>9000</Annual_Cost>
</Transaction>
<Transaction>
<Plan Descriptor="Plan C" />
<effective_date>2014-12-03-08:00</effective_date>
<Annual_Cost>3000</Annual_Cost>
</Transaction>
<Transaction>
<Plan Descriptor="Plan C" />
<effective_date>2013-01-03-08:00</effective_date>
<Annual_Cost>3000</Annual_Cost>
</Transaction>
</Employee>
</Employees>
And the XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<!-- TODO customize transformation rules
syntax recommendation http://www.w3.org/TR/xslt
-->
<xsl:variable name="newline" select="'
'"/>
<xsl:variable name="delimiter">,</xsl:variable>
<xsl:variable name="qualifier">
<xsl:text>"</xsl:text>
</xsl:variable>
<xsl:template match="/Employees">
<EES>
<xsl:apply-templates/>
</EES>
</xsl:template>
<xsl:template match="Employee">
<EE>
<EID>
<xsl:value-of select="Employee_ID" />
</EID>
<FULNAME>
<xsl:value-of select="Legal_Name/#Descriptor"/>
</FULNAME>
<LNAME>
<xsl:value-of select="lastName"/>
</LNAME>
<FNAME>
<xsl:value-of select="firstName"/>
</FNAME>
<xsl:variable name="bework">
<xsl:for-each-group select="Transaction" group-by="Plan/#Descriptor" >
<berow>
<CurrentGroup>
<xsl:value-of select="current-grouping-key()"/>
</CurrentGroup>
<parm_from_date><xsl:value-of select="../P_From_Date" /></parm_from_date>
<xsl:for-each select="current-group()">
<begin-date><xsl:copy-of select="effective_date" /></begin-date>
<include-flag >
<xsl:choose>
<xsl:when test="xs:date( substring(effective_date,1,10)) >= xs:date(substring(../P_From_Date,1,10))">
<xsl:text>Y</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>N</xsl:text>
</xsl:otherwise>
</xsl:choose>
</include-flag>
<adj-begin-date>
<xsl:choose>
<xsl:when test="xs:date(effective_date) >= xs:date(../P_From_Date_1) ">
<xsl:value-of select="effective_date"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="../P_From_Date_1"/>
</xsl:otherwise>
</xsl:choose>
</adj-begin-date>
<end_date>
<xsl:value-of select="current-group()/end_date"/>
</end_date>
<Cost>
<xsl:value-of select="Annual_Cost"/>
</Cost>
</xsl:for-each>
</berow>
</xsl:for-each-group>
</xsl:variable>
<xsl:for-each select="$bework/berow">
<CGR>
<xsl:value-of select="CurrentGroup" />
</CGR>
<PFRMDT>
<xsl:value-of select="parm_from_date" />
</PFRMDT>
<BDT>
<xsl:value-of select="begin-date" />
</BDT>
<FLAG>
<xsl:value-of select="include-flag" />
</FLAG>
<ADJBGNDT>
<xsl:value-of select="$qualifier"/>
</ADJBGNDT>
<EDATE>
<xsl:value-of select="$qualifier"/>
</EDATE>
<CO>
<xsl:value-of select="Cost" />
</CO>
</xsl:for-each>
</EE>
</xsl:template>
</xsl:stylesheet>
The result
<?xml version="1.0" encoding="UTF-8"?>
<EES xmlns:xs="http://www.w3.org/2001/XMLSchema">
<EE>
<EID>E00001</EID>
<FULNAME>John Doe</FULNAME>
<LNAME>Doe</LNAME>
<FNAME>John</FNAME>
<CGR>Plan A</CGR>
<PFRMDT>2015-04-01-08:00</PFRMDT>
<BDT>2015-03-22-08:00 2015-02-03-08:00 2013-02-03-08:00</BDT>
<FLAG>N N N</FLAG>
<ADJBGNDT>"</ADJBGNDT>
<EDATE>"</EDATE>
<CO>6000 4000 3000</CO>
<CGR>Plan B</CGR>
<PFRMDT>2015-04-01-08:00</PFRMDT>
<BDT>2014-12-03-08:00 2014-10-03-08:00</BDT>
<FLAG>N N</FLAG>
<ADJBGNDT>"</ADJBGNDT>
<EDATE>"</EDATE>
<CO>12000 1000</CO>
</EE>
<EE>
<EID>E00002</EID>
<FULNAME>John Doe</FULNAME>
<LNAME>Test</LNAME>
<FNAME>Jane</FNAME>
<CGR>Plan D</CGR>
<PFRMDT>2015-01-01-08:00</PFRMDT>
<BDT>2015-05-22-08:00 2014-03-01-08:00</BDT>
<FLAG>Y N</FLAG>
<ADJBGNDT>"</ADJBGNDT>
<EDATE>"</EDATE>
<CO>12000 9000</CO>
<CGR>Plan C</CGR>
<PFRMDT>2015-01-01-08:00</PFRMDT>
<BDT>2014-12-03-08:00 2013-01-03-08:00</BDT>
<FLAG>N N</FLAG>
<ADJBGNDT>"</ADJBGNDT>
<EDATE>"</EDATE>
<CO>3000 3000</CO>
</EE>
</EES>
If you look at John Doe's BDT node, there are 2 dates. These are 2 nodes for the sample plan are getting added to the same node despite looping through the current group.
WHat is causing this? And what should be done to remedy this? I will have to use variables as there is more manulations I will have to do. But that is for another day.
Thanks for providng me some insight.
Inside of your bework variable you create a berow element for each Transaction group but then you use <xsl:for-each select="current-group()"> to output a begin-date for each Transaction in that group, without structuring or wrapping them further. With your input that means that berow element can contain two or three begin-date elements.
Then you have <xsl:for-each select="$bework/berow"> and inside
<BDT>
<xsl:value-of select="begin-date" />
</BDT>
which will select and output the string value of the two or three begin-date elements.
I am not sure which value you want to output for BDT, you could use e.g. <xsl:value-of select="begin-date[1]"/> or <xsl:value-of select="begin-date[last()]"/> to output only the string value of the first or last element created earlier.
Perhaps you just need to change:
<BDT>
<xsl:value-of select="begin-date" />
</BDT>
to:
<BDT>
<xsl:copy-of select="begin-date" />
</BDT>
In XSLT 2.0, xsl:value-of will generate a single text node, concatenating the values of all matching nodes, separated by a space (or another separator, if specified).
Additional explanation:
WHat is causing this?
The reason why your nodes are getting merged into a single text node is that you are using xsl:value-of when you try to fetch them from the $bework variable in order to write them into the output tree.
Consider the following simplified example:
XML
<root>
<item>
<category>A</category>
<amount>1000</amount>
</item>
<item>
<category>A</category>
<amount>500</amount>
</item>
<item>
<category>A</category>
<amount>250</amount>
</item>
<item>
<category>B</category>
<amount>600</amount>
</item>
<item>
<category>B</category>
<amount>300</amount>
</item>
</root>
XSLT 2.0
<xsl:stylesheet version="2.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:template match="/root">
<xsl:variable name="groups">
<xsl:for-each-group select="item" group-by="category">
<group category="{current-grouping-key()}">
<xsl:for-each select="current-group()">
<amount>
<xsl:value-of select="amount"/>
</amount>
</xsl:for-each>
</group>
</xsl:for-each-group>
</xsl:variable>
<output>
<xsl:for-each select="$groups/group">
<group category="{#category}">
<copy-of-amount>
<xsl:copy-of select="amount"/>
</copy-of-amount>
<for-each-amount>
<xsl:for-each select="amount">
<new-node value="{.}"/>
</xsl:for-each>
</for-each-amount>
<sum-of-amount>
<xsl:value-of select="sum(amount)"/>
</sum-of-amount>
<value-of-amount>
<xsl:value-of select="amount"/>
</value-of-amount>
</group>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<output>
<group category="A">
<copy-of-amount>
<amount>1000</amount>
<amount>500</amount>
<amount>250</amount>
</copy-of-amount>
<for-each-amount>
<new-node value="1000"/>
<new-node value="500"/>
<new-node value="250"/>
</for-each-amount>
<sum-of-amount>1750</sum-of-amount>
<value-of-amount>1000 500 250</value-of-amount>
</group>
<group category="B">
<copy-of-amount>
<amount>600</amount>
<amount>300</amount>
</copy-of-amount>
<for-each-amount>
<new-node value="600"/>
<new-node value="300"/>
</for-each-amount>
<sum-of-amount>900</sum-of-amount>
<value-of-amount>600 300</value-of-amount>
</group>
</output>
As you can see, inside the $myVar variable, each group contains 2-3 distinct amount nodes. You can copy them, sum them or create something for each one of them. However, when you do:
<xsl:value-of select="amount"/>
you are addressing all the amount nodes in the current group, and you will get a result that incorporates them all, same as:
<xsl:value-of select="sum(amount)"/>
returns a result based on all the amount nodes being addressed.
And what should be done to remedy this?
We won't know that until you tell us what is the actual result you want to get. In the comments below you said that:
the final result is really the total cost per employee.
If so, the example above shows how to get it.