com.cladonia.xml.ExchangerDocument.java Source code

Java tutorial

Introduction

Here is the source code for com.cladonia.xml.ExchangerDocument.java

Source

/*
 * $Id: ExchangerDocument.java,v 1.38 2005/05/06 16:22:18 gmcgoldrick Exp $
 *
 * Copyright (C) 2002-2003, Cladonia Ltd. All rights reserved.
 *
 * This software is the proprietary information of Cladonia Ltd.  
 * Use is subject to license terms.
 */
package com.cladonia.xml;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.EventListener;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Vector;

import javax.swing.event.EventListenerList;
import javax.xml.parsers.ParserConfigurationException;

import org.dom4j.DocumentException;
import org.dom4j.DocumentType;
import org.dom4j.Namespace;
import org.dom4j.Node;
import org.dom4j.QName;
import org.dom4j.io.DOMReader;
import org.dom4j.io.XMLWriter;
import org.w3c.dom.Document;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

//import com.cladonia.xml.c14n.C14N;
import com.cladonia.xngreditor.URLUtilities;

/**
 * The default implementation of the Xml document.
 * This implementation is completely thread safe, the events
 * are always fired on the Swing event thread.
 *
 * @version   $Revision: 1.38 $, $Date: 2005/05/06 16:22:18 $
 * @author Dogsbay
 */
public class ExchangerDocument {
    private static final boolean DEBUG = false;

    public static final int UNKNOWN_DOCUMENT = -1;
    public static final int XML_DOCUMENT = 0;
    public static final int DTD_DOCUMENT = 1;

    private EventListenerList listeners = null;
    //   private XMLErrorHandler errorHandler   = null;

    private String encoding = null;

    private String name = "New Document";
    private File file = null;
    private URL url = null;

    private XDocument document = null;
    private XDocument lastDocument = null;
    private String text = null;

    private int forcedType = UNKNOWN_DOCUMENT;

    private XMLGrammar grammar = null;

    private boolean valid = false;

    private Exception exception = null;
    //   private Exception validationException   = null;

    private Hashtable elementNames = null;
    private Hashtable attributeNames = null;
    private Vector namespaces = null;

    private long modified = 0;

    private boolean stripWhiteSpace = false;

    public static final String STANDALONE_NONE = "NONE";

    private boolean hasEncoding = false;
    private boolean saving = false;
    private String standalone = STANDALONE_NONE;
    private String version = "1.0";

    /**
     * Constructs a Document for the type.
     *
     * @param location the url of the document.
     */
    public ExchangerDocument(int type) {
        listeners = new EventListenerList();

        forcedType = type;
    }

    /**
     * Constructs an Xml Document from the url.
     *
     * @param location the url of the document.
     */
    public ExchangerDocument(XElement root) throws IOException, SAXParseException {
        this(null, root);
    }

    /**
     * Constructs an Xml Document from the url.
     *
     * @param location the url of the document.
     */
    public ExchangerDocument(URL location) throws IOException, SAXParseException {
        this(null, location);
    }

    /**
     * Constructs an Xml Document from the url.
     *
     * @param location the url of the document.
     */
    public ExchangerDocument(URL location, boolean stripWhiteSpace) throws IOException, SAXParseException {
        this(null, location, stripWhiteSpace);
    }

    /**
     * Constructs an Xml Document from the url.
     *
     * @param location the url of the document.
     * @param root the root element for the document.
     */
    public ExchangerDocument(URL location, XElement root) {
        this(new XDocument(root), location);
    }

    /**
     * Constructs an Xml Document from the url and 
     * dom4j document supplied.
     *
     * @param document the dom4j document.
     * @param location the url of the document.
     */
    private ExchangerDocument(XDocument document, URL location) {
        listeners = new EventListenerList();
        //      errorHandler = new XMLErrorHandler();

        this.document = document;
        this.lastDocument = document;
        this.url = location;

        if (location != null && "file".equals(location.getProtocol())) {
            file = new File(location.getPath());
            modified = file.lastModified();
        }

        if (document != null) {
            setEncoding(document.getEncoding());

            XElement root = (XElement) document.getRootElement();

            //         if ( root != null) {
            // Set this as the document in the root element.
            //            root.document( this);
            //         }

            writeText();
        }
    }

    /**
     * Constructs an Xml Document from the url and 
     * dom4j document supplied.
     *
     * @param document the dom4j document.
     * @param location the url of the document.
     */
    private ExchangerDocument(XDocument document, URL location, boolean stripWhiteSpace) {
        listeners = new EventListenerList();
        //      errorHandler = new XMLErrorHandler();
        this.stripWhiteSpace = stripWhiteSpace;

        this.document = document;
        this.lastDocument = document;
        this.url = location;

        if (location != null && "file".equals(location.getProtocol())) {
            file = new File(location.getPath());
            modified = file.lastModified();
        }

        if (document != null) {
            setEncoding(document.getEncoding());

            XElement root = (XElement) document.getRootElement();

            //         if ( root != null) {
            // Set this as the document in the root element.
            //            root.document( this);
            //         }

            writeText();
        }
    }

    /**
     * Constructs an Xml Document from the text supplied.
     *
     * @param document the dom4j document.
     * @param location the url of the document.
     */
    public ExchangerDocument(String text) {
        listeners = new EventListenerList();
        //       errorHandler = new XMLErrorHandler();

        try {
            setText(text);
        } catch (Exception e) {
            // allow the document to be created with errors!
        }
    }

    /**
     * Constructs an Xml Document from the text supplied.
     *
     * @param document the dom4j document.
     * @param location the url of the document.
     */
    public ExchangerDocument(String text, boolean stripWhiteSpace) {
        listeners = new EventListenerList();
        //       errorHandler = new XMLErrorHandler();
        this.stripWhiteSpace = stripWhiteSpace;

        try {
            setText(text);
        } catch (Exception e) {
            // allow the document to be created with errors!
        }
    }

    /**
     * Gets an element from this document for the specified xpath.
    * Returns Null, if the element cannot be found.
    *
    * @param the xpath expression to the element.
     *
     * @return the element.
     */
    public XElement getElement(String xpath) {
        XElement[] elements = getElements(xpath);

        if (elements.length > 0) {
            return elements[0];
        }

        return null;
    }

    /**
     * Gets a list of elements from this document for the 
    * specified xpath. Returns Null, if no elements can 
    * be found.
     *
     * @param the xpath expression to the elements.
     *
     * @return the elements.
     */
    public XElement[] getElements(String xpath) {
        List list = document.selectNodes(xpath);
        Vector elements = new Vector();
        Iterator iterator = list.iterator();

        while (iterator.hasNext()) {
            Object object = iterator.next();

            if (object instanceof XElement) {
                elements.addElement(object);
            }
        }

        XElement[] result = new XElement[elements.size()];

        for (int i = 0; i < elements.size(); i++) {
            result[i] = (XElement) elements.elementAt(i);
        }

        return result;
    }

    /**
     * Returns the element for the position in the test, 
     * this will return the root element if the 
     * position is not an element. 
     *
     * @param pos the text position.
     *
     * @return the element that is at the position.
     */
    public XElement getElement(int pos) {
        XElement result = null;

        result = getElement(getRoot(), pos, true);

        if (result == null) {
            result = getRoot();
        }

        return result;
    }

    public XElement getLastElement(int pos) {
        XElement result = null;

        result = getElement(getLastRoot(), pos, true);

        if (result == null) {
            result = getLastRoot();
        }

        return result;
    }

    public Object getLastNode(int pos, boolean current) {
        Object result = null;

        result = getNode(getLastRoot(), pos, current);

        if (result == null) {
            result = getLastRoot();
        }

        return result;
    }

    /**
     * Returns the URL for this document. 
     *
     * @return the URL for this document.
     */
    public URL getURL() {
        try {
            return new URL(URLUtilities.toString(url));
        } catch (MalformedURLException e) {
            // do nothing;
        }

        return null;
    }

    /**
    * Sets the URL for this document. 
    *
    * @param url the URL for this document.
    */
    public void setURL(URL url) {
        this.url = url;

        if (url != null && "file".equals(url.getProtocol())) {
            file = new File(url.getPath());
            modified = file.lastModified();
        } else {
            file = null;
            modified = 0;
        }
    }

    /**
     * The name used when no File or URL are set...
     *
     * @param name the name for the document.
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * Sets the grammar type for this document.
     *
     * @param grammar the grammar for this document.
     */
    public void setGrammar(XMLGrammar grammar) {
        if (DEBUG)
            System.out.println("ExchangerDocument.setGrammar( " + grammar + ")");
        this.grammar = grammar;
    }

    /**
     * Get the grammar type for this document.
     *
     * @return the grammar for this document.
     */
    public XMLGrammar getGrammar() {
        if (DEBUG)
            System.out.println("ExchangerDocument.getGrammar()");
        return grammar;
    }

    /**
     * Returns the name for this document. 
     *
     * @return the name for this document.
     */
    public String getName() {
        if (file != null) {
            return file.getName();
        } else if (url != null) {
            return URLUtilities.getFileName(url);
        } else {
            return name;
        }
    }

    /**
     * Returns the root element for this document. 
     *
     * @return the root element.
     */
    public XElement getRoot() {
        if (document != null) {
            return (XElement) document.getRootElement();
        } else {
            return null;
        }
    }

    /**
     * Returns the root element for the last well-formed document.
     *
     * @return the root element.
     */
    public XElement getLastRoot() {
        if (lastDocument != null) {
            return (XElement) lastDocument.getRootElement();
        } else {
            return null;
        }
    }

    /**
     * Sets the root element for this document. 
     *
     * @param root the root element.
     */
    public void setRoot(XElement root) {
        document.setRootElement(root);

        //      if ( root != null) {
        // Set this as the document in the root element.
        //         root.document( this);
        //      }
    }

    /**
     * Check to see if previously loading the document
     * resulted in an error.
     *
     * @return true when loading the document generated an error.
     */
    public boolean isError() {
        return exception != null;
    }

    /**
     * Check to see if previously validating the document
     * resulted in an error.
     *
     * @return true when valdating the document generated an error.
     */
    //   public boolean isValidationError() {
    //      return validationException != null;
    //   }

    /**
     * Check to see if the document is a remote document.
     *
     * @return true when the document is remote.
     */
    public boolean isRemote() {
        if (url != null) {
            return !url.getProtocol().equals("file");
        } else {
            return false;
        }
    }

    /**
     * Check to see if the document is set to read only.
     *
     * @return true when the document cannot be changed.
     */
    public boolean isReadOnly() {
        if (file != null) {
            return !file.canWrite();
        } else {
            return false;
        }
    }

    /**
     * Returns the error generated when previously loading 
     * the document resulted in an error.
     *
     * @return the error.
     */
    public Exception getError() {
        return exception;
    }

    /**
     * Returns the error generated when previously validating
     * the document resulted in an error.
     *
     * @return the error.
     */
    //   public Exception getValidationError() {
    //      return validationException;
    //   }

    /**
     * Saves the document to disc and informs the 
     * listeners that the document has been changed.
     */
    public void save() throws IOException {
        if (DEBUG)
            System.out.println("ExchangerDocument.save()");
        try {
            saving = true;

            URLUtilities.save(url, new ByteArrayInputStream(text.getBytes(getJavaEncoding())), getJavaEncoding());
            //);
            //         BufferedWriter writer = new BufferedWriter( new OutputStreamWriter( out, getJavaEncoding()));
            //
            //         writer.write( text, 0, text.length());
            //         writer.flush();
            //         writer.close();
        } finally {
            if (file != null) {
                modified = file.lastModified();
            }

            saving = false;

            fireDocumentUpdated(getRoot(), ExchangerDocumentEvent.SAVED);
        }
    }

    /**
     * Returns the text for this document.
     *
     * @return the text.
     */
    public String getText() {
        return text;
    }

    /**
     * Sets the text in the document model.
     *
     * @param text the text.
     */
    public void setText(String text) throws IOException, SAXParseException {
        if (DEBUG)
            System.out.println("ExchangerDocument.setText()");
        XElement root = null;

        exception = null;
        //      validationException = null;

        document = null;

        this.text = text;

        // check for any updates to the XML Declaration
        updateDeclaration();

        try {
            byte[] bytes = text.getBytes(getJavaEncoding());
            ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
            InputStreamReader reader = new InputStreamReader(stream, getJavaEncoding());
            String systemId = null;

            if (url != null) {
                systemId = URLUtilities.toString(url);
            }

            String location = null;

            if (grammar != null) {
                location = grammar.getLocation();
            }

            if (grammar != null && grammar.useExternal() && grammar.getType() == XMLGrammar.TYPE_DTD
                    && location != null) {
                if (stripWhiteSpace) {
                    document = XMLUtilities.parse(new BufferedReader(reader), bytes.length, systemId, location,
                            stripWhiteSpace);
                } else {
                    document = XMLUtilities.parse(new BufferedReader(reader), bytes.length, systemId, location);
                }
            } else {
                if (stripWhiteSpace) {
                    document = XMLUtilities.parse(new BufferedReader(reader), bytes.length, systemId,
                            stripWhiteSpace);
                } else {
                    document = XMLUtilities.parse(new BufferedReader(reader), bytes.length, systemId);
                }
            }
            //setEncoding( document.getEncoding());

            valid = false;

            //         if ( lastDocument != null) {
            //            lastDocument.cleanup();
            //         }
            lastDocument = document;

            root = (XElement) document.getRootElement();

            // Set this as the document in the root element.
            //         root.document( this);

            // make sure the text is uptodate 
            // and that the model nodes have the correct positions
            writeText(text);

        } catch (IOException e) {
            exception = e;
            //         e.printStackTrace();
            throw e;
        } catch (SAXParseException e) {
            exception = e;
            //         e.printStackTrace();
            throw e;
        } finally {
            fireDocumentUpdated(root, ExchangerDocumentEvent.MODEL_UPDATED);
        }
    }

    /**
     * Validates the document.
     */
    public void validate(ErrorHandler handler) throws IOException, SAXParseException {
        if (DEBUG)
            System.out.println("ExchangerDocument.validate()");
        //      validationException = null;

        try {
            String systemId = null;

            if (url != null) {
                systemId = URLUtilities.toString(url);
            }

            ByteArrayInputStream stream = new ByteArrayInputStream(getText().getBytes(getJavaEncoding()));
            InputStreamReader reader = new InputStreamReader(stream, getJavaEncoding());
            //         errorHandler.clear();

            String location = null;

            if (grammar != null) {
                location = grammar.getLocation();
            }

            if (grammar != null && grammar.useExternal() && location != null) {
                XMLUtilities.validate(handler, new BufferedReader(reader), systemId, getJavaEncoding(),
                        grammar.getType(), location);
            } else {
                XMLUtilities.validate(handler, new BufferedReader(reader), systemId);
            }

            valid = true;

        } catch (IOException e) {
            //         validationException = e;
            throw e;
        } catch (SAXParseException e) {
            //         validationException = e;
            throw e;
        } catch (Exception e) {
            e.printStackTrace();
        }
        //      finally {
        //         fireDocumentUpdated( getRoot(), ExchangerDocumentEvent.VALIDATED);
        //      }
    }

    public InputSource getInputSource() throws IOException {
        ByteArrayInputStream stream = new ByteArrayInputStream(getText().getBytes(getJavaEncoding()));
        InputStreamReader reader = new InputStreamReader(stream, getJavaEncoding());

        String systemId = "";

        if (url != null) {
            systemId = URLUtilities.toString(url);
        }

        InputSource source = new InputSource(reader);
        source.setEncoding(getJavaEncoding());
        source.setSystemId(systemId);

        return source;
    }

    public URL checkSchemaLocation() throws IOException {
        if (DEBUG)
            System.out.println("ExchangerDocument.checkSchemaLocation()");
        if (!isError()) {
            int type = -1;

            String schemaLocation = null;
            URL baseURL = getURL();

            if (grammar != null && grammar.useExternal() && grammar.getLocation() != null) {
                schemaLocation = grammar.getLocation();
                type = grammar.getType();
            } else {
                DocumentType docType = document.getDocType();

                if (docType != null) {
                    schemaLocation = XMLUtilities.resolve(docType.getPublicID(), docType.getSystemID());
                    type = XMLGrammar.TYPE_DTD;

                    if (schemaLocation == null) {
                        schemaLocation = docType.getSystemID();
                    }

                    // no system id in the doctype, maybe internal DTD???
                    if (schemaLocation == null) {
                        return null;
                    }
                }

                if (schemaLocation == null) {
                    schemaLocation = getRoot().getAttribute("schemaLocation");

                    if (schemaLocation != null) {
                        StringTokenizer tokenizer = new StringTokenizer(schemaLocation);
                        if (tokenizer.countTokens() > 1) {
                            String namespace = tokenizer.nextToken(); // discard first token, it is a namespace...

                            schemaLocation = schemaLocation.substring(namespace.length()).trim();
                            //schemaLocation = tokenizer.nextToken();
                        } //else {
                          //schemaLocation = tokenizer.nextToken();
                          //}
                    }

                    type = XMLGrammar.TYPE_XSD;
                }

                if (schemaLocation == null) {
                    schemaLocation = getRoot().getAttribute("noNamespaceSchemaLocation");

                    type = XMLGrammar.TYPE_XSD;
                }
            }

            if (DEBUG)
                System.out.println("schemaLocation: " + schemaLocation);

            if (schemaLocation == null) {
                throw new IOException("No validation schema location has been defined for this document.");
            }

            URL url = null;

            try {
                if (baseURL != null) {
                    url = new URL(baseURL, schemaLocation);
                } else {
                    url = new URL(schemaLocation);
                }
            } catch (Exception e) {
                url = XngrURLUtilities.getURLFromFile(new File(schemaLocation));
            }

            if (DEBUG)
                System.out.println("url: " + url.toString());

            InputStream stream = null;

            try {
                stream = url.openStream();
                return url;
            } catch (IOException e) {
                String message = null;

                if (type == XMLGrammar.TYPE_DTD) {
                    message = "No valid DTD location has been defined for this document.";
                } else if (type == XMLGrammar.TYPE_XSD) {
                    message = "No valid Schema location has been defined for this document.";
                } else if (type == XMLGrammar.TYPE_RNG) {
                    message = "No valid RelaxNG location has been defined for this document.";
                } else if (type == XMLGrammar.TYPE_RNC) {
                    message = "No valid RelaxNG location has been defined for this document.";
                } else if (type == XMLGrammar.TYPE_NRL) {
                    message = "No valid NRL location has been defined for this document.";
                }

                throw new IOException(message);
            } finally {
                if (stream != null) {
                    stream.close();
                }
            }
        }

        return null;
    }

    /**
     * Validates the document.
     */
    /*public void canonicalize() throws Exception, IOException, SAXParseException {
       if (DEBUG) System.out.println( "ExchangerDocument.canonicalize()");
           
       text = C14N.canonicalize( text, getEncoding());
       setText( text);
    }*/

    public int getInternalGrammarType() {
        int type = -1;

        if (!isError() && isXML() && document != null) {
            DocumentType docType = document.getDocType();

            if (docType != null) {
                type = XMLGrammar.TYPE_DTD;
            }

            if (type == -1) {

                String schemaLocation = getRoot().getAttribute("schemaLocation");

                if (schemaLocation != null) {
                    type = XMLGrammar.TYPE_XSD;
                }

                if (schemaLocation == null) {
                    schemaLocation = getRoot().getAttribute("noNamespaceSchemaLocation");

                    if (schemaLocation != null) {
                        type = XMLGrammar.TYPE_XSD;
                    }
                }
            }
        }

        return type;
    }

    public String getPublicID() {
        DocumentType docType = document.getDocType();

        if (docType != null) {
            return docType.getPublicID();
        }

        return null;
    }

    public String getSystemID() {
        DocumentType docType = document.getDocType();

        if (docType != null) {
            String systemID = XMLUtilities.resolve(docType.getPublicID(), docType.getSystemID());

            if (systemID == null) {
                systemID = docType.getSystemID();
            }

            return systemID;
        }

        return null;
    }

    public URL getSchemaURL() {
        URL schemaURL = null;

        if (!isError() && isXML()) {
            String schemaLocation = null;

            if (grammar != null && grammar.useExternal() && grammar.getLocation() != null
                    && grammar.getType() == XMLGrammar.TYPE_XSD) {
                schemaLocation = grammar.getLocation();
            } else {
                schemaLocation = getRoot().getAttribute("schemaLocation");

                if (schemaLocation != null) {

                    StringTokenizer tokenizer = new StringTokenizer(schemaLocation);
                    if (tokenizer.countTokens() > 1) {
                        tokenizer.nextToken(); // discard first token, it is a namespace...
                        schemaLocation = tokenizer.nextToken();
                    } else {
                        schemaLocation = tokenizer.nextToken();
                    }
                }

                if (schemaLocation == null) {
                    schemaLocation = getRoot().getAttribute("noNamespaceSchemaLocation");
                }
            }

            URL baseURL = getURL();

            try {
                if (baseURL != null) {
                    schemaURL = new URL(baseURL, schemaLocation);
                } else {
                    schemaURL = new URL(schemaLocation);
                }
            } catch (Exception e) {
                try {
                    if (baseURL != null) {
                        schemaURL = new URL(baseURL, schemaLocation);
                    } else {
                        schemaURL = XngrURLUtilities.getURLFromFile(new File(schemaLocation));
                    }
                } catch (Exception x) {
                    // The url could not be constructed, just return null
                    schemaURL = null;
                }
            }
        }

        return schemaURL;
    }

    public boolean isValid() {
        return valid;
    }

    public int getType() {
        if (forcedType == UNKNOWN_DOCUMENT) {
            URL url = getURL();

            if (url != null) {
                if (url.toString().toLowerCase().endsWith("dtd") || url.toString().toLowerCase().endsWith("mod")
                        || url.toString().toLowerCase().endsWith("ent")) {
                    return DTD_DOCUMENT;
                }
            }

            if (exception instanceof XMLUtilities.NotXMLException) {
                return UNKNOWN_DOCUMENT;
            }

            return XML_DOCUMENT;
        } else {
            return forcedType;
        }
    }

    public boolean isXML() {
        return getType() == XML_DOCUMENT;
    }

    public boolean isDTD() {
        return getType() == DTD_DOCUMENT;
    }

    public String getEncoding() {
        if (DEBUG)
            System.out.println("ExchangerDocument.getEncoding() [" + encoding + "]");

        if (encoding != null) {
            return encoding;
        }

        return "UTF-8";
    }

    public int getCount(QName name) {
        return ((Counter) elementNames.get(name)).counter;
    }

    public void setEncoding(String encoding) {
        if (DEBUG)
            System.out.println("ExchangerDocument.setEncoding( " + encoding + ")");

        this.encoding = encoding;
    }

    public String getJavaEncoding() {
        String encoding = XMLUtilities.mapXMLEncodingToJava(getEncoding());

        if (DEBUG)
            System.out.println("ExchangerDocument.getJavaEncoding() [" + encoding + "]");
        return encoding;
    }

    /**
     * Loads the document from disc and informs the 
     * listeners that the document has been changed.
     */
    public void load() throws IOException, SAXParseException {
        if (DEBUG)
            System.out.println("ExchangerDocument.load()");
        XElement root = null;
        exception = null;

        document = null;

        String textURL = new String();

        try {
            XMLUtilities.XMLDeclaration decl = new XMLUtilities.XMLDeclaration();
            text = XMLUtilities.getText(url, decl);

            // need the encoding for setText() !
            setEncoding(decl.getEncoding());

            setText(text);

            //         document = XMLUtilities.parse( url);
            //         setEncoding( document.getEncoding());
            //
            //         valid = false;
            //         modified = file.lastModified();
            //
            //         if ( lastDocument != null) {
            //            lastDocument.cleanup();
            //         }
            //
            //         lastDocument = document;
            //
            //         root = (XElement)document.getRootElement();
            //
            //         // Set this as the document in the root element.
            //         root.document( this);
            //         
            //         textURL = XMLUtilities.getText( url, getJavaEncoding());
        } catch (IOException e) {
            exception = e;
            throw e;

        } catch (SAXParseException e) {
            exception = e;

            // there was a SAXParseException, try to get the xml as text
            //         XMLUtilities.XMLDeclaration decl = new XMLUtilities.XMLDeclaration();
            //         text = XMLUtilities.getText( url, decl);
            //         setEncoding( decl.getEncoding());

            throw e;
            //      } finally {
            //         if ( !isError()) { 
            //writeText();
            //            writeText( text);
            //            fireDocumentUpdated( root, ExchangerDocumentEvent.CONTENT_UPDATED);
            //         }
        }
    }

    /**
     * Loads the document from disc and informs the listeners that 
     * the document has been changed, does not substitute '&' characters
     * and is only used for the properties file.
     */
    public void loadWithoutSubstitution() throws IOException, SAXParseException {
        if (DEBUG)
            System.out.println("ExchangerDocument.loadWithoutSubstitution()");
        XElement root = null;
        exception = null;

        document = null;

        try {
            document = XMLUtilities.parseWithoutSubstitution(url);
            setEncoding(document.getEncoding());

            valid = false;
            modified = file.lastModified();

            //         if ( lastDocument != null) {
            //            lastDocument.cleanup();
            //         }

            lastDocument = document;

            root = (XElement) document.getRootElement();

            // Set this as the document in the root element.
            //         root.document( this);

        } catch (IOException e) {
            exception = e;
            throw e;
        } catch (SAXParseException e) {
            exception = e;

            // there was an error, try to get the xml as text
            XMLUtilities.XMLDeclaration decl = new XMLUtilities.XMLDeclaration();
            text = XMLUtilities.getText(url, decl);
            setEncoding(decl.getEncoding());

            throw e;
        } finally {
            if (!isError()) {
                //            writeText();
                fireDocumentUpdated(root, ExchangerDocumentEvent.CONTENT_UPDATED);
            }
        }
    }

    /**
     * Searches for elements and attributes matching the given xpath.
     *
     * @return a list of matching (parent)elements or attributes
     */
    public Vector search(String xpath) {
        return search(xpath, getDeclaredNamespaces());
    }

    public Vector search(String xpath, Vector namespaces) {
        Vector results = new Vector();

        if (!isError()) {
            Map namespaceURIs = XDocumentFactory.getInstance().getXPathNamespaceURIs();
            HashMap map = new HashMap();
            Iterator keys = namespaceURIs.keySet().iterator();

            while (keys.hasNext()) {
                Object prefix = keys.next();
                Object namespace = namespaceURIs.get(prefix);

                map.put(prefix, namespace);
            }

            for (int i = 0; i < namespaces.size(); i++) {
                Namespace namespace = (Namespace) namespaces.elementAt(i);
                String prefix = namespace.getPrefix();

                if (prefix != null && prefix.trim().length() > 0) {
                    map.put(prefix, namespace.getURI());
                }
            }

            XDocumentFactory.getInstance().setXPathNamespaceURIs(map);

            Object object = document.selectObject(xpath);

            if (object instanceof List) {
                List list = (List) object;
                Iterator iterator = list.iterator();

                while (iterator.hasNext()) {
                    Node node = (Node) iterator.next();
                    results.addElement(node);
                }
            } else {
                results.addElement(object);
            }

            XDocumentFactory.getInstance().setXPathNamespaceURIs(namespaceURIs);
        }

        return results;
    }

    public Vector searchLastWellFormedDocument(String xpath) {
        Vector results = new Vector();

        if (lastDocument != null) {
            Map namespaceURIs = XDocumentFactory.getInstance().getXPathNamespaceURIs();
            HashMap map = new HashMap();
            Iterator keys = namespaceURIs.keySet().iterator();

            while (keys.hasNext()) {
                Object prefix = keys.next();
                Object namespace = namespaceURIs.get(prefix);

                map.put(prefix, namespace);
            }

            Vector namespaces = getDeclaredNamespaces();

            for (int i = 0; i < namespaces.size(); i++) {
                Namespace namespace = (Namespace) namespaces.elementAt(i);
                String prefix = namespace.getPrefix();

                if (prefix != null && prefix.trim().length() > 0) {
                    map.put(prefix, namespace.getURI());
                }
            }

            XDocumentFactory.getInstance().setXPathNamespaceURIs(map);

            Object object = lastDocument.selectObject(xpath);

            if (object instanceof List) {
                List list = (List) object;
                Iterator iterator = list.iterator();

                while (iterator.hasNext()) {
                    Node node = (Node) iterator.next();
                    results.addElement(node);
                }
            } else {
                results.addElement(object);
            }

            XDocumentFactory.getInstance().setXPathNamespaceURIs(namespaceURIs);
        }

        return results;
    }

    /**
     * The document model has been updated, update the text 
     * and fire an update event. 
     */
    public void update() {
        if (DEBUG)
            System.out.println("ExchangerDocument.update()");
        writeText();

        fireDocumentUpdated(getRoot(), ExchangerDocumentEvent.TEXT_UPDATED);
    }

    public boolean isSOAP() {
        XElement root = getLastRoot();

        if (root != null) {
            if (root.getName().equals("Envelope")) {
                if (root.getNamespaceURI().equals("http://schemas.xmlsoap.org/soap/envelope/")) {
                    return true;
                }
            }
        }

        return false;
    }

    public boolean isWSDL() {
        XElement root = getLastRoot();

        if (root != null) {
            if (root.getName().equals("definitions")) {
                if (root.getNamespaceURI().equals("http://schemas.xmlsoap.org/wsdl/")) {
                    return true;
                }
            }
        }

        return false;
    }

    public boolean isSVG() {
        XElement root = getLastRoot();

        if (root != null) {
            if (root.getName().equals("svg")) {
                if (root.getNamespaceURI().equals("http://www.w3.org/2000/svg")) {
                    return true;
                }
            }
        }

        return false;
    }

    public boolean isXSD() {
        XElement root = getLastRoot();

        if (root != null) {
            if (root.getName().equals("schema")) {
                if (root.getNamespaceURI().equals("http://www.w3.org/2001/XMLSchema")) {
                    return true;
                }
            }
        }

        return false;
    }

    public boolean isRNG() {
        XElement root = getLastRoot();

        if (root != null) {
            if (root.getName().equals("grammar")) {
                if (root.getNamespaceURI().equals("http://relaxng.org/ns/structure/1.0")) {
                    return true;
                }
            }
        }

        return false;
    }

    public boolean isXSL() {
        XElement root = getLastRoot();

        if (root != null) {
            if (root.getName().equals("stylesheet") || root.getName().equals("transform")) {
                if (root.getNamespaceURI().equals("http://www.w3.org/1999/XSL/Transform")) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Checks to find out if this version of the document is consistent 
     * with the one saved on disk. Tries to find out if the document has 
     * been changed or deleted by an external process.<p>
     * When the document has been changed or deleted by an external 
     * process the correct event is fired to the document listener.
     */
    public void consistent() {

        if (file != null && url != null && "file".equals(url.getProtocol())) {
            File file = new File(url.getPath());

            if (file.exists()) {
                if (modified != file.lastModified()) {
                    // Make sure the event is always fired on the GUI thread!
                    //            SwingUtilities.invokeAndWait( new Runnable() {
                    //                public void run() {
                    //                  fireDocumentUpdatedExternally();
                    //                }
                    //            });
                }
            } else {
                // Make sure the event is always fired on the GUI thread!
                //         SwingUtilities.invokeAndWait( new Runnable() {
                //             public void run() {
                //               fireDocumentDeletedExternally();
                //             }
                //         });
            }
        }
    }

    public boolean isModified() {
        if (!saving && file != null && url != null && "file".equals(url.getProtocol())) {
            File file = new File(url.getPath());

            if (file.exists()) {
                if (modified != file.lastModified()) {
                    modified = file.lastModified();
                    return true;
                }
                //         } else {
                //            return true;
            }
        }

        return false;
    }

    /**
    * Adds a document listener to the document. 
    */
    public void addListener(ExchangerDocumentListener listener) {
        if (listeners != null) {
            listeners.add((Class) listener.getClass(), listener);
        }
    }

    /**
     * Removes a document listener from the document.
     */
    public void removeListener(ExchangerDocumentListener listener) {
        if (listeners != null) {
            listeners.remove((Class) listener.getClass(), listener);
        }
    }

    /**
     * This is not an XDocument method!
     * This method returns the dom4j document.
     *
     * @return the properties for this document.
     */
    public XDocument getDocument() {
        return document;
    }

    /**
     * Returns the list of defined element names.
     *
     * @return the list of element names.
     */
    public Vector getElementNames() {
        return new Vector(elementNames.keySet());
    }

    /**
     * Returns the list of defined attribute names.
     *
     * @return the list of attribute names.
     */
    public Vector getAttributeNames() {
        return new Vector(attributeNames.keySet());
    }

    /**
     * Returns the list of values defined for the current attribute.
     *
     * @return the list of attribute values.
     */
    public Vector getAttributeValues(QName name) {
        return (Vector) attributeNames.get(name);
    }

    /**
     * Returns the list of defined prefixes.
     *
     * @return the list of prefixes.
     */
    public Vector getDeclaredNamespaces() {
        return namespaces;
    }

    /**
     * Returns the W3C Document.
     *
     * @return the w3c document.
     */
    public Document getW3CDocument() throws IOException, SAXException {
        Document doc = null;

        try {
            javax.xml.parsers.DocumentBuilderFactory dbf = javax.xml.parsers.DocumentBuilderFactory.newInstance();
            dbf.setNamespaceAware(true);
            dbf.setAttribute("http://xml.org/sax/features/namespaces", Boolean.TRUE);

            javax.xml.parsers.DocumentBuilder db = dbf.newDocumentBuilder();
            ByteArrayInputStream stream = new ByteArrayInputStream(getText().getBytes(getJavaEncoding()));

            InputStreamReader isReader = new InputStreamReader(stream, getJavaEncoding());
            InputSource source = new InputSource(isReader);

            if (url != null) {
                source.setSystemId(url.toString());
            }

            doc = db.parse(source);
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        }

        //      DOMWriter writer = new DOMWriter();
        //      
        //      Document doc = writer.write( document);
        //
        //      DOMReader reader = new DOMReader();
        //      XDocument doc2 = (XDocument)reader.read( doc);
        //      System.out.println( "doc = "+doc2.asXML());

        return doc;
    }

    /**
     * Sets the W3C Document.
     *
     * @param the w3c document.
     * @deprecated this does not work!
     */
    public void setW3CDocument(Document document) throws DocumentException {
        DOMReader reader = new DOMReader();
        this.document = (XDocument) reader.read(document);

        writeText(); // write the text out of the dom directly

        fireDocumentUpdated(getRoot(), ExchangerDocumentEvent.CONTENT_UPDATED);
    }

    private void setNames(Hashtable eNames, Hashtable aNames, Vector prefixes, XElement element) {
        XElement[] elements = element.getElements();

        for (int i = 0; i < elements.length; i++) {
            setNames(eNames, aNames, prefixes, elements[i]);
        }

        XAttribute[] attributes = element.getAttributes();

        for (int i = 0; i < attributes.length; i++) {
            QName name = attributes[i].getQName();

            Vector values = (Vector) aNames.get(name);

            if (values == null) {
                values = new Vector();
                aNames.put(name, values);
            }

            if (!values.contains(attributes[i].getValue())) {
                values.addElement(attributes[i].getValue());
            }
        }

        QName name = element.getQName();
        Counter count = (Counter) eNames.get(name);

        if (count == null) {
            eNames.put(name, new Counter());
        } else {
            count.counter++;
        }

        List nss = element.declaredNamespaces();

        for (int i = 0; i < nss.size(); i++) {
            Namespace ns = (Namespace) nss.get(i);

            if (!prefixes.contains(ns) && ns.getURI() != null && ns.getURI().trim().length() > 0) { //&& ns.getPrefix() != null && ns.getPrefix().trim().length() > 0) {
                prefixes.addElement(ns);
            }
        }
    }

    // uses the SimpleParser to create the positions for the tree model
    private void writeText(String text) {
        if (DEBUG)
            System.out.println("ExchangerDocument.writeText(String text)");
        try {
            XElement root = getRoot();
            if (root != null) {
                elementNames = new Hashtable();
                attributeNames = new Hashtable();
                namespaces = new Vector();

                setNames(elementNames, attributeNames, namespaces, getRoot());
            }

            // get the stream from the input text
            ByteArrayInputStream stream = new ByteArrayInputStream(text.getBytes(getJavaEncoding()));
            InputStreamReader reader = new InputStreamReader(stream, getJavaEncoding());

            // call the simple parser
            SimpleParser simpleparser = new SimpleParser();
            simpleparser.writeText(reader, root);

            this.text = text;
        } catch (Exception e) {
            System.out.println("An error occurred in the simple parser: " + e.getMessage());
            e.printStackTrace();

            // if the simple kparser has a problem then just call the normal writeText() and continue
            writeText();
        }
    }

    // writes the document to text
    private void writeText() {
        if (DEBUG)
            System.out.println("ExchangerDocument.writeText()");
        try {
            XElement root = getRoot();
            if (root != null) {
                elementNames = new Hashtable();
                attributeNames = new Hashtable();
                namespaces = new Vector();

                setNames(elementNames, attributeNames, namespaces, getRoot());
            }

            //         System.out.println( document.asXML());

            StringWriter writer = new StringWriter();
            ExchangerOutputFormat format = new ExchangerOutputFormat("", false, getEncoding());

            if (hasDeclaration()) {
                if (getStandalone() != STANDALONE_NONE) {
                    format.setStandalone(getStandalone());
                    format.setOmitStandalone(false);
                }

                format.setVersion(getVersion());
                format.setOmitEncoding(!hasEncoding());
                format.setSuppressDeclaration(false);
            } else {
                format.setSuppressDeclaration(true);
            }

            //         if ( !getRoot().hasContent()) {
            //            format.setExpandEmptyElements( true);
            //         }

            XMLWriter formatter = new ExchangerXMLWriter(writer, format);
            formatter.write(document);
            formatter.flush();

            text = writer.toString();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private Object getNode(XElement root, int pos, boolean current) {
        XElement element = getElement(root, pos, current);

        if (element != null) {
            XAttribute[] attributes = element.getAttributes();

            for (int i = 0; i < attributes.length; i++) {
                if (pos >= attributes[i].getAttributeStartPosition()
                        && pos < attributes[i].getAttributeEndPosition()) {
                    return attributes[i];
                }
            }
        }

        return element;
    }

    private XElement getElement(XElement element, int pos, boolean current) {
        XElement result = null;

        if (element != null) {
            XElement[] elements = element.getElements();

            for (int i = 0; i < elements.length; i++) {
                if (pos >= elements[i].getElementStartPosition() && pos < elements[i].getElementEndPosition()) {
                    XElement e = getElement(elements[i], pos, current);

                    if (e == null) {
                        return elements[i];
                    } else {
                        return e;
                    }
                } else if (!current && pos >= elements[i].getContentEndPosition()
                        && pos < ((XElement) elements[i].getParent()).getContentEndPosition() + 1) {
                    result = elements[i];
                }
            }
        }

        return result;
    }

    /** 
     * Notifies the listeners about a change in the document.
     *
     * Note: This does not happen on the event-dispatching thread!
     */
    protected void fireDocumentUpdated(final XElement element, final int type) {
        // Make sure the event is always fired on the GUI thread!
        //      try {
        //         SwingUtilities.invokeAndWait( new Runnable() {
        //             public void run() {
        // Guaranteed to return a non-null array
        Object[] list = listeners.getListenerList();

        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = list.length - 2; i >= 0; i -= 2) {
            ((ExchangerDocumentListener) list[i + 1])
                    .documentUpdated(new ExchangerDocumentEvent(ExchangerDocument.this, element, type));
        }
        //            }
        //         });
        //      } catch ( InvocationTargetException e) {
        //         e.printStackTrace();
        //      } catch ( InterruptedException e) {
        //         e.printStackTrace();
        //      }
    }

    /** 
     * Notifies the listeners about the deletion of this document.
     */
    //   protected void fireDocumentDeleted() {
    // Guaranteed to return a non-null array
    //      Object[] list = listeners.getListenerList();

    // Process the listeners last to first, notifying
    // those that are interested in this event
    //      for ( int i = list.length-2; i >= 0; i -= 2) {
    //          if ( list[i] == ExchangerDocumentListener.class) {
    //            ((ExchangerDocumentListener)list[i+1]).documentDeleted( new ExchangerDocumentEvent( this, getRoot()));
    //          }
    //      }
    //   }

    /** 
     * Removes all the listeners from this document.
     */
    protected void removeAllListeners() {
        // Guaranteed to return a non-null array
        if (listeners != null) {
            Object[] list = listeners.getListenerList();

            for (int i = list.length - 2; i >= 0; i -= 2) {
                listeners.remove((Class) list[i], (EventListener) list[i + 1]);
            }
        }
    }

    public void cleanup() {
        if (document != null) {
            document.cleanup();
        } else if (lastDocument != null) {
            lastDocument.cleanup();
        }

        finalize();
    }

    protected void finalize() {
        removeAllListeners();

        listeners = null;

        encoding = null;
        name = null;
        file = null;
        url = null;

        document = null;
        text = null;

        grammar = null;

        exception = null;
        //      validationException   = null;

        elementNames = null;
        attributeNames = null;
        namespaces = null;

        modified = 0;
    }

    public boolean hasEncoding() {
        return hasEncoding;
    }

    public boolean hasDeclaration() {
        if (text != null) {
            return text.indexOf("<?xml") != -1;
        } else {
            return false;
        }

    }

    /**
     * Updates the XML declaration values (i.e version, encoding and standalone values)
     */
    private void updateDeclaration() {
        try {
            int start = text.indexOf("<?xml");
            if (start == -1 || text.trim().startsWith("<?xml-stylesheet")) {
                // no XML declaration so set the defaults
                setVersion("1.0");
                setEncoding("UTF-8");
                setStandalone(STANDALONE_NONE);
                return;
            }

            int end = text.indexOf("?>");

            // get the declaration, as need to search for encoding and standalone, cant use full text for this
            // in case "encoding" of "standalone" occurs in the document anyway
            String decl = text.substring(start, end);

            String versionValue = getAttributeValue(decl, "version");
            if (versionValue != null) {
                setVersion(versionValue);
            } else {
                throw new Exception("No XML version found");
            }

            String encodingValue = getAttributeValue(decl, "encoding");
            if (encodingValue != null) {
                hasEncoding = true;
                setEncoding(encodingValue);
            } else {
                hasEncoding = false;
                setEncoding("UTF-8");
            }

            String standaloneValue = getAttributeValue(decl, "standalone");
            if (standaloneValue != null) {
                setStandalone(standaloneValue);
            } else {
                setStandalone(STANDALONE_NONE);
            }
        } catch (Exception e) {
            System.out.println("The following error occurred parsing the XML declaration: " + e.toString());
        }

    }

    /**
     * Gets the value of an XML attribute
     * 
     * @param text The XML like text
     * @param attr The attribute name
     * 
     * @return Returns the attribue value
     */
    private String getAttributeValue(String text, String attr) {
        // get the attribute start position
        int attrStart = 0;
        if ((attrStart = text.indexOf(attr)) == -1) {
            return null;
        }

        int attrValueStart = attrStart + attr.length();
        ;
        char next = text.charAt(attrValueStart);
        while ((next != '"') && (next != '\'')) {
            attrValueStart++;
            next = text.charAt(attrValueStart);
        }

        // get the attribute end position
        int attrValueEnd = attrValueStart + 1;
        next = text.charAt(attrValueEnd);
        while ((next != '"') && (next != '\'')) {
            attrValueEnd++;
            next = text.charAt(attrValueEnd);
        }

        return text.substring(attrValueStart + 1, attrValueEnd);
    }

    /**
     * @return Returns the standalone value
     */
    public String getStandalone() {
        return standalone;
    }

    /**
     * @param standalone The standalone value to set.
     */
    public void setStandalone(String standalone) {
        this.standalone = standalone;
    }

    /**
     * @return Returns the version.
     */
    public String getVersion() {
        return version;
    }

    /**
     * @param version The version to set.
     */
    public void setVersion(String version) {
        this.version = version;
    }

    private class Counter {
        public int counter = 1;

    }
}