replace XmlSlurper tag with arbitrary XML - groovy

I am trying to replace specific XmlSlurper tags with arbitrary XML strings. The best way I have managed to come up with to do this is:
#!/usr/bin/env groovy
import groovy.xml.StreamingMarkupBuilder
def page=new XmlSlurper(new org.cyberneko.html.parsers.SAXParser()).parseText("""
<html>
<head></head>
<body>
<one attr1='val1'>asdf</one>
<two />
<replacemewithxml />
</body>
</html>
""".trim())
import groovy.xml.XmlUtil
def closure
closure={ bind,node->
if (node.name()=="REPLACEMEWITHXML") {
bind.mkp.yieldUnescaped "<replacementxml>sometext</replacementxml>"
} else {
bind."${node.name()}"(node.attributes()) {
mkp.yield node.text()
node.children().each { child->
closure(bind,child)
}
}
}
}
println XmlUtil.serialize(
new StreamingMarkupBuilder().bind { bind->
closure(bind,page)
}
)
However, the only problem is the text() element seems to capture all child text nodes, and thus I get:
<?xml version="1.0" encoding="UTF-8"?>
<HTML>asdf<HEAD/>
<BODY>asdf<ONE attr1="val1">asdf</ONE>
<TWO/>
<replacementxml>sometext</replacementxml>
</BODY>
</HTML>
Any ideas/help much appreciated.
Thank you!
Misha
p.s. Also, out of curiosity, if I change the above to the "Groovier" notation as follows, the groovy compiler thinks I am trying to access the ${node.name()} member of my test class. Is there a way to specify this is not the case while still not passing the actual builder object? Thank you! :)
def closure
closure={ node->
if (node.name()=="REPLACEMEWITHXML") {
mkp.yieldUnescaped "<replacementxml>sometext</replacementxml>"
} else {
"${node.name()}"(node.attributes()) {
mkp.yield node.text()
node.children().each { child->
closure(child)
}
}
}
}
println XmlUtil.serialize(
new StreamingMarkupBuilder().bind {
closure(page)
}
)

Ok here is what I came up with:
#!/usr/bin/env groovy
import groovy.xml.StreamingMarkupBuilder
import groovy.xml.XmlUtil
def printSlurper={page->
println XmlUtil.serialize(
new StreamingMarkupBuilder().bind { bind->
mkp.yield page
}
)
}
def saxParser=new org.cyberneko.html.parsers.SAXParser()
saxParser.setFeature('http://xml.org/sax/features/namespaces',false)
saxParser.setFeature("http://cyberneko.org/html/features/balance-tags/document-fragment",true)
def string="TEST"
def middleClosureHelper={ builder->
builder."${string}" {
mkp.yieldUnescaped "<inner>XML</inner>"
}
}
def middleClosure={
MiddleClosure {
middleClosureHelper(delegate)
}
}
def original=new XmlSlurper(saxParser).parseText("""
<original>
<middle>
</middle>
</original>
""")
original.depthFirst().find { it.name()=='MIDDLE' }.replaceNode { node->
mkp.yield middleClosure
}
printSlurper(original)
assert original.depthFirst().find { it.name()=='INNER' } == null
def modified=new XmlSlurper(saxParser).parseText(new StreamingMarkupBuilder().bind {mkp.yield original}.toString())
assert modified.depthFirst().find { it.name()=='INNER' } != null
You have to reload the slurper, but it works!
Misha

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.

how to create Gsp tag by Markupbuilder?

I want to create a gsp file like this:
but I fail to find how to write the code by markupbuilder.
my code like this:
MarkupBuilder mb = new groovy.xml.MarkupBuilder(strXml);
def builderA = new StreamingMarkupBuilder()
def gsp = builderA.bind{
html{
g.uploadForm(action:"saveDataItem"){
table{
f.with{
tr{
td{
"Test"
}
}
}
}
}
}
}
println XmlUtil.serialize(gsp)
It dos not work.
import groovy.xml.*
def mb = new StreamingMarkupBuilder()
def gsp = mb.bind {
html{
"g:uploadForm"(action:"saveDataItem"){
table{
tr{
td("Test")
}
}
}
}
}
println gsp.toString()

Calling method from within a Groovy DSL

I have a simple Groovy method that uses Groovy's MarkupBuilder to print HTML, very simplified version below:
void writeHtmlFile(<args>) {
def writer = new FileWriter(fileName.toFile())
def html = new MarkupBuilder(writer)
html.mkp.yieldUnescaped '<!DOCTYPE html>'
html.mkp.xmlDeclaration(version: "1.0", encoding: "utf-8")
html.html {
head { ... }
body(id: 'main') {
h1 "Report Title"
}
}
writer.flush()
writer.close()
}
This works well. Say I wanted to call a method after the h1 that does some calculations and adds more to the MarkupBuilder. How do I get the elements defined in the called method added to the MarkupBuilder? Here's something I tried that doesn't cause an exception, but also doesn't work (the resulting HTML has no <h2> element):
Closure testNested() {
println '---'
return { h2 "here's a subheading" }
}
// .... other stuff from above example not repeated ...
html.html {
head {...}
body(id: 'main') {
h1 "Report Title"
testNested()
}
I know I can easily do this inline. I'm trying to deepen my understanding of how Groovy uses closures and delegates in DSLs and clearly I'm missing something.
Consider the following code, which executes fine for me, using Groovy 2.4.5.
The builder pattern is a bit tricky because it can be viewed as hierarchical data and/or code, depending on your perspective. With practice, one can switch perspectives as necessary.
import groovy.xml.*
void testNested(def html) {
html.h2("here's a subheading from testNested")
}
void writeHtmlFile(def fileName) {
def writer = new FileWriter(fileName)
def html = new MarkupBuilder(writer)
html.mkp.yieldUnescaped '<!DOCTYPE html>'
html.mkp.xmlDeclaration(version: "1.0", encoding: "utf-8")
html.html {
body(id: 'main') {
h1 "Report Title"
testNested(html)
}
}
writer.flush()
writer.close()
}
writeHtmlFile("out.html")

XmlSlurper in Groovy Script --- Insert nodes to GpathResult using external closures

I have a problem with the below scenario:
-- I have a GPathResult "body" to which I want to append some more xml (nodes and children)
-- Some parts are common so I am trying to have them kept in an outer closure "commonNode" I can insert wherever I need
// some more code here to get body
def commonNode = {
return {
node2() {
child("childValue")
}
}
}
body.appendNode(
{
node1("value1")
commonNode()
node3("value3")
}
)
What I want to get after I would call XmlUtil.serialize(body) is this:
...
<body>
<node1>value</node1>
<node2>
<child>childValue</child>
</node2>
<node3>value3</node3>
<body>
...
however structure is missing from the result entirely, so I guess there is something wrong with the way I call the outer closure "commonNode()".
Hope someone has an answer. Let me know if you need further details.
This works:
import groovy.xml.*
def xml = '<body/>'
def body = new XmlSlurper().parseText( xml )
def commonNode = {
node2 {
child "childValue"
}
}
body.appendNode {
node1 "value1"
commonNode.delegate = delegate
commonNode()
node3 "value3"
}
println XmlUtil.serialize( body )

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>

Resources