// =========================================================================
//
// xmlsax.js - an XML SAX parser in JavaScript.
//
// version 3.1
//
// =========================================================================
//
// Copyright (C) 2001 - 2002 David Joham (djoham@yahoo.com) and Scott Severtson
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.

// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.

// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
//
// Visit the XML for <SCRIPT> home page at http://xmljs.sourceforge.net
//

// CONSTANTS

// =========================================================================
// =========================================================================
// =========================================================================
var whitespace = "\n\r\t ";


/***************************************************************************************************************
XMLP is a pull-based parser. The calling application passes in a XML string
to the constructor, then repeatedly calls .next() to parse the next segment.
.next() returns a flag indicating what type of segment was found, and stores
data temporarily in couple member variables (name, content, array of
attributes), which can be accessed by several .get____() methods.

Basically, XMLP is the lowest common denominator parser - an very simple
API which other wrappers can be built against.
*****************************************************************************************************************/


XMLP = function(strXML) {
    /*******************************************************************************************************************
    function:   this is the constructor to the XMLP Object

    Author:   Scott Severtson

    Description:
        Instantiates and initializes the object
    *********************************************************************************************************************/
    // Normalize line breaks
    strXML = SAXStrings.replace(strXML, null, null, "\r\n", "\n");
    strXML = SAXStrings.replace(strXML, null, null, "\r", "\n");

    this.m_xml = strXML;
    this.m_iP = 0;
    this.m_iState = XMLP._STATE_PROLOG;
    this.m_stack = new Stack();
    this._clearAttributes();

}  // end XMLP constructor


// CONSTANTS    (these must be below the constructor)

// =========================================================================
// =========================================================================
// =========================================================================

XMLP._NONE    = 0;
XMLP._ELM_B   = 1;
XMLP._ELM_E   = 2;
XMLP._ELM_EMP = 3;
XMLP._ATT     = 4;
XMLP._TEXT    = 5;
XMLP._ENTITY  = 6;
XMLP._PI      = 7;
XMLP._CDATA   = 8;
XMLP._COMMENT = 9;
XMLP._DTD     = 10;
XMLP._ERROR   = 11;

XMLP._CONT_XML = 0;
XMLP._CONT_ALT = 1;

XMLP._ATT_NAME = 0;
XMLP._ATT_VAL  = 1;

XMLP._STATE_PROLOG = 1;
XMLP._STATE_DOCUMENT = 2;
XMLP._STATE_MISC = 3;

XMLP._errs = new Array();
XMLP._errs[XMLP.ERR_CLOSE_PI       = 0 ] = "PI: missing closing sequence";
XMLP._errs[XMLP.ERR_CLOSE_DTD      = 1 ] = "DTD: missing closing sequence";
XMLP._errs[XMLP.ERR_CLOSE_COMMENT  = 2 ] = "Comment: missing closing sequence";
XMLP._errs[XMLP.ERR_CLOSE_CDATA    = 3 ] = "CDATA: missing closing sequence";
XMLP._errs[XMLP.ERR_CLOSE_ELM      = 4 ] = "Element: missing closing sequence";
XMLP._errs[XMLP.ERR_CLOSE_ENTITY   = 5 ] = "Entity: missing closing sequence";
XMLP._errs[XMLP.ERR_PI_TARGET      = 6 ] = "PI: target is required";
XMLP._errs[XMLP.ERR_ELM_EMPTY      = 7 ] = "Element: cannot be both empty and closing";
XMLP._errs[XMLP.ERR_ELM_NAME       = 8 ] = "Element: name must immediatly follow \"<\"";
XMLP._errs[XMLP.ERR_ELM_LT_NAME    = 9 ] = "Element: \"<\" not allowed in element names";
XMLP._errs[XMLP.ERR_ATT_VALUES     = 10] = "Attribute: values are required and must be in quotes";
XMLP._errs[XMLP.ERR_ATT_LT_NAME    = 11] = "Element: \"<\" not allowed in attribute names";
XMLP._errs[XMLP.ERR_ATT_LT_VALUE   = 12] = "Attribute: \"<\" not allowed in attribute values";
XMLP._errs[XMLP.ERR_ATT_DUP        = 13] = "Attribute: duplicate attributes not allowed";
XMLP._errs[XMLP.ERR_ENTITY_UNKNOWN = 14] = "Entity: unknown entity";
XMLP._errs[XMLP.ERR_INFINITELOOP   = 15] = "Infininte loop";
XMLP._errs[XMLP.ERR_DOC_STRUCTURE  = 16] = "Document: only comments, processing instructions, or whitespace allowed outside of document element";
XMLP._errs[XMLP.ERR_ELM_NESTING    = 17] = "Element: must be nested correctly";

// =========================================================================
// =========================================================================
// =========================================================================


XMLP.prototype._addAttribute = function(name, value) {
    /*******************************************************************************************************************
    function:   _addAttribute

    Author:   Scott Severtson
    *********************************************************************************************************************/
    this.m_atts[this.m_atts.length] = new Array(name, value);
}  // end function _addAttribute


XMLP.prototype._checkStructure = function(iEvent) {
    /*******************************************************************************************************************
    function:   _checkStructure

    Author:   Scott Severtson
    *********************************************************************************************************************/
  
	if(XMLP._STATE_PROLOG == this.m_iState) {
		if((XMLP._TEXT == iEvent) || (XMLP._ENTITY == iEvent)) {
            if(SAXStrings.indexOfNonWhitespace(this.getContent(), this.getContentBegin(), this.getContentEnd()) != -1) {
				return this._setErr(XMLP.ERR_DOC_STRUCTURE);
            }
        }

        if((XMLP._ELM_B == iEvent) || (XMLP._ELM_EMP == iEvent)) {
            this.m_iState = XMLP._STATE_DOCUMENT;
            // Don't return - fall through to next state
        }
    }
    if(XMLP._STATE_DOCUMENT == this.m_iState) {
        if((XMLP._ELM_B == iEvent) || (XMLP._ELM_EMP == iEvent)) {
            this.m_stack.push(this.getName());
        }

        if((XMLP._ELM_E == iEvent) || (XMLP._ELM_EMP == iEvent)) {
            var strTop = this.m_stack.pop();
            if((strTop == null) || (strTop != this.getName())) {
                return this._setErr(XMLP.ERR_ELM_NESTING);
            }
        }

        if(this.m_stack.count() == 0) {
            this.m_iState = XMLP._STATE_MISC;
            return iEvent;
        }
    }
    if(XMLP._STATE_MISC == this.m_iState) {
		if((XMLP._ELM_B == iEvent) || (XMLP._ELM_E == iEvent) || (XMLP._ELM_EMP == iEvent) || (XMLP.EVT_DTD == iEvent)) {
			return this._setErr(XMLP.ERR_DOC_STRUCTURE);
        }

        if((XMLP._TEXT == iEvent) || (XMLP._ENTITY == iEvent)) {
			if(SAXStrings.indexOfNonWhitespace(this.getContent(), this.getContentBegin(), this.getContentEnd()) != -1) {
				return this._setErr(XMLP.ERR_DOC_STRUCTURE);
            }
        }
    }

    return iEvent;

}  // end function _checkStructure


XMLP.prototype._clearAttributes = function() {
    /*******************************************************************************************************************
    function:   _clearAttributes

    Author:   Scott Severtson
    *********************************************************************************************************************/
    this.m_atts = new Array();
}  // end function _clearAttributes


XMLP.prototype._findAttributeIndex = function(name) {
    /*******************************************************************************************************************
    function:   findAttributeIndex

    Author:   Scott Severtson
    *********************************************************************************************************************/
    for(var i = 0; i < this.m_atts.length; i++) {
        if(this.m_atts[i][XMLP._ATT_NAME] == name) {
            return i;
        }
    }
    return -1;

}  // end function _findAttributeIndex


XMLP.prototype.getAttributeCount = function() {
    /*******************************************************************************************************************
    function:   getAttributeCount

    Author:   Scott Severtson
    *********************************************************************************************************************/

    return this.m_atts ? this.m_atts.length : 0;

}  // end function getAttributeCount()


XMLP.prototype.getAttributeName = function(index) {
    /*******************************************************************************************************************
    function:   getAttributeName

    Author:   Scott Severtson
    *********************************************************************************************************************/

    return ((index < 0) || (index >= this.m_atts.length)) ? null : this.m_atts[index][XMLP._ATT_NAME];

}  //end function getAttributeName


XMLP.prototype.getAttributeValue = function(index) {
    /*******************************************************************************************************************
    function:   getAttributeValue

    Author:   Scott Severtson
    *********************************************************************************************************************/

    return ((index < 0) || (index >= this.m_atts.length)) ? null : __unescapeString(this.m_atts[index][XMLP._ATT_VAL]);

} // end function getAttributeValue


XMLP.prototype.getAttributeValueByName = function(name) {
    /*******************************************************************************************************************
    function:   getAttributeValueByName

    Author:   Scott Severtson
    *********************************************************************************************************************/

    return this.getAttributeValue(this._findAttributeIndex(name));

}  // end function getAttributeValueByName


XMLP.prototype.getColumnNumber = function() {
    /*******************************************************************************************************************
    function:   getColumnNumber

    Author:   Scott Severtson
    *********************************************************************************************************************/

    return SAXStrings.getColumnNumber(this.m_xml, this.m_iP);

}  // end function getColumnNumber


XMLP.prototype.getContent = function() {
    /*******************************************************************************************************************
    function:   getContent

    Author:   Scott Severtson
    *********************************************************************************************************************/

    return (this.m_cSrc == XMLP._CONT_XML) ? this.m_xml : this.m_cAlt;

}  //end function getContent


XMLP.prototype.getContentBegin = function() {
    /*******************************************************************************************************************
    function:   getContentBegin

    Author:   Scott Severtson
    *********************************************************************************************************************/

    return this.m_cB;

}  //end function getContentBegin


XMLP.prototype.getContentEnd = function() {
    /*******************************************************************************************************************
    function:   getContentEnd

    Author:   Scott Severtson
    *********************************************************************************************************************/

    return this.m_cE;

}  // end function getContentEnd


XMLP.prototype.getLineNumber = function() {
    /*******************************************************************************************************************
    function:   getLineNumber

    Author:   Scott Severtson
    *********************************************************************************************************************/

    return SAXStrings.getLineNumber(this.m_xml, this.m_iP);

}  // end function getLineNumber


XMLP.prototype.getName = function() {
    /*******************************************************************************************************************
    function:   getName

    Author:   Scott Severtson
    *********************************************************************************************************************/

    return this.m_name;

}  // end function getName()


XMLP.prototype.next = function() {
    /*******************************************************************************************************************
    function:   next

    Author:   Scott Severtson
    *********************************************************************************************************************/

    return this._checkStructure(this._parse());

}  // end function next()


XMLP.prototype._parse = function() {
    /*******************************************************************************************************************
    function:   _parse

    Author:   Scott Severtson
    *********************************************************************************************************************/

	if(this.m_iP == this.m_xml.length) {
        return XMLP._NONE;
    }

    if(this.m_iP == this.m_xml.indexOf("<?",        this.m_iP)) {
        return this._parsePI     (this.m_iP + 2);
    }
    else if(this.m_iP == this.m_xml.indexOf("<!DOCTYPE", this.m_iP)) {
        return this._parseDTD    (this.m_iP + 9);
    }
    else if(this.m_iP == this.m_xml.indexOf("<!--",      this.m_iP)) {
        return this._parseComment(this.m_iP + 4);
    }
    else if(this.m_iP == this.m_xml.indexOf("<![CDATA[", this.m_iP)) {
        return this._parseCDATA  (this.m_iP + 9);
    }
    else if(this.m_iP == this.m_xml.indexOf("<",         this.m_iP)) {
        return this._parseElement(this.m_iP + 1);
    }
    else if(this.m_iP == this.m_xml.indexOf("&",         this.m_iP)) {
        return this._parseEntity (this.m_iP + 1);
    }
    else{
        return this._parseText   (this.m_iP);
    }
	

}  // end function _parse


XMLP.prototype._parseAttribute = function(iB, iE) {
    /*******************************************************************************************************************
    function:   _parseAttribute

    Author:   Scott Severtson
    *********************************************************************************************************************/
    var iNB, iNE, iEq, iVB, iVE;
    var cQuote, strN, strV;

	this.m_cAlt = ""; //resets the value so we don't use an old one by accident (see testAttribute7 in the test suite)
    
	iNB = SAXStrings.indexOfNonWhitespace(this.m_xml, iB, iE);
    if((iNB == -1) ||(iNB >= iE)) {
        return iNB;
    }

    iEq = this.m_xml.indexOf("=", iNB);
    if((iEq == -1) || (iEq > iE)) {
        return this._setErr(XMLP.ERR_ATT_VALUES);
    }

    iNE = SAXStrings.lastIndexOfNonWhitespace(this.m_xml, iNB, iEq);

    iVB = SAXStrings.indexOfNonWhitespace(this.m_xml, iEq + 1, iE);
    if((iVB == -1) ||(iVB > iE)) {
        return this._setErr(XMLP.ERR_ATT_VALUES);
    }

    cQuote = this.m_xml.charAt(iVB);
    if(SAXStrings.QUOTES.indexOf(cQuote) == -1) {
        return this._setErr(XMLP.ERR_ATT_VALUES);
    }

    iVE = this.m_xml.indexOf(cQuote, iVB + 1);
    if((iVE == -1) ||(iVE > iE)) {
        return this._setErr(XMLP.ERR_ATT_VALUES);
    }

    strN = this.m_xml.substring(iNB, iNE + 1);
    strV = this.m_xml.substring(iVB + 1, iVE);

    if(strN.indexOf("<") != -1) {
        return this._setErr(XMLP.ERR_ATT_LT_NAME);
    }

    if(strV.indexOf("<") != -1) {
        return this._setErr(XMLP.ERR_ATT_LT_VALUE);
    }

    strV = SAXStrings.replace(strV, null, null, "\n", " ");
    strV = SAXStrings.replace(strV, null, null, "\t", " ");
	iRet = this._replaceEntities(strV);
    if(iRet == XMLP._ERROR) {
        return iRet;
    }

    strV = this.m_cAlt;

    if(this._findAttributeIndex(strN) == -1) {
        this._addAttribute(strN, strV);
    }
    else {
        return this._setErr(XMLP.ERR_ATT_DUP);
    }

    this.m_iP = iVE + 2;

    return XMLP._ATT;

}  // end function _parseAttribute


XMLP.prototype._parseCDATA = function(iB) {
    /*******************************************************************************************************************
    function:   _parseCDATA

    Author:   Scott Severtson
    *********************************************************************************************************************/
    var iE = this.m_xml.indexOf("]]>", iB);
    if (iE == -1) {
        return this._setErr(XMLP.ERR_CLOSE_CDATA);
    }

    this._setContent(XMLP._CONT_XML, iB, iE);

    this.m_iP = iE + 3;

    return XMLP._CDATA;

}  // end function _parseCDATA


XMLP.prototype._parseComment = function(iB) {
    /*******************************************************************************************************************
    function:   _parseComment

    Author:   Scott Severtson
    *********************************************************************************************************************/
    var iE = this.m_xml.indexOf("-" + "->", iB);
    if (iE == -1) {
        return this._setErr(XMLP.ERR_CLOSE_COMMENT);
    }

    this._setContent(XMLP._CONT_XML, iB, iE);

    this.m_iP = iE + 3;

    return XMLP._COMMENT;

}  // end function _parseComment


XMLP.prototype._parseDTD = function(iB) {
    /*******************************************************************************************************************
    function:  _parseDTD

    Author:   Scott Severtson
    *********************************************************************************************************************/

    // Eat DTD

    var iE, strClose, iInt, iLast;

    iE = this.m_xml.indexOf(">", iB);
    if(iE == -1) {
        return this._setErr(XMLP.ERR_CLOSE_DTD);
    }

    iInt = this.m_xml.indexOf("[", iB);
    strClose = ((iInt != -1) && (iInt < iE)) ? "]>" : ">";

    while(true) {
        // DEBUG: Remove
        if(iE == iLast) {
            return this._setErr(XMLP.ERR_INFINITELOOP);
        }

        iLast = iE;
        // DEBUG: Remove End

        iE = this.m_xml.indexOf(strClose, iB);
        if(iE == -1) {
            return this._setErr(XMLP.ERR_CLOSE_DTD);
        }

        // Make sure it is not the end of a CDATA section
        if (this.m_xml.substring(iE - 1, iE + 2) != "]]>") {
            break;
        }
    }

    this.m_iP = iE + strClose.length;

    return XMLP._DTD;

}  // end function _parseDTD


XMLP.prototype._parseElement = function(iB) {
    /*******************************************************************************************************************
    function:   _parseElement

    Author:   Scott Severtson
    *********************************************************************************************************************/
    var iE, iDE, iNE, iRet;
    var iType, strN, iLast;

    iDE = iE = this.m_xml.indexOf(">", iB);
    if(iE == -1) {
        return this._setErr(XMLP.ERR_CLOSE_ELM);
    }

    if(this.m_xml.charAt(iB) == "/") {
        iType = XMLP._ELM_E;
        iB++;
    } else {
        iType = XMLP._ELM_B;
    }

    if(this.m_xml.charAt(iE - 1) == "/") {
        if(iType == XMLP._ELM_E) {
            return this._setErr(XMLP.ERR_ELM_EMPTY);
        }
        iType = XMLP._ELM_EMP;
        iDE--;
    }

    iDE = SAXStrings.lastIndexOfNonWhitespace(this.m_xml, iB, iDE);

    //djohack
    //hack to allow for elements with single character names to be recognized

    if (iE - iB != 1 ) {
        if(SAXStrings.indexOfNonWhitespace(this.m_xml, iB, iDE) != iB) {
            return this._setErr(XMLP.ERR_ELM_NAME);
        }
    }
    // end hack -- original code below

    /*
    if(SAXStrings.indexOfNonWhitespace(this.m_xml, iB, iDE) != iB)
        return this._setErr(XMLP.ERR_ELM_NAME);
    */
    this._clearAttributes();

    iNE = SAXStrings.indexOfWhitespace(this.m_xml, iB, iDE);
    if(iNE == -1) {
        iNE = iDE + 1;
    }
    else {
        this.m_iP = iNE;
        while(this.m_iP < iDE) {
            // DEBUG: Remove
            if(this.m_iP == iLast) return this._setErr(XMLP.ERR_INFINITELOOP);
            iLast = this.m_iP;
            // DEBUG: Remove End


            iRet = this._parseAttribute(this.m_iP, iDE);
            if(iRet == XMLP._ERROR) return iRet;
        }
    }

    strN = this.m_xml.substring(iB, iNE);

    if(strN.indexOf("<") != -1) {
        return this._setErr(XMLP.ERR_ELM_LT_NAME);
    }

    this.m_name = strN;
    this.m_iP = iE + 1;

    return iType;

}  // end function _parseElement


XMLP.prototype._parseEntity = function(iB) {
    /*******************************************************************************************************************
    function:   _parseEntity

    Author:   Scott Severtson
    *********************************************************************************************************************/
    var iE = this.m_xml.indexOf(";", iB);
    if(iE == -1) {
        return this._setErr(XMLP.ERR_CLOSE_ENTITY);
    }

    this.m_iP = iE + 1;

    return this._replaceEntity(this.m_xml, iB, iE);

}  // end function _parseEntity


XMLP.prototype._parsePI = function(iB) {
    /*******************************************************************************************************************
    function:   _parsePI

    Author:   Scott Severtson
    *********************************************************************************************************************/

    var iE, iTB, iTE, iCB, iCE;

    iE = this.m_xml.indexOf("?>", iB);
    if(iE   == -1) {
        return this._setErr(XMLP.ERR_CLOSE_PI);
    }

    iTB = SAXStrings.indexOfNonWhitespace(this.m_xml, iB, iE);
    if(iTB == -1) {
        return this._setErr(XMLP.ERR_PI_TARGET);
    }

    iTE = SAXStrings.indexOfWhitespace(this.m_xml, iTB, iE);
    if(iTE  == -1) {
        iTE = iE;
    }

    iCB = SAXStrings.indexOfNonWhitespace(this.m_xml, iTE, iE);
    if(iCB == -1) {
        iCB = iE;
    }

    iCE = SAXStrings.lastIndexOfNonWhitespace(this.m_xml, iCB, iE);
    if(iCE  == -1) {
        iCE = iE - 1;
    }

    this.m_name = this.m_xml.substring(iTB, iTE);
    this._setContent(XMLP._CONT_XML, iCB, iCE + 1);
    this.m_iP = iE + 2;

    return XMLP._PI;

}  // end function _parsePI


XMLP.prototype._parseText = function(iB) {
    /*******************************************************************************************************************
    function:   _parseText

    Author:   Scott Severtson
    *********************************************************************************************************************/
    var iE, iEE;

    iE = this.m_xml.indexOf("<", iB);
    if(iE == -1) {
        iE = this.m_xml.length;
    }

    iEE = this.m_xml.indexOf("&", iB);
    if((iEE != -1) && (iEE <= iE)) {
        iE = iEE;
    }

    this._setContent(XMLP._CONT_XML, iB, iE);

    this.m_iP = iE;

    return XMLP._TEXT;

} // end function _parseText


XMLP.prototype._replaceEntities = function(strD, iB, iE) {
    /*******************************************************************************************************************
    function:   _replaceEntities

    Author:   Scott Severtson
    *********************************************************************************************************************/
    if(SAXStrings.isEmpty(strD)) return "";
    iB = iB || 0;
    iE = iE || strD.length;


    var iEB, iEE, strRet = "";

    iEB = strD.indexOf("&", iB);
    iEE = iB;

    while((iEB > 0) && (iEB < iE)) {
        strRet += strD.substring(iEE, iEB);

        iEE = strD.indexOf(";", iEB) + 1;

        if((iEE == 0) || (iEE > iE)) {
            return this._setErr(XMLP.ERR_CLOSE_ENTITY);
        }

        iRet = this._replaceEntity(strD, iEB + 1, iEE - 1);
        if(iRet == XMLP._ERROR) {
            return iRet;
        }

        strRet += this.m_cAlt;

        iEB = strD.indexOf("&", iEE);
    }

    if(iEE != iE) {
        strRet += strD.substring(iEE, iE);
    }

    this._setContent(XMLP._CONT_ALT, strRet);

    return XMLP._ENTITY;

}  // end function _replaceEntities


XMLP.prototype._replaceEntity = function(strD, iB, iE) {
    /*******************************************************************************************************************
    function:   _replaceEntity

    Author:   Scott Severtson
    *********************************************************************************************************************/
    if(SAXStrings.isEmpty(strD)) return -1;
    iB = iB || 0;
    iE = iE || strD.length;

    switch(strD.substring(iB, iE)) {
        case "amp":  strEnt = "&";  break;
        case "lt":   strEnt = "<";  break;
        case "gt":   strEnt = ">";  break;
        case "apos": strEnt = "'";  break;
        case "quot": strEnt = "\""; break;
        default:
            if(strD.charAt(iB) == "#") {
                strEnt = String.fromCharCode(parseInt(strD.substring(iB + 1, iE)));
            } else {
                return this._setErr(XMLP.ERR_ENTITY_UNKNOWN);
            }
        break;
    }
    this._setContent(XMLP._CONT_ALT, strEnt);

    return XMLP._ENTITY;
}  // end function _replaceEntity


XMLP.prototype._setContent = function(iSrc) {
    /*******************************************************************************************************************
    function:   _setContent

    Author:   Scott Severtson
    *********************************************************************************************************************/
    var args = arguments;

    if(XMLP._CONT_XML == iSrc) {
        this.m_cAlt = null;
        this.m_cB = args[1];
        this.m_cE = args[2];
    } else {
        this.m_cAlt = args[1];
        this.m_cB = 0;
        this.m_cE = args[1].length;
    }
    this.m_cSrc = iSrc;

}  // end function _setContent


XMLP.prototype._setErr = function(iErr) {
    /*******************************************************************************************************************
    function:   _setErr

    Author:   Scott Severtson
    *********************************************************************************************************************/
    var strErr = XMLP._errs[iErr];

    this.m_cAlt = strErr;
    this.m_cB = 0;
    this.m_cE = strErr.length;
    this.m_cSrc = XMLP._CONT_ALT;

    return XMLP._ERROR;

}  // end function _setErr






/***************************************************************************************************************
SAXDriver is an object that basically wraps an XMLP instance, and provides an
event-based interface for parsing. This is the object users interact with when coding
with XML for <SCRIPT>
*****************************************************************************************************************/


SAXDriver = function() {
    /*******************************************************************************************************************
    function:   SAXDriver

    Author:   Scott Severtson

    Description:
        This is the constructor for the SAXDriver Object
    *********************************************************************************************************************/
    this.m_hndDoc = null;
    this.m_hndErr = null;
    this.m_hndLex = null;
}


// CONSTANTS    (these must be below the constructor)

// =========================================================================
// =========================================================================
// =========================================================================
SAXDriver.DOC_B = 1;
SAXDriver.DOC_E = 2;
SAXDriver.ELM_B = 3;
SAXDriver.ELM_E = 4;
SAXDriver.CHARS = 5;
SAXDriver.PI    = 6;
SAXDriver.CD_B  = 7;
SAXDriver.CD_E  = 8;
SAXDriver.CMNT  = 9;
SAXDriver.DTD_B = 10;
SAXDriver.DTD_E = 11;
// =========================================================================
// =========================================================================
// =========================================================================



SAXDriver.prototype.parse = function(strD) {
    /*******************************************************************************************************************
    function:   parse

    Author:   Scott Severtson
    *********************************************************************************************************************/
    var parser = new XMLP(strD);

    if(this.m_hndDoc && this.m_hndDoc.setDocumentLocator) {
        this.m_hndDoc.setDocumentLocator(this);
    }

    this.m_parser = parser;
    this.m_bErr = false;

    if(!this.m_bErr) {
        this._fireEvent(SAXDriver.DOC_B);
    }
    this._parseLoop();
    if(!this.m_bErr) {
        this._fireEvent(SAXDriver.DOC_E);
    }

    this.m_xml = null;
    this.m_iP = 0;

}  // end function parse


SAXDriver.prototype.setDocumentHandler = function(hnd) {
    /*******************************************************************************************************************
    function:   setDocumentHandler

    Author:   Scott Severtson
    *********************************************************************************************************************/

    this.m_hndDoc = hnd;

}   // end function setDocumentHandler


SAXDriver.prototype.setErrorHandler = function(hnd) {
    /*******************************************************************************************************************
    function:   setErrorHandler

    Author:   Scott Severtson
    *********************************************************************************************************************/

    this.m_hndErr = hnd;

}  // end function setErrorHandler


SAXDriver.prototype.setLexicalHandler = function(hnd) {
    /*******************************************************************************************************************
    function:   setLexicalHandler

    Author:   Scott Severtson
    *********************************************************************************************************************/

    this.m_hndLex = hnd;

}  // end function setLexicalHandler


    /*******************************************************************************************************************
                                                LOCATOR/PARSE EXCEPTION INTERFACE
    *********************************************************************************************************************/

SAXDriver.prototype.getColumnNumber = function() {
    /*******************************************************************************************************************
    function:   getSystemId

    Author:   Scott Severtson
    *********************************************************************************************************************/

    return this.m_parser.getColumnNumber();

}  // end function getColumnNumber


SAXDriver.prototype.getLineNumber = function() {
    /*******************************************************************************************************************
    function:   getLineNumber

    Author:   Scott Severtson
    *********************************************************************************************************************/

    return this.m_parser.getLineNumber();

}  // end function getLineNumber


SAXDriver.prototype.getMessage = function() {
    /*******************************************************************************************************************
    function:   getMessage

    Author:   Scott Severtson
    *********************************************************************************************************************/

    return this.m_strErrMsg;

}  // end function getMessage


SAXDriver.prototype.getPublicId = function() {
    /*******************************************************************************************************************
    function:   getPublicID

    Author:   Scott Severtson
    *********************************************************************************************************************/

    return null;

}  // end function getPublicID


SAXDriver.prototype.getSystemId = function() {
    /*******************************************************************************************************************
    function:   getSystemId

    Author:   Scott Severtson
    *********************************************************************************************************************/

    return null;

}  // end function getSystemId


    /*******************************************************************************************************************
                                                Attribute List Interface
    *********************************************************************************************************************/

SAXDriver.prototype.getLength = function() {
    /*******************************************************************************************************************
    function:   getLength

    Author:   Scott Severtson
    *********************************************************************************************************************/

    return this.m_parser.getAttributeCount();

}  // end function getAttributeCount


SAXDriver.prototype.getName = function(index) {
    /*******************************************************************************************************************
    function:   getName

    Author:   Scott Severtson
    *********************************************************************************************************************/

    return this.m_parser.getAttributeName(index);

} // end function getAttributeName


SAXDriver.prototype.getValue = function(index) {
    /*******************************************************************************************************************
    function:   getValue

    Author:   Scott Severtson
    *********************************************************************************************************************/

    return this.m_parser.getAttributeValue(index);

}  // end function getAttributeValue


SAXDriver.prototype.getValueByName = function(name) {
    /*******************************************************************************************************************
    function:   getValueByName

    Author:   Scott Severtson
    *********************************************************************************************************************/

    return this.m_parser.getAttributeValueByName(name);

} // end function getAttributeValueByName


    /*******************************************************************************************************************
                                                                Private functions
    *********************************************************************************************************************/

SAXDriver.prototype._fireError = function(strMsg) {
    /*******************************************************************************************************************
    function:   _fireError

    Author:   Scott Severtson
    *********************************************************************************************************************/
    this.m_strErrMsg = strMsg;
    this.m_bErr = true;

    if(this.m_hndErr && this.m_hndErr.fatalError) {
        this.m_hndErr.fatalError(this);
    }

}   // end function _fireError


SAXDriver.prototype._fireEvent = function(iEvt) {
    /*******************************************************************************************************************
    function:   _fireEvent

    Author:   Scott Severtson
    *********************************************************************************************************************/
    var hnd, func, args = arguments, iLen = args.length - 1;

    if(this.m_bErr) return;

    if(SAXDriver.DOC_B == iEvt) {
        func = "startDocument";         hnd = this.m_hndDoc;
    }
    else if (SAXDriver.DOC_E == iEvt) {
        func = "endDocument";           hnd = this.m_hndDoc;
    }
    else if (SAXDriver.ELM_B == iEvt) {
        func = "startElement";          hnd = this.m_hndDoc;
    }
    else if (SAXDriver.ELM_E == iEvt) {
        func = "endElement";            hnd = this.m_hndDoc;
    }
    else if (SAXDriver.CHARS == iEvt) {
        func = "characters";            hnd = this.m_hndDoc;
    }
    else if (SAXDriver.PI    == iEvt) {
        func = "processingInstruction"; hnd = this.m_hndDoc;
    }
    else if (SAXDriver.CD_B  == iEvt) {
        func = "startCDATA";            hnd = this.m_hndLex;
    }
    else if (SAXDriver.CD_E  == iEvt) {
        func = "endCDATA";              hnd = this.m_hndLex;
    }
    else if (SAXDriver.CMNT  == iEvt) {
        func = "comment";               hnd = this.m_hndLex;
    }

    if(hnd && hnd[func]) {
        if(0 == iLen) {
            hnd[func]();
        }
        else if (1 == iLen) {
            hnd[func](args[1]);
        }
        else if (2 == iLen) {
            hnd[func](args[1], args[2]);
        }
        else if (3 == iLen) {
            hnd[func](args[1], args[2], args[3]);
        }
    }

}  // end function _fireEvent


SAXDriver.prototype._parseLoop = function(parser) {
    /*******************************************************************************************************************
    function:   _parseLoop

    Author:   Scott Severtson
    *********************************************************************************************************************/
    var iEvent, parser;

    parser = this.m_parser;
    while(!this.m_bErr) {
        iEvent = parser.next();

        if(iEvent == XMLP._ELM_B) {
            this._fireEvent(SAXDriver.ELM_B, parser.getName(), this);
        }
        else if(iEvent == XMLP._ELM_E) {
            this._fireEvent(SAXDriver.ELM_E, parser.getName());
        }
        else if(iEvent == XMLP._ELM_EMP) {
            this._fireEvent(SAXDriver.ELM_B, parser.getName(), this);
            this._fireEvent(SAXDriver.ELM_E, parser.getName());
        }
        else if(iEvent == XMLP._TEXT) {
            this._fireEvent(SAXDriver.CHARS, parser.getContent(), parser.getContentBegin(), parser.getContentEnd() - parser.getContentBegin());
        }
        else if(iEvent == XMLP._ENTITY) {
            this._fireEvent(SAXDriver.CHARS, parser.getContent(), parser.getContentBegin(), parser.getContentEnd() - parser.getContentBegin());
        }
        else if(iEvent == XMLP._PI) {
            this._fireEvent(SAXDriver.PI, parser.getName(), parser.getContent().substring(parser.getContentBegin(), parser.getContentEnd()));
        }
        else if(iEvent == XMLP._CDATA) {
            this._fireEvent(SAXDriver.CD_B);
            this._fireEvent(SAXDriver.CHARS, parser.getContent(), parser.getContentBegin(), parser.getContentEnd() - parser.getContentBegin());
            this._fireEvent(SAXDriver.CD_E);
        }
        else if(iEvent == XMLP._COMMENT) {
            this._fireEvent(SAXDriver.CMNT, parser.getContent(), parser.getContentBegin(), parser.getContentEnd() - parser.getContentBegin());
        }
        else if(iEvent == XMLP._DTD) {
        }
        else if(iEvent == XMLP._ERROR) {
            this._fireError(parser.getContent());
        }
        else if(iEvent == XMLP._NONE) {
            return;
        }
    }

}  // end function _parseLoop



/***************************************************************************************************************
SAXStrings: a useful object containing string manipulation functions
*****************************************************************************************************************/


SAXStrings = function() {
    /*******************************************************************************************************************
    function:   SAXStrings

    Author:   Scott Severtson

    Description:
        This is the constructor of the SAXStrings object
    *********************************************************************************************************************/
}  // end function SAXStrings


// CONSTANTS    (these must be below the constructor)

// =========================================================================
// =========================================================================
// =========================================================================
SAXStrings.WHITESPACE = " \t\n\r";
SAXStrings.QUOTES = "\"'";
// =========================================================================
// =========================================================================
// =========================================================================


SAXStrings.getColumnNumber = function(strD, iP) {
    /*******************************************************************************************************************
    function:   replace

    Author:   Scott Severtson
    *********************************************************************************************************************/
    if(SAXStrings.isEmpty(strD)) {
        return -1;
    }
    iP = iP || strD.length;

    var arrD = strD.substring(0, iP).split("\n");
    var strLine = arrD[arrD.length - 1];
    arrD.length--;
    var iLinePos = arrD.join("\n").length;

    return iP - iLinePos;

}  // end function getColumnNumber


SAXStrings.getLineNumber = function(strD, iP) {
    /*******************************************************************************************************************
    function:   getLineNumber

    Author:   Scott Severtson
    *********************************************************************************************************************/
    if(SAXStrings.isEmpty(strD)) {
        return -1;
    }
    iP = iP || strD.length;

    return strD.substring(0, iP).split("\n").length
}  // end function getLineNumber


SAXStrings.indexOfNonWhitespace = function(strD, iB, iE) {
    /*******************************************************************************************************************
    function:   indexOfNonWhitespace

    Author:   Scott Severtson
    *********************************************************************************************************************/
    if(SAXStrings.isEmpty(strD)) {
        return -1;
    }
    iB = iB || 0;
    iE = iE || strD.length;

    for(var i = iB; i < iE; i++){
        if(SAXStrings.WHITESPACE.indexOf(strD.charAt(i)) == -1) {
            return i;
        }
    }
    return -1;

}  // end function indexOfNonWhitespace


SAXStrings.indexOfWhitespace = function(strD, iB, iE) {
    /*******************************************************************************************************************
    function:   indexOfWhitespace

    Author:   Scott Severtson
    *********************************************************************************************************************/
    if(SAXStrings.isEmpty(strD)) {
        return -1;
    }
    iB = iB || 0;
    iE = iE || strD.length;

    for(var i = iB; i < iE; i++) {
        if(SAXStrings.WHITESPACE.indexOf(strD.charAt(i)) != -1) {
            return i;
        }
    }
    return -1;
}  // end function indexOfWhitespace


SAXStrings.isEmpty = function(strD) {
    /*******************************************************************************************************************
    function:   isEmpty

    Author:   Scott Severtson
    *********************************************************************************************************************/

    return (strD == null) || (strD.length == 0);

}  // end function isEmpty


SAXStrings.lastIndexOfNonWhitespace = function(strD, iB, iE) {
    /*******************************************************************************************************************
    function:   lastIndexOfNonWhiteSpace

    Author:   Scott Severtson
    *********************************************************************************************************************/
    if(SAXStrings.isEmpty(strD)) {
        return -1;
    }
    iB = iB || 0;
    iE = iE || strD.length;

    for(var i = iE - 1; i >= iB; i--){
        if(SAXStrings.WHITESPACE.indexOf(strD.charAt(i)) == -1){
            return i;
        }
    }
    return -1;
}  // end function lastIndexOfNonWhitespace


SAXStrings.replace = function(strD, iB, iE, strF, strR) {
    /*******************************************************************************************************************
    function:   replace

    Author:   Scott Severtson
    *********************************************************************************************************************/
    if(SAXStrings.isEmpty(strD)) {
        return "";
    }
    iB = iB || 0;
    iE = iE || strD.length;

    return strD.substring(iB, iE).split(strF).join(strR);

}  // end function replace



/***************************************************************************************************************
Stack: A simple stack class, used for verifying document structure.
*****************************************************************************************************************/

Stack = function() {
    /*******************************************************************************************************************
    function:   Stack

    Author:   Scott Severtson

    Description:
        Constructor of the Stack Object
    *********************************************************************************************************************/
    this.m_arr = new Array();

}  // end function Stack


Stack.prototype.clear = function() {
    /*******************************************************************************************************************
    function:   clear

    Author:   Scott Severtson
    *********************************************************************************************************************/

    this.m_arr = new Array();

}  // end function clear


Stack.prototype.count = function() {
    /*******************************************************************************************************************
    function:   count

    Author:   Scott Severtson
    *********************************************************************************************************************/

    return this.m_arr.length;

}  // end function count


Stack.prototype.destroy = function() {
    /*******************************************************************************************************************
    function:   destroy

    Author:   Scott Severtson
    *********************************************************************************************************************/

    this.m_arr = null;

}   // end function destroy


Stack.prototype.peek = function() {
    /*******************************************************************************************************************
    function:   peek

    Author:   Scott Severtson
    *********************************************************************************************************************/
    if(this.m_arr.length == 0) {
        return null;
    }

    return this.m_arr[this.m_arr.length - 1];

}  // end function peek


Stack.prototype.pop = function() {
    /*******************************************************************************************************************
    function:   pop

    Author:   Scott Severtson
    *********************************************************************************************************************/
    if(this.m_arr.length == 0) {
        return null;
    }

    var o = this.m_arr[this.m_arr.length - 1];
    this.m_arr.length--;
    return o;

}  // end function pop


Stack.prototype.push = function(o) {
    /*******************************************************************************************************************
    function:   push

    Author:   Scott Severtson
    *********************************************************************************************************************/

    this.m_arr[this.m_arr.length] = o;

}  // end function push



// =========================================================================
// =========================================================================
// =========================================================================

// CONVENIENCE FUNCTIONS

// =========================================================================
// =========================================================================
// =========================================================================

function isEmpty(str) {
    /*******************************************************************************************************************
    function: isEmpty

    Author: mike@idle.org

    Description:
        convenience function to identify an empty string

    *********************************************************************************************************************/
    return (str==null) || (str.length==0);

} // end function isEmpty



function trim(trimString, leftTrim, rightTrim) {
    /*******************************************************************************************************************
    function: trim

    Author: may106@psu.edu

    Description:
        helper function to trip a string (trimString) of leading (leftTrim)
        and trailing (rightTrim) whitespace

    *********************************************************************************************************************/
    if (isEmpty(trimString)) {
        return "";
    }

    // the general focus here is on minimal method calls - hence only one
    // substring is done to complete the trim.

    if (leftTrim == null) {
        leftTrim = true;
    }

    if (rightTrim == null) {
        rightTrim = true;
    }

    var left=0;
    var right=0;
    var i=0;
    var k=0;


    // modified to properly handle strings that are all whitespace
    if (leftTrim == true) {
        while ((i<trimString.length) && (whitespace.indexOf(trimString.charAt(i++))!=-1)) {
            left++;
        }
    }
    if (rightTrim == true) {
        k=trimString.length-1;
        while((k>=left) && (whitespace.indexOf(trimString.charAt(k--))!=-1)) {
            right++;
        }
    }
    return trimString.substring(left, trimString.length - right);
} // end function trim

/**
 * function __escapeString
 *
 * author: David Joham djoham@yahoo.com
 *
 * @param  str : string - The string to be escaped
 *
 * @return : string - The escaped string
 */
function __escapeString(str) {

    var escAmpRegEx = /&/g;
    var escLtRegEx = /</g;
    var escGtRegEx = />/g;
    var quotRegEx = /"/g;
    var aposRegEx = /'/g;

    str = str.replace(escAmpRegEx, "&amp;");
    str = str.replace(escLtRegEx, "&lt;");
    str = str.replace(escGtRegEx, "&gt;");
    str = str.replace(quotRegEx, "&quot;");
    str = str.replace(aposRegEx, "&apos;");

  return str;
}

/**
 * function __unescapeString 
 *
 * author: David Joham djoham@yahoo.com
 *
 * @param  str : string - The string to be unescaped
 *
 * @return : string - The unescaped string
 */
function __unescapeString(str) {

    var escAmpRegEx = /&amp;/g;
    var escLtRegEx = /&lt;/g;
    var escGtRegEx = /&gt;/g;
    var quotRegEx = /&quot;/g;
    var aposRegEx = /&apos;/g;

    str = str.replace(escAmpRegEx, "&");
    str = str.replace(escLtRegEx, "<");
    str = str.replace(escGtRegEx, ">");
    str = str.replace(quotRegEx, "\"");
    str = str.replace(aposRegEx, "'");

  return str;
}

