Programming

In this chapter, we will see how to programmatically call TAXI to edit an XML document for which you have a DOM representation.

1. Example

1.1. Code

Let's have an example to show how you can do that.

Save the following code under editor/test/XMLEditorTest.java :

    import fr.loria.taxi.editor.*;
    import fr.loria.taxi.parser.*;
    import javax.swing.*;
    import javax.swing.tree.*;
    import java.awt.*;
    import java.awt.event.*;
    import java.io.*;
    import org.w3c.dom.*;


    public class XMLEditorTest extends JFrame {
      // Constantes
      private static final String DEF_FILE      = "../XML/Generic/generic.def";
      private static final String DOCUMENT_NAME = "test.xml";
      private static final String XSLT_FILE     = "../XML/Generic/genericHorizontal.xsl";

      // Variables
      private Document document;


      public XMLEditorTest() throws Exception {
        super("XMLEditorTest");

        // Loading the document
        loadDocument();

        // GUI behavior
        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        addWindowListener(new WindowAdapter() {
                            public void windowClosed(WindowEvent e) {
                              System.exit(0);
                            }
                          });

        // Content
        JPanel contentPane = new JPanel(new BorderLayout(5, 5), false);
        setContentPane(contentPane);

        // Tree showing the XML document
        final JTree tree = new JTree(createTreeModel());
        contentPane.add(BorderLayout.CENTER, new JScrollPane(tree));

        // South Panel
        JPanel southPanel = new JPanel(new GridLayout(0, 1), false);
        contentPane.add(BorderLayout.SOUTH, southPanel);

        // Checkbox to ask for the transformation of the XML document
        final JCheckBox transformCheckBox = new JCheckBox("Transform document on opening", true);
        southPanel.add(transformCheckBox);

        // Button to launch the edition
        final JButton editButton = new JButton("Edit");
        southPanel.add(editButton);
        editButton.addActionListener(new ActionListener() {
                                       public void actionPerformed(ActionEvent e) {
                                         // We disable the button the user clicked on
                                         editButton.setEnabled(false);

                                         // We instanciate e new Thread to free the AWT-EventThread
                                         new Thread() {
                                           public void run() {
                                             // Launching the edition
                                             Editor.editDOM(document, XSLT_FILE, DEF_FILE, transformCheckBox.isSelected());

                                             // Updating the tree
                                            tree.setModel(createTreeModel());

                                            editButton.setEnabled(true);
                                          }
                                        }.start();
                                      }
                                    });

        pack();
        setSize(300, 200);
        }


      /**
       * Create a DefaultMutableTreeNode that reflects the structure of parameter node.
       */
      private DefaultMutableTreeNode createTreeNode(Node node) {
        DefaultMutableTreeNode treeNode = new DefaultMutableTreeNode(node.getNodeName());

        // Dealing with children
        NodeList children = node.getChildNodes();
        int size = children.getLength();
        Node child;
        for (int i = 0; i < size; i++) {
          child = children.item(i);
          if (child instanceof Element) treeNode.add(createTreeNode(child));
        }

        return treeNode;
      }


      /**
       * Create a DefaultTreeModel that reflects the structure of the XML document.
       */
      private DefaultTreeModel createTreeModel() {
        DefaultMutableTreeNode root = createTreeNode(document.getDocumentElement());

        DefaultTreeModel treeModel = new DefaultTreeModel(root);

        return treeModel;
      }


      /**
       * Loads the XML document.
       */
      private void loadDocument() throws Exception {
        Parser parser = Parser.getInstance();
        parser.setValidate(true);
        parser.setStopAfterError(true);
        parser.parse(DOCUMENT_NAME);
        document = parser.getDocument();
      }

      // Main method
      public static void main(String[] args) throws Exception {
        XMLEditorTest editorTest = new XMLEditorTest();
        editorTest.setVisible(true);
      }
    }
  

Open a console, go to directory editor/test. Compile XMLEditorTest.java with the following command :

    javac -classpath ../lib/taxi.jar;../lib/xerces.jar XMLEditorTest.java
  

To compile the code, you need to put in your classpath :

1.2. Explaining the code

The code is a little long, but not difficult, and there is only one line we are really interested in : this line is in bold in the code above.

1.2.1. Method main(String[] args)

This method instanciates an XMLEditorTest and simply displays it.

1.2.2. Constructor XMLEditorTest

XMLEditorTest extends javax.swing.JFrame. In the constructor, we prepare the frame that will be displayed. First, we load the XML document we will edit by calling the method loadDocument(). Then we prepare the content of the frame. We place inside :

When we create the button, we place a java.awt.ActionListener on it to listen to when the user clicks on it. When this event occurs, method actionPerformed(ActionEvent e) is called on that java.awt.ActionListener. In that method, we will call XML editor to edit the document loaded and shown by the javax.swing.JTree. First, we disable the button the user clicked on so that s/he won't be able to launch the edition of the document until the edition we are about to launch has ended. Then, very important in that case (explained later), we create a new Thread that we immediately start. This Thread launches TAXI with the following line of code :

    Editor.editDOM(document, XSLT_FILE, DEF_FILE, transformCheckBox.isSelected());
  

The static method editDOM() of object editor.Editor takes 4 arguments :

This method does not return until the user closes the edition window used for editing document inside TAXI.

When the method returns, we create a new javax.swing.DefaultTreeModel to reflect the possible modifications made to document and we update the tree :

    tree.setModel(createTreeModel());
  

And then, we enable the button to let the user the possibility to launch another edition.

Why did we instantiate a new Thread to launch TAXI ? This is simple, when the user clicks the button, an event is created and placed on the AWT-EventThread queue. When AWT-EventThread deals with this event, it calls the method actionPerformed(ActionEvent e), passing the event as parameter. When we call TAXI, events for painting the application are sent to the AWT-EventThread. They are placed on queue as this AWT-EventThread is not free since it is running our method actionPerformed(ActionEvent e). In other words, TAXI won't display correctly and the user won't have the possibility to close the edition window opened to edit the XML document. But, while this edition window is not closed, our method actionPerformed(ActionEvent e) does not return, and thus, does not free the AWT-EventThread. This is a deadlock. When you programmatically call TAXI to edit an XML document, be carefull from where you launch it. If this is from a method called by the AWT-EventThread, place that call in another Thread.

Note : it is important to disable the button used for launching the edition to prevent the user to call TAXI for editing the same document many times.

1.2.3. Method createTreeNode(Node node)

It is used to create a javax.swing.tree.DefaultMutableTreeNode from an org.w3c.dom.Node (DOM representation).

1.2.4. Method createTreeModel()

It is used to create the tree we will display in the javax.swing.JTree from the XML document loaded, references by the instance variable document.

1.2.5. Method loadDocument()

Loads the document pointed by constant DOCUMENT_NAME and keeps a reference to its DOM representation via the instance variable document.

1.3. Running the code

We need to create the XML document test.xml. Save the following code under editor/test/test.xml :

    <?xml version="1.0" encoding="ISO-8859-1"?>

    <a>
      <b/>
      <c/>
      <d/>
    </a>
  

Open a console, go to directory editor/test. Run XMLEditorTest with the following command :

    java -classpath ../lib/taxi.jar;. XMLEditorTest
  

The following window appears :

You can see the XML document test.xml loaded and displayed as a tree. Click the button Edit to launch TAXI. TAXI is opened and transforms the document (TAXI must be well configured, see the topic Getting started in the online help if some error occurs during the transformation) :

When the transformation is over, you should see this :

You can notice that TAXI created a temporary document to work with (you can see it in the title bar of the edition window). On the graphic, double-click on node a to highlight it in the selection area :

In the selection area, select node d :

Hit the delete key to delete the selected node and transform the document :

Close the edition window. TAXI asks you whether you want to save the document :

Click No. We don't need to save the document as a file, it is modified in memory and that is all we need (if you want to save it, you can do it nonetheless). The edition window is closed. Look at the window called XMLEditorTest :

You can see that the tree is updated (node d is no more present), and the button Edit turned back available. TAXI is still open, if you click again on the button Edit, this instance of TAXI is used to open the edition window.

If you close the window XMLEditorTest, a System.exit(0); is performed by the java.awt.WindowAdapter we placed on XMLEditorTest. Thus, TAXI is closed too since it runs under the same Java Virtual Machine stopped by this call.

2. Things not to do

We will show here things you must not do to make the call to TAXI works correctly. There are 2 :

  1. blocking the AWT-EventThread,
  2. and editing an XML document having a Doctype that points to a file as relative.

2.1. Blocking the AWT-EventThread

As we explained previously, if you call TAXI from the Blocking the AWT-EventThread, you must ensure that you won't block it and cause a deadlock.

To show what happens if you are not carefull about it, modify the constructor of editor/test/XMLEditorTest.java (lines 60-70, changes are in bold) :

    editButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        // We disable the button the user clicked on
        editButton.setEnabled(false);

        // We instanciate e new Thread to free the AWT-EventThread
        /*
        new Thread() {
          public void run() {
        */
            // Launching the edition
            Editor.editDOM(document, XSLT_FILE, DEF_FILE, transformCheckBox.isSelected());

            // Updating the tree
            tree.setModel(createTreeModel());

            editButton.setEnabled(true);
        /*
          }
        }.start();
        */
      }
    });
  

We simply commented the creation of the Thread, so that the call to XML editor is now performed inside the AWT-EventThread.

Compile XMLEditorTest.java :

    javac -classpath ../lib/taxi.jar;../lib/xerces.jar XMLEditorTest.java
  

Run XMLEditorTest :

    java -classpath ../lib/taxi.jar;. XMLEditorTest
  

Click the button Edit and see what happens.

Once you have finished with that test, don't forget to undo the changes to editor/test/XMLEditorTest.java.

2.2. Editing an XML document having a Doctype that points to a file as relative

To work with the DOM representation of the XML document, TAXI creates a temporary file that holds that document. If the document as a Doctype that points to a file as relative, then TAXI will try to locate it from the temporary directory, it will certainly fail.

In editor/test/XMLEditorTest.java, modify the value of the constant DOCUMENT_NAME (line 14) :

    private static final String DOCUMENT_NAME = "../XML/XML/ex1.cd";
  

We will try to transform the XML document editor/XML/XML/ex1.cd shipped with TAXI. This document states that it is ruled by the DTD cd.dtd contained in the same directory than the document :

    <?xml version="1.0" encoding="ISO-8859-1"?>
    <!DOCTYPE collection SYSTEM "cd.dtd">
    <collection>
      ...
    </collection>
  

Compile XMLEditorTest.java :

    javac -classpath ../lib/taxi.jar;../lib/xerces.jar XMLEditorTest.java
  

Run XMLEditorTest (from directory editor/test) :

    java -classpath ../lib/taxi.jar;. XMLEditorTest
  

Click the button Edit. TAXI opens up, begin the transformation of the document, and displays an error message like the following one :

TAXI opened the temporary document temp58606.xml (on your machine, the name will surely be different) to transform it, from temporary directory C:\DOCUME~1\Loria\LOCALS~1\Temp (on my Windows machine). In the same directory, the file cd.dtd could not be found.

Conclusion : you can programmatically call TAXI to transform an XML document, for which you have a DOM representation, ruled by a DTD. But that DTD must be accessed from the XML document, either with a URL, or with an absolute path.