Calling method from within a Groovy DSL - groovy

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

Related

Groovy TemplateEngine - function for markup creation

I'm a JS programmer trying to write some Groovy code (with no Java background at all). I need to write some templates using Groovy, so I created a function in order to avoid repetition. My goal is to be able to pass html objects to the function (for example: p(), div(), span() and so on), but is isn't working as I expected:
The function
void addon ( addonType, mainHolder, content ) {
div( class: "addon " + addonType ) {
div( class: "addon-main" ) {
div( class: "addon-main-holder" ) {
yieldUnescaped mainHolder
}
}
div( class: "addon-content" ) {
yieldUnescaped content
}
}
}
Doesn't works:
[...]
body {
addon( 'addon-warning', p('Lorem Ipsum'), p('Dolor sit amet consectetur') )
}
[...]
Works:
[...]
body {
addon( 'addon-warning', '<p>Lorem Ipsum</p>', '<p>Dolor sit amet consectetur</p>') )
}
[...]
I tried some variations, using yield rather than yieldUnescaped, but no success. Is it possible? Any help would be much appreciated.
Assuming you want to pass in further DSL-based tags into your addon function, I'd pass closure to that function instead. This is an simplified, self containing version (which makes it bit harder to read, as the template is in a string; take a look at the XXX comments):
import groovy.text.markup.*
def config = new TemplateConfiguration()
def engine = new MarkupTemplateEngine(config)
def template = engine.createTemplate('''
html {
body {
addon("Hello World", { // XXX: pass a closure
p("Lorem Ipsum")
p("Lorem Ipsum")
p("Lorem Ipsum")
})
}
}
void addon(title, content) {
h1(title)
div {
content() // XXX call the closure
}
}
''')
Writable output = template.make([:])
println output
// => <html><body><h1>Hello World</h1><div><p>Lorem Ipsum</p><p>Lorem Ipsum</p><p>Lorem Ipsum</p></div></body></html>

Use method to style table in groovy

I'm building a html email with groovy and I want to style my elements using a method. I want to use the method createTableCSS to set style to my table. But it doesn't work. The styling is coming outside the table tag.
String createTableCSS(String width, String border, String cellSpacing, String background, String classes ){
return "'width':'"+width+"'"
}
def responseDoc = job.addDocument("ECommerce_test.html"){out ->
def xmlWriter = new OutputStreamWriter(out)
MarkupBuilder html = new MarkupBuilder(xmlWriter)
html.doubleQuotes = true
html.expandEmptyElements = true
html.omitEmptyAttributes = false
html.omitNullAttributes = false
html.escapeAttributes = false
html.html(lang:'en') {
head {
title('E-Commerce email')
base('target':'_blank')
meta('http-equiv' : 'Content-Type', 'content' : 'text/html; charset=ISO-8859-1')
meta('name':'viewport', 'content':'width=320')
style(type:"text/css", '''
''')
}
body('style':'padding:0; margin:0; -webkit-text-size-adjust:none; width:100%;','bgcolor':'#F2F2F2') {
div(){
table(){ //Container table
tr(){
td('width':'20','class':'nomob'){
}
td('align':'center'){
table(createTableCSS("640", "", "", "", "")){
}
}
td(){
}
}
}
}
} //End <body>
} //End <html>
}
The result looks like this
<table>'width':'640'</table>
and it should look like this
<table width:"640"></table>
I can do this without a method, but would really like to know how to use a method in this type of code.
The problem in here is, that using this method results in a string given as parameter into the table closure. What you need to make this work is a method returning a map. You can solve this by replacing your method with this one:
def createTableCSS(String width, String border, String cellSpacing, String background, String classes) {
return [width: width]
}

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

Closure with conditional logging

I have a function like this:
private downloadAllFiles() {
sftpRetriever.listFiles().findAll {
filter.isResponse(it) || filter.isResponseTurned(it)
}.each { String fileName ->
log.info 'Downloading file: {}', fileName
sftpRetriever.downloadFile(fileName)
log.info 'File downloaded'
removeRemoteFile(fileName)
}
}
I am looking for a simple way of modyfing this closure inside of that function so if the size() of findAll is 0 it will simply log 'No more files to download' and .each won't be executed. Is there any simple way to make it in single closure? It is really simply task if I divide it in several parts, but trying to learn closures here and improve my expressiveness :) Thank you in advance for your help.
Take a look at creature below :) It works due the fact that each returns the collection on which it's invoked (+ elvis operator and quite nice Groovy's truth evaluation):
def printContents(Collection collection) {
collection.each {
println it
} ?: println('Collection is empty')
}
printContents([1,2,'hello'])
printContents([])
I don't like this syntax but it's the shorter version which came to my mind.
You can also use metaprogramming to add the method provided by Steinar. It must be added to metaClass before first use but you'll avoid an effort to make extension module:
Collection.metaClass.doIfEmpty { Closure ifEmptyClosure ->
if (delegate.empty) {
ifEmptyClosure()
}
return delegate
}
def printContents(Collection collection) {
collection.doIfEmpty {
println "Collection is empty"
}.each {
println it
}
}
printContents([1,2,'hello'])
printContents([])
One rather generic and reusable option is to extend Collection using an extension module. This is surprisingly easy to do and is even recognized in IDE's (at least in IntelliJ) so you get code completion, etc.
For example, write an the extension class for collections which will perform the closure if the collection is empty. In addtion, it should always return the collection to allow further chaining:
package stackoverflow
class CollectionExtension {
static <T extends Collection> T doIfEmpty(T self, Closure closure) {
if (self.empty) {
closure()
}
return self
}
}
You will also need to tell groovy that this file is an extension module. Add a property file as a resource on the classpath: META-INF/services/org.codehaus.groovy.runtime.ExtensionModule (note: this name and location is mandatory for extension modules, i.e. you cannot change it).
moduleName=stackoverflow-module
moduleVersion=1.0
extensionClasses=stackoverflow.CollectionExtension
Finally a simple test script to show how this can be used:
def printContents(Collection collection) {
collection.doIfEmpty {
println "Collection is empty"
}.each {
println it
}
}
printContents([1,2,'hello'])
printContents([])
Output:
1
2
hello
Collection is empty
You may try the following piece of code:
def l1 = [1,2,3,4]
def l2 = [5,6,7,8]
def m(list) {
list.findAll { it < 5}.with { l ->
size > 0 ?
l.each { e ->
println e
}
:
println('Zero elements found')
}
}
m(l1)
m(l2)
No better idea at the moment.

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 )

Resources