Sorting multiple field values from Input XML to top in the Output XML - xslt

Sorting the same field values from xml to top and rest field values to bottom.
this is my input to xslt:
<?xml version='1.0' encoding='UTF-8'?>
<feed>
<entry>
<name>David</name>
<updated>AA123</updated>
<title>BB123</title>
</entry>
<entry>
<name>John</name>
<updated>AA123</updated>
<title>AA123</title>
</entry>
<entry>
<name>Jenny</name>
<updated>CC789</updated>
<title>TT789</title>
</entry>
<entry>
<name>Dan</name>
<updated>CC456</updated>
<title>HH456</title>
</entry>
<entry>
<name>Steve</name>
<updated>CC456</updated>
<title>CC456</title>
</entry>
<entry>
<name>Jenny</name>
<updated>AB456</updated>
<title>DD789</title>
</entry>
</feed>
Expected Output:
<?xml version='1.0' encoding='UTF-8'?>
<feed>
<entry>
<name>John</name>
<updated>AA123</updated>
<title>AA123</title>
</entry>
<entry>
<name>Steve</name>
<updated>CC456</updated>
<title>CC456</title>
</entry>
<entry>
<name>David</name>
<updated>AA123</updated>
<title>BB123</title>
</entry>
<entry>
<name>Dan</name>
<updated>CC456</updated>
<title>HH456</title>
</entry>
<entry>
<name>Jenny</name>
<updated>AB456</updated>
<title>DD789</title>
</entry>
<entry>
<name>Jenny</name>
<updated>CC789</updated>
<title>TT789</title>
</entry>
</feed>
Below XSLT is not SORTING in correct way:
<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="/feed">
<Customer>
<xsl:for-each-group select="entry" group-by="updated">
<xsl:for-each-group select="current-group()" group-by="title">
<xsl:copy-of select="current-group()"/>
<xsl:apply-templates>
<xsl:sort select="updated"/>
<xsl:sort select="title"/>
</xsl:apply-templates>
</xsl:for-each-group>
</xsl:for-each-group>
</Customer>
</xsl:template>
</xsl:stylesheet>
I have a requirement to SORT same FIELD values of field name "updated" and "title". If both the field values are same then it should come first in the OUTPUT XML and then later the different field value.

If I understand correctly, you want to do:
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:template match="/feed">
<feed>
<xsl:perform-sort select="entry">
<xsl:sort select="number(updated = title)" data-type="number" order="descending"/>
</xsl:perform-sort>
</feed>
</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>

How to use XSLT to create distinct values in target using XSLT 1.0 [duplicate]

I have challenge to add xml tags dynamically based on count of one xml tag and also should not allow duplicates (I am using XSLT 1.0). For ex: I have 3 Creditor records in "CreditorPPContractParts" section in below xml as shown in test data.
<PPPrivPropertyLine>
<InsuredProperties>
<Entry>
<Buildings>
<Entry>
<AlarmClass>None_De</AlarmClass>
<InterestType>OwnerOccupied_De</InterestType>
<BuildingStandard_De>Normal</BuildingStandard_De>
</Entry>
</Buildings>
<ContractParts>
<Entry>
<CreditorPPContractParts>
<Entry>
<Creditor>
<Contact>
<AddressBookUID>D73GLX</AddressBookUID>
</Contact>
</Creditor>
</Entry>
<Entry>
<Creditor>
<Contact>
<AddressBookUID>OAS5OE</AddressBookUID>
</Contact>
</Creditor>
</Entry>
<Entry>
<Creditor>
<Contact>
<AddressBookUID>OAS5OE</AddressBookUID>
</Contact>
</Creditor>
</Entry>
</CreditorPPContractParts>
</Entry>
</ContractParts>
</Entry>
</InsuredProperties>
<PolicyContactRoles></PolicyContactRoles>
</PPPrivPropertyLine>
Now I have to create 3 entries in 'PolicyContactRoles' in same xml like below format since I've 3 creditor records above. We may have more than 3 creditor records but we need to add based on the creditor records count. As I said above we should not allow duplicates. We have one duplicate creditor record. so output should be 2 creditor entries.
<PolicyContactRoles>
<Entry>
<AccountContactRole>
<Subtype>Creditor_De</Subtype>
<AccountContact>
<Contact>
<AddressBookUID>D73GLX</AddressBookUID>
</Contact>
</AccountContact>
</AccountContactRole>
<Subtype>PolicyCreditor_De</Subtype>
</Entry>
<Entry>
<AccountContactRole>
<Subtype>Creditor_De</Subtype>
<AccountContact>
<Contact>
<AddressBookUID>OAS5OE</AddressBookUID>
</Contact>
</AccountContact>
</AccountContactRole>
<Subtype>PolicyCreditor_De</Subtype>
</Entry>
</PolicyContactRoles>
I have done it using below XSLT script. but could not able to avoid the duplicates. Please help me out, thank you!
<xsl:template match="PolicyContactRoles">
<xsl:copy>
<xsl:apply-templates select="//Creditor" mode="pcr"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Creditor" mode="pcr">
<Entry>
<AccountContactRole>
<Subtype>Creditor_De</Subtype>
<AccountContact>
<Contact>
<xsl:copy-of select=".//AddressBookUID"/>
</Contact>
</AccountContact>
</AccountContactRole>
<Subtype>PolicyCreditor_De</Subtype>
</Entry>
</xsl:template>
And also, please use this XSLT Fiddle:https://xsltfiddle.liberty-development.net/pNEj9dH/13
Use Muenchian grouping to get only distinct creditors:
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="creditor" match="Creditor" use="Contact/AddressBookUID" />
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="PolicyContactRoles">
<xsl:copy>
<xsl:for-each select="//Creditor[count(. | key('creditor', Contact/AddressBookUID)[1]) = 1]">
<Entry>
<AccountContactRole>
<Subtype>Creditor_De</Subtype>
<AccountContact>
<Contact>
<xsl:copy-of select="Contact/AddressBookUID"/>
</Contact>
</AccountContact>
</AccountContactRole>
<Subtype>PolicyCreditor_De</Subtype>
</Entry>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

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" />

XSLT: Sort by multiple items

I have XML data such as:
<feed>
<entry>
<id>4</id>
<updated>2011-01-18T16:55:54Z</updated>
<title>title2</title>
</entry>
<entry>
<id>3</id>
<updated>2011-01-18T16:55:54Z</updated>
<title>title1</title>
</entry>
<entry>
<id>2</id>
<updated>2011-01-18T16:55:54Z</updated>
<title>title1</title>
</entry>
<entry>
<id>1</id>
<updated>2011-01-18T16:55:54Z</updated>
<title>title</title>
</entry>
</feed>
And I need the outcome to result like:
<feed>
<entry>
<id>1</id>
<updated>2011-01-18T16:55:54Z</updated>
<title>title</title>
</entry>
<entry>
<id>2</id>
<updated>2011-01-18T16:55:54Z</updated>
<title>title1</title>
</entry>
<entry>
<id>3</id>
<updated>2011-01-18T16:55:54Z</updated>
<title>title1</title>
</entry>
<entry>
<id>4</id>
<updated>2011-01-18T16:55:54Z</updated>
<title>title2</title>
</entry>
</feed>
Basically I need the XSLT to sort on the title, then the ID. I have made an XSLT but the shorter times come out last (using Xerces):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="atom:feed">
<xsl:copy>
<xsl:apply-templates select="*" />
<xsl:for-each select="atom:entry">
<xsl:sort select="string-length(atom:title)" order="descending" />
<xsl:sort select="atom:title" data-type="text" order="ascending" />
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="atom:feed/atom:entry"/>
</xsl:stylesheet>
For your input sample (not actually an Atom feed), this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="feed">
<xsl:copy>
<xsl:apply-templates>
<xsl:sort select="update" order="descending"/>
<xsl:sort select="title"/>
<xsl:sort select="id" data-type="number"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output:
<feed>
<entry>
<id>1</id>
<updated>2011-01-18T16:55:54Z</updated>
<title>title</title>
</entry>
<entry>
<id>2</id>
<updated>2011-01-18T16:55:54Z</updated>
<title>title1</title>
</entry>
<entry>
<id>3</id>
<updated>2011-01-18T16:55:54Z</updated>
<title>title1</title>
</entry>
<entry>
<id>4</id>
<updated>2011-01-18T16:55:54Z</updated>
<title>title2</title>
</entry>
</feed>
Note: This date time format can be ordered like string (default) as long as there is no different time zone.