builder.invoiceswhich produces:
{
invoice(number: 1234)
{
item(type: 'part')
{ product(name: 'widget', cost: 100) }
}
}
<invoices>The builder doesn't actually have methods for "invoice", "part", etc. Instead, dynamic language "tricks" are used to catch "unknown" method calls.
<invoice number="1234">
<item type="part">
<product name="widget" cost="100" />
</item>
</invoice>
</invoices>
It made me wonder how close I could come to this with Suneido. Here's what I came up with:
builder.invoices()The main difference is that Suneido requires '.' on method calls. Otherwise it's pretty much identical. One thing the Groovy XML builder doesn't handle is tag contents containing a mixture of text and tags. I handled this in Suneido with a special '_' method. For example:
{
.invoice(number: 1234)
{
.item(type: 'part')
{ .product(name: 'widget', cost: 100) }
}
}
builder.p()which produces:
{
._('start ')
.b() { 'middle' }
._(' end')
}
<p>start <b>middle</b> end</p>Here is the entire implementation of a Suneido XmlBuilder:
classOne minor problem with the Suneido version is that certain methods are "built in" (e.g. Size) and therefore can't be used in the builder. [This is a result of trying to make class instances behave the same as generic containers. I'm starting to think this was a mistake, but I'm not sure how to go about changing something so fundamental.]
{
New()
{ .s = '' }
Default(@args)
{
.s $= '<' $ args[0]
for m in args.Members(named:)
if m isnt #block
.s $= ' ' $ m $ '="' $ args[m] $ '"'
if args.Member?(#block)
{
.s $= '>'
result = .Eval2(args.block)
if result.Size() is 1
.s $= result[0]
.s $= '</' $ args[0] $ '>'
}
else
.s $= ' />'
return
}
_(s)
{ .s $= s; return }
ToString()
{ return .s }
}
I had to make to make a slight fix to Suneido to make this work. The approach I used was to use instance.Eval(function) to evaluate the blocks in the "context" of the builder. But I found that Eval didn't work with blocks (only functions). Luckily it was easy to fix. (Actually, I'm using Eval2 which returns the result inside an object so you can determine if there was a return value or not.)
3 comments:
Nice post. Note that MarkupBuilder in Groovy doesn't support mixed content, but StreamingMarkupBuilder (a similar builder that lazily streams XML ) does.
MarkupBuilder also supports mixed content.
MarkupBuilder also supports simple mixed content in Groovy 1.1.
Post a Comment