I came across a strange behavior of the each() method when trying this code:
def xml = new XmlSlurper().parseText('''
<list>
<item a="1">a</item>
<item a="2">b</item>
<item a="1">c</item>
</list>
''')
def i = 0
xml.'**'.findAll { it.#a=='1' }.each {
println "hi" + i
}
The result is only hi0, however I would expect hi0hi1. Is this behavior a bug or per language design? The second result is only provided if I write println "hi" + i++ instead of the current closure body, so when the content is different for each item...
Your i variable is not being incremented because there's nothing that tells it to increment. The way your code is currently written, I would expect the output to be:
hi0
hi0
I think what you are looking for is eachWithIndex, which provides the closure with two arguments - the current item and the index of the item. Your code would then look like this:
def xml = new XmlSlurper().parseText('''
<list>
<item a="1">a</item>
<item a="2">b</item>
<item a="1">c</item>
</list>
''')
xml.'**'.findAll { it.#a=='1' }.eachWithIndex { item, i ->
println "hi" + i
}
This results in an output of:
hi0
hi1
Related
I want to split the xml tags based on the nth occurance of special character coming in input tags using groovy
I have a xml input with many tags that hold huge data. I want to split the xml tags based on the nth occurance of special character coming in the input tags using groovy.
How to split a string at every Nth occurrence of a character in Java -- this logic works for me but for one xml tag. I have multiple xml tags and want to split all the tags based on say 4th occurance of delimiter '/'.
Can I get some directions to achieve this? Thanks.
Example input xml:
<Row>
<EntityID>9035158701/9035158702/9035158703/9035158704/9035158705/9035158706/9035158707/9035158708/9035158709/9035158710</EntityID>
<RefID>7U2HYUTP/5Z1IWGUS/7AK9MDDJ/6RP9DXAW/29FBRBEL/5YKDCO3B/75MUQU7S/57QCGOQE/2EUX64ON/2VTJPVUV</RefID>
</Row>
Expected Output:
<Root>
<Row>
<EntityID>9035158701/9035158702/9035158703/9035158704</EntityID>
<RefID>7U2HYUTP/5Z1IWGUS/7AK9MDDJ/6RP9DXAW</RefID>
</Row>
<Row>
<EntityID>9035158705/9035158706/9035158707/9035158708</EntityID>
<RefID>29FBRBEL/5YKDCO3B/75MUQU7S/57QCGOQE</RefID>
</Row>
<Row>
<EntityID>9035158709/9035158710</EntityID>
<RefID>2EUX64ON/2VTJPVUV</RefID>
</Row>
</Root>
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
import groovy.xml.*;
def Message processData(Message message) {
def body = message.getBody(java.io.Reader)
assert body != null
def input = new XmlSlurper().parse(body);
def entityid = input.EntityID.text();
def refid = input.RefID.text();
int nth=0;
int cont=0;
def writer = new StringWriter()
for(int i=0;i<entityid.length();i++){
if(entityid.charAt(i)=='/')
nth++;
if(nth == 4 || i==entityid.length()-1){
new MarkupBuilder(writer).Row{
if(i==entityid.length()-1) //with this if you preveent to cut the last number
EntityID (entityid.substring(cont,i+1))
else
EntityID (entityid.substring(cont,i+1))
nth=0;
cont =i+1;
}
}
}
def output = writer.toString()
message.setBody(output)
return message;
}
You can use split to separate the string by a character, then use collate to batch them together into groups, then join the elements of each group, and transpose the Entity and Ref ids together for each row:
def processData(String input) {
def parsed = new XmlParser().parseText(input)
def eids = parsed.EntityID.text().split("/").collate(4)*.join("/")
def rids = parsed.RefID.text().split("/").collate(4)*.join("/")
StringWriter writer = new StringWriter()
new MarkupBuilder(writer).Root {
[eids, rids].transpose().each { eid, rid ->
Row {
EntityID(eid)
RefID(rid)
}
}
}
writer.toString()
}
That takes a String, and returns a String, so you'll need to integrate it with the Message class (as you have above)
def input = '''<Row>
<EntityID>9035158701/9035158702/9035158703/9035158704/9035158705/9035158706/9035158707/9035158708/9035158709/9035158710</EntityID>
<RefID>7U2HYUTP/5Z1IWGUS/7AK9MDDJ/6RP9DXAW/29FBRBEL/5YKDCO3B/75MUQU7S/57QCGOQE/2EUX64ON/2VTJPVUV</RefID>
</Row>'''
processData(input)
returns
<Root>
<Row>
<EntityID>9035158701/9035158702/9035158703/9035158704</EntityID>
<RefID>7U2HYUTP/5Z1IWGUS/7AK9MDDJ/6RP9DXAW</RefID>
</Row>
<Row>
<EntityID>9035158705/9035158706/9035158707/9035158708</EntityID>
<RefID>29FBRBEL/5YKDCO3B/75MUQU7S/57QCGOQE</RefID>
</Row>
<Row>
<EntityID>9035158709/9035158710</EntityID>
<RefID>2EUX64ON/2VTJPVUV</RefID>
</Row>
</Root>
Using python3 I am trying to read an xml file and recalculate values based on the attributes within the Item, then write a copy of entire xml file with new values.
Example of xml file (about 10k rows in full file):
<?xml version="1.0" encoding="utf-8"?>
<Items>
<Item id="headscarf_d"
name="{=wW3iouiU}Hijab"
mesh="headscarf_d"
culture="Culture.aserai"
weight="0.5"
value="63"
appearance="1"
Type="HeadArmor">
<ItemComponent>
<Armor head_armor="3"
has_gender_variations="false"
beard_cover_type="type3"
hair_cover_type="all"
modifier_group="cloth_unarmoured"
material_type="Cloth"/>
</ItemComponent>
<Flags Civilian="true"
UseTeamColor="true" />
</Item>
<Item id="open_head_scarf"
name="{=qsVRoGUv}Open Head Scarf"
mesh="aserai_helmet_c"
culture="Culture.aserai"
weight="0.6"
value="174"
appearance="1"
Type="HeadArmor">
<ItemComponent>
<Armor head_armor="5"
has_gender_variations="false"
beard_cover_type="type3"
hair_cover_type="all"
modifier_group="cloth_unarmoured"
material_type="Cloth"/>
</ItemComponent>
<Flags Civilian="true"
UseTeamColor="true" />
</Item>
<Item id="woven_turban"
name="{=ArPvuBYK}Woven Turban"
subtype="head_armor"
mesh="aserai_helmet_h"
culture="Culture.aserai"
weight="0.8"
difficulty="0"
value="250"
appearance="1"
Type="HeadArmor">
<ItemComponent>
<Armor head_armor="6"
has_gender_variations="false"
beard_cover_type="type2"
hair_cover_type="all"
modifier_group="cloth_unarmoured"
material_type="Cloth"/>
</ItemComponent>
<Flags Civilian="true"
UseTeamColor="true" />
</Item>
</Items>
Taking a single item from the example xml,
<Item id="headscarf_d"
name="{=wW3iouiU}Hijab"
mesh="headscarf_d"
culture="Culture.aserai"
weight="0.5"
value="63"
appearance="1"
Type="HeadArmor">
<ItemComponent>
<Armor head_armor="3"
has_gender_variations="false"
beard_cover_type="type3"
hair_cover_type="all"
modifier_group="cloth_unarmoured"
material_type="Cloth"/>
</ItemComponent>
<Flags Civilian="true"
UseTeamColor="true" />
For simplicity say I wanted to take the Item value (63 above example) and divide by 2 (63/2=31.5). Then if the Item's ItemComponent material_type="Cloth" divide by 2 again (31.5/2=15.75). Finally round to an integer before updating the value and repeating for each item then writing the new updated xml file.
I attempted to use Reading, modifying and writing xml but could not get anything useful.
You are probably looking for something along these lines:
from lxml import etree
import math
inv="""[your xml above]"""
doc = etree.XML(inv)
values = doc.xpath('//Item')
materials = doc.xpath('Item//ItemComponent//Armor')
for t, m in zip(values,materials):
if m.attrib['material_type'] == 'Cloth':
val = float(t.attrib['value'])/4
t.attrib['value'] = str(math.ceil(val))
else:
t.attrib['value']= str(math.ceil(val*2))
print(etree.tostring(doc).decode())
The output is your xml with the Items/Item/#value attribute value divided by 2 or 4, as necessary, and rounded up by math.ceil(). Since all Items in your example have cloth as the value of the attribute material_type, they were all divided by 4 and rounded up to:
16
44
63
I am trying to get values from a web service response in readyAPI, so i can pass it to another web service request, so i can create a automated test flow.
I have tried different code pieces most of them was a single line of code, which i prefer if it possible. I can take value from a node by typing the parent node by its attribute value. I also can get parent node by child nodes attribute value and use it to get another child value.
Here some examples:
First Format that I can use it to get childs value:
<webserviceResponse>
<documentslist>
<document #id="1">
<payment #currency="USD" >
<amount>1250.00</amount>
</payment>
</document>
<document #id="2">
<payment #currency="JPY" >
<amount>150.00</amount>
</payment>
</document>
<document #id="3">
<payment #currency="EUR" >
<amount>1170.00</amount>
</payment>
</document>
<!-- etc. -->
</documentslist>
-----> To get currency for a specific document
def webServiceResponse = "webservice#Response"
int index=2
def currency = context.expand('${'+webServiceResponse+'//*:document[#id="['+index+']"]//*:payment/#currency}')
-----> Result of this is "JPY"
<webserviceResponse>
<documentslist>
<document #id="1">
<payment #currency="USD" >
<amount>1250.00</amount>
</payment>
<refund>true</refund>
</document>
<document #id="2">
<payment #currency="JPY" >
<amount>150.00</amount>
</payment>
</document>
<document #id="3">
<payment #currency="EUR" >
<amount>1170.00</amount>
</payment>
<refund>false</refund>
</document>
<!-- etc. -->
</documentslist>
-------> To get a currency dependent on existence of a specific node
In this example we are looking the file from up to down and we are finding every refund nodes,
and taking currency value that is in the same block with the second time we see a refund node.
def webServiceResponse = "webservice#Response"
int index=2
def currrency= context.expand('${'+webServiceResponse+'(//*:refund)['+index+']//parent::*//*:payment/#currency}')
--------> Result for this is "EUR"
This one is that i cant take child value with the same way.
<webserviceResponse>
<documentslist>
<document>
<key>D_Computer</key>
<currency>USD</currency>
<amount>1250.00</amount>
<refund>true</refund>
</document>
<document>
<key>D_Keyboard</key>
<currency>JPY</currency>
<amount>150.00</amount>
</document>
<document>
<key>D_Monitor</key>
<currency>EUR</currency>
<amount>1170.00</amount>
<refund>false</refund>
</document>
<!-- etc. -->
</documentslist>
My problem with this one it doesn't have any attributes, has only values of the nodes. I know that it doesnt have an integer by the way but maybe i am doing wrong that i dont realize.
I want to get the amount value only dependent to the "key" nodes value which i am going to specify in the script.
result should show :150.00
Thank you for the very detailed and well written question.
You can use the below. Your problem is easy as there are no namespace in it.
Technique is same which you have dispalyed, its just that you need not to use # as its for attributes
def groovyUtils=new com.eviware.soapui.support.GroovyUtils(context)
def xml=groovyUtils.getXmlHolder("NameOfRequest#Response");
def currency=xml.getNodeValue("//*:documentslist/*:document[key='${key}']/*:amount");
log.info "Value of $key is " + currency
key="D_Monitor"
currency=xml.getNodeValue("//*:documentslist/*:document[key='${key}']/*:amount");
log.info "Value of $key is " + currency
Replace NameOfRequest with your Request's name
There is an alternative way too. I will post it as a separate answer so not to cause confusion. This one is still better than other one
There is an alternate way of doing things using Hashmap if the other answer is not working due to namespaces in your XML
Try this method
We are getting all values first by using getNodeValues and then since we have pair we are putting in hashmap.
Now you can retrieve anything.
def groovyUtils=new com.eviware.soapui.support.GroovyUtils(context)
def xml=groovyUtils.getXmlHolder("Request1#Response");
def keys=xml.getNodeValues("//*:documentslist/*:document/*:key")
def amounts=xml.getNodeValues("//*:documentslist/*:document/*:amount")
log.info keys.toString()
log.info amounts.toString()
HashMap h1=[:]
// Add the pair into hashmap and then retrieve
for(int i=0;i<keys.size();i++)
{
h1.put(keys[i],amounts[i])
}
def whichone="D_Computer"
log.info "Value for $whichone is " + h1.get(whichone)
Lets say you want to retrieve more than one value then you can use arrays.
i.e. take arrays as key,currency,amount,refund
so if you want to retrieve the refund for a key='Z' So using a for loop you can know that Z is present at 3 location in the array
then your refund should be refund[3]. Similarly currency[3] and amount[3]
Both the answers have their own relevance
I have a list of things, each of which might be a foo or a bar. I want to build some xml that looks like this:
<rdf:RDF>
<foo id="1">
<foo id="2">
<bar id="3">
</rdf:RDF>
So I have gotten this far:
MarkupBuilder xml = new MarkupBuilder(writer)
xml.'rdf:RDF' (nsmap) { }
But now I am stuck. How do i - within that xml.'rdf:RDF' (nsmap) { } closure - iterate over my list of stuff? How do i - within that iterator - spit out a foo or a bar element as applicable?
Its simpler as you may think. Include a loop in the xml closure and in turn include markup in the loop. This script ...
import groovy.xml.MarkupBuilder
things = ['foo','foo','bar']
writer = new StringWriter()
xml = new MarkupBuilder(writer)
xml.'rdf:RDF' {
things.eachWithIndex {thing,index ->
"$thing" id:index+1
}
}
println writer
... will produce following output:
<rdf:RDF>
<foo id='1' />
<foo id='2' />
<bar id='3' />
</rdf:RDF>
Here You go:
import groovy.xml.MarkupBuilder
import org.custommonkey.xmlunit.*
def data = [foo:1,bar:2,baz:3]
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
xml.'rdf:RDF' {
data.each { e ->
"$e.key"(id:e.value)
}
}
println writer
Ok.
The issue is that when a closure is run by a builder, it operates within the context of the builder. Now, it turns out that the builder context hides methods, but does not hide variables. This means that you can create a closure, assign it to a variable, and access it from within the builder.
NOw, I get the impression that this closure, too, runs in the context of the builder. But I suspect you can assign "this" to a variable outside the closures and then access that variable to get the context back.
It's ridiculous that you have to go to this sort of trouble. Im reminded of coding in Microsoft Access, back in the 90's. It works great, until the moment when you want to do something more than it was strictly designed to do.
I am having this strange problem of parsing an xml through XMLSlurper in groovy and it shows the size as 0. Cannot figure out why.
My xml file looks like:
<?xml version="1.0" encoding="iso-8859-1"?>
<sites>
<site name="OctUK">
<property name="warName">OctUKbuild-Deployable</property>
</site>
<site name="GbsJP">
<property name="warName">GbsJPbuild-Deployable</property>
</site>
</sites>
Code:
findSite("${project.GTA_BUILD_HOME}/platforms/pos/config/pos-sites.xml")
//Passed the path of the xml file to the method below:
GPathResult findSite(String sitesXml) {
xmlConfig = new XmlSlurper().parse(new File(sitesXml))
def siteGPath = xmlConfig.sites.site.findAll
// Check that a POS-sites.xml is valid
assert siteGPath.size() != 0, 'Error: no site found'
return(siteGPath)
}
The method fails with the error saying Error: no site found, because it is giving the result of siteGPath as 0. Not sure why it is giving the result as 0. It should have the size as 2.
Is there anything wrong I am doing. Any help is much appreciated. I am stuck at this point.
You don't need sites when looking at the xmlConfig object.
sites is the root node, so is implied, try:
assert xmlConfig.site.size() == 2
Also, xmlConfig.site is a instance of NodeChildren, but you seem to be declaring a return type of GPathResult
And I'm not sure what's missing from the end of your findAll call, as that should take a Closure, or empty parentheses