Hi all, my name is Hubert A. Klein Ikkink. Not a very common name, right? To make things easier I just picked the first letters of my firstname and surname and came up with haki. So there you have it, now I am also known as Mr. Haki or mrhaki for short. You can read more blog postings at www.mrhaki.com. I am a passionate Groovy and Java developer based in Tilburg, The Netherlands. My goal is to write clean, elegant, user-centered and high quality software. You can find me on Google+ and Twitter. Hubert is a DZone MVB and is not an employee of DZone and has posted 162 posts at DZone. You can read more from them at their website. View Full User Profile

Grassroots Groovy: Parse XML with XmlSlurper from Java

10.22.2012
| 6907 views |
  • submit to reddit

We can introduce Groovy into our Java projects at grassroots level. Even if we aren't allowed to run the Groovy compiler we can use other ways to run Groovy code. As long as we can include the Groovy libraries as a compile dependency than we can already use Groovy from Java. In this post we see how we can use the power of XmlSlurper to parse XML from our Java code.

To execute a Groovy script from we can use a GroovyShell object and invoke the evaluate() method. The evaluate() method can parse a Groovy script as File or Reader object. We can also use a String value to be evaluated. The last statement of the script that is evaluated can be assigned to a Java variable. To pass variables to the script we use the Binding object. This is a map of variables and their values. We assign values to the variables in the Java code and in the Groovy script we can use the variable values.

In the example we need to be able to parse a XML document with the following structure and find users with a given age:

<?xml version="1.0"?>
<users>
<user age="39">mrhaki</user>
<user age="39">hubert</user>
<user age="23">chris</user>
</users>

We first define a simple Java interface with a method getUsersWithAge(int):

package com.mrhaki.groovy.grassroots.model;

public class User {

    private final String username;
    private final int age;

    public User(final String username, final int age) {
        this.username = username;
        this.age = age;
    }

    public String getUsername() {
        return username;
    }

    public int getAge() {
        return age;
    }
}

We have the following User class:

package com.mrhaki.groovy.grassroots.model;

public class User {

    private final String username;
    private final int age;

    public User(final String username, final int age) {
        this.username = username;
        this.age = age;
    }

    public String getUsername() {
        return username;
    }

    public int getAge() {
        return age;
    }
}

The following class is an implementation of the DataExtractor interface and uses Groovy code to find all users from an XML source with a given age:

package com.mrhaki.groovy.grassroots.xml;

import com.mrhaki.groovy.grassroots.model.User;
import groovy.lang.Binding;
import groovy.lang.GroovyShell;

import java.util.List;

/**
 * Use XML input source to extract data from.
 */
public class XMLDataExtractor implements DataExtractor {
    /**
     * XML input source String.
     */
    private final String xml;

    public XMLDataExtractor(final String xml) {
        this.xml = xml;
    }

    @Override
    public List<User> getUsersWithAge(final int age) {
        // First we create the binding with variables
        // to be used in the Groovy script.
        final Binding binding = new Binding();
        binding.setVariable("xml", xml);
        binding.setVariable("age", age);

        // Create Groovy shell to run script and
        // set binding with variables for the script.
        final GroovyShell shell = new GroovyShell(getClass().getClassLoader(), binding);

        // Create Groovy script as String.
        // We use the XmlSlurper to parse the XML String and
        // return a list of found objects.
        final ScriptBuilder parseScript = new ScriptBuilder();
        parseScript.addLine("import com.mrhaki.groovy.grassroots.model.User");
        parseScript.addLine("def slurper = new XmlSlurper().parseText(xml)");
        parseScript.addLine("slurper.user.findAll { it.@age == age }.collect { new User(it.text(), it.@age.toInteger()) } ");

        // Evaluate script. The last line of the script is
        // automatically the return statement, so the value
        // is assigned to result.
        // We could also assign it to a variable in the script and
        // use binding.getVariable() to get the value.
        final Object result = shell.evaluate(parseScript.build());

        return (List<User>) result;
    }

    /**
     * Utility builder class to create a Groovy script
     * as String with the correct line endings.
     */
    private final class ScriptBuilder {
        private StringBuilder script = new StringBuilder();

        public ScriptBuilder addLine(final String scriptLine) {
            script.append(scriptLine);
            script.append(newLine());
            return this;
        }

        private String newLine() {
            return System.getProperty("line.separator");
        }

        public String build() {
            return script.toString();
        }
    }

}

To finish it up we can here see some simple tests to check the results of our XMLDataExtractor object:

package com.mrhaki.groovy.grassroots.xml;

import static org.junit.Assert.assertEquals;

import com.mrhaki.groovy.grassroots.model.User;
import org.junit.Test;

import java.util.List;

public class XMLDataExtractorTest {

    @Test
    public void numberOfUsersWithGivenAgeMustReflectNumbeOfUsersInXML() {
        final String sample = createSampleXml();
        final DataExtractor extractor = getExtractor(sample);

        final List<User> users = extractor.getUsersWithAge(39);

        assertEquals(2, users.size());
    }

    @Test
    public void foundUserMustHaveCorrectPropertyValues() {
        final String sample = createSampleXml();
        final DataExtractor extractor = getExtractor(sample);

        final List<User> users = extractor.getUsersWithAge(23);

        final User user = users.get(0);
        assertEquals("chris", user.getUsername());
        assertEquals(23, user.getAge());
    }

    private String createSampleXml() {
        final StringBuilder xml = new StringBuilder("<?xml version=\"1.0\"?>");
        xml.append("<users>");
        xml.append(createUserSampleXml("mrhaki", 39));
        xml.append(createUserSampleXml("hubert", 39));
        xml.append(createUserSampleXml("chris", 23));
        xml.append("</users>");
        return xml.toString();
    }

    private String createUserSampleXml(final String username, final int age) {
        return String.format("<user age=\"%2$s\">%1$s</user>", username, age);
    }

    protected DataExtractor getExtractor(final String sample) {
        return new XMLDataExtractor(sample);
    }

}

We have to add Groovy as a compile dependency to a Java project. The following build script defines a dependency on the Groovy XML module. Since Groovy 2 we don't have to include all of Groovy, but we can pick the modules we need and add them to our project:

// File: build.gradle
apply {
    plugin 'java'
    plugin 'idea'
}

version = 1.0

repositories {
    mavenLocal()
    mavenCentral()
}

ext {
    groovyVersion = '2.0.+'
    junitVersion = '4.+'
}

dependencies {
    compile "org.codehaus.groovy:groovy-xml:$groovyVersion"
    testCompile "junit:junit:$junitVersion"
}

 

 

 

 

 

Published at DZone with permission of Hubert Klein Ikkink, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Mike Wicks replied on Tue, 2012/10/30 - 6:24am

Am I being thick or something, or are Code samples #2 and #3 exactly the same?

#2 is supposed to be an Interface, and #3 the User Class - but they're both the User class, I think. 

Otherwise, Excellent!

MW 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.