I need to add # attribute to the root element of XML fragment in Groovy. I want to use XmlSlurper. How to do it? Adding elements is easy.
Run this in the Groovy console to verify that it works
import groovy.xml.StreamingMarkupBuilder
// the original XML
def input = "<foo><bar></bar></foo>"
// add attributeName="attributeValue" to the root
def root = new XmlSlurper().parseText(input)
root.#attributeName = 'attributeValue'
// get the modified XML and check that it worked
def outputBuilder = new StreamingMarkupBuilder()
String updatedXml = outputBuilder.bind{ mkp.yield root }
assert "<foo attributeName='attributeValue'><bar></bar></foo>" == updatedXml
adding an attribute is the same as reading it:
import groovy.xml.StreamingMarkupBuilder
def input = '''
<thing>
<more>
</more>
</thing>'''
def root = new XmlSlurper().parseText(input)
root.#stuff = 'new'
def outputBuilder = new StreamingMarkupBuilder()
String result = outputBuilder.bind{ mkp.yield root }
println result
will give you:
<thing stuff='new'><more></more></thing>
Related
I am using the Groovy MarkupBuilder to create some XML.
In my scenario I get an xml string which is essentially the body of the xml document and then I want to surround it with some other stuff.
Something like this....
def xmltext = '''<node><name short="yes">tim</name><fun>maybe</fun></node>'''
def body = new XmlSlurper(false,false).parseText( xmltext )
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
xml.mkp.xmlDeclaration version: '1.0', encoding: 'UTF-8'
xml.'cre:InputParamters'('xmlns:cre': 'http://xmlns.oracle.com/apps/ar/soaprovider/plsql/ar_invoice_api_pub/create_single_invoice/') {
xml.'cre:P_P_TRX_HEADER_TBL' {
xml.'cre:P_TRX_HEADER_TBL_ITEM' {
map 'cre:TRX_HEADER_ID', '',xml
map 'cre:TRX_DATE', requestPayload?.invoiceDate, xml
map 'cre:TRX_CURRENCY', requestPayload?.currencyCode, xml
map 'cre:TRX_CLASS', 'INV', xml
map 'cre:CUST_TRX_TYPE_ID', '1034', xml
map 'cre:BILL_TO_CUSTOMER_ID', '147055', xml
}
}
<<APPEND ELEMENTS FROM XML STRING HERE>>
}
private map (Object field, Object value, MarkupBuilder xml) {
if (value) {
xml."$field"(value)
}
}
return writer.toString()
Could someone help me with the bit 'APPEND ELEMENTS FROM XML STRING HERE' and how to get that the work for me?
thanks
Here is the changed script:
Note that you have used some variable to get the values. To demonstrate I have used fixed values below which you may be able to replace it.
import groovy.xml.*
def xmltext = '''<node><name short="yes">tim</name><fun>maybe</fun></node>'''
def builder = new StreamingMarkupBuilder()
builder.encoding = 'UTF-8'
def xml = builder.bind {
mkp.xmlDeclaration()
namespaces << [cre:'http://xmlns.oracle.com/apps/ar/soaprovider/plsql/ar_invoice_api_pub/create_single_invoice/']
cre.InputParamters{
cre.P_P_TRX_HEADER_TBL {
cre.P_TRX_HEADER_TBL_ITEM {
cre.TRX_HEADER_ID ('')
cre.TRX_DATE ('2017-02-17')
cre.TRX_CURRENCY( 'USD')
cre.TRX_CLASS('INV')
cre.CUST_TRX_TYPE_ID( '1034')
cre.BILL_TO_CUSTOMER_ID( '147055')
}
}
mkp.yieldUnescaped xmltext
}
}
println XmlUtil.serialize(xml)
You may try it quickly online Demo
Output
When parsing an attribute, the slurper sets an empty string when an attribute is not found.
For e.g., car.setOwner(node.#owner.text());
In the above code, if the owner attribute is not found, then the slurper sets a blank string ("").
In my case, I'd rather leave it as null than setting an empty string.
Is it possible to configure the Slurper not to do this?
You could do
car.setOwner(node.#owner.text() ?: null)
If we distinguish between configure and using the Meta Object Protocol (MOP), then we can state that it is not possible to configure XmlSlurper as you describe, but it is possible to use the MOP.
For configure, note the following:
def node = new XmlSlurper().parseText('<car>No Owner</car>' )
def attr = node.#owner
assert groovy.util.slurpersupport.Attributes == attr.getClass()
If you look at the code for Attributes.text() (in this case, Groovy 2.2.2), it is clear that this cannot be configured to return null.
For MOP, we can capture the original Attributes.text() method and then override it:
import groovy.util.slurpersupport.*
def originalText = Attributes.metaClass.getMetaMethod("text")
Attributes.metaClass.text = { ->
def result = originalText.invoke(delegate)
if (result.isEmpty()) {
result = null
}
result
}
// no owner
def node = new XmlSlurper().parseText('<car>No Owner</car>')
def attr = node.#owner
assert null == attr.text()
// with owner
node = new XmlSlurper().parseText('<car owner="codetojoy"></car>')
attr = node.#owner
assert "codetojoy" == attr.text()
I am using MarkupBuilder to generate xml, need to know how can I add NodeChild to a MarkupBuilder Object
my code
def fxml=new File("E:\\Projects\\dom.xml")
def xmltext=new XmlSlurper(false,false).parseText(fxml.text)
or
def xml=new XmlSlurper(false,false).parse("E:\\Projects\\dom.xml")
def abc = new groovy.xml.MarkupBuilder()
abc.product(name:"Dota"){
language("Java")
language("Groovy")
language("JavaScript")
domainsinfa{delegate.current.appendNode( xmltext)}
}
You can use StreamingMarkupBuilder to insert arbitrary nodes to the xml:
import groovy.xml.*
def xmltext = '''<node><name short="yes">tim</name><fun>maybe</fun></node>'''
def xml = new XmlSlurper( false, false ).parseText( xmltext )
def newxml = new StreamingMarkupBuilder().bind {
product(name:"Dota") {
language("Java")
language("Groovy")
language("JavaScript")
mkp.yield xml
}
}
println XmlUtil.serialize( newxml )
//Unable to add replaced nodes back to xml using xml slurper. Throws stackoverflow exception. Now idea how to do it
def xml = """<container>
<listofthings>
<thing id="100" name="foo"/>
</listofthings>
</container>"""
def root = new XmlSlurper().parseText(xml)
def fun = new ArrayList(root.listofthings.collect{it})
root.listofthings.thing.each {
it.replaceNode {}
}
root.listofthings.appendNode ( { thing(id:102, name:'baz') })
fun.each {
root.listofthings.appendNode it
}
def outputBuilder = new groovy.xml.StreamingMarkupBuilder()
String result = outputBuilder.bind { mkp.yield root }
print result
You call:
def fun = new ArrayList(root.listofthings.collect{it})
Which sets fun to be the node <listofthings> (and btw could be shortened to: def fun = root.listofthings)
Then on the line:
fun.each {
root.listofthings.appendNode it
}
You append this node to the node <listofthings>. This means your tree will be never ending (as you are attaching a node to itself), and hence the StackOverflowException
To get the code to run, you can change it to:
import groovy.xml.StreamingMarkupBuilder
def xml = """<container>
| <listofthings>
| <thing id="100" name="foo"/>
| </listofthings>
|</container>""".stripMargin()
def root = new XmlSlurper().parseText(xml)
root.listofthings.thing*.replaceNode {}
root.listofthings.appendNode {
thing( id:102, name:'baz' )
}
def outputBuilder = new StreamingMarkupBuilder()
String result = outputBuilder.bind { mkp.yield root }
print result
ie: get rid of the recursive node addition.
However, I'm not sure what you were trying to do with the recursive addition, so this probably doesn't do what you wanted to do... Can you explain more what result you wanted to see?
Edit
I managed to get XmlParser to do what I think you were trying to do?
def xml = """<container>
| <listofthings>
| <thing id="100" name="foo"/>
| </listofthings>
|</container>""".stripMargin()
def root = new XmlParser().parseText(xml)
def listofthings = root.find { it.name() == 'listofthings' }
def nodes = listofthings.findAll { it.name() == 'thing' }
listofthings.remove nodes
listofthings.appendNode( 'thing', [ id:102, name:'baz' ] )
nodes.each {
listofthings.appendNode( it.name(), it.attributes(), it.value() )
}
def writer = new StringWriter()
new XmlNodePrinter(new PrintWriter(writer)).print(root)
def result = writer.toString()
print result
That prints:
<container>
<listofthings>
<thing id="102" name="baz"/>
<thing id="100" name="foo"/>
</listofthings>
</container>
I've been trying to do some xml modifications with groovy's XML Slurper.
Basically, i'm going through the xml and looking for tags or attributes that have ? as the value and then replacing it with some value.
I've got it working for xml that doesn't have namespaces but once I include them things get wonky. For example, this:
String foo = "<xs:test xmlns:xs="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:foo="http://myschema/xmlschema" name='?'>
<foo:tag1>?</foo:tag1>
<foo:tag2>?</foo:tag2>
</xs:test>";
produces:
<Envelope/>
Here's the groovy code I'm using. This does appear to work when I am not using a namespace:
public def populateRequest(xmlString, params) {
def slurper = new XmlSlurper().parseText(xmlString)
//replace all tags with ?
def tagsToReplace = slurper.depthFirst().findAll{ foundTag ->
foundTag.text() == "?"
}.each { foundTag ->
foundTag.text = {webServiceOperation.parameters[foundTag.name()]}
foundTag.replaceNode{
"${foundTag.name()}"(webServiceOperation.parameters[foundTag.name()])
}
}
//replace all attributes with ?
def attributesToReplace = slurper.list().each{
it.attributes().each{ attributes ->
if(attributes.value == '?')
{
attributes.value = webServiceOperation.parameters[attributes.key]
}
}
}
new StreamingMarkupBuilder().bind { mkp.yield slurper }.toString()
}
from groovy documentation
def wsdl = '''
<definitions name="AgencyManagementService"
xmlns:ns1="http://www.example.org/NS1"
xmlns:ns2="http://www.example.org/NS2">
<ns1:message name="SomeRequest">
<ns1:part name="parameters" element="SomeReq" />
</ns1:message>
<ns2:message name="SomeRequest">
<ns2:part name="parameters" element="SomeReq" />
</ns2:message>
</definitions>
'''
def xml = new XmlSlurper().parseText(wsdl).declareNamespace(ns1: 'http://www.example.org/NS1', ns2: 'http://www.example.org/NS2')
println xml.'ns1:message'.'ns1:part'.size()
println xml.'ns2:message'.'ns2:part'.size()