xml-commons-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From chris0 <technic...@gmail.com>
Subject Proposal - A Very Simple API for Reading Simple XML Data
Date Sun, 31 May 2009 16:24:48 GMT

Hello,

I'm making this proposal because I couldn't find a good solution to a
problem that I recently had.

What I wanted to do was to configure an application with a simple XML file,
something along the lines of:

<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>

So the point is that it's really a very simple bit of XML data and what I
wanted was a nice easy way to read it in. The current options seem to be
SAX, DOM or JAXB.

To use JAXB I need an XSD which is just complicating things too much for a
simple config file. SAX is a bit low-level and complicated too. The best
option is probably DOM, but even DOM is quite verbose. Infact DOM is verbose
enough that it made me think for a while if I could just use some other
mechanism such as a properties file so that I didn't have to bother with
DOM.

For the purpose of this proposal I've written what I think is pretty much
the most concise way to parse this data with DOM. Also note that I want to
provide some basic error reporting if the XML data is not in the expected
format. The code follows below:

	FileInputStream fileInputStream = null;
	try
	{
		String rootName = "config";
		fileInputStream = new FileInputStream("config.xml");		
		DocumentBuilderFactory builderFactory =
DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = builderFactory.newDocumentBuilder();
		Document document = builder.parse(fileInputStream);
		Element rootElement = document.getDocumentElement();
		if(!rootElement.getNodeName().equals(rootName)) 
			throw new Exception("Could not find root node: "+rootName);

		NodeList titles = rootElement.getElementsByTagName("title");
		if(titles.getLength()!=1) throw new Exception("Could not find individual
node: title");
		Node title = titles.item(0);
		out.println("title: "+title.getTextContent());
		
		NodeList versions = rootElement.getElementsByTagName("version");
		if(versions.getLength()!=1) throw new Exception("Could not find individual
node: version");
		Node version = versions.item(0);
		NamedNodeMap versionAttributes = version.getAttributes();
		Node major = versionAttributes.getNamedItem("major");
		if(major==null) throw new Exception("Could not find attribute: major");
		Node minor = versionAttributes.getNamedItem("minor");
		if(minor==null) throw new Exception("Could not find attribute: minor");
		out.println(
			"version:
"+Integer.parseInt(major.getNodeValue())+"."+Integer.parseInt(minor.getNodeValue()));
		
		NodeList roles = rootElement.getElementsByTagName("roles");
		if(roles.getLength()!=1) throw new Exception("Could not find individual
node: roles");
		
		NodeList roleList = ((Element)roles.item(0)).getElementsByTagName("role");
		int n = roleList.getLength();
		for(int i=0;i<n;i++)
		{
			Node role = roleList.item(i);
			Node roleName = role.getAttributes().getNamedItem("name");
			if(roleName==null) throw new Exception("Could not find attribute: name");
			out.println("role: name: "+roleName.getNodeValue());
		}
		
		NodeList users = rootElement.getElementsByTagName("users");
		if(users.getLength()!=1) throw new Exception("Could not find individual
node: users");
		
		NodeList userList = ((Element)users.item(0)).getElementsByTagName("user");
		n = userList.getLength();
		for(int i=0;i<n;i++)
		{
			Node user = userList.item(i);
			NamedNodeMap userAttributes = user.getAttributes();
			Node userName = userAttributes.getNamedItem("name");
			if(userName==null) throw new Exception("Could not find attribute: name");
			Node userPassword = userAttributes.getNamedItem("password");
			if(userPassword==null) throw new Exception("Could not find attribute:
password");
			Node userRole = userAttributes.getNamedItem("role");
			if(userPassword==null) throw new Exception("Could not find attribute:
role");
			out.println(
				"user: name: "+userName.getNodeValue()+
				", password: "+userPassword.getNodeValue()+
				", role: "+userRole.getNodeValue());
		}
	}
	finally
	{
		if(fileInputStream!=null) fileInputStream.close();
	}
	
Output:

	title: test
	version: 1.2
	role: name: admin
	role: name: user
	user: name: joe, password: pass, role: admin
	user: name: harry, password: secret, role: user

And it's pretty verbose, 65 lines in all.

What I then decided to do was to write a very simple utility class called
XmlData that could be used to get this information as easily as possible,
and that would have basic built-in error reporting. When using the XmlData
class it is possible to write equivalent code to the above in a fraction of
the number of lines:

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

	out.println("title: "+config.child("title").content());
	
	XmlData version = config.child("version");
	out.println("version:
"+version.integer("major")+"."+version.integer("minor"));
	
	for(XmlData role:config.child("roles").children("role")) out.println("role:
name: "+role.string("name"));
	
	for(XmlData user:config.child("users").children("user"))
	{
		out.println(
			"user: name: "+user.string("name")+
			", password: "+user.string("password")+
			", role: "+user.string("role"));
	}

Output:

	title: test
	version: 1.2
	role: name: admin
	role: name: user
	user: name: joe, password: pass, role: admin
	user: name: harry, password: secret, role: user

As you can see, this is a really simple way to read basic XML files. All
nodes, attributes and content can very easily be accessed, and only one
class is required.

The XmlData class uses DOM to read an XML file then builds its own
representation of the tree in a very simple form, with only the basic data
present and all data exposed using standard Collections classes where
appropriate. The XmlData class source is included at the end of this post.

So in summary, the main point of this single utility class, XmlData, is to
provide a very simple and user-friendly way of reading simple XML files that
may contain simple data such as startup configurations. This class is not
supposed to be a substitute for DOM and is not intended for high-performance
scenarios, rather to make XML as easily accessible as possible.

[One improvement to this may be to base XmlData on SAX rather than DOM in
order to increase performance]

My proposal is perhaps to see something like XmlData as a Commons utility
class.

All comments very welcome.

Thanks,

Chris.

--

package main;

import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Collections;
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 XmlData
{
	private static Element rootElement(String filename, String rootName) throws
Exception
	{
		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;
		}
		finally
		{
			if(fileInputStream!=null) fileInputStream.close();
		}
	}
	
	public XmlData(String filename, String rootName) throws Exception
	{
		this(rootElement(filename,rootName));
	}
	
	private XmlData(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
XmlData((Element)node));
	    }
	}
	
	private void addAttribute(String name, String value)
	{
		nameAttributes.put(name,value);
	}
	
	private void addChild(String name, XmlData child)
	{
		List<XmlData> children = nameChildren.get(name);
		if(children==null)
		{
			children = new ArrayList<XmlData>();
			nameChildren.put(name,children);
		}
		children.add(child);
	}
	
	public String name()
	{
		return name;
	}
	
	public String content()
	{
		return content;
	}
	
	public XmlData child(String name) throws Exception
	{
		List<XmlData> children = children(name);
		if(children.size()!=1) throw new Exception("Could not find individual
child node: "+name);
		return children.get(0);
	}
	
	public List<XmlData> children(String name)
	{
		List<XmlData> children = nameChildren.get(name);
		return children==null ? Collections.EMPTY_LIST : children;			
	}
	
	public String string(String name) throws Exception
	{
		String value = nameAttributes.get(name);
		if(value==null) throw new Exception("Could not find attribute: "+name+",
in node: "+this.name);
		return value;
	}
	
	public int integer(String name) throws Exception
	{
		return Integer.parseInt(string(name)); 
	}
	
	private String name;
	private String content;
	private Map<String,String> nameAttributes = new HashMap<String,String>();
	private Map<String,List<XmlData>> nameChildren = new
HashMap<String,List<XmlData>>();
}
-- 
View this message in context: http://www.nabble.com/Proposal---A-Very-Simple-API-for-Reading-Simple-XML-Data-tp23804602p23804602.html
Sent from the Apache XML - Commons - Dev mailing list archive at Nabble.com.


Mime
View raw message