HomeXML Page 3 - XML Parsing With SAX and Xerces (part 2)
Nailing It To The Wall - XML
The first part of this article demonstrated the basics of the Xerces XML parser, explaining how it could be used to process XML documents in a non-Web environment. This concluding section closes the circle, taking everything you've learned so far and demonstrating how it can be applied to create dynamic Web pages from static XML documents with Xerces.
Now, how about something a little more useful? Consider the following modification of the previous example:
<?xml version="1.0"?>
<inventory>
<item>
<id>758</id>
<name>Rusty,
jagged nails for nailgun</name>
<supplier>NailBarn, Inc.</supplier>
<cost>2.99</cost>
<quantity
alert="500">10000</quantity>
</item>
<item>
<id>6273</id>
<name>Power
pack for death ray</name>
<supplier>QuakePower.domain.com</supplier>
<cost
currency="USD">9.99</cost>
<quantity alert="20">10</quantity>
</item>
<item>
<id>3784</id>
<name>Axe</name>
<supplier>Axe
And You Shall
Receive, Inc.</supplier>
<cost currency="USD">56.74</cost>
<quantity
alert="5">25</quantity>
</item>
<item>
<id>986</id>
<name>NVGs</name>
<supplier>Quake
Eyewear</supplier>
<cost currency="USD">1399.99</cost>
<quantity
alert="5">2</quantity>
</item>
</inventory>
Now, let's suppose I want to display this information in a neatly-formatted table,
with those items that I'm low on highlighted in red. My preferred output would look something like this:
Here's the code to accomplish this:
import org.apache.xerces.parsers.SAXParser;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
import
java.io.*;
public class MyFifthSaxApp extends DefaultHandler {
private Writer
out;
private String ElementName, AttributeName,AttributeValue = "";
private
Integer Quantity, Alert;
// constructor
public MyFifthSaxApp (String
xmlFile, Writer out)
throws SAXException {
this.out = out;
//
Create a Xerces SAX parser
SAXParser parser = new SAXParser();
//
Set the Content Handler
parser.setContentHandler(this);
//
parse the Document
try {
parser.parse(xmlFile);
out.flush();
} catch (IOException e) {
throw new SAXException(e);
}
}
// call this when opening
element found
public
void startElement (String uri, String local, String
qName,
Attributes atts)
throws
SAXException {
try {
// this is useful
later
ElementName = local;
//
display table header
if(local.equals("inventory"))
{
out.write("<h1><font
face=Verdana>Inventory
Management</font></h1>n<table
width="55%" cellpadding="5"
cellspacing="5"
border="1"><tr><td><p align=right><b><font
face=Verdana
size=2>Code</font></b></p></td><td><b><font
face=Verdana
size=2>Name</font></b></td><td><b><font face=Verdana
size=2>Supplier</font></b></td><td><p
align=right><b><font face=Verdana
size=2>Cost</font></b></p></td><td><p
align=right><font face=Verdana
size=2><b>Quantity</b></font></p></td></tr>");
}
else if(local.equals("item")) {
// "item" element starts a new row
out.write("<tr>");
}
else if( local.equals("name") || local.equals("supplier"))
{
// create table
cells within row
// align strings left, numbers right
out.write("<td><p
align=left><font face=Verdana size=2>");
} else if( local.equals("id")
||
local.equals("cost") ||
local.equals("quantity")) {
out.write("<td><p
align=right><font face=Verdana size=2>");
} else {
out.write("<br>");
}
for
(int i = 0; i < atts.getLength(); i++) {
AttributeName = atts.getLocalName(i);
AttributeValue
= atts.getValue(AttributeName);
if(AttributeName.equals("currency"))
{
out.write(AttributeValue
+ " ");
} else if(AttributeName.equals("alert"))
{
Alert = new
Integer(AttributeValue);
} else {
out.write(" ");
}
}
} catch (IOException e) {
throw new SAXException(e);
}
}
//
call this when cdata found
public void characters(char[] text,
int start,
int length)
throws SAXException {
try {
String Content
= new String(text,
start, length);
if (!Content.trim().equals(""))
{
if ((ElementName
!= null && ElementName.equals("quantity")) &&
(AttributeName
!=
null && AttributeName.equals("alert"))) {
Quantity = new Integer(Content);
//
if quantity lower than expected, highlight in red
if((Quantity.intValue())
< (Alert.intValue())) {
out.write("<font color="#ff0000">" + Quantity +
"</font>");
} else {
out.write("<font color="#000000">" +
Quantity
+ "</font>");
}
} else {
out.write(Content);
}
}
}
catch (IOException e) {
throw new SAXException(e);
}
}
// call
this when closing element found
public void endElement (String
uri, String local,
String qName)
throws SAXException {
try {
if(local.equals("inventory"))
{
out.write("</table>");
} else if(local.equals("item")) {
//
"item" closes table row
out.write("</tr>");
} else if(local.equals("id")
|| local.equals("name") ||
local.equals("supplier") || local.equals("cost") ||
local.equals("quantity"))
{
// close table cells
out.write("</font></p></td>");
}
else {
out.write(" ");
}
} catch (IOException e) {
throw
new SAXException(e);
}
}
}
As you can see, the callback functions used here have evolved substantially from
the previous examples - they now contain more conditional tests, and better error handling capabilities. Let's take a closer look.
Most of the work in this script is done by the startElement() callback function. This function prints specific HTML output depending on the element encountered by the parser.
// call this when opening element found
public void startElement (String
uri, String local, String qName,
Attributes atts)
throws SAXException {
try
{
// this is useful later
ElementName = local;
// display table
header
if(local.equals("inventory")) {
out.write("<h1><font face=Verdana>Inventory
Management</font></h1>n<table
width="55%" cellpadding="5"
cellspacing="5" border="1"><tr><td><p align=right><b><font
face=Verdana
size=2>Code</font></b></p></td><td><b><font
face=Verdana
size=2>Name</font></b></td><td><b><font face=Verdana
size=2>Supplier</font></b></td><td><p
align=right><b><font face=Verdana
size=2>Cost</font></b></p></td><td><p
align=right><font face=Verdana
size=2><b>Quantity</b></font></p></td></tr>");
}
else if(local.equals("item")) {
// "item" element starts a new row
out.write("<tr>");
}
else if( local.equals("name") || local.equals("supplier"))
{
// create table
cells within row
// align strings left, numbers right
out.write("<td><p
align=left><font face=Verdana size=2>");
} else if( local.equals("id")
||
local.equals("cost") ||
local.equals("quantity")) {
out.write("<td><p
align=right><font face=Verdana size=2>");
} else {
out.write("<br>");
}
for
(int i = 0; i < atts.getLength(); i++) {
AttributeName = atts.getLocalName(i);
AttributeValue
= atts.getValue(AttributeName);
if(AttributeName.equals("currency"))
{
out.write(AttributeValue
+ " ");
} else if(AttributeName.equals("alert"))
{
Alert = new
Integer(AttributeValue);
} else {
out.write(" ");
}
}
} catch (IOException e) {
throw new SAXException(e);
}
}
This function maps different XML elements to appropriate HTML markup. As you
can see, the document element "inventory", which marks the start of the XML document, is used to create the skeleton and first row of an HTML table, while the different "item" elements correspond to rows within this table. The details of each item - name, supplier, quantity et al - are formatted as cells within each row of the table.
Next, the characters() callback function handles formatting of the content embedded within the elements.
// call this when cdata found
public void characters(char[] text, int start,
int length)
throws SAXException {
try {
String Content = new String(text,
start, length);
if (!Content.trim().equals("")) {
if ((ElementName
!= null && ElementName.equals("quantity")) &&
(AttributeName
!=
null && AttributeName.equals("alert"))) {
Quantity = new Integer(Content);
//
if quantity lower than expected, highlight in red
if((Quantity.intValue())
< (Alert.intValue())) {
out.write("<font color="#ff0000">" + Quantity +
"</font>");
} else {
out.write("<font color="#000000">" +
Quantity
+ "</font>");
}
} else {
out.write(Content);
}
}
}
catch (IOException e) {
throw new SAXException(e);
}
}
For most of the elements, I'm simply displaying the content as is. The only deviation
from this standard policy occurs with the "quantity" element, which has an additional "alert" attribute. This "alert" attribute specifies the minimum number of units that should be in stock of the corresponding item; if the quantity drops below this minimum level, an alert should be generated. Consequently, the characters() callback includes some code to test the current quantity against the minimum quantity, and highlight the data in red if the test fails.
And finally, to wrap things up, the endElement() callback closes the HTML tags opened earlier.
// call this when closing element found
public void endElement (String uri,
String local, String qName)
throws SAXException {
try {
if(local.equals("inventory"))
{
out.write("</table>");
} else if(local.equals("item")) {
//
"item" closes table row
out.write("</tr>");
} else if(local.equals("id")
|| local.equals("name") ||
local.equals("supplier") || local.equals("cost") ||
local.equals("quantity"))
{
// close table cells
out.write("</font></p></td>");
}
else {
out.write(" ");
}
} catch (IOException e) {
throw
new SAXException(e);
}
}
Once you've compiled this class, you can use it in a JSP page, as you did with
the previous example. Here's the code,