Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
engswee
Active Contributor
4,659

Introduction


Recently, I worked on a requirement to send data from S/4HANA to a third party system in a specific XML format. The target XML format does not use namespaces in its schema. Since the data is transferred out of S/4HANA via SOAP web service to CPI, a custom ABAP service consumer is generated from a custom WSDL, and that requires a namespace.

In CPI, I would have to perform some manipulation to the source payload, performing an identity transformation and additionally removing any namespace/prefix in the payload.

In PI, this would have been quite easily achieved by adding XMLAnonymizerBean to a channel's module processor. However, there is no equivalent out-of-the-box functionality in CPI.

Despite being a self-professed XSLT noob, XSLT seems like an obvious choice to handle such transformation.

So after spending some time scouring the internet forums for solutions, and clumsily testing the transformation using Notepad++'s XML Tools plugin (I don't have Altova or Oxygen XML!), I managed to achieve the requirement using the following XSLT code - hurray!
<?xml version="1.0"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" method="xml" encoding="utf-8"/>
<!-- template to copy elements -->
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="@* | node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>

 

Note: This is a simplified version that does not cater for comments or attributes in the XML payload.

 

What about Groovy?


You might be aware that I am more comfortable working with Groovy - so I gave myself a challenge to see if I could come up with an alternative solution based on Groovy. Ideally, it should solve not just my particular issue, which only removes namespace from the root node, but throughout the entire XML payload as well.

To achieve the requirement, it requires some form of recursive processing similar to XSLT template matching.

 

Core concepts


Let us look at some of the core concepts required to achieve the requirement in Groovy.

Parsing and updating XML payload


As I have mentioned before, working with XML is easy in Groovy. In particular, if we want to parse and update the XML payload at the same time, groovy.util.XmlParser is a good choice. More details can be found in Scenario 2 of my blog post Parse XML easily with Groovy scripts. The following lines show how to instantiate a parser and access the root node of the parsed XML payload.
XmlParser parser = new XmlParser()
Node root = parser.parse(message.getBody(Reader))

 

Determining nodes with namespace prefix


Once we can access the nodes of the XML payload, we will need to determine which nodes contain namespace prefix. This is achieved by checking if the object returned by the name() method of groovy.util.Node is an instance of groovy.xml.QName.
def nodeName = node.name()
if (nodeName instanceof QName)
... do something ...

 

Recursive processing through the XML hierarchy


Lastly, the most critical portion is to process through all the nodes the XML hierarchy. This is achieved with a recursive algorithm.

In Groovy, such recursive algorithms can be achieved using trampolines (Groovy has the grooviest names, doesn't it!) Trampoline overcomes the limitation of the stack height in case a method recursively calls itself too deep. The calls are made serially therefore avoiding StackOverflowException.

Following is an example of a recursive function defined as a Groovy closure, followed by an invocation of it.
// Define the recursive function as a closure
def recursiveFunction
recursiveFunction = {
... do something ...
recursiveFunction.trampoline()
... do something ...
}.trampoline()

// call the closure
recursiveFunction()

 

Putting it together


With all the components in place, let's put them together to see how it would look like in a CPI Groovy script. The steps in the script would be as follows:-

  • Parse XML payload

  • Process through XML payload starting from root node

    • Recreate each node as a node without namespace prefix

    • Repeat steps recursively for children of current node



  • Serialize modified XML as output


import com.sap.gateway.ip.core.customdev.util.Message
import groovy.xml.QName
import groovy.xml.XmlUtil

Message processData(Message message) {
XmlParser parser = new XmlParser()

// Recursive closure to replace nodes with prefix
def replaceNodePrefix
replaceNodePrefix = { Node node ->
def nodeName = node.name()
// Replace current node
def localNodeName = (nodeName instanceof QName) ? nodeName.getLocalPart() : nodeName
Node outputNode = parser.createNode(null, localNodeName, null)
// Iterate and process the children of the node
node.children().each { child ->
switch (child){
case Node:
outputNode.append(replaceNodePrefix.trampoline(child).call())
break
case String:
outputNode.setValue(child)
break
}
}
return outputNode
}.trampoline()

Node root = parser.parse(message.getBody(Reader))
message.setBody(XmlUtil.serialize(replaceNodePrefix(root)))
return message
}

 

Conclusion


In the software world, there are more than one approach to fulfilling any particular requirement. Although the outcome of the Groovy script is not as compact as its XSLT counterpart, I was glad I spent the time on the challenge. I definitely learnt one or two things along the way, and hopefully it helped to hone my Groovy skills for further challenges to come 🙂

 

 
4 Comments
Bais
Participant
Very good thread, we are using xslt way.

 
gagandeepbatras
Product and Topic Expert
Product and Topic Expert
Nice...Thanks For Sharing!!
peakMeissner
Active Participant
THX for sharing your solution. I like the way how you've solved this issue using a Groovy script.
sunil_singh13
Active Contributor
0 Kudos
Assuming the namespace will remain same from message to message, using kind of "Replace all namespace with blank" was an option?
Labels in this area