Most of the time, parsing an XML document involves either programming a parser to sequentially traverse an XML document, taking different actions as it encounters different tags (SAX) or building a tree representation of the document in memory and using tree methods to navigate the tree's parent-child relationships (DOM).

While both these methods work, they tend to be either complex to program or memory- and processor-inefficient.

The Jakarta Project offers a third option, via its Digester component: writing rules to map XML elements into Java objects and defining complex actions for such objects. This is a brief introduction to processing XML with Jakarta Digester, showing you how it can be used to create pattern-matching rules for an XML document and perform actions on the resulting collections.

Note: This tutorial assumes that Digester is correctly installed and configured on your system. For download and installation instructions, visit the official Jakarta Digester website .

Basic Usage

Let's begin by setting up the XML file that will serve as the basis for examples in this tutorial. Pop open your favourite text editor, and create the XML document shown below.

<?xml version='1.0'?>
<library>
<movie>
<title>The Matrix</title>
<rating>5</rating>
</movie>
<movie>
<title>Mission: Impossible III</title>
<rating>3</rating>
</movie>
<movie>
<title>Minority Report</title>
<rating>5</rating>
</movie>
</library>

Before you can begin using Digester, it's important to have a clear understanding of both the XML structure and the output you plan to generate, because the design of your classes and rulesets will depend on this. For the first example, therefore, let's assume that the task is to parse the XML file above and generate a list of movie titles with their associated rating.

With this goal in mind, create some Configuration classes that will serve as Java objects to store the XML data.


public class LibraryDetailsConfiguration  {
    private HashMap movieDetailsMap;
    // movieDetailsMap would store the title as key and
    // MovieDetailsConfiguration object as value
     
    public LibraryDetailsConfiguration() {
        movieDetailsMap = new HashMap();
    }
   
    public void setMovieDetailsMap(HashMap movieDetailsMap) {
        this.movieDetailsMap = movieDetailsMap;
    }
   
    public HashMap getMovieDetailsMap() {
        return movieDetailsMap;
    }
   
    public void setMovie(MovieDetailsConfiguration movieDetails) {
        movieDetailsMap.put(movieDetails.getTitle(), movieDetails);
    }
   
    public void printLibraryDetails(){
        Collection movieDetailsColl = this.movieDetailsMap.values();
        if (movieDetailsColl != null) {
            Iterator itMovieDetails = movieDetailsColl.iterator();
            while(itMovieDetails.hasNext()) {
                MovieDetailsConfiguration movieDetails = (MovieDetailsConfiguration)
                itMovieDetails.next();
                System.out.println(movieDetails.getTitle() + " -> " + movieDetails.getRating());
            }
        } else {
            System.out.println("Movie details do not exist"); 
        }
     }
}

This class sets up a HashMap to hold information on each movie. For each element of the HashMap, the movie title serves as the key, and an instance of the MovieDetailsConfiguration class holds corresponding movie metadata (in this case, the rating). The code for this class should be fairly self-explanatory: it simply contains standard methods to set and get movie information, such as the rating and title.


public class MovieDetailsConfiguration {
  private String title;
  private int rating;

  public MovieDetailsConfiguration() {
  }
  public void setTitle(String title) {
    this.title = title;
  }

  public String getTitle() {
    return title;
  }
 public void setRating(int rating) {
   this.rating = rating;
 }
 public int getRating() {
   return rating;
 } 

}

Once the Configuration classes are created, the next step is to create the RuleSet class. This RuleSet class contains the set of rules that Digester will use to parse the XML file. These rules are simply the actions that you want Digester to take whenever it encounters a particular pattern during the parsing process. Take a look at the class code shown below.


public class LibraryRuleSet {
 
    public static LibraryDetailsConfiguration getLibraryDetailsConfig (String libraryConfigFilePath) {
       
        LibraryDetailsConfiguration libraryDetails = null;
       
        try {
       
            Digester digester = new Digester();
            digester.setValidating(false);
       
            // create an object of the given class,
            //push it to top of Digester object stack
                 digester.addObjectCreate("library",LibraryDetailsConfiguration.class);
            digester.addObjectCreate("library/movie", MovieDetailsConfiguration.class);
   
            // call the setter method of the given bean property on the object
            digester.addBeanPropertySetter("library/movie/title","title");
            digester.addBeanPropertySetter("library/movie/rating","rating");
           
            // pop the object on top of the stack
            digester.addSetNext("library/movie","setMovie");
           
            // begin parsing
            File fileObj = new File(libraryConfigFilePath);
            libraryDetails = (LibraryDetailsConfiguration) digester.parse(fileObj);
   
        } catch(Exception e) {
            System.out.println("Exception in parsing data file: " + e);
            e.printStackTrace();
        }
    return libraryDetails;
    }
}

This class actually performs the hard work of parsing the XML data and mapping this data into the previously-defined Java objects. First, the addObjectCreate() method creates instances of the two Configuration classes; next, the addBeanPropertySetter() method tells Digester which setter methods to call to attach the character data of the <title> and <rating> elements to the object instances; and finally, the parse() method actually takes care of parsing the file and executing the defined actions. The end result is a properly-configured instance of the LibraryDetailsConfiguration class, and all that is now required is to write a small stub that will call that instance's printLibraryDetails() method.


public class TestDigester
{
    public static void main(String args[]) {
        LibraryDetailsConfiguration libraryDetails = LibraryRuleSet.getLibraryDetails("");
        libraryDetails.printLibraryDetails();
    }
}

Here's the output:

Minority Report -> 5
Mission: Impossible III -> 3
The Matrix -> 5

Altering the ruleset

With Digester (as with any SAX-based API), a change in output requirements necessitates a change in the XML-to-Java-object mapping, and hence a change in the rulesets used. To illustrate this, let's assume a slightly different XML file as shown in below.

<?xml version='1.0'?>
<library>
<movie>
<title>The Matrix</title>
<cast>
<person>Keanu Reeves</person>
<person>Laurence Fishburne</person>
<person>Carrie-Anne Moss</person>
</cast>
<rating>5</rating>
</movie>
<movie>
<title>Mission: Impossible III</title>
<cast>
<person>Tom Cruise</person>
<person>Ving Rhames</person>
<person>Laurence Fishburne</person>
</cast>
<rating>3</rating>
</movie>
<movie>
<title>Minority Report</title>
<cast>
<person>Tom Cruise</person>
<person>Max von Sydow</person>
</cast>
<rating>5</rating>
</movie>
</library>

And let's also assume that you now wish to process this XML data in a different way, this time keying an actor name and listing all the movies for that actor. This would require a change in the MovieDetailsConfiguration class, which now needs an array to hold cast information for each movie.


public class MovieDetailsConfiguration  {

    private String title;
    private ArrayList movieCastList;
    private int rating;
 
    public MovieDetailsConfiguration() {
        movieCastList = new ArrayList();
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getTitle() {
        return title;
    }

    public void setMovieCastList(ArrayList movieCastList) {
        this.movieCastList = movieCastList;
    }

    public ArrayList getMovieCastList() {
        return movieCastList;
    }

    public void addMovieCast(String castName) {
        movieCastList.add(castName);
    }

    public void setRating(int rating) {
        this.rating = rating;
    }

    public int getRating() {
        return rating;
    } 
}

It would also necessitate a change in the setMovie() and printLibraryDetails() methods of the LibraryDetailsConfiguration class as shown below.


public class LibraryDetailsConfiguration  {
    private HashMap movieDetailsMap;
    // movieDetailsMap would store the title as key and
    // MovieDetailsConfiguration object as value
     
    public LibraryDetailsConfiguration() {
        movieDetailsMap = new HashMap();
    }
   
    public void setMovieDetailsMap(HashMap movieDetailsMap) {
        this.movieDetailsMap = movieDetailsMap;
    }
   
    public HashMap getMovieDetailsMap() {
        return movieDetailsMap;
    }

    public void setMovie(MovieDetailsConfiguration movieDetails) {
        ArrayList movieDetailList = null;
        ArrayList movieCastList = movieDetails.getMovieCastList();
        Iterator itMovieCast = movieCastList.iterator();

        while (itMovieCast.hasNext()) {
            String movieCastName = (String)itMovieCast.next();
            if(movieDetailsMap.containsKey(movieCastName)) {
                movieDetailList = (ArrayList)
                movieDetailsMap.get(movieCastName);
            } else {
                movieDetailList = new ArrayList();
            }

            movieDetailList.add(movieDetails);
            movieDetailsMap.put(movieCastName, movieDetailList);
        }
    }

   
    public void printLibraryDetails() {
        Collection movieCastColl = this.movieDetailsMap.keySet();
        if (movieCastColl != null) {
            Iterator itMovieCast = movieCastColl.iterator();
            while(itMovieCast.hasNext()) {
                String movieCastName =  (String)itMovieCast.next();
                System.out.println("ACTOR: " + movieCastName);
                ArrayList movieDetailsList = (ArrayList)movieDetailsMap.get(movieCastName);
                Iterator itMovieDetailsList = movieDetailsList.iterator();
                while (itMovieDetailsList.hasNext()) {
                    MovieDetailsConfiguration movieDetails = (MovieDetailsConfiguration)itMovieDetailsList.next();
                    System.out.println("\t\tMOVIE TITLE: " + movieDetails.getTitle());
                }
            }
        } else {
            System.out.println("Movie details do not exist"); 
        }
    }
}

Finally, the rulesets would also need to change, to find and locate cast members and attach to the cast array.


public class LibraryRuleSet {
 
    public static LibraryDetailsConfiguration getLibraryDetailsConfig (String libraryConfigFilePath) {
       
        LibraryDetailsConfiguration libraryDetails = null;
       
        try {
       
            Digester digester = new Digester();
            digester.setValidating(false);
       
            // create an object of the given class, push it to top of Digester object stack
            digester.addObjectCreate("library",LibraryDetailsConfiguration.class);
            digester.addObjectCreate("library/movie", MovieDetailsConfiguration.class);
   
            // call the setter method of the given bean property on the object
            digester.addBeanPropertySetter("library/movie/title","title");
digester.addBeanPropertySetter("library/movie/rating","rating");

            // call the given method on the object, specify parameters
            digester.addCallMethod("library/movie/cast/person", "addMovieCast",1);
            digester.addCallParam("library/movie/cast/person",0);
           
            // pop the object on top of the stack
            digester.addSetNext("library/movie","setMovie");
            
            // begin parsing
            File fileObj = new File(libraryConfigFilePath);
            libraryDetails = (LibraryDetailsConfiguration) digester.parse(fileObj);
   
        } catch(Exception e) {
            System.out.println("Exception in parsing movie data: " + e);
            e.printStackTrace();
        }
    return libraryDetails;
    }
}

And here's the revised output of the printLibraryDetails() method:

ACTOR: Carrie-Anne Moss
MOVIE TITLE: The Matrix
ACTOR: Keanu Reeves
MOVIE TITLE: The Matrix
ACTOR: Laurence Fishburne
MOVIE TITLE: The Matrix
MOVIE TITLE: Mission: Impossible III
ACTOR: Ving Rhames
MOVIE TITLE: Mission: Impossible III
ACTOR: Tom Cruise
MOVIE TITLE: Mission: Impossible III
MOVIE TITLE: Minority Report
ACTOR: Max von Sydow
MOVIE TITLE: Minority Report

Storing rulesets as XML

In the previous examples, you've seen Digester rulesets written in Java. However, one of the coolest things about Digester is that this isn't the only way to define patterns for mapping XML elements to actions. Instead, it's also possible to specify the rulesets themselves in XML, thus making it possible for even non-Java developers to leverage Digester's capabilities.

To see how this works, begin by removing the LibraryRuleSet class defined in the previous example, and replace it with rulesets written in XML, as shown below.

<?xml version='1.0'?>
<digester-rules>
<pattern value="library">
<object-create-rule classname="sample.config.LibraryDetailsConfiguration"/>
</pattern>
<pattern value="library/movie">
<object-create-rule classname="sample.config.MovieDetailsConfiguration"/>
<bean-property-setter-rule pattern="title"/>
<call-method-rule pattern="cast/person" methodname="addMovieCast" paramcount="1" />
<call-param-rule pattern="cast/person" paramnumber="0"/>
<set-next-rule methodname="setMovie" />
</pattern>
</digester-rules>

Save this file as movieRules.xml.

Next, alter the stub such that it knows to read these XML rulesets.


public class TestDigester {
  public static void main(String args[]) {
    TestDigester digesterClient = new TestDigester();
    digesterClient.digest();
  }
 
  public void digest() {
    try {
      //Create Digester using rules defined in movieRules.xml
      Digester digester = DigesterLoader.createDigester(this.getClass().getClassLoader().getResource("movieRules.xml"));

      //Parse movie.xml using the Digester to get an instance of Academy
      LibraryDetailsConfiguration libraryConfig = (LibraryDetailsConfiguration) digester.parse(this.getClass().getClassLoader().getResourceAsStream("movie.xml"));
      libraryConfig.printLibraryDetails();
    } catch(Exception e) {
      System.out.println("Exception while parsing the data file : " + e);
      e.printStackTrace();
    }
  }
}

And now try running it -- the output should be the same as that of the previous example.

As these examples illustrate, Jakarta's Digester component provides an intuitive rules-based framework for parsing an XML file, one that's significantly easier to program for than the standard SAX-based API. The use of XML-based rulesets further improves usability, allowing even non-Java developers to get their hands dirty with the application. Try it out for yourself -- and happy coding!

Interpreting Java This was published in Interpreting Java, check every Tuesday for more stories

Related links

Comments

1

affan - 19/07/08

Bimbingan Belajar KSC-IPB
Guru les private ke rumah Anda.
Pelajaran Mat,Fis,Kim,German,English,Japan

Hub Affan 085691375204
Ason 085695159962

Pengajar adalah para sarjana dan mahasiswa tingkat akhir UI dan IPB

» Report offensive content

Leave a comment

You must read and type the 6 chars within 0..9 and A..F

* indicates mandatory fields.

1

affan - 19/07/08

Bimbingan Belajar KSC-IPB Guru les private ke rumah Anda. Pelajaran Mat,Fis,Kim,German,English,Japan Hub Affan 085691375204 Ason 085695159962 Pengajar adalah para sarjana dan ... more

Log in


Sign up | Forgot your password?

  • Staff Share a keyboard and mouse with Synergy

    Even in the era of virtualization, many IT pros (including myself) have a small army of computers sitting on, under, and around their desks. Read more »

    -- posted by Staff

  • Staff Android devs less than gruntled

    Yet more discouraging news on the Android front. Having hacked off its developer community by releasing updated SDKs to just a small group of chosen devs, Google has now given the brush-off to a petition that called for more to be given to the wider community. Read more »

    -- posted by Staff

  • Staff VMware shows how not to do it

    As a developer there will be a time when you ship a bug -- be it a stub that you left in, or a flaming, crashtastic segfault. The next time this happens and your bosses come baying for blood, point them in the direction of VMware, who this week gave the developer world a great example of how to ship a showstopper bug. Read more »

    -- posted by Staff

What's on?

  • Club Builder: Captain Obvious vs the Crackpots

    In the case of the bleeding obvious, IBM says open source needs good designers; a claim is made that China can activate your phone to snoop on you; and we take a look at the Defcon conference.