Groovy Xml Parsing with namespaces - groovy

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()

Related

passing in args to a Closure for a StreamingMarkupBuilder

Groovy 2.4, Spring 5.3.13
Not having much luck using StreamingMarkupBuilder to create some XML, serialize it and print it
public void createMsgToStreamOut( String strCreatedAt, String strEntity, String strIdNum, String strEvent) {
def streamBuilder = new StreamingMarkupBuilder();
streamBuilder.encoding = "UTF-8"
def xml = streamBuilder.bind{ strCreatedAt, strEntity, strIdNum, strEvent ->
>> some magic goes here
}
def xmlStr = XmlUtil.serialize( xml)
println xmlStr;
}
createMsgToStreamOut( "2022-09-10T12:13:14.567", "Matter", "907856", "create");
should give
<?xml version="1.0" encoding="UTF-8"?>
<message>
<timestamp>2022-09-10T12:13:14.567</timestamp>
<entity>Matter</entity>
<number>907856</number>
<event>create</event>
</message>
next step is to stream the output to a Kafka producer.
The magic you're looking for looks like that, I suppose:
def xml = streamBuilder.bind {
message {
timestamp(strCreatedAt)
entity(strEntity)
number(strIdNum)
event(strEvent)
}
}
Here is the fully working script:
import groovy.xml.*
createMsgToStreamOut( "2022-09-10T12:13:14.567", "Matter", "907856", "create");
void createMsgToStreamOut(String strCreatedAt, String strEntity, String strIdNum, String strEvent) {
def streamBuilder = new StreamingMarkupBuilder();
streamBuilder.encoding = "UTF-8"
def xml = streamBuilder.bind {
message {
timestamp(strCreatedAt)
entity(strEntity)
number(strIdNum)
event(strEvent)
}
}
def xmlStr = XmlUtil.serialize( xml)
println xmlStr;
}
Let me know if it helps.

Groovy map constructor keys to different variable names

I have JSON looking like:
{
"days": [
{
"mintemp": "21.8"
}
]
}
With Groovy, I parse it like this:
class WeatherRow {
String mintemp
}
def file = new File("data.json")
def slurper = new JsonSlurper().parse(file)
def days = slurper.days
def firstRow = days[0] as WeatherRow
println firstRow.mintemp
But actually, I would like to name my instance variable something like minTemp (or even something completely random, like numberOfPonies). Is there a way in Groovy to map a member of a map passed to a constructor to something else?
To clarify, I was looking for something along the lines of #XmlElement(name="mintemp"), but could not easily find it:
class WeatherRow {
#Element(name="mintemp")
String minTemp
}
Create a constructor that takes a map.
Runnable example:
import groovy.json.JsonSlurper
def testJsonStr = '''
{"days": [
{ "mintemp": "21.8" }
]}'''
class WeatherRow {
String minTemp
WeatherRow(map) {
println "Got called with constructor that takes a map: $map"
minTemp = map.mintemp
}
}
def slurper = new JsonSlurper().parseText(testJsonStr)
def days = slurper.days
def firstRow = days[0] as WeatherRow
println firstRow.minTemp
Result:
Got called with constructor that takes a map: [mintemp:21.8]
21.8
(of course you'd remove the println line, it's just there for the demo)
You can achieve this using annotation and simple custom annotation processor like this:
1. Create a Custom Annotation Class
#Retention(RetentionPolicy.RUNTIME)
#interface JsonDeserializer {
String[] names() default []
}
2. Annotate your instance fields with the custom annotation
class WeatherRow{
#JsonDeserializer(names = ["mintemp"])
String mintemp;
#JsonDeserializer(names = ["mintemp"])
String minTemp;
#JsonDeserializer(names = ["mintemp"])
String numberOfPonies;
}
3. Add custom json deserializer method using annotation processing:
static WeatherRow fromJson(def jsonObject){
WeatherRow weatherRow = new WeatherRow();
try{
weatherRow = new WeatherRow(jsonObject);
}catch(MissingPropertyException ex){
//swallow missing property exception.
}
WeatherRow.class.getDeclaredFields().each{
def jsonDeserializer = it.getDeclaredAnnotations()?.find{it.annotationType() == JsonDeserializer}
def fieldNames = [];
fieldNames << it.name;
if(jsonDeserializer){
fieldNames.addAll(jsonDeserializer.names());
fieldNames.each{i ->
if(jsonObject."$i")//TODO: if field type is not String type custom parsing here.
weatherRow."${it.name}" = jsonObject."$i";
}
}
};
return weatherRow;
}
Example:
def testJsonStr = '''
{
"days": [
{
"mintemp": "21.8"
}
]
}'''
def parsedWeatherRows = new JsonSlurper().parseText(testJsonStr);
assert WeatherRow.fromJson(parsedWeatherRows.days[0]).mintemp == "21.8"
assert WeatherRow.fromJson(parsedWeatherRows.days[0]).minTemp == "21.8"
assert WeatherRow.fromJson(parsedWeatherRows.days[0]).numberOfPonies == "21.8"
Check the full working code at groovyConsole.

Groovy MarkupBuilder : How to create markup and append string

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

How to add a node to groovy MarkupBuilder?

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 )

How to add XML attribute using Groovy?

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>

Resources