XSLT - transform XML in REST response result - xslt

I am currently trying to use XSLT to convert some XML pseudo-code to a rest response result
For example, with the following xml:
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:functions="http://foo.com/api/rest">
<item bar="foo">some item</item>
<functions:fetch a="3" b="foo" />
</root>
And if the result of http://foo.com/services/rest/fetch/a/3/b/foo is:
<?xml version="1.0" encoding="UTF-8"?>
<rsp stat="ok">
<book>
<title>Ethics</title>
</book>
<book>
<title>Beyond Good and Evil</title>
</book>
</rsp>
The result of the XSLT parsing I am trying to obtain is:
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:functions="http://foo.com/api/rest">
<item bar="foo">some item</item>
<book>
<title>Ethics</title>
</book>
<book>
<title>Beyond Good and Evil</title>
</book>
</root>
I know I can use <xsl:value-of select="document('http://foo.com/services/rest/fetch/a/3/b/foo')"/> to make the request, but I am stuck on how to generate and process it in the same XSLT.
Any help will be greatly appreciated, Thanks in advance!

Here is one possible pure XSLT 1.0 solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:functions="http://foo.com/api/rest"
exclude-result-prefixes="functions">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vDynPart">
<xsl:for-each select="/*/functions:*/#*">
<xsl:value-of select="concat('/', name(), '/', .)"/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="vRes" select=
"document(concat('http://foo.com/services/rest/fetch',
$vDynPart)
)"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
<xsl:apply-templates select="$vRes/*/node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="functions:*"/>
</xsl:stylesheet>

Thanks to the answer from Dimitre, I found a solution!
(I make a new reply as I tuned Dimitre solution a little to work with any function name and multiple functions in the same XML)
Here the xslt:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:functions="http://foo.com/api/rest"
exclude-result-prefixes="functions">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="functions:*">
<xsl:variable name="request_function">
<xsl:value-of select="local-name(.)"/>
</xsl:variable>
<xsl:variable name="request_parameters">
<xsl:for-each select="#*">
<xsl:value-of select="concat('/', name(), '/', .)"/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="rest_response" select="document(concat('http://foo.com/services/rest/', $request_function, $request_parameters) )"/>
<xsl:apply-templates select="$rest_response/*/node()"/>
</xsl:template>
</xsl:stylesheet>
Thanks!

Related

Sending value with different tag XSLT

is there any chance can sending value to output other node response, with different path from there input request. i have input and response tag like this
Input Request:
<Root>
<Items>
<Item1>Rambutan12</Item1>
</Items>
</Root>
and i try with this code for add new node with additional info at Response
i try like this
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output method="text"/>
<xsl:template match="/">
<Root>
<ItemsResponse>
<xsl:call-templates select="items">
<xsl:with-param name="item1s" select="//Root/Items/Item1"/>
</xsl:call-templates>
</ItemsResponse>
</Root>
</xst:template>
<xsl:template name="items">
<xsl:param name="item1s"/>
<xsl:variable name="information">
<xsl:choose>
<xsl:when test="fn:matches($item1s, '^[a-zA-Z]*$') ">
<Item1><xsl:value-of select="$item1s"/></Item1>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<Item1><xsl:value-of select="$item1s"/></Item1>
<xsl:apply-templates select="Item1Information"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="Item1Information">
<xsl:copy>
<Item1Information>Wrong Failed Format Input</Item1Information>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Expected Result was like this
<Root>
<ItemsResponse>
<Item1>Rambutan12</Item1>
<Item1Information>Input Failed Format</ItemInformation>
</ItemsResponse>
</Root>
Any tips like for this case, thanks
It is very difficult to understand what your question is.
On the off-chance that I am guessing correctly, and that you want to add an error warning when Item1 contains any characters other than the 26 letters of the English alphabet, then you could do simply:
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:template match="/Root">
<Root>
<ItemsResponse>
<xsl:copy-of select="Items/Item1"/>
<xsl:if test="translate(Items/Item1, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', '')">
<Item1Information>Input Failed Format</Item1Information>
</xsl:if>
</ItemsResponse>
</Root>
</xsl:template>
</xsl:stylesheet>

Create copy of record based on node that occurs multiple times

I have a requirement to create a copy of an XML file based on a field that occurs multiple times.
Input XML: There are two EmpEmployment nodes in the XML. I need to separate them and copy the rest of the nodes so that I have two PerPerson records with one EmpEmployment each.
<PerPerson>
<PerPerson>
<personalInfoNav>
<PerPersonal/>
</personalInfoNav>
<nationalIdNav>
<PerNationalId/>
</nationalIdNav>
<personIdExternal>AA</personIdExternal>
<personEmpTerminationInfoNav>
<PersonEmpTerminationInfo/>
</personEmpTerminationInfoNav>
<phoneNav>
<PerPhone/>
</phoneNav>
<employmentNav>
<EmpEmployment>
<compInfoNav>
<EmpCompensation/>
</compInfoNav>
<jobInfoNav>
<EmpJob/>
</jobInfoNav>
</EmpEmployment>
<EmpEmployment>
<compInfoNav>
<EmpCompensation/>
</compInfoNav>
<jobInfoNav>
<EmpJob/>
</jobInfoNav>
</EmpEmployment>
</employmentNav>
<homeAddressNavDEFLT>
<PerAddressDEFLT/>
</homeAddressNavDEFLT>
</PerPerson>
</PerPerson>
I am trying to do this using XSLT 1.0
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:hci="http://sap.com/it/" exclude-result-prefixes="hci">
<xsl:strip-space elements="*"/>
<xsl:output encoding="utf-8" indent="yes" method="xml"/>
<xsl:template match="/">
<PerPerson>
<xsl:for-each select="PerPerson/PerPerson">
<xsl:variable name="var_person" select="./*[not(name()='EmpEmployment')]"/>
<xsl:for-each select="employmentNav/EmpEmployment">
<xsl:variable name="var_empInfo" select="."/>
<PerPerson>
<xsl:copy-of select="$var_person"/>
<xsl:copy-of select="$var_empInfo"/>
</PerPerson>
</xsl:for-each>
</xsl:for-each>
</PerPerson>
</xsl:template>
</xsl:stylesheet>
Its not working as expected. I am unable to create the desired output below:
<PerPerson>
<PerPerson>
<personalInfoNav>
<PerPersonal/>
</personalInfoNav>
<nationalIdNav>
<PerNationalId/>
</nationalIdNav>
<personIdExternal>AA</personIdExternal>
<personEmpTerminationInfoNav>
<PersonEmpTerminationInfo/>
</personEmpTerminationInfoNav>
<phoneNav>
<PerPhone/>
</phoneNav>
<employmentNav>
<EmpEmployment>
<compInfoNav>
<EmpCompensation/>
</compInfoNav>
<jobInfoNav>
<EmpJob/>
</jobInfoNav>
</EmpEmployment>
</employmentNav>
<homeAddressNavDEFLT>
<PerAddressDEFLT/>
</homeAddressNavDEFLT>
</PerPerson>
<PerPerson>
<personalInfoNav>
<PerPersonal/>
</personalInfoNav>
<nationalIdNav>
<PerNationalId/>
</nationalIdNav>
<personIdExternal>AA</personIdExternal>
<personEmpTerminationInfoNav>
<PersonEmpTerminationInfo/>
</personEmpTerminationInfoNav>
<phoneNav>
<PerPhone/>
</phoneNav>
<employmentNav>
<EmpEmployment>
<compInfoNav>
<EmpCompensation/>
</compInfoNav>
<jobInfoNav>
<EmpJob/>
</jobInfoNav>
</EmpEmployment>
</employmentNav>
<homeAddressNavDEFLT>
<PerAddressDEFLT/>
</homeAddressNavDEFLT>
</PerPerson>
</PerPerson>
You could do:
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:template match="/PerPerson">
<PerPerson>
<xsl:for-each select="PerPerson/employmentNav/EmpEmployment">
<PerPerson>
<xsl:copy-of select="../preceding-sibling::*"/>
<employmentNav>
<xsl:copy-of select="."/>
</employmentNav>
<xsl:copy-of select="../following-sibling::*"/>
</PerPerson>
</xsl:for-each>
</PerPerson>
</xsl:template>
</xsl:stylesheet>
In XSLT 2.0 you can use tunnel-parameters.
And then just use the template macht engine like below
<?xml version='1.0' encoding='UTF-8'?>
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all">
<xsl:template match="#*|node()" mode="#all">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="#current"/>
</xsl:copy>
</xsl:template>
<xsl:template match="PerPerson/PerPerson">
<xsl:variable name="person" select="."/>
<xsl:for-each select="employmentNav/EmpEmployment">
<xsl:apply-templates select="$person" mode="denormalize">
<xsl:with-param name="position" as="xs:integer" select="position()" tunnel="yes"/>
</xsl:apply-templates>
</xsl:for-each>
</xsl:template>
<xsl:template match="employmentNav" mode="denormalize">
<xsl:param name="position" as="xs:integer" tunnel="yes"/>
<xsl:copy>
<xsl:apply-templates select="EmpEmployment[$position]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Call templates with same match string in different context

I want to transform a source xml into a target xml where certain matches from the source xml are included in different context in the target xml. For example I have a source xml like:
<shiporder>
<shipto>orderperson1</shipto>
<shipto>orderperson1</shipto>
<city>London</city>
</shiporder>
On this source xml I apply the following stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:call-template name="root" />
</xsl:template>
<xsl:template name="root">
<root>
<xsl:apply-templates select="/shiporder"/>
<xsl:call-template name="Customer"/>
</root>
</xsl:template>
<xsl:template name="Customer">
<Customer>
<!--<xsl:apply-templates select="/shiporder"/>-->
</Customer>
</xsl:template>
<xsl:template match="/shiporder">
<xsl:apply-templates select="shipto"/>
</xsl:template>
<xsl:template match="/shiporder/shipto">
<Address>
<xsl:apply-templates select="text()"/>
</Address>
</xsl:template>
</xsl:stylesheet>
In the template of name Customer I like to apply a template like:
<xsl:template match="/shiporder">
<xsl:apply-templates select="city"/>
</xsl:template>
<xsl:template match="/shiporder/city">
<City>
<xsl:apply-templates select="text()"/>
</City>
</xsl:template>
But I already defined a template with match /shiporder. So I don't know how to design a stylesheet where both templates with the same match exists in their own context?
If you use mode, like #michael.hor257k suggested you can differentiate between two or more templates that match on the same element but with different results.
In your case that could end up looking like this:
<?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" encoding="UTF-8"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:call-template name="root" />
</xsl:template>
<xsl:template name="root">
<root>
<xsl:apply-templates select="/shiporder" mode="root"/>
<xsl:call-template name="Customer"/>
</root>
</xsl:template>
<xsl:template name="Customer">
<Customer>
<xsl:apply-templates select="/shiporder" mode="customer"/>
</Customer>
</xsl:template>
<xsl:template match="/shiporder" mode="root">
<xsl:apply-templates select="shipto"/>
</xsl:template>
<xsl:template match="/shiporder" mode="customer">
<xsl:apply-templates select="city"/>
</xsl:template>
<xsl:template match="shipto">
<Address>
<xsl:apply-templates select="text()"/>
</Address>
</xsl:template>
<xsl:template match="city">
<City>
<xsl:apply-templates select="text()"/>
</City>
</xsl:template>
</xsl:stylesheet>
Obviously all credits here go to Michael for pointing this out first.

xslt extract and sort leaf nodes by name - unexpected result

I wanted to extract leaf nodes and have them sorted.
My XSL gives unexpected results. How can I solve this?
Input
<root>
<b>
<b33 zzz="2" fff="3"></b33>
<b11></b11>
<b22></b22>
</b>
<a>
<a27></a27>
<a65 fff="0" eee="2" zzz="10"></a65>
<a11></a11>
</a>
</root>
Xsl
<?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" omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<root>
<xsl:call-template name="leafnodes"/>
</root>
</xsl:template>
<xsl:template match="*[not(*)]|#*" name="leafnodes">
<xsl:copy>
<xsl:apply-templates select="node()">
<xsl:sort select="name()"/>
</xsl:apply-templates>
<xsl:apply-templates select="#*">
<xsl:sort select="name()"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output (I would expected it to be sorted, it is not)
<root>
<b33 fff="3" zzz="2" />
<b11 />
<b22 />
<a27 />
<a65 eee="2" fff="0" zzz="10" />
<a11 />
</root>
I would expect the nodes in the order a11, a27, a65, b11, b22, b33.
If I leave out the '[not(*)]', the xsl takes all nodes and sorts them properly.
How can this be solved?
To output all element which have no child sorted by name and the attributes also sorted by name. Try this;
<?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" omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<root>
<xsl:apply-templates select="//*[not(*)]">
<xsl:sort select="name()"/>
</xsl:apply-templates>
</root>
</xsl:template>
<xsl:template match="*|#*">
<xsl:copy>
<xsl:apply-templates select="#*" >
<xsl:sort select="name()"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Which will generate following output:
<root>
<a11/>
<a27/>
<a65 eee="2" fff="0" zzz="10"/>
<b11/>
<b22/>
<b33 fff="3" zzz="2"/>
</root>

How to remove the rootnodes using XSLT?

Input file:
<?xml version="1.0" encoding="UTF-8"?>
<ns0:root xmlns:ns0="http://xyz.com/separate">
<ns0:root1>
<ns3:Detail xmlns:ns3="http://POProject/Details">
<DetailLines>
<ItemID>
<ItemDescription/>
</DetailLines>
</ns3:Detail>
</ns0:root1>
</ns0:root>
Output file:
<?xml version="1.0" encoding="UTF-8"?>
<ns0:Detail xmlns:ns0="http://POProject/Details">
<DetailLines>
<ItemID>
<ItemDescription/>
</DetailLines>
</ns0:Detail>
Question: I have to remove the root1 and root nodes and need to do small
changes in Detail node. How to write a xslt code to achieve this?
This...
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://xyz.com/separate"
xmlns:ns3="http://POProject/Details">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="*/*/ns3:Detail" />
</xsl:template>
<xsl:template match="ns3:Detail">
<xsl:apply-templates select="." mode="copy-sans-namespace" />
</xsl:template>
<xsl:template match="*" mode="copy-sans-namespace">
<xsl:element name="{name()}" namespace="{namespace-uri()}">
<xsl:copy-of select="#*"/>
<xsl:apply-templates mode="copy-sans-namespace" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
...will yield this...
<?xml version="1.0" encoding="utf-8"?>
<ns3:Detail xmlns:ns3="http://POProject/Details">
<DetailLines>
<ItemID />
<ItemDescription />
</DetailLines>
</ns3:Detail>
I'm not sure it is possible to control the prefix. The XDM data model does not consider it to be significant information.
UDPATE
To get the prefix rename, I thought you would have to go to an XML 1.1 supporting XSLT processor (allowing prefix undefine), but I found a way to do it with XML 1.0 . Try this ...
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://xyz.com/separate">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/" xmlns:ns3="http://POProject/Details">
<xsl:apply-templates select="*/*/ns3:Detail" />
</xsl:template>
<xsl:template match="ns0:Detail" xmlns:ns0="http://POProject/Details">
<ns0:Detail xmlns:ns0="http://POProject/Details">
<xsl:apply-templates select="*" mode="copy-sans-namespace" />
</ns0:Detail>
</xsl:template>
<xsl:template match="*" mode="copy-sans-namespace">
<xsl:element name="{name()}" namespace="{namespace-uri()}">
<xsl:copy-of select="#*"/>
<xsl:apply-templates mode="copy-sans-namespace" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>