/* XMLElement.java                                                 NanoXML/Java
 *
 * This file is part of NanoXML 2 for Java.
 * Copyright (C) 2000-2002 Marc De Scheemaecker, All Rights Reserved.
 *
 * This software is provided 'as-is', without any express or implied warranty.
 * In no event will the authors be held liable for any damages arising from the
 * use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 *  1. The origin of this software must not be misrepresented; you must not
 *     claim that you wrote the original software. If you use this software in
 *     a product, an acknowledgment in the product documentation would be
 *     appreciated but is not required.
 *
 *  2. Altered source versions must be plainly marked as such, and must not be
 *     misrepresented as being the original software.
 *
 *  3. This notice may not be removed or altered from any source distribution.
 */

package processing.xml;

import java.io.*;
import java.util.*;

import processing.core.PApplet;


/**
 * XMLElement is an XML element. This is the base class used for the 
 * Processing XML library, representing a single node of an XML tree.
 * 
 * This code is based on a modified version of NanoXML by Marc De Scheemaecker.
 *
 * @author Marc De Scheemaecker
 * @author processing.org
 */
public class XMLElement implements Serializable {

    /**
     * No line number defined.
     */
    public static final int NO_LINE = -1;


    /**
     * The parent element.
     */
    private XMLElement parent;


    /**
     * The attributes of the element.
     */
    private Vector<XMLAttribute> attributes;


    /**
     * The child elements.
     */
    private Vector<XMLElement> children;


    /**
     * The name of the element.
     */
    private String name;


    /**
     * The full name of the element.
     */
    private String fullName;


    /**
     * The namespace URI.
     */
    private String namespace;


    /**
     * The content of the element.
     */
    private String content;


    /**
     * The system ID of the source data where this element is located.
     */
    private String systemID;


    /**
     * The line in the source data where this element starts.
     */
    private int lineNr;


    /**
     * Creates an empty element to be used for #PCDATA content.
     */
    public XMLElement() {
        this(null, null, null, NO_LINE);
    }


    protected void set(String fullName,
                           String namespace,
                           String systemID,
                           int lineNr) {
        this.fullName = fullName;
        if (namespace == null) {
                this.name = fullName;
        } else {
                int index = fullName.indexOf(':');
                if (index >= 0) {
                        this.name = fullName.substring(index + 1);
                } else {
                        this.name = fullName;
                }
        }
        this.namespace = namespace;
        this.lineNr = lineNr;
        this.systemID = systemID;
    }


    /**
     * Creates an empty element.
     *
     * @param fullName the name of the element.
     */
//    public XMLElement(String fullName) {
//        this(fullName, null, null, NO_LINE);
//    }


    /**
     * Creates an empty element.
     *
     * @param fullName the name of the element.
     * @param systemID the system ID of the XML data where the element starts.
     * @param lineNr   the line in the XML data where the element starts.
     */
//    public XMLElement(String fullName,
//                      String systemID,
//                      int    lineNr) {
//        this(fullName, null, systemID, lineNr);
//    }


    /**
     * Creates an empty element.
     *
     * @param fullName  the full name of the element
     * @param namespace the namespace URI.
     */
//    public XMLElement(String fullName,
//                      String namespace) {
//        this(fullName, namespace, null, NO_LINE);
//    }


    /**
     * Creates an empty element.
     *
     * @param fullName  the full name of the element
     * @param namespace the namespace URI.
     * @param systemID  the system ID of the XML data where the element starts.
     * @param lineNr    the line in the XML data where the element starts.
     */
    public XMLElement(String fullName,
                      String namespace,
                      String systemID,
                      int    lineNr) {
        this.attributes = new Vector<XMLAttribute>();
        this.children = new Vector<XMLElement>(8);
        this.fullName = fullName;
        if (namespace == null) {
            this.name = fullName;
        } else {
            int index = fullName.indexOf(':');
            if (index >= 0) {
                this.name = fullName.substring(index + 1);
            } else {
                this.name = fullName;
            }
        }
        this.namespace = namespace;
        this.content = null;
        this.lineNr = lineNr;
        this.systemID = systemID;
        this.parent = null;
    }


    /**
     * Begin parsing XML data passed in from a PApplet. This code
     * wraps exception handling, for more advanced exception handling,
     * use the constructor that takes a Reader or InputStream.
     * @author processing.org
     * @param filename
     * @param parent
     */
    public XMLElement(PApplet parent, String filename) {
        this();
        parseFromReader(parent.createReader(filename));
    }


    public XMLElement(Reader r) {
        this();
        parseFromReader(r);
    }


    public XMLElement(String xml) {
        this();
        parseFromReader(new StringReader(xml));
    }


    protected void parseFromReader(Reader r) {
        try {
            StdXMLParser parser = new StdXMLParser();
            parser.setBuilder(new StdXMLBuilder(this));
            parser.setValidator(new XMLValidator());
            parser.setReader(new StdXMLReader(r));
            //System.out.println(parser.parse().getName());
            /*XMLElement xm = (XMLElement)*/ parser.parse();
            //System.out.println("xm name is " + xm.getName());
            //System.out.println(xm + " " + this);
            //parser.parse();
        } catch (XMLException e) {
            e.printStackTrace();
        }
    }


//    static public XMLElement parse(Reader r) {
//        try {
//              StdXMLParser parser = new StdXMLParser();
//              parser.setBuilder(new StdXMLBuilder());
//              parser.setValidator(new XMLValidator());
//              parser.setReader(new StdXMLReader(r));
//              return (XMLElement) parser.parse();
//              } catch (XMLException e) {
//                      e.printStackTrace();
//                      return null;
//              }
//    }


    /**
     * Creates an element to be used for #PCDATA content.
     */
    public XMLElement createPCDataElement() {
        return new XMLElement();
    }


    /**
     * Creates an empty element.
     *
     * @param fullName the name of the element.
     */
//    public XMLElement createElement(String fullName) {
//        return new XMLElement(fullName);
//    }


    /**
     * Creates an empty element.
     *
     * @param fullName the name of the element.
     * @param systemID the system ID of the XML data where the element starts.
     * @param lineNr   the line in the XML data where the element starts.
     */
//    public XMLElement createElement(String fullName,
//                                    String systemID,
//                                    int lineNr) {
//        //return new XMLElement(fullName, systemID, lineNr);
//      return new XMLElement(fullName, null, systemID, lineNr);
//    }


    /**
     * Creates an empty element.
     *
     * @param fullName  the full name of the element
     * @param namespace the namespace URI.
     */
    public XMLElement createElement(String fullName,
                                    String namespace) {
        //return new XMLElement(fullName, namespace);
        return new XMLElement(fullName, namespace, null, NO_LINE);
    }


    /**
     * Creates an empty element.
     *
     * @param fullName  the full name of the element
     * @param namespace the namespace URI.
     * @param systemID  the system ID of the XML data where the element starts.
     * @param lineNr    the line in the XML data where the element starts.
     */
    public XMLElement createElement(String fullName,
                                     String namespace,
                                     String systemID,
                                     int    lineNr) {
        return new XMLElement(fullName, namespace, systemID, lineNr);
    }


    /**
     * Cleans up the object when it's destroyed.
     */
    protected void finalize() throws Throwable {
        this.attributes.clear();
        this.attributes = null;
        this.children = null;
        this.fullName = null;
        this.name = null;
        this.namespace = null;
        this.content = null;
        this.systemID = null;
        this.parent = null;
        super.finalize();
    }


    /**
     * Returns the parent element. This method returns null for the root
     * element.
     */
    public XMLElement getParent() {
        return this.parent;
    }


    /**
     * Returns the full name (i.e. the name including an eventual namespace
     * prefix) of the element.
     *
     * @return the name, or null if the element only contains #PCDATA.
     */
    public String getName() {
        return this.fullName;
    }


    /**
     * Returns the name of the element.
     *
     * @return the name, or null if the element only contains #PCDATA.
     */
    public String getLocalName() {
        return this.name;
    }


    /**
     * Returns the namespace of the element.
     *
     * @return the namespace, or null if no namespace is associated with the
     *         element.
     */
    public String getNamespace() {
        return this.namespace;
    }


    /**
     * Sets the full name. This method also sets the short name and clears the
     * namespace URI.
     *
     * @param name the non-null name.
     */
    public void setName(String name) {
        this.name = name;
        this.fullName = name;
        this.namespace = null;
    }


    /**
     * Sets the name.
     *
     * @param fullName  the non-null full name.
     * @param namespace the namespace URI, which may be null.
     */
    public void setName(String fullName, String namespace) {
        int index = fullName.indexOf(':');
        if ((namespace == null) || (index < 0)) {
            this.name = fullName;
        } else {
            this.name = fullName.substring(index + 1);
        }
        this.fullName = fullName;
        this.namespace = namespace;
    }


    /**
     * Adds a child element.
     *
     * @param child the non-null child to add.
     */
    public void addChild(XMLElement child) {
        if (child == null) {
            throw new IllegalArgumentException("child must not be null");
        }
        if ((child.getLocalName() == null) && (! this.children.isEmpty())) {
            XMLElement lastChild = (XMLElement) this.children.lastElement();

            if (lastChild.getLocalName() == null) {
                lastChild.setContent(lastChild.getContent()
                                     + child.getContent());
                return;
            }
        }
        ((XMLElement)child).parent = this;
        this.children.addElement(child);
    }


    /**
     * Inserts a child element.
     *
     * @param child the non-null child to add.
     * @param index where to put the child.
     */
    public void insertChild(XMLElement child, int index) {
        if (child == null) {
            throw new IllegalArgumentException("child must not be null");
        }
        if ((child.getLocalName() == null) && (! this.children.isEmpty())) {
            XMLElement lastChild = (XMLElement) this.children.lastElement();
            if (lastChild.getLocalName() == null) {
                lastChild.setContent(lastChild.getContent()
                                     + child.getContent());
                return;
            }
        }
        ((XMLElement) child).parent = this;
        this.children.insertElementAt(child, index);
    }


    /**
     * Removes a child element.
     *
     * @param child the non-null child to remove.
     */
    public void removeChild(XMLElement child) {
        if (child == null) {
            throw new IllegalArgumentException("child must not be null");
        }
        this.children.removeElement(child);
    }


    /**
     * Removes the child located at a certain index.
     *
     * @param index the index of the child, where the first child has index 0.
     */
    public void removeChildAtIndex(int index) {
        this.children.removeElementAt(index);
    }


    /**
     * Returns an enumeration of all child elements.
     *
     * @return the non-null enumeration
     */
    public Enumeration<XMLElement> enumerateChildren() {
        return this.children.elements();
    }


    /**
     * Returns whether the element is a leaf element.
     *
     * @return true if the element has no children.
     */
    public boolean isLeaf() {
        return this.children.isEmpty();
    }


    /**
     * Returns whether the element has children.
     *
     * @return true if the element has children.
     */
    public boolean hasChildren() {
        return (! this.children.isEmpty());
    }


    /**
     * Returns the number of children.
     *
     * @return the count.
     */
    public int getChildCount() {
        return this.children.size();
    }


    /**
     * Returns a vector containing all the child elements.
     *
     * @return the vector.
     */
//    public Vector getChildren() {
//        return this.children;
//    }


    /**
     * Put the names of all children into an array. Same as looping through 
     * each child and calling getName() on each XMLElement.
     */
    public String[] listChildren() {
      int childCount = getChildCount();
      String[] outgoing = new String[childCount];
      for (int i = 0; i < childCount; i++) {
        outgoing[i] = getChild(i).getName();
      }
      return outgoing;
    }
    
    
    /**
     * Returns an array containing all the child elements.
     */
    public XMLElement[] getChildren() {
        int childCount = getChildCount();
        XMLElement[] kids = new XMLElement[childCount];
        children.copyInto(kids);
        return kids;
    }


    /**
     * Quick accessor for an element at a particular index.
     * @author processing.org
     */
    public XMLElement getChild(int which) {
        return (XMLElement) children.elementAt(which);
    }


    /**
     * Get a child by its name or path.
     * @param name element name or path/to/element
     * @return the element
     * @author processing.org
     */
    public XMLElement getChild(String name) {
        if (name.indexOf('/') != -1) {
            return getChildRecursive(PApplet.split(name, '/'), 0);
        }
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            XMLElement kid = getChild(i);
            String kidName = kid.getName();
            if (kidName != null && kidName.equals(name)) {
                return kid;
            }
        }
        return null;
    }


    /**
     * Internal helper function for getChild(String).
     * @param items result of splitting the query on slashes
     * @param offset where in the items[] array we're currently looking
     * @return matching element or null if no match
     * @author processing.org
     */
    protected XMLElement getChildRecursive(String[] items, int offset) {
        // if it's a number, do an index instead
        if (Character.isDigit(items[offset].charAt(0))) {
            XMLElement kid = getChild(Integer.parseInt(items[offset]));
            if (offset == items.length-1) {
                return kid;
            } else {
                return kid.getChildRecursive(items, offset+1);
            }
        }
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            XMLElement kid = getChild(i);
            String kidName = kid.getName();
            if (kidName != null && kidName.equals(items[offset])) {
                if (offset == items.length-1) {
                    return kid;
                } else {
                    return kid.getChildRecursive(items, offset+1);
                }
            }
        }
        return null;
    }


    /**
     * Returns the child at a specific index.
     *
     * @param index the index of the child
     *
     * @return the non-null child
     *
     * @throws java.lang.ArrayIndexOutOfBoundsException
     *          if the index is out of bounds.
     */
    public XMLElement getChildAtIndex(int index)
    throws ArrayIndexOutOfBoundsException {
        return (XMLElement) this.children.elementAt(index);
    }


    /**
     * Searches a child element.
     *
     * @param name the full name of the child to search for.
     *
     * @return the child element, or null if no such child was found.
     */
//    public XMLElement getFirstChildNamed(String name) {
//        Enumeration enum = this.children.elements();
//        while (enum.hasMoreElements()) {
//            XMLElement child = (XMLElement) enum.nextElement();
//            String childName = child.getFullName();
//            if ((childName != null) && childName.equals(name)) {
//                return child;
//            }
//        }
//        return null;
//    }


    /**
     * Searches a child element.
     *
     * @param name      the name of the child to search for.
     * @param namespace the namespace, which may be null.
     *
     * @return the child element, or null if no such child was found.
     */
//    public XMLElement getFirstChildNamed(String name,
//                                          String namespace) {
//        Enumeration enum = this.children.elements();
//        while (enum.hasMoreElements()) {
//            XMLElement child = (XMLElement) enum.nextElement();
//            String str = child.getName();
//            boolean found = (str != null) && (str.equals(name));
//            str = child.getNamespace();
//            if (str == null) {
//                found &= (name == null);
//            } else {
//                found &= str.equals(namespace);
//            }
//            if (found) {
//                return child;
//            }
//        }
//        return null;
//    }


    /**
     * Get any children that match this name or path. Similar to getChild(),
     * but will grab multiple matches rather than only the first.
     * @param name element name or path/to/element
     * @return array of child elements that match
     * @author processing.org
     */
    public XMLElement[] getChildren(String name) {
        if (name.indexOf('/') != -1) {
        	return getChildrenRecursive(PApplet.split(name, '/'), 0);
        }
        // if it's a number, do an index instead
        // (returns a single element array, since this will be a single match
        if (Character.isDigit(name.charAt(0))) {
        	return new XMLElement[] { getChild(Integer.parseInt(name)) };
        }
        int childCount = getChildCount();
        XMLElement[] matches = new XMLElement[childCount];
        int matchCount = 0;
        for (int i = 0; i < childCount; i++) {
        	XMLElement kid = getChild(i);
        	String kidName = kid.getName();
        	if (kidName != null && kidName.equals(name)) {
        		matches[matchCount++] = kid;
        	}
        }
        return (XMLElement[]) PApplet.subset(matches, 0, matchCount);
    }


    protected XMLElement[] getChildrenRecursive(String[] items, int offset) {
    	if (offset == items.length-1) {
    		return getChildren(items[offset]);
    	}
    	XMLElement[] matches = getChildren(items[offset]);
    	XMLElement[] outgoing = new XMLElement[0];
    	for (int i = 0; i < matches.length; i++) {
    		XMLElement[] kidMatches = matches[i].getChildrenRecursive(items, offset+1);
    		outgoing = (XMLElement[]) PApplet.concat(outgoing, kidMatches);
    	}
    	return outgoing;
    }


    /**
     * Returns a vector of all child elements named <I>name</I>.
     *
     * @param name the full name of the children to search for.
     *
     * @return the non-null vector of child elements.
     */
//    public Vector getChildrenNamed(String name) {
//        Vector result = new Vector(this.children.size());
//        Enumeration enum = this.children.elements();
//        while (enum.hasMoreElements()) {
//            XMLElement child = (XMLElement) enum.nextElement();
//            String childName = child.getFullName();
//            if ((childName != null) && childName.equals(name)) {
//                result.addElement(child);
//            }
//        }
//        return result;
//    }


    /**
     * Returns a vector of all child elements named <I>name</I>.
     *
     * @param name      the name of the children to search for.
     * @param namespace the namespace, which may be null.
     *
     * @return the non-null vector of child elements.
     */
//    public Vector getChildrenNamed(String name,
//                                   String namespace) {
//        Vector result = new Vector(this.children.size());
//        Enumeration enum = this.children.elements();
//        while (enum.hasMoreElements()) {
//            XMLElement child = (XMLElement) enum.nextElement();
//            String str = child.getName();
//            boolean found = (str != null) && (str.equals(name));
//            str = child.getNamespace();
//            if (str == null) {
//                found &= (name == null);
//            } else {
//                found &= str.equals(namespace);
//            }
//
//            if (found) {
//                result.addElement(child);
//            }
//        }
//        return result;
//    }


    /**
     * Searches an attribute.
     *
     * @param fullName the non-null full name of the attribute.
     *
     * @return the attribute, or null if the attribute does not exist.
     */
    private XMLAttribute findAttribute(String fullName) {
        Enumeration<XMLAttribute> en = this.attributes.elements();
        while (en.hasMoreElements()) {
            XMLAttribute attr = (XMLAttribute) en.nextElement();
            if (attr.getFullName().equals(fullName)) {
                return attr;
            }
        }
        return null;
    }


    /**
     * Searches an attribute.
     *
     * @param name the non-null short name of the attribute.
     * @param namespace the name space, which may be null.
     *
     * @return the attribute, or null if the attribute does not exist.
     */
    private XMLAttribute findAttribute(String name,
                                       String namespace) {
        Enumeration<XMLAttribute> en = this.attributes.elements();
        while (en.hasMoreElements()) {
            XMLAttribute attr = (XMLAttribute) en.nextElement();
            boolean found = attr.getName().equals(name);
            if (namespace == null) {
                found &= (attr.getNamespace() == null);
            } else {
                found &= namespace.equals(attr.getNamespace());
            }

            if (found) {
                return attr;
            }
        }
        return null;
    }


    /**
     * Returns the number of attributes.
     */
    public int getAttributeCount() {
        return this.attributes.size();
    }


    /**
     * Returns the value of an attribute.
     *
     * @param name the non-null name of the attribute.
     *
     * @return the value, or null if the attribute does not exist.
     */
    public String getAttribute(String name) {
        return this.getAttribute(name, null);
    }


    /**
     * Returns the value of an attribute.
     *
     * @param name the non-null full name of the attribute.
     * @param defaultValue the default value of the attribute.
     *
     * @return the value, or defaultValue if the attribute does not exist.
     */
    public String getAttribute(String name,
                               String defaultValue) {
        XMLAttribute attr = this.findAttribute(name);
        if (attr == null) {
            return defaultValue;
        } else {
            return attr.getValue();
        }
    }


    /**
     * Returns the value of an attribute.
     *
     * @param name the non-null name of the attribute.
     * @param namespace the namespace URI, which may be null.
     * @param defaultValue the default value of the attribute.
     *
     * @return the value, or defaultValue if the attribute does not exist.
     */
    public String getAttribute(String name,
                               String namespace,
                               String defaultValue) {
        XMLAttribute attr = this.findAttribute(name, namespace);
        if (attr == null) {
            return defaultValue;
        } else {
            return attr.getValue();
        }
    }


    public String getStringAttribute(String name) {
        return getAttribute(name);
    }


    public String getStringAttribute(String name, String defaultValue) {
        return getAttribute(name, defaultValue);
    }


    public String getStringAttribute(String name,
                                     String namespace,
                                     String defaultValue) {
        return getAttribute(name, namespace, defaultValue);
    }


    public int getIntAttribute(String name) {
        return getIntAttribute(name, 0);
    }


    /**
     * Returns the value of an attribute.
     *
     * @param name the non-null full name of the attribute.
     * @param defaultValue the default value of the attribute.
     *
     * @return the value, or defaultValue if the attribute does not exist.
     */
    public int getIntAttribute(String name,
                               int defaultValue) {
        String value = this.getAttribute(name, Integer.toString(defaultValue));
        return Integer.parseInt(value);
    }


    /**
     * Returns the value of an attribute.
     *
     * @param name the non-null name of the attribute.
     * @param namespace the namespace URI, which may be null.
     * @param defaultValue the default value of the attribute.
     *
     * @return the value, or defaultValue if the attribute does not exist.
     */
    public int getIntAttribute(String name,
                               String namespace,
                               int defaultValue) {
        String value = this.getAttribute(name, namespace,
                                         Integer.toString(defaultValue));
        return Integer.parseInt(value);
    }


    public float getFloatAttribute(String name) {
        return getFloatAttribute(name, 0);
    }


    /**
     * Returns the value of an attribute.
     *
     * @param name the non-null full name of the attribute.
     * @param defaultValue the default value of the attribute.
     *
     * @return the value, or defaultValue if the attribute does not exist.
     */
    public float getFloatAttribute(String name,
                                   float defaultValue) {
        String value = this.getAttribute(name, Float.toString(defaultValue));
        return Float.parseFloat(value);
    }


    /**
     * Returns the value of an attribute.
     *
     * @param name the non-null name of the attribute.
     * @param namespace the namespace URI, which may be null.
     * @param defaultValue the default value of the attribute.
     *
     * @return the value, or defaultValue if the attribute does not exist.
     */
    public float getFloatAttribute(String name,
                                   String namespace,
                                   float defaultValue) {
        String value = this.getAttribute(name, namespace,
                                         Float.toString(defaultValue));
        return Float.parseFloat(value);
    }


    public double getDoubleAttribute(String name) {
        return getDoubleAttribute(name, 0);
    }


    /**
     * Returns the value of an attribute.
     *
     * @param name the non-null full name of the attribute.
     * @param defaultValue the default value of the attribute.
     *
     * @return the value, or defaultValue if the attribute does not exist.
     */
    public double getDoubleAttribute(String name,
                                     double defaultValue) {
        String value = this.getAttribute(name, Double.toString(defaultValue));
        return Double.parseDouble(value);
    }


    /**
     * Returns the value of an attribute.
     *
     * @param name the non-null name of the attribute.
     * @param namespace the namespace URI, which may be null.
     * @param defaultValue the default value of the attribute.
     *
     * @return the value, or defaultValue if the attribute does not exist.
     */
    public double getDoubleAttribute(String name,
                                     String namespace,
                                     double defaultValue) {
        String value = this.getAttribute(name, namespace,
                                         Double.toString(defaultValue));
        return Double.parseDouble(value);
    }


    /**
     * Returns the type of an attribute.
     *
     * @param name the non-null full name of the attribute.
     *
     * @return the type, or null if the attribute does not exist.
     */
    public String getAttributeType(String name) {
        XMLAttribute attr = this.findAttribute(name);
        if (attr == null) {
            return null;
        } else {
            return attr.getType();
        }
    }


    /**
     * Returns the namespace of an attribute.
     *
     * @param name the non-null full name of the attribute.
     *
     * @return the namespace, or null if there is none associated.
     */
    public String getAttributeNamespace(String name) {
        XMLAttribute attr = this.findAttribute(name);
        if (attr == null) {
            return null;
        } else {
            return attr.getNamespace();
        }
    }


    /**
     * Returns the type of an attribute.
     *
     * @param name the non-null name of the attribute.
     * @param namespace the namespace URI, which may be null.
     *
     * @return the type, or null if the attribute does not exist.
     */
    public String getAttributeType(String name,
                                   String namespace) {
        XMLAttribute attr = this.findAttribute(name, namespace);
        if (attr == null) {
            return null;
        } else {
            return attr.getType();
        }
    }


    /**
     * Sets an attribute.
     *
     * @param name the non-null full name of the attribute.
     * @param value the non-null value of the attribute.
     */
    public void setAttribute(String name,
                             String value) {
        XMLAttribute attr = this.findAttribute(name);
        if (attr == null) {
            attr = new XMLAttribute(name, name, null, value, "CDATA");
            this.attributes.addElement(attr);
        } else {
            attr.setValue(value);
        }
    }


    /**
     * Sets an attribute.
     *
     * @param fullName the non-null full name of the attribute.
     * @param namespace the namespace URI of the attribute, which may be null.
     * @param value the non-null value of the attribute.
     */
    public void setAttribute(String fullName,
                             String namespace,
                             String value) {
        int index = fullName.indexOf(':');
        String vorname = fullName.substring(index + 1);
        XMLAttribute attr = this.findAttribute(vorname, namespace);
        if (attr == null) {
            attr = new XMLAttribute(fullName, vorname, namespace, value, "CDATA");
            this.attributes.addElement(attr);
        } else {
            attr.setValue(value);
        }
    }


    /**
     * Removes an attribute.
     *
     * @param name the non-null name of the attribute.
     */
    public void removeAttribute(String name) {
        for (int i = 0; i < this.attributes.size(); i++) {
            XMLAttribute attr = (XMLAttribute) this.attributes.elementAt(i);
            if (attr.getFullName().equals(name)) {
                this.attributes.removeElementAt(i);
                return;
            }
        }
    }


    /**
     * Removes an attribute.
     *
     * @param name the non-null name of the attribute.
     * @param namespace the namespace URI of the attribute, which may be null.
     */
    public void removeAttribute(String name,
                                String namespace) {
        for (int i = 0; i < this.attributes.size(); i++) {
            XMLAttribute attr = (XMLAttribute) this.attributes.elementAt(i);
            boolean found = attr.getName().equals(name);
            if (namespace == null) {
                found &= (attr.getNamespace() == null);
            } else {
                found &= attr.getNamespace().equals(namespace);
            }

            if (found) {
                this.attributes.removeElementAt(i);
                return;
            }
        }
    }


    /**
     * Returns an enumeration of all attribute names.
     *
     * @return the non-null enumeration.
     */
    public Enumeration<String> enumerateAttributeNames() {
        Vector<String> result = new Vector<String>();
        Enumeration<XMLAttribute> en = this.attributes.elements();
        while (en.hasMoreElements()) {
            XMLAttribute attr = (XMLAttribute) en.nextElement();
            result.addElement(attr.getFullName());
        }
        return result.elements();
    }


    /**
     * Returns whether an attribute exists.
     *
     * @return true if the attribute exists.
     */
    public boolean hasAttribute(String name) {
        return this.findAttribute(name) != null;
    }


    /**
     * Returns whether an attribute exists.
     *
     * @return true if the attribute exists.
     */
    public boolean hasAttribute(String name,
                                String namespace) {
        return this.findAttribute(name, namespace) != null;
    }


    /**
     * Returns all attributes as a Properties object.
     *
     * @return the non-null set.
     */
    public Properties getAttributes() {
        Properties result = new Properties();
        Enumeration<XMLAttribute> en = this.attributes.elements();
        while (en.hasMoreElements()) {
            XMLAttribute attr = (XMLAttribute) en.nextElement();
            result.put(attr.getFullName(), attr.getValue());
        }
        return result;
    }


    /**
     * Returns all attributes in a specific namespace as a Properties object.
     *
     * @param namespace the namespace URI of the attributes, which may be null.
     *
     * @return the non-null set.
     */
    public Properties getAttributesInNamespace(String namespace) {
        Properties result = new Properties();
        Enumeration<XMLAttribute> en = this.attributes.elements();
        while (en.hasMoreElements()) {
            XMLAttribute attr = (XMLAttribute) en.nextElement();
            if (namespace == null) {
                if (attr.getNamespace() == null) {
                    result.put(attr.getName(), attr.getValue());
                }
            } else {
                if (namespace.equals(attr.getNamespace())) {
                    result.put(attr.getName(), attr.getValue());
                }
            }
        }
        return result;
    }


    /**
     * Returns the system ID of the data where the element started.
     *
     * @return the system ID, or null if unknown.
     *
     * @see #getLineNr
     */
    public String getSystemID() {
        return this.systemID;
    }


    /**
     * Returns the line number in the data where the element started.
     *
     * @return the line number, or NO_LINE if unknown.
     *
     * @see #NO_LINE
     * @see #getSystemID
     */
    public int getLineNr() {
        return this.lineNr;
    }


    /**
     * Return the #PCDATA content of the element. If the element has a
     * combination of #PCDATA content and child elements, the #PCDATA
     * sections can be retrieved as unnamed child objects. In this case,
     * this method returns null.
     *
     * @return the content.
     */
    public String getContent() {
        return this.content;
    }


    /**
     * Sets the #PCDATA content. It is an error to call this method with a
     * non-null value if there are child objects.
     *
     * @param content the (possibly null) content.
     */
    public void setContent(String content) {
        this.content = content;
    }


    /**
     * Returns true if the element equals another element.
     *
     * @param rawElement the element to compare to
     */
    public boolean equals(Object rawElement) {
        try {
            return this.equalsXMLElement((XMLElement) rawElement);
        } catch (ClassCastException e) {
            return false;
        }
    }


    /**
     * Returns true if the element equals another element.
     *
     * @param rawElement the element to compare to
     */
    public boolean equalsXMLElement(XMLElement rawElement) {
        if (! this.name.equals(rawElement.getLocalName())) {
            return false;
        }
        if (this.attributes.size() != rawElement.getAttributeCount()) {
            return false;
        }
        Enumeration<XMLAttribute> en = this.attributes.elements();
        while (en.hasMoreElements()) {
            XMLAttribute attr = (XMLAttribute) en.nextElement();
            if (! rawElement.hasAttribute(attr.getName(), attr.getNamespace())) {
                return false;
            }
            String value = rawElement.getAttribute(attr.getName(),
                                            attr.getNamespace(),
                                            null);
            if (! attr.getValue().equals(value)) {
                return false;
            }
            String type = rawElement.getAttributeType(attr.getName(),
                                               attr.getNamespace());
            if (! attr.getType().equals(type)) {
                return false;
            }
        }
        if (this.children.size() != rawElement.getChildCount()) {
            return false;
        }
        for (int i = 0; i < this.children.size(); i++) {
            XMLElement child1 = this.getChildAtIndex(i);
            XMLElement child2 = rawElement.getChildAtIndex(i);

            if (! child1.equalsXMLElement(child2)) {
                return false;
            }
        }
        return true;
    }


    public String toString() {
      return toString(true);
    }
    
    
    public String toString(boolean pretty) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        OutputStreamWriter osw = new OutputStreamWriter(baos);
        XMLWriter writer = new XMLWriter(osw);
        try {
          if (pretty) {
            writer.write(this, true, 2, true);
          } else {
            writer.write(this, false, 0, true);
          }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return baos.toString();
    }
}
