/* $Id: XMLElement.java,v 1.2 2003/02/27 03:12:51 tsm Exp $

Copyright (c) 2003 Tienshiao Ma

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the 
"Software"), to deal in the Software without restriction, including 
without limitation the rights to use, copy, modify, merge, publish, 
distribute, sublicense, and/or sell copies of the Software, and to 
permit persons to whom the Software is furnished to do so, subject to 
the following conditions:

The above copyright notice and this permission notice shall be included 
in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
DEALINGS IN THE SOFTWARE.

 */

package org.tienshiao.XMLLite;

import java.io.*;
import java.util.*;

/**
 * XMLElement represents an element of the XML document tree.
 * @author  Tienshiao Ma
 */
public class XMLElement {
    
    private String name;
    private String content;
    private Hashtable attributes;
    private Vector children;
        
    /** Creates a new instance of XMLElement */
    public XMLElement() {
        attributes = new Hashtable();
        children = new Vector();
    }
    
    /** 
     * Returns the name of the element. 
     * @return          name of element
     */
    public String getName() {
        return name;
    }
    
    /** 
     * Sets the name of the element
     * @param   name    string of new name
     */
    public void setName(String name) {
        this.name = name;
    }
    
    /**
     * Gets the content of the element as a string. The contents are the text
     * between the start tag and the end tag.  Comments and other tags found
     * between the tags are not included.
     * @return          string of contents
     */
    public String getContent() {
        return content;
    }
    
    /**
     * Sets the content of the element.
     * @param content   
     */
    public void setContent(String content) {
        this.content = content;
    }
    
    /**
     * Returns the number of attributes of this element.
     * @return          number of attributes.
     */
    public int countAttributes() {
        return attributes.size();
    }
    
    /**
     * Returns the value associated with the attributed referred to by name.
     * @param name      name of attribute
     * @return          value of attribute
     */
    public Object getAttribute(String name) {
       return attributes.get(name);
    }
    
    /**
     * Removes the attribute (and associated value) referred to by name. 
     * Returns the value object.  Basically a wrapper for Hashtable.remove.
     * @param name      name of attribute
     * @return          value of attribute
     */
    public Object removeAttribute(String name) {
        return attributes.remove(name);
    }
    
    /**
     * Sets the value of the attribute referred to by name.  If the attribute
     * does not exist, it is created.  Otherwise the previous value is 
     * overwritten.
     * @param name      name of attribute
     *        value     new value of attribute
     * @return
     */
    public Object setAttribute(String name, Object value) {
        return attributes.put(name, value);
    }
    
    /**
     * Sets the attribute hashtable to attrs. If there were previous attributes
     * they are overwritten.
     * @param attrs     Hashtable of attributes (String, Object pairs)
     * @return
     */
    public void setAttributes(Hashtable attrs) {
        attributes = attrs;
    }
    
    /**
     * Returns an Enumeration of the the attribute names of this XMLElement.
     * @return          Enumeration of attribute names
     */
    public Enumeration enumerateAttributeNames() {
        return attributes.keys();
    }
    
    /**
     * Returns the number of children XMLElements.
     * @return
     */
    public int countChildren() {
        return children.size();
    }
    
    /**
     * Returns an Enumeration of the children of this XMLElement.
     */
    public Enumeration enumerateChildren() {
        return children.elements();
    }
    
    /**
     * Adds a new child XMLElement to this XMLElement.
     * @param child     Child element
     */
    public void addChild(XMLElement child) {
        children.addElement(child);
    }
    
    /**
     * getChildren returns the Vector of children, may be null
     * @return          Vector of XMLElements
     */
    public Vector getChildren() {
        return children;
    }
    
    /**
     * Removes the referenced child from the Vector of children.
     * @param   child   reference to child to remove
     * @return          success of removal
     */
    public boolean removeChild(XMLElement child) {
        return children.removeElement(child);
    }
    
    /**
     * Converts this XMLElement to a String.
     * @return          text representation of XML
     */
    public String toString() {
        StringBuffer sb = new StringBuffer("<");
        sb.append(this.name);
        if (attributes != null) {
            for (Enumeration e = attributes.keys(); e.hasMoreElements();) {
                Object temp = e.nextElement();
                sb.append(' ');
                sb.append(temp);
                sb.append("=\"");
                sb.append(attributes.get(temp));
                sb.append('"');
            }
        }
        if ((content == null) && ((children == null) || children.size() == 0)) {
            sb.append("/>\n");
        } else {
            sb.append('>');
            if (content != null) {
                sb.append(content);
            } 
            if (children != null && children.size() > 0) {
                sb.append('\n');
                for (int i = 0; i < children.size(); i++) {
                    sb.append(children.elementAt(i).toString());
                }
            }
            sb.append("</");
            sb.append(this.name);
            sb.append(">\n");
        }
        return new String(sb);
    }
    
    /**
     * parseFromReader takes the input from a reader to construct a model
     * view of the XML document. The root of the document tree is "this".
     * Uses/depends on XMLPullParser class.
     * @param   r       input reader
     * @exception       java.io.IOException XMLParserException
     */
    public void parseFromReader(Reader r) throws IOException, XMLParserException {
        XMLPullParser xpp = new XMLPullParser(r);
        boolean keepParsing = true;
        Stack stack = new Stack();
        XMLElement current = this;
        
        while(keepParsing) {
            xpp.getNextToken();
 
            switch(xpp.getType()) {
                case XMLPullParser.START_TAG:
                    if (this.name != null) {
                        XMLElement child = new XMLElement();
                        current.addChild(child);
                        current = child;
                    } 
                    current.setName(xpp.getName());
                    current.setAttributes(xpp.getAttributes());
                    stack.push(current);
                    break;
                case XMLPullParser.END_TAG:
                    if (xpp.getName().compareTo(((XMLElement) stack.pop()).getName()) != 0) {
                        throw new XMLParserException("Mismatched end tag", xpp.getLineNumber());
                    }
                    if (stack.size() == 0) { // parsed endtag for root
                        keepParsing = false;
                    } else {
                        current = (XMLElement) stack.peek();
                    }
                    break;
                case XMLPullParser.EMPTY_TAG:
                    if (this.name == null) { // it's the root tag
                        current.setName(xpp.getName());
                        current.setAttributes(xpp.getAttributes());
                        keepParsing = false;
                    } else {  // not the root tag
                        XMLElement child = new XMLElement();
                        child.setName(xpp.getName());
                        child.setAttributes(xpp.getAttributes());
                        current.addChild(child);
                    } 
                    break;
                case XMLPullParser.TEXT:
                    if (stack.size() == 0) {
//                        throw new XMLParserException("Unexpected text");
                    } else {
                        current = (XMLElement) stack.peek();
                        if (current.content != null) {
                            current.content.concat(xpp.getText());
                        } else {
                            current.setContent(xpp.getText());
                        }
                    }
                    break;
            }
        }
    }
}