/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is JIM.
 *
 * The Initial Developer of the Original Code is
 * Pawel Chmielowski <prefiks@o2.pl>
 *
 * Portions created by the Initial Developer are Copyright (C) 2003
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * Pawel Chmielowski <prefiks@o2.pl>
 *
 * ***** END LICENSE BLOCK ***** */

/**
 * xml.XMLParser
 *
 *
 */
ML.load("xml/xmlutils.js");
ML.load("utils/observable.js");

const JIM_ERROR_XMLPARSER_CLOSING_UNOPENED_TAG                  = 0x70000100;
const JIM_ERROR_XMLPARSER_INVALID_ATTRIBUTE_DEFINITION          = 0x70000101;
const JIM_ERROR_XMLPARSER_INVALID_ATTRIBUTE_VALUE_DEFINITION    = 0x70000102;
const JIM_ERROR_XMLPARSER_INVALID_CDATA_DECLARATION             = 0x70000103;
const JIM_ERROR_XMLPARSER_INVALID_COMMENT_DECLARATION           = 0x70000104;
const JIM_ERROR_XMLPARSER_INVALID_DOCTYPE_DECLARATION           = 0x70000105;
const JIM_ERROR_XMLPARSER_INVALID_FORMAT                        = 0x70000106;
const JIM_ERROR_XMLPARSER_INVALID_TAG_DECLARATION               = 0x70000107;
const JIM_ERROR_XMLPARSER_INVALID_TAG_ENDING                    = 0x70000108;
const JIM_ERROR_XMLPARSER_INVALID_TAG_NAME                      = 0x70000109;
const JIM_ERROR_XMLPARSER_MASK                                  = 0x7000010F;

function XMLParser() {
    this.tags = [];
    this.base = Observable;
    this.base();
    this.converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].getService(
        Components.interfaces.nsIScriptableUnicodeConverter);
}

XMLParser.prototype = {
    __proto__: Observable.prototype,
    state: 0,
    data: "",
    cdata: "",
    charset: null,
    line: 1,
    column: 1,
    
    reset: function()
    {
        this.state = 0;
        this.data = "";
        this.cdata = "";
        this.charset = null;
        this.tags = [];
        this.line = 1;
        this.column = 1;
    },
    
    exception: function(errorCode)
    {
        throw { code: errorCode, line: this.line, column: this.column};
    },

    convertToUnicode: function(data, beg)
    {
        if (this.charset) {
            this.converter.charset = this.charset;
            if (beg == 0)
                return this.converter.ConvertToUnicode(data);
            else
                return this.converter.ConvertToUnicode(data.substring(beg));
        }
        return data;
    },

    convertToUnicodeSpec: function(data)
    {
        var i, c, state;

        state = 0;
        for (i = 0; i < data.length; i++) {
            c = data.charCodeAt(i);

            switch (state) {
            case 0:
                switch (c) {
                case 254:
                    state = 1;
                    break;
                case 255:
                    state = 2;
                    break;
                case 0:
                    state = 3;
                    break;
                case 60:
                    state = 4;
                    break;
                case 239:
                    state = 5;
                    break;
                default:
                    charset = "UTF-8";
                    i--;
                    state = 11;
                    break;
                }
                break;
            case 1:
                if (c != 255)
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_FORMAT);
                charset = "UTF-16BE";
                state = 11;
                break;

            case 2:
                if (c != 254)
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_FORMAT);
                charset = "UTF-16LE";
                state = 11;
                break;

            case 3:
                if (c == 0)
                    state = 6;
                else if (c == 60) {
                    charset = "UTF-16BE";
                    i-=2;
                    state = 11;
                } else
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_FORMAT);
                break;

            case 4:
                if (c == 0)
                    state = 7;
                else {
                    charset = "UTF-8";
                    i-=2;
                    state = 11;
                }
                break;

            case 5:
                if (c != 187)
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_FORMAT);
                state = 8;
                break;

            case 6:
                if (c != 0)
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_FORMAT);
                state = 9;
                break;

            case 7:
                if (c == 0)
                    state = 10;
                else {
                    charset = "UTF-16LE";
                    i-=3;
                    state = 11;
                }
                break;

            case 8:
                if (c != 191)
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_FORMAT);
                charset = "UTF-8";
                state = 11;
                break;

            case 9:
                if (c != 60)
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_FORMAT);
                charset = "UTF-32BE";
                i-=4;
                state = 11;
                break;

            case 10:
                if (c != 0)
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_FORMAT);
                charset = "UTF-32LE";
                i-=4;
                state = 11;
                break;
            case 11:
                this.converter.charset = charset;
                if (i == 0)
                    return this.converter.ConvertToUnicode(data);
                else
                    return this.converter.ConvertToUnicode(data.substring(i));
            }
        }

    },

    parse: function (data)
    {
        var i;
        var len;
        var c, ch;

        data = this.convertToUnicode(data, 0);
        len = data.length;

        for (i = 0; i < len; i++) {
            ch = data.charAt(i);
            c = data.charCodeAt(i);

            if (c == 13) {
                this.line++;
                this.column = 1;
            } else
                this.column++;

            switch (this.state) {
            case 0:
                switch (c) {
                case 254:
                    this.state = 1;
                    break;
                case 255:
                    this.state = 2;
                    break;
                case 0:
                    this.state = 3;
                    break;
                case 60:
                    this.state = 4;
                    break;
                case 239:
                    this.state = 5;
                    break;
                default:
                    this.column = 1;
                    this.line = 1;
                    this.charset = "UTF-8";
                    data = this.convertToUnicode(data, --i);
                    i = 0;
                    len = data.length;
                    this.state = 11;
                    break;
                }
                break;
            case 1:
                if (c != 255)
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_FORMAT);
                this.charset = "UTF-16BE";
                this.column = 1;
                this.line = 1;
                data = this.convertToUnicode(data, i);
                i = 0;
                len = data.length;
                this.state = 11;
                break;

            case 2:
                if (c != 254)
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_FORMAT);
                this.charset = "UTF-16LE";
                this.column = 1;
                this.line = 1;
                data = this.convertToUnicode(data, i);
                i = 0;
                len = data.length;
                this.state = 11;
                break;

            case 3:
                if (c == 0)
                    this.state = 6;
                else if (c == 60) {
                    this.charset = "UTF-16BE";
                    this.column = 2;
                    this.line = 1;
                    data = this.convertToUnicode(data, i);
                    i = 0;
                    len = data.length;
                    this.state = 12;
                } else
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_FORMAT);
                break;

            case 4:
                if (c == 0)
                    this.state = 7;
                else {
                    this.charset = "UTF-8";
                    this.column = 2;
                    this.line = 1;
                    data = this.convertToUnicode(data, --i);
                    i = 0;
                    len = data.length;
                    this.state = 12;
                }
                break;

            case 5:
                if (c != 187)
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_FORMAT);
                this.state = 8;
                break;

            case 6:
                if (c != 0)
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_FORMAT);
                this.state = 9;
                break;

            case 7:
                if (c == 0)
                    this.state = 10;
                else {
                    this.charset = "UTF-16LE";
                    this.column = 2;
                    this.line = 1;
                    data = this.convertToUnicode(data, --i);
                    i = 0;
                    len = data.length;
                    this.state = 12;
                }
                break;

            case 8:
                if (c != 191)
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_FORMAT);
                this.charset = "UTF-8";
                this.column = 1;
                this.line = 1;
                data = this.convertToUnicode(data, i+1);
                i = -1;
                len = data.length;
                this.state = 11;
                break;

            case 9:
                if (c != 60)
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_FORMAT);
                this.charset = "UTF-32BE";
                this.column = 2;
                this.line = 1;
                data = this.convertToUnicode(data, i);
                i = 0;
                len = data.length;
                this.state = 12;
                break;

            case 10:
                if (c != 0)
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_FORMAT);
                this.charset = "UTF-32LE";
                this.column = 2;
                this.line = 1;
                data = this.convertToUnicode(data, i);
                i = 0;
                len = data.length;
                this.state = 12;
                break;

            case 11:
                switch (c) {
                case 32:
                case 9:
                case 13:
                case 10:
                    break;
                case 60:
                    this.fireEvent("onStartDocument");
                    this.state = 12;
                    break;
                default:
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_FORMAT);
                }
                break;
            case 12:
                switch (c) {
                case 32:
                case 9:
                case 13:
                case 10:
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_TAG_NAME);
                case 63:
                    this.state = 13;
                    break;
                case 33:
                    this.state = 14;
                    break;
                case 47:
                    this.exception(JIM_ERROR_XMLPARSER_CLOSING_UNOPENED_TAG);
                default:
                    this.attributes = { };
                    this.tagName = ch;
                    this.state = 15;
                    break;
                }
                break;
            case 13:
                if (c == 63)
                    this.state = 16;
                break;
            case 14:
                if (c == 45)
                    this.state = 17;
                else
                    this.state == 18;
                break;
            case 15:
                switch (c) {
                case 32:
                case 9:
                case 13:
                case 10:
                    this.state = 19;
                    break;
                case 47:
                    this.state = 21;
                    break;
                case 62:
                    this.fireEvent("onStartTag", this.tagName, this.attributes);
                    this.tags.push(this.tagName);
                    this.state = 29;
                    break;
                default:
                    this.tagName += ch;
                    break;
                }
                break;
            case 16:
                if (c == 62)
                    this.state = 11;
                else
                    this.state = 13;
                break;
            case 17:
                if (c == 45)
                    this.state = 20;
                else
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_COMMENT_DECLARATION);
                break;
            case 18:
                if (c == 62)
                    this.state = 11;
                break;
            case 19:
                switch (c) {
                case 32:
                case 9:
                case 13:
                case 10:
                    break;
                case 47:
                    this.state = 21;
                    break;
                case 62:
                    this.fireEvent("onStartTag", this.tagName, this.attributes);
                    this.tags.push(this.tagName);
                    this.state = 29;
                    break;
                default:
                    this.attrName = ch;
                    this.state = 22;
                    break;
                }
                break;
            case 20:
                if (c == 45)
                    this.state = 23;
                break;

            case 21:
                if (c != 62)
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_TAG_ENDING);
                this.fireEvent("onStartTag", this.tagName, this.attributes);
                this.fireEvent("onEndTag", this.tagName);
                this.fireEvent("onEndDocument", this.tagName);
                this.state = 11;
                break;
            case 22:
                switch (c) {
                case 32:
                case 9:
                case 13:
                case 10:
                    this.state = 24;
                    break;
                case 61:
                    this.state = 25;
                    break;
                default:
                    this.attrName += ch;
                    break;
                }
                break;
            case 23:
                if (c == 45)
                    this.state = 26;
                else
                    this.state = 20;
                break;
            case 24:
                switch (c) {
                case 32:
                case 9:
                case 13:
                case 10:
                    break;
                case 61:
                    this.state = 25;
                    break;
                default:
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_ATTRIBUTE_DEFINITION);
                }
                break;
            case 25:
                switch (c) {
                case 32:
                case 9:
                case 13:
                case 10:
                    break;
                case 39:
                    this.state = 27;
                    this.attrValue = "";
                    break;
                case 34:
                    this.state = 28;
                    this.attrValue = "";
                    break;
                default:
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_ATTRIBUTE_VALUE_DEFINITION);
                }
                break;
            case 26:
                if (c == 62)
                    this.state = 11;
                else
                    this.state = 20;
                break;
            case 27:
                switch (c) {
                case 39:
                    this.attributes[this.attrName] = decodeEntity(this.attrValue);
                    this.state = 19;
                    break;
                case 60:
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_ATTRIBUTE_VALUE_DEFINITION);
                default:
                    this.attrValue += ch;
                    break;
                }
                break;
            case 28:
                switch (c) {
                case 34:
                    this.attributes[this.attrName] = decodeEntity(this.attrValue);
                    this.state = 19;
                    break;
                case 60:
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_ATTRIBUTE_VALUE_DEFINITION);
                default:
                    this.attrValue += ch;
                    break;
                }
                break;
            case 29:
                switch (c) {
                case 60:
                    if (this.data.length > 0) {
                        this.fireEvent("onData", decodeEntity(this.data));
                        this.data = "";
                    }
                    this.state = 30;
                    break;
                default:
                    this.data += ch;
                }
                break;
            case 30:
                switch (c) {
                case 32:
                case 9:
                case 13:
                case 10:
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_TAG_NAME);
                case 63:
                    this.state = 31;
                    break;
                case 33:
                    this.state = 32;
                    break;
                case 47:
                    this.state = 33;
                    break;
                default:
                    this.attributes = { };
                    this.tagName = ch;
                    this.state = 34;
                    break;
                }
                break;
            case 31:
                if (c == 63)
                    this.state = 35;
                break;
            case 32:
                switch (c) {
                case 32:
                case 9:
                case 13:
                case 10:
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_DOCTYPE_DECLARATION);
                case 45:
                    this.state = 36;
                    break;
                case 91:
                    this.state = 37;
                    break;
                default:
                    this.tagName = ch;
                    this.state = 38;
                    break;
                }
                break;
            case 33:
                switch (c) {
                case 32:
                case 9:
                case 13:
                case 10:
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_TAG_NAME);
                default:
                    this.tagName = ch;
                    this.state = 39;
                    break;
                }
                break;
            case 34:
                switch (c) {
                case 32:
                case 9:
                case 13:
                case 10:
                    this.state = 40;
                    break;
                case 47:
                    this.state = 41;
                    break;
                case 62:
                    this.fireEvent("onStartTag", this.tagName, this.attributes);
                    this.tags.push(this.tagName);
                    this.state = 29;
                    break;
                default:
                    this.tagName += ch;
                    break;
                }
                break;
            case 35:
                if (c == 62)
                    this.state = 29;
                else
                    this.state = 31;
                break;
            case 36:
                if (c == 45)
                    this.state = 42;
                else
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_COMMENT_DECLARATION);
                break;
            case 37:
                if (c != 67)
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_CDATA_DECLARATION);
                this.state = 43;
                break;
            case 38:
                if (c == 62)
                    this.state = 29;
                break;
            case 39:
                switch (c) {
                case 32:
                case 9:
                case 13:
                case 10:
                    this.state = 58;
                    break;
                case 62:
                    if (this.tags.pop() != this.tagName)
                        this.exception(JIM_ERROR_XMLPARSER_CLOSING_UNOPENED_TAG);
                    this.fireEvent("onEndTag", this.tagName);
                    if (this.tags.length == 0) {
                        this.state = 11;
                        this.fireEvent("onEndDocument");
                    } else
                        this.state = 29;
                    break;
                default:
                    if (c == 47)
                        this.state = 59;
                    else
                        this.tagName += ch;
                }
                break;
            case 40:
                switch (c) {
                case 32:
                case 9:
                case 13:
                case 10:
                    break;
                case 47:
                    this.state = 41;
                    break;
                case 62:
                    this.fireEvent("onStartTag", this.tagName, this.attributes);
                    this.tags.push(this.tagName);
                    this.state = 29;
                    break;
                default:
                    this.attrName = ch;
                    this.state = 44;
                    break;
                }
                break;
            case 41:
                if (c != 62)
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_TAG_ENDING);
                this.fireEvent("onStartTag", this.tagName, this.attributes);
                this.fireEvent("onEndTag", this.tagName);
                this.state = 29;
                break;
            case 42:
                if (c == 45)
                    this.state = 45;
                break;
            case 43:
                if (c != 68)
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_CDATA_DECLARATION);
                this.state = 46;
                break;
            case 44:
                switch (c) {
                case 32:
                case 9:
                case 13:
                case 10:
                    this.state = 47;
                    break;
                case 61:
                    this.state = 48;
                    break;
                default:
                    this.attrName += ch;
                    break;
                }
                break;
            case 45:
                if (c == 45)
                    this.state = 49;
                else
                    this.state = 42;
                break;
            case 46:
                if (c != 65)
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_CDATA_DECLARATION);
                this.state = 50;
                break;
            case 47:
                switch (c) {
                case 32:
                case 9:
                case 13:
                case 10:
                    break;
                case 61:
                    this.state = 48;
                    break;
                default:
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_ATTRIBUTE_DEFINITION);
                }
                break;
            case 48:
                switch (c) {
                case 32:
                case 9:
                case 13:
                case 10:
                    break;
                case 39:
                    this.state = 51;
                    this.attrValue = "";
                    break;
                case 34:
                    this.state = 52;
                    this.attrValue = "";
                    break;
                default:
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_ATTRIBUTE_VALUE_DEFINITION);
                }
                break;
            case 49:
                if (c == 62)
                    this.state = 29;
                else
                    this.state = 42;
                break;
            case 50:
                if (c != 84)
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_CDATA_DECLARATION);
                this.state = 53;
                break;
            case 51:
                switch (c) {
                case 39:
                    this.attributes[this.attrName] = decodeEntity(this.attrValue);
                    this.state = 40;
                    break;
                case 60:
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_ATTRIBUTE_VALUE_DEFINITION);
                default:
                    this.attrValue += ch;
                    break;
                }
                break;
            case 52:
                switch (c) {
                case 34:
                    this.attributes[this.attrName] = decodeEntity(this.attrValue);
                    this.state = 40;
                    break;
                case 60:
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_ATTRIBUTE_VALUE_DEFINITION);
                default:
                    this.attrValue += ch;
                    break;
                }
                break;
            case 53:
                if (c != 65)
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_CDATA_DECLARATION);
                this.state = 54;
                break;
            case 54:
                if (c != 91)
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_CDATA_DECLARATION);
                this.state = 55;
                break;
            case 55:
                if (c != 93)
                    this.cdata += ch;
                else
                    this.state = 56;
                break;
            case 56:
                if (c != 93) {
                    this.cdata += "]" + ch;
                    this.state = 55;
                } else
                    this.state = 57;
                break;
            case 57:
                if (c != 62) {
                    this.cdata += "]]" + ch;
                    this.state = 55;
                } else {
                    if (this.cdata.length > 0)
                        this.fireEvent("onData", this.cdata);
                    this.cdata = "";
                    this.state = 29;
                }
                break;
            case 58:
                switch (c) {
                case 32:
                case 9:
                case 13:
                case 10:
                    break;
                case 62:
                    if (this.tags.pop() != this.tagName)
                        this.exception(JIM_ERROR_XMLPARSER_CLOSING_UNOPENED_TAG);
                    this.fireEvent("onEndTag", this.tagName);
                    if (this.tags.length == 0) {
                        this.state = 11;
                        this.fireEvent("onEndDocument");
                    } else
                        this.state = 29;
                    break;
                default:
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_TAG_DECLARATION);
                }
                break;
            case 59:
                if (c != 62)
                    this.exception(JIM_ERROR_XMLPARSER_INVALID_TAG_ENDING);
                if (this.tags.pop() != this.tagName)
                    this.exception(JIM_ERROR_XMLPARSER_CLOSING_UNOPENED_TAG);
                this.fireEvent("onEndTag", this.tagName);
                if (this.tags.length == 0) {
                    this.state = 11;
                    this.fireEvent("onEndDocument");
                } else
                    this.state = 29;
                break;
            }
        }
        if (this.data.length > 0) {
            this.fireEvent("onData", decodeEntity(this.data));
            this.data = "";
        }
        if (this.cdata.length > 0) {
            this.fireEvent("onData", this.cdata);
            this.cdata = "";
        }
    }        
}
