Saturday 30 May 2009

A Simple Way to Parse XML in Java

Dear All,
This post is now hosted here, which is hopefully somewhat easier on the eyes.
Many thanks

You can do a lot with XML, but often all you want to do is to read a simple file with some basic data in it. The options for doing this, SAX, DOM and JAXB are all relatively verbose and often off-putting. I've devised a simple XML API for parsing simple XML files in Java. Parsing a file such as:

<config>
<title>test</title>
<version
major="1"
minor
="2"/>
<roles>
<role name="admin"/>
<role name="user"/>
</roles>
<users>
<user name="joe" password="pass" role="admin"/>
<user name="harry" password="secret" role="user"/>
</users>
</config>

Can be achieved with the following code:


Xml config = new Xml("config.xml","config");

System.out.println(
"title: "+config.child("title").content());

Xml version
= config.child("version");
System.out.println(
"version: "+version.integer("major")+"."+version.integer("minor"));

for(Xml role:config.child("roles").children("role"))
System.out.println(
"role: name: "+role.string("name"));

for(Xml user:config.child("users").children("user"))
{
System.out.println(
"user: name: "+user.string("name")+
", password: "+user.string("password")+
", role: "+user.string("role"));
}

As you can see, it's nice and simple and allows you to get to the information quickly and without any hassle. The API uses the DOM parser underneath, but attempts to make the data more easily available. All you need is the following class, which you can of course customise however you like:

import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class Xml
{
private static Element rootElement(String filename, String rootName)
{
FileInputStream fileInputStream
= null;
try
{
fileInputStream
= new FileInputStream(filename);
DocumentBuilderFactory builderFactory
= DocumentBuilderFactory.newInstance();
DocumentBuilder builder
= builderFactory.newDocumentBuilder();
Document document
= builder.parse(fileInputStream);
Element rootElement
= document.getDocumentElement();
if(!rootElement.getNodeName().equals(rootName))
throw new RuntimeException("Could not find root node: "+rootName);
return rootElement;
}
catch(Exception exception)
{
throw new RuntimeException(exception);
}
finally
{
if(fileInputStream!=null)
{
try
{
fileInputStream.close();
}
catch(Exception exception)
{
throw new RuntimeException(exception);
}
}
}
}

public Xml(String filename, String rootName)
{
this(rootElement(filename,rootName));
}

private Xml(Element element)
{
this.name = element.getNodeName();
this.content = element.getTextContent();
NamedNodeMap namedNodeMap
= element.getAttributes();
int n = namedNodeMap.getLength();
for(int i=0;i<n;i++)
{
Node node
= namedNodeMap.item(i);
String name
= node.getNodeName();
addAttribute(name,node.getNodeValue());
}
NodeList nodes
= element.getChildNodes();
n
= nodes.getLength();
for(int i=0;i<n;i++)
{
Node node
= nodes.item(i);
int type = node.getNodeType();
if(type==Node.ELEMENT_NODE) addChild(node.getNodeName(),new Xml((Element)node));
}
}

private void addAttribute(String name, String value)
{
nameAttributes.put(name,value);
}

private void addChild(String name, Xml child)
{
List
<Xml> children = nameChildren.get(name);
if(children==null)
{
children
= new ArrayList<Xml>();
nameChildren.put(name,children);
}
children.add(child);
}

public String name()
{
return name;
}

public String content()
{
return content;
}

public Xml child(String name)
{
List
<Xml> children = children(name);
if(children.size()!=1) throw new RuntimeException("Could not find individual child node: "+name);
return children.get(0);
}

public List<Xml> children(String name)
{
List
<Xml> children = nameChildren.get(name);
return children==null ? new ArrayList<Xml>() : children;
}

public String string(String name)
{
String value
= nameAttributes.get(name);
if(value==null) throw new RuntimeException("Could not find attribute: "+name+", in node: "+this.name);
return value;
}

public int integer(String name)
{
return Integer.parseInt(string(name));
}

private String name;
private String content;
private Map<String,String> nameAttributes = new HashMap<String,String>();
private Map<String,List<Xml>> nameChildren = new HashMap<String,List<Xml>>();
}

16 comments:

  1. Found it very helpful. Thanks!

    ReplyDelete
  2. Code is erroring out at "element.getTextContent());" saying method not found. Please help.

    ReplyDelete
  3. Mohammad, please make sure you have the latest Java 6 API.

    ReplyDelete
  4. Can we save some data to xml using that?

    ReplyDelete
  5. I see you have all the data points as attributes in the example. Does it work for data between element tags?

    [I wrote a whole example up but this won't let me post anything in tag form]

    ReplyDelete
  6. Brian, yes it does, please see the "title" tag in the example.

    ReplyDelete
  7. Can we save some data to xml using your sample?

    ReplyDelete
  8. please show me how you read attribute "name" in user from your example..

    I dun tink it can be done using yr code..

    ReplyDelete
  9. why does it need the much code? could you make an api that is used like this?:

    Xml config = new Xml("config.xml","config");

    String title = config.title;
    /* give the string "title" the value of of the * title tag in the xml doc

    String name = config.users.user(0, name);
    \* in this, user is a tag with attributes. also in this case, there are 2 tags called user in the one parent tag. the says that it is user tag the computer will come across. the second is tag
    attribute to read.
    *\
    System.out.println("Title: ", title, ".");
    System.out.println("Name: ", name, ".");

    I myself wouldn't be skilled enough to do this, but i'm guessing that a lot of people would be.

    P.S
    Writing

    write(config.users.user(2, name), name);
    /* writes the string 'name' to the xml document.

    bye!

    ReplyDelete
  10. Hi Mat,
    Thanks for the input, but unfortunately what you are suggesting is not actually possible; at least not in Java anyway.

    ReplyDelete
  11. Thank you so much.very helpful.
    Expecting lot more useful stuffs from this blog.

    ReplyDelete
  12. Thank you very much for posting this. Is this code in the "public domain" -- can it be freely used? Or is there some special licensing / restrictions that go along with this?

    ReplyDelete
  13. Thank you so much. This is going to be very useful for me.

    ReplyDelete
  14. Hi Michael,
    This code is completely free so please use it however you like!

    ReplyDelete
  15. Dear All,

    Many thanks for all your comments, I'm really glad that so many of you have found this useful.

    I am currently moving this post to Wordpress instead of Blogger as I just think it looks a bit better.

    Please could you all post any future comments to the new site instead of this one. The new post is here:

    http://argonrain.wordpress.com/2009/10/27/000/

    Many thanks,
    Chris

    ReplyDelete