/* -*-             c-basic-offset: 4; indent-tabs-mode: nil; -*-  //------100-columns-wide------>|*/

/******************************************************************************
 *
 * TibiXmlParser Version 1.1.0
 *
 * Copyright (c) 2004 NI-Lab.
 *
 * Author: NI-Lab.
 * Access: http://www.nilab.info/
 * Since : 2004-07-01
 *
 *****************************************************************************/

/*
 * The TibiXmlParser License
 *
 * Copyright (c) 2004 NI-Lab.
 *
 * 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.
 */

/*
 * ChangeLog
 *
 * 2004-07-07(Version 1.1.0)  NI-Lab.
 *
 *   * make use of XML Pull Parsing Common API Version 1_1_3_4a.
 *
 *   * make use of kXML 2 Version 2.1.9.
 *
 * 2004-07-07(Version 1.0.1)  NI-Lab.
 *
 *   * add comment.
 *
 * 2004-07-01(Version 1.0.0)  NI-Lab.
 *
 *   * Make use of XML Pull Parsing Common API Version 1_1_4.
 *     This Version 1_1_4 is missing at the present moment.
 *     The timestamp is 2003-07-08 16:27:10.
 *     Where have it been?
 *
 *   * Make use of kXML 2 Version 2.1.8.
 *
 */

/* from XML Pull Parsing Common API Version 1_1_3_4a 's LICENSE.txt */
/*
 * XMLPULL API IS FREE
 * -------------------
 * 
 * All of the XMLPULL API source code, compiled code, and documentation 
 * contained in this distribution *except* for tests (see separate LICENSE_TESTS.txt)
 * are in the Public Domain.
 * 
 * XMLPULL API comes with NO WARRANTY or guarantee of fitness for any purpose.
 * 
 * Initial authors:
 * 
 *   Stefan Haustein
 *   Aleksander Slominski
 * 
 * 2001-12-12
 */

/* from kXML 2 Version 2.1.9 's KXmlParser.java */
/* Copyright (c) 2002,2003, Stefan Haustein, Oberhausen, Rhld., Germany
 *
 * 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 foo.bar.hoge.hoge; // <- just as you like!

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

/**
 * The TibiXmlParser is a pull based XML parser by alone one class of Java.
 *
 * <p>
 * The TibiXmlParser is a small XML pull parser,
 * specially designed for constrained environments such as
 * </p>
 * <ul>
 * <li>Applets</li>
 * <li>J2ME Applications</li>
 * <li>J-Phone Java Applications</li>
 * <li>KDDI au EZ Applications</li>
 * <li>NTT Docomo iApplications</li>
 * <li>Vodafone V Applications</li>
 * <li>Personal Java</li>
 * <li>MIDP devices(PDA, Palm, etc...)</li>
 * </ul>
 *
 * <p>
 * The TibiXmlParser is based on two classes.
 * </p>
 * <ul>
 * <li>This product is based on the org.xmlpull.v1.XmlPullParser in <a href="http://xmlpull.org/">XML Pull Parsing Common API</a> Version 1_1_3_4a.</li>
 * <li>This product is based on the org.kxml2.io.KXmlParser in <a href="http://kxml.org/">kXML 2</a> Version 2.1.9.</li>
 * </ul>
 * <p>
 * A main change is a replacing org.xmlpull.v1.XmlPullParserException with java.lang.Exception.
 * </p>
 *
 * <p>
 * A minimal example for using this API may look as follows:
 * </p>
 * <pre>
 * public class SimpleXmlPullApp
 * {
 *
 *     public static void main (String args[])
 *         throws Exception, IOException
 *     {
 *         TibiXmlParser xpp = new TibiXmlParser();
 *
 *         xpp.<a href="#setInput">setInput</a>( new StringReader ( "&lt;foo>Hello World!&lt;/foo>" ) );
 *         int eventType = xpp.getEventType();
 *         while (eventType != xpp.END_DOCUMENT) {
 *          if(eventType == xpp.START_DOCUMENT) {
 *              System.out.println("Start document");
 *          } else if(eventType == xpp.END_DOCUMENT) {
 *              System.out.println("End document");
 *          } else if(eventType == xpp.START_TAG) {
 *              System.out.println("Start tag "+xpp.<a href="#getName()">getName()</a>);
 *          } else if(eventType == xpp.END_TAG) {
 *              System.out.println("End tag "+xpp.getName());
 *          } else if(eventType == xpp.TEXT) {
 *              System.out.println("Text "+xpp.<a href="#getText()">getText()</a>);
 *          }
 *          eventType = xpp.next();
 *         }
 *     }
 * }
 * </pre>
 *
 * <p>
 * The above example will generate the following output:
 * </p>
 * <pre>
 * Start document
 * Start tag foo
 * Text Hello World!
 * End tag foo
 * </pre>
 *
 * @see #defineEntityReplacementText
 * @see #getName
 * @see #getNamespace
 * @see #getText
 * @see #next
 * @see #nextToken
 * @see #setInput
 * @see #FEATURE_PROCESS_DOCDECL
 * @see #FEATURE_VALIDATION
 * @see #START_DOCUMENT
 * @see #START_TAG
 * @see #TEXT
 * @see #END_TAG
 * @see #END_DOCUMENT
 *
 * @author NI-Lab.
 * @version 1.1.0
 */
public class TibiXmlParser {

    /** This constant represents the default namespace (empty string "") */
    public static final String NO_NAMESPACE = "";

    // ----------------------------------------------------------------------------
    // EVENT TYPES as reported by next()

    /**
     * Signalize that parser is at the very beginning of the document
     * and nothing was read yet.
     * This event type can only be observed by calling getEvent()
     * before the first call to next(), nextToken, or nextTag()</a>).
     *
     * @see #next
     * @see #nextToken
     */
    public final static int START_DOCUMENT = 0;

    /**
     * Logical end of the xml document. Returned from getEventType, next()
     * and nextToken()
     * when the end of the input document has been reached.
     * <p><strong>NOTE:</strong> calling again
     * <a href="#next()">next()</a> or <a href="#nextToken()">nextToken()</a>
     * will result in exception being thrown.
     *
     * @see #next
     * @see #nextToken
     */
    public final static int END_DOCUMENT = 1;

    /**
     * Returned from getEventType(),
     * <a href="#next()">next()</a>, <a href="#nextToken()">nextToken()</a> when
     * a start tag was read.
     * The name of start tag is available from getName(), its namespace and prefix are
     * available from getNamespace() and getPrefix()
     * if <a href='#FEATURE_PROCESS_NAMESPACES'>namespaces are enabled</a>.
     * See getAttribute* methods to retrieve element attributes.
     * See getNamespace* methods to retrieve newly declared namespaces.
     *
     * @see #next
     * @see #nextToken
     * @see #getName
     * @see #getPrefix
     * @see #getNamespace
     * @see #getAttributeCount
     * @see #getDepth
     * @see #getNamespaceCount
     * @see #getNamespace
     * @see #FEATURE_PROCESS_NAMESPACES
     */
    public final static int START_TAG = 2;

    /**
     * Returned from getEventType(), <a href="#next()">next()</a>, or
     * <a href="#nextToken()">nextToken()</a> when an end tag was read.
     * The name of start tag is available from getName(), its
     * namespace and prefix are
     * available from getNamespace() and getPrefix().
     *
     * @see #next
     * @see #nextToken
     * @see #getName
     * @see #getPrefix
     * @see #getNamespace
     * @see #FEATURE_PROCESS_NAMESPACES
     */
    public final static int END_TAG = 3;


    /**
     * Character data was read and will is available by calling getText().
     * <p><strong>Please note:</strong> <a href="#next()">next()</a> will
     * accumulate multiple
     * events into one TEXT event, skipping IGNORABLE_WHITESPACE,
     * PROCESSING_INSTRUCTION and COMMENT events,
     * In contrast, <a href="#nextToken()">nextToken()</a> will stop reading
     * text when any other event is observed.
     * Also, when the state was reached by calling next(), the text value will
     * be normalized, whereas getText() will
     * return unnormalized content in the case of nextToken(). This allows
     * an exact roundtrip without chnanging line ends when examining low
     * level events, whereas for high level applications the text is
     * normalized apropriately.
     *
     * @see #next
     * @see #nextToken
     * @see #getText
     */
    public final static int TEXT = 4;

    // ----------------------------------------------------------------------------
    // additional events exposed by lower level nextToken()

    /**
     * A CDATA sections was just read;
     * this token is available only from calls to <a href="#nextToken()">nextToken()</a>.
     * A call to next() will accumulate various text events into a single event
     * of type TEXT. The text contained in the CDATA section is available
     * by callling getText().
     *
     * @see #nextToken
     * @see #getText
     */
    public final static int CDSECT = 5;

    /**
     * An entity reference was just read;
     * this token is available from <a href="#nextToken()">nextToken()</a>
     * only. The entity name is available by calling getName(). If available,
     * the replacement text can be obtained by calling getTextt(); otherwise,
     * the user is responsibile for resolving the entity reference.
     * This event type is never returned from next(); next() will
     * accumulate the replacement text and other text
     * events to a single TEXT event.
     *
     * @see #nextToken
     * @see #getText
     */
    public final static int ENTITY_REF = 6;

    /**
     * Ignorable whitespace was just read.
     * This token is available only from <a href="#nextToken()">nextToken()</a>).
     * For non-validating
     * parsers, this event is only reported by nextToken() when outside
     * the root element.
     * Validating parsers may be able to detect ignorable whitespace at
     * other locations.
     * The ignorable whitespace string is available by calling getText()
     *
     * <p><strong>NOTE:</strong> this is different from calling the
     *  isWhitespace() method, since text content
     *  may be whitespace but not ignorable.
     *
     * Ignorable whitespace is skipped by next() automatically; this event
     * type is never returned from next().
     *
     * @see #nextToken
     * @see #getText
     */
    public static final int IGNORABLE_WHITESPACE = 7;

    /**
     * An XML processing instruction declaration was just read. This
     * event type is available only via <a href="#nextToken()">nextToken()</a>.
     * getText() will return text that is inside the processing instruction.
     * Calls to next() will skip processing instructions automatically.
     * @see #nextToken
     * @see #getText
     */
    public static final int PROCESSING_INSTRUCTION = 8;

    /**
     * An XML comment was just read. This event type is this token is
     * available via <a href="#nextToken()">nextToken()</a> only;
     * calls to next() will skip comments automatically.
     * The content of the comment can be accessed using the getText()
     * method.
     *
     * @see #nextToken
     * @see #getText
     */
    public static final int COMMENT = 9;

    /**
     * An XML document type declaration was just read. This token is
     * available from <a href="#nextToken()">nextToken()</a> only.
     * The unparsed text inside the doctype is available via
     * the getText() method.
     *
     * @see #nextToken
     * @see #getText
     */
    public static final int DOCDECL = 10;

    /**
     * This array can be used to convert the event type integer constants
     * such as START_TAG or TEXT to
     * to a string. For example, the value of TYPES[START_TAG] is
     * the string "START_TAG".
     *
     * This array is intended for diagnostic output only. Relying
     * on the contents of the array may be dangerous since malicous
     * applications may alter the array, although it is final, due
     * to limitations of the Java language.
     */
    public static final String [] TYPES = {
        "START_DOCUMENT",
            "END_DOCUMENT",
            "START_TAG",
            "END_TAG",
            "TEXT",
            "CDSECT",
            "ENTITY_REF",
            "IGNORABLE_WHITESPACE",
            "PROCESSING_INSTRUCTION",
            "COMMENT",
            "DOCDECL"
    };


    // ----------------------------------------------------------------------------
    // namespace related features

    /**
     * This feature determines whether the parser processes
     * namespaces. As for all features, the default value is false.
     * <p><strong>NOTE:</strong> The value can not be changed during
     * parsing an must be set before parsing.
     *
     * @see #getFeature
     * @see #setFeature
     */
    public static final String FEATURE_PROCESS_NAMESPACES =
        "http://xmlpull.org/v1/doc/features.html#process-namespaces";

    /**
     * This feature determines whether namespace attributes are
     * exposed via the attribute access methods. Like all features,
     * the default value is false. This feature cannot be changed
     * during parsing.
     *
     * @see #getFeature
     * @see #setFeature
     */
    public static final String FEATURE_REPORT_NAMESPACE_ATTRIBUTES =
        "http://xmlpull.org/v1/doc/features.html#report-namespace-prefixes";

    /**
     * This feature determines whether the document declaration
     * is processed. If set to false,
     * the DOCDECL event type is reported by nextToken()
     * and ignored by next().
     *
     * If this featue is activated, then the document declaration
     * must be processed by the parser.
     *
     * <p><strong>Please note:</strong> If the document type declaration
     * was ignored, entity references may cause exceptions
     * later in the parsing process.
     * The default value of this feature is false. It cannot be changed
     * during parsing.
     *
     * @see #getFeature
     * @see #setFeature
     */
    public static final String FEATURE_PROCESS_DOCDECL =
        "http://xmlpull.org/v1/doc/features.html#process-docdecl";

    /**
     * If this feature is activated, all validation errors as
     * defined in the XML 1.0 sepcification are reported.
     * This implies that FEATURE_PROCESS_DOCDECL is true and both, the
     * internal and external document type declaration will be processed.
     * <p><strong>Please Note:</strong> This feature can not be changed
     * during parsing. The default value is false.
     *
     * @see #getFeature
     * @see #setFeature
     */
    public static final String FEATURE_VALIDATION =
        "http://xmlpull.org/v1/doc/features.html#validation";

    private Object location;
	static final private String UNEXPECTED_EOF = "Unexpected EOF";
    static final private String ILLEGAL_TYPE = "Wrong event type";
    static final private int LEGACY = 999;
    static final private int XML_DECL = 998;

    // general

    private String version;
    private Boolean standalone;

    private boolean processNsp;
    private boolean relaxed;
    private Hashtable entityMap;
    private int depth;
    private String[] elementStack = new String[16];
    private String[] nspStack = new String[8];
    private int[] nspCounts = new int[4];

    // source

    private Reader reader;
    private String encoding;
    private char[] srcBuf;

    private int srcPos;
    private int srcCount;

    private int line;
    private int column;

    // txtbuffer

    private char[] txtBuf = new char[128];
    private int txtPos;

    // Event-related

    private int type;
    //private String text;
    private boolean isWhitespace;
    private String namespace;
    private String prefix;
    private String name;

    private boolean degenerated;
    private int attributeCount;
    private String[] attributes = new String[16];
    private int stackMismatch = 0;
    private String error;

    /** 
     * A separate peek buffer seems simpler than managing
     * wrap around in the first level read buffer */

    private int[] peek = new int[2];
    private int peekCount;
    private boolean wasCR;

    private boolean unresolved;
    private boolean token;

    public TibiXmlParser() {
        srcBuf =
            new char[Runtime.getRuntime().freeMemory() >= 1048576 ? 8192 : 128];
    }

    private final boolean isProp(String n1, boolean prop, String n2) {
        if (!n1.startsWith("http://xmlpull.org/v1/doc/"))
            return false;
        if (prop)
            return n1.substring(42).equals(n2);
        else
            return n1.substring(40).equals(n2);
    }

    private final boolean adjustNsp() throws Exception {

        boolean any = false;

        for (int i = 0; i < attributeCount << 2; i += 4) {
            // * 4 - 4; i >= 0; i -= 4) {

            String attrName = attributes[i + 2];
            int cut = attrName.indexOf(':');
            String prefix;

            if (cut != -1) {
                prefix = attrName.substring(0, cut);
                attrName = attrName.substring(cut + 1);
            }
            else if (attrName.equals("xmlns")) {
                prefix = attrName;
                attrName = null;
            }
            else
                continue;

            if (!prefix.equals("xmlns")) {
                any = true;
            }
            else {
                int j = (nspCounts[depth]++) << 1;

                nspStack = ensureCapacity(nspStack, j + 2);
                nspStack[j] = attrName;
                nspStack[j + 1] = attributes[i + 3];

                if (attrName != null && attributes[i + 3].equals(""))
                    error("illegal empty namespace");

                //  prefixMap = new PrefixMap (prefixMap, attrName, attr.getValue ());

                //System.out.println (prefixMap);

                System.arraycopy(
                    attributes,
                    i + 4,
                    attributes,
                    i,
                    ((--attributeCount) << 2) - i);

                i -= 4;
            }
        }

        if (any) {
            for (int i = (attributeCount << 2) - 4; i >= 0; i -= 4) {

                String attrName = attributes[i + 2];
                int cut = attrName.indexOf(':');

                if (cut == 0 && !relaxed)
                    throw new RuntimeException(
                        "illegal attribute name: " + attrName + " at " + this);

                else if (cut != -1) {
                    String attrPrefix = attrName.substring(0, cut);

                    attrName = attrName.substring(cut + 1);

                    String attrNs = getNamespace(attrPrefix);

                    if (attrNs == null && !relaxed)
                        throw new RuntimeException(
                            "Undefined Prefix: " + attrPrefix + " in " + this);

                    attributes[i] = attrNs;
                    attributes[i + 1] = attrPrefix;
                    attributes[i + 2] = attrName;

                    /*
                                        if (!relaxed) {
                                            for (int j = (attributeCount << 2) - 4; j > i; j -= 4)
                                                if (attrName.equals(attributes[j + 2])
                                                    && attrNs.equals(attributes[j]))
                                                    exception(
                                                        "Duplicate Attribute: {"
                                                            + attrNs
                                                            + "}"
                                                            + attrName);
                                        }
                        */
                }
            }
        }

        int cut = name.indexOf(':');

        if (cut == 0)
            error("illegal tag name: " + name);

        if (cut != -1) {
            prefix = name.substring(0, cut);
            name = name.substring(cut + 1);
        }

        this.namespace = getNamespace(prefix);

        if (this.namespace == null) {
            if (prefix != null)
                error("undefined prefix: " + prefix);
            this.namespace = NO_NAMESPACE;
        }

        return any;
    }

    private final String[] ensureCapacity(String[] arr, int required) {
        if (arr.length >= required)
            return arr;
        String[] bigger = new String[required + 16];
        System.arraycopy(arr, 0, bigger, 0, arr.length);
        return bigger;
    }

    private final void error(String desc) throws Exception {
        if (relaxed) {
            if (error == null)
                error = "ERR: " + desc;
        }
        else
            exception(desc);
    }

    private final void exception(String desc) throws Exception {
        throw new Exception(
            desc.length() < 100 ? desc : desc.substring(0, 100) + "\n" + this);
    }

    /** 
     * common base for next and nextToken. Clears the state, except from 
     * txtPos and whitespace. Does not set the type variable */

    private final void nextImpl() throws IOException, Exception {

        if (reader == null)
            exception("No Input specified");

        if (type == END_TAG)
            depth--;

        while (true) {
            attributeCount = -1;

			// degenerated needs to be handled before error because of possible
			// processor expectations(!)

			if (degenerated) {
				degenerated = false;
				type = END_TAG;
				return;
			}


            if (error != null) {
                for (int i = 0; i < error.length(); i++)
                    push(error.charAt(i));
                //				text = error;
                error = null;
                type = COMMENT;
                return;
            }


            if (relaxed
                && (stackMismatch > 0 || (peek(0) == -1 && depth > 0))) {
                int sp = (depth - 1) << 2;
                type = END_TAG;
                namespace = elementStack[sp];
                prefix = elementStack[sp + 1];
                name = elementStack[sp + 2];
                if (stackMismatch != 1)
                    error = "missing end tag /" + name + " inserted";
                if (stackMismatch > 0)
                    stackMismatch--;
                return;
            }

            prefix = null;
            name = null;
            namespace = null;
            //            text = null;

            type = peekType();

            switch (type) {

                case ENTITY_REF :
                    pushEntity();
                    return;

                case START_TAG :
                    parseStartTag(false);
                    return;

                case END_TAG :
                    parseEndTag();
                    return;

                case END_DOCUMENT :
                    return;

                case TEXT :
                    pushText('<', !token);
                    if (depth == 0) {
                        if (isWhitespace)
                            type = IGNORABLE_WHITESPACE;
                        // make exception switchable for instances.chg... !!!!
                        //	else 
                        //    exception ("text '"+getText ()+"' not allowed outside root element");
                    }
                    return;

                default :
                    type = parseLegacy(token);
                    if (type != XML_DECL)
                        return;
            }
        }
    }

    private final int parseLegacy(boolean push)
        throws IOException, Exception {

        String req = "";
        int term;
        int result;
        int prev = 0;

        read(); // <
        int c = read();

        if (c == '?') {
            if ((peek(0) == 'x' || peek(0) == 'X')
                && (peek(1) == 'm' || peek(1) == 'M')) {

                if (push) {
                    push(peek(0));
                    push(peek(1));
                }
                read();
                read();

                if ((peek(0) == 'l' || peek(0) == 'L') && peek(1) <= ' ') {

                    if (line != 1 || column > 4)
                        error("PI must not start with xml");

                    parseStartTag(true);

                    if (attributeCount < 1 || !"version".equals(attributes[2]))
                        error("version expected");

                    version = attributes[3];

                    int pos = 1;

                    if (pos < attributeCount
                        && "encoding".equals(attributes[2 + 4])) {
                        encoding = attributes[3 + 4];
                        pos++;
                    }

                    if (pos < attributeCount
                        && "standalone".equals(attributes[4 * pos + 2])) {
                        String st = attributes[3 + 4 * pos];
                        if ("yes".equals(st))
                            standalone = new Boolean(true);
                        else if ("no".equals(st))
                            standalone = new Boolean(false);
                        else
                            error("illegal standalone value: " + st);
                        pos++;
                    }

                    if (pos != attributeCount)
                        error("illegal xmldecl");

                    isWhitespace = true;
                    txtPos = 0;

                    return XML_DECL;
                }
            }

            /*            int c0 = read ();
                        int c1 = read ();
                        int */

            term = '?';
            result = PROCESSING_INSTRUCTION;
        }
        else if (c == '!') {
            if (peek(0) == '-') {
                result = COMMENT;
                req = "--";
                term = '-';
            }
            else if (peek(0) == '[') {
                result = CDSECT;
                req = "[CDATA[";
                term = ']';
                push = true;
            }
            else {
                result = DOCDECL;
                req = "DOCTYPE";
                term = -1;
            }
        }
        else {
            error("illegal: <" + c);
            return COMMENT;
        }

        for (int i = 0; i < req.length(); i++)
            read(req.charAt(i));

        if (result == DOCDECL)
            parseDoctype(push);
        else {
            while (true) {
                c = read();
                if (c == -1){
                    error(UNEXPECTED_EOF);
                    return COMMENT;
                }

                if (push)
                    push(c);

                if ((term == '?' || c == term)
                    && peek(0) == term
                    && peek(1) == '>')
                    break;

                prev = c;
            }

            if (term == '-' && prev == '-')
                error("illegal comment delimiter: --->");

            read();
            read();

            if (push && term != '?')
                txtPos--;

        }
        return result;
    }

    /** precondition: &lt! consumed */

    private final void parseDoctype(boolean push)
        throws IOException, Exception {

        int nesting = 1;
        boolean quoted = false;

        // read();

        while (true) {
            int i = read();
            switch (i) {

                case -1 :
                    error(UNEXPECTED_EOF);
                    return;

                case '\'' :
                    quoted = !quoted;
                    break;

                case '<' :
                    if (!quoted)
                        nesting++;
                    break;

                case '>' :
                    if (!quoted) {
                        if ((--nesting) == 0)
                            return;
                    }
                    break;
            }
            if (push)
                push(i);
        }
    }

    /* precondition: &lt;/ consumed */

    private final void parseEndTag()
        throws IOException, Exception {

        read(); // '<'
        read(); // '/'
        name = readName();
        skip();
        read('>');

        int sp = (depth - 1) << 2;

        if (depth == 0) {
            error("element stack empty");
            type = COMMENT;
            return;
        }

        if (!name.equals(elementStack[sp + 3])) {
            error("expected: /" + elementStack[sp + 3] + " read: " + name);

			// become case insensitive in relaxed mode

            int probe = sp;
            while (probe >= 0 && !name.toLowerCase().equals(elementStack[probe + 3].toLowerCase())) {
                stackMismatch++;
                probe -= 4;
            }

            if (probe < 0) {
                stackMismatch = 0;
                //			text = "unexpected end tag ignored";
                type = COMMENT;
                return;
            }
        }

        namespace = elementStack[sp];
        prefix = elementStack[sp + 1];
        name = elementStack[sp + 2];
    }

    private final int peekType() throws IOException {
        switch (peek(0)) {
            case -1 :
                return END_DOCUMENT;
            case '&' :
                return ENTITY_REF;
            case '<' :
                switch (peek(1)) {
                    case '/' :
                        return END_TAG;
                    case '?' :
                    case '!' :
                        return LEGACY;
                    default :
                        return START_TAG;
                }
            default :
                return TEXT;
        }
    }

    private final String get(int pos) {
        return new String(txtBuf, pos, txtPos - pos);
    }

    /*
    private final String pop (int pos) {
    String result = new String (txtBuf, pos, txtPos - pos);
    txtPos = pos;
    return result;
    }
    */

    private final void push(int c) {

        isWhitespace &= c <= ' ';

        if (txtPos == txtBuf.length) {
            char[] bigger = new char[txtPos * 4 / 3 + 4];
            System.arraycopy(txtBuf, 0, bigger, 0, txtPos);
            txtBuf = bigger;
        }

        txtBuf[txtPos++] = (char) c;
    }

    /** Sets name and attributes */

    private final void parseStartTag(boolean xmldecl)
        throws IOException, Exception {

        if (!xmldecl)
            read();
        name = readName();
        attributeCount = 0;

        while (true) {
            skip();

            int c = peek(0);

            if (xmldecl) {
                if (c == '?') {
                    read();
                    read('>');
                    return;
                }
            }
            else {
                if (c == '/') {
                    degenerated = true;
                    read();
                    skip();
                    read('>');
                    break;
                }

                if (c == '>' && !xmldecl) {
                    read();
                    break;
                }
            }

            if (c == -1) {
                error(UNEXPECTED_EOF);
                //type = COMMENT;
                return;
            }

            String attrName = readName();

            if (attrName.length() == 0) {
                error("attr name expected");
               //type = COMMENT;
                break;
            }

            int i = (attributeCount++) << 2;

            attributes = ensureCapacity(attributes, i + 4);

            attributes[i++] = "";
            attributes[i++] = null;
            attributes[i++] = attrName;

            skip();

            if (peek(0) != '=') {
				error("Attr.value missing f. "+attrName);
                attributes[i] = "1";
            }
            else {
                read('=');
                skip();
                int delimiter = peek(0);

                if (delimiter != '\'' && delimiter != '"') {
                    error("attr value delimiter missing!");
                    delimiter = ' ';
                }
				else 
					read();
				
                int p = txtPos;
                pushText(delimiter, true);

                attributes[i] = get(p);
                txtPos = p;

                if (delimiter != ' ')
                    read(); // skip endquote
            }
        }

        int sp = depth++ << 2;

        elementStack = ensureCapacity(elementStack, sp + 4);
        elementStack[sp + 3] = name;

        if (depth >= nspCounts.length) {
            int[] bigger = new int[depth + 4];
            System.arraycopy(nspCounts, 0, bigger, 0, nspCounts.length);
            nspCounts = bigger;
        }

        nspCounts[depth] = nspCounts[depth - 1];

        /*
        		if(!relaxed){
                for (int i = attributeCount - 1; i > 0; i--) {
                    for (int j = 0; j < i; j++) {
                        if (getAttributeName(i).equals(getAttributeName(j)))
                            exception("Duplicate Attribute: " + getAttributeName(i));
                    }
                }
        		}
        */
        if (processNsp)
            adjustNsp();
        else
            namespace = "";

        elementStack[sp] = namespace;
        elementStack[sp + 1] = prefix;
        elementStack[sp + 2] = name;
    }

    /** result: isWhitespace; if the setName parameter is set,
    the name of the entity is stored in "name" */

    private final void pushEntity()
        throws IOException, Exception {

        read(); // &

        int pos = txtPos;

        while (true) {
            int c = read();
            if (c == ';')
                break;
            if (c < 128
                && (c < '0' || c > '9')
                && (c < 'a' || c > 'z')
                && (c < 'A' || c > 'Z')
                && c != '_'
                && c != '-'
                && c != '#') {
                error("unterminated entity ref");
                //; ends with:"+(char)c);           
                if (c != -1)
                    push(c);
                return;
            }

            push(c);
        }

        String code = get(pos);
        txtPos = pos;
        if (token && type == ENTITY_REF)
            name = code;

        if (code.charAt(0) == '#') {
            int c =
                (code.charAt(1) == 'x'
                    ? Integer.parseInt(code.substring(2), 16)
                    : Integer.parseInt(code.substring(1)));
            push(c);
            return;
        }

        String result = (String) entityMap.get(code);

        unresolved = result == null;

        if (unresolved) {
            if (!token)
                error("unresolved: &" + code + ";");
        }
        else {
            for (int i = 0; i < result.length(); i++)
                push(result.charAt(i));
        }
    }

    /** types:
    '<': parse to any token (for nextToken ())
    '"': parse to quote
    ' ': parse to whitespace or '>'
    */

    private final void pushText(int delimiter, boolean resolveEntities)
        throws IOException, Exception {

        int next = peek(0);
        int cbrCount = 0;

        while (next != -1 && next != delimiter) { // covers eof, '<', '"'

            if (delimiter == ' ')
                if (next <= ' ' || next == '>')
                    break;

            if (next == '&') {
                if (!resolveEntities)
                    break;

                pushEntity();
            }
            else if (next == '\n' && type == START_TAG) {
                read();
                push(' ');
            }
            else
                push(read());

            if (next == '>' && cbrCount >= 2 && delimiter != ']')
                error("Illegal: ]]>");

            if (next == ']')
                cbrCount++;
            else
                cbrCount = 0;

            next = peek(0);
        }
    }

    private final void read(char c)
        throws IOException, Exception {
        int a = read();
        if (a != c)
            error("expected: '" + c + "' actual: '" + ((char) a) + "'");
    }

    private final int read() throws IOException {
        int result;

        if (peekCount == 0)
            result = peek(0);
        else {
            result = peek[0];
            peek[0] = peek[1];
        }
        //		else {
        //			result = peek[0]; 
        //			System.arraycopy (peek, 1, peek, 0, peekCount-1);
        //		}
        peekCount--;

        column++;

        if (result == '\n') {

            line++;
            column = 1;
        }

        return result;
    }

    /** Does never read more than needed */

    private final int peek(int pos) throws IOException {

        while (pos >= peekCount) {

            int nw;

            if (srcBuf.length <= 1)
                nw = reader.read();
            else if (srcPos < srcCount)
                nw = srcBuf[srcPos++];
            else {
                srcCount = reader.read(srcBuf, 0, srcBuf.length);
                if (srcCount <= 0)
                    nw = -1;
                else
                    nw = srcBuf[0];

                srcPos = 1;
            }

            if (nw == '\r') {
                wasCR = true;
                peek[peekCount++] = '\n';
            }
            else {
                if (nw == '\n') {
                    if (!wasCR)
                        peek[peekCount++] = '\n';
                }
                else
                    peek[peekCount++] = nw;

                wasCR = false;
            }
        }

        return peek[pos];
    }

    private final String readName()
        throws IOException, Exception {

        int pos = txtPos;
        int c = peek(0);
        if ((c < 'a' || c > 'z')
            && (c < 'A' || c > 'Z')
            && c != '_'
            && c != ':'
            && c < 0x0c0
            && !relaxed)
            error("name expected");

        do {
            push(read());
            c = peek(0);
        }
        while ((c >= 'a' && c <= 'z')
            || (c >= 'A' && c <= 'Z')
            || (c >= '0' && c <= '9')
            || c == '_'
            || c == '-'
            || c == ':'
            || c == '.'
            || c >= 0x0b7);

        String result = get(pos);
        txtPos = pos;
        return result;
    }

    private final void skip() throws IOException {

        while (true) {
            int c = peek(0);
            if (c > ' ' || c == -1)
                break;
            read();
        }
    }

    //--------------- public part starts here... ---------------

    /**
     * Set the input source for parser to the given reader and
     * resets the parser. The event type is set to the initial value
     * START_DOCUMENT.
     * Setting the reader to null will just stop parsing and
     * reset parser state,
     * allowing the parser to free internal resources
     * such as parsing buffers.
     */
    public void setInput(Reader reader) throws Exception {
        this.reader = reader;

        line = 1;
        column = 0;
        type = START_DOCUMENT;
        name = null;
        namespace = null;
        degenerated = false;
        attributeCount = -1;
        encoding = null;
        version = null;
        standalone = null;

        if (reader == null)
            return;

        srcPos = 0;
        srcCount = 0;
        peekCount = 0;
        depth = 0;

        entityMap = new Hashtable();
        entityMap.put("amp", "&");
        entityMap.put("apos", "'");
        entityMap.put("gt", ">");
        entityMap.put("lt", "<");
        entityMap.put("quot", "\"");
    }

    /**
     * Sets the input stream the parser is going to process.
     * This call resets the parser state and sets the event type
     * to the initial value START_DOCUMENT.
     *
     * <p><strong>NOTE:</strong> If an input encoding string is passed,
     *  it MUST be used. Otherwise,
     *  if inputEncoding is null, the parser SHOULD try to determine
     *  input encoding following XML 1.0 specification (see below).
     *  If encoding detection is supported then following feature
     *  <a href="http://xmlpull.org/v1/doc/features.html#detect-encoding">http://xmlpull.org/v1/doc/features.html#detect-encoding</a>
     *  MUST be true amd otherwise it must be false
     *
     * @param is contains a raw byte input stream of possibly
     *     unknown encoding (when inputEncoding is null).
     *
     * @param _enc if not null it MUST be used as encoding for inputStream
     */
    public void setInput(InputStream is, String _enc)
        throws Exception {

        srcPos = 0;
        srcCount = 0;
        String enc = _enc;

        if (is == null)
            throw new IllegalArgumentException();

        try {

            if (enc == null) {
                // read four bytes 

                int chk = 0;

                while (srcCount < 4) {
                    int i = is.read();
                    if (i == -1)
                        break;
                    chk = (chk << 8) | i;
                    srcBuf[srcCount++] = (char) i;
                }

                if (srcCount == 4) {
                    switch (chk) {
                        case 0x00000FEFF :
                            enc = "UTF-32BE";
                            srcCount = 0;
                            break;

                        case 0x0FFFE0000 :
                            enc = "UTF-32LE";
                            srcCount = 0;
                            break;

                        case 0x03c :
                            enc = "UTF-32BE";
                            srcBuf[0] = '<';
                            srcCount = 1;
                            break;

                        case 0x03c000000 :
                            enc = "UTF-32LE";
                            srcBuf[0] = '<';
                            srcCount = 1;
                            break;

                        case 0x0003c003f :
                            enc = "UTF-16BE";
                            srcBuf[0] = '<';
                            srcBuf[1] = '?';
                            srcCount = 2;
                            break;

                        case 0x03c003f00 :
                            enc = "UTF-16LE";
                            srcBuf[0] = '<';
                            srcBuf[1] = '?';
                            srcCount = 2;
                            break;

                        case 0x03c3f786d :
                            while (true) {
                                int i = is.read();
                                if (i == -1)
                                    break;
                                srcBuf[srcCount++] = (char) i;
                                if (i == '>') {
                                    String s = new String(srcBuf, 0, srcCount);
                                    int i0 = s.indexOf("encoding");
                                    if (i0 != -1) {
                                        while (s.charAt(i0) != '"'
                                            && s.charAt(i0) != '\'')
                                            i0++;
                                        char deli = s.charAt(i0++);
                                        int i1 = s.indexOf(deli, i0);
                                        enc = s.substring(i0, i1);
                                    }
                                    break;
                                }
                            }

                        default :
                            if ((chk & 0x0ffff0000) == 0x0FEFF0000) {
                                enc = "UTF-16BE";
                                srcBuf[0] =
                                    (char) ((srcBuf[2] << 8) | srcBuf[3]);
                                srcCount = 1;
                            }
                            else if ((chk & 0x0ffff0000) == 0x0fffe0000) {
                                enc = "UTF-16LE";
                                srcBuf[0] =
                                    (char) ((srcBuf[3] << 8) | srcBuf[2]);
                                srcCount = 1;
                            }
                            else if ((chk & 0x0ffffff00) == 0x0EFBBBF) {
                                enc = "UTF-8";
                                srcBuf[0] = srcBuf[3];
                                srcCount = 1;
                            }
                    }
                }
            }

            if (enc == null)
                enc = "UTF-8";

            int sc = srcCount;
            setInput(new InputStreamReader(is, enc));
            encoding = _enc;
            srcCount = sc;
        }
        catch (Exception e) {
            throw new Exception(
                "Invalid stream or encoding: " + e.toString() + ": " + this + ": " + e.toString());
        }
    }

    /**
     * Returns the current value of the given feature.
     * <p><strong>Please note:</strong> unknown features are
     * <strong>always</strong> returned as false.
     *
     * @param feature The name of feature to be retrieved.
     * @return The value of the feature.
     * @exception IllegalArgumentException if string the feature name is null
     */
    public boolean getFeature(String feature) {
        if (FEATURE_PROCESS_NAMESPACES.equals(feature))
            return processNsp;
        else if (isProp(feature, false, "relaxed"))
            return relaxed;
        else
            return false;
    }

    /**
     * Returns the input encoding if known, null otherwise.
     * If setInput(InputStream, inputEncoding) was called with an inputEncoding
     * value other than null, this value must be returned
     * from this method. Otherwise, if inputEncoding is null and
     * the parser suppports the encoding detection feature
     * (http://xmlpull.org/v1/doc/features.html#detect-encoding),
     * it must return the detected encoding.
     * If setInput(Reader) was called, null is returned.
     * After first call to next if XML declaration was present this method
     * will return encoding declared.
     */
    public String getInputEncoding() {
        return encoding;
    }

    /**
     * Set new value for entity replacement text as defined in
     * <a href="http://www.w3.org/TR/REC-xml#intern-replacement">XML 1.0 Section 4.5
     * Construction of Internal Entity Replacement Text</a>.
     * If FEATURE_PROCESS_DOCDECL or FEATURE_VALIDATION are set, calling this
     * function will result in an exception -- when processing of DOCDECL is
     * enabled, there is no need to the entity replacement text manually.
     *
     * <p>The motivation for this function is to allow very small
     * implementations of XMLPULL that will work in J2ME environments.
     * Though these implementations may not be able to process the document type
     * declaration, they still can work with known DTDs by using this function.
     *
     * <p><b>Please notes:</b> The given value is used literally as replacement text
     * and it corresponds to declaring entity in DTD that has all special characters
     * escaped: left angle bracket is replaced with &amp;lt;, ampersnad with &amp;amp;
     * and so on.
     *
     * <p><b>Note:</b> The given value is the literal replacement text and must not
     * contain any other entity reference (if it contains any entity reference
     * there will be no further replacement).
     *
     * <p><b>Note:</b> The list of pre-defined entity names will
     * always contain standard XML entities such as
     * amp (&amp;amp;), lt (&amp;lt;), gt (&amp;gt;), quot (&amp;quot;), and apos (&amp;apos;).
     * Those cannot be redefined by this method!
     *
     * @see #setInput
     * @see #FEATURE_PROCESS_DOCDECL
     * @see #FEATURE_VALIDATION
     */
    public void defineEntityReplacementText(String entity, String value)
        throws Exception {
        if (entityMap == null)
            throw new RuntimeException("entity replacement text must be defined after setInput!");
        entityMap.put(entity, value);
    }

    /**
     * Look up the value of a property.
     *
     * The property name is any fully-qualified URI.
     * <p><strong>NOTE:</strong> unknown properties are <strong>always</strong>
     * returned as null.
     *
     * @param property The name of property to be retrieved.
     * @return The value of named property.
     */
    public Object getProperty(String property) {
        if (isProp(property, true, "xmldecl-version"))
            return version;
        if (isProp(property, true, "xmldecl-standalone"))
            return standalone;
		if (isProp(property, true, "location"))            
			return location != null ? location : reader.toString();
        return null;
    }

    /**
     * Returns the numbers of elements in the namespace stack for the given
     * depth.
     * If namespaces are not enabled, 0 is returned.
     *
     * <p><b>NOTE:</b> when parser is on END_TAG then it is allowed to call
     *  this function with getDepth()+1 argument to retrieve position of namespace
     *  prefixes and URIs that were declared on corresponding START_TAG.
     * <p><b>NOTE:</b> to retrieve lsit of namespaces declared in current element:<pre>
     *       TibiXmlParser pp = ...
     *       int nsStart = pp.getNamespaceCount(pp.getDepth()-1);
     *       int nsEnd = pp.getNamespaceCount(pp.getDepth());
     *       for (int i = nsStart; i < nsEnd; i++) {
     *          String prefix = pp.getNamespacePrefix(i);
     *          String ns = pp.getNamespaceUri(i);
     *           // ...
     *      }
     * </pre>
     *
     * @see #getNamespacePrefix
     * @see #getNamespaceUri
     * @see #getNamespace()
     * @see #getNamespace(String)
     */
    public int getNamespaceCount(int depth) {
        if (depth > this.depth)
            throw new IndexOutOfBoundsException();
        return nspCounts[depth];
    }

    /**
     * Returns the namespace prefixe for the given position
     * in the namespace stack.
     * Default namespace declaration (xmlns='...') will have null as prefix.
     * If the given index is out of range, an exception is thrown.
     * <p><b>Please note:</b> when the parser is on an END_TAG,
     * namespace prefixes that were declared
     * in the corresponding START_TAG are still accessible
     * although they are no longer in scope.
     */
    public String getNamespacePrefix(int pos) {
        return nspStack[pos << 1];
    }

    /**
     * Returns the namespace URI for the given position in the
     * namespace stack
     * If the position is out of range, an exception is thrown.
     * <p><b>NOTE:</b> when parser is on END_TAG then namespace prefixes that were declared
     *  in corresponding START_TAG are still accessible even though they are not in scope
     */
    public String getNamespaceUri(int pos) {
        return nspStack[(pos << 1) + 1];
    }

    /**
     * Returns the URI corresponding to the given prefix,
     * depending on current state of the parser.
     *
     * <p>If the prefix was not declared in the current scope,
     * null is returned. The default namespace is included
     * in the namespace table and is available via
     * getNamespace (null).
     *
     * <p>This method is a convenience method for
     *
     * <pre>
     *  for (int i = getNamespaceCount(getDepth ())-1; i >= 0; i--) {
     *   if (getNamespacePrefix(i).equals( prefix )) {
     *     return getNamespaceUri(i);
     *   }
     *  }
     *  return null;
     * </pre>
     *
     * <p><strong>Please note:</strong> parser implementations
     * may provide more efifcient lookup, e.g. using a Hashtable.
     * The 'xml' prefix is bound to "http://www.w3.org/XML/1998/namespace", as
     * defined in the
     * <a href="http://www.w3.org/TR/REC-xml-names/#ns-using">Namespaces in XML</a>
     * specification. Analogous, the 'xmlns' prefix is resolved to
     * <a href="http://www.w3.org/2000/xmlns/">http://www.w3.org/2000/xmlns/</a>
     *
     * @see #getNamespaceCount
     * @see #getNamespacePrefix
     * @see #getNamespaceUri
     */
    public String getNamespace(String prefix) {

        if ("xml".equals(prefix))
            return "http://www.w3.org/XML/1998/namespace";
        if ("xmlns".equals(prefix))
            return "http://www.w3.org/2000/xmlns/";

        for (int i = (getNamespaceCount(depth) << 1) - 2; i >= 0; i -= 2) {
            if (prefix == null) {
                if (nspStack[i] == null)
                    return nspStack[i + 1];
            }
            else if (prefix.equals(nspStack[i]))
                return nspStack[i + 1];
        }
        return null;
    }

    /**
     * Returns the current depth of the element.
     * Outside the root element, the depth is 0. The
     * depth is incremented by 1 when a start tag is reached.
     * The depth is decremented AFTER the end tag
     * event was observed.
     *
     * <pre>
     * &lt;!-- outside --&gt;     0
     * &lt;root>                  1
     *   sometext                 1
     *     &lt;foobar&gt;         2
     *     &lt;/foobar&gt;        2
     * &lt;/root&gt;              1
     * &lt;!-- outside --&gt;     0
     * </pre>
     */
    public int getDepth() {
        return depth;
    }

    /**
     * Returns a short text describing the current parser state, including
     * the position, a
     * description of the current event and the data source if known.
     * This method is especially useful to provide meaningful
     * error messages and for debugging purposes.
     */
    public String getPositionDescription() {

        StringBuffer buf =
            new StringBuffer(type < TYPES.length ? TYPES[type] : "unknown");
        buf.append(' ');

        if (type == START_TAG || type == END_TAG) {
            if (degenerated)
                buf.append("(empty) ");
            buf.append('<');
            if (type == END_TAG)
                buf.append('/');

            if (prefix != null)
                buf.append("{" + namespace + "}" + prefix + ":");
            buf.append(name);

            int cnt = attributeCount << 2;
            for (int i = 0; i < cnt; i += 4) {
                buf.append(' ');
                if (attributes[i + 1] != null)
                    buf.append(
                        "{" + attributes[i] + "}" + attributes[i + 1] + ":");
                buf.append(attributes[i + 2] + "='" + attributes[i + 3] + "'");
            }

            buf.append('>');
        }
        else if (type == IGNORABLE_WHITESPACE);
        else if (type != TEXT)
            buf.append(getText());
        else if (isWhitespace)
            buf.append("(whitespace)");
        else {
            String text = getText();
            if (text.length() > 16)
                text = text.substring(0, 16) + "...";
            buf.append(text);
        }

		buf.append("@"+line + ":" + column);
		buf.append(" in ");
       	buf.append(location == null ? reader.toString() : location);
        return buf.toString();
    }

    /**
     * Returns the current line number, starting from 1.
     * When the parser does not know the current line number
     * or can not determine it,  -1 is returned (e.g. for WBXML).
     *
     * @return current line number or -1 if unknown.
     */
    public int getLineNumber() {
        return line;
    }

    /**
     * Returns the current column number, starting from 0.
     * When the parser does not know the current column number
     * or can not determine it,  -1 is returned (e.g. for WBXML).
     *
     * @return current column number or -1 if unknown.
     */
    public int getColumnNumber() {
        return column;
    }

    /**
     * Checks whether the current TEXT event contains only whitespace
     * characters.
     * For IGNORABLE_WHITESPACE, this is always true.
     * For TEXT and CDSECT, false is returned when the current event text
     * contains at least one non-white space character. For any other
     * event type an exception is thrown.
     *
     * <p><b>Please note:</b> non-validating parsers are not
     * able to distinguish whitespace and ignorable whitespace,
     * except from whitespace outside the root element. Ignorable
     * whitespace is reported as separate event, which is exposed
     * via nextToken only.
     *
     */
    public boolean isWhitespace() throws Exception {
        if (type != TEXT && type != IGNORABLE_WHITESPACE && type != CDSECT)
            exception(ILLEGAL_TYPE);
        return isWhitespace;
    }

    /**
     * Returns the text content of the current event as String.
     * The value returned depends on current event type,
     * for example for TEXT event it is element content
     * (this is typical case when next() is used).
     *
     * See description of nextToken() for detailed description of
     * possible returned values for different types of events.
     *
     * <p><strong>NOTE:</strong> in case of ENTITY_REF, this method returns
     * the entity replacement text (or null if not available). This is
     * the only case where
     * getText() and getTextCharacters() return different values.
     *
     * @see #getEventType
     * @see #next
     * @see #nextToken
     */
    public String getText() {
        return type < TEXT
            || (type == ENTITY_REF && unresolved) ? null : get(0);
    }

    /**
     * Returns the buffer that contains the text of the current event,
     * as well as the start offset and length relevant for the current
     * event. See getText(), next() and nextToken() for description of possible returned values.
     *
     * <p><strong>Please note:</strong> this buffer must not
     * be modified and its content MAY change after a call to
     * next() or nextToken(). This method will always return the
     * same value as getText(), except for ENTITY_REF. In the case
     * of ENTITY ref, getText() returns the replacement text and
     * this method returns the actual input buffer containing the
     * entity name.
     * If getText() returns null, this method returns null as well and
     * the values returned in the holder array MUST be -1 (both start
     * and length).
     *
     * @see #getText
     * @see #next
     * @see #nextToken
     *
     * @param poslen Must hold an 2-element int array
     * into which the start offset and length values will be written.
     * @return char buffer that contains the text of the current event
     *  (null if the current event has no text associated).
     */
    public char[] getTextCharacters(int[] poslen) {
        if (type >= TEXT) {
            if (type == ENTITY_REF) {
                poslen[0] = 0;
                poslen[1] = name.length();
                return name.toCharArray();
            }
            poslen[0] = 0;
            poslen[1] = txtPos;
            return txtBuf;
        }

        poslen[0] = -1;
        poslen[1] = -1;
        return null;
    }

    /**
     * Returns the namespace URI of the current element.
     * The default namespace is represented
     * as empty string.
     * If namespaces are not enabled, an empty String ("") is always returned.
     * The current event must be START_TAG or END_TAG; otherwise,
     * null is returned.
     */
    public String getNamespace() {
        return namespace;
    }

    /**
     * For START_TAG or END_TAG events, the (local) name of the current
     * element is returned when namespaces are enabled. When namespace
     * processing is disabled, the raw name is returned.
     * For ENTITY_REF events, the entity name is returned.
     * If the current event is not START_TAG, END_TAG, or ENTITY_REF,
     * null is returned.
     * <p><b>Please note:</b> To reconstruct the raw element name
     *  when namespaces are enabled and the prefix is not null,
     * you will need to  add the prefix and a colon to localName..
     *
     */
    public String getName() {
        return name;
    }

    /**
     * Returns the prefix of the current element.
     * If the element is in the default namespace (has no prefix),
     * null is returned.
     * If namespaces are not enabled, or the current event
     * is not  START_TAG or END_TAG, null is returned.
     */
    public String getPrefix() {
        return prefix;
    }

    /**
     * Returns true if the current event is START_TAG and the tag
     * is degenerated
     * (e.g. &lt;foobar/&gt;).
     * <p><b>NOTE:</b> if the parser is not on START_TAG, an exception
     * will be thrown.
     */
    public boolean isEmptyElementTag() throws Exception {
        if (type != START_TAG)
            exception(ILLEGAL_TYPE);
        return degenerated;
    }

    /**
     * Returns the number of attributes of the current start tag, or
     * -1 if the current event type is not START_TAG
     *
     * @see #getAttributeNamespace
     * @see #getAttributeName
     * @see #getAttributePrefix
     * @see #getAttributeValue
     */
    public int getAttributeCount() {
        return attributeCount;
    }

    /**
     * Returns the type of the specified attribute
     * If parser is non-validating it MUST return CDATA.
     *
     * @param index zero based index of attribute
     * @return attribute type (null is never returned)
     */
    public String getAttributeType(int index) {
        return "CDATA";
    }

    /**
     * Returns if the specified attribute was not in input was declared in XML.
     * If parser is non-validating it MUST always return false.
     * This information is part of XML infoset:
     *
     * @param index zero based index of attribute
     * @return false if attribute was in input
     */
    public boolean isAttributeDefault(int index) {
        return false;
    }

    /**
     * Returns the namespace URI of the attribute
     * with the given index (starts from 0).
     * Returns an empty string ("") if namespaces are not enabled
     * or the attribute has no namespace.
     * Throws an IndexOutOfBoundsException if the index is out of range
     * or the current event type is not START_TAG.
     *
     * <p><strong>NOTE:</strong> if FEATURE_REPORT_NAMESPACE_ATTRIBUTES is set
     * then namespace attributes (xmlns:ns='...') must be reported
     * with namespace
     * <a href="http://www.w3.org/2000/xmlns/">http://www.w3.org/2000/xmlns/</a>
     * (visit this URL for description!).
     * The default namespace attribute (xmlns="...") will be reported with empty namespace.
     * <p><strong>NOTE:</strong>The xml prefix is bound as defined in
     * <a href="http://www.w3.org/TR/REC-xml-names/#ns-using">Namespaces in XML</a>
     * specification to "http://www.w3.org/XML/1998/namespace".
     *
     * @param index zero based index of attribute
     * @return attribute namespace,
     *   empty string ("") is returned  if namesapces processing is not enabled or
     *   namespaces processing is enabled but attribute has no namespace (it has no prefix).
     */
    public String getAttributeNamespace(int index) {
        if (index >= attributeCount)
            throw new IndexOutOfBoundsException();
        return attributes[index << 2];
    }

    /**
     * Returns the local name of the specified attribute
     * if namespaces are enabled or just attribute name if namespaces are disabled.
     * Throws an IndexOutOfBoundsException if the index is out of range
     * or current event type is not START_TAG.
     *
     * @param index zero based index of attribute
     * @return attribute name (null is never returned)
     */
    public String getAttributeName(int index) {
        if (index >= attributeCount)
            throw new IndexOutOfBoundsException();
        return attributes[(index << 2) + 2];
    }

    /**
     * Returns the prefix of the specified attribute
     * Returns null if the element has no prefix.
     * If namespaces are disabled it will always return null.
     * Throws an IndexOutOfBoundsException if the index is out of range
     * or current event type is not START_TAG.
     *
     * @param index zero based index of attribute
     * @return attribute prefix or null if namespaces processing is not enabled.
     */
    public String getAttributePrefix(int index) {
        if (index >= attributeCount)
            throw new IndexOutOfBoundsException();
        return attributes[(index << 2) + 1];
    }

    /**
     * Returns the given attributes value.
     * Throws an IndexOutOfBoundsException if the index is out of range
     * or current event type is not START_TAG.
     *
     * <p><strong>NOTE:</strong> attribute value must be normalized
     * (including entity replacement text if PROCESS_DOCDECL is false) as described in
     * <a href="http://www.w3.org/TR/REC-xml#AVNormalize">XML 1.0 section
     * 3.3.3 Attribute-Value Normalization</a>
     *
     * @see #defineEntityReplacementText
     *
     * @param index zero based index of attribute
     * @return value of attribute (null is never returned)
     */
    public String getAttributeValue(int index) {
        if (index >= attributeCount)
            throw new IndexOutOfBoundsException();
        return attributes[(index << 2) + 3];
    }

    /**
     * Returns the attributes value identified by namespace URI and namespace localName.
     * If namespaces are disabled namespace must be null.
     * If current event type is not START_TAG then IndexOutOfBoundsException will be thrown.
     *
     * <p><strong>NOTE:</strong> attribute value must be normalized
     * (including entity replacement text if PROCESS_DOCDECL is false) as described in
     * <a href="http://www.w3.org/TR/REC-xml#AVNormalize">XML 1.0 section
     * 3.3.3 Attribute-Value Normalization</a>
     *
     * @see #defineEntityReplacementText
     *
     * @param namespace Namespace of the attribute if namespaces are enabled otherwise must be null
     * @param name If namespaces enabled local name of attribute otherwise just attribute name
     * @return value of attribute or null if attribute with given name does not exist
     */
    public String getAttributeValue(String namespace, String name) {

        for (int i = (attributeCount << 2) - 4; i >= 0; i -= 4) {
            if (attributes[i + 2].equals(name)
                && (namespace == null || attributes[i].equals(namespace)))
                return attributes[i + 3];
        }

        return null;
    }

    /**
     * Returns the type of the current event (START_TAG, END_TAG, TEXT, etc.)
     *
     * @see #next()
     * @see #nextToken()
     */
    public int getEventType() throws Exception {
        return type;
    }

    /**
     * Get next parsing event - element content wil be coalesced and only one
     * TEXT event must be returned for whole element content
     * (comments and processing instructions will be ignored and emtity references
     * must be expanded or exception mus be thrown if entity reerence can not be exapnded).
     * If element content is empty (content is "") then no TEXT event will be reported.
     *
     * <p><b>NOTE:</b> empty element (such as &lt;tag/>) will be reported
     *  with  two separate events: START_TAG, END_TAG - it must be so to preserve
     *   parsing equivalency of empty element to &lt;tag>&lt;/tag>.
     *  (see isEmptyElementTag ())
     *
     * @see #isEmptyElementTag
     * @see #START_TAG
     * @see #TEXT
     * @see #END_TAG
     * @see #END_DOCUMENT
     */
    public int next() throws Exception, IOException {

        txtPos = 0;
        isWhitespace = true;
        int minType = 9999;
        token = false;

        do {
            nextImpl();
            if (type < minType)
                minType = type;
            //	    if (curr <= TEXT) type = curr; 
        }
        while (minType > ENTITY_REF // ignorable
            || (minType >= TEXT && peekType() >= TEXT));

        type = minType;
        if (type > TEXT)
            type = TEXT;

        return type;
    }

    /**
     * This method works similarly to next() but will expose
     * additional event types (COMMENT, CDSECT, DOCDECL, ENTITY_REF, PROCESSING_INSTRUCTION, or
     * IGNORABLE_WHITESPACE) if they are available in input.
     *
     * <p>If special feature
     * <a href="http://xmlpull.org/v1/doc/features.html#xml-roundtrip">FEATURE_XML_ROUNDTRIP</a>
     * (identified by URI: http://xmlpull.org/v1/doc/features.html#xml-roundtrip)
     * is enabled it is possible to do XML document round trip ie. reproduce
     * exectly on output the XML input using getText():
     * returned content is always unnormalized (exactly as in input).
     * Otherwise returned content is end-of-line normalized as described
     * <a href="http://www.w3.org/TR/REC-xml#sec-line-ends">XML 1.0 End-of-Line Handling</a>
     * and. Also when this feature is enabled exact content of START_TAG, END_TAG,
     * DOCDECL and PROCESSING_INSTRUCTION is available.
     *
     * <p>Here is the list of tokens that can be  returned from nextToken()
     * and what getText() and getTextCharacters() returns:<dl>
     * <dt>START_DOCUMENT<dd>null
     * <dt>END_DOCUMENT<dd>null
     * <dt>START_TAG<dd>null unless FEATURE_XML_ROUNDTRIP
     *   enabled and then returns XML tag, ex: &lt;tag attr='val'>
     * <dt>END_TAG<dd>null unless FEATURE_XML_ROUNDTRIP
     *  id enabled and then returns XML tag, ex: &lt;/tag>
     * <dt>TEXT<dd>return element content.
     *  <br>Note: that element content may be delivered in multiple consecutive TEXT events.
     * <dt>IGNORABLE_WHITESPACE<dd>return characters that are determined to be ignorable white
     * space. If the FEATURE_XML_ROUNDTRIP is enabled all whitespace content outside root
     * element will always reported as IGNORABLE_WHITESPACE otherise rteporting is optional.
     *  <br>Note: that element content may be delevered in multiple consecutive IGNORABLE_WHITESPACE events.
     * <dt>CDSECT<dd>
     * return text <em>inside</em> CDATA
     *  (ex. 'fo&lt;o' from &lt;!CDATA[fo&lt;o]]>)
     * <dt>PROCESSING_INSTRUCTION<dd>
     *  if FEATURE_XML_ROUNDTRIP is true
     *  return exact PI content ex: 'pi foo' from &lt;?pi foo?>
     *  otherwise it may be exact PI content or concatenation of PI target,
     * space and data so for example for
     *   &lt;?target    data?> string &quot;target data&quot; may
     *       be returned if FEATURE_XML_ROUNDTRIP is false.
     * <dt>COMMENT<dd>return comment content ex. 'foo bar' from &lt;!--foo bar-->
     * <dt>ENTITY_REF<dd>getText() MUST return entity replacement text if PROCESS_DOCDECL is false
     * otherwise getText() MAY return null,
     * additionally getTextCharacters() MUST return entity name
     * (for example 'entity_name' for &amp;entity_name;).
     * <br><b>NOTE:</b> this is the only place where value returned from getText() and
     *   getTextCharacters() <b>are different</b>
     * <br><b>NOTE:</b> it is user responsibility to resolve entity reference
     *    if PROCESS_DOCDECL is false and there is no entity replacement text set in
     *    defineEntityReplacementText() method (getText() will be null)
     * <br><b>NOTE:</b> character entities (ex. &amp;#32;) and standard entities such as
     *  &amp;amp; &amp;lt; &amp;gt; &amp;quot; &amp;apos; are reported as well
     *  and are <b>not</b> reported as TEXT tokens but as ENTITY_REF tokens!
     *  This requirement is added to allow to do roundtrip of XML documents!
     * <dt>DOCDECL<dd>
     * if FEATURE_XML_ROUNDTRIP is true or PROCESS_DOCDECL is false
     * then return what is inside of DOCDECL for example it returns:<pre>
     * &quot; titlepage SYSTEM "http://www.foo.bar/dtds/typo.dtd"
     * [&lt;!ENTITY % active.links "INCLUDE">]&quot;</pre>
     * <p>for input document that contained:<pre>
     * &lt;!DOCTYPE titlepage SYSTEM "http://www.foo.bar/dtds/typo.dtd"
     * [&lt;!ENTITY % active.links "INCLUDE">]></pre>
     * otherwise if FEATURE_XML_ROUNDTRIP is false and PROCESS_DOCDECL is true
     *    then what is returned is undefined (it may be even null)
     * </dd>
     * </dl>
     *
     * <p><strong>NOTE:</strong> there is no gurantee that there will only one TEXT or
     * IGNORABLE_WHITESPACE event from nextToken() as parser may chose to deliver element content in
     * multiple tokens (dividing element content into chunks)
     *
     * <p><strong>NOTE:</strong> whether returned text of token is end-of-line normalized
     *  is depending on FEATURE_XML_ROUNDTRIP.
     *
     * <p><strong>NOTE:</strong> XMLDecl (&lt;?xml ...?&gt;) is not reported but its content
     * is available through optional properties (see class description above).
     *
     * @see #next
     * @see #START_TAG
     * @see #TEXT
     * @see #END_TAG
     * @see #END_DOCUMENT
     * @see #COMMENT
     * @see #DOCDECL
     * @see #PROCESSING_INSTRUCTION
     * @see #ENTITY_REF
     * @see #IGNORABLE_WHITESPACE
     */
    public int nextToken() throws Exception, IOException {

        isWhitespace = true;
        txtPos = 0;

        token = true;
        nextImpl();
        return type;
    }

    //----------------------------------------------------------------------
    // utility methods to make XML parsing easier ...

    /**
     * Call next() and return event if it is START_TAG or END_TAG
     * otherwise throw an exception.
     * It will skip whitespace TEXT before actual tag if any.
     *
     * <p>essentially it does this
     * <pre>
     *   int eventType = next();
     *   if(eventType == TEXT &amp;&amp;  isWhitespace()) {   // skip whitespace
     *      eventType = next();
     *   }
     *   if (eventType != START_TAG &amp;&amp;  eventType != END_TAG) {
     *      throw new Exception("expected start or end tag", this, null);
     *   }
     *   return eventType;
     * </pre>
     */
    public int nextTag() throws Exception, IOException {

        next();
        if (type == TEXT && isWhitespace)
            next();

        if (type != END_TAG && type != START_TAG)
            exception("unexpected type");

        return type;
    }

    /**
     * Test if the current event is of the given type and if the
     * namespace and name do match. null will match any namespace
     * and any name. If the test is not passed, an exception is
     * thrown. The exception text indicates the parser position,
     * the expected event and the current event that is not meeting the
     * requirement.
     *
     * <p>Essentially it does this
     * <pre>
     *  if (type != getEventType()
     *  || (namespace != null &amp;&amp;  !namespace.equals( getNamespace () ) )
     *  || (name != null &amp;&amp;  !name.equals( getName() ) ) )
     *     throw new Exception( "expected "+ TYPES[ type ]+getPositionDescription());
     * </pre>
     */
    public void require(int type, String namespace, String name)
        throws Exception, IOException {

        if (type != this.type
            || (namespace != null && !namespace.equals(getNamespace()))
            || (name != null && !name.equals(getName())))
            exception(
                "expected: " + TYPES[type] + " {" + namespace + "}" + name);
    }

    /**
     * If current event is START_TAG then if next element is TEXT then element content is returned
     * or if next event is END_TAG then empty string is returned, otherwise exception is thrown.
     * After calling this function successfully parser will be positioned on END_TAG.
     *
     * <p>The motivation for this function is to allow to parse consistently both
     * empty elements and elements that has non empty content, for example for input: <ol>
     * <li>&lt;tag&gt;foo&lt;/tag&gt;
     * <li>&lt;tag&gt;&lt;/tag&gt; (which is equivalent to &lt;tag/&gt;
     * both input can be parsed with the same code:
     * <pre>
     *   p.nextTag()
     *   p.requireEvent(p.START_TAG, "", "tag");
     *   String content = p.nextText();
     *   p.requireEvent(p.END_TAG, "", "tag");
     * </pre>
     * This function together with nextTag make it very easy to parse XML that has
     * no mixed content.
     *
     *
     * <p>Essentially it does this
     * <pre>
     *  if(getEventType() != START_TAG) {
     *     throw new Exception(
     *       "parser must be on START_TAG to read next text", this, null);
     *  }
     *  int eventType = next();
     *  if(eventType == TEXT) {
     *     String result = getText();
     *     eventType = next();
     *     if(eventType != END_TAG) {
     *       throw new Exception(
     *          "event TEXT it must be immediately followed by END_TAG", this, null);
     *      }
     *      return result;
     *  } else if(eventType == END_TAG) {
     *     return "";
     *  } else {
     *     throw new Exception(
     *       "parser must be on START_TAG or TEXT to read text", this, null);
     *  }
     * </pre>
     */
    public String nextText() throws Exception, IOException {
        if (type != START_TAG)
            exception("precondition: START_TAG");

        next();

        String result;

        if (type == TEXT) {
            result = getText();
            next();
        }
        else
            result = "";

        if (type != END_TAG)
            exception("END_TAG expected");

        return result;
    }

    /**
     * Use this call to change the general behaviour of the parser,
     * such as namespace processing or doctype declaration handling.
     * This method must be called before the first call to next or
     * nextToken. Otherwise, an exception is thrown.
     * <p>Example: call setFeature(FEATURE_PROCESS_NAMESPACES, true) in order
     * to switch on namespace processing. The initial settings correspond
     * to the properties requested from the XML Pull Parser factory.
     * If none were requested, all feautures are deactivated by default.
     *
     * @exception Exception If the feature is not supported or can not be set
     * @exception IllegalArgumentException If string with the feature name is null
     */
    public void setFeature(String feature, boolean value)
        throws Exception {
        if (FEATURE_PROCESS_NAMESPACES.equals(feature))
            processNsp = value;
        else if (isProp(feature, false, "relaxed"))
            relaxed = value;
        else
            exception("unsupported feature: " + feature);
    }

    /**
     * Set the value of a property.
     *
     * The property name is any fully-qualified URI.
     *
     * @exception Exception If the property is not supported or can not be set
     * @exception IllegalArgumentException If string with the property name is null
     */
    public void setProperty(String property, Object value)
        throws Exception {
        if(isProp(property, true, "location"))
        	location = value;
        else
	        throw new Exception("unsupported property: " + property);
    }

    /**
      * Skip sub tree that is currently porser positioned on.
      * <br>NOTE: parser must be on START_TAG and when funtion returns
      * parser will be positioned on corresponding END_TAG. 
      */

    //	Implementation copied from Alek's mail... 

    public void skipSubTree() throws Exception, IOException {
        require(START_TAG, null, null);
        int level = 1;
        while (level > 0) {
            int eventType = next();
            if (eventType == END_TAG) {
                --level;
            }
            else if (eventType == START_TAG) {
                ++level;
            }
        }
    }
}
