Issue with Accumulator in XSLT3.0 - xslt

I have the below data from one data source,
<Reference>
<Worker>
<Employee_ID>1234</Employee_ID>
<Salary_Type>H</Salary_Type>
</Worker>
<Worker>
<Employee_ID>5678</Employee_ID>
<Salary_Type>H</Salary_Type>
</Worker>
</Reference>
the below data come from another data source,
<root>
<row>
<ID>1234</ID>
<ADDR>ABC</ADDR>
<PHONE>9999999998</PHONE>
<SAL>S</SAL>
</row>
<row>
<ID>5678</ID>
<ADDR>ABD</ADDR>
<PHONE>9999999999</PHONE>
<SAL>S</SAL>
</row>
</root>
I have merged the data from these data sources and have the following data,
<root>
<Reference>
<Worker>
<EmployeeID>1234</Employee_ID>
<SalaryType>H</Salary_Type>
</Worker>
<Worker>
<EmployeeID>5678</Employee_ID>
<SalaryType>H</Salary_Type>
</Worker>
</Reference>
<root>
<row>
<ID>1234</ID>
<ADDR>ABC</ADDR>
<PHONE>9999999998</PHONE>
<SAL>S</SAL>
</row>
<row>
<ID>5678</ID>
<ADDR>ABD</ADDR>
<PHONE>9999999999</PHONE>
<SAL>S</SAL>
</row>
</root>
</root>
In XSLT3.0 I m trying to replace the value of <SAL> with value of <SalaryType> with the following code, buts its not working.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="#all" version="3.0">
<xsl:mode streamable="no" on-no-match="shallow-copy" use-accumulators="#all"/>
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:accumulator name="EIDKey" as="xs:string" initial-value="''" streamable="yes">
<xsl:accumulator-rule match="/root/Reference/Worker/EmployeeID/text()" select="."/>
</xsl:accumulator>
<xsl:accumulator name="PayTypeLookup" as="map(xs:string,xs:string)" initial-value="map {}"
streamable="yes">
<xsl:accumulator-rule match="/root/Reference/Worker/SalaryType/text()"
select="map:put($value, accumulator-before('EIDKey'), string(.))"/>
</xsl:accumulator>
<xsl:template match="/">
<root>
<xsl:for-each select="root/root/row">
<row>
<xsl:apply-templates/>
</row>
</xsl:for-each>
</root>
</xsl:template>
<xsl:template match="SAL">
<xsl:variable name="PayType" select="normalize-space(.)"/>
<SAL><xsl:value-of select="accumulator-before('PayTypeLookup')($PayType)"/></SAL>
</xsl:template>
</xsl:stylesheet>
I need the final output like below, can you please help? Thanks in advance
<root>
<row>
<ID>1234</ID>
<ADDR>ABC</ADDR>
<PHONE>9999999998</PHONE>
<SAL>H</SAL>
</row>
<row>
<ID>5678</ID>
<ADDR>ABD</ADDR>
<PHONE>9999999999</PHONE>
<SAL>H</SAL>
</row>
</root>

I think you need another accumulator
<xsl:accumulator name="rowID" as="xs:string" initial-value="''" streamable="yes">
<xsl:accumulator-rule match="/root/root/row/ID/text()" select="."/>
</xsl:accumulator>
and then you can use
<xsl:template match="SAL">
<SAL><xsl:value-of select="accumulator-before('PayTypeLookup')(accumulator-before('rowID'))"/></SAL>
</xsl:template>

Related

Group XML elements with comma seperated values with XSLT program

We are new to xslt programming, can you please help us with xslt program.
We need to group xml elements based on "id" tag and concatenate the other xml tag with comma.
input xml file:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<row>
<id>123</id>
<functional_manager__c.users>1234567</functional_manager__c.users>
</row>
<row>
<id>123</id>
<functional_manager__c.users>1200000</functional_manager__c.users>
</row>
<row>
<id>111</id>
<functional_manager__c.users>11111111</functional_manager__c.users>
</row>
<row>
<id>111</id>
<functional_manager__c.users>2222222</functional_manager__c.users>
</row>
<row>
<id>123</id>
<editor__v.users>1234567</editor__v.users>
</row>
<row>
<id>123</id>
<editor__v.users>1200000</editor__v.users>
</row>
<row>
<id>111</id>
<learning_partner__c.users>11111111</learning_partner__c.users>
</row>
<row>
<id>111</id>
<learning_partner__c.users>2222222</learning_partner__c.users>
</row>
</root>
Required Output:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<row>
<id>123</id>
<functional_manager__c.users>1234567,1200000</functional_manager__c.users>
</row>
<row>
<id>111</id>
<functional_manager__c.users>11111111,2222222</functional_manager__c.users>
</row>
<row>
<id>123</id>
<editor__v.users>1234567,1200000</editor__v.users>
</row>
<row>
<id>111</id>
<learning_partner__c.users>11111111,2222222</learning_partner__c.users>
</row>
</root>
code we tried:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" exclude-result-prefixes="xsl wd xsd this env"
xmlns:wd="urn:com.workday/bsvc"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:this="urn:this-stylesheet">
<xsl:output indent="yes" method="xml"/>
<xsl:template match="/">
<Sharingsettings>
<xsl:for-each-group select="/root/row" group-by="id">
<row>
<ID>
<xsl:value-of select="id"/>
</ID>
<functional_manager__c.users>
<xsl:value-of select="//current-group()//functional_manager__c.users">
</xsl:value-of>
</functional_manager__c.users>
</row>
</xsl:for-each-group>
</Sharingsettings>
</xsl:template>
</xsl:stylesheet>
we are trying with XSLT program but it is not giving required output properly.
Thank you so much in advance
With XSLT 3 you can use
<?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 match="root">
<xsl:copy>
<xsl:for-each-group select="row" group-adjacent="id">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="row/*[not(self::id)]">
<xsl:copy>
<xsl:value-of select="current-group()/*[node-name() = node-name(current())]" separator=","/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Grouping and moving remaining nodes using XSLT 1.0

I have the following xml,
<?xml version="1.0" encoding="UTF-8"?>
<response>
<case>
<CMEDIA>Phone</CMEDIA>
</case>
<results>
<row>
<IKEY>TestKey1</IKEY>
<OBJECTID>TestObject1</OBJECTID>
</row>
<row>
<IKEY>TestKey1</IKEY>
<OBJECTID>TestObject2</OBJECTID>
</row>
<row>
<IKEY>TestKey1</IKEY>
<OBJECTID>TestObject3</OBJECTID>
</row>
<row>
<IKEY>TestKey4</IKEY>
<OBJECTID>TestObject4</OBJECTID>
</row>
</results>
</response>
My requirement is to group all the matching <IKEY> rows and move them under one <row> and moving all <OBJECTID> nodes under that new <row>.
<?xml version="1.0" encoding="UTF-8"?>
<response>
<case>
<CMEDIA>Phone</CMEDIA>
</case>
<results>
<row>
<IKEY>TestKey1</IKEY>
<OBJECTID>TestObject1</OBJECTID>
<OBJECTID>TestObject2</OBJECTID>
<OBJECTID>TestObject3</OBJECTID>
</row>
<row>
<IKEY>TestKey4</IKEY>
<OBJECTID>TestObject4</OBJECTID>
</row>
</results>
</response>
I am trying with the following xsl for grouping based on <IKEY>, but I am not able to move all <OBJECTID> nodes to new <row>(Here I have to use only XSLT 1.0).
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:key name="ikey" match="row" use="string(IKEY)" />
<xsl:template match="results">
<xsl:copy>
<xsl:apply-templates select="row[generate-id() = generate-id(key('ikey', string(IKEY))[1])]" mode="ikey" />
</xsl:copy>
</xsl:template>
<xsl:template match="row" mode="ikey">
<xsl:choose>
<xsl:when test="IKEY">
<row>
<xsl:apply-templates select="IKEY|OBJECTID" />
</row>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Can somebody tell me what I am missing here?
Change
<xsl:apply-templates select="IKEY|OBJECTID" />
to
<xsl:apply-templates select="IKEY|key('ikey', IKEY)/OBJECTID" />

Importing OAI source into Filemaker

I have a problem with importing an OAI source into Filemaker. The mapping is ok but the result is empty.
This is the source:
<OAI-PMH xmlns="http://www.openarchives.org/OAI/2.0/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dc="http://dublincore.org/documents/dcmi-namespace/" xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/ http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd">
<responseDate>2015-01-15T12:05:11Z</responseDate>
<request verb="ListRecords" metadataPrefix="oai_dc">
http://api.memorix-maior.nl/collectiebeheer/oai-pmh/key/SORRY_THIS_KEY_I_CANNOT_SHOW/tenant/nfm
</request>
<ListRecords>
<record>
<header>
<identifier>
e:1d59bf74-a57c-11e1-af90-bf6f69fae6b6:000a80bf-e7d6-7670-b2bd-c269b2e58878
</identifier>
etc.
And this is the xslt I made:
<?xml version='1.0' encoding='UTF-8'?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<FMPXMLRESULT xmlns="http://www.filemaker.com/fmpxmlresult">
<ERRORCODE>0</ERRORCODE>
<METADATA>
<FIELD NAME="identifier" TYPE="TEXT"/>
</METADATA>
<RESULTSET>
<xsl:for-each select="OAI-PMH/ListRecords/record">
<ROW>
<COL>
<DATA><xsl:value-of select="header/identifier"/></DATA>
</COL>
</ROW>
</xsl:for-each>
</RESULTSET>
</FMPXMLRESULT>
</xsl:template>
</xsl:stylesheet>
To make it clear I only pointed the first field in the OAI source.
I hope you can help me to fix this.
Best regards,
Boudewijn Ridder
The reason why your attempt doesn't work is that the source XML nodes are in a namespace. You must declare this namespace in your stylesheet, assign it a prefix and use that prefix when addressing the nodes:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:oai="http://www.openarchives.org/OAI/2.0/">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/">
<FMPXMLRESULT xmlns="http://www.filemaker.com/fmpxmlresult">
<METADATA>
<FIELD NAME="identifier" TYPE="TEXT"/>
</METADATA>
<RESULTSET>
<xsl:for-each select="oai:OAI-PMH/oai:ListRecords/oai:record">
<ROW>
<COL>
<DATA><xsl:value-of select="oai:header/oai:identifier"/></DATA>
</COL>
</ROW>
</xsl:for-each>
</RESULTSET>
</FMPXMLRESULT>
</xsl:template>
</xsl:stylesheet>
Note:
If your input example is representative, you might want to use :
<xsl:value-of select="normalize-space(oai:header/oai:identifier)"/>
to trim the extraneous whitespace from the result.

XSLT to remove duplicate values while concating

My XML looks like folloing:
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<BATCHES>
<item>
<Material>1000000079</Material>
<Description>330 Bulk</Description>
<Tank>T123</Tank>
<Batch>2013225287</Batch>
<Quantity>510</Quantity>
</item>
<item>
<Material>1000000079</Material>
<Description>330 Bulk</Description>
<Tank>T123</Tank>
<Batch>2013225301</Batch>
<Quantity>520</Quantity>
</item>
<item>
<Material>1000000196</Material>
<Description>340R Bulk</Description>
<Tank>T700</Tank>
<Batch>1000188378</Batch>
<Quantity>510</Quantity>
</item>
<item>
<Material>1000002754</Material>
<Description>43 Bulk</Description>
<Tank>T515</Tank>
<Batch>2013180125</Batch>
<Quantity>300</Quantity>
</item>
<item>
<Material>1000002754</Material>
<Description>43 Bulk</Description>
<Tank>T515</Tank>
<Batch>2013203124</Batch>
<Quantity>200</Quantity>
</item>
<item>
<Material>1000002754</Material>
<Description>43 Bulk</Description>
<Tank>T515</Tank>
<Batch>2013214839</Batch>
<Quantity>700</Quantity>
</item>
<item>
<Material>1000002754</Material>
<Description>43 Bulk</Description>
<Tank>T517</Tank>
<Batch>2013214342</Batch>
<Quantity>890</Quantity>
</item>
</BATCHES>
My original XSLT look like this:
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output encoding="UTF-8" indent="yes" method="xml" version="1.0"/>
<xsl:template match="/">
<Rowsets>
<Rowset>
<xsl:variable name="materials" select=".//item[Tank!='RECV' and Tank!='PARK'] "/>
<xsl:for-each select="$materials">
<xsl:if test="generate-id(.)= generate-id($materials[Material=current()/Material])">
<Row>
<Material>
<xsl:value-of select="Material"/>
</Material>
<Description>
<xsl:value-of select="Description"/>
</Description>
<Value>
<xsl:for-each select="$materials[Material=current()/Material]/Tank">
<xsl:if test="node()">
<xsl:value-of select="concat(.,'||')"/>
</xsl:if>
</xsl:for-each>
</Value>
</Row>
</xsl:if>
</xsl:for-each>
</Rowset>
</Rowsets>
</xsl:template>
</xsl:stylesheet>
The result of this XSLT looks like this:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Rowsets>
<Rowset>
<Row>
<Material>1000000079</Material>
<Description>330 Bulk</Description>
<Value>T123||T123||</Value>
</Row>
<Row>
<Material>1000000196</Material>
<Description>340R Bulk</Description>
<Value>T700||</Value>
</Row>
<Row>
<Material>1000002754</Material>
<Description>43 Bulk</Description>
<Value>T515||T517||</Value>
</Row>
</Rowset>
</Rowsets>
I wanted to remove duplicate tanks while concatinating it in Value field. So I changed my XSLT to follwoing:
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output encoding="UTF-8" indent="yes" method="xml" version="1.0"/>
<xsl:template match="/">
<Rowsets>
<Rowset>
<xsl:variable name="materials" select=".//item[Tank!='RECV' and Tank!='PARK' and Quantity > 500]"/>
<xsl:for-each select="$materials">
<xsl:if test="generate-id(.)= generate-id($materials[Material=current()/Material])">
<Row>
<Material>
<xsl:value-of select="Material"/>
</Material>
<Description>
<xsl:value-of select="Description"/>
</Description>
<Value>
<xsl:for-each select="$materials[Material=current()/Material]/Tank[not(.=preceding::Tank)]">
<xsl:if test="node()">
<xsl:value-of select="concat(.,'||')"/>
</xsl:if>
</xsl:for-each>
</Value>
</Row>
</xsl:if>
</xsl:for-each>
</Rowset>
</Rowsets>
</xsl:template>
</xsl:stylesheet>
My result now looks like this:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Rowsets>
<Rowset>
<Row>
<Material>1000000079</Material>
<Description>330 Bulk</Description>
<Value>T123||</Value>
</Row>
<Row>
<Material>1000000196</Material>
<Description>340R Bulk</Description>
<Value>T700||</Value>
</Row>
<Row>
<Material>1000002754</Material>
<Description>43 Bulk</Description>
<Value>T517||</Value>
</Row>
</Rowset>
</Rowsets>
It removed the duplicate tank T123 for material 1000000079 but for material 1000002754, it even removed T515which should appear in Value field as its quantity is greater than 500 for folloing:
<item>
<Material>1000002754</Material>
<Description>43 Bulk</Description>
<Tank>T515</Tank>
<Batch>2013214839</Batch>
<Quantity>700</Quantity>
</item>
what am I doing wrong here?
Ok I see your problem.
For this a key based solution Using Keys to Group: The Muenchian Method:
Try this:
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output encoding="UTF-8" indent="yes" method="xml" version="1.0"/>
<xsl:key name="kMaterial" match="item[Tank!='RECV' and Tank!='PARK' and Quantity > 500]"
use="Material"/>
<xsl:key name="kMaterialTank" match="item[Tank!='RECV' and Tank!='PARK' and Quantity > 500]"
use="concat(Material,'|', Tank)"/>
<xsl:template match="/">
<Rowsets>
<Rowset>
<xsl:variable name="materials" select=".//item[Tank!='RECV' and Tank!='PARK'] "/>
<xsl:for-each select="//item[ generate-id(.) = generate-id( key('kMaterial', Material)[1]) ]" >
<xsl:variable name="m" select="Material" />
<Row>
<Material>
<xsl:value-of select="Material"/>,<xsl:value-of select="$m"/>
</Material>
<Description>
<xsl:value-of select="Description"/>
</Description>
<Value>
<xsl:for-each select="//item[ generate-id(.) =
generate-id( key('kMaterialTank', concat( $m,'|', Tank))[1])]" mode="tank" >
<xsl:apply-templates select="." mode="tank" />
</xsl:for-each>
</Value>
</Row>
</xsl:for-each>
</Rowset>
</Rowsets>
</xsl:template>
<xsl:template match="item" mode="tank" >
<xsl:value-of select="Tank"/>
<xsl:text>||</xsl:text>
</xsl:template>
</xsl:stylesheet>
Which will generate the following output:
<?xml version="1.0" encoding="UTF-8"?>
<Rowsets>
<Rowset>
<Row>
<Material>1000000079,1000000079</Material>
<Description>330 Bulk</Description>
<Value>T123||</Value>
</Row>
<Row>
<Material>1000000196,1000000196</Material>
<Description>340R Bulk</Description>
<Value>T700||</Value>
</Row>
<Row>
<Material>1000002754,1000002754</Material>
<Description>43 Bulk</Description>
<Value>T515||T517||</Value>
</Row>
</Rowset>
</Rowsets>

Sorting and moving elements into a new element

Even with all the good tips on this site, I still have some trouble with my xslt. I'm pretty new to it. I have this source file:
<?xml version="1.0" encoding="utf-8"?>
<file>
<id>1</id>
<row type="A">
<name>ABC</name>
</row>
<row type="B">
<name>BCA</name>
</row>
<row type="A">
<name>CBA</name>
</row>
</file>
and I want to add an element and sort the rows on type, to get this result
<file>
<id>1</id>
<details>
<row type="A">
<name>ABC</name>
</row>
<row type="A">
<name>CBA</name>
</row>
<row type="B">
<name>BCA</name>
</row>
</details>
</file>
I'm able to sort the rows using this:
<xsl:template match="file">
<xsl:copy>
<xsl:apply-templates select="#*/row"/>
<xsl:apply-templates>
<xsl:sort select="#type" data-type="text"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
and I'm able to move the rows using this
<xsl:template match="file">
<xsl:copy>
<xsl:copy-of select="#*" />
<xsl:apply-templates select="*[not(name(.)='row')]" />
<details>
<xsl:apply-templates select="row" />
</details>
</xsl:copy>
</xsl:template>
but I'm not able to produce the correct answer when I try to combine them. Hopefully I understand more of XSLT when I see how things are combined. Since I'm creating a new element <details>, I think the sorting has to be done before the creation of the new <details> element. I have to use xslt 1.0.
Something like this seems to work:
<?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:template match="file">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:copy-of select="row[1]/preceding-sibling::*" />
<details>
<xsl:for-each select="row">
<xsl:sort select="#type" data-type="text"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</details>
<xsl:copy-of select="row[last()]/following-sibling::*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Here is the result I got:
<?xml version="1.0" encoding="utf-8"?>
<file>
<id>1</id>
<details>
<row type="A">
<name>ABC</name>
</row>
<row type="A">
<name>CBA</name>
</row>
<row type="B">
<name>BCA</name>
</row>
</details>
</file>