tags:

views:

108

answers:

3

I've built a custom builder in Groovy by extending BuilderSupport. It works well when configured like nearly every builder code sample out there:

def builder = new MyBuilder()
builder.foo {
    "Some Entry" (property1:value1, property2: value2)
}

This, of course, works perfectly. The problem is that I don't want the information I'm building to be in the code. I want to have this information in a file somewhere that is read in and built into objects by the builder. I cannot figure out how to do this.

I can't even make this work by moving the simple entry around in the code. This works:

def textClosure = { "Some Entry" (property1:value1, property2: value2) }
builder.foo(textClosure)

because textClosure is a closure.

If I do this:

def text = '"Some Entry" (property1:value1, property2: value2)'
def textClosure = { text }
builder.foo(textClosure)

the builder only gets called for the "foo" node. I've tried many variants of this, including passing the text block directly into the builder without wrapping it in a closure. They all yield the same result.

Is there some way I take a piece of arbitrary text and pass it into my builder so that it will be able to correctly parse and build it?

A: 

I think the problem you described is better solved with a slurper or parser.

See:

http://groovy.codehaus.org/Reading+XML+using+Groovy%27s+XmlSlurper http://groovy.codehaus.org/Reading+XML+using+Groovy%27s+XmlParser

for XML based examples.

In your case. Given the XML file:

<foo>
    <entry name='Some Entry' property1="value1" property2="value2"/>
</foo>

You could slurp it with:

def text = new File("test.xml").text
def foo = new XmlSlurper().parseText(text)
def allEntries = foo.entry
allEntries.each { 
    println it.@name
    println it.@property1
    println it.@property2
}
alexander.egger
These examples still assume that the content to be parsed/slurped is in the classpath, which won't be the case for my application. I don't want to use XML for my configuration - I looked at the source for both the XmlSlurper and XmlParser and they're both very XML specific. I would have to write my slurper/parser from scratch because there's no common base like there is for BuilderSupport.
Chad
Seams I didn't get you problem. I added an example to clarify what I meant. Maybe you can point out what is the problem with that solution.
alexander.egger
A: 

Originally, I wanted to be able to specify

"Some Entry" (property1:value1, property2: value2)

in an external file. I'm specifically trying to avoid XML and XML-like syntax to make these files easier for regular users to create and modify. My current solution uses ConfigSlurper and the file now looks like:

"Some Entry"
{ 
   property1 = value1 
   property2 = value2 
}

ConfigSlurper gives me a map like this:

["Some Entry":[property1:value1,property2:value2]]

It's pretty simple to use these values to create my objects, especially since I can just pass the property/value map into the constructor.

Chad
A: 

Your problem is that a String is not Groovy code. The way ConfigSlurper handles this is to compile the text into an instance of Script using GroovyClassLoader#parseClass. e.g.,

// create a Binding subclass that delegates to the builder
class MyBinding extends Binding {
    def builder
    Object getVariable(String name) {
        return { Object... args ->  builder.invokeMethod(name,args) }
    }   
}

// parse the script and run it against the builder
new File("foo.groovy").withInputStream { input ->
    Script s = new GroovyClassLoader().parseClass(input).newInstance()
    s.binding = new MyBinding(builder:builder)
    s.run()
}

The subclass of Binding simply returns a closure for all variables that delegates the call to the builder. So assuming foo.groovy contains:

foo {
    "Some Entry" (property1:value1, property2: value2)
}

It would be equivalent to your code above.

noah