Question

I'm using Groovy's StreamingMarkupBuilder to generate XML dynamically based on the results of a few SQL queries. I'd like to call a method from inside of the closure but the markup builder tries to create an XML node using the method name.

Here's an example of what I'm trying to do:

Map generateMapFromRow(GroovyRowResult row) {
  def map = [:]
  def meta = row.getMetaData()

  // Dynamically generate the keys and values
  (1..meta.getColumnCount()).each { column -> map[meta.getColumnName(column)] = row[column-1] }
  return map
}

def sql = Sql.newInstance(db.url, db.user, db.password, db.driver)
def builder = new StreamingMarkupBuilder()

def studentsImport = {
  students {
    sql.eachRow('select first_name, middle_name, last_name from students') { row ->
      def map = generateMapFromRow(row) // Here is the problem line
      student(map)
    }
  }
}

println builder.bind(studentsImport).toString()

This will generate XML similar to the following:

<students>
  <generateMapFromRow>
    [first_name:Ima, middle_name:Good, last_name:Student]
  </generateMapFromRow>
  <student/>
  <generateMapFromRow>
    [first_name:Ima, middle_name:Bad, last_name:Student]
  </generateMapFromRow>
  <student/>
</students>

I've tried moving the method out to a class and calling to statically on the class, which doesn't work also.

Due to the nature of how StreamingMarkupBuilder works, I'm afraid that it isn't actually possible to do this, but I'm hoping that it is.

Was it helpful?

Solution

I may loose smth during example simplification, but such code will work.

In your example students is a closure call, so it may mess smth inside.

def builder = new groovy.xml.StreamingMarkupBuilder()
def generateMapFromRow = { ["$it": it] }
builder.bind {
10.times {
    def map = generateMapFromRow(it) // Now closure is escaped, there is local variable with such name.
    student(map)
    }
}

As said here: http://groovy.codehaus.org/Using+MarkupBuilder+for+Agile+XML+creation

Things to be careful about when using markup builders is not to overlap variables you currently have in scope. The following is a good example

import groovy.xml.MarkupBuilder

def book = "MyBook"

def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
xml.shelf() {
    book(name:"Fight Club") { // Will produce error.
    }
}

println writer.toString()

Builder's work similar to MethodMissing captors, ans if there is local variable in scope, no node will be produced.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top