Create xml tree from xPath expression using xslt - xslt

I have a sample source xml like:
<?xml version="1.0" encoding="utf-16"?>
<shiporder orderid="orderid1">
<item>
item1
</item>
<item>
item2
</item>
</shiporder>
The assumptions for the transformation of my source xml is that I have a list of xPaths in this case /shiporder[1]/item[1] and /shiporder[1]/item[2] and the information that I have to transform the content of those nodes into Customer[1]/adress[1] and Customer[1]/adress[2]. In detail the result should look like:
<?xml version="1.0" encoding="utf-16"?>
<customer>
<adress>
item1
</adress>
<adress>
item2
</adress>
</customer>
Now I'm looking for a template which applies for each path in my source list ["/shiporder[1]/item"] and transform the inner text of that nodes into nodes with respect to ["Customer[1]/adress"].
First of all I thought to create templates like:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:func="http://www.functx.com">
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/shiporder[1]">
<Customer>
<xsl:apply-templates select="item"/>
</Customer>
</xsl:template>
<xsl:template match="/shiporder[1]/item">
<adress>
<xsl:value-of select="text()"/>
</adress>
</xsl:template>
</xsl:stylesheet>
But later I was thinking about a function which combines the implicit call of the templates in a recursive way. I hope it is clear from here on other wise I could add some pseudo code what kind of function I have in mind.

Related

How to use Key in XSLT Oxygen

I am trying to get the output based on two separate nodes in XML using key concept of XSLT
I have below XML
<?xml version="1.0" encoding="UTF-8"?>
<mdti:Input xmlns:mdti="urn:com.workday/multiDocumentTransform/Input">
<mdti:Files xmlns:mdti="urn:com.workday/multiDocumentTransform/Input">
<mdti:EventFiles>
<mdti:File mdti:filename="first.xml" mdti:contentType="text/xml">
<wd:Report_Data xmlns:wd="urn:com.workday/bsvc">
<wd:Report_Entry>
<wd:key>1234</wd:key>
<wd:comp>ABC</wd:comp>
<wd:asof>2021-03-24T04:59:32.179-07:00</wd:asof>
<wd:emplid>33333333</wd:emplid>
<wd:worker_type>EMP</wd:worker_type>
<wd:emp_type>Regular</wd:emp_type>
<wd:orig_hire_dt>2021-11-27</wd:orig_hire_dt>
<wd:rehire_dt>2019-04-01</wd:rehire_dt>
<wd:home_host_class>M</wd:home_host_class>
<wd:service_dt>2014-11-27</wd:service_dt>
</wd:Report_Entry>
</wd:Report_Data>
</mdti:File>
<mdti:File mdti:filename="second.xml" mdti:contentType="text/xml">
<wd:Report_Data xmlns:wd="urn:com.workday/bsvc">
<wd:Report_Entry>
<wd:key>1234</wd:key>
<wd:supervisor_lname>xyz</wd:supervisor_lname>
<wd:hr_status>A</wd:hr_status>
<wd:hr_status_descr>Active</wd:hr_status_descr>
<wd:empl_status>A</wd:empl_status>
<wd:empl_status_descr>Active</wd:empl_status_descr>
<wd:ben_status>A</wd:ben_status>
<wd:home_address_change_dt>2019-07-30</wd:home_address_change_dt>
<wd:location>444</wd:location>
<wd:location_descr>Ind</wd:location_descr>
</wd:Report_Entry>
</wd:Report_Data>
</mdti:File>
</mdti:EventFiles>
</mdti:Files>
</mdti:Input>
**I am using below XSLT for my data. Please let me know if I am missing something and way to do it. The only identifier in each node is the mdti:filename **
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:mdti="urn:com.workday/multiDocumentTransform/Input"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:wd="urn:com.workday/bsvc"
exclude-result-prefixes="#all">
<xsl:key name="share" match="mdti:Input/mdti:Files/mdti:EventFiles/mdti:File/wd:Report_Data/wd:Report_Entry" use="mdti:Input/mdti:Files/mdti:EventFiles/mdti:File/wd:Report_Data/wd:Report_Entry/wd:key"/>
<xsl:template match="/">
<data>
<key>1234</key>
<xsl:copy-of select="key('share', wd:key)/wd:hr_status"/>
<emp_type>Regular</emp_type>
<supervisor_lname>xyz</supervisor_lname>
<hr_status>A</hr_status>
<location>444</location>
</data>
</xsl:template>
</xsl:stylesheet>
You have several mistakes.
First, <xsl:template match="/"> puts you in the context of the root node; from this context, the expression wd:key selects nothing - so your instruction <xsl:copy-of select="key('share', wd:key)/wd:hr_status"/> does nothing.
Next, if you want your key to match nodes in the second file, you should restrict it to match only nodes in the second file. Also, the use attribute must be relative to the matched node.
Furthermore, if you want the result to be in no-namespace, you cannot copy nodes from the input - at least not in XSLT 1.0.
There is more, but these are the ones that stand out immediately.
Consider the following example:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:mdti="urn:com.workday/multiDocumentTransform/Input"
xmlns:wd="urn:com.workday/bsvc"
exclude-result-prefixes="mdti wd">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:key name="entry2" match="mdti:File[#mdti:filename='second.xml']/wd:Report_Data/wd:Report_Entry" use="wd:key"/>
<xsl:template match="/mdti:Input">
<root>
<xsl:for-each select="mdti:Files/mdti:EventFiles/mdti:File[#mdti:filename='first.xml']/wd:Report_Data/wd:Report_Entry">
<data>
<!-- data from file1 -->
<key>
<xsl:value-of select="wd:key"/>
</key>
<emplid>
<xsl:value-of select="wd:emplid"/>
</emplid>
<!-- data from file2 -->
<xsl:variable name="entry2" select="key('entry2', wd:key)" />
<supervisor_lname>
<xsl:value-of select="$entry2/wd:supervisor_lname"/>
</supervisor_lname>
<hr_status>
<xsl:value-of select="$entry2/wd:hr_status"/>
</hr_status>
</data>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Applied to your input example, this will produce:
Result
<?xml version="1.0" encoding="utf-8"?>
<root>
<data>
<key>1234</key>
<emplid>33333333</emplid>
<supervisor_lname>xyz</supervisor_lname>
<hr_status>A</hr_status>
</data>
</root>
You can add fields from both branches as required.

Merge data from two node sets using XSLT [duplicate]

I am trying to get the output based on two separate nodes in XML using key concept of XSLT
I have below XML
<?xml version="1.0" encoding="UTF-8"?>
<mdti:Input xmlns:mdti="urn:com.workday/multiDocumentTransform/Input">
<mdti:Files xmlns:mdti="urn:com.workday/multiDocumentTransform/Input">
<mdti:EventFiles>
<mdti:File mdti:filename="first.xml" mdti:contentType="text/xml">
<wd:Report_Data xmlns:wd="urn:com.workday/bsvc">
<wd:Report_Entry>
<wd:key>1234</wd:key>
<wd:comp>ABC</wd:comp>
<wd:asof>2021-03-24T04:59:32.179-07:00</wd:asof>
<wd:emplid>33333333</wd:emplid>
<wd:worker_type>EMP</wd:worker_type>
<wd:emp_type>Regular</wd:emp_type>
<wd:orig_hire_dt>2021-11-27</wd:orig_hire_dt>
<wd:rehire_dt>2019-04-01</wd:rehire_dt>
<wd:home_host_class>M</wd:home_host_class>
<wd:service_dt>2014-11-27</wd:service_dt>
</wd:Report_Entry>
</wd:Report_Data>
</mdti:File>
<mdti:File mdti:filename="second.xml" mdti:contentType="text/xml">
<wd:Report_Data xmlns:wd="urn:com.workday/bsvc">
<wd:Report_Entry>
<wd:key>1234</wd:key>
<wd:supervisor_lname>xyz</wd:supervisor_lname>
<wd:hr_status>A</wd:hr_status>
<wd:hr_status_descr>Active</wd:hr_status_descr>
<wd:empl_status>A</wd:empl_status>
<wd:empl_status_descr>Active</wd:empl_status_descr>
<wd:ben_status>A</wd:ben_status>
<wd:home_address_change_dt>2019-07-30</wd:home_address_change_dt>
<wd:location>444</wd:location>
<wd:location_descr>Ind</wd:location_descr>
</wd:Report_Entry>
</wd:Report_Data>
</mdti:File>
</mdti:EventFiles>
</mdti:Files>
</mdti:Input>
**I am using below XSLT for my data. Please let me know if I am missing something and way to do it. The only identifier in each node is the mdti:filename **
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:mdti="urn:com.workday/multiDocumentTransform/Input"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:wd="urn:com.workday/bsvc"
exclude-result-prefixes="#all">
<xsl:key name="share" match="mdti:Input/mdti:Files/mdti:EventFiles/mdti:File/wd:Report_Data/wd:Report_Entry" use="mdti:Input/mdti:Files/mdti:EventFiles/mdti:File/wd:Report_Data/wd:Report_Entry/wd:key"/>
<xsl:template match="/">
<data>
<key>1234</key>
<xsl:copy-of select="key('share', wd:key)/wd:hr_status"/>
<emp_type>Regular</emp_type>
<supervisor_lname>xyz</supervisor_lname>
<hr_status>A</hr_status>
<location>444</location>
</data>
</xsl:template>
</xsl:stylesheet>
You have several mistakes.
First, <xsl:template match="/"> puts you in the context of the root node; from this context, the expression wd:key selects nothing - so your instruction <xsl:copy-of select="key('share', wd:key)/wd:hr_status"/> does nothing.
Next, if you want your key to match nodes in the second file, you should restrict it to match only nodes in the second file. Also, the use attribute must be relative to the matched node.
Furthermore, if you want the result to be in no-namespace, you cannot copy nodes from the input - at least not in XSLT 1.0.
There is more, but these are the ones that stand out immediately.
Consider the following example:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:mdti="urn:com.workday/multiDocumentTransform/Input"
xmlns:wd="urn:com.workday/bsvc"
exclude-result-prefixes="mdti wd">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:key name="entry2" match="mdti:File[#mdti:filename='second.xml']/wd:Report_Data/wd:Report_Entry" use="wd:key"/>
<xsl:template match="/mdti:Input">
<root>
<xsl:for-each select="mdti:Files/mdti:EventFiles/mdti:File[#mdti:filename='first.xml']/wd:Report_Data/wd:Report_Entry">
<data>
<!-- data from file1 -->
<key>
<xsl:value-of select="wd:key"/>
</key>
<emplid>
<xsl:value-of select="wd:emplid"/>
</emplid>
<!-- data from file2 -->
<xsl:variable name="entry2" select="key('entry2', wd:key)" />
<supervisor_lname>
<xsl:value-of select="$entry2/wd:supervisor_lname"/>
</supervisor_lname>
<hr_status>
<xsl:value-of select="$entry2/wd:hr_status"/>
</hr_status>
</data>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Applied to your input example, this will produce:
Result
<?xml version="1.0" encoding="utf-8"?>
<root>
<data>
<key>1234</key>
<emplid>33333333</emplid>
<supervisor_lname>xyz</supervisor_lname>
<hr_status>A</hr_status>
</data>
</root>
You can add fields from both branches as required.

Merging two different parent nodes content based on the same child value (ID) - XSL Transform

I couldn't find this specific scenario (guess I can`t describe it right in english).
The input XML looks something like this (multiple items inside "listOne" and "listTwo" of course:
<root>
<listOne>
<listOneItem>
<ID>1</ID> // Always Unique
<SKU>ABC</SKU>
</listOneItem>
</listOne>
<listTwo>
<listTwoItem>
<ID>1</ID> / Identical to node in listOneItem, but unique (no scenario of a third element with such ID)
<STOCK>10</STOCK>
</listTwoItem>
</listTwo>
</root>
The desired output should merge the "listOne" and "listTwo" items based on the same child "ID" nodes value:
<root>
<finalItemsList>
<item>
<ID>1</ID>
<SKU>ABC</SKU>
<STOCK>10</STOCK>
</item>
</finalItemsList>
</root>
XSLT has a built-in key mechanism to resolve cross-references:
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:key name="list2" match="listTwoItem" use="ID" />
<xsl:template match="/root">
<root>
<finalItemsList>
<xsl:for-each select="listOne/listOneItem">
<item>
<xsl:copy-of select="*"/>
<xsl:copy-of select="key('list2', ID)/STOCK"/>
</item>
</xsl:for-each>
</finalItemsList>
</root>
</xsl:template>
</xsl:stylesheet>

Group XML using xslt based on Header or Document Entry

How to group the XML using xlst for the below xml code.
Following are the input XML: I'm using this input xml in order to import into an ERP system.
<row>
<Ref>1</Ref>
<Code>IT001</Code>
<Qty>11</Qty>
</row>
<row>
<Ref>1</Ref>
<Code>IT002</Code>
<Qty>21</Qty>
</row>
<row>
<Ref>2</Ref>
<Code>IT002</Code>
<Qty>12</Qty>
</row>
following are the Output or expected XML: ERP system generally accepts one line per document and it's siblings of document lines. Thus the following desired output is required.
<Document>
<Ref>1</Ref><Lines>
<Item>
<Code>IT001</Code>
<Qty>11</Qty>
</Item>
<Item>
<Code>IT002</Code>
<Qty>21</Qty>
</Item>
</Lines>
</Document>
<Document>
<OrderRef>2</OrderRef>
<Lines>
<Item>
<Code>IT002</Code>
<Qty>12</Qty>
</Item>
</Lines>
</Document>
Let's start from a correction of your source XML:
There must be only one root element (I called it Root)
and inside it there can be multiple (e.g. Document) elements.
The template performing the transformation should match the
Root element.
As I see from your expected output, you want to group Document
elements on DocumentRef, so in the script below there is
corresponding xsl:for-each-group instruction.
For each such group there should be Document output element
and inside it Ref element with the value of the current
grouping key.
Then there should be a Lines element and inside it, for each
member of the current group, there should be Item element
and inside it 2 child elements with required values from the
source element.
So the whole script can look like below:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="Root">
<xsl:copy>
<xsl:for-each-group select="Document" group-by="DocumentRef">
<Document>
<Ref><xsl:value-of select="current-grouping-key()"/></Ref>
<Lines>
<xsl:for-each select="current-group()">
<Item>
<ItemCode><xsl:value-of select="DocumentLines/ItemCode"/></ItemCode>
<Qty><xsl:value-of select="DocumentLines/ItemQty"/></Qty>
</Item>
</xsl:for-each>
</Lines>
</Document>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:transform>
For a working example, including corrected input, see http://xsltransform.net/eieE3PX
XSLT 1.0 version
In XSLT 1.0 it is also possible, using Muenchian Grouping:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:key name="groups" match="row" use="OrderRef"/>
<xsl:template match="Payload">
<xsl:copy>
<xsl:apply-templates select="row[generate-id() = generate-id(
key('groups', OrderRef)[1])]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="row">
<Document>
<Ref><xsl:value-of select="OrderRef"/></Ref>
<CardCode><xsl:value-of select="CustomerCode"/></CardCode>
<Lines>
<xsl:for-each select="key('groups', OrderRef)">
<Item>
<ItemCode><xsl:value-of select="ItemCode"/></ItemCode>
<Qty><xsl:value-of select="Quantity"/></Qty>
</Item>
</xsl:for-each>
</Lines>
</Document>
</xsl:template>
</xsl:transform>
The initial step is to create an xsl:key. Each key must have a name to
refer to it later. match defines which elements to include in this key
and use defines the grouping key.
Then look at:
<xsl:apply-templates select="row[generate-id() = generate-id(
key('groups', OrderRef)[1])]"/>
It "calls an action" (in this case xsl:apply-templates) on
the first object in each group.
The rest of code from my initial solution has been moved to
a template matching row.
The initial part of it performs actions for the current group
(generate output Document, Ref, CardCode and Lines
elements).
The rest (xsl:for-each) performs actions for individual
members of the current group, generating Item, ItemCode
and Qty elements.
I updated your solution in xsltransform, so you can view
it on http://xsltransform.net/jxWYjW2/2
Note that I changed the XSLT engine to Saxon 6.5.5. You can also
switch it to Xalan, although then you loose indentation.
If this approach is new to you, maybe you should read a little about
generate-id and Muenchian Grouping itself. Even StackOverflow contains
a lot of posts about these issues.

How to select the value from an attribute that has a colon in xslt?

I am working with xslt to handle the results that are returned from a web service. I first need to determine which web service the results are for. I know that the tag platformCore:record has the attribute "xsi:type="listRel:Contact or "xsi:type="listEmp:Employee". I am trying to select the value that the attribute is storing, but the colon seems to be causing some issues when I attempt to select the value.
Here is what I tried, but fails to work.
<?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" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<xsl:variable name="Type"><xsl:value-of select="//*[local-name()='searchResponse']//*[local-name()='searchResult']//*[local-name()='recordList']//*[local-name()='record']#xsi:type"/></xsl:variable>
<root>
<test><xsl:value-of select="$Type"/></test>
</root>
</xsl:template>
</xsl:stylesheet>
Here is a simple sample
<?xml version="1.0" encoding="UTF-8"?>
<searchResponse:searchResponse xmlns="urn:messages_2012_2.platform.webservices.itsthesuite.com"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:searchResponse="urn:messages_2012_2.platform.webservices.itsthesuite.com"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<platformCore:searchResult xmlns:platformCore="urn:core_2012_2.platform.webservices.itsthesuite.com">
<platformCore:status isSuccess="true"/>
<platformCore:totalRecords>1</platformCore:totalRecords>
<platformCore:recordList>
<platformCore:record internalId="154098" xsi:type="listRel:Contact" xmlns:listRel="urn:relationships_2012_2.lists.webservices.itsthesuite.com">
<listRel:entityId>John Smith</listRel:entityId>
<listRel:firstName>John</listRel:firstName>
<listRel:lastName>Smith</listRel:lastName>
<listRel:phone>(777) 777-7777</listRel:phone>
<listRel:email>john.smith#yormoms.com</listRel:email>
</platformCore:record>
</platformCore:recordList>
</platformCore:searchResult>
</searchResponse:searchResponse>
I need the solution to work for this sample as well.
Employee Sample
<?xml version="1.0" encoding="UTF-8"?>
<searchResponse xmlns="urn:messages_2012_2.platform.webservices.netsuite.com" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:searchResponse="urn:messages_2012_2.platform.webservices.netsuite.com" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<platformCore:searchResult xmlns:platformCore="urn:core_2012_2.platform.webservices.netsuite.com" >
<platformCore:status isSuccess="true"/>
<platformCore:totalRecords>1</platformCore:totalRecords>
<platformCore:recordList>
<platformCore:record internalId="158778" xsi:type="listEmp:Employee" xmlns:listEmp="urn:employees_2012_2.lists.webservices.netsuite.com">
<listEmp:entityId>331sfds Dipo Chaponda</listEmp:entityId>
<listEmp:salutation>Mr.</listEmp:salutation>
<listEmp:firstName>Dipo</listEmp:firstName>
<listEmp:lastName>Chaponda</listEmp:lastName>
<listEmp:email>dchapond#youmm.com</listEmp:email>
</platformCore:record>
</platformCore:recordList>
</platformCore:searchResult>
</searchResponse>
You can select an attribute using local name similarly to what you are already doing, but by prefacing the * with an #:
#*[local-name() = 'type']
However, littering your XPaths with local-name() = and double slashes is not a good practice. You should use namespaces properly, and use precise paths when they are known, although it seems that is not an option for the elements in your case because they are using different namespaces in the two examples. This should work:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
exclude-result-prefixes="sr pc xsi"
>
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<xsl:variable name="Type">
<xsl:value-of select="*[local-name() = 'searchResponse']/
*[local-name() = 'searchResult']/
*[local-name() = 'recordList']/
*[local-name() = 'record']/
#xsi:type"/>
</xsl:variable>
<root>
<test>
<xsl:value-of select="$Type"/>
</test>
</root>
</xsl:template>
</xsl:stylesheet>
When run on your sample input, this produces the expected result:
<root>
<test>listRel:Contact</test>
</root>