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

XML, A Simple XML Editor using SAX

Java Programming, Lecture Notes # 832, Revised 6/13/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 13, 1999 and has been updated since then.

Introduction

A previous lesson showed you how to use SAX to convert an XML file in a specific format to a set of Java objects stored in a Java Vector. The lesson also showed you how to convert the set of objects stored in the Vector to an XML file.

The earlier lesson pointed out that the objects in the vector could be modified with the net result being that the resulting XML file would be a modified version of the original XML file.

This lesson illustrates the modification of the objects in the Vector file prior to converting them back to XML format and writing them into an output XML file.

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. The Java objects are used as the input initialization data to a GUI editor.

The edited objects are converted to XML format and written into an output file. The resulting XML file 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

The driver portion of the program is contained in the source file named Sax03.java. It illustrates the implementation of a simple XML file editor. It works in conjunction with the following files:

Complete Listings of all five files are provided near the end of the lesson. Taken together, the five files illustrate

Converting XML to Java objects

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

The XML input file can be completely empty, but it must exist. Otherwise, the program will throw a FileNotFoundException. If it is not empty, the contents must match the required XML format.

Editing the data

After generating the Vector object containing the XML records, the program invokes a GUI object editor that provides the capability to

Thus, the program can be used for editing existing XML data or for generating new XML data or both.

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 Sax03C.java are used for this purpose.

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

The object class

A class definition in the file named Sax03D.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 Sax03.xml listed earlier. Note that line breaks were manually inserted in that listing to force the text to fit in this format).

Comments on the GUI

No particular effort was expended to provide a pleasing layout for the GUI. The GUI simply contains TextField and TextArea objects in a FlowLayout in a Panel in the center of a Frame. In addition, the GUI contains some navigation buttons.

Changing the size of the Frame will cause the normal behavior of FlowLayout.

GUI component labels

Except for the buttons used to navigate among the objects being edited, the GUI components are not labeled. The user must know what type of information needs to be entered into each component. However, when a GUI form is presented for a new object, the text fields are initialized with the names of the elements and attributes in the original XML file. This is intended to provide clues to the user as to the intended contents of each text field.

The two numeric fields provide no clues whatsoever. The first numeric field is intended to contain the problem number. The second numeric field is intended to contain the number of items to be displayed as possible answers when the XML file is presented in a GUI for administering the exam. For example, if the problem is a True/False problem, this value should be 2.

The GUI is not user friendly

In short, the GUI is not user-friendly insofar as data entry is concerned, but it does illustrate the concepts involved. Since this lesson is intended to illustrate the concept of editing an XML file, and is not intended to illustrate the layout and design of a GUI, it will be left as an exercise for the student to improve the GUI.

GUI buttons

The GUI provides the following buttons to navigate through the objects for editing and saving:

The behavior of each button is as the name implies. Whenever one of the buttons is selected, the current contents of the GUI fields are saved, possibly overwriting an existing element in the Vector object in which the objects are saved.

In other words, the user process is,

  1. Select an object to be edited
  2. Modify the fields in the GUI
  3. Select another object, or select the Save button described below.

When another object is selected, or the Save button is selected, the edited version of the current object is saved into the Vector before moving on.

Creating new objects

A new object can be created and then edited by selecting the Next button when the last object is currently displayed in the editor. Another good exercise for the student would be to upgrade the program to support the insertion of new objects.

The Save button

This button causes the current edited object to be saved into the Vector.

It also causes the entire Vector object to be converted to a new XML file named junk.xml and written to the disk. (Actually, it simply terminates the Editor thread allowing he main thread to create the XML file.)

Interesting Code Fragments

The entire program consists of a driver file named Sax03.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.

Sax03D.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 item 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. It is essentially a copy of the file named Sax02D from an earlier lesson. The details of the file were explained in that lesson and won't be discussed further in this lesson. It is reproduced here simply for convenience.

class Sax03D{
  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 Sax03D

Sax03.java

Next I will discuss the driver file named Sax03.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.

Virtually everything in this lesson has been discussed in detail in one or more previous lessons. Consequently, the discussion in this lesson will be brief. The purpose of this lesson is to provide you with a high-level discussion to guide you through the entire process.

If you find material in this lesson that you don't understand, go back and review the previous lessons.

class Sax03 {
  static Vector theExam = new Vector();//store objects here
  static Thread mainThread = Thread.currentThread();
  static String XMLfile = "Sax03.xml";//input file name

main()

The next fragment shows the beginning of the main() method. In this method, I spawn a new thread of type Sax03B 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. If the XML file is a long one, some time will pass before 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 Sax03B 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.)

  public static void main (String args[]){
    try{
      //Launch a thread that will parse the XML file 
      Sax03B fetchItObj = 
                    new Sax03B(XMLfile,theExam,mainThread);
      fetchItObj.start();//start the thread running
    
      //Sleep until parse is completed.  
      try{
        while(true){//sleep and loop until interrupted
          Thread.currentThread().sleep(100000);
        }//end while
      }catch(InterruptedException e){
        //Wake up and invoke the editor
      }//end catch

Launch the GUI editor

The next fragment shows the only real difference between this program and the program named Sax02.java that was discussed in the earlier lesson.

This fragment launches a thread that provides the capability to edit the data in the Vector. Then, as in the previous fragment, the main thread goes to sleep until the edit is complete. It wakes up when interrupted by the editor thread and writes the edited data into a new XML file named junk.xml.

If editing is not completed during the 100000-millisecond period, it just goes back to sleep for an additional 100000 milliseconds.

      Editor editor = new Editor(theExam,mainThread);
      editor.start();//start the Editor thread running

try{ while(true){//sleep and loop until interrupted Thread.currentThread().sleep(100000); }//end while }catch(InterruptedException e){ //Wake up and save the data in the Vector as // an XML file. }//end catch

XML file has been converted to objects and edited

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. The objects that comprise the contents of the Vector object have been edited. Editing involves both the modification of existing objects and the creation of new objects.

Convert objects to XML

The next fragment instantiates an object of type Sax03C 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.

      Sax03C xmlWriter = new Sax03C(theExam,"junk.xml");
      xmlWriter.writeTheFile();
    
    }catch(Exception e){System.out.println(e);}
    
    System.exit(0);//Kill all threads and terminate the JVM
  }//end main
}//end class Sax03

That completes the main() method and also completes the class definition for Sax03.

The Editor class

The Editor class is long and tedious, but it is straightforward. Therefore, I will edit large amounts of repetitive material out of the following fragments. The entire class can be viewed in the listings provided near the end of the lesson.

The next fragment shows the beginning of the class definition along with some typical declarations of reference variables.

class Editor extends Thread{
  Vector theExam;//test problem objects are stored here
  Thread mainThread;//save a reference to main thread here
  
  //A series of references to GUI components on the Frame.
  // There is a component for each field in the object,
  // plus some Button objects for control.
  TextField problemNumber = new TextField("00");
  TextArea question = new TextArea(2,40);
  //Several TextField and TextArea reference variables
// were omitted for brevity
Button firstButton = new Button("First");
//Several Button reference varariables omitted for brevity Frame frame;//top level container Panel panel = new Panel();//contains all GUI components int elementNumber = 0;//object currently being edited Sax03D theElement;//reference to object being edited

The constructor

The constructor is also straightforward. An abbreviated listing is shown in the nest fragment. After saving some incoming parameters, it constructs the GUI by

Then it registers a series of listener objects on the buttons. Many lines of similar code were omitted from the following listing for brevity.

  Editor(Vector theExam, Thread mainThread){//constructor
    //save incoming parameters
    this.theExam = theExam;
    this.mainThread = mainThread;
    
    //construct and display the GUI object editor
    panel.add(problemNumber);
//Several panel.add() method invocations were omitted // for brevity frame = new Frame("Copyright 1999, R.G.Baldwin"); frame.add(panel); frame.setSize(310,500); //register listeners on the buttons firstButton.addActionListener(new FirstListener());
//Several registrations of listener objects were // omitted for brevity //make it all visible frame.setVisible(true); }//end constructor

The run() method

The run() method is very straightforward. If the Vector is empty, meaning that the XML file didn't contain any data, a blank data entry form is generated by invoking the initializeForm() method that will be discussed later.

Otherwise, the GUI is populated with the first object in the Vector by invoking the populateForm() method that will be discussed later..

  public void run(){
    if(theExam.isEmpty{
      initializeForm();
    }//end if(theExam.isEmpty())
    else{//Vector is not empty
      theElement = (Sax03D)theExam.firstElement();
      populateForm();
    }//end else
  }//end run()

The populateForm() method

This method populates the GUI form with the current element that has been extracted from the Vector.

The code is completely straightforward. The setText() method is invoked on all of the TextField and TextArea components in the GUI to set their values to values stored in the current object being edited.

  void populateForm(){
    problemNumber.setText("" + theElement.problemNumber);
    question.setText(theElement.question);
    //Several setText lines omitted for brevity
  }//end populateForm()

The initializeForm() method

This method populates the current form with the names of the fields. This is considered to be a blank GUI as discussed earlier. It is intended to be used for a new data entry form. The names of the fields are provided as clues to the user as to the intended contents of each field in the form.

  void initializeForm(){
      problemNumber.setText("0");
      question.setText("question");
//Several setText lines were omitted for brevity }//end intializeForm()

The updateElement() method

This method is more substantive than the previous methods, so I will break it up and discuss it in fragments.

This method updates the working element with the data in the form. Then it saves the working element into the Vector, possibly replacing an element already there.

The method begins by getting a new empty object of type Sax03D. Then it uses the getText() method to get the String contents of each of the GUI text components and assigns those values to the corresponding instance variables in the object.

Note the requirement in some cases to convert the data from a String to an int before assigning it to an instance variable in the object.

  void updateElement(){
    theElement = new Sax03D();
    theElement.problemNumber = 
                 Integer.parseInt(problemNumber.getText());
    theElement.question = question.getText();
//Several similar assignment statements omitted
// for brevity

Save the element in the Vector

There are two possibilities here. In one case, the element is a new object that should be appended to the end of the Vector. This is accomplished using the addElement() method of the Vector object.

In the other case, the element should replace an element already in the Vector. This is accomplished using the setElementAt() method of the Vector object.

The determination between the two cases is made on the basis of the index of the element being edited.

    if(elementNumber == theExam.size()){//new element
      theExam.addElement(theElement);//add to end
    }//end if
    else{//replace existing element
      theExam.setElementAt(theElement,elementNumber);
    }//end else
  }//end updateElement()

The ActionListener classes

The GUI has five buttons labeled as

Action listener objects are registered on each of these buttons. The listener objects registered on the first four buttons

The Save button causes the data currently in the GUI to be saved in the Vector. It also terminates the Editor thread allowing the data in the Vector to be converted to XML format and written into an output XML file.

The listener classes for the first four buttons are very similar. In addition, everything contained in those classes is discussed elsewhere in this lesson, or in the many lessons that I have written on event handling in Java. Therefore, I am going to present the first listener class here and may discuss it briefly in class for students that may need such a discussion. However, I won't provide a written discussion.

The code for the next three listener classes can be viewed in the listing near the end of the lesson.

  //This is an inner class used to instantiate an action
  // listener object for the First button.
  class FirstListener implements ActionListener{
    public void actionPerformed(ActionEvent e){
      //Save data from current GUI before moving to
      // the first element in the Vector.
      updateElement();
      
      elementNumber = 0;//set element number to first
      if(theExam.isEmpty()){
        //This will be a new element so get a blank form.
        initializeForm();
      }//end if
      else{
        //This will be an edit of an existing element, so
        // get that element and use it to populate the
        // GUI form.
        theElement = 
                  (Sax03D)theExam.elementAt(elementNumber);
        populateForm();
      }//end else
    }//end actionPerformed()
  }//end class NextListener

The SaveListener class

The definition for this class is shown in the next fragment. This is an inner class used to instantiate a listener object for the Save button.

The listener object for the Save button saves the data in the current GUI editor screen just the same as the other buttons. However, it doesn't access another object. Rather, it invokes the interrupt() method on the reference to the mainThread to awaken the main thread, which should be sleeping.

The code in the main thread then causes the edited contents of the Vector object to be converted to XML format and written into an output file.

  class SaveListener implements ActionListener{
    public void actionPerformed(ActionEvent e){
      updateElement();
      mainThread.interrupt();
    }//end actionPerformed()
  }//end class SaveListener

That ends the discussion of the class definition for the Editor class.

Sax03B.java

The classes and methods in this file are essentially the same as the classes and methods in the file named Sax02B.java. I discussed those classes and methods in detail in an earlier lesson, and won't repeat that discussion here.

A complete listing of the file is available near the end of this lesson.

Sax03C.java

The classes and methods in this file are essentially the same as the classes and methods in the file named Sax02C.java. I discussed those classes and methods in detail in an earlier lesson, and won't repeat that discussion here.

A complete listing of this file is available in the next section.

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>

<problem problemNumber="3">
<question>Snow is warm, true or false?</question>
<answer type="single" numberChoices="2" valid="1">
<item>True</item>
<item>False</item>
</answer>
<explanation>Snow is cold, not warm.</explanation>
</problem>

</exam>

.

/*File Sax03.java Copyright 1999, R.G.Baldwin

This program illustrates the implementation of a simple 
XML file editor.  It works in conjunction with the 
following files:

  Sax03B.java - converts XML file to objects
  Sax03C.java - converts objects to XML files
  Sax03D.java - defines class for objects
  Sax03.xml - sample XML input file
  

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

The XML input file can be completely empty, but it must 
exist.  Otherwise, the program will throw a 
FileNotFoundException.  If it is not empty, the contents
must match the required XML format.  

After generating the Vector object containing the XML 
records, the program invokes a GUI object editor that
provides the capability to edit the objects already in the 
Vector, and to create new objects and add them to the
Vector.  Thus, the program can be used both for editing
existing XML data and generating new XML data.

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 Sax03C.java
are used for this purpose.

A class definition in the file named Sax03D.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.

Also, no particular effort was made relative to the
cosmetics and layout of the GUI. The GUI simply contains
TextField and TextArea objects in a FlowLayout in a Panel
in the center of a Frame. Changing the size of the Frame 
will cause the normal behavior of FlowLayout.

Except for the buttons used to navigate among the objects,
the GUI components are not labeled.  The user must know
what type of information needs to be entered into each
component.  However, when a GUI form is presented
for a new object, the text fields are initialized with
the names of the elements and attributes in the original
XML file.  This is intended to provide clues to the 
user as to the intended contents of each text field.

The two numeric fields provide no clues.  The first
numeric field is intended to contain the problem number.
The second numeric field is intended to contain the number
of items to be displayed as possible answers when the
XML file is presented in a GUI for administering of a 
test.  For example, if the problem is a True/False
problem, this value should be 2.

In short, the GUI is not very user-friendly insofar as
data entry is concerned, but it does illustrate the 
concepts involved.  It will be left as an exercise for the
student to improve the GUI.

The GUI provides the following buttons to navigate through
the objects for editing: First, Prev, Next, and Last.
The behavior is as the name implies.  Whenever one of 
these buttons is selected, the current contents of the
GUI fields are saved, possibly overwriting an existing
element in the Vector object in which the objects are 
saved.

In other words, the process is, select an object to be
edited, modify the fields in the GUI, and then select
another object, or select the Save button described below.
When the next object is selected, or the Save button is
selected, the edited version of the current object is
saved into the Vector before moving on.

A Save button is also provided.  This button causes the 
current edited object to be saved into the Vector, and then
causes the entire Vector object to be converted to a new 
XML file named junk.xml and written to the disk.

The program was tested using JDK 1.2 under Win95.  It
also requires the IBM XML4Java (or some suitable
substitute) parser.
**********************************************************/
import java.util.*;
import java.awt.*;
import java.awt.event.*;

class Sax03 {
  static Vector theExam = new Vector();//store objects here
  static Thread mainThread = Thread.currentThread();
  static String XMLfile = "Sax03.xml";//input file name

  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.
      Sax03B fetchItObj = 
                    new Sax03B(XMLfile,theExam,mainThread);
      fetchItObj.start();//start the thread running
    
      //Sleep until parse is completed.  Then wake up when
      // interrupted by the parser and begin the editing
      // process 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 invoke the editor
      }//end catch

      //Launch a thread that will edit the data in the
      // Vector.
      Editor editor = new Editor(theExam,mainThread);
      editor.start();//start the Editor thread running
      
      //Sleep until edit is complete.  Then wake up when
      // interrupted by the editor and write the edited
      // data into a new XML file named junk.xml. If
      // editing is not completed during the 100000 
      // millisecond period, just go back to sleep for
      // an additional 100000 milliseconds.

      try{
        while(true){//sleep and loop until interrupted
          Thread.currentThread().sleep(100000);
        }//end while
      }catch(InterruptedException e){
        //Wake up and save the data in the Vector as
        // an XML file.
      }//end catch
      
      //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.
      Sax03C xmlWriter = new Sax03C(theExam,"junk.xml");
      xmlWriter.writeTheFile();
    
    }catch(Exception e){System.out.println(e);}
    
    System.exit(0);//Kill all threads and terminate the JVM
  }//end main
}//end class Sax03
//=======================================================//

class Editor extends Thread{
  Vector theExam;//test problem objects are stored here
  Thread mainThread;//save a reference to main thread here
  
  //A series of references to GUI components on the Frame.
  // There is a component for each field in the object,
  // plus some Button objects for control.
  TextField problemNumber = new TextField("00");
  TextArea question = new TextArea(2,40);
  TextField type = new TextField("multiple");
  TextField numberChoices = new TextField("0");
  TextField valid = new TextField("0,0,0,0,0");
  TextArea item0 = new TextArea(2,40);
  TextArea item1 = new TextArea(2,40);
  TextArea item2 = new TextArea(2,40);
  TextArea item3 = new TextArea(2,40);
  TextArea item4 = new TextArea(2,40);
  TextArea explanation = new TextArea(2,40);
  Button firstButton = new Button("First");
  Button prevButton = new Button("Prev");
  Button nextButton = new Button("Next");
  Button lastButton = new Button("Last");
  Button saveButton = new Button("Save");
  
  Frame frame;//top level container
  Panel panel = new Panel();//contains all GUI components
  
  int elementNumber = 0;//object currently being edited
  Sax03D theElement;//reference to object being edited

  Editor(Vector theExam, Thread mainThread){//constructor
    //save incoming parameters
    this.theExam = theExam;
    this.mainThread = mainThread;
    
    //construct and display the GUI object editor
    panel.add(problemNumber);
    panel.add(question);
    panel.add(type);
    panel.add(numberChoices);
    panel.add(valid);
    panel.add(item0);
    panel.add(item1);
    panel.add(item2);
    panel.add(item3);
    panel.add(item4);
    panel.add(explanation);
    panel.add(firstButton);
    panel.add(prevButton);
    panel.add(nextButton);
    panel.add(lastButton);
    panel.add(saveButton);

    frame = new Frame("Copyright 1999, R.G.Baldwin");
    frame.add(panel);
    frame.setSize(310,500);
    
    //register listeners on the buttons
    firstButton.addActionListener(new FirstListener());
    prevButton.addActionListener(new PrevListener());
    nextButton.addActionListener(new NextListener());
    lastButton.addActionListener(new LastListener());
    saveButton.addActionListener(new SaveListener());
    
    //make it all visible
    frame.setVisible(true);
  }//end constructor
  //-----------------------------------------------------//
  
  public void run(){
    if(theExam.isEmpty()){//if Vector is empty
      //Create a blank GUI data entry form
      initializeForm();
    }//end if(theExam.isEmpty())
    else{//Vector is not empty
      //populate GUI with data from first Vector element
      theElement = (Sax03D)theExam.firstElement();
      populateForm();
    }//end else
  }//end run()
  //-----------------------------------------------------//
    
  //This method populates the GUI form with the current
  // element that has been extracted from the Vector
  void populateForm(){
    problemNumber.setText("" + theElement.problemNumber);
    question.setText(theElement.question);
    type.setText(theElement.type);
    numberChoices.setText("" + theElement.numberChoices);
    valid.setText(theElement.valid);
    item0.setText(theElement.item[0]);
    item1.setText(theElement.item[1]);
    item2.setText(theElement.item[2]);
    item3.setText(theElement.item[3]);
    item4.setText(theElement.item[4]);
    explanation.setText(theElement.explanation);
  }//end populateForm()
  //-----------------------------------------------------//
    
  //This method populates the current form with the names
  // of the fields.  This is considered to be a blank GUI
  // as discussed above and is intended to be used for
  // a new form.  The names of the fields are provided as
  // clues to the user as to the intended contents of each
  // field in the form.
  void initializeForm(){
      problemNumber.setText("0");
      question.setText("question");
      type.setText("type");
      numberChoices.setText("3");
      valid.setText("valid");
      item0.setText("item");
      item1.setText("item");
      item2.setText("item");
      item3.setText("item");
      item4.setText("item");
      explanation.setText("explanation"); 
  }//end intializeForm()
  //-----------------------------------------------------//
    
  //This method updates the working element with the data
  // in the form. Then it saves the working element into
  // the Vector, possibly replacing an element already
  // there.
  void updateElement(){
    theElement = new Sax03D();//get a clean one

    //Populate it from the data entry screen  
    theElement.problemNumber = 
                 Integer.parseInt(problemNumber.getText());
    theElement.question = question.getText();
    theElement.type = type.getText();
    theElement.numberChoices = 
                 Integer.parseInt(numberChoices.getText());
    theElement.valid = valid.getText();
    theElement.item[0] = item0.getText();
    theElement.item[1] = item1.getText();
    theElement.item[2] = item2.getText();
    theElement.item[3] = item3.getText();
    theElement.item[4] = item4.getText();
    theElement.explanation = explanation.getText();
    
    //Either add the populated working element to the end
    // of the Vector, or replace one of the elements 
    // already in the Vector, depending on the index of
    // the element in the Vector being edited.
    if(elementNumber == theExam.size()){//new element
      theExam.addElement(theElement);//add to end
    }//end if
    else{//replace existing element
      theExam.setElementAt(theElement,elementNumber);
    }//end else
  }//end updateElement()


  //=====================================================//
  //This is an inner class used to instantiate an action
  // listener object for the First button.
  class FirstListener implements ActionListener{
    public void actionPerformed(ActionEvent e){
      //Save data from current GUI before moving to
      // the first element in the Vector.
      updateElement();
      
      elementNumber = 0;//set element number to first
      if(theExam.isEmpty()){
        //This will be a new element so get a blank form.
        initializeForm();
      }//end if
      else{
        //This will be an edit of an existing element, so
        // get that element and use it to populate the
        // GUI form.
        theElement = 
                  (Sax03D)theExam.elementAt(elementNumber);
        populateForm();
      }//end else
    }//end actionPerformed()
  }//end class NextListener
  //=====================================================//
  //This is an inner class used to instantiate an action
  // listener object for the Next button.
  class NextListener implements ActionListener{
    public void actionPerformed(ActionEvent e){
      //Save data from current GUI before moving to
      // the next element in the Vector.
      updateElement();
      
      elementNumber += 1;//increment element number
      if(elementNumber == theExam.size()){
        //This will be a new element to be added to the
        // end of the Vector so get a blank GUI form.
        initializeForm();
      }//end if
      else{
        //This will be an edit of an existing element, so
        // get that element and use it to populate the
        // GUI form.
        theElement = 
                  (Sax03D)theExam.elementAt(elementNumber);
        populateForm();
      }//end else
    }//end actionPerformed()
  }//end class NextListener
  //=====================================================//
  
  //This is an inner class used to instantiate an action
  // listener object for the Prev button
  class PrevListener implements ActionListener{
    public void actionPerformed(ActionEvent e){
      //Save data from current GUI before moving to
      // the previous element in the Vector.
      updateElement();
      
      //Don't allow the element number to be less than zero
      if(elementNumber > 0){
        elementNumber -= 1;//decrement element number
      }//end if
      else{//beep and ignore the request
        Toolkit.getDefaultToolkit().beep();
      }//end else

      //This will be an edit of an existing element, so
      // get that element and use it to populate the
      // GUI form.
      theElement = 
                  (Sax03D)theExam.elementAt(elementNumber);
      populateForm();
    }//end actionPerformed()
  }//end class PrevListener
  //=====================================================//

  //This is an inner class used to instantiate an action
  // listener object for the Last button.
  class LastListener implements ActionListener{
    public void actionPerformed(ActionEvent e){
      //Save data from current GUI before moving to
      // the last element in the Vector.
      updateElement();
      
      //Set the element number to the last element in
      // the Vector.
      elementNumber = theExam.size()-1;
      if(theExam.isEmpty()){
        //This will be a new element so get a blank form.
        initializeForm();
      }//end if
      else{
        //This will be an edit of an existing element, so
        // get that element and use it to populate the
        // GUI form.
        theElement = 
                  (Sax03D)theExam.elementAt(elementNumber);
        populateForm();
      }//end else
    }//end actionPerformed()
  }//end class NextListener
  //=====================================================//
  
  //This is an inner class used to instantiate an action
  // listener object for the Save button.
  class SaveListener implements ActionListener{
    public void actionPerformed(ActionEvent e){
      //Save data from current GUI into the Vector before
      // causing the Vector to be saved to an XML file.
      updateElement();
      
      //The main thread should be asleep.  Wake it up so
      // that it can save the Vector into an XML file
      // and then terminate the program.
      mainThread.interrupt();
    }//end actionPerformed()
  }//end class SaveListener
  //=====================================================//

}//end class Editor

.

/*File Sax03B.java Copyright 1999, R.G.Baldwin
This file is essentially a copy of the file named Sax02B.

This program, when used in combination with the following
files:
  Sax03.java
  Sax03C.java
  Sax03D.java
  
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 Sax03D stored in a Vector

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

See Sax03.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 Sax03B extends Thread{
  String XMLfile;
  Vector theExam;
  Thread mainThread;
  static final String parserClass = 
                           "com.ibm.xml.parsers.SAXParser";
  //-----------------------------------------------------//
                             
  //Constructor
  Sax03B(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 Sax03B
//=======================================================//

//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
  Sax03D 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 Sax03D();
      //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 Sax03C.java Copyright 1999, R.G.Baldwin
This file is essentially a copy of the file named Sax02C.

This program, when used in combination with the following
files:
  Sax03.java
  Sax03B.java
  Sax03D.java
  
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 Sax03D stored in a Vector into an XML file.

See Sax03.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 Sax03C{
  Vector theExam;//contains references to the objects
  String XMLfile;//output file name
  
  Sax03C(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
      Sax03D theDataObj = (Sax03D)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 Sax03C
//=======================================================//

.

/*File Sax03D.java Copyright 1999, R.G.Baldwin
This file is essentially a copy of the file named Sax02D.

This program, when used in combination with the following
files:
  Sax03.java
  Sax03B.java
  Sax03C.java
  
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 Sax03.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 Sax03D{
  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 Sax03D
//=======================================================//

-end-