简体   繁体   中英

How to render a List with Groovy's MarkupBuilder

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?

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()

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?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM