    
    
/*
    
    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 the parser.
    LMXP is a lame, quick and dirty XMLParser written in pure java. It is in no way
    compatible to any XML specs, but it is fast and it is mine :-)
    this class' parse() method takes a String and tries to parse it into a tree of
    Nodes.<br>
    <br>
    much of this code has been stolen from <br>
        http://www.javaworld.com/javaworld/javatips/jw-javatip128.html<br>
    <br>
    thanks Steven! <br>

    @see LMXPNode
 */
public class LMXParser {

    private static          int                         PRE = 1,
                                                        TAG_START = 2,
                                                        VERSION = 3,
                                                        DOCTYPE_START = 4,
                                                        TAG = 5,
                                                        END_TAG = 6,
                                                        SINGLE_TAG = 7,
                                                        ATTRIBUTE_START = 8,
                                                        ATTRIBUTE = 9,
                                                        ATTRIBUTE_VALUE = 10 ,
                                                        EXCLAMATIONMARK = 11,
                                                        DOCTYPE_VALUE = 12,
                                                        CDATA_START = 13,
                                                        CDATA_VALUE = 14,
                                                        CDATA_END = 15
                                                        
                                                        ;


    private LMXParser() {
    }

    private static int pop(Stack st) {
        if(!st.empty()) {
            int r = ((Integer)st.pop()).intValue();
            return r;
        } else
            return PRE;
    }

    private static void push(Stack st, int i) {
        st.push(new Integer(i));
    }

    /** parses the given XML String and returns a tree consisting
        of LMXPNodes
     */
    public static LMXPDocument parseString(String xml) throws Exception {
        return LMXParser.parse(new java.io.StringReader(xml));
    }

    /** parses the given File and returns a tree consisting
        of LMXPNodes
     */
    public static LMXPDocument parseFile(String filename) throws Exception {
        return LMXParser.parse(new java.io.FileReader(filename));
    }

    /**
      parses the stream by byte and returns a tree consisting of LMXPNodes
     */
    public static LMXPDocument parse(java.io.Reader r) throws Exception {
        LMXPDocument root = new LMXPDocument();

        Stack st = new Stack();
        LMXPNode currentNode = root;
        int c = 0;
        int line = 1;
        int col = 0;
        int mode = PRE;
        StringBuffer sb = new StringBuffer();
        //Attributes
        StringBuffer at = new StringBuffer();
        String attribname = new String();
        //Value
        StringBuffer val = new StringBuffer();

        while ((c = r.read()) != -1) {

            //First of all: check for newline, update line and col
            boolean eol = false;
            if(c == '\n' && eol) {
                eol = false;
                if (mode != CDATA_VALUE && mode != PRE) {
                    //continue;
                }
            } else if(eol) {
                eol = false;
            } else if(c == '\n') {
                line++;
                col=0;
                if (mode != CDATA_VALUE && mode != PRE) {
                    //continue;
                }
            } else if(c == '\r') {
                eol = true;
                c = '\n';
                line++;
                col=0;
                if (mode != CDATA_VALUE && mode != PRE) {
                    //continue;
                }
            } else {
                col++;
            }

        //    System.out.println("--> "+(char)c+"    mode = "+mode+ " val : "+val.toString());
        
            //We have not yet discovered XML or are in between tags
            if (mode == PRE) {
                if (c == '<') {
                    if (currentNode != null) {
                        if (val.length() > 0) {
                            currentNode.setValue(val.toString());
                        }
                        val = new StringBuffer();
                    }
                    push(st,mode);
                    mode = TAG_START;
                    LMXPNode n = new LMXPNode();
                    currentNode.addChild(n);
                    currentNode = n;
                } else {
                    val.append((char)c);
                }
                continue;
            }
            //Ok, we found a tag, we now try to determine if 
            //it is a special tag like <? or <!DOCTYPE> or <![CDATA[
            else if (mode == TAG_START) {
                if (c == '?') {
                    currentNode.detach();
                    push(st,mode);
                    mode = VERSION;
                    currentNode = root.getVersion();
                } else if (c == '!') {
                    currentNode.detach();
                    push(st,mode);
                    mode = EXCLAMATIONMARK;
                } else if (c == '/') {
                    mode = END_TAG;
                } else {
                    push(st,mode);
                    mode = TAG;
                    sb.append((char)c);
                } 
                continue;
            }
            //we hav found an exclamation mark
            else if (mode == EXCLAMATIONMARK) {
                if (c == 'D') {
                    mode = DOCTYPE_START;
                    currentNode = root.getDocType(); 
                } else if (c == '[') {
                    mode = CDATA_START;
                }
                continue;
            }
            //we are inside the version String: <?xml version ... ?>
            else if (mode == VERSION) {
                //ignore that, just find the next >
                if (c == '>') {
                    sb = new StringBuffer();
                    pop(st);
                    mode = pop(st);
                    currentNode = root;
                } else if (c == ' ') {
                    push(st,mode);
                    mode = ATTRIBUTE_START;
                }
                continue;
            }
            //we are inside the doctype declaration: <!DOCTYPE
            else if (mode == DOCTYPE_START) {
                //we ignore that also, let's just finde the next >
                if (c == ' ') {
                    mode = DOCTYPE_VALUE;
                }
                continue;
            } else if (mode == DOCTYPE_VALUE) {
                if (c == '>') {
                    root.getDocType().setType(sb.toString());
                    sb = new StringBuffer();
                    pop(st);
                    mode = pop(st);
                    currentNode = root;
                } else {
                    sb.append((char)c);
                }
                continue;
            }
            //we found the start of a CDATA declaration
            else if (mode == CDATA_START) {
                if (c == '[') {
                    mode = CDATA_VALUE;
                }
                continue;
            }
            //we are inside the CDATA data section
            else if (mode == CDATA_VALUE) {
                if (c == ']') {
                    int c2;
                    c2 = r.read();
                    if (c2 == ']') {
                        mode = CDATA_END;
                    } else {
                        sb.append((char)c2);
                    }
                } else {
                    sb.append((char)c);
                }
                continue;
            }
            //we might just have reached the end of a CDATA section
            else if (mode == CDATA_END) {
                if (c == '>') {
                    LMXPDataNode dnode = new LMXPDataNode(currentNode);
                    dnode.setValue(sb.toString());
                    currentNode = currentNode.detach();
                    currentNode.addChild(dnode);
                    sb = new StringBuffer();
                    pop(st);
                    mode = pop(st);
                } else {
                    mode = CDATA_VALUE;
                    sb.append((char)c);
                }
                continue;
            }
            //we inside a tag declaration: <TAGNAME>
            else if (mode == TAG) {
                if (c == '>') {
                    currentNode.setName(sb.toString());
                    sb = new StringBuffer();
                    pop(st);
                    mode = pop(st);
                } else if (c == '/') {
                    mode = SINGLE_TAG;
                } else if (c == ' ') {
                    push(st,mode);
                    mode = ATTRIBUTE_START;
                } else {
                    sb.append((char)c);
                }
                continue;
            }
            //we found a self closing tag <.../>
            else if (mode == SINGLE_TAG) {
                if (c == '>') {
                    currentNode.setName(sb.toString());
                    currentNode = currentNode.closeNode();
                    sb = new StringBuffer();
                    pop(st);
                    mode = pop(st);
                }
                continue;
            }
            //we are closing the node
            else if (mode == END_TAG) {
                if (c == '>') {
                    if (currentNode != null) {
                        LMXPNode tmp = currentNode;
                        currentNode = currentNode.getParent();
                        currentNode.delChild(tmp);
                        currentNode = currentNode.closeNode();
                    } else {
                    }
                    mode = pop(st);
                    sb = new StringBuffer();
                }
                continue;
            }
            //we possibly encountered an attribute
            else if (mode == ATTRIBUTE_START) {
                if (c == ' ') {
                    //ignore
                } else {
                    at = new StringBuffer();
                    at.append((char)c);
                    mode = ATTRIBUTE;
                }
                continue;
            }
            //we found an attribute
            else if (mode == ATTRIBUTE) {
                if (c == ' ') {
                    //ignore
                } else if (c == '=') {
                    attribname = at.toString();
                    at = new StringBuffer();
                } else if (c == '"') {
                    mode = ATTRIBUTE_VALUE;
                } else {
                    at.append((char)c);
                }
            }
            //we are trying to determine the attribute's value
            else if (mode == ATTRIBUTE_VALUE) {
                if (c == '"') {
                    currentNode.addAttribute(attribname,at.toString());
                    mode = pop(st);
                } else {
                    at.append((char)c);
                }
            }
        
        } // end while

        return root; 
    }
   
    private static void logline(int line, int col, String msg) {
        System.out.println("LOG ==> "+msg+" <== @ "+line+", "+col);
    }
   
}
