Richard G Baldwin (512) 223-4758, baldwin@austin.cc.tx.us, http://www2.austin.cc.tx.us/baldwin/

XML to Objects and Objects to XML using SAX

Java Programming, Lecture Notes # 826, Revised 6/10/99.


Preface

Students in Prof. Baldwin's Advanced Java Programming classes at ACC will be responsible for knowing and understanding all of the material in this lesson beginning with the summer semester of 1999.

This lesson was originally written on June 10, 1999 and has been updated several times since then.

Overview

A previous lesson explained SAX and showed how to use SAX for a simple application.

This lesson expands on that explanation and provides a more substantive sample program. The sample program in this lesson parses an XML file and extracts the information contained in the file. The information extracted from the XML file is used to create a set of Java objects.

Those objects would be suitable for a variety of purposes, such as editing for example. Then the information contained in the objects is used to produce a second XML file. Had the objects been edited in the interim, the result would be a new XML file that is an edited version of the original XML file.

The sample program in this lesson is designed for a very specific XML file format, as described in the following section.

The XML File

An examination

The XML file used with this sample program represents the rudimentary aspects of a set of examination questions. The first listing below shows a schematic of the element structure of the XML file along with the attributes of the elements. The second listing shows the actual XML file that was used to test the program, including the content of each of the elements.

<?xml version="1.0"?>
<exam>
  <problem problemNumber="1">
    <question>...</question>
    <answer type="multiple" numberChoices="4" valid="0,1,3">
      <item>...</item>
      <item>...</item>
      <item>...</item>
      <item>...</item>
    </answer>
    <explanation>...</explanation>
  </problem>

  <problem problemNumber="2">
    ...
  </problem>
</exam>

XML file structure

The XML file uses the following types of elements as highlighted above:

The exam element

The XML document contains a single exam element.

The exam element contains one or more problem elements.

The problem elements

The problem elements have an attribute that specifies the problem number associated with each problem element.

Each problem element also contains

The question element

The question element is simply that. It asks the question for a particular problem.

The answer element

Each answer element contains one or more item elements and some attribute values. The attribute values explain how different parts of the answer elements should be used to comprise the answer to the question.

The explanation element

The explanation element is simply some text that explains the answer.

Example problem element

For example, the attributes of the answer element of the case shown above (and repeated below) represent a multiple-choice question having four available choices.

    <answer type="multiple" numberChoices="4" valid="0,1,3">

In an online testing situation, the student would select one or more of the item elements as her answer to the question. In this particular case, the student would need to select items 0, 1, and 3 in order to provide a valid answer to the question.

Credit

The idea for using an XML file to contain information for administering online examinations came from an article published by Claude Duguay. He uses this concept to illustrate the use of XML and Java Servlets to provide an online testing capability. As of 6/10/99, the article is available online at http://www.devx.com/upload/free/features/javapro/1999/04apr99/cd0499/cd0499.asp.

The XML file

A listing of the actual XML file used to test the sample program in this lesson is shown below. Note that this file contains one exam element with two problem elements. The first problem has a multiple-choice question requiring the selection of three items as a valid answer as discussed above.

The second problem is a multiple-choice question requiring the selection of a single item as the valid answer.

<?xml version="1.0"?>
<exam>
<problem problemNumber="1">
<question>
Which of the following are grown 
in a vegetable garden?
</question>
<answer type="multiple" numberChoices="4" valid="0,1,3">
<item>Carrots</item>
<item>Cabbage</item>
<item>Apples</item>
<item>Lettuce</item>
</answer>
<explanation>
Carrots, cabbage, and lettuce are grown in
a vegetable garden. Apples are grown in an orchard.
</explanation>
</problem>

<problem problemNumber="2">
<question>
Which one of the following requires 
XML entities?
</question>
<answer type="single" numberChoices="3" valid="1">
<item>Tom</item>
<item>&quot;&lt;Mary &amp; Sue&gt;&quot;</item>
<item>Dick</item>
</answer>
<explanation>
Left and right angle brackets, ampersands, and quotation 
marks must be represented in XML by entities
</explanation>
</problem>

</exam>

The manner in which the sample program processes this XML file is described in the following sections.

Sample Program

Handling parser events and errors

This program uses the IBM parser (XML4J) along with the XML file from the previous section.

Purpose of the program

This program, consists of the following four source files:

Complete Listings of all four files are provided near the end of the lesson. Taken together, the four files illustrate the conversion of an XML file to a set of Java objects, and the conversion of those objects back to a new XML file. The contents of the objects are displayed.

Converting XML to Java objects

This program reads an XML file with a very specific format as described earlier. It creates a Java object for each record in the file, (where each record represents an exam problem). It stores the objects in a Vector container. Classes in the file named Sax02B.java are used for this purpose.

Displaying the data

After generating the Vector object containing the XML records, the program displays each of the instance variables in each object whose reference is stored in the Vector. This is a very simple illustration of how the XML data can be processed after first having converted it into object format.

Converting Java objects to XML

Then the program generates an XML file containing a record for each element in the vector, and writes it out to a disk file named junk.xml. Classes in the file named Sax02C.java are used for this purpose.

This conversion is very specific and fairly brute force. A more generalized approach using the Document Object Model will be illustrated in a subsequent lesson.

The object class

A class definition in the file named Sax02D.java is used by all of the classes that make up this program as a common class for representing an XML record as an object.

Miscellaneous comments

No particular effort was expended to make this program robust. In particular, if it encounters an XML file in the wrong format, it may throw an exception, or it may continue to run, but it probably will not work properly.

The program was tested using JDK 1.2 under Win95. It also requires the IBM XML4J (or some suitable substitute) parser.

The program was tested with the XML file named Sax02.xml listed earlier. Note that line breaks were manually inserted in that listing to force the text to fit in this format):

Processing results

The program produced the following output on the screen. Line breaks were manually inserted here to force the text to fit in this format)

Problem Number: 1
Question: Which of the following are grown 
in a vegetable garden?
Type: multiple
Number choices: 4
Valid: 0,1,3
Item number 0: Carrots
Item number 1: Cabbage
Item number 2: Apples
Item number 3: Lettuce
Explanation: Carrots, cabbage, and lettuce are grown in
a vegetable garden. Apples are grown in an orchard.
Problem Number: 2
Question: Which one of the following requires 
XML entities?
Type: single
Number choices: 3
Valid: 1
Item number 0: Tom
Item number 1: "<Mary & Sue>"
Item number 2: Dick
Explanation: Left and right angle brackets, ampersands, and
 quotation 
marks must be represented in XML by entities

Program output

The program produced an output file named junk.xml containing the following. (Line breaks were manually inserted here to force the text to fit in this format): Since the program did not modify the data after reading the original XML file and before creating the new XML file, this output should be a replica of the original XML file.

<?xml version="1.0"?>
<exam>
<problem problemNumber="1">
<question>Which of the following are grown 
in a vegetable garden?</question>
<answer type="multiple" numberChoices="4" valid="0,1,3">
<item>Carrots</item>
<item>Cabbage</item>
<item>Apples</item>
<item>Lettuce</item>
</answer>
<explanation>Carrots, cabbage, and lettuce are grown in
a vegetable garden. Apples are grown in an orchard.
</explanation>
</problem>
<problem problemNumber="2">
<question>Which one of the following requires 
XML entities?</question>
<answer type="single" numberChoices="3" valid="1">
<item>Tom</item>
<item>&quot;&lt;Mary &amp; Sue&gt;&quot;</item>
<item>Dick</item>
</answer>
<explanation>Left and right angle brackets, ampersands, and
 quotation 
marks must be represented in XML by entities</explanation>
</problem>
</exam>

 

Interesting Code Fragments

The entire program consists of a driver file named Sax02.java and several helper files as listed earlier. I'm going to begin with the file containing the class definition used to instantiate objects to contain the XML data.

Sax02D.java

The class definition in this file provides a common object format for storage of the data from the XML file. The class is designed to contain an instance variable for each piece of data stored in a single exam problem in the XML file. This class has no methods. It is simply a container for the data extracted from the XML file.

This is a very specific class designed for a very specific XML format.

class Sax02D{
  int problemNumber;//an attribute of <problem>
  String question;//the content of <question>
  String type;//an attribute of <answer>
  int numberChoices;//an attribute of <answer>
  String valid;//an attribute of <answer>

  //Each populated element in the following array contains
  // the content of one <item> element in the XML file
  // with an arbitrary limit of five such elements.
  String[] item = new String[5];
  String explanation;//the content of <explanation>
}//end Sax02D

Sax02.java

Next I will discuss the driver file named Sax02.java by breaking it up into fragments. A complete listing is provided near the end of the lesson.

The first fragment shows the beginning of the controlling class along with the declaration of three class variables.

class Sax02 {
  static Vector theExam = new Vector();
  static Thread mainThread = Thread.currentThread();
  static String XMLfile = "Sax02.xml";

main()

The next fragment shows the beginning of the main() method. In this method, I spawn a new thread of type Sax02B that will parse the incoming XML file, creating an object for each problem specification in the exam, and storing a reference to each of those objects in the Vector mentioned above and referred to by theExam.

start()

Then I invoke the start() method on the thread to start it running.

  public static void main (String args[]){
    try{
      Sax02B fetchItObj = 
                    new Sax02B(XMLfile,theExam,mainThread);
      fetchItObj.start();//start the thread running

If the XML file is a long one, some time will pass before the theExam has been populated and is ready for use.

Producer/Consumer scenario

This is a typical producer/consumer scenario for which there are several control solutions. In this case, the Sax02B thread is the producer and the main thread is the consumer.

Go to sleep

Because of the simplicity of this particular situation, I chose simply to put the main thread to sleep and let it sleep until the thread that is parsing the XML file awakens it. That thread will interrupt the main thread when it finishes parsing the XML file, which will cause the main thread to wake up and process theExam.

Thus, the main thread will sleep until the parse is completed. It will wake up when interrupted by the parser thread and will then process the data in the Vector.

If parsing is not completed during the first 100000 milliseconds, it will wake up and then go back to sleep. (However, that would be an awfully long time to complete the parse so it might be better to throw an exception under those conditions.)

      try{
        while(true){//sleep and loop until interrupted
          Thread.currentThread().sleep(100000);
        }//end while
      }catch(InterruptedException e){
        //Wake up and do something with the Vector data
      }//end catch

XML file has been converted to objects

At this point, each of the exam problems in the XML file has been converted into a Java object. References to those objects have been stored in a Vector object named theExam. This Vector object can be used for any number of useful purposes, such as editing the contents of the individual objects, sorting the problems, administering the test, etc.

Display the object contents

In this sample program, I simply display all of the data in each object before converting the objects back to XML format and writing the XML data into a new disk file named junk.xml.

Everything in this fragment is simple Java programming using the Enumeration interface, so I won't bother to provide an explanation. If this is new to you, you should review the material in earlier lessons.

      Enumeration theEnum = theExam.elements();
      while(theEnum.hasMoreElements()){
        Sax02D theDataObj = (Sax02D)theEnum.nextElement();
        System.out.println(
            "Problem Number: " + theDataObj.problemNumber);
        System.out.println(
                       "Question: " + theDataObj.question);
        System.out.println(
                               "Type: " + theDataObj.type);
        System.out.println(
            "Number choices: " + theDataObj.numberChoices);
        System.out.println(
                             "Valid: " + theDataObj.valid);
      
        //The XML file contains a field that specifies the
        // number of choices that will be presented for the
        // problem on a multiple-choice test.  That value
        // should specify the number of data values in the
        // array.  Use that value to extract the data
        // values from the array.
        for(int cnt = 0; 
                      cnt<theDataObj.numberChoices; cnt++){
          System.out.println("Item number " 
                      + cnt + ": " + theDataObj.item[cnt]);
        }//end for loop
      
        System.out.println(
                 "Explanation: " + theDataObj.explanation);
      }//end while(theEnum.hasMoreElements())

Convert objects to XML

The next fragment instantiates an object of type Sax02C and invokes the writeTheFile() method on that object to convert the data stored in the Vector object to XML format and write it into an output file named junk.xml. The particulars of the Sax02C class used to accomplish that will be discussed later in this lesson.

      Sax02C xmlWriter = new Sax02C(theExam,"junk.xml");
      xmlWriter.writeTheFile();
    
    }catch(Exception e){System.out.println(e);}
  }//end main
}//end class Sax02 

That completes the main() method and also completes the class definition for Sax02. I will discuss the process of converting the XML file to a set of objects in the next section.

Sax02B.java

The classes in this file convert the XML file to a set of objects of type Sax02D stored in a Vector. Note that this is a highly specialized class designed to accommodate a very specific XML format.

Some required import directives

The first fragment shows required import directives simply to illustrate that the program imports packages that are part of the IBM parser library and are not part of the standard Java API.

import org.xml.sax.*;
import org.xml.sax.helpers.ParserFactory;

Purpose

The purpose of the class named Sax02B is to parse a specified XML file, creating an object for each problem specification in the file, and writing the references to those objects in a Vector passed in as a parameter named theExam.

The main thread should go to sleep to await completion of the parse. When the parse is complete the constructor parameter named mainThread is used to interrupt the sleeping thread and wake it up.

The Sax02B class

The next fragment shows the beginning of the class definition including the declaration (and initialization) of some instance variables along with the constructor. All that the constructor does is to save the incoming parameters in the instance variables.

class Sax02B extends Thread{
  String XMLfile;
  Vector theExam;
  Thread mainThread;
  static final String parserClass = 
                           "com.ibm.xml.parsers.SAXParser";
  //-----------------------------------------------------//
                             
  //Constructor
  Sax02B(String XMLfile,Vector theExam,Thread mainThread){
    this.XMLfile = XMLfile;
    this.theExam = theExam;
    this.mainThread = mainThread;
  }//end constructor

Identifying the parser package

Note that the code in the previous fragment defines and initializes a String reference named parserClass that identifies the class from which the parser will be instantiated. The particular string used here identifies the IBM parser.

The run() method

As you will recall, the run() method is where the action takes place in a Java thread. The next fragment shows the beginning of the run() method for this thread.

A SAX factory method

The first statement inside the run() method uses a SAX factory method along with the identification of the parser vender to create an object of type Parser. (This is actually an object of type Interface org.xml.sax.Parser.)

All SAX parsers must implement this interface. It allows applications to register handlers for different types of events and to initiate a parse from a URI, or a character stream.

public void run(){
    try{
      Parser parser = 
                     ParserFactory.makeParser(parserClass);

The bottom line on makeParser()

The bottom line is that the makeParser() method of the ParserFactory class creates an instance (object) of a class that implements the Parser interface.

The object is based on a String that specifies the class libraries provided by the vendor of the SAX based parser software.

This parser object can then be used to perform the routine processing of the XML file, generating a series of document events and potentially error events based on the information in the file.

A DocumentHandler object

The next fragment instantiates an object of the DocumentHandler type to handle events and errors. Note that DocumentHandler is an interface and is not a class.

I will explain how this object performs its work in conjunction with a discussion of the EventHandler class later.

      DocumentHandler handler = 
                      new EventHandler(theExam,mainThread);

Register event handler and error handler

The handler object instantiated above has the ability to handle both document events and error events. In one case, it listens for document events such as the start or end of an element. In the other case, it listens for events caused by errors in the XML data.

Different interfaces for events and errors

Document event methods and error event methods are declared in two different interfaces. The handler object instantiated above is of the type EventHandler. A superclass of that class implements both interfaces making it possible for an object of that type to listen for both types of events. However, it does give rise to the requirement to cast the handler object to type ErrorHandler before registering it on the parser object.

(Note that setDocumentHandler() and setErrorHandler() are listener registration methods and are not methods used to set properties as might be indicated by their names.)

      parser.setDocumentHandler(handler);
      parser.setErrorHandler((ErrorHandler)handler);

Generating events

The single executable statement in the next fragment is what makes it all happen. This statement executes the parse() method on the object of type Parser to make a pass through the XML document specified by the parameter.

While making the pass through the document, this method generates a variety of document events and error events as the various tags, attributes, and data values in that document are encountered.

That completes the definition of the run() method of the class named Sax02B.

But, don't go away. There is much more that we need to cover in order to understand this program.

      parser.parse(XMLfile);
    }catch(Exception e){System.out.println(e);}
  }//end run
}//end class Sax02B

The EventHandler class

The methods of this class are listeners for document events and error events. The next fragment shows the beginning of the class that includes the constructor and the declaration and initialization of some instance variables.

This class extends the class named HandlerBase. The class named HandlerBase, which is the default base class for handlers, implements the default behavior for four different SAX interfaces:

The first two of these interfaces are of interest to us in this lesson. We will pursue the other two interfaces in subsequent lessons.

The constructor

The constructor simply stores the incoming parameters in the corresponding instance variables.

The instance variables

Three of the instance variables are initialized to a value of false. These are status flags used later by the program to keep track of the type of element being processed at any particular point in time. The type of element involved in each case is indicated by the name of the variable.

class EventHandler extends HandlerBase{
  Vector theExam; //store objects in this Vector
  Thread mainThread; //wake this thread upon completion

  Sax02D theDataObj; //create objects of this type

  boolean inQuestionElement = false;
  boolean inItemElement = false;
  boolean inExplanationElement = false;

  int itemNumber;
//-----------------------------------------------------//
  
  //Constructor
  EventHandler(Vector theExam,Thread mainThread){
    this.theExam = theExam;
    this.mainThread = mainThread;
  }//end constructor
  //-----------------------------------------------------//

Overriding methods to provide functionality

The EventHandler class overrides the event handling methods of the DocumentHandler interface and the ErrorHandler interface to provide the desired functionality for the program.

The next fragment shows the first two overridden event-handling methods.

startDocument() and endDocument() methods

The Parser object invokes these two overridden methods when the parse process encounters the beginning or the end of the XML document.

The default versions of these two methods return quietly doing nothing. Application writers can override the startDocument() method to take specific actions at the beginning of a document (such as creating an output file).

Similarly the application writer can override endDocument() to take specific action at the end of a document (such as closing a file).

Note that these methods don't receive any parameters.

Action at beginning of a document

In this program, there is nothing that needs to be done at the beginning of a document. Therefore, I could have accepted the default version of startDocument() without overriding it. However, I elected to override it to do nothing simply for completeness of the illustration.

Action at end of a document

However, there is something that needs to be done at the end of the document. In particular, the main thread needs to be awakened.

This is accomplished in the endDocument() method by invoking the interrupt() method on the reference to the main thread. This causes the main thread to awaken from its sleep and throw an exception. The exception is ignored in this case since the objective is to awaken the main thread so that it can continue its task and display the contents of the objects just created from the incoming XML file.

//Handle event at beginning of document
  public void startDocument(){
    //Not required.  Nothing to do here.
  }//end startDocument()
  //-----------------------------------------------------//
    
  //Handle event at end of document.
  public void endDocument(){
    mainThread.interrupt();//wake up the main thread
  }//end endDocument()

The start element event

The next overridden handler method is invoked at the start of every element. For review, the beginning of an element might look like this in an XML document:

<poem PoemNumber="1" DummyAttribute="dummy value">

The boldface portions are commonly referred to as attributes. An element can contain none, one, or more attributes. Each attribute has a name and a value.

In this case, the element named poem contains two attributes named PoemNumber and DummyAttribute (the name of the attribute is unrelated to the name of the element).

Each attribute also has a value, which is enclosed in double quotation marks. In this case, the values for the two attributes are 1 and dummy value respectively.

The startElement() event handler method

The event handler method that gets called when the parser encounters a new element is startElement(), as shown in the next fragment.

This method receives two parameters. The first parameter is a String containing the name of the element. The second parameter is a reference to an object of type AttributeList containing information about the attributes.

Only the beginning portion of the startElement() method is shown in the following fragment, as the method is fairly large. Overall, this method identifies the type of element and takes the appropriate action for each type.

Some identifications result in no action being taken because no action is required for that element type. Code that takes no action was included simply to illustrate how to take a particular action for those element types if needed.

This method contains a series of if-else statements that determine the type of element and deal with it appropriately based on its type.

Element type exam

The beginning of the if-else chain is shown in the following fragment. In this case, a test is made to determine if the element is of type exam. If so, no action is taken because there is no specific action needed at the start of an element of type exam.

(The exam type could be ignored in the startElement() method.)

  public void startElement(String elementName,
                   AttributeList atts) throws SAXException{
    if(elementName.equals("exam")){
      //Not required, nothing to do here.
    }//end if(elementName.equals("exam"))

Element type problem

Several different actions are needed at the start of an element of type problem as reflected in the following fragment. This fragment

    else if(elementName.equals("problem")){
      itemNumber = 0;//initialize the item counter
      //instantiate a new data object
      theDataObj = new Sax02D();
      //begin populating the object with attribute value
      theDataObj.problemNumber = 
          Integer.parseInt(atts.getValue("problemNumber"));
    }//end if(elementName.equals("problem"))

Element type question

Three types of elements have text content that will need to be extracted and stored in the object. Those types are:

The method used to extract the text content from the XML file doesn't provide any indication of the element type to which the content belongs. Therefore, it is necessary for the program to determine and remember the type of element being processed before extracting the text content. This is accomplished by setting the three status flags discussed earlier to values of true or false.

The following fragment tests to determine if the element is of type question. If so, it sets the flag named inQuestionElement to a value of true. This flag will be consulted by the characters() method later when extracting text to determine where to put that text in the object being populated.

    else if(elementName.equals("question")){
      //set flag that identifies the type of element
      inQuestionElement = true;
    }//end if(elementName.equals("question"))

Element type answer 

Elements of type answer contain one or more elements of type item. In addition, they contain attributes that are used to determine how to make use of the information contained in the elements of type item.

The next fragment tests to determine if the element is of type answer. If so, it extracts the values for the attributes named type, numberChoices, and valid, and saves that information in the appropriate fields of the object being populated.

    else if(elementName.equals("answer")){
      //populate data object with attribute values
      theDataObj.type = atts.getValue("type");
      theDataObj.numberChoices = 
          Integer.parseInt(atts.getValue("numberChoices"));
      theDataObj.valid = atts.getValue("valid");
    }//end if(elementName.equals("answer")

Element types item and explanation

The next fragments tests to determine if the element is of type item or type explanation. If so, it sets the status flag identifying the element type as one of those types.

The fragment also throws an exception if a match for the element type has not been found. Control should never reach that point unless the types of elements in the XML file are different from what this program was designed to process.

    else if(elementName.equals("item")){
      //set flag that identifies the type of element
      inItemElement = true;
    }//end if(elementName.equals("item"))
    
    else if(elementName.equals("explanation")){
      //set flag that identifies the type of element
      inExplanationElement = true;
    }//end if(elementName.equals("explanation"))
    
    //should never reach here
    else throw new SAXException(
                   "Invalid element name: " + elementName);

  }//end start element

That ends the method that is invoked at the start of each element. When control reaches this point, attribute values have been extracted and stored in the object being populated. Also, in some cases, a flag has been set  identifying the type of element that triggered the event.

The endElement() handler

Because it doesn't need to deal with attributes, the overridden endElement() event handler is simpler. This method is invoked when the parser encounters an end tag for an element.

The method receives a single parameter that is the name of the element.

Identify the type of element

This method identifies the type of element and takes the appropriate action for each type. Some identifications result in no action being taken because no action is required for that element type. In those cases, I could have simply ignored the element type. This code was included simply to illustrate how to take a particular action for those element types if needed.

As in the overridden startElement() method, this method executes a series of if-else statements to identify the type of element and take the appropriate action.

Element type exam

The next fragment shows the processing for the end of the element of type exam. In this case, no action is required, so the code essentially does nothing.

  public void endElement (String elementName) 
                                      throws SAXException{
    if(elementName.equals("exam")){
      //Not required.  Nothing to do here.
    }//end if(elementName.equals("exam"))

Element type problem 

The program extracts the information from the XML file and creates one object for each element of type problem contained in that file. Therefore, the end of an element of type problem signals the need to store the object that has just been created and populated in the object of type Vector.

This is accomplished by invoking the addElement() method on the Vector object named theExam as shown in the next fragment.

    else if(elementName.equals("problem")){
      theExam.addElement(theDataObj);
    }//end if(elementName.equals("problem"))

Element types question and answer 

The next fragment shows the processing applied when the end of an element of either the question or answer types is encountered.

In the first case, the status flag is cleared to indicate that an element of the type question is no longer being processed.

In the second case of type answer, no processing is required. This type could simply have been ignored in this method.

    else if(elementName.equals("question")){
      inQuestionElement = false;
    }//end if(elementName.equals("question"))
      
    else if(elementName.equals("answer")){
      //Not required.  Nothing to do here.
    }//end if(elementName.equals("answer"))

Element type item

Code in the next fragment clears the flag that indicates that an element of type item is being processed when the end of an item element is encountered.

In addition, you will recall that an answer element can contain from one to five elements of type item. Subsequent processing needs to know which item is being processed. Therefore, when the end of an item element is encountered, the following code increments a counter that is keeping track of the item number. 

    else if(elementName.equals("item")){
      inItemElement = false;
      itemNumber += 1;
    }//end if(elementName.equals("item"))

Element type explanation 

The next fragment tests to determine if the element is of type explanation. If so, it clears the flag to indicate that an element of type explanation is no longer being processed.

In addition, the fragment throws an exception if a match for the element type was not found in the previous code within the endElement() method. This shouldn't happen unless the XML file contains an element type for which this program was not designed.

    else if(elementName.equals("explanation")){
      //Set flag showing that an element of this type
      // is no longer being processed
      inExplanationElement = false;
    }//end if(elementName.equals("explanation"))
      
    //should never reach here
    else throw new SAXException(
                   "Invalid element name: " + elementName);

  }//end endElement()    

That ends the discussion of the endElement() event handler.

The content of an element

The content of an XML element is the text that appears between the beginning and ending tags. The next fragment shows the event handler that is invoked by the parser when the parser encounters content. The name of the content handler method is characters().

Note that the character data may arrive all together or may arrive in chunks. Therefore it is necessary to concatenate the chunks when reconstructing the content.

In a nutshell, this method receives a character array containing the content of an element. The overridden version of the method in this sample program first determines the type of element to which the content belongs, and then stores it in the object being populated according to the type of element.

Element type question

The next fragment shows the beginning of the characters() method and shows the first of several if-else statements used to determine the type of element being processed to determine where to store the character data.

The fragment also shows how the program tests to determine if the chunk of characters that generated the event are the first characters received for that particular element. If not, the characters received are concatenated onto the characters previously received and stored in the object for that element type.

  public void characters(char[] ch,int start,int length){
    if(inQuestionElement){//if processing question element
      if(theDataObj.question == null){//if first chunk
        //save first chunk in the data object
        theDataObj.question = 
                             new String(ch, start, length);
      }//end if(theDataObj.question == null)
        
      else{
        //Not first chunk.  Concatenate this chunk with 
        // previous data in the data object.
        theDataObj.question += 
                             new String(ch, start, length);
      }//end else
    }//end if(inQuestionElement)

Element type item 

Processing of the content of elements of type item is complicated by the fact that each element of type answer can contain up to five elements of type item.

Recall that the number of the item is maintained in an instance variable of the object named itemNumber. The value of this instance variable is used to store the concatenated content string into an element of a five-element array in the object based on the item number.

Otherwise, this code is essentially the same as the code in the previous fragment.

    else if(inItemElement){//if processing item element
      if(itemNumber < 5){//hard code the limit for brevity
        if(theDataObj.item[itemNumber] == null){
          //This is first chunk.  Store it in data object
          theDataObj.item[itemNumber] = 
                             new String(ch, start, length);
        }//end if(theDataObj.item[itemNumber] == null)
          
        else{//Not first chunk.  Concatenate it.
          theDataObj.item[itemNumber] += 
                             new String(ch, start, length);
        }//end else
      }//end if(itemNumber < 5)
    }//end if(inItemElement)

Element type explanation 

The only other type of element that can contain content is type explanation. The following code fragment extracts the content for type explanation, concatenating the chunks if necessary, and stores it in the object being populated.

    else if(inExplanationElement){
      if(theDataObj.explanation == null){
        //This is first chunk.  Store it in data object.
        theDataObj.explanation = 
                             new String(ch, start, length);
      }//end if(theDataObj.explanation == null)
        
      else{//Not first chunk.  Concatenate it
        theDataObj.explanation += 
                             new String(ch, start, length);
      }//end else
    }//end if(inExplanationElement)
  }//end characters()

That ends the discussion of the event handling method named characters().

The ErrorHandler interface

That brings us to the methods that are declared in the interface named ErrorHandler. This interface, which declares three different handler methods, is the Basic interface for SAX error handlers.

A SAX application that needs to implement customized error handling, must implement this interface. Then it must register an object of the interface type with the SAX parser using the parser's setErrorHandler() method. The parser will then report all errors and warnings through this interface.

The code to accomplish this in this program is essentially the same as was explained in an earlier lesson on SAX. Therefore, I won't discuss that code further in this lesson. You will find a complete listing of that code near the end of this lesson.

Producing the output XML file

We're now about two-thirds of the way through our discussion of the sample program. There is one major area left to cover -- the creation of an XML file based on the contents of the objects stored in the Vector.

Sax02C.java

This is accomplished by the class named Sax02C. The purpose of this class is to provide a utility capability to convert the data stored in the objects in the Vector to XML format and to write that data into a new XML file.

Note that this is a highly specialized class designed to accommodate a very specific XML format. A more general approach that makes use of the Document Object Model will be presented in a subsequent lesson.

The next fragment shows the beginning of the class along with the constructor. There is nothing new or exciting in this fragment.

class Sax02C{
  Vector theExam;//contains references to the objects
  String XMLfile;//output file name
  
  Sax02C(Vector theExam,String XMLfile){//constuctor
    this.theExam = theExam;
    this.XMLfile = XMLfile;
  }//end constructor

The writeTheFile() method

This is the method that does all of the work in this class. The next fragment shows the beginning of the method. First it gets an output file writer using the standard stream I/O capabilities of Java. Then it writes two lines of XML that are required at the beginning of the XML file independent of the number of problems contained in the exam.

  void writeTheFile() throws Exception{
    //Get an output file writer.
    PrintWriter fileOut = new PrintWriter(
                            new FileOutputStream(XMLfile));
    
    //Write the preliminary material to the output file.
    fileOut.println("<?xml version=\"1.0\"?>");
    fileOut.println("<exam>");

Loop and write

Form this point on, this program is rather brute force, simply extracting data from each object and formatting it into the characters required to meet the XML specification.

The only thing unique or interesting about the code is the requirement to create and substitute XML entity strings for the <, >, &, and " characters. A utility method named strToXML() is provided to accomplish this substitution.

Because of the straightforward nature of the writeTheFile() method, I won't discuss it further at this point.

    Enumeration theEnum = theExam.elements();
    while(theEnum.hasMoreElements()){
      //Get next reference to object from the Vector
      Sax02D theDataObj = (Sax02D)theEnum.nextElement();
      
      //Write the contents of the instance variables in the
      // object to the output file in XML format.  Note
      // that it is necessary to use the strToXML() method
      // to ensure that no extraneous <, >, &, or "
      // characters are written into the XML file.
      
      //Write the <problem> element and its attribute to
      // the XML file.
      fileOut.println("<problem problemNumber=\"" 
        + strToXML("" + theDataObj.problemNumber) + "\">");
      
      //Write the <question> element and its content to the
      // XML file.
      fileOut.println("<question>" 
          + strToXML(theDataObj.question) + "</question>");
      
      //Write the various parts of the <answer> element to
      // the XML file.  This includes tags, attributes,
      // and content.
      fileOut.print("<answer type=\"" 
                      + strToXML(theDataObj.type) + "\" ");
      fileOut.print("numberChoices=\"" 
        + strToXML("" + theDataObj.numberChoices) + "\" ");
      fileOut.println("valid=\"" 
                     + strToXML(theDataObj.valid) + "\">");
      
      //The instance variable named item is actually an
      // array containing up to five data values.  Use a
      // for loop to extract the data from the array and
      // write them into the XML file.
      for(int cnt = 0; 
                    cnt < theDataObj.numberChoices; cnt++){
        fileOut.println("<item>" 
             + strToXML(theDataObj.item[cnt]) + "</item>");
      }//end for loop
      
      //Close out the <answer> element.
      fileOut.println("</answer>");
      
      //Write the <explanation> element and its content
      // to the XML file.
      fileOut.println("<explanation>" 
                        + strToXML(theDataObj.explanation) 
                                       + "</explanation>");
      
      //Close out the <problem> element.
      fileOut.println("</problem>");
    }//end while
    
    //Close out the <exam> element
    fileOut.println("</exam>");
    
    //Close the output XML file.
    fileOut.close();
    
  }//end writeTheFile()

The strToXML() method 

The purpose of this method is to modify and return a String object replacing angle brackets, ampersands, and quotation marks with XML entities.

This is also a very straightforward method that shouldn't need any additional discussion.

  private String strToXML(String string) {
    StringBuffer str = new StringBuffer();

    int len = (string != null) ? string.length() : 0;
    
    for (int cnt = 0; cnt < len; cnt++) {
      char ch = string.charAt(cnt);
      switch (ch) {
        case '<': {
          str.append("&lt;");
          break;
        }//end case '<'
        case '>': {
          str.append("&gt;");
          break;
        }//end case '>'
        case '&': {
          str.append("&amp;");
          break;
        }//end case '&'
        case '"': {
          str.append("&quot;");
          break;
        }//end case '"':
        default: {
          str.append(ch);
        }//end default
      }//end switch
    }//end for loop
    return str.toString();
  }//end strToXML()

}//end class Sax02C   

That ends the discussion of the class named Sax02C and ends the discussion of the sample program.

 

Program Listings

A complete listing of the programs and the XML file is contained in this section.

<?xml version="1.0"?>
<exam>
<problem problemNumber="1">
<question>
Which of the following are grown 
in a vegetable garden?
</question>
<answer type="multiple" numberChoices="4" valid="0,1,3">
<item>Carrots</item>
<item>Cabbage</item>
<item>Apples</item>
<item>Lettuce</item>
</answer>
<explanation>
Carrots, cabbage, and lettuce are grown in
a vegetable garden. Apples are grown in an orchard.
</explanation>
</problem>

<problem problemNumber="2">
<question>
Which one of the following requires 
XML entities?
</question>
<answer type="single" numberChoices="3" valid="1">
<item>Tom</item>
<item>&quot;&lt;Mary &amp; Sue&gt;&quot;</item>
<item>Dick</item>
</answer>
<explanation>
Left and right angle brackets, ampersands, and quotation 
marks must be represented in XML by entities
</explanation>
</problem>

</exam>

.

/*File Sax02.java Copyright 1999, R.G.Baldwin
This program, when used in combination with the following
files:
  Sax02.java
  Sax02B.java
  Sax02C.java
  Sax02D.java
  Sax02.xml
  
illustrates the conversion of an XML file to a set of 
objects, and the conversion of those objects back to a
new XML file.

This program reads an XML file with a specific format, 
creates a Java object for each record in the file, (where
each record represents a test problem) and stores the 
objects in a Vector.  Classes in the file named Sax02B.java
are used for this purpose.

After generating the Vector object containing the XML 
records, the program displays each of the instance 
variables in each object whose reference is stored in the
Vector.  This is a very simple illustration of how the
XML data can be processed after first having converted it
into object format.

Then the program generates an XML file containing a record 
for each element in the vector, and writes it out to a disk
file named junk.xml.  Classes in the file named Sax02C.java
are used for this purpose.

A class definition in the file named Sax02D.java is used
by all of the classes that make up this program as a
common class for representing an XML record as an object.

No particular effort was expended to make this program
robust.  In particular, if it encounters an XML file in
the wrong format, it may throw an exception, or it may
continue to run, but it may not work properly.

The program was tested using JDK 1.2 under Win95.  It
also requires the IBM XML4Java (or some suitable
substitute) parser.

The program was tested with an XML file named Sax02.xml
that contained the following (note that line breaks were
manually inserted here to force the text to fit in this
format):
  
<?xml version="1.0"?>
<exam>
<problem problemNumber="1">
<question>Which of the following are grown 
in a vegetable garden?</question>
<answer type="multiple" numberChoices="4" valid="0,1,3">
<item>Carrots</item>
<item>Cabbage</item>
<item>Apples</item>
<item>Lettuce</item>
</answer>
<explanation>Carrots, cabbage, and lettuce are grown in
a vegetable garden. Apples are grown in an orchard.
</explanation>
</problem>

<problem problemNumber="2">
<question>Which one of the following requires 
XML entities?</question>
<answer type="single" numberChoices="3" valid="1">
<item>Tom</item>
<item>&quot;&lt;Mary &amp; Sue&gt;&quot;</item>
<item>Dick</item>
</answer>
<explanation>Left and right angle brackets, ampersands, and
 quotation 
marks must be represented in XML by entities</explanation>
</problem>

</exam>

The program produced the following output on the screen.
(Note that line breaks were manually inserted here to force
the text to fit in this format)

Problem Number: 1
Question: Which of the following are grown 
in a vegetable garden?
Type: multiple
Number choices: 4
Valid: 0,1,3
Item number 0: Carrots
Item number 1: Cabbage
Item number 2: Apples
Item number 3: Lettuce
Explanation: Carrots, cabbage, and lettuce are grown in
a vegetable garden. Apples are grown in an orchard.
Problem Number: 2
Question: Which one of the following requires 
XML entities?
Type: single
Number choices: 3
Valid: 1
Item number 0: Tom
Item number 1: "<Mary & Sue>"
Item number 2: Dick
Explanation: Left and right angle brackets, ampersands, and
 quotation 
marks must be represented in XML by entities

The program produced an output file named junk.xml
containing the following (Note that line breaks were 
manually inserted here to force the text to fit in this 
format):
  
<?xml version="1.0"?>
<exam>
<problem problemNumber="1">
<question>Which of the following are grown 
in a vegetable garden?</question>
<answer type="multiple" numberChoices="4" valid="0,1,3">
<item>Carrots</item>
<item>Cabbage</item>
<item>Apples</item>
<item>Lettuce</item>
</answer>
<explanation>Carrots, cabbage, and lettuce are grown in
a vegetable garden. Apples are grown in an orchard.
</explanation>
</problem>
<problem problemNumber="2">
<question>Which one of the following requires 
XML entities?</question>
<answer type="single" numberChoices="3" valid="1">
<item>Tom</item>
<item>&quot;&lt;Mary &amp; Sue&gt;&quot;</item>
<item>Dick</item>
</answer>
<explanation>Left and right angle brackets, ampersands, and
 quotation 
marks must be represented in XML by entities</explanation>
</problem>
</exam>

**********************************************************/
import java.util.*;

class Sax02 {
  static Vector theExam = new Vector();
  static Thread mainThread = Thread.currentThread();
  static String XMLfile = "Sax02.xml";

  public static void main (String args[]){
    try{
    
      //Launch a thread that will parse the XML file, 
      // creating an object for each problem specification
      // in the file, and storing the references to those 
      // objects in the Vector referred to by theExam.
      Sax02B fetchItObj = 
                    new Sax02B(XMLfile,theExam,mainThread);
      fetchItObj.start();//start the thread running
    
      //Sleep until parse is complete.  Then wake up when
      // interrupted by the parser and do something useful
      // with the data in the Vector.  If parsing is not
      // completed during the first 100000 milliseconds, 
      // just go back to sleep. (However, that would be an
      // awfully long time to complete the parse.)
      try{
        while(true){//sleep and loop until interrupted
          Thread.currentThread().sleep(100000);
        }//end while
      }catch(InterruptedException e){
        //Wake up and do something with the Vector data
      }//end catch
    
      //At this point, each of the test "problems" in the
      // XML file has been converted into a Java object.
      // References to those objects have been stored in a
      // Vector object.  This Vector object can be used for
      // any number of useful purposes, such as editing the
      // contents of the individual objects, sorting the
      // problems, administering the test, etc.
    
      //In this sample program, I will simply display all
      // of the data in each object before converting the
      // objects back to XML format and writing the XML
      // data into a new disk file named junk.xml.
      Enumeration theEnum = theExam.elements();
      while(theEnum.hasMoreElements()){
        Sax02D theDataObj = (Sax02D)theEnum.nextElement();
        System.out.println(
            "Problem Number: " + theDataObj.problemNumber);
        System.out.println(
                       "Question: " + theDataObj.question);
        System.out.println(
                               "Type: " + theDataObj.type);
        System.out.println(
            "Number choices: " + theDataObj.numberChoices);
        System.out.println(
                             "Valid: " + theDataObj.valid);
      
        //The XML file contains a field that specifies the
        // number of choices that will be presented for the
        // problem on a multiple-choice test.  That value
        // should specify the number of data values in the
        // array.  Use that value to extract the data
        // values from the array.
        for(int cnt = 0; 
                      cnt<theDataObj.numberChoices; cnt++){
          System.out.println("Item number " 
                      + cnt + ": " + theDataObj.item[cnt]);
        }//end for loop
      
        System.out.println(
                 "Explanation: " + theDataObj.explanation);
      }//end while(theEnum.hasMoreElements())

      //Now convert each test "problem" object referenced
      // in the Vector to XML format and write the XML data
      // into a new file on the disk named junk.xml.
      Sax02C xmlWriter = new Sax02C(theExam,"junk.xml");
      xmlWriter.writeTheFile();
    
    }catch(Exception e){System.out.println(e);}
  }//end main
}//end class Sax02
//=======================================================// 

.

/*File Sax02B.java Copyright 1999, R.G.Baldwin
This program, when used in combination with the following
files:
  Sax02.java
  Sax02B.java
  Sax02C.java
  Sax02D.java
  Sax02.idl
  
illustrates the conversion of an XML file to a set of 
objects, and the conversion of those objects back to a
new XML file.

The classes in this particular file convert the XML file
to a set of objects of type Sax02D stored in a Vector

Note that this is a highly specialized class designed
to accommodate a very specific XML format.

See Sax02.java for a fuller description.

The program was tested using JDK 1.2 under Win95.
**********************************************************/
import java.util.*;
import org.xml.sax.*;
import org.xml.sax.helpers.ParserFactory;

//The purpose of this class is to parse a specified XML
// file, creating an object for each problem specification
// in the file, and writing the references to those
// objects in a Vector passed in as a parameter named
// theExam.  The main thread should go to sleep to await
// completion of the parse.  When the parse is complete
// the constructor parameter named mainThread is used
// to interrupt the sleeping thread and wake it up.
class Sax02B extends Thread{
  String XMLfile;
  Vector theExam;
  Thread mainThread;
  static final String parserClass = 
                           "com.ibm.xml.parsers.SAXParser";
  //-----------------------------------------------------//
                             
  //Constructor
  Sax02B(String XMLfile,Vector theExam,Thread mainThread){
    this.XMLfile = XMLfile;
    this.theExam = theExam;
    this.mainThread = mainThread;
  }//end constructor
  //-----------------------------------------------------//
    
  public void run(){
    try{
      Parser parser = 
                     ParserFactory.makeParser(parserClass);
      //Instantiate an event and error handler
      DocumentHandler handler = 
                      new EventHandler(theExam,mainThread);
      //Register the event handler and the error handler
      parser.setDocumentHandler(handler);
      parser.setErrorHandler((ErrorHandler)handler);
      //Parse the document to create the events causing
      // the objects to be created and saved in the 
      // Vector passed as a parameter to the constructor.
      parser.parse(XMLfile);
    }catch(Exception e){System.out.println(e);}
  }//end run
}//end class Sax02B
//=======================================================//

//Methods of this class are listeners for document events
// and error events.
class EventHandler extends HandlerBase{
  Vector theExam; //store objects in this Vector
  Thread mainThread; //wake this thread upon completion
  Sax02D theDataObj; //create objects of this type
  boolean inQuestionElement = false;
  boolean inItemElement = false;
  boolean inExplanationElement = false;
  int itemNumber;
  //-----------------------------------------------------//
  
  //Constructor
  EventHandler(Vector theExam,Thread mainThread){
    this.theExam = theExam;
    this.mainThread = mainThread;
  }//end constructor
  //-----------------------------------------------------//

  //Handle event at beginning of document
  public void startDocument(){
    //Not required.  Nothing to do here.
  }//end startDocument()
  //-----------------------------------------------------//
    
  //Handle event at end of document.
  public void endDocument(){
    mainThread.interrupt();//wake up the main thread
  }//end endDocument()
  //-----------------------------------------------------//

  //Handle event at beginning of element.  This method
  // identifies the type of element and takes the 
  // appropriate action for each type.  Some 
  // identifications result in no action being taken
  // because no action is required for that element type.
  // This code was included simply to illustrate how to
  // take a particular action for those element types if
  // needed.
  public void startElement(String elementName,
                   AttributeList atts) throws SAXException{
    if(elementName.equals("exam")){
      //Not required, nothing to do here.
    }//end if(elementName.equals("exam"))
      
    else if(elementName.equals("problem")){
      itemNumber = 0;//initialize the item counter
      //instantiate a new data object
      theDataObj = new Sax02D();
      //begin populating the object with attribute value
      theDataObj.problemNumber = 
          Integer.parseInt(atts.getValue("problemNumber"));
    }//end if(elementName.equals("problem"))
      
    else if(elementName.equals("question")){
      //set flag that identifies the type of element
      inQuestionElement = true;
    }//end if(elementName.equals("question"))
    
    else if(elementName.equals("answer")){
      //populate data object with attribute values
      theDataObj.type = atts.getValue("type");
      theDataObj.numberChoices = 
          Integer.parseInt(atts.getValue("numberChoices"));
      theDataObj.valid = atts.getValue("valid");
    }//end if(elementName.equals("answer")
    
    else if(elementName.equals("item")){
      //set flag that identifies the type of element
      inItemElement = true;
    }//end if(elementName.equals("item"))
    
    else if(elementName.equals("explanation")){
      //set flag that identifies the type of element
      inExplanationElement = true;
    }//end if(elementName.equals("explanation"))
    
    //should never reach here
    else throw new SAXException(
                   "Invalid element name: " + elementName);

  }//end start element
  //-----------------------------------------------------//

  //Handle event at end of element. This method identifies
  // the type of event and takes the appropriate action 
  // for each type.  Some identifications result in no
  // action being taken because no action is required for
  // that element type.  This code was included simply to
  // illustrate how to take a particular action for those
  // element types if needed.
  
  public void endElement (String elementName) 
                                      throws SAXException{
    if(elementName.equals("exam")){
      //Not required.  Nothing to do here.
    }//end if(elementName.equals("exam"))
      
    else if(elementName.equals("problem")){
      //Store the object that was created and populated
      // for this element in the Vector.
      theExam.addElement(theDataObj);
    }//end if(elementName.equals("problem"))
      
    else if(elementName.equals("question")){
      //Set flag showing that an element of this type
      // is no longer being processed
      inQuestionElement = false;
    }//end if(elementName.equals("question"))
      
    else if(elementName.equals("answer")){
      //Not required.  Nothing to do here.
    }//end if(elementName.equals("answer"))
      
    else if(elementName.equals("item")){
      //Set flag showing that an element of this type
      // is no longer being processed
      inItemElement = false;
      //Increment the item counter
      itemNumber += 1;
    }//end if(elementName.equals("item"))
      
    else if(elementName.equals("explanation")){
      //Set flag showing that an element of this type
      // is no longer being processed
      inExplanationElement = false;
    }//end if(elementName.equals("explanation"))
      
    //should never reach here
    else throw new SAXException(
                   "Invalid element name: " + elementName);

  }//end endElement()
  //-----------------------------------------------------//
      
  //Handle events caused by encountering character data
  // in the XML file.  Note that the character data may 
  // arrive altogether or may arrive in chunks.
  // Therefore it is necessary to concatenate the chunks.
  public void characters(char[] ch,int start,int length){
    if(inQuestionElement){//if processing question element
      if(theDataObj.question == null){//if first chunk
        //save first chunk in the data object
        theDataObj.question = 
                             new String(ch, start, length);
      }//end if(theDataObj.question == null)
        
      else{
        //Not first chunk.  Concatenate this chunk with 
        // previous data in the data object.
        theDataObj.question += 
                             new String(ch, start, length);
      }//end else
    }//end if(inQuestionElement)
      
    else if(inItemElement){//if processing item element
      if(itemNumber < 5){//hard code the limit for brevity
        if(theDataObj.item[itemNumber] == null){
          //This is first chunk.  Store it in data object
          theDataObj.item[itemNumber] = 
                             new String(ch, start, length);
        }//end if(theDataObj.item[itemNumber] == null)
          
        else{//Not first chunk.  Concatenate it.
          theDataObj.item[itemNumber] += 
                             new String(ch, start, length);
        }//end else
      }//end if(itemNumber < 5)
    }//end if(inItemElement)
      
    else if(inExplanationElement){
      if(theDataObj.explanation == null){
        //This is first chunk.  Store it in data object.
        theDataObj.explanation = 
                             new String(ch, start, length);
      }//end if(theDataObj.explanation == null)
        
      else{//Not first chunk.  Concatenate it
        theDataObj.explanation += 
                             new String(ch, start, length);
      }//end else
    }//end if(inExplanationElement)
  }//end characters()
  //-----------------------------------------------------//
    
  //That is the end of all of the normal event handlers.
  //Begin error handlers here.  These methods are declared
  // in the ErrorHandler interface that is implemented by
  // the HandlerBase class and extended by this class.
  
  //Handle a warning
  public void warning(SAXParseException ex){
    System.out.println("[Warning] " +
              getLocationString(ex)+": "+ ex.getMessage());
  }//end warning()
  //-----------------------------------------------------//

  //Handle an error
  public void error(SAXParseException ex) {
    System.out.println("[Error] "+
              getLocationString(ex)+": "+ ex.getMessage());
  }//end error()
  //-----------------------------------------------------//

  //Handle a fatal error
  public void fatalError(SAXParseException ex)
                                       throws SAXException{
    System.out.println("[Fatal Error] "+
              getLocationString(ex)+": "+ ex.getMessage());
    System.out.println("Terminating");
    System.exit(1);
  }//end fatalError()
  //-----------------------------------------------------//
  
  //Private method called by error handlers to return
  // information regarding the point in the document where
  // the error was detected by the parser.  
  private String getLocationString(SAXParseException ex){
    StringBuffer str = new StringBuffer();

    //Get SystemId, display it, and use it to get the 
    // name of the file being parsed
    String systemId = ex.getSystemId();
      if(systemId != null){
        System.out.println("systemID: " + systemId);
        //get file name from end of systemID
        int index = systemId.lastIndexOf('/');
        if(index != -1){
          systemId = systemId.substring(index + 1);
        }//end if(index..
        str.append(systemId);
      }//end if(systemID...
      //now get and append location information
      str.append(':');
      str.append(ex.getLineNumber());
      str.append(':');
      str.append(ex.getColumnNumber());

      return str.toString();

    }//end getLocationString()

}//end class EventHandler
//=======================================================// 

.

/*File Sax02C.java Copyright 1999, R.G.Baldwin
This program, when used in combination with the following
files:
  Sax02.java
  Sax02B.java
  Sax02C.java
  Sax02D.java
  Sax02.idl
  
illustrates the conversion of an XML file to a set of 
objects, and the conversion of those objects back to a
new XML file.

The classes in this particular file convert the objects
of type Sax02D stored in a Vector into an XML file.

See Sax02.java for a fuller description.

The program was tested using JDK 1.2 under Win95.
**********************************************************/
import java.util.*;
import java.io.*;

//The purpose of this class is to provide a utility
// capability to convert the data stored in the objects
// in a Vector to XML format and to write that data into
// a new XML file.  Note that this is a highly 
// specialized class designed to accommodate a very
// specific XML format.

class Sax02C{
  Vector theExam;//contains references to the objects
  String XMLfile;//output file name
  
  Sax02C(Vector theExam,String XMLfile){//constuctor
    this.theExam = theExam;
    this.XMLfile = XMLfile;
  }//end constructor
  
  //-----------------------------------------------------//
  
  void writeTheFile() throws Exception{
    //Get an output file writer.
    PrintWriter fileOut = new PrintWriter(
                            new FileOutputStream(XMLfile));
    
    //Write the preliminary material to the output file.
    fileOut.println("<?xml version=\"1.0\"?>");
    fileOut.println("<exam>");

    //Loop and convert each object referenced in the vector
    // to XML format and write it into the output file.
    Enumeration theEnum = theExam.elements();
    while(theEnum.hasMoreElements()){
      //Get next reference to object from the Vector
      Sax02D theDataObj = (Sax02D)theEnum.nextElement();
      
      //Write the contents of the instance variables in the
      // object to the output file in XML format.  Note
      // that it is necessary to use the strToXML() method
      // to ensure that no extraneous <, >, &, or "
      // characters are written into the XML file.
      
      //Write the <problem> element and its attribute to
      // the XML file.
      fileOut.println("<problem problemNumber=\"" 
        + strToXML("" + theDataObj.problemNumber) + "\">");
      
      //Write the <question> element and its content to the
      // XML file.
      fileOut.println("<question>" 
          + strToXML(theDataObj.question) + "</question>");
      
      //Write the various parts of the <answer> element to
      // the XML file.  This includes tags, attributes,
      // and content.
      fileOut.print("<answer type=\"" 
                      + strToXML(theDataObj.type) + "\" ");
      fileOut.print("numberChoices=\"" 
        + strToXML("" + theDataObj.numberChoices) + "\" ");
      fileOut.println("valid=\"" 
                     + strToXML(theDataObj.valid) + "\">");
      
      //The instance variable named item is actually an
      // array containing up to five data values.  Use a
      // for loop to extract the data from the array and
      // write them into the XML file.
      for(int cnt = 0; 
                    cnt < theDataObj.numberChoices; cnt++){
        fileOut.println("<item>" 
             + strToXML(theDataObj.item[cnt]) + "</item>");
      }//end for loop
      
      //Close out the <answer> element.
      fileOut.println("</answer>");
      
      //Write the <explanation> element and its content
      // to the XML file.
      fileOut.println("<explanation>" 
                        + strToXML(theDataObj.explanation) 
                                       + "</explanation>");
      
      //Close out the <problem> element.
      fileOut.println("</problem>");
    }//end while
    
    //Close out the <exam> element
    fileOut.println("</exam>");
    
    //Close the output XML file.
    fileOut.close();
    
  }//end writeTheFile()
  //-----------------------------------------------------//
  
  //The purpose of this method is to modify and return a
  // String object replacing angle brackets, ampersands, 
  // and quotation marks with XML entities.
  
  private String strToXML(String string) {
    StringBuffer str = new StringBuffer();

    int len = (string != null) ? string.length() : 0;
    
    for (int cnt = 0; cnt < len; cnt++) {
      char ch = string.charAt(cnt);
      switch (ch) {
        case '<': {
          str.append("&lt;");
          break;
        }//end case '<'
        case '>': {
          str.append("&gt;");
          break;
        }//end case '>'
        case '&': {
          str.append("&amp;");
          break;
        }//end case '&'
        case '"': {
          str.append("&quot;");
          break;
        }//end case '"':
        default: {
          str.append(ch);
        }//end default
      }//end switch
    }//end for loop
    return str.toString();
  }//end strToXML()

}//end class Sax02C
//=======================================================// 

.

/*File Sax02D.java Copyright 1999, R.G.Baldwin
This program, when used in combination with the following
files:
  Sax02.java
  Sax02B.java
  Sax02C.java
  Sax02D.java
  Sax02.idl
  
illustrates the conversion of an XML file to a set of 
objects, and the conversion of those objects back to a
new XML file.

The class in this particular file provides a common object
format for storage of the data from the XML file.

See Sax02.java for a fuller description.

The program was tested using JDK 1.2 under Win95.
**********************************************************/

//This class is designed to contain an instance variable
// for each piece of data stored in a single test "problem"
// in the XML file.  This class has no methods.  It is
// simply a container for the data extracted from the 
// XML file.  Ths is a very specific class designed for a
// very specific XML format.
class Sax02D{
  int problemNumber;//an attribute of <problem>
  String question;//the content of <question>
  String type;//an attribute of <answer>
  int numberChoices;//an attribute of <answer>
  String valid;//an attribute of <answer>
  //Each populated element in the following array contains
  // the content of one <item> element in the XML file
  // with an arbitrary limit of five such elements.
  String[] item = new String[5];
  String explanation;//the content of <explanation>
}//end Sax02D
//=======================================================// 

-end-