Groovy get text of node that has children [duplicate] - groovy

I need to access the new value for each field if it exists, and the "previous" value in order to determine if a record has a change.
Sample XML Payload
<Persons>
<Person>
<id>8675309</id>
<person>
<action>CHANGE</action>
<status>
active
<previous>inactive</previous>
</status>
</person>
</Person>
<Person>
<id>8675308</id>
<person>
<action>CHANGE</action>
<code>
5678
<previous>1234</previous>
</code>
</person>
</Person>
</Persons>
I am using XmlParser/XmlSlurper to iterate over a similar xml payload that's provided from an API which includes the previous value in the field as a sub-field, how can I access just the root value of the status field in the above payload?
xml.each { it ->
PersonId = it.id.text();
Status = it.person.status.text(); // this is including the new and previous, 'activeinactive', and not just 'active'
PrevStatus = it.person.status.previous.text();
if (Status && PrevStatus) {
// Status has changed, do something
}
};

If you are using XmlParser, you need localText() instead of text()
Here is my test xml file
<Persons>
<Person>
<id>8675309</id>
<person>
<action>CHANGE</action>
<status>
active
<previous>inactive</previous>
</status>
</person>
</Person>
</Persons>
This is my script:
def xml = new XmlParser().parse("text.xml")
xml.each { it ->
def personId = it.id.text();
def status = it.person.status[0].localText()[0].trim()
def prevStatus = it.person.status.previous.text();
println status
println prevStatus
};
Output after executing the script:
active
inactive
Note: I am using groovy3

Related

Editing XML files with Node.JS

So as you can see, we have this XML
<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<GetECConfigResponse xmlns="urn:ecs.wsapi.broadon.com">
<Version>2.0</Version>
<DeviceId>4498122730</DeviceId>
<MessageId>ECDK-4498122730-39653600512638909</MessageId>
<TimeStamp>1599408189821</TimeStamp>
<ErrorCode>0</ErrorCode>
<ServiceStandbyMode>false</ServiceStandbyMode>
<ContentPrefixURL>http://example.com</ContentPrefixURL>
<UncachedContentPrefixURL>http://example.com</UncachedContentPrefixURL>
<SystemContentPrefixURL>http://example.com</SystemContentPrefixURL>
<SystemUncachedContentPrefixURL>http://example.com</SystemUncachedContentPrefixURL>
<EcsURL>http://example.com</EcsURL>
<IasURL>http://example.com</IasURL>
<CasURL>http://example.com</CasURL>
<NusURL>http://example.com</NusURL>
</GetECConfigResponse>
</soapenv:Body>
</soapenv:Envelope>
I do this so I can edit stuff inside the xml depending on the user's POST body but I have to edit the stuff I want, and turn it into a XML back again and send it as a response, since it's not possible with Node.JS to edit the XML directly without converting to JS or JSON
const xmlParser = require("xml2json");
var json2xml = require("json2xml");
// Current time in epoch
function currentEpoch() {
const now = Date.now()
return now
}
// Read the XML file
fs.readFile("./ecs/ecommercesoap/getecconfigresponse.xml", function (err, data) {
// Convert the XML to JSON
const xmlObj = xmlParser.toJson(data, {
object: true
})
// Get the user's body details
var deviceId = req.body["DeviceId"]
// Change XML index depending on the user
xmlObj["soapenv:Envelope"]["soapenv:Body"]["GetECConfigResponse"]["DeviceId"] = deviceId
xmlObj["soapenv:Envelope"]["soapenv:Body"]["GetECConfigResponse"]["TimeStamp"] = currentEpoch().toString()
This is where it converts xmlObj back to xml again
// Convert the JSON to XML and finalize it
const finalXml = xmlParser.toXml(xmlObj)
// Set the response's type as application/xml
res.type('application/xml');
// Sending the completely edited XML back to the user
res.send(finalXml)
And as you can see, the output changes when the converting happens. The XML below is after it's edited and parsed back into XML from JSON. If you noticed, <?xml version="1.0" encoding="utf-8"?> is also deleted, which shouldn't be.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<GetECConfigResponse xmlns="urn:ecs.wsapi.broadon.com" Version="2.0" DeviceId="4498122730" MessageId="ECDK-4498122730-39653600512638909" TimeStamp="1599505063565" ErrorCode="0" ServiceStandbyMode="false" ContentPrefixURL="http://example.com" UncachedContentPrefixURL="http://example.com" SystemContentPrefixURL="http://example.com" SystemUncachedContentPrefixURL="http://example.com" EcsURL="http://example.com" IasURL="http://example.com" CasURL="http://example.com" NusURL="http://example.com"/>
</soapenv:Body>
</soapenv:Envelope>
So I wonder what can I do to fix this, the tags start and end with <> in the original while edited one doesn't even have it. Long story short, bunch of stuff got changed. I also tried xml2js instead of xml2json but the output is still the same. I just want my output exactly same as the original file, but with my stuff changed.
In our project we have to deal with manually modifiying soap/xml in NodeJS as well - we've recently switched to fast-xml-parser and are really happy with it. I took your sample input and using the following code:
const xmlToJsonParser = require('fast-xml-parser');
const options = {
ignoreAttributes: false,
ignoreNameSpace: false,
parseNodeValue: true,
parseAttributeValue: true,
trimValues: true,
};
// xmlData refers to the xml-string
const tObj = xmlToJsonParser.getTraversalObj(xmlData, options);
const jsonObj = xmlToJsonParser.convertToJson(tObj, options);
// modify json
jsonObj["soapenv:Envelope"]["soapenv:Body"]["GetECConfigResponse"].DeviceId = 123456;
const JsonToXmlParser = require("fast-xml-parser").j2xParser;
const parser = new JsonToXmlParser({format: true, ignoreAttributes: false});
const xml = parser.parse(jsonObj);
console.log(xml);
it produces:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<GetECConfigResponse xmlns="urn:ecs.wsapi.broadon.com">
<Version>2</Version>
<DeviceId>123456</DeviceId>
<MessageId>ECDK-4498122730-39653600512638909</MessageId>
<TimeStamp>1599408189821</TimeStamp>
<ErrorCode>0</ErrorCode>
<ServiceStandbyMode>false</ServiceStandbyMode>
<ContentPrefixURL>http://example.com</ContentPrefixURL>
<UncachedContentPrefixURL>http://example.com</UncachedContentPrefixURL>
<SystemContentPrefixURL>http://example.com</SystemContentPrefixURL>
<SystemUncachedContentPrefixURL>http://example.com</SystemUncachedContentPrefixURL>
<EcsURL>http://example.com</EcsURL>
<IasURL>http://example.com</IasURL>
<CasURL>http://example.com</CasURL>
<NusURL>http://example.com</NusURL>
</GetECConfigResponse>
</soapenv:Body>
</soapenv:Envelope>
All that's left is to prepend the xml-header, as they've decided to exclude this feature from the parser (https://github.com/NaturalIntelligence/fast-xml-parser/issues/184).

Modifying Soap UI request using groovy

we have a requirement of finding the number of dealers in current country.In the below xml request key-value pair will be varied for every request. Inputs to the soap request will be given in .txt file.Based on the number of inputs in .txt file i need to generate key-value pair xml tags dynamically.
**Format of Input.txt**
1.key1=Fruit,value1=Carrot,Key2=Vegetable,value2=Carrot
2.key1=Vegetable,value1=Potato
3.key1=Fruit,value1=Apple,key2=Fruit,value2=Orange,key3=Fruit,value3=Mango
SoapUI Request
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://www.webserviceX.NET">
<soapenv:Header/>
<soapenv:Body>
<web:GetCitiesByCountry>
<web:CountryName>Ind</web:CountryName>
<web:queryParameters>
<web:key>Fruits</web:key>
<web:value>Mango</web:value>
</web:queryParameters>
<web:queryParameters>
<web:key>Vegetables</web:key>
<web:value>Carrot</web:value>
</web:queryParameters>
</web:GetCitiesByCountry>
</soapenv:Body>
</soapenv:Envelope>
You say you have the reading of the input records sorted, so I've just put some records in a map for demonstration purposes. Then, if your request payload starts off looking like:
<soap:Envelope xmlns:soap="w3.org/2003/05/soap-envelope">
<soap:Header/>
<soap:Body>
<web:GetCitiesByCountry xmlns:web="webserviceX.NET">
<web:CountryName>hello</web:CountryName>
</web:GetCitiesByCountry>
</soap:Body>
</soap:Envelope>
You can loop through your input records and append them to the request:
import groovy.xml.XmlUtil
// Assuming the input document has been read into a HashMap
def map = ['Fruits': 'Banana',
'Vegetable': 'Potato'
]
// Get testStep by it's name
def testStep = context.testCase.getTestStepByName('soapRequest')
// Parse the request
def xml = new XmlSlurper().parseText(testStep.getPropertyValue('request'))
// Loop through the map and append the key/value pairs
xml.Body.appendNode {
map.each {k, v ->
Parameters {
Key(k)
Value(v)
}
}
}
// Check the output
log.info(XmlUtil.serialize( xml ))
// Update the request
testStep.setPropertyValue('request',groovy.xml.XmlUtil.serialize( xml ))
Afterwards, your request will look like:
<soap:Envelope xmlns:soap="w3.org/2003/05/soap-envelope">
<soap:Header/>
<soap:Body>
<web:GetCitiesByCountry xmlns:web="webserviceX.NET">
<web:CountryName>hello</web:CountryName>
</web:GetCitiesByCountry>
<Parameters>
<Key>Fruits</Key>
<Value>Banana</Value>
</Parameters>
<Parameters>
<Key>Vegetable</Key>
<Value>Potato</Value>
</Parameters>
</soap:Body>
</soap:Envelope>
As you said you need to add
<Parameters> <Key>key1</Key> <Value>Value1</Value> </Parameters>
dynamically while executing groovy script
So inside your groovy script in a variable
xmlValue="<Parameters> <Key>key1</Key> <Value>Value1</Value> </Parameters>"
testRunner.testCase.setPropertyValue(xmlNodes,xmlValue)
So now your dynamic xml is in testCase property. So inside your xml where you want to place it
you can place this code
<web:CountryName>${#TestCase#xmlValue}</web:CountryName>
This way it will get added. If you pass it null,then nothing will be added in your xml.
There's some good technical solutions here, but if you buy a SoapUI license, you'll be able to access the data driven test functionality, which does exactly what you want right out of the box. No groovy scripts required.

how to pull the parameter from cdata response and use it in another request using groovy in soapUi

This is my response from soapUI
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<SearchAirFaresResponse xmlns="http://www.sample.com/xchange">
<SearchAirFaresResult>
<![CDATA[
<FareSearchResponse>
<MasterDetails>
<CurrencyCode>INR</CurrencyCode>
<RecStNo>1</RecStNo>
<SessionID>5705b1a6-95ac-486c-88a1f90f85e57590</SessionID>
</MasterDetails>
</FareSearchResponse>
]]>
</SearchAirFaresResult>
</SearchAirFaresResponse>
</soap:Body>
</soap:Envelope>
How to extract SessionID element which is inside CDATA using groovy script and use it in another request like
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<xch:GetMoreFares>
<xch:sGetMoreFare>
<![CDATA[
<MoreFlights>
<MasterDetails>
<NoOfResult Index="1">40</NoOfResult>
<BranchId>1</BranchId>
<SessionId>5705b1a6-95ac-486c-88a1f90f85e57590</SessionId>
</MasterDetails>
<Journey>DOM</Journey>
<ResponseType>XML</ResponseType>
<SearchType>OW</SearchType>
</MoreFlights>
]]>
</xch:sGetMoreFare>
</soap:Body>
</soap:Envelope>
3.I have been searching lot but didnt get the right one,and also am new to groovy script using soapUi , pls guide me stepwise procedure in soapUi to implement .
To do so you can use a Groovy testStep, inside it get the SOAP testStep where you've the response with desired sessionID and use a XmlSlurper to parse the response and get the CDATA value. Note that XmlSlurper treat CDATA as String so you've to parse it again. Finally save the returned value as a TestSuite or TestCase level (in the example I use TestCase):
// get your first testStep by its name
def tr = testRunner.testCase.getTestStepByName('Test Request')
// get your response
def response = tr.getPropertyValue('response')
// parse the response and find the node with CDATA content
def xml = new XmlSlurper().parseText(response)
def cdataContent = xml.'**'.find { it.name() == 'SearchAirFaresResponse' }
// XmlSlurper treat CDATA as String so you've to parse
// its content again
def cdata = new XmlSlurper().parseText(cdataContent.toString())
// finally get the SessionID node content
def sessionId = cdata.'**'.find { it.name() == 'SessionID' }
// now save this value at some level (for example testCase) in
// order to get it later
testRunner.testCase.setPropertyValue('MySessionId',sessionId.toString())
Then change a bit your second testStep to use property expansion to get the MySessionId property in your second request as ${#TestCase#MySessionId}:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<xch:GetMoreFares>
<xch:sGetMoreFare>
<![CDATA[
<MoreFlights>
<MasterDetails>
<NoOfResult Index="1">40</NoOfResult>
<BranchId>1</BranchId>
<SessionId>${#TestCase#MySessionId}</SessionId>
</MasterDetails>
<Journey>DOM</Journey>
<ResponseType>XML</ResponseType>
<SearchType>OW</SearchType>
</MoreFlights>
]]>
</xch:sGetMoreFare>
</soap:Body>
</soap:Envelope>
This is also most similar to albciff's answer, but with little variant (using reusable closure to parse).
Here is the Script Assertion for the first request step. This will avoid additional groovy script step in the test case.
Please follow the appropriate comments inline:
Script Assertion:
/**
* This is Script Assertion for the first
* request step which extracts cdata from response,
* then sessionId from extracted cdata. And
* Assigns the value at test case level, so that
* it can be used in the rest of the test steps of
* the same test case.
* However, it is possible to store the sessionId
* at Suite level as well if you want use the sessionId
* across the test cases too.
*
* */
/**
* Closure to search for certain element data
* input parameters
* xml data, element name to search
**/
def searchData = { data, element ->
def parsedData = new XmlSlurper().parseText(data)
parsedData?.'**'.find { it.name() == element} as String
}
//Assert the response
assert context.response, "Current step response is empty or null"
//Get the cdata part which is inside element "SearchAirFaresResult"
def cData = searchData(context.response,'SearchAirFaresResult')
//Assert CDATA part is not null
assert cData, "Extracted CDATA of the response is empty or null"
//Get the SessionID from cdata
def sessionId = searchData(cData, 'SessionID')
//Assert sessionId is not null or empty
assert sessionId, "Session Id is empty or null"
log.info "Session id of response $sessionId1"
//Set the session to test case level custom property
//So, that it can be used for the rest of the steps
//in the same test case
context.testCase.setPropertyValue('SESSION_ID', sessionId)
In the following test steps, you can use the saved SESSION_ID in the following ways:
If the following step is request step (REST, SOAP, HTTP, JDBC etc), then use property expansion ${#TestCase#SESSION_ID} like <SessionId>${#TestCase#SESSION_ID}</SessionId>
If the following step is groovy script, then use one of the below:
context.expand('${#TestCase#SESSION_ID}') or
context.testCase.getPropertyValue('SESSION_ID') or
testRunner.testCase.getPropertyValue('SESSION_ID')

SoapUI Mock Service Transfer Multiple Nodes

I am in a pickle.
I feel there is a simple solution to what I want to achieve, but I am at a loss to how to go about it.
Basically, I am standing up some mock Soap services.
I want to echo back an update call with that which is passed in.
My request looks like this:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"/>
<soap:Body>
<ns2:setEvents xmlns:ns2="http://example.com/eventingservices" xmlns:ns3="http://example.com/eventing/sdo">
<setEventsRequest>
<SystemCode>ABC</SystemCode>
<Event>
<EventTypeCode>01</EventTypeCode>
</Event>
<Event>
<EventTypeCode>04</EventTypeCode>
</Event>
</setEventsRequest>
</ns2:SetEvents>
</soap:Body>
</soap:Envelope>
I then want to simply transfer the list of Events to the response. They have the same attributes as the request.
A typical response, would look like this:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"/>
<soapenv:Body>
<qu:setEventsResponse xmlns:qu="http:/example.com/eventingServices">
<setEventsResponse>
<Event>
<EventTypeCode>01</EventTypeCode>
</Event>
<Event>
<EventTypeCode>04</EventTypeCode>
</Event>
</setEventsResponse>
</qu:setEventsResponse>
</soapenv:Body>
</soapenv:Envelope>
I tried using the following Groovy script, replacing the Events in the response with ${events}:
def groovyUtils = new com.eviware.soapui.support.GroovyUtils(context)
def holder = groovyUtils.getXmlHolder(mockRequest.requestContent)
def events = String.valueOf(holder.getNodeValues("//Event"))
context.setProperty("events", events);
I also tried the above without doing the string of. To no avail.
Please please help me.
I'll buy you a beer if I can get this damned thing to work!
I suppose that you have a mock service in SOAPUI configured as follows.
Operation with a response similar to:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"/>
<soapenv:Body>
<setEventsResponse xmlns="http:/example.com/eventingServices">
<setEventsResponse>
${events}
</setEventsResponse>
</setEventsResponse>
</soapenv:Body>
</soapenv:Envelope>
And on the onRequestScript tab of your mockService you want to have the necessary script to get the <Event> nodes from the request and put it in the response using ${events} property. To do so, I recommend that you can use XmlSlurper as follows:
import groovy.xml.StreamingMarkupBuilder
// parse the request
def reqSlurper = new XmlSlurper().parseText(mockRequest.requestContent)
// find all 'Event' nodes from request
def events = reqSlurper.depthFirst().findAll { it.name() == 'Event' }
// builder to convert the nodes to string
def smb = new StreamingMarkupBuilder()
// concatenate in the string all "<Event>" nodes from request
def eventAsString = ''
events.each{
eventAsString += smb.bindNode(it)
}
// set the property to use in the response
context.setProperty("events", eventAsString);
That's all to make it work :).
Additionally note that there are some mistakes in you xmls. The request in your question is not well formed: </ns2:SetEvents> not close <ns2:setEvents> (note uppercase), and if you want that xmlns:ns2="http://example.com/eventingservices" namespace applies to all childs of <SetEvents> you have to add ns2 to all the child nodes or remove the ns2 to make it default for this subtree:<SetEvents xmlns="http://example.com/eventingservices">` (this applies also for your response)
Hope it helps,

How to add complex data type from Groovy script to the response in SoapUI

My question is about putting data elements (from groovy script) in the response in SoapUI.
I've an array of data that I would like to put in my response (in different tags/elements)
I'm aware of putting a simple element like this:
The element "MyName" in the Xml response:
<ns:MyName>${MyName}</ns:MyName>
Is mapped from the Groovy script by
context.setProperty("MyName" , "My name" )
Now the problem:
my Xml response looks like this:
<soapenv:Body>
<ns:GetDataSummaryResponse>
<!--Optional:-->
<ns:GetDataSummaryResult>
<ns:DataSummary>
<!--Zero or more repetitions:-->
<ns:DataSummaryResponseDetail>
<ns:Name>?</ns:Name>
<!--Optional:-->
<ns:DataProgress>
<!--Optional:-->
<From>?</From>
<!--Optional:-->
<Procent>?</Procent>
<!--Optional:-->
<To>?</To>
<!--Optional:-->
In Groovy I've built data array which is filled with data for example like this:
context:[DataSummary:[DataSummaryResponseDetail:[Name:My name, DataProgress:[From:some text, **Procent:some value**, To:some text]]]
In the response I'm able to see the whole value of ${DataSummary} but how do I get the element "Procent"
I maybe am wrong about how to build my context data, but feel free to adjust!
I managed to do this with a lot of searching.
First of all, I needed to use "=" in front of the element to be able to get a handle like
${=DataSummary[0]}
This works on the top level of my data shown before.
But to really solve the problem I created a Class in Groovy like this:
Code: Select all
Class DataSummaryResponseDetail {
public String name
public DataProgress DataProgress = new DataProgress()
public Value[] value = new Value[2]
}
class DataProgress {
public Date From
public Date To
public float Procent
}
class Value {
public String Currency
public int Amount
}
def hsrd = new DataSummaryResponseDetail()
hsrd.name = 'Some name'
hsrd.value[0]=new Value()
hsrd.value[0].Amount = 1000
hsrd.value[0].Currency = 'SEK'
hsrd.totalValue.Amount = 2000
hsrd.totalValue.Currency = 'USD'
and then in the response xml. I refer to elements like this
${hsrd.value[0].Amount}
${=hsrd.totalValue.Amount}
def hsrd = new DataSummaryResponseDetail()
hsrd.name = 'Some name'
hsrd.value[0]=new Value()
hsrd.value[0].Amount = 1000
hsrd.value[0].Currency = 'SEK'
hsrd.totalValue.Amount = 2000
hsrd.totalValue.Currency = 'USD'
Problem solved. If you know of a better way, please let me know.
I haven't work with SoapUI, but have you tried this:
${DataSummary.DataSummaryResponseDetail.DataProgress.Procent}
If DataSummary is an array, you could access the first entry using
${DataSummary[0].DataSummaryResponseDetail.DataProgress.Procent}
You can iterate over the array using the method each(), so
${DataSummary.each { it.DataSummaryResponseDetail.DataProgress.Procent }}
would get you all entries.

Resources