Skip to Content
Technical Articles

SAP CPI – Parsing Mechanisms – GroovyScript

Hello Folks,

Returning to the cloud I would like to show you one the similar blog  that I developed 7 years ago of DOM Parsing via java mapping – SAP PI/PO:

Easy way to learn – Java Mapping using DOM – SAP PI 7.1+

The proposal of this blog it’s only to show the PARSING Mechanisms available in Groovy Script to SAP CPI, not the integration itself.


Differences of – XmlParser vs XmlSlurper


The parses of XmlParser and XmlSlurper  (similar to DOM or SAX in java) are pretty similar. We can more or less achieve the same results with both. However, some differences between them can tilt the scales towards one or the other.

First of all, XmlParser always parses the whole document into the DOM-ish structure. Because of that, we can simultaneously read from and write into it. We can’t do the same with XmlSlurper as it evaluates paths more lazily. As a result, XmlParser can consume more memory.

On the other hand, XmlSlurper uses more straightforward definitions, making it simpler to work with. We also need to remember that any structural changes made to XML using XmlSlurper require reinitialization, which can have an unacceptable performance hit in case of making many changes one after another.

Let’s see the similarities between XmlParser and XmlSlurper  first:

  • Both can update/transform the XML

But they have key differences:

  • XmlSlurper:
    1. Evaluates the structure lazily. So if you update the XML you’ll have to evaluate the whole tree again.
    2. Returns GPathResult instances when parsing XML
    3. Consumes Lesser Memory Than XML Parser.
  • XmlParser:
    1. Returns Almost All Nodes objects when parsing XM
    2. Is Faster Then XML Slurper.
    3. Can Parse And Update The XML Simultaneously.

The decision of which tool to use should be made with care and depends entirely on the use case.

  • If you want to transform an existing document to another then XmlSlurper  will be the choice
  • If you want to update and read at the same time then XmlParser is the choice.
  • If you just have to read a few nodes XmlSlurper should be your choice, since it will not have to create a complete structure in memory”
  • In general, both classes perform similar ways. Even the way of using GPath expressions with them are the same (both use breadthFirst() and depthFirst() expressions or (XmlSlurper .‘**’.find { it.name() == ‘Node’ })

Gpath:

GPath is a path expression language integrated into Groovy which allows parts of nested structured data to be identified. In this sense, it has similar aims and scope as XPath does for XML. The two main places where you use GPath expressions is when dealing with nested POJOs or when dealing with XML

 


MarkupBuilder – StreamingMarkupBuilder


Apart from reading and manipulating the XML tree, Groovy also provides tooling to create an XML document from scratch. Let’s now create a document consisting of the first two articles from our first example using groovy.xml.MarkupBuilder:


DOMCategory


There is another way of parsing XML documents with Groovy with the use of groovy.xml.dom.DOMCategory which is a category class which adds GPath style operations to Java’s DOM classes.

Java has in-built support for DOM processing of XML using classes representing the various parts of XML documents, e.g. Document, Element, NodeList, Attr, etc. For more information about these classes, refer to the respective JavaDocs.


Decision


I will present you only the – XmlSlurper and StreamingMarkupBuilder


First Steps – Install Groovy Plugin in NWDS:


I will explain you in details how to install the groovy script plugging in the SAP Netweaver Developer Studio to be easier to build and test, instead of directly in the SAP Cloud Platform Integration – CPI.

  • Download the SAP Netweaver Developer Studio from SAP Marketplace 7.5 sp16.

Version Oxygen:



  • Github with all plugins and versions of the groovy script:

Many releases can be found in Github – GroovyScript, in particular, you may want to try the “snapshot builds” instead of the release builds.

If you are using the Netweaver Developer Studio you must set up for Oxygen:

Oxygen – SAP Netweaver Developer Studio 7.5 sp16

  • Go to Help > Install New Software




  • Install New Software > Add



  • Add Repository:

Name: Groovy Development Eclipse (Suggestion, you can add anything)

Location: http://dist.springsource.org/snapshot/GRECLIPSE/e4.7 – Select all packages


   


  • Next Steps:

Click the Next button twice

Select “I accept the terms and conditions”

Click Finish button

Once updates have finished you must restart the Netweaver Developer Studio, click Restart Now.

You can now do File > New > Project > Groovy > Groovy Project


Check if the installation is with success:

  • Or here – Help > Eclipse Marketplace > Installed

     


  • Or here – Help > About Eclipse

   


Creating the groovy project in SAP Netweaver Developer Studio.

  • New Project > Groovy Others > Groovy

         


Now you can developer in your local SAP Netweaver developer studio and play around read and writing local files before upload in the SAP CPI.


Also, if you are in the hurry can use this very nice web console for Groovy if you don’t want to keep your projects in local machine – online testing tool:


Integration Diagram Perspective:


Proof of concept with Postman simulating the backend system via HTTPS call, SAP CPI platform receiving the message steps below:

  • HTTPS Call – POST
  • Groovy script to parse the XML using XmlSlurper(),StreamingMarkupBuilder() and Logging()
  • XSLT – Just to preserve the format of <![CDATA<Business Data>]> – Only for XmlSlurper and XmlParser
  • Groovy Script to log the result
  • End process.


Integration Flow – CPI with XmlSluper and StreamingMarkupBuilder:


Integration Flow Description:

  • Sender
  • Groovy XmlSlurper Parsing
  • XSLT Mapping
  • End Message


Integration Flow Description:

  • Sender
  • StreamingMarkupBuilder
  • End Message


  • Sender:

Postman tool under the SAP CPI endpoint with content-type application/xml, after you deploy the scenario with sender HTTP you copy the endpoint.



  • Groovy XmlSlurper Parsing full code and comments:


/**
* Name: Proof of Concept using XmlSlurper() to parsing the InputXML to OutputXML - Groovy Script Parsing
* Object: Comparion of parsing XML using JAVA DOM Parsing and Groovy Script XmlSlurper()
* Creator: Ricardo Viana
* Date: 13/02/2020
**/
import com.sap.gateway.ip.core.customdev.util.Message;
import groovy.util.XmlSlurper;
import groovy.xml.XmlUtil;
import groovy.xml.MarkupBuilder;
import groovy.xml.StreamingMarkupBuilder;
def Message processData(Message message) {
       /**
	   * Variable def:body get message as String
	   **/
       def body = message.getBody(String.class);
       /**
	   * Invoke XmlSlurper() as string to variable def:completeXml
	   **/
       def completeXml = new XmlSlurper().parseText(body.toString());
       /**
	   * Reading whole XML file into variable cdataAsStr completeXml.'**'.find { it.name() == 'XMLNFe' }.toString();
	   * Reading whole XML Java Dom Parsing - Sample: NodeList x = doc.getDocumentElement().getElementsByTagName("*");
	   * Using substring with indexOf
	   **/
       def cdataAsStr = completeXml.'**'.find { it.name() == 'XMLNFe' }.toString();
       /**
	   * Extracting the values from <XMLNFe><![CDATA[<XML>]]></XMLNFe>
	   * Using substring with indexOf
	   **/
        String nNF = cdataAsStr.substring(cdataAsStr.indexOf("<nNF>")+5, cdataAsStr.indexOf("</nNF>"));
        String verProc = cdataAsStr.substring(cdataAsStr.indexOf("<verProc>")+9, cdataAsStr.indexOf("</verProc>"));
        String serie = cdataAsStr.substring(cdataAsStr.indexOf("<serie>")+8, cdataAsStr.indexOf("</serie>"));
        String cnfe = cdataAsStr.substring(cdataAsStr.indexOf("<chNFe>")+7, cdataAsStr.indexOf("</chNFe>"));
        String versaoNFe = cdataAsStr.substring(cdataAsStr.indexOf("<verAplic>")+10, cdataAsStr.indexOf("</verAplic>"));
        String tpAmb = cdataAsStr.substring(cdataAsStr.indexOf("<tpAmb>") + 7,cdataAsStr.indexOf("</tpAmb>"));
        String chNFe = cdataAsStr.substring(cdataAsStr.indexOf("<chNFe>") + 7,cdataAsStr.indexOf("</chNFe>"));
        String dhRecbto = cdataAsStr.substring(cdataAsStr.indexOf("<dhRecbto>")+10,cdataAsStr.indexOf("</dhRecbto>"));
        String nProt = cdataAsStr.substring(cdataAsStr.indexOf("<nProt>") + 7,cdataAsStr.indexOf("</nProt>"));
        String cStat = cdataAsStr.substring(cdataAsStr.indexOf("<cStat>") + 7,cdataAsStr.indexOf("</cStat>"));
        String xMotivo = cdataAsStr.substring(cdataAsStr.indexOf("<xMotivo>") + 9,cdataAsStr.indexOf("</xMotivo>"));
        String digestVal = cdataAsStr.substring(cdataAsStr.indexOf("<digVal>") + 8,cdataAsStr.indexOf("</digVal>"));
	   /**
	   *AppendNode under the tag <Ok> - GroovyScript Parsing XmlSlurper() - replaceNode()
           *MarkupBuilderHelper: mkp.yield (Object value) or (StringValue)--> Prints data in the body 
           *of the current tag, escaping XML entities.
	   * AppendNode in specific position Java Dom Parsing - Sample: 
           *root.insertBefore(tag_position,tag);  
	   * to appendNode "GPath:XmlSlurper().XMLResponse/NotaFiscaResponse/RespuestaSefaz" into the 
           *current inputstream XML
	   **/
	   
       completeXml.Response.NotaFiscalResponse.ok.replaceNode { 
            node ->    mkp.yield(node) 
            RespuestaSefaz{  
              if(cStat=='100') {
                 AutorizadaPorSefaz(true);
               }else{
                 AutorizadaPorSefaz(false);
               }
                ClaveAcesso (cnfe);
                CodigoAmbiente('0000001');
                Codigo('1');
                CodigoEstado(cStat);
                DigestValue(digestVal);
                DigitoVerificador(cnfe.substring(43,44));
                FechaRecido(dhRecbto);
                Motivo(xMotivo);
                NroNotaFiscal(nNF);
                NroSerieNotaFiscal(serie);
                NumeroProtocolo(nProt);
            }
                XMLNFe(cdataAsStr)
        }
         /**
	 * Set a new output XML using StreamingMarkupBuilder - Building from scratch the new XML
         * MarkupBuilderHelper: mkp.yield (Object value) or (StringValue)--> Prints data in the body of 
         *the current tag, escaping XML entities.
	 **/
    String Response = new StreamingMarkupBuilder().bind {
    /**
    * Adding Namespace root element
    **/
    namespaces << [ ns0: 'http://blog.groovy.sap.com/XMLSlurper']
    /**
    * Creating the root element with namespace reference
    **/
        ns0.GroovyPocResponse{
          /**
          *Subelement Response
          **/
            Response {
                 /**
                 *Subelement Response/AsientoResponse
                 **/
                    AsientoResponse{

                    mkp.yield completeXml.Response.AsientoResponse.children()
                }
                /**
                *Subelement Response/MovimentoResponse
                **/
                MovimentoResponse  {
                    mkp.yield completeXml.Response.MovimentoResponse .children()
                }
                /**
                *Subelement Response/NotaFiscalResponse
                **/
                NotaFiscalResponse{
                    mkp.yield completeXml.Response.NotaFiscalResponse.children()
                }
            }
        }
    }
    /**
     * Set new boby as - Output xml create using StreamingMarkupBuilder
     * replace ALL is preserving the CDATA enabled inside the XML
     * to works proper you must remove to set new body groovy.xml.XmlUtil.serialize 
    **/
    message.setBody(Response.replaceAll("&lt;","<").replaceAll("&gt;",">"));
    /**
    * Set new boby as - XmlUtil.serialize to format the output xml create using XmlSlurper parsing
    **/
    message.setBody(groovy.xml.XmlUtil.serialize(completeXml));
    /**
    * Returning the new message after parsing the input XML to output XML
    **/
	 def messageLog = messageLogFactory.getMessageLog(message);
        if(messageLog != null){
            messageLog.setStringProperty("Logging", "StreamingMarkupBuilder")
            messageLog.addAttachmentAsString("StreamingMarkupBuilder", body, "application/xml");
         }
    return message;
}






The trick point about CDATA enabled using StreamingMarkupBuilder

  •  replaceAll: set new body using replace string and without groovy.xml.XmlUtil.serialize.

    /**
     * Set new boby as - Output xml create using StreamingMarkupBuilder
     * replace ALL is preserving the CDATA enabled inside the XML
     * to works proper you must remove to set new body groovy.xml.XmlUtil.serialize 
    **/
    message.setBody(Response.replaceAll("&lt;","<").replaceAll("&gt;",">"));

Result CPI StreamingMarkupBuilder:



Why XSLT after?

XSLT to preserve CDATA XML enabled XML inside the XML tag

> This only needs in case of you decide parsing using XmlSlurper and XmlParser
both don’t preserve CDATA

Groovy’s XmlSlurper and XmlParser don’t preserve <![CDATA[<XML>]]>


  • XSLT Mapping program:


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <! --cdata-section-elements maintain the string inside <![CDATA<Business Data>]> -->
   <xsl:output method="xml" encoding="utf-8" indent="yes" cdata-section-elements="XMLNFe" />
   <! -- The node()|@* is matching all child nodes (node() is all text,element,processing instructions,comments)
   and attributes (@*) of the current context.  -->
   <xsl:template match="@*|node()">
      <xsl:copy>
         <xsl:apply-templates select="@*|node()" />
      </xsl:copy>
   </xsl:template>
</xsl:stylesheet>

Wait.. wait… wait…Why you don’t use the message mapping if you said that you used it before?


I really tried but somehow when I was doing the simulation in CPI the tag <XMLNFe> always was empty, I could not find anything to solve this fast so I decided to approach via XSLT.

Message Mapping Steps

  1. Import the external definitions (XSD or WSDLs)
  2. Create the Message Mapping program
  3. Proceed with mappings in my this case like in the previous blog not mapped <RespuesaSefaz>
  4. Simulation to generate the Output XML without <RespuesaSefaz> that will be created in the Groovy Parsing.
  • Import the external definitions (XSD or WSDLs)



Create a message mapping program, mapping the fields relevant for the transformations without <NotaFiscalResponse>

  • Proceed with mappings:


 

  • Simulation – testing the mapping with external input XML


I really don’t have any idea, I could not find fast any note or something about that and because of that, I decided to develop the XSLT transformer after the groovy.



  • Groovy Payload


import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
def Message processData(Message message) {
    def body = message.getBody(java.lang.String) as String;
    def messageLog = messageLogFactory.getMessageLog(message);
        if(messageLog != null){
            messageLog.setStringProperty("Logging", "XmlSlurper")
            messageLog.addAttachmentAsString("Parsing XmlSlurper ", body, "application/xml");
         }
  return message;
}

Let’s test and check..? 


Expected result as a similar image from the 7 years old DOM Parsing blog:

  • Result SAP PI – Javamapping DOM Parsing:


Result in SAP CPI using:

Send HTTPS call on CPI per default configuration it’s synchronous, so the image below it’s the replay after logging message, look that the parsing Groovy Parsing XmlSlurper works fine and insert the node RespustaSefaz after <ok>

  • Postman CALL


Groovy Logging on SAP CPI:

Parsing – XmLSluper –  after the XSLT Preserve the cdata enabled inside tag XMLNfe:



Groovy Logging on SAP CPI:


Parsing – StreamingMarkupBuilder – Preserve the cdata enabled inside tag XMLNfe:


Comparison of the result Java and Groovy



Java DOM Paring part of code – 40 lines:


/*------------------------------------------------------------------------------
* Creating elements
*------------------------------------------------------------------------------*/
respostaSefaz = doc.createElement("ns1:RespuestaSefaz");
autorizadaPorSefaz = doc.createElement("ns1:AutorizadaPorSefaz");
claveAcceso = doc.createElement("ns1:ClaveAcceso");
codigo = doc.createElement("ns1:Codigo");
codigoAmbiente = doc.createElement("ns1:CodigoAmbiente");
codigoEstado = doc.createElement("ns1:CodigoEstado");
digestValue = doc.createElement("ns1:DigestValue");
digitoVerificador=doc.createElement("ns1:DigitoVerificador");
fechaRecibido = doc.createElement("ns1:FechaRecibido");
motivo = doc.createElement("ns1:Motivo");
nroNotaFiscal = doc.createElement("ns1:NroNotaFiscal");
nroSerieNotaFiscal = doc.createElement("ns1:NroSerieNotaFiscal");
nroProtocolo = doc.createElement("ns1:NumeroProtocolo");
/*------------------------------------------------------------------------------
* Inserting the values into new tags from values extracts
*------------------------------------------------------------------------------*/
autorizadaPorSefaz.appendChild(doc.createTextNode(cStat.replace(cStat, "true")));
claveAcceso.appendChild(doc.createTextNode(chNFe));
codigo.appendChild(doc.createTextNode(cNF));
codigoAmbiente.appendChild(doc.createTextNode(tpAmb));
codigoEstado.appendChild(doc.createTextNode(cStat));
digestValue.appendChild(doc.createTextNode(digestVal));
fechaRecibido.appendChild(doc.createTextNode(dhRecbto));
motivo.appendChild(doc.createTextNode(xMotivo));
nroNotaFiscal.appendChild(doc.createTextNode(nNF));
nroSerieNotaFiscal.appendChild(doc.createTextNode(serie));
nroProtocolo.appendChild(doc.createTextNode(nProt));
/*---------------------------------------------------------------------
* Creating the <ns1:RespuestaSefaz> and inserting child tags
*---------------------------------------------------------------------*/
respostaSefaz.appendChild(autorizadaPorSefaz);
respostaSefaz.appendChild(nroNotaFiscal);
respostaSefaz.appendChild(nroSerieNotaFiscal);
respostaSefaz.appendChild(claveAcceso);
respostaSefaz.appendChild(codigoAmbiente);
respostaSefaz.appendChild(codigoEstado);
respostaSefaz.appendChild(digestValue);
respostaSefaz.appendChild(fechaRecibido);
respostaSefaz.appendChild(motivo);
respostaSefaz.appendChild(nroNotaFiscal);
respostaSefaz.appendChild(nroSerieNotaFiscal);
rootNFResponse.insertBefore(respostaSefaz, tagXmlNFe);
absTraceLog.addDebugMessage(" O elemento <"+respostaSefaz.getNodeName()+"> foi inserido acima da tag <"+ tagXmlNFe.getNodeName() + ">"
+ "que fica na estrutura <"+ rootNFResponse.getNodeName() + ">");
source = new DOMSource(doc);
result = new StreamResult(tOut.getOutputPayload().getOutputStream());
tf.transform(source, result);
}

Groovy XmlSlurper Parsing – 24 lines to do the same:


  completeXml.Response.NotaFiscalResponse.ok.replaceNode { 
            node ->    mkp.yield(node) 
            RespuestaSefaz{  
              if(cStat=='100') {
                 AutorizadaPorSefaz(true);
               }else{
                 AutorizadaPorSefaz(false);
               }
                ClaveAcesso (cnfe);
                CodigoAmbiente('0000001');
                CodigoEstado('1');
                CodigoEstado(cStat);
                DigestValue(digestVal);
                DigitoVerificador(cnfe.substring(43,44));
                FechaRecido(dhRecbto);
                Motivo(xMotivo);
                NroNotaFiscal(nNF);
                NroSerieNotaFiscal(serie);
                NumeroProtocolo(nProt);
            }
        }
       /**
	 * Groovy's XmlSlurper and XmlParser don't preserve <![CDATA[<XML>]]>
	 * to solve this problem I apply the next step the XSLT mapping
	 * XmlUtil.serialize the inputXml transformed in xml as return:message
	 **/
    message.setBody(groovy.xml.XmlUtil.serialize(completeXml));
    /**
	 * Returning the new message after parsing the input XML to output XML
	 **/
    return message;
}

Thanks and credits: I would like to thank you…

Let’s referral all professional that post something related to GroovyScript that was relevant for this PoC – in SAP CPI Platform:

I know that there are many other blogs but for me, those were more relevant for this proposal.


Result:


DOM parsing definitely it’s a great mechanism to parsing XML but as I remember my code in java was around N lines and with Groovy less the 50 and easier/faster to the developer, for me takes time because I’m learning…still.

So it’s time for you GROOVY yourself because everything starts to be more in the cloud…. wake up !!

It’s taken a long time to collect and provide in this level of detail and information to you, I hope that you like it.

Thanks once again.

@SAP please take look about this MESSAGE MAPPING program simulation with input XML that’s contains <![CDATA[<XML>]]> embedded inside one XML tag.

Kind Regards,

Viana.

 

6 Comments
You must be Logged on to comment or reply to a post.