2

Setup: SOAP UI 5.2.0., Groovy step to generate XMLs. We need to read a CSV which contains XPath-like nodes locations and new values to be placed to sample XML.
The last version of the code in the following answer fits our goal perfectly fine: Groovy replace node values in xml with xpath
Only one problem: our XML contains repeating elements and using is not possible as it misinterprets "[1]" in Body.GetWeather[1].CityName

    def node = xml
key.split("\\.").each {
  node = node."${it}"
}

Ideally, we would also need to use something like Body.GetWeather[CountryName="Africa"].CityName.
I tried using XMLParser also and experimented with syntax (see below). I am new to Groovy and I might be missing something about it. So, let me know if I need to approach the problem differently.

Below is the actual problem described in 3rd example:

// reading XML
def myInputXML = '''
<soapenv:Envelope   xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Header/>
  <soapenv:Body>
      <web:GetWeather xmlns:web="http://www.webserviceX.NET">
          <web:CityName>Cairo</web:CityName>
          <web:CountryName>Africa</web:CountryName>
      </web:GetWeather>
      <web:GetWeather xmlns:web="http://www.webserviceX.NET">
          <web:CityName>Heidelberg</web:CityName>
          <web:CountryName>Germany</web:CountryName>
      </web:GetWeather>
      <web:GetWeather xmlns:web="http://www.webserviceX.NET">
          <web:CityName>Strasbourg</web:CityName>
          <web:CountryName>France</web:CountryName>
      </web:GetWeather>
  </soapenv:Body>
</soapenv:Envelope>
'''
def xml = new XmlSlurper().parseText( myInputXML )
// Example 1 //
def GetAllCities = xml.Body.GetWeather.CityName
log.info ("Example 1: "+GetAllCities.text()) // references all 3 CityName nodes, prints out - CairoHeidelbergStrasbourg
// Example 2 //
def Get2ndCity = xml.Body.GetWeather[1].CityName
log.info ("Example 2: "+Get2ndCity.text()) // references 2nd node, prints out - Heidelberg
// Example 3 //
def tmpNode1 = "Body"
def tmpNode2 = "GetWeather[0]" 
   // This problem is with interpolation of GetWeather[0]. tmpNode2 = "GetWeather" would work as Example 1
def tmpNode3 = "CityName"
def GetFirstCity = xml."${tmpNode1}"."${tmpNode2}"."${tmpNode3}"
log.info ("Example 3: "+GetFirstCity.text()) // prints "" - WHY?
log.info ("Interpolation of tmpNodes 1, 2, 3:") 
log.info ("${tmpNode1}") // prints Body
log.info ("${tmpNode2}") // prints GetWeather[0]
log.info ("${tmpNode3}") // prints CityName

P.S. Apologies in case if my examples are irrelevant for the actual problem, I thought they are somewhat helpful, but the goal is to improve the mentioned stackoverflow answer to support repeating elements.

Community
  • 1
  • 1
Zaplatki
  • 35
  • 7

1 Answers1

1

In case, you want only to fix your script, then please make the below changes to get the data what you expected.

Change from

// Example 3 //
def tmpNode1 = "Body"
def tmpNode2 = "GetWeather[0]" 
   // This problem is with interpolation of GetWeather[0]. tmpNode2 = "GetWeather" would work as Example 1
def tmpNode3 = "CityName"
def GetFirstCity = xml."${tmpNode1}"."${tmpNode2}"."${tmpNode3}"
log.info ("Example 3: "+GetFirstCity.text()) // prints "" - 

To

// Example 3 //
def tmpNode1 = "Body"
//removed index from here
def tmpNode2 = "GetWeather" 
def tmpNode3 = "CityName"
//Added index here in below
def GetFirstCity = xml."${tmpNode1}"."${tmpNode2}"[0]."${tmpNode3}"
log.info ("Example 3: "+GetFirstCity.text()) 

Elegant Approach:

However, here is the way I would generate request from csv file. It does not involve any xml template, instead build the whole request xml using StreamingMarkupBuilder. Because this is an elegant way and flexible. Also csv is very readable as it contains only data, not any xpath's like you mentioned.

Below script uses this very good library groovycsv (which depends on opencsv), please follow its readme.

  • Download the jar files.
  • Copy them under SOAPUI_HOME/bin/ext directory.
  • Restart soapui.

Here is the script which builds the request xml based on the csv file:

import groovy.xml.*
import static com.xlson.groovycsv.CsvParser.parseCsv
//closure which builds the request based on the data provided
def requestBuilder = { csvData ->
    def builder = new StreamingMarkupBuilder()
    builder.encoding = 'UTF-8'
    def soapRequest = builder.bind {
        mkp.xmlDeclaration()
        namespaces << [soap: 'http://schemas.xmlsoap.org/soap/envelope/',
                           web : 'http://www.webserviceX.NET']
        soap.Envelope {
            soap.Header{}
            soap.Body {
              //loop thru the rows
                csvData.each { row ->
                  //create GetWeather element for each row
                    web.GetWeather{
                        web.CityName(row.CityName)
                        web.CountryName(row.CountryName)
                    }
                }
            }
        }
    }
}
//Used fixed csv data. But you can replace it with reading from file too
def csv = '''CityName,CountryName
Cairo,Africa
Heidelberg,Germany
Strasbourg,France'''
/**
//use this to read from file and remove above statement
def csv = new File('/absolute/csv/file/path').text
**/
//parse the csv using groovy csv library
def data = parseCsv(csv)
//call the above closure get the request and serialize it to string 
def request = XmlUtil.serialize(requestBuilder(data))
log.info request

If you use print request instead of log.info and it will show pretty-print xml (you need to start the soapui in command line SOAPUI_HOME/bin/soapui.bat) enter image description here

Rao
  • 20,781
  • 11
  • 57
  • 77
  • thank you. I tested the first suggestion with Example 3, but it did not work for me... It would be very good if such or similar thing works, because then I will be able to use our existing setup. – Zaplatki Jun 22 '16 at 12:12
  • do you know, what could be wrong - Groovy version/ misprint? Regarding the second suggestion: I will refactor my script to use openCSV, but creating XML without use of sample XML would not help us with our goal: we have hundreds of elements in XML and we need to just update several elements always at the same places, but in various different XML files. We need to use these XPath-like locations and limitation with inability to set different values to different instanses of repeted elements is the main problem now... – Zaplatki Jun 22 '16 at 12:25
  • @Zaplatki, actually I did typo mistake, fixed it. `xml."${tmpNode1}"."${tmpNode2}"[0]."$tmpNode3"`. Please retry. – Rao Jun 22 '16 at 13:55
  • I do not know how you maintain such data. Having csv and creating xml would be clean and neat. – Rao Jun 22 '16 at 13:57
  • it works now, this is great, thank you! Sample XMLs are generated by another application when the end-users enter the data into UI, but it takes a lot of time to generate even 1. That system then fires them into system we are working on. We receive the sample XML from testers and modify them slightly to test certain functionality in our system - we do not need to generate everything, just modify parts related to certain functionality. – Zaplatki Jun 22 '16 at 14:07
  • I will mark it as an answer, one more remark: is it possible to use similar syntax to locate a node basing on other elements values, something like: `def GetCity = xml."${tmpNode1}"."${tmpNode2}"[CountryName="Africa"]."${tmpNode3}" // does not work` – Zaplatki Jun 22 '16 at 14:18
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/115319/discussion-between-rao-and-zaplatki). – Rao Jun 22 '16 at 14:19