Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 4.0

Tree Based Syntax

Groovy has special syntax support for List and Maps. This is great because it gives a concise representation of the actual object being defined, so its easier to keep track of what a program or script is doing. But what about programs which contain arbitrary nested tree structures. Surely, they are the hardest ones to keep track of what is going on. Isn't that an area where syntactic help will be most beneficial?

The answer is definitely yes and Groovy comes to the party with its builder concept. You can use it for DOM-like APIs or Ant tasks or Jelly tags or Swing widgets or whatever. Each may have their own particular factory mechanism to create the tree of objects - however they can share the same builder syntax to define them - in a concise alternative to XML or lengthy programming code. See How Builders Work

Example

[Note: the syntax in some of these examples is slightly out-dated. See chapter 8 of GINA in the mean-time until these examples are updated.]

Here's an example:

Code Block
def f = frame(size:[300,300], text:'My Window') {
    label(bounds:[10,10,290,30], text:'Save changes')
    panel(bounds:[10,40,290,290]) {
        button(text:'OK', action:{ save close })
        button(text:'Cancel', action:{ close })
    }
}

The above invokes a number of methods on the owner class using named-parameter passing syntax. Then the button method would create JButton etc. The { } is used to define a closure which adds its content to the newly created node. Also notice that the action parameter is passed as a closure - which is ideal for working with UI centric listeners etc.

Note that within the 'markup' you can embed normal expressions - i.e. this markup syntax is a normal part of the Groovy language. e.g.

Code Block
def f = frame(text: calculateFieldNamefoo, 1234){

    // lets iterate through some map
    map = [1:"hello", 2:"there"]

    for e in map {
      labelname:e.value
      textfieldname:e.value
    }
}

Using this simple mechanism we can easily create any structured tree of data - or provide an event based model too. Note in Groovy you can just overload the invokeMethodname, arguments to have a simple polymorphic tree creation - such as for DOM is structures or Ant tasks or Jelly tags etc.

Here's an example of some HTML using some mixed content which is typically hard to do neatly in some markup languages

Code Block
html {
    head {
        title"XML encoding with Groovy"
    }
    body {
        h1"XML encoding with Groovy"
        p"this format can be used as an alternative markup to XML"

        / an element with attributes and text content /
        ahref:'http://groovy.codehaus.org' ["Groovy"]

        / mixed content /
        p [
            "This is some",
            b"mixed",
            "text. For more see the",
            ahref:'http://groovy.codehaus.org' ["Groovy"],
            "project"
        ]
        p "some text"
    }
}

Finally here's an example of creating some name-spaced XML structure XSD...

Code Block
def builder = NodeBuilder.newInstance()
def xmlns = new groovy.xml.NamespaceBuilder(builder)

def xsd = xmlns.namespace('http://www.w3.org/2001/XMLSchema', 'xsd')

def root = xsd.schema(xmlns:['foo':'http://someOtherNamespace']) {
  annotation {
      documentation("Purchase order schema for Example.com.")
      //documentation(xmlns=[xml.lang:'en']) ["Purchase order schema for Example.com."]
  }
  element(name:'purchaseOrder', type:'PurchaseOrderType')
  element(name:'comment', type:'xsd:string')
  complexType(name:'PurchaseOrderType') {
    sequence {
      element(name:'shipTo', type:'USAddress')
      element(name:'billTo', type:'USAddress')
      element(minOccurs:'0', ref:'comment')
      element(name:'items', type:'Items')
    }
    attribute(name:'orderDate', type:'xsd:date')
  }
  complexType(name:'USAddress') {
    sequence {
      element(name:'name', type:'xsd:string')
      element(name:'street', type:'xsd:string')
      element(name:'city', type:'xsd:string')
      element(name:'state', type:'xsd:string')
      element(name:'zip', type:'xsd:decimal')
    }
    attribute(fixed:'US', name:'country', type:'xsd:NMTOKEN')
  }
  complexType(name:'Items') {
    sequence {
      element(maxOccurs:'unbounded', minOccurs:'0', name:'item') {
        complexType {
          sequence {
            element(name:'productName', type:'xsd:string')
            element(name:'quantity') {
              simpleType {
                restriction(base:'xsd:positiveInteger') {
                  maxExclusive(value:'100')
                }
              }
            }
            element(name:'USPrice', type:'xsd:decimal')
            element(minOccurs:'0', ref:'comment')
            element(minOccurs:'0', name:'shipDate', type:'xsd:date')
          }
          attribute(name:'partNum', type:'SKU', use:'required')
        }
      }
    }
  }
  /* Stock Keeping Unit, a code for identifying products */
  simpleType(name:'SKU') {
    restriction(base:'xsd:string') {
      pattern(value:'\\d{3}-[A-Z]{2}')
    }
  }
}

There's a converter org.codehaus.groovy.tools.xml.DomToGroovy from XML to groovy markup so you can try out this new markup language on any XML documents you have already.

Special cases

To output elements or attributes with a '-' in their name, you need to quote the names. For example, to generate a web-app descriptor for a Servlet app:

Code Block
def builder = new groovy.xml.MarkupBuilder()
builder.'web-app' {
    'display-name' 'My Web Application'
}

generates:

Code Block
<web-app>
  <display-name>My Web Application</display-name>
</web-app>

Read from external variable

Most builder examples are inline usage. To use a builder to build for an external variable, you may use:

Code Block
class MyConfig{
  static nodes = {
    'first entry'( key: 'value')
  }
}

def result = new YourBuilder().invokeMethod('rootNode', MyConfig.nodes )

Andy Glover introduces builders through an astronomical example