Skip to content
Skip to breadcrumbs
Skip to header menu
Skip to action menu
Skip to quick search
Quick Search
Browse
Pages
Blog
Labels
Attachments
Mail
Advanced
What’s New
Space Directory
Feed Builder
Keyboard Shortcuts
Confluence Gadgets
Log In
Sign Up
Dashboard
Groovy
Copy Page
You are not logged in. Any changes you make will be marked as
anonymous
. You may want to
Log In
if you already have an account. You can also
Sign Up
for a new account.
This page is being edited by
.
Paragraph
Paragraph
Heading 1
Heading 2
Heading 3
Heading 4
Heading 5
Heading 6
Preformatted
Quote
Bold
Italic
Underline
More colours
Strikethrough
Subscript
Superscript
Monospace
Clear Formatting
Bullet list
Numbered list
Outdent
Indent
Align left
Align center
Align right
Link
Table
Insert
Insert Content
Image
Link
Attachment
Symbol
Emoticon
Wiki Markup
Horizontal rule
tinymce.confluence.insert_menu.macro_desc
Info
JIRA Issue
Status
Gallery
Tasklist
Table of Contents
Other Macros
Page Layout
No Layout
Two column (simple)
Two column (simple, left sidebar)
Two column (simple, right sidebar)
Three column (simple)
Two column
Two column (left sidebar)
Two column (right sidebar)
Three column
Three column (left and right sidebars)
Undo
Redo
Find/Replace
Keyboard Shortcuts Help
<p>Two principles of Agile development are <em>DRY</em> (don't repeat yourself) and <em>merciless refactoring</em>. Thanks to excellent IDE support it isn't too hard to apply these principles to coding Java and Groovy but it's a bit harder with XML.</p> <p>The good news is that Groovy's Builder notation can help. Whether you are trying to refactor your Ant build file(s) or manage a family of related XML files (e.g. XML request and response files for testing Web Services) you will find that you can make great advances in managing your XML files using builder patterns.</p> <table class="wysiwyg-macro" data-macro-name="section" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e3NlY3Rpb259&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="RICH_TEXT"><tr><td class="wysiwyg-macro-body"> <table class="wysiwyg-macro" data-macro-name="column" data-macro-parameters="width=5%" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvbHVtbjp3aWR0aD01JX0&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="RICH_TEXT"><tr><td class="wysiwyg-macro-body"><p /></td></tr></table> <table class="wysiwyg-macro" data-macro-name="column" data-macro-parameters="width=90%" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvbHVtbjp3aWR0aD05MCV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="RICH_TEXT"><tr><td class="wysiwyg-macro-body"> <p><strong>Scenario:</strong> Consider we have a program to track the sales of copies of <a href="http://groovy.canoo.com/gina">GINA</a> <img class="emoticon emoticon-smile" data-emoticon-name="smile" border="0" src="/s/en_GB/3278/15/_/images/icons/emoticons/smile.png" alt="(smile)" title="(smile)" /> . Books leave a warehouse in trucks. Trucks contain big boxes which are sent off to various countries. The big boxes contain smaller boxes which travel to different states and cities around the world. These boxes may also contain smaller boxes as required. Eventually some of the boxes contain just books. Either GINA or some potential upcoming Groovy titles. Suppose the delivery system produces XML files containing the items in each truck. We are responsible for writing the system which does some fancy reporting.</p></td></tr></table> <table class="wysiwyg-macro" data-macro-name="column" data-macro-parameters="width=5%" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvbHVtbjp3aWR0aD01JX0&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="RICH_TEXT"><tr><td class="wysiwyg-macro-body"><p /></td></tr></table></td></tr></table> <p>If we are a vigilant tester, we will have a family of test files which allow us to test the many possible kinds of XML files we need to deal with. Instead of having to manage a directory full of files which would be hard to maintain if the delivery system changed, we decide to use Groovy to generate the XML files we need. Here is our first attempt:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> import groovy.xml.MarkupBuilder def writer = new StringWriter() def xml = new MarkupBuilder(writer) xml.truck(id:'ABC123') { box(country:'Australia') { box(country:'Australia', state:'QLD') { book(title:'Groovy in Action', author:'Dierk König et al') book(title:'Groovy in Action', author:'Dierk König et al') book(title:'Groovy for VBA Macro writers') } box(country:'Australia', state:'NSW') { box(country:'Australia', state:'NSW', city:'Sydney') { book(title:'Groovy in Action', author:'Dierk König et al') book(title:'Groovy for COBOL Programmers') } box(country:'Australia', state:'NSW', suburb:'Albury') { book(title:'Groovy in Action', author:'Dierk König et al') book(title:'Groovy for Fortran Programmers') } } } box(country:'USA') { box(country:'USA', state:'CA') { book(title:'Groovy in Action', author:'Dierk König et al') book(title:'Groovy for Ruby programmers') } } box(country:'Germany') { box(country:'Germany', city:'Berlin') { book(title:'Groovy in Action', author:'Dierk König et al') book(title:'Groovy for PHP Programmers') } } box(country:'UK') { box(country:'UK', city:'London') { book(title:'Groovy in Action', author:'Dierk König et al') book(title:'Groovy for Haskel Programmers') } } } println writer.toString() </pre></td></tr></table> <p>There is quite a lot of replication in this file. Lets refactor out two helper methods <code>standardBook1</code> and <code>standardBook2</code> to remove some of the duplication. We now have something like this:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> import groovy.xml.MarkupBuilder // standard book def standardBook1(builder) { builder.book(title:'Groovy in Action', author:'Dierk König et al') } // other standard books def standardBook2(builder, audience) { builder.book(title:"Groovy for ${audience}") } def writer = new StringWriter() def xml = new MarkupBuilder(writer) xml.truck(id:'ABC123') { box(country:'Australia') { box(country:'Australia', state:'QLD') { standardBook1(xml) standardBook1(xml) standardBook2(xml, 'VBA Macro writers') } box(country:'Australia', state:'NSW') { box(country:'Australia', state:'NSW', city:'Sydney') { standardBook1(xml) standardBook2(xml, 'COBOL Programmers') } box(country:'Australia', state:'NSW', suburb:'Albury') { standardBook1(xml) standardBook2(xml, 'Fortran Programmers') } } } box(country:'USA') { box(country:'USA', state:'CA') { standardBook1(xml) standardBook2(xml, 'Ruby Programmers') } } box(country:'Germany') { box(country:'Germany', city:'Berlin') { standardBook1(xml) standardBook2(xml, 'PHP Programmers') } } box(country:'UK') { box(country:'UK', city:'London') { standardBook1(xml) standardBook2(xml, 'Haskel Programmers') } } } println writer.toString() </pre></td></tr></table> <p>Next, let's refactor out a few more methods to end up with the following:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> import groovy.xml.MarkupBuilder // define standard book and version allowing multiple copies def standardBook1(builder) { builder.book(title:'Groovy in Action', author:'Dierk König et al') } def standardBook1(builder, copies) { (0..<copies).each{ standardBook1(builder) } } // another standard book def standardBook2(builder, audience) { builder.book(title:"Groovy for ${audience}") } // define standard box def standardBox1(builder, args) { def other = args.findAll{it.key != 'audience'} builder.box(other) { standardBook1(builder); standardBook2(builder, args['audience']) } } // define standard country box def standardBox2(builder, args) { builder.box(country:args['country']) { if (args.containsKey('language')) { args.put('audience', args['language'] + ' programmers') args.remove('language') } standardBox1(builder, args) } } def writer = new StringWriter() def xml = new MarkupBuilder(writer) xml.truck(id:'ABC123') { box(country:'Australia') { box(country:'Australia', state:'QLD') { standardBook1(xml, 2) standardBook2(xml, 'VBA Macro writers') } box(country:'Australia', state:'NSW') { [Sydney:'COBOL', Albury:'Fortran'].each{ city, language -> standardBox1(xml, [country:'Australia', state:'NSW', city:"${city}", audience:"${language} Programmers"]) } } } standardBox2(xml, [country:'USA', state:'CA', language:'Ruby']) standardBox2(xml, [country:'Germany', city:'Berlin', language:'PHP']) standardBox2(xml, [country:'UK', city:'London', language:'Haskel']) } println writer.toString() </pre></td></tr></table> <p>This is better. If the format of our XML changes, we will minimise the changes required in our builder code. Similarly, if we need to produce multiple XML files, we can add some for loops, closures or if statements to generate all the files from one or a small number of source files.</p> <p>We could extract out some of our code into a helper method and the code would become:</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> import groovy.xml.MarkupBuilder def writer = new StringWriter() def xml = new MarkupBuilder(writer) def standard = new StandardBookDefinitions(xml) xml.truck(id:'ABC123') { box(country:'Australia') { box(country:'Australia', state:'QLD') { standard.book1(2) standard.book2('VBA Macro writers') } box(country:'Australia', state:'NSW') { [Sydney:'COBOL', Albury:'Fortran'].each{ city, language -> standard.box1(country:'Australia', state:'NSW', city:"${city}", audience:"${language} Programmers") } } } standard.box2(country:'USA', state:'CA', language:'Ruby') standard.box2(country:'Germany', city:'Berlin', language:'PHP') standard.box2(country:'UK', city:'London', language:'Haskel') } println writer.toString() </pre></td></tr></table> <p>So far we have just produced the one XML file. It would make sense to use similar techniques to produce all the XML files we need. We can take this in several directions at this point including using GStrings, using database contents to help generate the content or making use of templates.</p> <p>We won't look at any of these, instead we will just augment the previous example just a little more.<br /> First we will slightly expand our helper class. Here is the result:</p> <table class="wysiwyg-macro" data-macro-name="code" data-macro-parameters="borderColor=#D0D9BD|borderStyle=solid|borderWidth=1|title=StandardBookDefinitions.groovy|titleBGColor=#D0D9BD" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6dGl0bGVCR0NvbG9yPSNEMEQ5QkR8Ym9yZGVyU3R5bGU9c29saWR8dGl0bGU9U3RhbmRhcmRCb29rRGVmaW5pdGlvbnMuZ3Jvb3Z5fGJvcmRlckNvbG9yPSNEMEQ5QkR8Ym9yZGVyV2lkdGg9MX0&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> import groovy.xml.MarkupBuilder class StandardBookDefinitions { private def builder StandardBookDefinitions(builder) { this.builder = builder } def removeKey(args, key) { return args.findAll{it.key != key} } // define standard book and version allowing multiple copies def book1() { builder.book(title:'Groovy in Action', author:'Dierk König et al') } def book1(copies) { (0..<copies).each{ book1() } } // another standard book def book2(audience) { builder.book(title:"Groovy for ${audience}") } // define standard box def box1(args) { def other = removeKey(args, 'audience') builder.box(other) { book1(); book2(args['audience']) } } // define standard country box def box2(args) { builder.box(country:args['country']) { if (args.containsKey('language')) { args.put('audience', args['language'] + ' programmers') args.remove('language') } box1(args) } } // define deep box def box3(args) { def depth = args['depth'] def other = removeKey(args, 'depth') if (depth > 1) { builder.box(other) { other.put('depth', depth - 1) box3(other) } } else { box2(other) } } // define deep box def box4(args) { builder.box(country:'South Africa'){ (0..<args['number']).each{ book1() } } } } </pre></td></tr></table> <p>And now we will use this helper class to generate a family of related XML files. For illustrative purposes, we will just print out the generated files rather than actually store the files.</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> import groovy.xml.MarkupBuilder def writer = new StringWriter() xml = new MarkupBuilder(writer) standard = new StandardBookDefinitions(xml) def shortCountry = 'UK' def longCountry = 'The United Kingdom of Great Britain and Northern Ireland' def shortState = 'CA' def longState = 'The State of Rhode Island and Providence Plantations' def countryForState = 'USA' def generateWorldOrEuropeXml(world) { xml.truck(id:'ABC123') { if (world) { box(country:'Australia') { box(country:'Australia', state:'QLD') { standard.book1(2) standard.book2('VBA Macro writers') } box(country:'Australia', state:'NSW') { [Sydney:'COBOL', Albury:'Fortran'].each{ city, language -> standard.box1(country:'Australia', state:'NSW', city:"${city}", audience:"${language} Programmers") } } } standard.box2(country:'USA', state:'CA', language:'Ruby') } standard.box2(country:'Germany', city:'Berlin', language:'PHP') standard.box2(country:'UK', city:'London', language:'Haskel') } } def generateSpecialSizeXml(depth, number) { xml.truck(id:'DEF123') { standard.box3(country:'UK', city:'London', language:'Haskel', depth:depth) standard.box4(country:'UK', city:'London', language:'Haskel', number:number) box(country:'UK') {} // empty box } } def generateSpecialNamesXml(country, state) { xml.truck(id:'GHI123') { if (state) { box(country:country, state:state){ standard.book1() } } else { box(country:country){ standard.book1() } } } } generateWorldOrEuropeXml(true) generateWorldOrEuropeXml(false) generateSpecialSizeXml(10, 10) generateSpecialNamesXml(shortCountry, '') generateSpecialNamesXml(longCountry, '') generateSpecialNamesXml(countryForState, shortState) generateSpecialNamesXml(countryForState, longState) println writer.toString() </pre></td></tr></table> <p>This will be much more maintainable over time than a directory full of hand-crafted XML files.</p> <p>Here is what will be produced:</p> <table class="wysiwyg-macro" data-macro-name="code" data-macro-default-parameter="xml" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGU6eG1sfQ&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> <truck id='ABC123'> <box country='Australia'> <box state='QLD' country='Australia'> <book title='Groovy in Action' author='Dierk König et al' /> <book title='Groovy in Action' author='Dierk König et al' /> <book title='Groovy for VBA Macro writers' /> </box> <box state='NSW' country='Australia'> <box city='Albury' state='NSW' country='Australia'> <book title='Groovy in Action' author='Dierk König et al' /> <book title='Groovy for Fortran Programmers' /> </box> <box city='Sydney' state='NSW' country='Australia'> <book title='Groovy in Action' author='Dierk König et al' /> <book title='Groovy for COBOL Programmers' /> </box> </box> </box> <box country='USA'> <box state='CA' country='USA'> <book title='Groovy in Action' author='Dierk König et al' /> <book title='Groovy for Ruby programmers' /> </box> </box> <box country='Germany'> <box city='Berlin' country='Germany'> <book title='Groovy in Action' author='Dierk König et al' /> <book title='Groovy for PHP programmers' /> </box> </box> <box country='UK'> <box city='London' country='UK'> <book title='Groovy in Action' author='Dierk König et al' /> <book title='Groovy for Haskel programmers' /> </box> </box> </truck> <truck id='ABC123'> <box country='Germany'> <box city='Berlin' country='Germany'> <book title='Groovy in Action' author='Dierk König et al' /> <book title='Groovy for PHP programmers' /> </box> </box> <box country='UK'> <box city='London' country='UK'> <book title='Groovy in Action' author='Dierk König et al' /> <book title='Groovy for Haskel programmers' /> </box> </box> </truck> <truck id='DEF123'> <box language='Haskel' city='London' country='UK'> <box language='Haskel' city='London' country='UK'> <box language='Haskel' city='London' country='UK'> <box language='Haskel' city='London' country='UK'> <box language='Haskel' city='London' country='UK'> <box language='Haskel' city='London' country='UK'> <box language='Haskel' city='London' country='UK'> <box language='Haskel' city='London' country='UK'> <box language='Haskel' city='London' country='UK'> <box country='UK'> <box city='London' country='UK'> <book title='Groovy in Action' author='Dierk König et al' /> <book title='Groovy for Haskel programmers' /> </box> </box> </box> </box> </box> </box> </box> </box> </box> </box> </box> <box country='South Africa'> <book title='Groovy in Action' author='Dierk König et al' /> <book title='Groovy in Action' author='Dierk König et al' /> <book title='Groovy in Action' author='Dierk König et al' /> <book title='Groovy in Action' author='Dierk König et al' /> <book title='Groovy in Action' author='Dierk König et al' /> <book title='Groovy in Action' author='Dierk König et al' /> <book title='Groovy in Action' author='Dierk König et al' /> <book title='Groovy in Action' author='Dierk König et al' /> <book title='Groovy in Action' author='Dierk König et al' /> <book title='Groovy in Action' author='Dierk König et al' /> </box> <box country='UK' /> </truck> <truck id='GHI123'> <box country='UK'> <book title='Groovy in Action' author='Dierk König et al' /> </box> </truck> <truck id='GHI123'> <box country='The United Kingdom of Great Britain and Northern Ireland'> <book title='Groovy in Action' author='Dierk König et al' /> </box> </truck> <truck id='GHI123'> <box state='CA' country='USA'> <book title='Groovy in Action' author='Dierk König et al' /> </box> </truck> <truck id='GHI123'> <box state='The State of Rhode Island and Providence Plantations' country='USA'> <book title='Groovy in Action' author='Dierk König et al' /> </box> </truck> </pre></td></tr></table> <p>Things to be careful about when using markup builders is not to overlap variables you currently have in scope. The following is a good example</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> import groovy.xml.MarkupBuilder def book = "MyBook" def writer = new StringWriter() def xml = new MarkupBuilder(writer) xml.shelf() { book(name:"Fight Club") { } } println writer.toString() </pre></td></tr></table> <p>When run this will actually get the error</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> aught: groovy.lang.MissingMethodException: No signature of method: java.lang.String.call() is applicable for argument types: (java.util.LinkedHashMap, HelloWorld$_run_closure1_closure2) values: {["name":"Fight Club"], </pre></td></tr></table> <p>This is because we have a variable above called book, then we are trying to create an element called book using the markup. Markups will always honor for variables/method names in scope first before assuming something should be interpreted as markup. But wait, we want a variable called book AND we want to create an xml element called book! No problem, use delegate variable.</p> <table class="wysiwyg-macro" data-macro-name="code" style="background-image: url(/plugins/servlet/confluence/placeholder/macro-heading?definition=e2NvZGV9&locale=en_GB&version=2); background-repeat: no-repeat;" data-macro-body-type="PLAIN_TEXT"><tr><td class="wysiwyg-macro-body"><pre> import groovy.xml.MarkupBuilder def book = "MyBook" def writer = new StringWriter() def xml = new MarkupBuilder(writer) xml.shelf() { delegate.book(name:"Fight Club") { } } println writer.toString() </pre></td></tr></table>
Please type the word appearing in the picture.
Attachments
Labels
Location
Watch this page
< Edit
Preview >
Loading…
Save
Cancel
Next hint
search
attachments
weblink
advanced