Generating nested XML using MarkupBuilder or StreamingMarkupBuilder - groovy

I've got a grouped query that results in a list of clinics. Within the clinics are patients. And within the patients are prescriptions. I'm trying to output this structure using MarkupBuilder, but I can't seem to get the containment working.
What I'm getting is this:
<worklist>
<clinics>
<clinic id="1" name="Clinic 1"/>
<patient firstName="John" id="2" lastName="Doe"/>
<prescription id="4">
<prescriptionType/>
<duration/>
<drugName>Tums</drugName>
<route/>
<refills>0</refills>
</prescription>
<clinic id="2" name="Clinic 2"/>
<patient firstName="John" id="2" lastName="Doe"/>
<prescription id="2">
<prescriptionType>Formulary</prescriptionType>
<duration>duration</duration>
<drugName>Lipitor</drugName>
<route>route</route>
<refills>5</refills>
</prescription>
<patient firstName="Sylvia" id="4" lastName="Plath"/>
<prescription id="5">
<prescriptionType/>
<duration/>
<drugName>BandAids</drugName>
<route/>
<refills>0</refills>
</prescription>
</clinics>
</worklist>
Note that clinic element closes and does not contain the patients. And patient element closes and does not contain the prescriptions. This is incorrect. It should look like this:
<worklist>
<clinics>
<clinic id="1" name="Clinic 1">
<patient firstName="John" id="2" lastName="Doe">
<prescription id="4">
<prescriptionType/>
<duration/>
<drugName>Tums</drugName>
<route/>
<refills>0</refills>
</prescription>
</patient>
</clinic>
<clinic id="2" name="Clinic 2"/>
<patient firstName="John" id="2" lastName="Doe">
<prescription id="2">
<prescriptionType>Formulary</prescriptionType>
<duration>duration</duration>
<drugName>Lipitor</drugName>
<route>route</route>
<refills>5</refills>
</prescription>
</patient>
<patient firstName="Sylvia" id="4" lastName="Plath">
<prescription id="5">
<prescriptionType/>
<duration/>
<drugName>BandAids</drugName>
<route/>
<refills>0</refills>
</prescription>
</patient>
</clinic>
</clinics>
</worklist>
Here is my code:
import groovy.xml.StreamingMarkupBuilder
import groovy.xml.XmlUtil
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
#Path('/api/worklist')
class WorklistResource {
def addClinic = { idx, name ->
clinic(id:idx, name:name)
}
def addPatient = { idx, fname, lname ->
patient(id:idx, firstName:fname, lastName:lname)
}
#GET
#Produces(['application/xml','application/json'])
String getWorklistRepresentation() {
def groupedScripts = Prescription.createCriteria().list {
createAlias('clinic', 'clinicAlias')
createAlias('patient', 'patientAlias')
projections {
groupProperty "id"
groupProperty "clinicAlias.id"
groupProperty "patientAlias.id"
}
order "clinicAlias.name"
order "patientAlias.lastName"
order "patientAlias.firstName"
}
def curClinic = null
def curPatient = null
def worklist = new StreamingMarkupBuilder().bind {
worklist {
clinics {
groupedScripts.each { arr ->
def (rx, clinic, patient) = arr
def script = Prescription.get(rx)
def cl = Clinic.get(clinic)
def pat = Patient.get(patient)
if( curClinic != cl ) {
curClinic = cl
addClinic.delegate = delegate
addClinic(cl.id, cl.name)
}
if( curPatient != pat ) {
curPatient = pat
addPatient.delegate = delegate
addPatient(pat.id, pat.firstName, pat.lastName)
}
prescription(id:script.id) {
prescriptionType(script.prescriptionType)
duration(script.duration)
drugName(script.drugName)
route(script.route)
refills(script.refills)
}
}
}
}
}
def xml = XmlUtil.serialize(worklist)
xml
}
}
Obviously, I need to somehow keep the clinic closure open until I hit a new clinic or reach the end of the collection. And the same with patient closure. I'm just not sure how to do that.
Thanks in advance for any help. I need to get this working tonight.

You can use just the each method on lists. You have to take care on the following: every matched method call inside the builder.bind will call the respective method/variable; thus you will need different names. That's even better, because you don't make ambiguous naming. You can just structure the whole XML inside the eachs. It's a somewhat large, but this fills your XML the way you want:
UPDATE:
Since your model is reversed, i made the following:
// definition of the model and mock data
#Canonical class Prescription {
int id
String prescriptionType, duration, drugName, route
int refills
Clinic clinic
Patient patient
}
#Canonical class Clinic {
int id
String name
}
#Canonical class Patient {
int id
String firstName, lastName
}
def patient2 = new Patient(2, "John", "Doe")
def patient4 = new Patient(4, "Sylvia", "Plath")
def clinic1 = new Clinic(1, "Clinic 1")
def clinic2 = new Clinic(2, "Clinic 2")
def prescriptions = [
new Prescription(2, "Formulary", "duration", "Lipitor", "route", 5, clinic1, patient2),
new Prescription(4, null, null, "Tums", null, 0, clinic2, patient2),
new Prescription(5, null, null, "BandAids", null, 5, clinic2, patient4)
]
The best bet is to reverse the model so it matches the XML structure. You can easily reverse it using this snippet:
clins = prescriptions.inject([:].withDefault({ [:] })) { map, pres ->
map[ pres.clinic ] << [ (pres.patient) : pres ]
map
}
Now you build the XML according to the map structure:
builder = new groovy.xml.StreamingMarkupBuilder()
xml = builder.bind {
clinics {
clins.each { cliEntry -> cli = cliEntry.key
clinic(id: cli.id, name: cli.name) {
cliEntry.value.each { patEntry -> pat = patEntry.key
patient(id: pat.id, firstName: pat.firstName, lastName: pat.lastName){
patEntry.value.each { pres ->
prescription(id: pres.id) {
prescriptionType pres.prescriptionType
duration pres.duration
drugName pres.drugName
route pres.route
refills pres.refills
}
}
}
}
}
}
}
}
And the test:
assert xml.toString() == """<clinics><clinic id='1' name='Clinic 1'><patient id='2' firstName='John' lastName='Doe'><prescription id='2'><prescriptionType>Formulary</prescriptionType><duration>duration</duration><drugName>Lipitor</drugName><route>route</route><refills>5</refills></prescription></patient></clinic><clinic id='2' name='Clinic 2'><patient id='2' firstName='John' lastName='Doe'><prescription id='4'><prescriptionType/><duration/><drugName>Tums</drugName><route/><refills>0</refills></prescription></patient><patient id='4' firstName='Sylvia' lastName='Plath'><prescription id='4'><prescriptionType/><duration/><drugName>Tums</drugName><route/><refills>0</refills></prescription></patient></clinic></clinics>"""

I've got some ugliness in here with the global _index variable, termination conditions, and try..catch nonsense, but I've been at this for many hours and this code works:
import groovy.xml.StreamingMarkupBuilder
import groovy.xml.XmlUtil
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.Produces
#Path('/api/worklist')
class WorklistResource {
def _index = null // global index into groupedScripts collection
def addClinic = { scripts, index ->
addPatient.delegate = delegate
def (rxId, clinicId, patientId) = scripts[index]
def startingClinicId = clinicId
def cl = Clinic.get(clinicId)
clinic(id:cl.id, name:cl.name) {
while( clinicId == startingClinicId ) {
_index = index
index = addPatient(scripts, index)
if( index == -1 ) return -1
try {
(rxId, clinicId, patientId) = scripts[++index]
} catch(NullPointerException e) {
return -1
}
}
}
return _index
}
def addPatient = { scripts, index ->
def result = index
def (rxId, clinicId, patientId) = scripts[index]
def startingPatientId = patientId
def pat = Patient.get(patientId)
patient(id:pat.id, firstName:pat.firstName, lastName:pat.lastName) {
while( patientId == startingPatientId ) {
_index = index
def script = Prescription.get(rxId)
prescription(id:script.id) {
prescriptionType(script.prescriptionType)
duration(script.duration)
drugName(script.drugName)
route(script.route)
refills(script.refills)
}
try {
(rxId, clinicId, patientId) = scripts[++index]
} catch(NullPointerException e) {
return -1
}
}
}
return _index
}
#GET
#Produces(['application/xml','application/json'])
String getWorklistRepresentation() {
def groupedScripts = Prescription.createCriteria().list {
createAlias('clinic', 'clinicAlias')
createAlias('patient', 'patientAlias')
projections {
groupProperty "id"
groupProperty "clinicAlias.id"
groupProperty "patientAlias.id"
}
order "clinicAlias.name"
order "patientAlias.lastName"
order "patientAlias.firstName"
}
def finished = false
def worklist = new StreamingMarkupBuilder().bind {
worklist {
clinics {
_index = 0
addClinic.delegate = delegate
while( !finished && _index < groupedScripts.size() ) {
_index = addClinic(groupedScripts, _index)
if( _index == -1 ) finished = true
++_index
}
}
}
}
def xml = XmlUtil.serialize(worklist)
xml
}
}
The output is:
<worklist>
<clinics>
<clinic id="1" name="Clinic 1">
<patient firstName="Mariah" id="3" lastName="Brookstone">
<prescription id="1">
<prescriptionType>New</prescriptionType>
<duration>30 days</duration>
<drugName>Lisinopril 20mg Tablet</drugName>
<route>By Mouth</route>
<refills>2</refills>
</prescription>
</patient>
<patient firstName="John" id="2" lastName="Doe">
<prescription id="4">
<prescriptionType/>
<duration/>
<drugName>Tums</drugName>
<route/>
<refills>0</refills>
</prescription>
</patient>
</clinic>
<clinic id="2" name="Clinic 2">
<patient firstName="Mariah" id="3" lastName="Brookstone">
<prescription id="11">
<prescriptionType/>
<duration/>
<drugName>Milk Duds</drugName>
<route/>
<refills>0</refills>
</prescription>
<prescription id="12">
<prescriptionType/>
<duration/>
<drugName>Hershey</drugName>
<route/>
<refills>0</refills>
</prescription>
<prescription id="7">
<prescriptionType/>
<duration/>
<drugName>Skittles</drugName>
<route/>
<refills>0</refills>
</prescription>
</patient>
<patient firstName="John" id="2" lastName="Doe">
<prescription id="2">
<prescriptionType>Formulary</prescriptionType>
<duration>duration</duration>
<drugName>Lipitor</drugName>
<route>route</route>
<refills>5</refills>
</prescription>
</patient>
<patient firstName="Sylvia" id="4" lastName="Plath">
<prescription id="5">
<prescriptionType/>
<duration/>
<drugName>BandAids</drugName>
<route/>
<refills>0</refills>
</prescription>
</patient>
</clinic>
<clinic id="3" name="Clinic 3">
<patient firstName="Jane" id="12" lastName="Doe">
<prescription id="9">
<prescriptionType/>
<duration/>
<drugName>Reese's Pieces</drugName>
<route/>
<refills>0</refills>
</prescription>
</patient>
<patient firstName="Jill" id="13" lastName="Doe">
<prescription id="8">
<prescriptionType/>
<duration/>
<drugName>Toothpaste</drugName>
<route/>
<refills>0</refills>
</prescription>
</patient>
<patient firstName="Jim" id="11" lastName="Doe">
<prescription id="10">
<prescriptionType/>
<duration/>
<drugName>Hallmark</drugName>
<route/>
<refills>0</refills>
</prescription>
</patient>
<patient firstName="John" id="2" lastName="Doe">
<prescription id="6">
<prescriptionType/>
<duration/>
<drugName>Gauze</drugName>
<route/>
<refills>0</refills>
</prescription>
</patient>
</clinic>
</clinics>
</worklist>
That's (almost) exactly what I was after.

Related

search filter and listview in nativescript with angular

I am new to nativescript!
I am trying to implement a search filter with listview in nativescript-angular, but I am not getting result filter from listview. My HTML component files' code follows.
Here I take searchbar component and listview for showing on screen (home.html):
<ActionBar title="Search" class="action-bar">
<SearchBar hint="Enter search term here"
[text]="searchTerm" (textChange)="onTextChanged($event)"
(clear)="onClear($event)" (submit)="onSubmit($event)" >
</SearchBar>
</ActionBar>
<StackLayout backgroundColor="#66cdaa" padding="5">
<!-- <Label text="Select country"></Label> -->
<listView [items]="dataItems"
suggestMode="Suggest" displayMode="Tokens" backgroundColor="yellow" hint="serch suggestion">
<SuggestionView tkAutoCompleteSuggestionView>
<ng-template tkSuggestionItemTemplate let-item="item">
<StackLayout orientation="vertical" padding="10">
<Label [text]="item.text"></Label>
</StackLayout>
</ng-template>
</SuggestionView>
</listView>
</StackLayout>
My home.component.ts file code:
import { Component, ViewChild,OnInit } from "#angular/core";
import { ObservableArray } from "tns-core-modules/data/observable-array";
import { TokenModel, AutoCompleteCompletionMode, AutoCompleteDisplayMode, AutoCompleteSuggestMode } from "nativescript-ui-autocomplete";
import { SearchBar } from "tns-core-modules/ui/search-bar";
export class BrowseComponent {
data = [];
name="countries"
searchPhrase: string;
private _items: ObservableArray<TokenModel>;
private countries = ["Australia", "Albania", "Austria", "Argentina", Maldives",
"Bulgaria", "Belgium", "Cyprus", "Italy", "Japan", "Denmark", "Finland",
"France", "Germany", "Greece", "Hungary", "Ireland", "Saudi Arabia",
"Senegal","Serbia","Seychelles","Sierra Leone","Singapore","Slovakia",
"Slovenia","South Africa","South Korea","Spain","Sri Lanka",
"St Kitts & Nevis","St Lucia","St Vincent","St Lucia","Sudan",
"Suriname", "Swaziland", "Sweden", "Switzerland", "Syria", "Taiwan",
"Tajikistan","Tanzania","Thailand","Timor","Togo","Tonga",
"Trinidad & Tobago","Tunisia","Turkey","Turkmenistan",
"Turks & Caicos","Uganda","Ukraine","United Arab Emirates",
"United Kingdom","Uruguay","Uzbekistan","Venezuela","Vietnam",
"Virgin Islands (US)","Yemen","Zambia","Zimbabwe"];
Here, I take country array and I am trying to filter this country list from html but I am not getting how to implement:
constructor() {
this.initDataItems();
// Use the component constructor to inject providers.
}
These are the functions from searchbar:
onSumit(args) {
const searchBar = args.object as SearchBar;
console.log(`Searching for ${searchBar.text}`);
}
onTextChanged(dataItem) {
const searchBar = dataItem as SearchBar;
console.log(`Input changed! New value: ${dataItem}`);
}
onClear(args) {
const searchBar = args.object as SearchBar;
console.log(`Clear event raised`);
}
get dataItems(): ObservableArray<TokenModel> {
return this._items;
}
Here this is observable Array I take:
private initDataItems() {
this._items = new ObservableArray<TokenModel>();
for (let i = 0; i < this.countries.length; i++) {
this._items.push(new TokenModel(this.countries[i], undefined));
}
ngOnInit(): void {
// Init your component properties here.
}
}
GlobalCompanyArrList: Array<Company>;
onTextChanged(dataItem) {
const searchBar = dataItem as SearchBar;
const searchValue = searchBar.text.toLowerCase();
let arrayItems=this.GlobalCompanyArrList; //Set Main countries Arraylist data to Local Arraylist for Searching
this.companylist_arr=new Array();
if (searchValue !== "") {
for (let i = 0; i < arrayItems.length; i++) {
if (arrayItems[i].NAME.toLowerCase().indexOf(searchValue) !== -1) {
this.companylist_arr.push(arrayItems[i]);
}
}
}
console.log("SearchBar text changed! New value: " + searchBar.text);
}

Retrieve a whole node as a string with XmlSlurper

I want to retrieve the elements of <logs> as array of String and I am trying the following:
import groovy.util.XmlSlurper
def payload = '''<logs>
<log>
<text>LOG 1</text>
<timestamp>2017-05-18T16:20:00.000</timestamp>
</log>
<log>
<text>LOG 2</text>
<timestamp>2017-05-18T16:20:00.000</timestamp>
</log>
</logs>'''
def logs = new XmlSlurper().parseText(payload)
def result = []
logs.log.each{
result.add(it)
}
result
​However, I am getting the values, but I would like to get the whole node as text, more or less this:
[<log>
<text>LOG 1</text>
<timestamp>2017-05-18T16:20:00.000</timestamp>
</log>,
<log>
<text>LOG 2</text>
<timestamp>2017-05-18T16:20:00.000</timestamp>
</log>]
Is this at all possible with XmlSlurper or should I use some String operations?
You can use XmlUtil but have to remove the xml declaration:
import groovy.util.XmlSlurper
import groovy.xml.XmlUtil
def payload = '''<logs>
<log>
<text>LOG 1</text>
<timestamp>2017-05-18T16:20:00.000</timestamp>
</log>
<log>
<text>LOG 2</text>
<timestamp>2017-05-18T16:20:00.000</timestamp>
</log>
</logs>'''
def logs = new XmlSlurper().parseText(payload)
def result = logs.log.collect {
XmlUtil.serialize(it).replaceAll(/<.xml.*?>/,"")
}
println result
Try this:
def payload = '''<logs>
<log>
<text>LOG 1</text>
<timestamp>2017-05-18T16:20:00.000</timestamp>
</log>
<log>
<text>LOG 2</text>
<timestamp>2017-05-18T16:20:00.000</timestamp>
</log>
</logs>'''
def logs = new XmlSlurper().parseText(payload)
def result = []
logs.log.each{
result.add( "<log> <text>" + it?.'text'.text() + "</text> <timestamp> " + it?.'timestamp'.text() + "</timestamp> </log>")
}
return result
​
You can go with:
def payload = '''<logs>
<log>
<text>LOG 1</text>
<timestamp>2017-05-18T16:20:00.000</timestamp>
</log>
<log>
<text>LOG 2</text>
<timestamp>2017-05-18T16:20:00.000</timestamp>
</log>
</logs>'''
def logs = new XmlParser().parseText(payload)
def result = logs.log.collect {
def sw = new StringWriter()
def pw = new PrintWriter(sw)
new XmlNodePrinter(pw).print(it)
sw.toString().replaceAll('\\s', '')
}

XML combine documents

I am working on Boomi interface and I need to combine individual xml documents in to single output documents . The standard combine document step is not working properly.
All xml documents are of same structure .
First Document
<?xml version='1.0' encoding='UTF-8'?>
<EMPLEADOS>
<EMPLEADO TIPO="A" NUMERO="123">
<PROCESO PERIODO="201603" TT="MN" PAC="9999" />
<SECCION ID="ETACIV">
<CAMPO ID="ETA_ETCNOM" SEC=" " FECHA=" ">abc</CAMPO>
</SECCION>
</EMPLEADO>
</EMPLEADOS>
Second document
<?xml version='1.0' encoding='UTF-8'?>
<EMPLEADOS>
<EMPLEADO TIPO="A" NUMERO="123">
<PROCESO PERIODO="201603" TT="MN" PAC="9999" />
<SECCION ID="SADMIN ">
<CAMPO ID="SAD_SADESO" SEC=" " FECHA="01/03/2015">01/03/2015</CAMPO>
</SECCION>
</EMPLEADO>
</EMPLEADOS>
Third document
<?xml version='1.0' encoding='UTF-8'?>
<EMPLEADOS>
<EMPLEADO TIPO="A" NUMERO="123">
<PROCESO PERIODO="201603" TT="MN" PAC="9999" />
<SECCION ID="SADMIN ">
<CAMPO ID="SAD_SADESO" SEC=" " FECHA="01/06/2015">01/06/2015</CAMPO>
</SECCION>
</EMPLEADO>
</EMPLEADOS>
Expected output
<?xml version='1.0' encoding='UTF-8'?>
<EMPLEADOS>
<EMPLEADO TIPO="A" NUMERO="123">
<PROCESO PERIODO="201603" TT="MN" PAC="9999" />
<SECCION ID="ETACIV">
<CAMPO ID="ETA_ETCNOM" SEC=" " FECHA=" ">abc</CAMPO>
</SECCION>
<SECCION ID="SADMIN ">
<CAMPO ID="SAD_SADESO" SEC=" " FECHA="01/03/2015">01/03/2015</CAMPO>
<CAMPO ID="SAD_SADESO" SEC=" " FECHA="01/06/2015">01/06/2015</CAMPO>
</SECCION>
</EMPLEADO>
</EMPLEADOS>
The merging on elements are the same as attributes ? Technically , I need all the documents to be merged on ID attribute of CAMPO .
Any help greatly appreciated .
Thanks
Nag
I tried the below code ; getting Premature end of file. error.
import java.util.Properties;
import java.io.InputStream;
import org.jdom.input.SAXBuilder;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.xpath.XPath;
import org.jdom.output.XMLOutputter;
import groovy.util.slurpersupport.GPathResult;
import groovy.xml.StreamingMarkupBuilder;
for( int i = 0; i < dataContext.getDataCount(); i++ ) {
InputStream is = dataContext.getStream(i);
Properties props = dataContext.getProperties(i);
def xs = new XmlSlurper()
def employee = xs.parse(is);
String Encod = "UTF-8" ;
HashMap<String, GPathResult> CampoMap = new HashMap<String, GPathResult>()
employee.EMPLEADOS.EMPLEADO.PROCESO.SECCION.CAMPO.each {
CampoMap["${it.#ID}"] = it
}
new StreamingMarkupBuilder().bind {
mkp.xmlDeclaration(["version":"1.0", "encoding":"UTF-8"]);
EMPLEADOS {
EMPLEADO.PROCESO.SECCION.each {
if (CampoMap["${it.#ID}"] != null) {
it.appendNode(CampoMap["${it.#id}"].sites)
}
out << it
}
}
} .writeTo(is.newWriter(Encod))
}
dataContext.storeStream(is, props);
The new code is
import groovy.util.XmlParser
import groovy.xml.MarkupBuilder
def parser = new XmlParser()
def writer = new StringWriter()
def builder = new MarkupBuilder(writer)
for( int i = 0; i < dataContext.getDataCount(); i++ ) {
InputStream is = dataContext.getStream(i);
Properties props = dataContext.getProperties(i);
def mergedDocument = (0..<dataContext.dataCount)
.collect { XmlParser.parse(dataContext.getStream(it)) }
.inject { nodeA, nodeB -> merge(nodeA, nodeB) }
builder.mkp.xmlDeclaration(version:'1.0', encoding:'UTF-8')
builder.EMPLEADOS {
doc1.EMPLEADO.each { empleado ->
EMPLEADO(empleado.attributes()) {
empleado.PROCESO.each { proceso ->
PROCESO(proceso.attributes())
}
empleado.SECCION.each { seccion ->
SECCION(seccion.attributes()) {
seccion.CAMPO.each { campo ->
CAMPO(campo.attributes(), campo.value().head())
}
}
}
}
}
}
is = mergedDocument ;
}
/*
* Category to simplify XML node comparisons.
* Basically, two Nodes are equal if their attributes are the same.
*/
// class NodeCategory {
// static boolean equals(Node me, Node other) {
// me.attributes() == other.attributes()
// }
// static boolean isCase(List<Node> nodes, Node other) {
// nodes.find { it == other } != null
// }
//}
/*
* Merges document b into document a.
* WARNING: This method is destructive; it modifies document a
* #Returns a, for convenience
*/
def merge(a, b) {
// use(NodeCategory) {
b.EMPLEADO.each { empleado ->
def existingEmpleado = a.EMPLEADO.find {
it == empleado
}
if(existingEmpleado) {
// Empleado already exists, must merge differences.
// Add any missing PROCESO nodes.
empleado.PROCESO
.findAll { !(it in existingEmpleado.PROCESO) }
.with {
delegate.each { existingEmpleado.append(it) }
}
// Add any missing SECCION nodes.
empleado.SECCION
.findAll { !(it in existingEmpleado.SECCION) }
.with {
delegate.each { existingEmpleado.append(it) }
}
// Add any missing CAMPO nodes.
empleado.SECCION.each { seccion ->
existingEmpleado.SECCION
.find { it == seccion }
.with {
seccion.CAMPO
.findAll { !(it in delegate.CAMPO) }
.each { delegate.append(it) }
}
}
} else {
// Empleado does not exist, go ahead and add it as-is.
a.append(empleado)
}
}
// }
return a
}
First, I should mention that a generic method for combining XML documents is impossible because the merging process is contextual. How XML nodes are merged is dependent on what the nodes mean. A computer is incapable of figuring out the meaning on your data, so you as the programmer have to provide the instructions. Having said that, here's how to merge YOUR XML documents.
import groovy.util.XmlParser
import groovy.xml.MarkupBuilder
def parser = new XmlParser()
def writer = new StringWriter()
def builder = new MarkupBuilder(writer)
def doc1 = parser.parseText('''<?xml version='1.0' encoding='UTF-8'?>
<EMPLEADOS>
<EMPLEADO TIPO="A" NUMERO="123">
<PROCESO PERIODO="201603" TT="MN" PAC="9999" />
<SECCION ID="ETACIV">
<CAMPO ID="ETA_ETCNOM" SEC=" " FECHA=" ">abc</CAMPO>
</SECCION>
</EMPLEADO>
</EMPLEADOS>''')
def doc2 = parser.parseText('''<?xml version='1.0' encoding='UTF-8'?>
<EMPLEADOS>
<EMPLEADO TIPO="A" NUMERO="123">
<PROCESO PERIODO="201603" TT="MN" PAC="9999" />
<SECCION ID="SADMIN ">
<CAMPO ID="SAD_SADESO" SEC=" " FECHA="01/03/2015">01/03/2015</CAMPO>
</SECCION>
</EMPLEADO>
</EMPLEADOS>''')
def doc3 = parser.parseText('''<?xml version='1.0' encoding='UTF-8'?>
<EMPLEADOS>
<EMPLEADO TIPO="A" NUMERO="123">
<PROCESO PERIODO="201603" TT="MN" PAC="9999" />
<SECCION ID="SADMIN ">
<CAMPO ID="SAD_SADESO" SEC=" " FECHA="01/06/2015">01/06/2015</CAMPO>
</SECCION>
</EMPLEADO>
</EMPLEADOS>''')
merge(doc1, doc2)
merge(doc1, doc3)
builder.mkp.xmlDeclaration(version:'1.0', encoding:'UTF-8')
builder.EMPLEADOS {
doc1.EMPLEADO.each { empleado ->
EMPLEADO(empleado.attributes()) {
empleado.PROCESO.each { proceso ->
PROCESO(proceso.attributes())
}
empleado.SECCION.each { seccion ->
SECCION(seccion.attributes()) {
seccion.CAMPO.each { campo ->
CAMPO(campo.attributes(), campo.value().head())
}
}
}
}
}
}
println writer
/*
* Category to simplify XML node comparisons.
* Basically, two Nodes are equal if their attributes are the same.
*/
class NodeCategory {
static boolean equals(Node me, Node other) {
me.attributes() == other.attributes()
}
static boolean isCase(List<Node> nodes, Node other) {
nodes.find { it == other } != null
}
}
/*
* Merges document b into document a.
* WARNING: This method is destructive; it modifies document a
* #Returns a, for convenience
*/
def merge(a, b) {
use(NodeCategory) {
b.EMPLEADO.each { empleado ->
def existingEmpleado = a.EMPLEADO.find {
it == empleado
}
if(existingEmpleado) {
// Empleado already exists, must merge differences.
// Add any missing PROCESO nodes.
empleado.PROCESO
.findAll { !(it in existingEmpleado.PROCESO) }
.with {
delegate.each { existingEmpleado.append(it) }
}
// Add any missing SECCION nodes.
empleado.SECCION
.findAll { !(it in existingEmpleado.SECCION) }
.with {
delegate.each { existingEmpleado.append(it) }
}
// Add any missing CAMPO nodes.
empleado.SECCION.each { seccion ->
existingEmpleado.SECCION
.find { it == seccion }
.with {
seccion.CAMPO
.findAll { !(it in delegate.CAMPO) }
.each { delegate.append(it) }
}
}
} else {
// Empleado does not exist, go ahead and add it as-is.
a.append(empleado)
}
}
}
return a
}
The process goes like this:
The merge(Node a, Node b) method traverses through the nodes, handling each case so that a ends up being the combination of both documents (node trees). It's based on figuring out whether a node in b is already in a. If not, the node is added as-is. Otherwise, merge the changes a accordingly. Yes, this method is butt ugly and was a real PITA to write. Please, for the sake of sound programming, refactor the beast.
Finally, a MarkupDocumentBuilder is used to process the final node tree and produce a serialized XML document.
You may notice there's a Groovy category involved. It's used to simplify the Node comparisons.
Addendum - Using input streams
You can invoke the same process using InputStreams as the source of XML documents. It would go something like this:
def parser = new XmlParser()
def mergedDocument = (0..<dataContext.dataCount)
.collect { parser.parse(dataContext.getStream(it) }
.inject { nodeA, nodeB -> merge(nodeA, nodeB) }
Then, you can process mergedDocument with the MarkupBuilder.

Groovy: Accessing Closure Object's elements

I am new to Groovy, and was wondering:
If I define a object like this:
def buildParentXML(){
def parentXMLElement = {
ParentElement {
CreationDate(new Date())
out << buildChildXML()
ChildElementFK(buildChildXML().childElement.ChildPK) //Something like this
}
}
}
def buildChildXML() {
def childElement {
ChildPK("12345679")
Operator("Don't Know")
}
}
How would I access the value of Element1 or Element2?
I tried
println obj.RootElement.Element1
println obj[RootElement].[Element1]
println obj['RootElement'].['Element1']
Simple Example
<SavePolicy>
<Segment>
<IssueState>AK</IssueState>
<OptionCode>ADD</OptionCode>
<SegmentStatus>Aive</SegmentStatus>
<ApplicationReceivedDate>09/17/2013</ApplicationReceivedDate>
<ApplicationSignedDate>09/17/2013</ApplicationSignedDate>
<CreationDate>09/17/2013</CreationDate>
<EffeiveDate>09/17/2013</EffeiveDate>
<IssueDate>09/17/2013</IssueDate>
<TerminationDate>09/17/2013</TerminationDate>
<RateSeriesDate>09/17/2013</RateSeriesDate>
</Segment>
<Life>
<FaceAmount>250.00</FaceAmount>
</Life>
Will Be converted into
<?xml version="1.0" encoding="UTF-8"?>
<SEGRequestVO>
<Service>Policy</Service>
<Operation>submit</Operation>
<Operator>N/A</Operator>
<IgnoreEditWarningsNF/>
<RequestParameters>
<SubmissionType>SaveIt</SubmissionType>
<ContraNumber/>
<SegmentVO>
<IssueState>AK</IssueState>
<OptionCode>DD</OptionCode>
<SegmentStatus>Aive</SegmentStatus>
<ApplicationReceivedDate>09/17/2013</ApplicationReceivedDate>
<ApplicationSignedDate>09/17/2013</ApplicationSignedDate>
<CreationDate>09/17/2013</CreationDate>
<EffeiveDate>09/17/2013</EffeiveDate>
<IssueDate>09/17/2013</IssueDate>
<TerminationDate>09/17/2013</TerminationDate>
<RateSeriesDate>09/17/2013</RateSeriesDate>
<ContraNumber/>
<ProduStruureFK>01</ProduStruureFK>
<LifeVO>
<FaceAmount>250.00</FaceAmount>
<LifePK>-123464646</LifePK>
<SegmentFK/>
</LifeVO></SegmentVO>
</RequestParameters>
</SEGRequestVO>
Right, I took a wild guess...is this what you mean?
import groovy.xml.*
def buildChildXML = {
ChildPK("12345679")
Operator("Don't Know")
return "12345679"
}
def buildParentXML = {
ParentElement {
CreationDate(new Date())
def pk = buildChildXML()
ChildElementFK( pk )
}
}
println XmlUtil.serialize( new StreamingMarkupBuilder().bind { it ->
buildParentXML.delegate = it
buildChildXML.delegate = it
buildParentXML()
} )
That prints:
<?xml version="1.0" encoding="UTF-8"?><ParentElement>
<CreationDate>Mon Sep 16 17:02:42 BST 2013</CreationDate>
<ChildPK>12345679</ChildPK>
<Operator>Don't Know</Operator>
<ChildElementFK>12345679</ChildElementFK>
</ParentElement>

Change XML tag name

I want to transform an XML document which I have parsed with XmlSlurper. The (identical) XML tag names should be replaced with the value of the id attribute; all other attributes should be dropped. Starting from this code:
def xml = """<tag id="root">
| <tag id="foo" other="blah" more="meh">
| <tag id="bar" other="huh"/>
| </tag>
|</tag>""".stripMargin()
def root = new XmlSlurper().parseText(xml)
// Some magic here.
println groovy.xml.XmlUtil.serialize(root)
I want to get the following:
<root>
<foo>
<bar/>
</foo>
</root>
(I write test assertions on the XML, and want to simplify the structure for them.) I've read Updating XML with XmlSlurper and searched around, but found no way with replaceNode() or replaceBody() to exchange a node while keeping its children.
Adding the 'magic' in to the code in the question gives:
def xml = """<tag id="root">
| <tag id="foo" other="blah" more="meh">
| <tag id="bar" other="huh"/>
| </tag>
|</tag>""".stripMargin()
def root = new XmlSlurper().parseText(xml)
root.breadthFirst().each { n ->
n.replaceNode {
"${n.#id}"( n.children() )
}
}
println groovy.xml.XmlUtil.serialize(root)
Which prints:
<?xml version="1.0" encoding="UTF-8"?><root>
<foo>
<bar/>
</foo>
</root>
HOWEVER, this will drop any content in the nodes. To maintain content, we would probably need to use recursion and XmlParser to generate a new doc from the existing one... I'll have a think
More general solution
I think this is more generalised:
import groovy.xml.*
def xml = """<tag id="root">
| <tag id="foo" other="blah" more="meh">
| <tag id="bar" other="huh">
| something
| </tag>
| <tag id="bar" other="huh">
| something else
| </tag>
| <noid>woo</noid>
| </tag>
|</tag>""".stripMargin()
def root = new XmlParser().parseText( xml )
def munge( builder, node ) {
if( node instanceof Node && node.children() ) {
builder."${node.#id ?: node.name()}" {
node.children().each {
munge( builder, it )
}
}
}
else {
if( node instanceof Node ) {
"${node.#id ?: node.name()}"()
}
else {
builder.mkp.yield node
}
}
}
def w = new StringWriter()
def builder = new MarkupBuilder( w )
munge( builder, root )
println XmlUtil.serialize( w.toString() )
And prints:
<?xml version="1.0" encoding="UTF-8"?><root>
<foo>
<bar>something</bar>
<bar>something else</bar>
<noid>woo</noid>
</foo>
</root>
Now passes through nodes with no (or empty) id attributes

Resources