XSLT Group by using 2 different elements - xslt

I'm new to xslt. I'm trying to group by 2 different elements. First by Worker, then by Pay code. Please see that the amounts sum because of the group by paycode. Below is a before sample xsl, then a sample after xsl that I would like as output.
Before:
<?xml version='1.0' encoding='utf-8'?>
<File xmlns:is="java:com.workday.esb.intsys.xpath.ParsedIntegrationSystemFunctions"
xmlns:tv="java:com.workday.esb.intsys.TypedValue">
<Worker>
<Detail>
<EmployeeID>0008765</EmployeeID>
<FirstName>ROBERT</FirstName>
<PayCode>RSVEST</PayCode>
<Amount>5572.800000</Amount>
</Detail>
<Detail>
<EmployeeID>0008765</EmployeeID>
<FirstName>ROBERT</FirstName>
<PayCode>FICA</PayCode>
<Amount>40.000000</Amount>
</Detail>
</Worker>
<Worker>
<Detail>
<EmployeeID>0008765</EmployeeID>
<FirstName>ROBERT</FirstName>
<PayCode>RSVEST</PayCode>
<Amount>13545.000000</Amount>
</Detail>
</Worker>
<Worker>
<Detail>
<EmployeeID>00012345</EmployeeID>
<FirstName>RUSSELL</FirstName>
<PayCode>RSVEST</PayCode>
<Amount>84811.050000</Amount>
</Detail>
</Worker>
</File>
What I would like as output, grouping first by Worker, then group by Pay Code. Amounts sum because of grouping:
<?xml version='1.0' encoding='utf-8'?>
<File xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tv="java:com.workday.esb.intsys.TypedValue">
<Worker>
<Detail>
<EmployeeID>0008765</EmployeeID>
<FirstName>ROBERT</FirstName>
<PayCode>RSVEST</PayCode>
<Amount>19117.800000</Amount>
</Detail>
<Detail>
<EmployeeID>0008765</EmployeeID>
<FirstName>ROBERT</FirstName>
<PayCode>FICA</PayCode>
<Amount>40.000000</Amount>
</Detail>
</Worker>
<Worker>
<Detail>
<EmployeeID>00012345</EmployeeID>
<FirstName>RUSSELL</FirstName>
<PayCode>RSVEST</PayCode>
<Amount>84811.050000</Amount>
</Detail>
</Worker>
</File>
Below is my XSL that doesn't work:
<?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="/">
<File>
<Worker>
<xsl:for-each-group select="File" group-by="Worker">
<Detail>
<EmployeeID><xsl:value-of select="Worker/current-group()/EmployeeID"></xsl:value-of></EmployeeID>
<FirstName><xsl:value-of select="//current-group()//FirstName"></xsl:value-of></FirstName>
<PayCode><xsl:value-of select="PayCode"></xsl:value-of></PayCode>
<Amount><xsl:value-of select="format-number(sum(current-group()/number(translate(Amount,',',''))),'######.00')"></xsl:value-of></Amount>
</Detail>
</xsl:for-each-group>
</Worker>
</File>
</xsl:template>
</xsl:stylesheet>
Can someone please put an SL that will transform above to the desired output?
I've been working on this for hours and waving the white flag lol.
Thank you!

If you want to have a group for each EmployeeID, and within each such group a subgroup for each PayCode, then obviously you need to nest two xsl:for-each-group instructions. And the nodes you need to be grouping are the Detail elements, not the root File element of which there only will be one.
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="/File">
<xsl:copy>
<xsl:for-each-group select="Worker/Detail" group-by="EmployeeID">
<Worker>
<xsl:for-each-group select="current-group()" group-by="PayCode">
<Detail>
<xsl:copy-of select="EmployeeID | FirstName | PayCode"/>
<Amount>
<xsl:value-of select="format-number(sum(current-group()/Amount),'#.000000')"/>
</Amount>
</Detail>
</xsl:for-each-group>
</Worker>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Related

Merge two xml files based on key field

I have requirement two merge two xml files. Based on the key field in the files I want to merge entire content of the xml 1 to xml 2.
Could you please help me to achieve this scenario.
I have tried this xslt but I am getting below error.
"a sequence of more than one item is not allowed as the first argument of fn:parse-xml()"
XSLT code.
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:mode on-no-match="shallow-copy" />
<xsl:param name="XML1" />
<xsl:variable name="details-doc" select="parse-xml($XML1)" />
<xsl:template match="Key">
<xsl:copy-of select="." />
<xsl:variable name="Key" select="string(.)" />
<xsl:copy-of select="$details-doc/Record/Detail[Key = $Key]" />
</xsl:template>
</xsl:stylesheet>
XML1.
<Record>
<Detail>
<Key>1</Key>
<Place>Ocean</Place>
<City>Urban</City>
</Detail>
<Detail>
<Key>2</Key>
<Place>Road</Place>
<City>Rural</City>
</Detail>
<Detail>
<Key>3</Key>
<Place>Plane</Place>
<City>Semiurban</City>
</Detail>
</Record>
XML2
<Record>
<Contact>
<Key>1</Key>
<Name>Jack</Name>
</Contact>
<Contact>
<Key>2</Key>
<Name>Ethan</Name>
</Contact>
<Contact>
<Key>3</Key>
<Name>Ron</Name>
</Contact>
</Record>
And the expected output.
<Record>
<Contact>
<Key>1</Key>
<Name>Jack</Name>
<Place>Ocean</Place>
<City>Urban</City>
</Contact>
<Contact>
<Key>2</Key>
<Name>Ethan</Name>
<Place>Road</Place>
<City>Rural</City>
</Contact>
<Contact>
<Key>3</Key>
<Name>Ron</Name>
<Place>Plane</Place>
<City>Semiurban</City>
</Contact>
</Record>

XSLT transformation validate XSI:type (or) element node exists

I have an input XML, which needs to be filtered if element node exists 'ns1:getGenResponse' (or) validate with xsi:type = "Gen" of 'multiRef' element
If either one of the condition successful then I can process 'multiRef' records.
Input XML:
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
>
<soapenv:Body>
<ns1:getGenResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="http://service.pen.eewe.en.de>
<ns1:getGenReturn xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="soapenc:Array" soapenc:arrayType="xsd:anyType[2]">
<item href="#id0" />
<item href="#id1" />
</ns1:getGenReturn>
</ns1:getGenResponse>
<multiRef xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" id="id0" soapenc:root="0" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="ns2:Gen">
<name xsi:type="xsd:string">ulm</name>
<mail xsi:type="xsd:string">ulm#gmail.com</mail>
</multiRef>
<multiRef xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" id="id1" soapenc:root="0" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xsi:type="ns3:Gen">
<name xsi:type="xsd:string">ABC</name>
<mail xsi:type="xsd:string">abc#gmail.com</mail>
</multiRef>
</soapenv:Body>
</soapenv:Envelope>
Trying with node exist 'ns1:getGenResponse':
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:response="http://tempuri.org/"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
exclude-result-prefixes="soap response"
>
<!-- Output -->
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/">
<getGenResponse>
<xsl:for-each select="//soap:Body[ns1:getGenResponse]/multiRef">
<getGenReturn>
<name>
<xsl:value-of select="name" />
</name>
<mail>
<xsl:value-of select="mail" />
</mail>
</getGenReturn>
</xsl:for-each>
</getGenResponse>
</xsl:template>
</xsl:stylesheet>
With this XSLT, I can generate blank . I can't able to generate my desire output.
I am also trying to validate the data with xsi:type of 'multiRef' element node
with below code, but I am unable to execute the XSLT
**Trying to validate with xsi:type ="Gen"**
<xsl:stylesheet version="1.0" xmlns:response="http://tempuri.org/"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude-result-prefixes="soap response">
<!-- Output -->
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<getGenResponse>
<xsl:for-each select="//soap:Body/multiRef[substring- after(#xsi:type, ':')='Gen']">
<getGenReturn>
<name><xsl:value-of select="name"/></name>
<mail><xsl:value-of select="mail"/></mail>
</getGenReturn>
</xsl:for-each>
</getGenResponse>
</xsl:template>
</xsl:stylesheet>
Output expects:
**Output Expected**
<?xml version="1.0" encoding="UTF-8"?>
<getGenResponse>
<getGenReturn>
<name> ULM </name>
<mail>ulm#gmail.com<mail>
</getGenReturn>
<getGenReturn>
<name>ABC</name>
<mail>abc#gmail.com<mail>
</getGenReturn>
/getGenResponse>
Thank you very much.
yes, I declared xmlns:ns1="http://service.pen.eewe.en.de" in the XSLT. now I am able to execute the code with desired result.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:response="http://tempuri.org/"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
exclude-result-prefixes="soap response" xmlns:ns1="http://service.pen.eewe.en.de"
>
<!-- Output -->
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/">
<getGenResponse>
<xsl:for-each select="//soap:Body[ns1:getGenResponse]/multiRef">
<getGenReturn>
<name>
<xsl:value-of select="name" />
</name>
<mail>
<xsl:value-of select="mail" />
</mail>
</getGenReturn>
</xsl:for-each>
</getGenResponse>
</xsl:template>
</xsl:stylesheet>

XSLT keep namespace on output

I need help preserving the namespace on XSLT transformation. I see other threads have solutions, but I don't understand them. When I use the XSLT transformation below, all the namespace disappears. Below is what I'm trying to accomplish. Any help is much appreciated! Thank you!
Before transform XML:
<?xml version='1.0' encoding='UTF-8'?>
<Worker
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:this="this.file/eib"
xmlns:is="java:com.workday.esb.intsys.xpath.ParsedIntegrationSystemFunctions"
xmlns:tv="java:com.workday.esb.intsys.TypedValue">
<Detail>
<EmployeeID>123456</EmployeeID>
<PayCode>Earning</PayCode>
<Amount>4243.20</Amount>
</Detail>
<Detail>
<EmployeeID>123456</EmployeeID>
<PayCode>Deduction</PayCode>
<Amount>2265.60</Amount>
</Detail>
</Worker>
My XSLT transformation:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" exclude-result-prefixes="xsl wd is 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:is="java:com.workday.esb.intsys.xpath.ParsedIntegrationSystemFunctions"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:this="urn:this-stylesheet">
<xsl:output indent="yes" method="xml"/>
<xsl:template match="/">
<xsl:copy>
<Worker>
<xsl:for-each select="Worker" >
<Detail>
<EmployeeID><xsl:value-of select="Detail[(PayCode ='Earning')]/EmployeeID"/></EmployeeID>
<PayCode><xsl:value-of select="Detail[(PayCode ='Earning')]/PayCode"/></PayCode>
<Amount><xsl:value-of select="Detail[(PayCode ='Earning') and (string-length(Amount) > 0)]/Amount"/></Amount>
</Detail>
</xsl:for-each>
</Worker>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Desired output:
<?xml version="1.0" encoding="UTF-8"?>
<Worker
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:this="this.file/eib"
xmlns:is="java:com.workday.esb.intsys.xpath.ParsedIntegrationSystemFunctions"
xmlns:tv="java:com.workday.esb.intsys.TypedValue">
<Detail>
<EmployeeID>123456</EmployeeID>
<PayCode>Earning</PayCode>
<Amount>4243.20</Amount>
</Detail>
</Worker>
At least in XSLT 2.0 (the version you used), namespaces disappear because you used exclude-result-prefixes attribute.
Just remove it and all namespaces included in your XSLT sheet, except for xsl, will be presented in the output, in the order of appearance.
But if you decided to omit all namespaces, it is easier to write exclude-result-prefixes="#all", instead of writing them again.
I can see no good reason to keep namespace declarations that are not used in your output. But if you really want to have them, why don't you simply copy them (as part of their parent element) - for example:
XSLT
<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="/Worker">
<xsl:copy>
<xsl:copy-of select="Detail[PayCode ='Earning']"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When this is applied to your input example, the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<Worker xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:this="this.file/eib"
xmlns:is="java:com.workday.esb.intsys.xpath.ParsedIntegrationSystemFunctions"
xmlns:tv="java:com.workday.esb.intsys.TypedValue">
<Detail>
<EmployeeID>123456</EmployeeID>
<PayCode>Earning</PayCode>
<Amount>4243.20</Amount>
</Detail>
</Worker>
Demo: http://xsltransform.net/gVhD8Qt

Moving similar nodes to new node using XSLT

I have the following input xml:
<?xml version="1.0" encoding="UTF-8"?>
<response>
<PQGetCareGaps>
<METHOD>GET</METHOD>
<contract>HXXXX</contract>
</PQGetCareGaps>
<FinalCareGapResults>
<Gap>
<CareGap>Colorectal Cancer Screening</CareGap>
<GapHistory>
<row>
<MEMBERID>AAAAAA000016-00</MEMBERID>
</row>
</GapHistory>
</Gap>
</FinalCareGapResults>
<FinalCareGapResults>
<Gap>
<CareGap>Adult BMI Assessment</CareGap>
</Gap>
</FinalCareGapResults>
</response>
I want to modify the above xml in such a way that all the <Gap> nodes should come under a new node called <TestResults>. The resultant xml should look like below:
<?xml version="1.0" encoding="UTF-8"?>
<response>
<PQGetCareGaps>
<METHOD>GET</METHOD>
<contract>HXXXX</contract>
</PQGetCareGaps>
<TestResults>
<Gap>
<CareGap>Colorectal Cancer Screening</CareGap>
<GapHistory>
<row>
<MEMBERID>AAAAAA000016-00</MEMBERID>
</row>
</GapHistory>
</Gap>
<Gap>
<CareGap>Adult BMI Assessment</CareGap>
</Gap>
</TestResults>
</response>
Could you please help me out?
Try this
<?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"
exclude-result-prefixes="xs"
version="1.0">
<xsl:template match="/">
<xsl:apply-templates select="response" />
</xsl:template>
<xsl:template match="response">
<response>
<xsl:copy-of select="PQGetCareGaps" />
<TestResults>
<xsl:for-each select="FinalCareGapResults/Gap">
<xsl:copy-of select="." />
</xsl:for-each>
</TestResults>
</response>
</xsl:template>
</xsl:stylesheet>

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.