Group XML elements with comma seperated values with XSLT program - xslt

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>

Related

Issue with Accumulator in XSLT3.0

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>

Sum for distinct employee using xslt

I have a xml as below:
<root>
<row>
<Leave_days>6</Leave_days>
<Maximum>10</Maximum>
<Employee>John</Employee>
</row>
<row>
<Leave_days>4</Leave_days>
<Maximum>15</Maximum>
<Employee>Albert</Employee>
</row>
<row>
<Leave_days>2</Leave_days>
<Maximum>10</Maximum>
<Employee>John</Employee>
</row>
</root>
I need to sum the 'Maximum' but not consider the repeating employee. So for the example above, the output should be 25 (10 for John and 15 for Albert - should ignore the 2nd row for John as it is repeating)
<Root>
<row>25</row>
</Root>
In the below xslt, I'm not sure how to add a condition to eliminate the repeating employee row:
<xsl:template match="root">
<Root>
<row>
<xsl:value-of select="sum(row/Maximum)"/>
</row>
</Root>
</xsl:template>
Also tried with group-by but not able to achieve the result. Please help.
I believe the simplest solution - even in XSLT 2.0 - would be to use the Muenchian grouping expression to select the distinct employee rows:
<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="row-by-emp" match="row" use="Employee" />
<xsl:template match="/root">
<Root>
<row>
<xsl:value-of select="sum(row[count(. | key('row-by-emp', Employee)[1]) = 1]/Maximum)"/>
</row>
</Root>
</xsl:template>
</xsl:stylesheet>
To do this with XSLT 2.0 group-by you'd need something like:
<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:template match="/root">
<xsl:variable name="maxima">
<xsl:for-each-group select="row" group-by="Employee">
<xsl:copy-of select="Maximum"/>
</xsl:for-each-group>
</xsl:variable>
<Root>
<row>
<xsl:value-of select="sum($maxima/Maximum)"/>
</row>
</Root>
</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" />

XSLT: How to delete all nodes except ones having a child with certain value?

I am trying to delete from an XML file, all the nodes that not satisfy certain condition.
This is my simplified input:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<unit xmlns="http://www.srcML.org/srcML/src">
<Rowsets>
<Rowset>
<Row>
<FirstName>Michael</FirstName>
<LastName>David</LastName>
<Phone>1234567890</Phone>
</Row>
<Row>
<FirstName>David</FirstName>
<LastName>Michael</LastName>
<Phone>01234567890</Phone>
</Row>
<Row>
<FirstName>Yang</FirstName>
<LastName>Christina</LastName>
<Phone>2345678901</Phone>
</Row>
<Row>
<FirstName>Grey</FirstName>
<LastName>Meredith</LastName>
<Phone>3456789012</Phone>
</Row>
<Row>
<FirstName>Michael</FirstName>
<LastName>Shepherd</LastName>
<Phone>5678901234</Phone>
</Row>
</Rowset>
</Rowsets>
<Tag>
<FirstName>Michael</FirstName>
<LastName>Shepherd</LastName>
<Phone>5678901234</Phone>
</Tag>
</unit>
I would like to write an XSLT transformation file that will be able to delete all the nodes that don't have a child of type FirstName with value Michael.
For the input I have just provided, I would like to obtain this output:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<unit xmlns="http://www.srcML.org/srcML/src">
<Row>
<FirstName>Michael</FirstName>
<LastName>David</LastName>
<Phone>1234567890</Phone>
</Row>
<Row>
<FirstName>Michael</FirstName>
<LastName>Shepherd</LastName>
<Phone>5678901234</Phone>
</Row>
<Tag>
<FirstName>Michael</FirstName>
<LastName>Shepherd</LastName>
<Phone>5678901234</Phone>
</Tag>
</unit>
This is the transformation file I have written up now:
<xsl:stylesheet
xmlns="http://www.srcML.org/srcML/src"
xmlns:src="http://www.srcML.org/srcML/src"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template name="removingNotMichael" match="*[child::src:FirstName[.= 'Michael']]">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
</xsl:template>
</xsl:stylesheet>
It does not produce anything, just a completely empty file.
Could anybody please help me with this issue?
You can Try this:
<xsl:template match="/">
<unit>
<xsl:copy-of select="//*[src:FirstName = 'Michael']"/>
</unit>
</xsl:template>

EOF (End Of File) expexted error in output XML after XSL transformation

I have "EOF Expexted" error in output XML after XSL transformation.
Question: What i need to do with this XSLT to get correct output?
Input XML:
<RowSet>
<Row>
<msg_id>1</msg_id>
<doc_id>1</doc_id>
<doc_version>1</doc_version>
</Row>
<Row>
<msg_id>2</msg_id>
<doc_id>1</doc_id>
<doc_version>2</doc_version>
</Row>
<Row>
<msg_id>3</msg_id>
<doc_id>1</doc_id>
<doc_version>3</doc_version>
</Row>
<Row>
<msg_id>4</msg_id>
<doc_id>2</doc_id>
<doc_version>1</doc_version>
</Row>
<RowSet>
XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kRowByDocId" match="Row" use="doc_id"/>
<xsl:template match="/*">
<xsl:apply-templates select=
"Row[generate-id()=generate-id(key('kRowByDocId', doc_id)[1])]"/>
</xsl:template>
<xsl:template match="Row">
<xsl:for-each select="key('kRowByDocId',doc_id)">
<xsl:sort select="doc_version" data-type="number" order="descending"/>
<xsl:if test="position() = 1"><xsl:copy-of select="."/></xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output that i'm getting now (with EOF error):
<Row>
<msg_id>3</msg_id>
<doc_id>1</doc_id>
<doc_version>3</doc_version>
</Row>
<Row>
<msg_id>4</msg_id>
<doc_id>2</doc_id>
<doc_version>1</doc_version>
</Row>
Correct output must be something like this:
<RowSet>
<Row>
<msg_id>3</msg_id>
<doc_id>1</doc_id>
<doc_version>3</doc_version>
</Row>
<Row>
<msg_id>4</msg_id>
<doc_id>2</doc_id>
<doc_version>1</doc_version>
</Row>
<RowSet>
Thank you!
This is quite simple. If you want to output a Rowset element, you just to need to output one in your template that matches the root element
<xsl:template match="/*">
<RowSet>
<xsl:apply-templates select="Row[generate-id()=generate-id(key('kRowByDocId', doc_id)[1])]"/>
</RowSet>
</xsl:template>
Alternatively, if you don't want to hardcode the RowSet element, you can just use xsl:copy to copy whatever the root element is:
<xsl:template match="/*">
<xsl:copy>
<xsl:apply-templates select="Row[generate-id()=generate-id(key('kRowByDocId', doc_id)[1])]"/>
</xsl:copy>
</xsl:template>