TechBookReport logo

Groovying XML - Part 2


By Pan Pantziarka


Reading and Processing XML

In the first part of this tutorial we looked at how you can use Groovy's flexible strings, closures and iterators to quickly create XML fragments and files. The emphasis was on making the most of Groovy as a scripting language rather than on making use of the heavy-weight XML parsers and libraries that it can access due to it's use of the Java Virtual Machine (JVM) and ability to access Java libraries. Now it's time to look at the obverse - how can we use Groovy to process XML?

Groovy can be used with the full range of Java XML parsers and libraries - so in addition to plain vanilla DOM and SAX, you can use JDOM, XOM, StAX and the whole alphabet soup of alternatives that are available for the Java platform. However what we're interested in here is using Groovy for the kind of ad hoc scripting tasks that have traditionally been the preserve of Perl and co. Instead of the heavyweights we are going to focus instead on Groovy's XmlSlurper class - which provides a very natural and intuitive interface to XML.

In the examples that follow we're going to be processing a simple XML file of the type that crops up all over the place given the spread of XML file formats throughout the development world.





Our example data is the following simple XML file:

<?xml version="1.0"?>
<people>
  <person first_name='john' surname='smith'>
    <age>37</age>
    <gender>m</gender>
    <kids count='2' />
  </person>
  <person first_name='jill' surname='jones'>
    <age>28</age>
    <gender>f</gender>
    <kids count='0' />
  </person>
  <person first_name='clark' surname='kent'>
    <age>22</age>
    <gender>m</gender>
    <kids count='3' />
  </person>  
</people>

The first task is to parse the file (which we've called pers.xml and have placed in a /groovy/progs directory) from disk and make it available for processing. In Groovy this is a one-liner:

def pers=new XmlSlurper().parse(new File("/groovy/progs/pers.xml"))

Once we've got our XmlSlurper object we can use dot notation to access elements and attributes within it. Using dot notation is a very natural way of working for Java developers, and helps to bridge the gap between object notation and hierarchic XML structure. The top level element in this case is people, but we can find that simply enough:

println pers.name()

The above code will print people to the command-line or shell. Want to find out what the child elements are? Easy:

pers.children().each { println it.name() }

But why stop there? We can move up and down the tree easily enough. Say we want to find the children of each of the child elements in turn. Again, it's trivial:

pers.children().children().each {println it.name()}

That should print out three lots of age, gender and kids. Getting hold of attributes isn't much more difficult:

pers.children().each {println it.attributes()}

The output of that line of code is:

["first_name":"john", "surname":"smith"]
["first_name":"jill", "surname":"jones"]
["first_name":"clark", "surname":"kent"]

Note that the above is a map, with the attribute names acting as the keys.

If we're really interested in walking the tree then there are a couple of methods explicitly designed to do all the work for us, and we get a choice of how we want to order our walk, depth first or breadth first. Let's take a look at them both:

println '\nDepth first...'
pers.depthFirst().each {println it.name()}
println '\nBreadth first....'
pers.breadthFirst().each {println it.name()} 

Both of these will walk the XML tree encoded in our XmlSlurper object and print out the element names. Here's a bit of quick and dirty script to print out the attributes of every element that has them:

pers.depthFirst().each {   
atts = it.attributes().size() > 0 ? it.name() + " = " + 
	it.attributes() : null
    		if (atts) println atts
}

Running against our sample file that produces the following output:

person = ["first_name":"john", "surname":"smith"]
kids = ["count":"2"]
person = ["first_name":"jill", "surname":"jones"]
kids = ["count":"0"]
person = ["first_name":"clark", "surname":"kent"]
kids = ["count":"3"]

Another advantage of XmlSlurper is that we can address elements directly by name. In our case if we're crafting code that's specifically designed to process pers.xml documents, why not use the people, person, age, gender and kids elements directly in our code?

Here's how we can access the age of every person in the document:

pers.person.age.each { println it}

We can make some adjustments to the data too, say we want to up the age of the second person in the file:

pers.person.age[1]='999'
println pers.person.getAt(1).age

Note how we used an index method getAt() to grab the updated value at the end there.

We can do more than simply read and write values. Let's say we want to sum the ages of all the people in our XML file. We can iterate over the age elements, convert to integers and perform a running sum:

sum_of_ages=0
pers.person.age.each {sum_of_ages+=it.toInteger() }
println 'sum = ' + sum_of_ages

Before we finish off, we ought to take a look at how we do some processing that's a bit more demanding than the relatively straightforward accessing of elements and attributes that we've managed so far.

XmlSlurper includes both a find and a findAll method, which makes for straightforward processing of XML data based on content. While in no way a replacement for XPath, for simple cases it provides a good way to filter nodes. For example, let's say we want to extract those person nodes which don't have any kids. That means looking at the count attribute for the kids elements and only accessing those nodes which have a value greater than zero. With Groovy we can do it as follows:

def with_kids=pers.person.findAll {it.kids.@count.toInteger() > 0}

First we use the findAll method on the person elements, then inside the closure we access the count attribute of the kids element, convert to an integer and then test for a value greater than zero. The result of this is assigned to the with_kids object, which we can process further:

println "Number of people with kids: " + with_kids.size()
with_kids.each {println it.@first_name.text() + " has " +
it.kids.@count.toInteger() + " kids"}

This produces the following output:

Number of people with kids: 2
john has 2 kids
clark has 3 kids

As you can see, the XmlSlurper class provides a full set of methods for processing XML data. While it doesn't take the place of a complete framework, or provide all of the functionality of XPath, it does provide convenience methods aplenty. For many common XML processing activities it works and is easy to understand and follow - and it avoids the verbosity required for performing similar activities in pure Java code.


<<Previous Page: Writing XML


Contents copyright of Pan Pantziarka. If you like it link it, don't lift it. No copying for commercial use allowed. Site © 2008.