    
    
/*
    
    LMXP - a small XML parser written in pure JAVA 
    Copyright (C) 2003  Michael Bayer <nsn@nightspawn.com>
    
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    
*/


package de.nightspawn.lmxp;

import java.util.*;

/**
    this class represents a node in an XML Tree
 */
public class LMXPNode {

    public static           int                         XML_INDENT = 4;

    private                 Vector                      ChildNodes = new Vector();
    private                 HashMap                     Attributes = new HashMap();

    private                 LMXPNode                    Parent;

    private                 String                      Value;
    private                 String                      Name;

    /**
        create a new Node
     */
    public LMXPNode() {
    }

    /**
        create a new Node with a parent
     */
    public LMXPNode(LMXPNode parent) {
        Parent = parent;
    }

    /**
        create a new Node with a name
     */
    public LMXPNode(String name) {
        this.Name = name;
    }
    /**
        create a new Node with a name and a parent
     */
    public LMXPNode(LMXPNode parent, String name) {
        this.Parent = parent;
        this.Name = name;
    }

    /**
        create a new Node with a name and a value
     */
    public LMXPNode(String name, String val) {
        this.Name = name;
        setValue(val);
    }

    /**
        create a new Node with a parent, a name and a value
     */
    public LMXPNode(LMXPNode parent, String name, String val) {
        this.Parent = parent;
        this.Name = name;
        setValue(val);
    }

    /**
        closes this node and returns it's parent
     */
    public LMXPNode closeNode() {
        this.trimValue();
        return this.getParent();
    }

    /**
        trims each of the Values lines
     */
    public void trimValue() {
        String v = this.getValue();
        if (v == null || v.equals("")) return;
        this.setValue(v.trim());
        /*
        if (v == null || v.equals("")) return;
        String s = new String();
        if (v.indexOf("\n")!=-1) {
            java.util.StringTokenizer st = new java.util.StringTokenizer(v,"\n");
            while (st.hasMoreTokens()) {
                s = s.concat(st.nextToken().trim());
            }
        } else {
            s = v.trim();
        }
        this.setValue(s);
        */
    }

    /**
        escapes all the xml-special characters in the string and returns it
     */
    public static String escapeSpecialChars(String val) {
        if (val == null) return null;
        String v = val.replaceAll("&","&amp;");
        v = v.replaceAll("<","&lt;");
        v = v.replaceAll(">","&gt;");
        return v;
    }

    /**
        unescapes all the xml-special characters in the string and returns it
     */
    public static String unescapeSpecialChars(String val) {
        if (val == null) return null;
        String v = val.replaceAll("&amp;","&");
        v = v.replaceAll("&lt;","<");
        v = v.replaceAll("&gt;",">");
        return v;
    }

    /**
        add a child Node
     */
    public void addChild(LMXPNode n) {
        n.setParent(this);
        this.ChildNodes.add(n);
    }

    /**
        delete a child node
     */
    public void delChild(LMXPNode n) {
        this.ChildNodes.removeElement(n);
    }

    /**
        detaches the subtree, returns this nodes parent
     */
    public LMXPNode detach() {
        LMXPNode parent = this.getParent();
        parent.delChild(this);
        return parent;
    }

    /**
        get all child nodes
     */
    public LMXPNode[] getChildNodes() {
        LMXPNode[] r = new LMXPNode[this.ChildNodes.size()];
        this.ChildNodes.toArray(r);
        return r;
    }

    /**
        returns the childnode with the given name, null if not found
     */
    public LMXPNode getChildNode(String name) {
        LMXPNode[] c = this.getChildNodes();
        for (int i=0;i<c.length;i++) {
            if (c[i].getName().equals(name)) {
                return c[i];
            }
        }
        return null;
    }

    /**
        returns the ith child node, null if not found
     */
    public LMXPNode getChildNode(int i) {
        LMXPNode[] c = this.getChildNodes();
        if (i<0 || i>=c.length) return null;
        return c[i];
    }

    /**
        add an attribute
     */
    public void addAttribute(String name, String value) {
        this.Attributes.put(name,value);
    }

    /**
        retrieve an attribute.
        returns NULL if the attribute is unknown
     */
    public String getAttribute(String name) {
        Object o = this.Attributes.get(name);
        if (o==null) return null;
        return (String)o;
    }

    /**
        returns all attributes' names
     */
    public String[] getAttributes() {
        String[] r = new String[this.Attributes.size()];
        Set s = this.Attributes.keySet();
        Iterator i = s.iterator();
        int l = 0;
        while (i.hasNext()) {
            String a = (String)i.next();
            r[l] = a;
            l++;
        }
        return r;
    }

    /**
        set this nodes Value
     */
    public void setValue(String v) {
        this.Value = v;
    }

    /**
        returns this nodes Value, NULL if it is unknown
     */
    public String getValue() {
        return this.Value;
    }

    /**
        returns whether the Value is either empty or contains nothing but whitespaces
     */
    public boolean emptyValue() {
        if (   this.getValue() == null 
            || this.getValue().equals("")
           ) return true;
        
        return false;
    }

    /**
        set this nodes Name
     */
    public void setName(String v) {
        this.Name = v;
    }

    /**
        returns this nodes Name, NULL if it is unknown
     */
    public String getName() {
        return this.Name;
    }

    /**
        set this nodes Parent
     */
    public void setParent(LMXPNode parent) {
        this.Parent = parent;
    }

    /**
        returns this nodes Parent, NULL if it is unknown
     */
    public LMXPNode getParent() {
        return this.Parent;
    }

    /**
        returns an XML representation of the subtree.
        takes an int as a paramenter which determines the indent columns
    */
    public String toXML(int id) {
        String xml = new String();
        String newVal = unescapeSpecialChars(Value);
        newVal = escapeSpecialChars(newVal);
        String pre = LMXPNode.getPre(id);
        LMXPNode[] children = this.getChildNodes();
        //If no child nodes are present and the Value is empty create a single tag
        if (children.length == 0) {
            if (this.emptyValue()) {
                xml = xml.concat(pre+"<"+this.getName()+this.getAttrString()+"/>\n");
            } else {
                xml = xml.concat(pre+"<"+this.getName()+">"+newVal+"</"+this.getName()+">\n");
            }
        } else { 
            xml = xml.concat(pre+"<"+this.getName()+this.getAttrString()+">\n");
            for (int i=0;i<children.length;i++) {
                xml = xml.concat(children[i].toXML(id+XML_INDENT));
            }
            if (!this.emptyValue()) {
                xml = xml.concat(pre+this.getValue());
            }
            xml = xml.concat(pre+"</"+this.getName()+">\n");
        }
        return xml;
    }

    /**
        creates the AttributeString
     */
    protected String getAttrString() {
        String r = new String();
        String[] attrs = this.getAttributes();
        for (int i=0;i<attrs.length;i++) {
            r = r.concat(" "+attrs[i]+"=\""+this.getAttribute(attrs[i])+"\"");
        }
        return r;
    }

    /**
        prints a crude view of the subtree
     */
    public void printTree(int id) {
        String pre = LMXPNode.getPre(id);
        System.out.println(pre+"+ "+this.getName());
        String[] attrs = this.getAttributes();
        for (int i=0;i<attrs.length;i++) {
            System.out.println(pre+"  -> "+attrs[i]+" = "+this.getAttribute(attrs[i]));
        }

        if (this.getValue() != null) {
            System.out.println(pre+" VALUE = "+this.getValue());
        }
        
        LMXPNode[] children = this.getChildNodes();
        for (int i=0;i<children.length;i++) {
            children[i].printTree(id+XML_INDENT);
        }
    }

    /**
        returns a String consisting of l whitespaces
     */
    protected static String getPre(int l) {
        String r = new String();
        for (int i=0;i<l;i++) {
            r = r.concat(" ");
        }
        return r;
    }

}
