问题
I am converting a Map to XML using the Groovy MarkupBuilder. This Map can contain simple key/value pairs, other Maps, or Lists of Maps. I am piggybacking from the code here.
import groovy.xml.MarkupBuilder
def map = [
key1:'value1',
key2:'value2',
nestedMap : [
key1:'bar1',
key2:'bar2'
],
select : [
[option:'foo1'],
[option:'foo2']
]
]
Closure renderMap( Map map ){
return {
for ( entry in map ){
switch( entry.value.getClass() ){
case Map :
"${entry.key}" renderMap( entry.value )
break
case List:
entry.value.collect { listEntry ->
"${entry.key}" renderMap( listEntry )
}
break
default :
"${entry.key}"( "${entry.value}" )
break
}
}
}
}
StringWriter writer = new StringWriter()
new MarkupBuilder(writer).root renderMap(map)
println writer.toString()
This part I'm concerned about prints out:
<select>
<option>foo1</option>
</select>
<select>
<option>foo2</option>
</select>
However, I am wondering if there is a way to get the select to encapsulate both of the options, like so:
<select>
<option>foo1</option>
<option>foo2</option>
</select>
I've tried playing around with the placement of the key, but to no avail. Am I going about this all wrong, or should I not be using the builder?
回答1:
I think this will do what you want. The first two overloads take a map or a collection, and return a composed closure that can be passed to the builder method of the enclosing element to add the contents of the map or collection to the builder.
The third is a fallback, and just returns its arguments so they can be passed to the builder method. This handles the strings, but you could also pass it a closure if you want. I replaced the second option
element in the map you provided as an example of that.
ComposedClosure was added in Groovy 1.8, so this won't work in earlier versions.
import groovy.xml.MarkupBuilder
Closure buildxml(final Map map)
{
final compose = { f, tag, content -> f >> { "$tag"(buildxml(content)) } }
return map.inject(Closure.IDENTITY, compose)
}
Closure buildxml(final Collection col)
{
final compose = { f, content -> f >> buildxml(content) }
return col.inject(Closure.IDENTITY, compose)
}
def buildxml(final content)
{
return content
}
def map = [
key1:'value1',
key2:'value2',
nestedMap : [
key1:'bar1',
key2:'bar2'
],
select : [
[option:'foo1'],
{ option('foo2') },
],
]
final writer = new StringWriter()
final builder = new MarkupBuilder(writer)
builder.root buildxml(map)
assert writer as String == '''\
<root>
<key1>value1</key1>
<key2>value2</key2>
<nestedMap>
<key1>bar1</key1>
<key2>bar2</key2>
</nestedMap>
<select>
<option>foo1</option>
<option>foo2</option>
</select>
</root>'''.stripIndent()
回答2:
does
case List:
"${entry.key}" entry.value.collect {
renderMap it
}
break
get you anywhere? not at a computer to check atm though, but it feels right?
来源:https://stackoverflow.com/questions/13095486/how-to-render-a-list-with-groovys-markupbuilder