Monday, July 14, 2008

XML Namespaces

The concept of namespaces is similar to defining a variable with same name in different classes and accessing them by prefixing the object name to resolve the naming conflict. In xml document also if you have to define two elements with same tag (which would probably indicate two different things like html table & dining table) then we can use namespaces to resolve the naming conflict.

Namespace is defined by the xmlns attribute either in the start tag of an element or in the document root element as xmlns:prefix="URI". Below is the example:

<root>
<h:table h="http://www.w3.org/TR/html4/">
<h:tr>
<h:td>Apples</h:td>
<h:td>Bananas</h:td>
</h:tr>
</h:table>
<f:table f="http://www.w3schools.com/furniture">
<f:name>African Coffee Table</f:name>
<f:width>80</f:width>
<f:length>120</f:length>
</f:table>
</root>

<root
xmlns:h="http://www.w3.org/TR/html4/"
xmlns:f="http://www.w3schools.com/furniture">
<!-- Same as above -->
</root>
The namespace URI is not used by the parser to look up information. The purpose is just to give the namespace a unique name.

Defining a default namespace for an element saves us from using prefixes in all the child elements, the syntax is xmlns="namespaceURI". Below is an example:

<table xmlns="http://www.w3.org/TR/html4/">
<tr>
<td>Apples</td>
<td>Bananas</td>
</tr>
</table>

Converting DOM objects to XML

These days its rare to find any application not using XML for communicating with other applications. While we all use SAX or DOM parsers either directly or through JAX api, most of us are not aware of org.apache.xml.serialize.XMLSerializer class, which creates an xml string effortlessly. This supports both DOM and SAX. DOM serializing is done by calling serialize(Document) and SAX serializing is done by firing SAX events and using the serializer as document handler. Below is an example of converting a DOM object into xml string:

java.io.ByteArrayOutputStream outStream = new java.io.ByteArrayOutputStream();
org.apache.xml.serialize.OutputFormat outFormat = new org.apache.xml.serialize.OutputFormat();
org.apache.xml.serialize.XMLSerializer serializer = new org.apache.xml.serialize.XMLSerializer();
serializer.setOutputFormat(outFormat);
serializer.setOutputByteStream(outStream);
serializer.asDOMSerializer();
// document is the DOM object created elsewhere
serializer.serialize(document.getDocumentElement());
String outMessage = outStream.toString();

Careful with SAX parser

When parsing xml documents using SAX parser one need to attach an implementation of org.xml.sax.DocumentHandler to receive notifications from the parser. One of the tricky methods in this interface is public void characters(char[] ch, int start, int length) throws org.xml.sax.SAXException. Below is the extract from java docs:

The Parser will call this method to report each chunk of character data. SAX parsers may return all contiguous character data in a single chunk, or they may split it into several chunks; however, all of the characters in any single event must come from the same external entity so that the Locator provides useful information.

The underlined part above is the thing to be handled carefully. One can not simply read the data and allocate to a String as this method might get called repeatedly!! Below is one example of handling this:

// Buffer for holding the element data
StringBuffer dataBuffer = new StringBuffer();
public void characters(char[] ch, int start, int length) throws SAXException
{
dataBuffer.append(ch, start, length);
}

// Get the data from the buffer in endElement method call

One more quirk is that the characters method removes all encoding for special characters like &, <, > etc. So if the xml data contains these special characters outside CDATA section, one need to handle them explicitly !!

Saturday, July 12, 2008

DecimalFormat issue in jdk 1.4

Any message handling application need to maintain an unique id for each message to track the status. We are generating ids in Oracle database, which are 24 chars long. As this is done with a sequence the id we get is having some zeros followed by a number like '000000001234567890123456'. To remove the leading zeros we are simply converting this to BigDecimal and then again back to string using DecimalFormat as shown in the below code. While doing this we realised that the max number that DecimalFormat can format successfully is 15 digits if all 9's else 16 digits. Any value beyond that is causing the number to be incremented as shown in the below output.

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.StringTokenizer;

public class Test
{
public static void main(String[] args)
{
String[] data = {"999999999999999", "9999999999999998", "9999999999999999", "18411028910519619"};
BigDecimal num = null;
DecimalFormat format = new DecimalFormat();

for(int i = 0;i < data.length; i++)
{
num = new BigDecimal(data[i]);
System.out.println("Bignum: " + num.toString() + " converted to: " + format.format(num));
}
}
}

Output with JDK 1.4:
Bignum: 999999999999999 converted to: 999,999,999,999,999
Bignum: 9999999999999998 converted to: 9,999,999,999,999,998
Bignum: 9999999999999999 converted to: 10,000,000,000,000,000
Bignum: 18411028910519619 converted to: 18,411,028,910,519,620

This is not an issue in JDK 1.6:
Bignum: 999999999999999 converted to: 999,999,999,999,999
Bignum: 9999999999999998 converted to: 9,999,999,999,999,998
Bignum: 9999999999999999 converted to: 9,999,999,999,999,999
Bignum: 18411028910519619 converted to: 18,411,028,910,519,619