«    »

Parsing and Generating XML with Java

December, 2008 Update: I now recommend using JAXB and annotated Java objects to parse and generate XML. See my article Simple XML Parsing using JAXB for details.

I've worked a lot with XML over the years (i.e. for Ant build files), but I've never had to parse or generate XML in Java until recently. There are many options for working with XML in Java, so I thought I'd share the approach I picked. I'll assume you are already familiar with the basics of XML.

Java has a standard API for XML parsing called JAXP (Java API for XML Processing), which includes two different types of parsers: SAX and DOM. The SAX (Simple API for XML) parser is event-based: as it parses the XML, it generates events for each type of syntactical element, and the parsing code you write can choose which events to deal with. The DOM (Document Object Model) parser builds a tree structure in memory representing the XML. Since Java 1.4, the JAXP parsers have been included with the JRE, so you can use JAXP without any additional libraries required. The SAX parser is great for working with very large documents that won't fit in memory, while the DOM parser is great when you need to make multiple passes over the document, or need to transform it. The SAX and DOM parsers are actual language-neutral APIs from the W3C, and as such suffer from a few problems. The Java version of these APIs don't always follow normal Java API conventions. Another problem is more of a feature: the APIs can handle any feature of XML, including comments and white space, which makes the API more complicated and low-level, and sometimes leads to non-intuitive behavior.

For example, mixed mode XML where elements are mingled with text requires that each piece of text be contained in its own text node. Consider the xml fragment: <p>Mixed <em>mode</em> text</p>. When expressed as a DOM tree, the resulting 'p' node contains three children: a text node containing 'Mixed', an 'em' node which itself contains a text node , and another text node containing 'text'. While mixed mode XML is necessary for cases such as XHTML, it is seldom used in programmatic contexts. Therefore, working with the DOM tree is more painful because of all the additional text nodes.

JAXB (Java Architecture for XML Binding) is another approach to parsing XML which converts the XML document directly to generated Java classes based on a XML Schema. This method is called binding, and it is conceptually similar to object-relational mapping frameworks such as Hibernate. JAXB generates the Java classes based on the XML Schema, although you can instead provide your own classes mapped via annotations. There are limitations to the binding approach. Requiring an XML schema means that certain types of dynamic parsing cannot be done - i.e. in Ant, you can define new tasks in the ant build XML file which essentially extend the XML schema being used dynamically. Also, the binding strategy used generally places limitations on the types of XML supported - i.e. I don't believe mixed mode is supported. I have heard of open source libraries for XML binding (i.e. Castor) and they might avoid some of the limitations of JAXB. But overall, I thought that the binding approach was more complexity than what I needed on my simple project.

There are many open source libraries for working with XML. After looking at some of them, I selected dom4j. This library is built on top of JAXP but provides a more convenient API, for example using the Java Collections API rather than custom lists. Dom4j allows use of either the SAX or DOM parser internally, depending on your requirements.

To demonstrate the use of dom4j for parsing and generating XML, I'll start with some Java classes representing a maze, which I want to be able to convert to XML or create from XML.

/**
 * The maze is defined to be a certain # of squares wide and high. The
 * coordinate system for squares is that the top left hand corner is (0,0).
 * The maze can contain walls, which follow the grid.
 */
public class Maze
{
  private Dimension size;
  private List walls = new ArrayList();
  private String description;

  public Maze(int width, int height) {
    size = new Dimension(width, height);
  }

  public Dimension getSize() {
    return size;
  }

  public void addWall(int startX, int startY, int endX, int endY) {
    walls.add(new Wall(startX, startY, endX, endY));
  }

  public List getWalls() {
    return walls;
  }

  public String getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }
}

public class Wall
{
  private Point start;
  private Point end;

  public Wall(int startX, int startY, int endX, int endY) {
    start = new Point(startX, startY);
    end = new Point(endX, endY);
  }

  public Point getEnd() {
    return end;
  }

  public Point getStart() {
    return start;
  }

  @Override public boolean equals(Object obj) {
    if (!(obj instanceof Wall)) {
      return false;
    }
    Wall other = (Wall) obj;
    return this.start.equals(other.start) && this.end.equals(other.end);
  }

  @Override public int hashCode() {
    return this.start.hashCode() + 7 * this.end.hashCode();
  }
}

I placed the code to generate and parse XML in a single class called MazeXmlConverter. In this class I defined constants for the XML element and attribute names which would be used by both the generation and parsing code. Looking at these constants suggests the structure of the XML document, so I never bothered creating a DTD or XML Schema.

public class MazeXmlConverter
{
  private static final String MAZE_ELEMENT = "maze";
  private static final String HEIGHT_ATTRIBUTE = "height";
  private static final String WIDTH_ATTRIBUTE = "width";
  private static final String WALL_ELEMENT = "wall";
  private static final String START_X_ATTRIBUTE = "startX";
  private static final String START_Y_ATTRIBUTE = "startY";
  private static final String END_X_ATTRIBUTE = "endX";
  private static final String END_Y_ATTRIBUTE = "endY";
}

The code to generate XML from a Maze object is quite simple: I create the root document object, then add elements with attributes and/or text as required as I iterate over the Maze.

  public String toXml(Maze maze) {
    Document document = DocumentHelper.createDocument();
    Element root = document.addElement(MAZE_ELEMENT)
      .addAttribute(WIDTH_ATTRIBUTE, Integer.toString(maze.getSize().width))
      .addAttribute(HEIGHT_ATTRIBUTE, Integer.toString(maze.getSize().height))
      .addText(nullToEmpty(maze.getDescription()));

    for (Wall wall : maze.getWalls()) {
      root.addElement(WALL_ELEMENT)
        .addAttribute(START_X_ATTRIBUTE, Integer.toString(wall.getStart().x))
        .addAttribute(START_Y_ATTRIBUTE, Integer.toString(wall.getStart().y))
        .addAttribute(END_X_ATTRIBUTE, Integer.toString(wall.getEnd().x))
        .addAttribute(END_Y_ATTRIBUTE, Integer.toString(wall.getEnd().y));
    }
    return document.asXML();
  }

  private String nullToEmpty(String text) {
    if (text == null) {
      return "";
    } else {
      return text;
    }
  }
}

The code to parse XML is a little more complicated. I first parse the XML to create a Document object. While for my project I need to parse XML from a file, I also needed to be able to parse XML from a string for unit testing. Fortunately, dom4j made this easy.

  public Maze fromXml(File xmlFile) {
    SAXReader reader = new SAXReader();
    try {
      Document document = reader.read(xmlFile);
      return fromXml(document);
    } catch (DocumentException e) {
      throw new RuntimeException("Error reading file " + 
        xmlFile.getAbsolutePath() + ".", e);
    }
  }

  public Maze fromXml(String xml) {   // For testing.
    try {
      Document document = DocumentHelper.parseText(xml);
      return fromXml(document);
    } catch (DocumentException e) {
      throw new RuntimeException("Error parsing xml string.", e);
    }
  }
  
  private Maze fromXml(Document document) {
    // To implement - see below...
  }

The second and final step in parsing is to implement the fromXml(Document) method. This is basically the inverse of the toXml(Maze) method above: I iterate through the document, converting each element to a Java object and the attributes / text to properties of the object.

  private Maze fromXml(Document document) {
    Element root = document.getRootElement();

    int width = getElementIntAttribute(root, WIDTH_ATTRIBUTE);
    int height = getElementIntAttribute(root, HEIGHT_ATTRIBUTE);
    Maze maze = new Maze(width, height);
    maze.setDescription(root.getText());

    for (Object elementObj : root.elements(WALL_ELEMENT)) {
      Element element = (Element) elementObj;
      int startX = getElementIntAttribute(element, START_X_ATTRIBUTE);
      int startY = getElementIntAttribute(element, START_Y_ATTRIBUTE);
      int endX = getElementIntAttribute(element, END_X_ATTRIBUTE);
      int endY = getElementIntAttribute(element, END_Y_ATTRIBUTE);
      maze.addWall(startX, startY, endX, endY);
    }
    return maze;
  }

  private int getElementIntAttribute(
    Element element, String attributeName) {

    Attribute attribute = getNonNullAttributeForElement(element, attributeName);
    return Integer.parseInt(attribute.getValue());
  }

  private Attribute getNonNullAttributeForElement(
    Element element, String attributeName) {

    Attribute attribute = element.attribute(attributeName);
    if (attribute == null) {
      throw new RuntimeException("Element " + element.getName() + 
        " missing attribute named " + attributeName + ".");
    }
    return attribute;
  }

The generated XML is provided below, with line breaks and indentation added for readability:

<?xml version="1.0" encoding="UTF-8"?>
<maze width="5" height="4">
  A maze
  <wall startX="1" startY="0" endX="1" endY="1"/>
  <wall startX="0" startY="2" endX="1" endY="2"/>
  <wall startX="1" startY="3" endX="1" endY="4"/>
  <wall startX="2" startY="1" endX="2" endY="3"/>
  <wall startX="2" startY="1" endX="3" endY="1"/>
  <wall startX="3" startY="3" endX="3" endY="4"/>
  <wall startX="3" startY="3" endX="4" endY="3"/>
  <wall startX="4" startY="2" endX="4" endY="4"/>
  <wall startX="4" startY="1" endX="5" endY="1"/>
</maze>

Overall, I was happy with my choice of the dom4j library. Thanks largely to the good documentation on its website, I was able to quickly learn and use the library for my project. I did have a couple of complaints. The API could be a little more helpful: I had to write the getElementIntAttribute() and getNonNullAttributeForElement() methods which were generic enough that they should have been part of the library. I also missed having an addAttribute() method that takes an integer primitive as an an attribute value. My other complaint is that dom4j uses checked exceptions (DocumentException) rather than unchecked exceptions which I generally prefer.

While dom4j worked well for me, I did have fairly simple requirements for the XML I was generating and parsing. With more complex object models, I could see a binding library requiring less effort to use (similar to the tradeoff between using raw JDBC versus an object-relational mapping library for database persistence). So I encourage you to think about your current and future requirements before making a similar choice.

The source code listed in this article is provided in the Java Examples project which can be downloaded from the Software page.

If you find this article helpful, please make a donation.

2 Comments on “Parsing and Generating XML with Java”

  1. aillusions says:

    JAXP == Java API for XML Processing

  2. Thanks, I fixed that in the article.

«    »