// =========================================================================
//
// 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;
}


// =========================================================================
//
// xmldom.js - an XML DOM parser in JavaScript.
//
//	This is the classic DOM that has shipped with XML for <SCRIPT>
//  since the beginning. For a more standards-compliant DOM, you may
//  wish to use the standards-compliant W3C DOM that is included
//  with XML for <SCRIPT> versions 3.0 and above
//
// version 3.1
//
// =========================================================================
//
// Copyright (C) 2000 - 2002, 2003 Michael Houghton (mike@idle.org), Raymond Irving and David Joham (djoham@yahoo.com)
//
// 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

// =========================================================================
// =========================================================================
// =========================================================================

//define the characters which constitute whitespace, and quotes
var whitespace = "\n\r\t ";
var quotes = "\"'";


// =========================================================================
// =========================================================================
// =========================================================================

// CONVENIENCE FUNCTIONS

// =========================================================================
// =========================================================================
// =========================================================================


function convertEscapes(str) {
    /*******************************************************************************************************************
    function: convertEscapes

    Author: David Joham <djoham@yahoo.com>

    Description:
        Characters such as less-than signs, greater-than signs and ampersands are
        illegal in XML syntax and must be escaped before being inserted into the DOM.
        This function is a convience function to take those escaped characters and
        return them to their original values for processing outside the parser

        This XML Parser automagically converts the content of the XML elements to
        their non-escaped values when xmlNode.getText() is called for every element
        except CDATA.

        EXAMPLES:

        &amp; == &
        &lt; == <
        &gt; == >

    *********************************************************************************************************************/
    // not all Konqueror installations have regex support for some reason. Here's the original code using regexes
    // that is probably a little more efficient if it matters to you
    /*
    var escAmpRegEx = /&amp;/g;
    var escLtRegEx = /&lt;/g;
    var escGtRegEx = /&gt;/g;

    str = str.replace(escAmpRegEx, "&");
    str = str.replace(escLtRegEx, "<");
    str = str.replace(escGtRegEx, ">");
    */
    var gt;

    //&lt;
    gt = -1;
    while (str.indexOf("&lt;", gt + 1) > -1) {
        var gt = str.indexOf("&lt;", gt + 1);
        var newStr = str.substr(0, gt);
        newStr += "<";
        newStr = newStr + str.substr(gt + 4, str.length);
        str = newStr;
    }

    //&gt;
    gt = -1;
    while (str.indexOf("&gt;", gt + 1) > -1) {
        var gt = str.indexOf("&gt;", gt + 1);
        var newStr = str.substr(0, gt);
        newStr += ">";
        newStr = newStr + str.substr(gt + 4, str.length);
        str = newStr;
    }

    //&amp;
    gt = -1;
    while (str.indexOf("&amp;", gt + 1) > -1) {
        var gt = str.indexOf("&amp;", gt + 1);
        var newStr = str.substr(0, gt);
        newStr += "&";
        newStr = newStr + str.substr(gt + 5, str.length);
        str = newStr;
    }

    return str;
} // end function convertEscapes


function convertToEscapes(str) {
    /*******************************************************************************************************************
    function: convertToEscapes

    Author: David Joham djoham@yahoo.com

    Description:
        Characters such as less-than signs, greater-than signs and ampersands are
        illegal in XML syntax. This function is a convience function to escape those
        characters out to there legal values.

        EXAMPLES:

        < == &lt;
        > == &gt;
        & == &amp;
    *********************************************************************************************************************/
    // not all Konqueror installations have regex support for some reason. Here's the original code using regexes
    // that is probably a little more efficient if it matters to you
    /*
    var escAmpRegEx = /&/g;
    var escLtRegEx = /</g;
    var escGtRegEx = />/g;
    str = str.replace(escAmpRegEx, "&amp;");
    str = str.replace(escLtRegEx, "&lt;");
    str = str.replace(escGtRegEx, "&gt;");
    */

    // start with &
    var gt = -1;
    while (str.indexOf("&", gt + 1) > -1) {
        gt = str.indexOf("&", gt + 1);
        var newStr = str.substr(0, gt);
        newStr += "&amp;";
        newStr = newStr + str.substr(gt + 1, str.length);
        str = newStr;
    }

    // now <
    gt = -1;
    while (str.indexOf("<", gt + 1) > -1) {
        var gt = str.indexOf("<", gt + 1);
        var newStr = str.substr(0, gt);
        newStr += "&lt;";
        newStr = newStr + str.substr(gt + 1, str.length);
        str = newStr;
    }

    //now >
    gt = -1;
    while (str.indexOf(">", gt + 1) > -1) {
        var gt = str.indexOf(">", gt + 1);
        var newStr = str.substr(0, gt);
        newStr += "&gt;";
        newStr = newStr + str.substr(gt + 1, str.length);
        str = newStr;
    }


    return str;
} // end function convertToEscapes


function _displayElement(domElement, strRet) {
    /*******************************************************************************************************************
    function:       _displayElement

    Author: djoham@yahoo.com

    Description:
        returns the XML string associated with the DOM element passed in
        recursively calls itself if child elements are found

    *********************************************************************************************************************/
    if(domElement==null) {
        return;
    }
    if(!(domElement.nodeType=='ELEMENT')) {
        return;
    }

    var tagName = domElement.tagName;
    var tagInfo = "";
    tagInfo = "<" + tagName;

    // attributes
    var attributeList = domElement.getAttributeNames();

    for(var intLoop = 0; intLoop < attributeList.length; intLoop++) {
        var attribute = attributeList[intLoop];
        tagInfo = tagInfo + " " + attribute + "=";
        tagInfo = tagInfo + "\"" + domElement.getAttribute(attribute) + "\"";
    }

    //close the element name
    tagInfo = tagInfo + ">";

    strRet=strRet+tagInfo;

    // children
    if(domElement.children!=null) {
        var domElements = domElement.children;
        for(var intLoop = 0; intLoop < domElements.length; intLoop++) {
            var childNode = domElements[intLoop];
            if(childNode.nodeType=='COMMENT') {
                strRet = strRet + "<!--" + childNode.content + "-->";
            }

            else if(childNode.nodeType=='TEXT') {
                var cont = trim(childNode.content,true,true);
                strRet = strRet + childNode.content;
            }

            else if (childNode.nodeType=='CDATA') {
                var cont = trim(childNode.content,true,true);
                strRet = strRet + "<![CDATA[" + cont + "]]>";
            }

            else {
                strRet = _displayElement(childNode, strRet);
            }
        } // end looping through the DOM elements
    } // end checking for domElements.children = null

    //ending tag
    strRet = strRet + "</" + tagName + ">";
    return strRet;
} // end function displayElement


function firstWhiteChar(str,pos) {
    /*******************************************************************************************************************
    function: firstWhiteChar

    Author: may106@psu.edu ?

    Description:
        return the position of the first whitespace character in str after position pos

    *********************************************************************************************************************/
    if (isEmpty(str)) {
        return -1;
    }

    while(pos < str.length) {
        if (whitespace.indexOf(str.charAt(pos))!=-1) {
            return pos;
        }
        else {
            pos++;
        }
    }
    return str.length;
} // end function firstWhiteChar


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









// =========================================================================
// =========================================================================
// =========================================================================
// XML DOC FUNCTIONS
// =========================================================================
// =========================================================================
// =========================================================================

function XMLDoc(source, errFn) {
    /*******************************************************************************************************************
    function:       XMLDoc

    Author: mike@idle.org

    Description:
        a constructor for an XML document
        source: the string containing the document
        errFn: the (optional) function used to log errors
    *********************************************************************************************************************/
    // stack for document construction

    this.topNode=null;

    // set up the properties and methods for this object

    this.errFn = errFn;          // user defined error functions

    this.createXMLNode = _XMLDoc_createXMLNode;
    this.error = _XMLDoc_error;
    this.getUnderlyingXMLText = _XMLDoc_getUnderlyingXMLText;
    this.handleNode = _XMLDoc_handleNode;
    this.hasErrors = false;      // were errors found during the parse?
    this.insertNodeAfter =  _XMLDoc_insertNodeAfter;
    this.insertNodeInto = _XMLDoc_insertNodeInto;
    this.loadXML = _XMLDoc_loadXML;
	this.parse = _XMLDoc_parse;
    this.parseAttribute = _XMLDoc_parseAttribute;
    this.parseDTD = _XMLDoc_parseDTD;
    this.parsePI = _XMLDoc_parsePI;
    this.parseTag = _XMLDoc_parseTag;
    this.removeNodeFromTree = _XMLDoc_removeNodeFromTree;
    this.replaceNodeContents = _XMLDoc_replaceNodeContents;
    this.selectNode = _XMLDoc_selectNode;
	this.selectNodeText = _XMLDoc_selectNodeText;
	this.source = source;        // the string source of the document

    // parse the document

    if (this.parse()) {
        // we've run out of markup - check the stack is now empty
        if (this.topNode!=null) {
            return this.error("expected close " + this.topNode.tagName);
        }
        else {
            return true;
        }
    }
} // end function XMLDoc


function _XMLDoc_createXMLNode(strXML) {
    /*******************************************************************************************************************
    function:   _XMLDoc_createXMLNode

    Author: djoham@yahoo.com

    Description:
        convienience function to create a new node that inherits
        the properties of the document object
    *********************************************************************************************************************/
    return new XMLDoc(strXML, this.errFn).docNode;

} // end function _XMLDoc_createXMLNode


function _XMLDoc_error(str) {
    /*******************************************************************************************************************
    function:       _XMLDoc_error

    Author: mike@idle.org

    Description:
        used to log an error in parsing or validating
    *********************************************************************************************************************/
    
	this.hasErrors=true;
    if(this.errFn){
        this.errFn("ERROR: " + str);
    }else if(this.onerror){
        this.onerror("ERROR: " + str); 
	}
    return 0;
	
} // end function _XMLDoc_error


function _XMLDoc_getTagNameParams(tag,obj){
    /*******************************************************************************************************************
    function:       _XMLDoc_getTagNameParams

    Author: xwisdom@yahoo.com

    Description:
        convienience function for the nodeSearch routines
    *********************************************************************************************************************/
	var elm=-1,e,s=tag.indexOf('[');
	var attr=[];
	if(s>=0){
		e=tag.indexOf(']');
		if(e>=0)elm=tag.substr(s+1,(e-s)-1);
		else obj.error('expected ] near '+tag);
		tag=tag.substr(0,s);
		if(isNaN(elm) && elm!='*'){
			attr=elm.substr(1,elm.length-1); // remove @
			attr=attr.split('=');
			if(attr[1]) { //remove "
				s=attr[1].indexOf('"');
				attr[1]=attr[1].substr(s+1,attr[1].length-1);
				e=attr[1].indexOf('"');
				if(e>=0) attr[1]=attr[1].substr(0,e);
				else obj.error('expected " near '+tag)
			};elm=-1;
		}else if(elm=='*') elm=-1;
	}
	return [tag,elm,attr[0],attr[1]]
} // end function _XMLDoc_getTagNameParams


function _XMLDoc_getUnderlyingXMLText() {
    /*******************************************************************************************************************
    function:       _XMLDoc_getUnderlyingXMLText

    Author: djoham@yahoo.com

    Description:
        kicks off the process that returns the XML text representation of the XML
        document inclusive of any changes made by the manipulation of the DOM
    *********************************************************************************************************************/
    var strRet = "";
    //for now, hardcode the xml version 1 information. When we handle Processing Instructions later, this
    //should be looked at again
    strRet = strRet + "<?xml version=\"1.0\"?>";
    if (this.docNode==null) {
        return;
    }

    strRet = _displayElement(this.docNode, strRet);
    return strRet;

} // end function _XMLDoc_getCurrentXMLText



function _XMLDoc_handleNode(current) {
    /*******************************************************************************************************************
    function:   _XMLDoc_handleNode

    Author: mike@idle.org

    Description:
        adds a markup element to the document
    *********************************************************************************************************************/

    if ((current.nodeType=='COMMENT') && (this.topNode!=null)) {
        return this.topNode.addElement(current);
    }
    else if ((current.nodeType=='TEXT') ||  (current.nodeType=='CDATA')) {

        // if the current node is a text node:


        // if the stack is empty, and this text node isn't just whitespace, we have
        // a problem (we're not in a document element)

        if(this.topNode==null) {
            if (trim(current.content,true,false)=="") {
                return true;
            }
            else {
                return this.error("expected document node, found: " + current);
            }
        }
        else {
            // otherwise, append this as child to the element at the top of the stack
            return this.topNode.addElement(current);
        }


    }
    else if ((current.nodeType=='OPEN') || (current.nodeType=='SINGLE')) {
        // if we find an element tag (open or empty)
        var success = false;

        // if the stack is empty, this node becomes the document node

        if(this.topNode==null) {
            this.docNode = current;
            current.parent = null;
            success = true;
        }
        else {
            // otherwise, append this as child to the element at the top of the stack
            success = this.topNode.addElement(current);
        }


        if (success && (current.nodeType!='SINGLE')) {
            this.topNode = current;
        }

        // rename it as an element node

        current.nodeType = "ELEMENT";

        return success;
    }

    // if it's a close tag, check the nesting

    else if (current.nodeType=='CLOSE') {

        // if the stack is empty, it's certainly an error

        if (this.topNode==null) {
            return this.error("close tag without open: " +  current.toString());
        }
        else {

            // otherwise, check that this node matches the one on the top of the stack

            if (current.tagName!=this.topNode.tagName) {
                return this.error("expected closing " + this.topNode.tagName + ", found closing " + current.tagName);
            }
            else {
                // if it does, pop the element off the top of the stack
                this.topNode = this.topNode.getParent();
            }
        }
    }
    return true;
} // end function _XMLDoc_handleNode



function _XMLDoc_insertNodeAfter (referenceNode, newNode) {
    /*******************************************************************************************************************
    function:   _XMLDoc_insertNodeAfter

    Author: djoham@yahoo.com

    Description:
        inserts a new XML node after the reference node;

        for example, if we insert the node <tag2>hello</tag2>
        after tag1 in the xml <rootnode><tag1></tag1></rootnode>
        we will end up with <rootnode><tag1></tag1><tag2>hello</tag2></rootnode>

        NOTE: the return value of this function is a new XMLDoc object!!!!

    *********************************************************************************************************************/

    var parentXMLText = this.getUnderlyingXMLText();
    var selectedNodeXMLText = referenceNode.getUnderlyingXMLText();
    var originalNodePos = parentXMLText.indexOf(selectedNodeXMLText) + selectedNodeXMLText.length;
    var newXML = parentXMLText.substr(0,originalNodePos);
    newXML += newNode.getUnderlyingXMLText();
    newXML += parentXMLText.substr(originalNodePos);
    var newDoc = new XMLDoc(newXML, this.errFn);
    return newDoc;

} // end function _XMLDoc_insertNodeAfter



function _XMLDoc_insertNodeInto (referenceNode, insertNode) {
    /*******************************************************************************************************************
    function:   _XMLDoc_insertNodeInto

    Author: mike@idle.org

    Description:
        inserts a new XML node into the reference node;

        for example, if we insert the node <tag2>hello</tag2>
        into tag1 in the xml <rootnode><tag1><tag3>foo</tag3></tag1></rootnode>
        we will end up with <rootnode><tag1><tag2>hello</tag2><tag3>foo</tag3></tag1></rootnode>

        NOTE: the return value of this function is a new XMLDoc object!!!!
    *********************************************************************************************************************/

    var parentXMLText = this.getUnderlyingXMLText();
    var selectedNodeXMLText = referenceNode.getUnderlyingXMLText();
    var endFirstTag = selectedNodeXMLText.indexOf(">") + 1;
    var originalNodePos = parentXMLText.indexOf(selectedNodeXMLText) + endFirstTag;
    var newXML = parentXMLText.substr(0,originalNodePos);
    newXML += insertNode.getUnderlyingXMLText();
    newXML += parentXMLText.substr(originalNodePos);
    var newDoc = new XMLDoc(newXML, this.errFn);
    return newDoc;


} // end function _XMLDoc_insertNodeInto

function _XMLDoc_loadXML(source){
    /*******************************************************************************************************************
    function:   _XMLDoc_insertNodeInto

    Author: xwisdom@yahoo.com

    Description:
    	allows an already existing XMLDoc object to load XML
	*********************************************************************************************************************/
	this.topNode=null;
	this.hasErrors = false;
	this.source=source;
    // parse the document
	return this.parse();

} // end function _XMLDoc_loadXML

function _XMLDoc_parse() {
    /*******************************************************************************************************************
    function:   _XMLDoc_parse

    Author: mike@idle.org

    Description:
        scans through the source for opening and closing tags
        checks that the tags open and close in a sensible order
    *********************************************************************************************************************/

    var pos = 0;

    // set up the arrays used to store positions of < and > characters

    err = false;

    while(!err) {
        var closing_tag_prefix = '';
        var chpos = this.source.indexOf('<',pos);
        var open_length = 1;

        var open;
        var close;

        if (chpos ==-1) {
            break;
        }

        open = chpos;

        // create a text node

        var str = this.source.substring(pos, open);

        if (str.length!=0) {
            err = !this.handleNode(new XMLNode('TEXT',this, str));
        }

        // handle Programming Instructions - they can't reliably be handled as tags

        if (chpos == this.source.indexOf("<?",pos)) {
            pos = this.parsePI(this.source, pos + 2);
            if (pos==0) {
                err=true;
            }
            continue;
        }

        // nobble the document type definition

        if (chpos == this.source.indexOf("<!DOCTYPE",pos)) {
            pos = this.parseDTD(this.source, chpos+ 9);
            if (pos==0) {
                err=true;
            }
            continue;
        }

        // if we found an open comment, we need to ignore angle brackets
        // until we find a close comment

        if(chpos == this.source.indexOf('<!--',pos)) {
            open_length = 4;
            closing_tag_prefix = '--';
        }

        // similarly, if we find an open CDATA, we need to ignore all angle
        // brackets until a close CDATA sequence is found

        if (chpos == this.source.indexOf('<![CDATA[',pos)) {
            open_length = 9;
            closing_tag_prefix = ']]';
        }

        // look for the closing sequence

        chpos = this.source.indexOf(closing_tag_prefix + '>',chpos);
        if (chpos ==-1) {
            return this.error("expected closing tag sequence: " + closing_tag_prefix + '>');
        }

        close = chpos + closing_tag_prefix.length;

        // create a tag node

        str = this.source.substring(open+1, close);

        var n = this.parseTag(str);
        if (n) {
            err = !this.handleNode(n);
        }

        pos = close +1;

    // and loop

    }
    return !err;

} // end function _XMLDoc_parse



function _XMLDoc_parseAttribute(src,pos,node) {
    /*******************************************************************************************************************
    function:   _XMLDoc_parseAttribute

    Author: mike@idle.org

    Description:
        parse an attribute out of a tag string

    *********************************************************************************************************************/

    // chew up the whitespace, if any

    while ((pos<src.length) && (whitespace.indexOf(src.charAt(pos))!=-1)) {
        pos++;
    }

    // if there's nothing else, we have no (more) attributes - just break out

    if (pos >= src.length) {
        return pos;
    }

    var p1 = pos;

    while ((pos < src.length) && (src.charAt(pos)!='=')) {
        pos++;
    }

    var msg = "attributes must have values";

    // parameters without values aren't allowed.

    if(pos >= src.length) {
        return this.error(msg);
    }

    // extract the parameter name

    var paramname = trim(src.substring(p1,pos++),false,true);

    // chew up whitespace

    while ((pos < src.length) && (whitespace.indexOf(src.charAt(pos))!=-1)) {
        pos++;
    }

    // throw an error if we've run out of string

    if (pos >= src.length) {
        return this.error(msg);
    }

    msg = "attribute values must be in quotes";

    // check for a quote mark to identify the beginning of the attribute value

    var quote = src.charAt(pos++);

    // throw an error if we didn't find one

    if (quotes.indexOf(quote)==-1) {
        return this.error(msg);
    }

    p1 = pos;

    while ((pos < src.length) && (src.charAt(pos)!=quote)) {
        pos++;
    }

    // throw an error if we found no closing quote

    if (pos >= src.length) {
        return this.error(msg);
    }

    // store the parameter

    if (!node.addAttribute(paramname,trim(src.substring(p1,pos++),false,true))) {
        return 0;
    }

    return pos;

}  //end function _XMLDoc_parseAttribute



function _XMLDoc_parseDTD(str,pos) {
    /*******************************************************************************************************************
    function:   _XMLDoc_parseDTD

    Author: mike@idle.org

    Description:
        parse a document type declaration

        NOTE: we're just going to discard the DTD

    *********************************************************************************************************************/
    // we're just going to discard the DTD

    var firstClose = str.indexOf('>',pos);

    if (firstClose==-1) {
        return this.error("error in DTD: expected '>'");
    }

    var closing_tag_prefix = '';

    var firstOpenSquare = str.indexOf('[',pos);

    if ((firstOpenSquare!=-1) && (firstOpenSquare < firstClose)) {
        closing_tag_prefix = ']';
    }

    while(true) {
        var closepos = str.indexOf(closing_tag_prefix + '>',pos);

        if (closepos ==-1) {
            return this.error("expected closing tag sequence: " + closing_tag_prefix + '>');
        }

        pos = closepos + closing_tag_prefix.length +1;

        if (str.substring(closepos-1,closepos+2) != ']]>') {
            break;
        }
    }
    return pos;

} // end function _XMLDoc_ParseDTD


function _XMLDoc_parsePI(str,pos) {
    /*******************************************************************************************************************
    function:   _XMLDoc_parsePI

    Author: mike@idle.org

    Description:
        parse a processing instruction

        NOTE: we just swallow them up at the moment

    *********************************************************************************************************************/
    // we just swallow them up

    var closepos = str.indexOf('?>',pos);
    return closepos + 2;

} // end function _XMLDoc_parsePI



function _XMLDoc_parseTag(src) {
    /*******************************************************************************************************************
    function:   _XMLDoc_parseTag

    Author: mike@idle.org

    Description:
        parse out a non-text element (incl. CDATA, comments)
        handles the parsing of attributes
    *********************************************************************************************************************/

    // if it's a comment, strip off the packaging, mark it a comment node
    // and return it

    if (src.indexOf('!--')==0) {
        return new XMLNode('COMMENT', this, src.substring(3,src.length-2));
    }

    // if it's CDATA, do similar

    if (src.indexOf('![CDATA[')==0) {
        return new XMLNode('CDATA', this, src.substring(8,src.length-2));
    }

    var n = new XMLNode();
    n.doc = this;


    if (src.charAt(0)=='/') {
        n.nodeType = 'CLOSE';
        src = src.substring(1);
    }
    else {
        // otherwise it's an open tag (possibly an empty element)
        n.nodeType = 'OPEN';
    }

    // if the last character is a /, check it's not a CLOSE tag

    if (src.charAt(src.length-1)=='/') {
        if (n.nodeType=='CLOSE') {
            return this.error("singleton close tag");
        }
        else {
            n.nodeType = 'SINGLE';
        }

        // strip off the last character

        src = src.substring(0,src.length-1);
    }

    // set up the properties as appropriate

    if (n.nodeType!='CLOSE') {
        n.attributes = new Array();
    }

    if (n.nodeType=='OPEN') {
        n.children = new Array();
    }

    // trim the whitespace off the remaining content

    src = trim(src,true,true);

    // chuck out an error if there's nothing left

    if (src.length==0) {
        return this.error("empty tag");
    }

    // scan forward until a space...

    var endOfName = firstWhiteChar(src,0);

    // if there is no space, this is just a name (e.g. (<tag>, <tag/> or </tag>

    if (endOfName==-1) {
        n.tagName = src;
        return n;
    }

    // otherwise, we should expect attributes - but store the tag name first

    n.tagName = src.substring(0,endOfName);

    // start from after the tag name

    var pos = endOfName;

    // now we loop:

    while(pos< src.length) {
        pos = this.parseAttribute(src, pos, n);
        if (this.pos==0) {
            return null;
        }

        // and loop

    }
    return n;

} // end function _XMLDoc_parseTag



function _XMLDoc_removeNodeFromTree(node) {
    /*******************************************************************************************************************
    function:   _XMLDoc_removeNodeFromTree

    Author: djoham@yahoo.com

    Description:
        removes the specified node from the tree

        NOTE: the return value of this function is a new XMLDoc object

    *********************************************************************************************************************/

    var parentXMLText = this.getUnderlyingXMLText();
    var selectedNodeXMLText = node.getUnderlyingXMLText();
    var originalNodePos = parentXMLText.indexOf(selectedNodeXMLText);
    var newXML = parentXMLText.substr(0,originalNodePos);
    newXML += parentXMLText.substr(originalNodePos + selectedNodeXMLText.length);
    var newDoc = new XMLDoc(newXML, this.errFn);
    return newDoc;
} // end function _XMLDoc_removeNodeFromTree



function _XMLDoc_replaceNodeContents(referenceNode, newContents) {
    /*******************************************************************************************************************
    function:   _XMLDoc_replaceNodeContents

    Author: djoham@yahoo.com

    Description:

        make a node object out of the newContents text
        coming in ----

        The "X" node will be thrown away and only the children
        used to replace the contents of the reference node

        NOTE: the return value of this function is a new XMLDoc object

    *********************************************************************************************************************/

    var newNode = this.createXMLNode("<X>" + newContents + "</X>");
    referenceNode.children = newNode.children;
    return this;
} // end function _XMLDoc_replaceNodeContents


function _XMLDoc_selectNode(tagpath){
    /*******************************************************************************************************************
    function:	_XMLDoc_selectNode

    Author:		xwisdom@yahoo.com

    Description:
    	selects a single node using the nodes tag path.
    	examples: /node1/node2  or  /taga/tag1[0]/tag2
    *********************************************************************************************************************/

	tagpath = trim(tagpath, true, true);
	
	var srcnode,node,tag,params,elm,rg;
	var tags,attrName,attrValue,ok;
	srcnode=node=((this.source)?this.docNode:this);
	if (!tagpath) return node;
	if(tagpath.indexOf('/')==0)tagpath=tagpath.substr(1);
	tagpath=tagpath.replace(tag,'');
	tags=tagpath.split('/');
	tag=tags[0];
	if(tag){
		if(tagpath.indexOf('/')==0)tagpath=tagpath.substr(1);
		tagpath=tagpath.replace(tag,'');
		params=_XMLDoc_getTagNameParams(tag,this);
		tag=params[0];elm=params[1];
		attrName=params[2];attrValue=params[3];
		node=(tag=='*')? node.getElements():node.getElements(tag);
		if (node.length) {
			if(elm<0){
				srcnode=node;var i=0;
				while(i<srcnode.length){
					if(attrName){
						if (srcnode[i].getAttribute(attrName)!=attrValue) ok=false;
						else ok=true;
					}else ok=true;
					if(ok){
						node=srcnode[i].selectNode(tagpath);
						if(node) return node;
					}
					i++;
				}
			}else if (elm<node.length){
				node=node[elm].selectNode(tagpath);
				if(node) return node;
			}
		}
	}
} // end function _XMLDoc_selectNode

function _XMLDoc_selectNodeText(tagpath){
    /*******************************************************************************************************************
    function:	_XMLDoc_selectNodeText

    Author:		xwisdom@yahoo.com

    Description:
    	selects a single node using the nodes tag path and then returns the node text.
    *********************************************************************************************************************/

    var node=this.selectNode(tagpath);
    if (node != null) {
		return node.getText();
	}
    else {
		return null;
	}
} // end function _XMLDoc_selectNodeText








// =========================================================================
// =========================================================================
// =========================================================================
// XML NODE FUNCTIONS
// =========================================================================
// =========================================================================
// =========================================================================


function XMLNode(nodeType,doc, str) {
    /*******************************************************************************************************************
    function: xmlNode

    Author: mike@idle.org

    Description:

        XMLNode() is a constructor for a node of XML (text, comment, cdata, tag, etc)

        nodeType = indicates the node type of the node
        doc == contains a reference to the XMLDoc object describing the document
        str == contains the text for the tag or text entity

    *********************************************************************************************************************/


    // the content of text (also CDATA and COMMENT) nodes
    if (nodeType=='TEXT' || nodeType=='CDATA' || nodeType=='COMMENT' ) {
        this.content = str;
    }
    else {
        this.content = null;
    }

    this.attributes = null; // an array of attributes (used as a hash table)
    this.children = null;   // an array (list) of the children of this node
    this.doc = doc;         // a reference to the document
    this.nodeType = nodeType;          // the type of the node
    this.parent = "";
    this.tagName = "";           // the name of the tag (if a tag node)

    // configure the methods
    this.addAttribute = _XMLNode_addAttribute;
    this.addElement = _XMLNode_addElement;
    this.getAttribute = _XMLNode_getAttribute;
    this.getAttributeNames = _XMLNode_getAttributeNames;
    this.getElementById = _XMLNode_getElementById;
    this.getElements = _XMLNode_getElements;
    this.getText = _XMLNode_getText;
    this.getParent = _XMLNode_getParent;
    this.getUnderlyingXMLText = _XMLNode_getUnderlyingXMLText;
    this.removeAttribute = _XMLNode_removeAttribute;
    this.selectNode = _XMLDoc_selectNode;
	this.selectNodeText = _XMLDoc_selectNodeText;
    this.toString = _XMLNode_toString;

}  // end function XMLNode


function _XMLNode_addAttribute(attributeName,attributeValue) {
    /*******************************************************************************************************************
    function: _XMLNode_addAttribute

    Author: mike@idle.org

    Description:
        add an attribute to a node
    *********************************************************************************************************************/

    //if the name is found, the old value is overwritten by the new value
    this.attributes['_' + attributeName] = attributeValue;
    return true;

} // end function _XMLNode_addAttribute



function _XMLNode_addElement(node) {
    /*******************************************************************************************************************
    function: _XMLNode_addElement

    Author: mike@idle.org

    Description:
        add an element child to a node
    *********************************************************************************************************************/
    node.parent = this;
    this.children[this.children.length] = node;
    return true;

} // end function _XMLNode_addElement


function _XMLNode_getAttribute(name) {
    /*******************************************************************************************************************
    function: _XMLNode_getAttribute

    Author: mike@idle.org

    Description:
        get the value of a named attribute from an element node

        NOTE: we prefix with "_" because of the weird 'length' meta-property

    *********************************************************************************************************************/
    if (this.attributes == null) {
        return null;
    }
    return this.attributes['_' + name];
} // end function _XMLNode_getAttribute



function _XMLNode_getAttributeNames() {
    /*******************************************************************************************************************
    function: _XMLNode_getAttributeNames

    Author: mike@idle.org

    Description:
        get a list of attribute names for the node

        NOTE: we prefix with "_" because of the weird 'length' meta-property

        NOTE: Version 1.0 of getAttributeNames breaks backwards compatibility. Previous to 1.0
        getAttributeNames would return null if there were no attributes. 1.0 now returns an
        array of length 0.


    *********************************************************************************************************************/
    if (this.attributes == null) {
        var ret = new Array();
        return ret;
    }

    var attlist = new Array();

    for (var a in this.attributes) {
        attlist[attlist.length] = a.substring(1);
    }
    return attlist;
} // end function _XMLNode_getAttributeNames

function _XMLNode_getElementById(id) {

    /***********************************************************************************
    Function: getElementById

    Author: djoham@yahoo.com

    Description:
        Brute force searches through the XML DOM tree
        to find the node with the unique ID passed in

    ************************************************************************************/
    var node = this;
    var ret;

    //alert("tag name=" + node.tagName);
    //alert("id=" + node.getAttribute("id"));
    if (node.getAttribute("id") == id) {
        return node;
    }
    else{
        var elements = node.getElements();
        //alert("length=" + rugrats.length);
        var intLoop = 0;
        //do NOT use a for loop here. For some reason
        //it kills some browsers!!!
        while (intLoop < elements.length) {
            //alert("intLoop=" + intLoop);
            var element = elements[intLoop];
            //alert("recursion");
            ret = element.getElementById(id);
            if (ret != null) {
                //alert("breaking");
                break;
            }
            intLoop++;
        }
    }
    return ret;
} // end function _XMLNode_getElementById


function _XMLNode_getElements(byName) {
    /*******************************************************************************************************************
    function:   _XMLNode_getElements

    Author: mike@idle.org

    Description:
        get an array of element children of a node
        with an optional filter by name

        NOTE: Version 1.0 of getElements breaks backwards compatibility. Previous to 1.0
        getElements would return null if there were no attributes. 1.0 now returns an
        array of length 0.


    *********************************************************************************************************************/
    if (this.children==null) {
        var ret = new Array();
        return ret;
    }

    var elements = new Array();
    for (var i=0; i<this.children.length; i++) {
        if ((this.children[i].nodeType=='ELEMENT') && ((byName==null) || (this.children[i].tagName == byName))) {
            elements[elements.length] = this.children[i];
        }
    }
    return elements;
} // end function _XMLNode_getElements



function _XMLNode_getText() {
    /*******************************************************************************************************************
    function:       _XMLNode_getText

    Author: mike@idle.org

    Description:
        a method to get the text of a given node (recursively, if it's an element)
    *********************************************************************************************************************/

    if (this.nodeType=='ELEMENT') {
        if (this.children==null) {
            return null;
        }
        var str = "";
        for (var i=0; i < this.children.length; i++) {
            var t = this.children[i].getText();
            str +=  (t == null ? "" : t);
        }
        return str;
    }
    else if (this.nodeType=='TEXT') {
        return convertEscapes(this.content);
    }
    else {
        return this.content;
    }
} // end function _XMLNode_getText



function _XMLNode_getParent() {
    /*******************************************************************************************************************
    function:       _XMLNode_getParent

    Author: mike@idle.org

    Description:
        get the parent of this node
    *********************************************************************************************************************/
    return this.parent;

} // end function _XMLNode_getParent


function _XMLNode_getUnderlyingXMLText() {
    /*******************************************************************************************************************
    function:       David Joham

    Author: djoham@yahoo.com

    Description:
        returns the underlying XML text for the node
        by calling the _displayElement function
    *********************************************************************************************************************/

    var strRet = "";
    strRet = _displayElement(this, strRet);
    return strRet;

} // end function _XMLNode_getUnderlyingXMLText



function _XMLNode_removeAttribute(attributeName) {
    /*******************************************************************************************************************
    function:       _XMLNode_removeAttribute

    Author: djoham@yahoo.com

    Description:
        remove an attribute from a node
    *********************************************************************************************************************/
    if(attributeName == null) {
        return this.doc.error("You must pass an attribute name into the removeAttribute function");
    }

    //now remove the attribute from the list.
    // I want to keep the logic for adding attribtues in one place. I'm
    // going to get a temp array of attributes and values here and then
    // use the addAttribute function to re-add the attributes
    var attributes = this.getAttributeNames();
    var intCount = attributes.length;
    var tmpAttributeValues = new Array();
    for ( intLoop = 0; intLoop < intCount; intLoop++) {
        tmpAttributeValues[intLoop] = this.getAttribute(attributes[intLoop]);
    }

    // now blow away the old attribute list
    this.attributes = new Array();

    //now add the attributes back to the array - leaving out the one we're removing
    for (intLoop = 0; intLoop < intCount; intLoop++) {
        if ( attributes[intLoop] != attributeName) {
            this.addAttribute(attributes[intLoop], tmpAttributeValues[intLoop]);
        }
    }

return true;

} // end function _XMLNode_removeAttribute



function _XMLNode_toString() {
    /*******************************************************************************************************************
    function:       _XMLNode_toString

    Author: mike@idle.org

    Description:
        produces a diagnostic string description of a node
    *********************************************************************************************************************/
    return "" + this.nodeType + ":" + (this.nodeType=='TEXT' || this.nodeType=='CDATA' || this.nodeType=='COMMENT' ? this.content : this.tagName);

} // end function _XMLNode_toString


























// =========================================================================
//
// xmlw3cdom.js - a W3C compliant DOM parser for XML for <SCRIPT>
//
// version 3.1
//
// =========================================================================
//
// Copyright (C) 2002, 2003, 2004 Jon van Noort (jon@webarcana.com.au), 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 xmljs.sourceforge.net
//
// Contains text (used within comments to methods) from the
//  XML Path Language (XPath) Version 1.0 W3C Recommendation
//  Copyright � 16 November 1999 World Wide Web Consortium,
//  (Massachusetts Institute of Technology,
//  European Research Consortium for Informatics and Mathematics, Keio University).
//  All Rights Reserved.
//  (see: http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/)

/**
 * @function addClass - add new className to classCollection
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  classCollectionStr : string - list of existing class names
 *   (separated and top and tailed with '|'s)
 * @param  newClass           : string - new class name to add
 *
 * @return : string - the new classCollection, with new className appended,
 *   (separated and top and tailed with '|'s)
 */
function addClass(classCollectionStr, newClass) {
  if (classCollectionStr) {
    if (classCollectionStr.indexOf("|"+ newClass +"|") < 0) {
      classCollectionStr += newClass + "|";
    }
  }
  else {
    classCollectionStr = "|"+ newClass + "|";
  }

  return classCollectionStr;
}

/**
 * @class  DOMException - raised when an operation is impossible to perform
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  code : int - the exception code (one of the DOMException constants)
 */
DOMException = function(code) {
  this._class = addClass(this._class, "DOMException");

  this.code = code;
};

// DOMException constants
// Introduced in DOM Level 1:
DOMException.INDEX_SIZE_ERR                 = 1;
DOMException.DOMSTRING_SIZE_ERR             = 2;
DOMException.HIERARCHY_REQUEST_ERR          = 3;
DOMException.WRONG_DOCUMENT_ERR             = 4;
DOMException.INVALID_CHARACTER_ERR          = 5;
DOMException.NO_DATA_ALLOWED_ERR            = 6;
DOMException.NO_MODIFICATION_ALLOWED_ERR    = 7;
DOMException.NOT_FOUND_ERR                  = 8;
DOMException.NOT_SUPPORTED_ERR              = 9;
DOMException.INUSE_ATTRIBUTE_ERR            = 10;

// Introduced in DOM Level 2:
DOMException.INVALID_STATE_ERR              = 11;
DOMException.SYNTAX_ERR                     = 12;
DOMException.INVALID_MODIFICATION_ERR       = 13;
DOMException.NAMESPACE_ERR                  = 14;
DOMException.INVALID_ACCESS_ERR             = 15;


/**
 * @class  DOMImplementation - provides a number of methods for performing operations
 *   that are independent of any particular instance of the document object model.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 */
DOMImplementation = function() {
  this._class = addClass(this._class, "DOMImplementation");
  this._p = null;

  this.preserveWhiteSpace = false;  // by default, ignore whitespace
  this.namespaceAware = true;       // by default, handle namespaces
  this.errorChecking  = true;       // by default, test for exceptions
};


/**
 * @method DOMImplementation.escapeString - escape special characters
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  str : string - The string to be escaped
 *
 * @return : string - The escaped string
 */
DOMImplementation.prototype.escapeString = function DOMNode__escapeString(str) {

  //the sax processor already has this function. Just wrap it
  return __escapeString(str);
};

/**
 * @method DOMImplementation.unescapeString - unescape special characters
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  str : string - The string to be unescaped
 *
 * @return : string - The unescaped string
 */
DOMImplementation.prototype.unescapeString = function DOMNode__unescapeString(str) {

  //the sax processor already has this function. Just wrap it
  return __unescapeString(str);
};

/**
 * @method DOMImplementation.hasFeature - Test if the DOM implementation implements a specific feature
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  feature : string - The package name of the feature to test. the legal only values are "XML" and "CORE" (case-insensitive).
 * @param  version : string - This is the version number of the package name to test. In Level 1, this is the string "1.0".
 *
 * @return : boolean
 */
DOMImplementation.prototype.hasFeature = function DOMImplementation_hasFeature(feature, version) {

  var ret = false;
  if (feature.toLowerCase() == "xml") {
    ret = (!version || (version == "1.0") || (version == "2.0"));
  }
  else if (feature.toLowerCase() == "core") {
    ret = (!version || (version == "2.0"));
  }

  return ret;
};

/**
 * @method DOMImplementation.loadXML - parse XML string
 *
 * @author Jon van Noort (jon@webarcana.com.au), David Joham (djoham@yahoo.com) and Scott Severtson
 *
 * @param  xmlStr : string - the XML string
 *
 * @return : DOMDocument
 */
DOMImplementation.prototype.loadXML = function DOMImplementation_loadXML(xmlStr) {
  // create SAX Parser
  var parser;

  try {
    parser = new XMLP(xmlStr);
  }
  catch (e) {
    alert("Error Creating the SAX Parser. Did you include xmlsax.js or tinyxmlsax.js in your web page?\nThe SAX parser is needed to populate XML for <SCRIPT>'s W3C DOM Parser with data.");
  }

  // create DOM Document
  var doc = new DOMDocument(this);

  // populate Document with Parsed Nodes
  this._parseLoop(doc, parser);

  // set parseComplete flag, (Some validation Rules are relaxed if this is false)
  doc._parseComplete = true;

  return doc;
};


/**
 * @method DOMImplementation.translateErrCode - convert DOMException Code
 *   to human readable error message;
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  code : int - the DOMException code
 *
 * @return : string - the human readbale error message
 */
DOMImplementation.prototype.translateErrCode = function DOMImplementation_translateErrCode(code) {
  var msg = "";

  switch (code) {
    case DOMException.INDEX_SIZE_ERR :                // 1
       msg = "INDEX_SIZE_ERR: Index out of bounds";
       break;

    case DOMException.DOMSTRING_SIZE_ERR :            // 2
       msg = "DOMSTRING_SIZE_ERR: The resulting string is too long to fit in a DOMString";
       break;

    case DOMException.HIERARCHY_REQUEST_ERR :         // 3
       msg = "HIERARCHY_REQUEST_ERR: The Node can not be inserted at this location";
       break;

    case DOMException.WRONG_DOCUMENT_ERR :            // 4
       msg = "WRONG_DOCUMENT_ERR: The source and the destination Documents are not the same";
       break;

    case DOMException.INVALID_CHARACTER_ERR :         // 5
       msg = "INVALID_CHARACTER_ERR: The string contains an invalid character";
       break;

    case DOMException.NO_DATA_ALLOWED_ERR :           // 6
       msg = "NO_DATA_ALLOWED_ERR: This Node / NodeList does not support data";
       break;

    case DOMException.NO_MODIFICATION_ALLOWED_ERR :   // 7
       msg = "NO_MODIFICATION_ALLOWED_ERR: This object cannot be modified";
       break;

    case DOMException.NOT_FOUND_ERR :                 // 8
       msg = "NOT_FOUND_ERR: The item cannot be found";
       break;

    case DOMException.NOT_SUPPORTED_ERR :             // 9
       msg = "NOT_SUPPORTED_ERR: This implementation does not support function";
       break;

    case DOMException.INUSE_ATTRIBUTE_ERR :           // 10
       msg = "INUSE_ATTRIBUTE_ERR: The Attribute has already been assigned to another Element";
       break;

// Introduced in DOM Level 2:
    case DOMException.INVALID_STATE_ERR :             // 11
       msg = "INVALID_STATE_ERR: The object is no longer usable";
       break;

    case DOMException.SYNTAX_ERR :                    // 12
       msg = "SYNTAX_ERR: Syntax error";
       break;

    case DOMException.INVALID_MODIFICATION_ERR :      // 13
       msg = "INVALID_MODIFICATION_ERR: Cannot change the type of the object";
       break;

    case DOMException.NAMESPACE_ERR :                 // 14
       msg = "NAMESPACE_ERR: The namespace declaration is incorrect";
       break;

    case DOMException.INVALID_ACCESS_ERR :            // 15
       msg = "INVALID_ACCESS_ERR: The object does not support this function";
       break;

    default :
       msg = "UNKNOWN: Unknown Exception Code ("+ code +")";
  }

  return msg;
}

/**
 * @method DOMImplementation._parseLoop - process SAX events
 *
 * @author Jon van Noort (jon@webarcana.com.au), David Joham (djoham@yahoo.com) and Scott Severtson
 *
 * @param  doc : DOMDocument - the Document to contain the parsed XML string
 * @param  p   : XMLP        - the SAX Parser
 *
 * @return : DOMDocument
 */
DOMImplementation.prototype._parseLoop = function DOMImplementation__parseLoop(doc, p) {
  var iEvt, iNode, iAttr, strName;
  iNodeParent = doc;

  var el_close_count = 0;

  var entitiesList = new Array();
  var textNodesList = new Array();

  // if namespaceAware, add default namespace
  if (this.namespaceAware) {
    var iNS = doc.createNamespace(""); // add the default-default namespace
    iNS.setValue("http://www.w3.org/2000/xmlns/");
    doc._namespaces.setNamedItem(iNS);
  }

  // loop until SAX parser stops emitting events
  while(true) {
    // get next event
    iEvt = p.next();

    if (iEvt == XMLP._ELM_B) {                      // Begin-Element Event
      var pName = p.getName();                      // get the Element name
      pName = trim(pName, true, true);              // strip spaces from Element name

      if (!this.namespaceAware) {
        iNode = doc.createElement(p.getName());     // create the Element

        // add attributes to Element
        for(var i = 0; i < p.getAttributeCount(); i++) {
          strName = p.getAttributeName(i);          // get Attribute name
          iAttr = iNode.getAttributeNode(strName);  // if Attribute exists, use it

          if(!iAttr) {
            iAttr = doc.createAttribute(strName);   // otherwise create it
          }

          iAttr.setValue(p.getAttributeValue(i));   // set Attribute value
          iNode.setAttributeNode(iAttr);            // attach Attribute to Element
        }
      }
      else {  // Namespace Aware
        // create element (with empty namespaceURI,
        //  resolve after namespace 'attributes' have been parsed)
        iNode = doc.createElementNS("", p.getName());

        // duplicate ParentNode's Namespace definitions
        iNode._namespaces = iNodeParent._namespaces._cloneNodes(iNode);

        // add attributes to Element
        for(var i = 0; i < p.getAttributeCount(); i++) {
          strName = p.getAttributeName(i);          // get Attribute name

          // if attribute is a namespace declaration
          if (this._isNamespaceDeclaration(strName)) {
            // parse Namespace Declaration
            var namespaceDec = this._parseNSName(strName);

            if (strName != "xmlns") {
              iNS = doc.createNamespace(strName);   // define namespace
            }
            else {
              iNS = doc.createNamespace("");        // redefine default namespace
            }
            iNS.setValue(p.getAttributeValue(i));   // set value = namespaceURI

            iNode._namespaces.setNamedItem(iNS);    // attach namespace to namespace collection
          }
          else {  // otherwise, it is a normal attribute
            iAttr = iNode.getAttributeNode(strName);        // if Attribute exists, use it

            if(!iAttr) {
              iAttr = doc.createAttributeNS("", strName);   // otherwise create it
            }

            iAttr.setValue(p.getAttributeValue(i));         // set Attribute value
            iNode.setAttributeNodeNS(iAttr);                // attach Attribute to Element

            if (this._isIdDeclaration(strName)) {
              iNode.id = p.getAttributeValue(i);    // cache ID for getElementById()
            }
          }
        }

        // resolve namespaceURIs for this Element
        if (iNode._namespaces.getNamedItem(iNode.prefix)) {
          iNode.namespaceURI = iNode._namespaces.getNamedItem(iNode.prefix).value;
        }

        //  for this Element's attributes
        for (var i = 0; i < iNode.attributes.length; i++) {
          if (iNode.attributes.item(i).prefix != "") {  // attributes do not have a default namespace
            if (iNode._namespaces.getNamedItem(iNode.attributes.item(i).prefix)) {
              iNode.attributes.item(i).namespaceURI = iNode._namespaces.getNamedItem(iNode.attributes.item(i).prefix).value;
            }
          }
        }
      }

      // if this is the Root Element
      if (iNodeParent.nodeType == DOMNode.DOCUMENT_NODE) {
        iNodeParent.documentElement = iNode;        // register this Element as the Document.documentElement
      }

      iNodeParent.appendChild(iNode);               // attach Element to parentNode
      iNodeParent = iNode;                          // descend one level of the DOM Tree
    }

    else if(iEvt == XMLP._ELM_E) {                  // End-Element Event
      iNodeParent = iNodeParent.parentNode;         // ascend one level of the DOM Tree
    }

    else if(iEvt == XMLP._ELM_EMP) {                // Empty Element Event
      pName = p.getName();                          // get the Element name
      pName = trim(pName, true, true);              // strip spaces from Element name

      if (!this.namespaceAware) {
        iNode = doc.createElement(pName);           // create the Element

        // add attributes to Element
        for(var i = 0; i < p.getAttributeCount(); i++) {
          strName = p.getAttributeName(i);          // get Attribute name
          iAttr = iNode.getAttributeNode(strName);  // if Attribute exists, use it

          if(!iAttr) {
            iAttr = doc.createAttribute(strName);   // otherwise create it
          }

          iAttr.setValue(p.getAttributeValue(i));   // set Attribute value
          iNode.setAttributeNode(iAttr);            // attach Attribute to Element
        }
      }
      else {  // Namespace Aware
        // create element (with empty namespaceURI,
        //  resolve after namespace 'attributes' have been parsed)
        iNode = doc.createElementNS("", p.getName());

        // duplicate ParentNode's Namespace definitions
        iNode._namespaces = iNodeParent._namespaces._cloneNodes(iNode);

        // add attributes to Element
        for(var i = 0; i < p.getAttributeCount(); i++) {
          strName = p.getAttributeName(i);          // get Attribute name

          // if attribute is a namespace declaration
          if (this._isNamespaceDeclaration(strName)) {
            // parse Namespace Declaration
            var namespaceDec = this._parseNSName(strName);

            if (strName != "xmlns") {
              iNS = doc.createNamespace(strName);   // define namespace
            }
            else {
              iNS = doc.createNamespace("");        // redefine default namespace
            }
            iNS.setValue(p.getAttributeValue(i));   // set value = namespaceURI

            iNode._namespaces.setNamedItem(iNS);    // attach namespace to namespace collection
          }
          else {  // otherwise, it is a normal attribute
            iAttr = iNode.getAttributeNode(strName);        // if Attribute exists, use it

            if(!iAttr) {
              iAttr = doc.createAttributeNS("", strName);   // otherwise create it
            }

            iAttr.setValue(p.getAttributeValue(i));         // set Attribute value
            iNode.setAttributeNodeNS(iAttr);                // attach Attribute to Element

            if (this._isIdDeclaration(strName)) {
              iNode.id = p.getAttributeValue(i);    // cache ID for getElementById()
            }
          }
        }

        // resolve namespaceURIs for this Element
        if (iNode._namespaces.getNamedItem(iNode.prefix)) {
          iNode.namespaceURI = iNode._namespaces.getNamedItem(iNode.prefix).value;
        }

        //  for this Element's attributes
        for (var i = 0; i < iNode.attributes.length; i++) {
          if (iNode.attributes.item(i).prefix != "") {  // attributes do not have a default namespace
            if (iNode._namespaces.getNamedItem(iNode.attributes.item(i).prefix)) {
              iNode.attributes.item(i).namespaceURI = iNode._namespaces.getNamedItem(iNode.attributes.item(i).prefix).value;
            }
          }
        }
      }

      // if this is the Root Element
      if (iNodeParent.nodeType == DOMNode.DOCUMENT_NODE) {
        iNodeParent.documentElement = iNode;        // register this Element as the Document.documentElement
      }

      iNodeParent.appendChild(iNode);               // attach Element to parentNode
    }
    else if(iEvt == XMLP._TEXT || iEvt == XMLP._ENTITY) {                   // TextNode and entity Events
      // get Text content
      var pContent = p.getContent().substring(p.getContentBegin(), p.getContentEnd());
      
	  if (!this.preserveWhiteSpace ) {
		if (trim(pContent, true, true) == "") {
			pContent = ""; //this will cause us not to create the text node below
		}
	  }
	  
      if (pContent.length > 0) {                    // ignore empty TextNodes
        var textNode = doc.createTextNode(pContent);
        iNodeParent.appendChild(textNode); // attach TextNode to parentNode

        //the sax parser breaks up text nodes when it finds an entity. For
        //example hello&lt;there will fire a text, an entity and another text
        //this sucks for the dom parser because it looks to us in this logic
        //as three text nodes. I fix this by keeping track of the entity nodes
        //and when we're done parsing, calling normalize on their parent to
        //turn the multiple text nodes into one, which is what DOM users expect
        //the code to do this is at the bottom of this function
        if (iEvt == XMLP._ENTITY) {
            entitiesList[entitiesList.length] = textNode;
        }
		else {
			//I can't properly decide how to handle preserve whitespace
			//until the siblings of the text node are built due to 
			//the entitiy handling described above. I don't know that this
			//will be all of the text node or not, so trimming is not appropriate
			//at this time. Keep a list of all the text nodes for now
			//and we'll process the preserve whitespace stuff at a later time.
			textNodesList[textNodesList.length] = textNode;
		}
      }
    }
    else if(iEvt == XMLP._PI) {                     // ProcessingInstruction Event
      // attach ProcessingInstruction to parentNode
      iNodeParent.appendChild(doc.createProcessingInstruction(p.getName(), p.getContent().substring(p.getContentBegin(), p.getContentEnd())));
    }
    else if(iEvt == XMLP._CDATA) {                  // CDATA Event
      // get CDATA data
      pContent = p.getContent().substring(p.getContentBegin(), p.getContentEnd());

      if (!this.preserveWhiteSpace) {
        pContent = trim(pContent, true, true);      // trim whitespace
        pContent.replace(/ +/g, ' ');               // collapse multiple spaces to 1 space
      }

      if (pContent.length > 0) {                    // ignore empty CDATANodes
        iNodeParent.appendChild(doc.createCDATASection(pContent)); // attach CDATA to parentNode
      }
    }
    else if(iEvt == XMLP._COMMENT) {                // Comment Event
      // get COMMENT data
      var pContent = p.getContent().substring(p.getContentBegin(), p.getContentEnd());

      if (!this.preserveWhiteSpace) {
        pContent = trim(pContent, true, true);      // trim whitespace
        pContent.replace(/ +/g, ' ');               // collapse multiple spaces to 1 space
      }

      if (pContent.length > 0) {                    // ignore empty CommentNodes
        iNodeParent.appendChild(doc.createComment(pContent));  // attach Comment to parentNode
      }
    }
    else if(iEvt == XMLP._DTD) {                    // ignore DTD events
    }
    else if(iEvt == XMLP._ERROR) {
      throw(new DOMException(DOMException.SYNTAX_ERR));
      // alert("Fatal Error: " + p.getContent() + "\nLine: " + p.getLineNumber() + "\nColumn: " + p.getColumnNumber() + "\n");
      // break;
    }
    else if(iEvt == XMLP._NONE) {                   // no more events
      if (iNodeParent == doc) {                     // confirm that we have recursed back up to root
        break;
      }
      else {
        throw(new DOMException(DOMException.SYNTAX_ERR));  // one or more Tags were not closed properly
      }
    }
  }

  //normalize any entities in the DOM to a single textNode
  var intCount = entitiesList.length;
  for (intLoop = 0; intLoop < intCount; intLoop++) {
      var entity = entitiesList[intLoop];
      //its possible (if for example two entities were in the
      //same domnode, that the normalize on the first entitiy
      //will remove the parent for the second. Only do normalize
      //if I can find a parent node
      var parentNode = entity.getParentNode();
      if (parentNode) {
          parentNode.normalize();
		  
		  //now do whitespace (if necessary)
		  //it was not done for text nodes that have entities
		  if(!this.preserveWhiteSpace) {
		  		var children = parentNode.getChildNodes();
				var intCount2 = children.getLength();
				for ( intLoop2 = 0; intLoop2 < intCount2; intLoop2++) {
					var child = children.item(intLoop2);
					if (child.getNodeType() == DOMNode.TEXT_NODE) {
						var childData = child.getData();
						childData = trim(childData, true, true);
						childData.replace(/ +/g, ' ');
						child.setData(childData);
					}
				}
		  }
      }
  }
  
  //do the preserve whitespace processing on the rest of the text nodes
  //It's possible (due to the processing above) that the node will have been
  //removed from the tree. Only do whitespace checking if parentNode is not null.
  //This may duplicate the whitespace processing for some nodes that had entities in them
  //but there's no way around that
  if (!this.preserveWhiteSpace) {
  	var intCount = textNodesList.length;
	for (intLoop = 0; intLoop < intCount; intLoop++) {
		var node = textNodesList[intLoop];
		if (node.getParentNode() != null) {
			var nodeData = node.getData();
			nodeData = trim(nodeData, true, true);
			nodeData.replace(/ +/g, ' ');
			node.setData(nodeData);
		}
	}
  
  }
};

/**
 * @method DOMImplementation._isNamespaceDeclaration - Return true, if attributeName is a namespace declaration
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  attributeName : string - the attribute name
 *
 * @return : boolean
 */
DOMImplementation.prototype._isNamespaceDeclaration = function DOMImplementation__isNamespaceDeclaration(attributeName) {
  // test if attributeName is 'xmlns'
  return (attributeName.indexOf('xmlns') > -1);
}

/**
 * @method DOMImplementation._isIdDeclaration - Return true, if attributeName is an id declaration
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  attributeName : string - the attribute name
 *
 * @return : boolean
 */
DOMImplementation.prototype._isIdDeclaration = function DOMImplementation__isIdDeclaration(attributeName) {
  // test if attributeName is 'id' (case insensitive)
  return (attributeName.toLowerCase() == 'id');
}

/**
 * @method DOMImplementation._isValidName - Return true,
 *   if name contains no invalid characters
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  name : string - the candidate name
 *
 * @return : boolean
 */
DOMImplementation.prototype._isValidName = function DOMImplementation__isValidName(name) {
  // test if name contains only valid characters
  return name.match(re_validName);
}
re_validName = /^[a-zA-Z_:][a-zA-Z0-9\.\-_:]*$/;

/**
 * @method DOMImplementation._isValidString - Return true, if string does not contain any illegal chars
 *  All of the characters 0 through 31 and character 127 are nonprinting control characters.
 *  With the exception of characters 09, 10, and 13, (Ox09, Ox0A, and Ox0D)
 *  Note: different from _isValidName in that ValidStrings may contain spaces
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  name : string - the candidate string
 *
 * @return : boolean
 */
DOMImplementation.prototype._isValidString = function DOMImplementation__isValidString(name) {
  // test that string does not contains invalid characters
  return (name.search(re_invalidStringChars) < 0);
}
re_invalidStringChars = /\x01|\x02|\x03|\x04|\x05|\x06|\x07|\x08|\x0B|\x0C|\x0E|\x0F|\x10|\x11|\x12|\x13|\x14|\x15|\x16|\x17|\x18|\x19|\x1A|\x1B|\x1C|\x1D|\x1E|\x1F|\x7F/

/**
 * @method DOMImplementation._parseNSName - parse the namespace name.
 *  if there is no colon, the
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  qualifiedName : string - The qualified name
 *
 * @return : NSName - [
 *                     .prefix        : string - The prefix part of the qname
 *                     .namespaceName : string - The namespaceURI part of the qname
 *                    ]
 */
DOMImplementation.prototype._parseNSName = function DOMImplementation__parseNSName(qualifiedName) {
  var resultNSName = new Object();

  resultNSName.prefix          = qualifiedName;  // unless the qname has a namespaceName, the prefix is the entire String
  resultNSName.namespaceName   = "";

  // split on ':'
  delimPos = qualifiedName.indexOf(':');

  if (delimPos > -1) {
    // get prefix
    resultNSName.prefix        = qualifiedName.substring(0, delimPos);

    // get namespaceName
    resultNSName.namespaceName = qualifiedName.substring(delimPos +1, qualifiedName.length);
  }

  return resultNSName;
}

/**
 * @method DOMImplementation._parseQName - parse the qualified name
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  qualifiedName : string - The qualified name
 *
 * @return : QName
 */
DOMImplementation.prototype._parseQName = function DOMImplementation__parseQName(qualifiedName) {
  var resultQName = new Object();

  resultQName.localName = qualifiedName;  // unless the qname has a prefix, the local name is the entire String
  resultQName.prefix    = "";

  // split on ':'
  delimPos = qualifiedName.indexOf(':');

  if (delimPos > -1) {
    // get prefix
    resultQName.prefix    = qualifiedName.substring(0, delimPos);

    // get localName
    resultQName.localName = qualifiedName.substring(delimPos +1, qualifiedName.length);
  }

  return resultQName;
}

/**
 * @class  DOMNodeList - provides the abstraction of an ordered collection of nodes
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  ownerDocument : DOMDocument - the ownerDocument
 * @param  parentNode    : DOMNode - the node that the DOMNodeList is attached to (or null)
 */
DOMNodeList = function(ownerDocument, parentNode) {
  this._class = addClass(this._class, "DOMNodeList");
  this._nodes = new Array();

  this.length = 0;
  this.parentNode = parentNode;
  this.ownerDocument = ownerDocument;

  this._readonly = false;
};

/**
 * @method DOMNodeList.getLength - Java style gettor for .length
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : int
 */
DOMNodeList.prototype.getLength = function DOMNodeList_getLength() {
  return this.length;
};

/**
 * @method DOMNodeList.item - Returns the indexth item in the collection.
 *   If index is greater than or equal to the number of nodes in the list, this returns null.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  index : int - Index into the collection.
 *
 * @return : DOMNode - The node at the indexth position in the NodeList, or null if that is not a valid index
 */
DOMNodeList.prototype.item = function DOMNodeList_item(index) {
  var ret = null;

  if ((index >= 0) && (index < this._nodes.length)) { // bounds check
    ret = this._nodes[index];                    // return selected Node
  }

  return ret;                                    // if the index is out of bounds, default value null is returned
};

/**
 * @method DOMNodeList._findItemIndex - find the item index of the node with the specified internal id
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  id : int - unique internal id
 *
 * @return : int
 */
DOMNodeList.prototype._findItemIndex = function DOMNodeList__findItemIndex(id) {
  var ret = -1;

  // test that id is valid
  if (id > -1) {
    for (var i=0; i<this._nodes.length; i++) {
      // compare id to each node's _id
      if (this._nodes[i]._id == id) {            // found it!
        ret = i;
        break;
      }
    }
  }

  return ret;                                    // if node is not found, default value -1 is returned
};

/**
 * @method DOMNodeList._insertBefore - insert the specified Node into the NodeList before the specified index
 *   Used by DOMNode.insertBefore(). Note: DOMNode.insertBefore() is responsible for Node Pointer surgery
 *   DOMNodeList._insertBefore() simply modifies the internal data structure (Array).
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  newChild      : DOMNode - the Node to be inserted
 * @param  refChildIndex : int     - the array index to insert the Node before
 */
DOMNodeList.prototype._insertBefore = function DOMNodeList__insertBefore(newChild, refChildIndex) {
  if ((refChildIndex >= 0) && (refChildIndex < this._nodes.length)) { // bounds check
    // get array containing children prior to refChild
    var tmpArr = new Array();
    tmpArr = this._nodes.slice(0, refChildIndex);

    if (newChild.nodeType == DOMNode.DOCUMENT_FRAGMENT_NODE) {  // node is a DocumentFragment
      // append the children of DocumentFragment
      tmpArr = tmpArr.concat(newChild.childNodes._nodes);
    }
    else {
      // append the newChild
      tmpArr[tmpArr.length] = newChild;
    }

    // append the remaining original children (including refChild)
    this._nodes = tmpArr.concat(this._nodes.slice(refChildIndex));

    this.length = this._nodes.length;            // update length
  }
};

/**
 * @method DOMNodeList._replaceChild - replace the specified Node in the NodeList at the specified index
 *   Used by DOMNode.replaceChild(). Note: DOMNode.replaceChild() is responsible for Node Pointer surgery
 *   DOMNodeList._replaceChild() simply modifies the internal data structure (Array).
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  newChild      : DOMNode - the Node to be inserted
 * @param  refChildIndex : int     - the array index to hold the Node
 */
DOMNodeList.prototype._replaceChild = function DOMNodeList__replaceChild(newChild, refChildIndex) {
  var ret = null;

  if ((refChildIndex >= 0) && (refChildIndex < this._nodes.length)) { // bounds check
    ret = this._nodes[refChildIndex];            // preserve old child for return

    if (newChild.nodeType == DOMNode.DOCUMENT_FRAGMENT_NODE) {  // node is a DocumentFragment
      // get array containing children prior to refChild
      var tmpArr = new Array();
      tmpArr = this._nodes.slice(0, refChildIndex);

      // append the children of DocumentFragment
      tmpArr = tmpArr.concat(newChild.childNodes._nodes);

      // append the remaining original children (not including refChild)
      this._nodes = tmpArr.concat(this._nodes.slice(refChildIndex + 1));
    }
    else {
      // simply replace node in array (links between Nodes are made at higher level)
      this._nodes[refChildIndex] = newChild;
    }
  }

  return ret;                                   // return replaced node
};

/**
 * @method DOMNodeList._removeChild - remove the specified Node in the NodeList at the specified index
 *   Used by DOMNode.removeChild(). Note: DOMNode.removeChild() is responsible for Node Pointer surgery
 *   DOMNodeList._replaceChild() simply modifies the internal data structure (Array).
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  refChildIndex : int - the array index holding the Node to be removed
 */
DOMNodeList.prototype._removeChild = function DOMNodeList__removeChild(refChildIndex) {
  var ret = null;

  if (refChildIndex > -1) {                              // found it!
    ret = this._nodes[refChildIndex];                    // return removed node

    // rebuild array without removed child
    var tmpArr = new Array();
    tmpArr = this._nodes.slice(0, refChildIndex);
    this._nodes = tmpArr.concat(this._nodes.slice(refChildIndex +1));

    this.length = this._nodes.length;            // update length
  }

  return ret;                                   // return removed node
};

/**
 * @method DOMNodeList._appendChild - append the specified Node to the NodeList
 *   Used by DOMNode.appendChild(). Note: DOMNode.appendChild() is responsible for Node Pointer surgery
 *   DOMNodeList._appendChild() simply modifies the internal data structure (Array).
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  newChild      : DOMNode - the Node to be inserted
 */
DOMNodeList.prototype._appendChild = function DOMNodeList__appendChild(newChild) {

  if (newChild.nodeType == DOMNode.DOCUMENT_FRAGMENT_NODE) {  // node is a DocumentFragment
    // append the children of DocumentFragment
    this._nodes = this._nodes.concat(newChild.childNodes._nodes);
  }
  else {
    // simply add node to array (links between Nodes are made at higher level)
    this._nodes[this._nodes.length] = newChild;
  }

  this.length = this._nodes.length;              // update length
};

/**
 * @method DOMNodeList._cloneNodes - Returns a NodeList containing clones of the Nodes in this NodeList
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  deep : boolean - If true, recursively clone the subtree under each of the nodes;
 *   if false, clone only the nodes themselves (and their attributes, if it is an Element).
 * @param  parentNode : DOMNode - the new parent of the cloned NodeList
 *
 * @return : DOMNodeList - NodeList containing clones of the Nodes in this NodeList
 */
DOMNodeList.prototype._cloneNodes = function DOMNodeList__cloneNodes(deep, parentNode) {
  var cloneNodeList = new DOMNodeList(this.ownerDocument, parentNode);

  // create list containing clones of each child
  for (var i=0; i < this._nodes.length; i++) {
    cloneNodeList._appendChild(this._nodes[i].cloneNode(deep));
  }

  return cloneNodeList;
};

/**
 * @method DOMNodeList.toString - Serialize this NodeList into an XML string
 *
 * @author Jon van Noort (jon@webarcana.com.au) and David Joham (djoham@yahoo.com)
 *
 * @return : string
 */
DOMNodeList.prototype.toString = function DOMNodeList_toString() {
  var ret = "";

  // create string containing the concatenation of the string values of each child
  for (var i=0; i < this.length; i++) {
    ret += this._nodes[i].toString();
  }

  return ret;
};

/**
 * @class  DOMNamedNodeMap - used to represent collections of nodes that can be accessed by name
 *  typically a set of Element attributes
 *
 * @extends DOMNodeList - note W3C spec says that this is not the case,
 *   but we need an item() method identicle to DOMNodeList's, so why not?
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  ownerDocument : DOMDocument - the ownerDocument
 * @param  parentNode    : DOMNode - the node that the DOMNamedNodeMap is attached to (or null)
 */
DOMNamedNodeMap = function(ownerDocument, parentNode) {
  this._class = addClass(this._class, "DOMNamedNodeMap");
  this.DOMNodeList = DOMNodeList;
  this.DOMNodeList(ownerDocument, parentNode);
};
DOMNamedNodeMap.prototype = new DOMNodeList;

/**
 * @method DOMNamedNodeMap.getNamedItem - Retrieves a node specified by name
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  name : string - Name of a node to retrieve
 *
 * @return : DOMNode
 */
DOMNamedNodeMap.prototype.getNamedItem = function DOMNamedNodeMap_getNamedItem(name) {
  var ret = null;

  // test that Named Node exists
  var itemIndex = this._findNamedItemIndex(name);

  if (itemIndex > -1) {                          // found it!
    ret = this._nodes[itemIndex];                // return NamedNode
  }

  return ret;                                    // if node is not found, default value null is returned
};

/**
 * @method DOMNamedNodeMap.setNamedItem - Adds a node using its nodeName attribute
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  arg : DOMNode - A node to store in a named node map.
 *   The node will later be accessible using the value of the nodeName attribute of the node.
 *   If a node with that name is already present in the map, it is replaced by the new one.
 *
 * @throws : DOMException - WRONG_DOCUMENT_ERR: Raised if arg was created from a different document than the one that created this map.
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if this NamedNodeMap is readonly.
 * @throws : DOMException - INUSE_ATTRIBUTE_ERR: Raised if arg is an Attr that is already an attribute of another Element object.
 *  The DOM user must explicitly clone Attr nodes to re-use them in other elements.
 *
 * @return : DOMNode - If the new Node replaces an existing node with the same name the previously existing Node is returned,
 *   otherwise null is returned
 */
DOMNamedNodeMap.prototype.setNamedItem = function DOMNamedNodeMap_setNamedItem(arg) {
  // test for exceptions
  if (this.ownerDocument.implementation.errorChecking) {
    // throw Exception if arg was not created by this Document
    if (this.ownerDocument != arg.ownerDocument) {
      throw(new DOMException(DOMException.WRONG_DOCUMENT_ERR));
    }

    // throw Exception if DOMNamedNodeMap is readonly
    if (this._readonly || (this.parentNode && this.parentNode._readonly)) {
      throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
    }

    // throw Exception if arg is already an attribute of another Element object
    if (arg.ownerElement && (arg.ownerElement != this.parentNode)) {
      throw(new DOMException(DOMException.INUSE_ATTRIBUTE_ERR));
    }
  }

  // get item index
  var itemIndex = this._findNamedItemIndex(arg.name);
  var ret = null;

  if (itemIndex > -1) {                          // found it!
    ret = this._nodes[itemIndex];                // use existing Attribute

    // throw Exception if DOMAttr is readonly
    if (this.ownerDocument.implementation.errorChecking && ret._readonly) {
      throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
    }
    else {
      this._nodes[itemIndex] = arg;                // over-write existing NamedNode
    }
  }
  else {
    this._nodes[this.length] = arg;              // add new NamedNode
  }

  this.length = this._nodes.length;              // update length

  arg.ownerElement = this.parentNode;            // update ownerElement

  return ret;                                    // return old node or null
};

/**
 * @method DOMNamedNodeMap.removeNamedItem - Removes a node specified by name.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  name : string - The name of a node to remove
 *
 * @throws : DOMException - NOT_FOUND_ERR: Raised if there is no node named name in this map.
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if this NamedNodeMap is readonly.
 *
 * @return : DOMNode - The node removed from the map or null if no node with such a name exists.
 */
DOMNamedNodeMap.prototype.removeNamedItem = function DOMNamedNodeMap_removeNamedItem(name) {
  var ret = null;
  // test for exceptions
  // throw Exception if DOMNamedNodeMap is readonly
  if (this.ownerDocument.implementation.errorChecking && (this._readonly || (this.parentNode && this.parentNode._readonly))) {
    throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
  }

  // get item index
  var itemIndex = this._findNamedItemIndex(name);

  // throw Exception if there is no node named name in this map
  if (this.ownerDocument.implementation.errorChecking && (itemIndex < 0)) {
    throw(new DOMException(DOMException.NOT_FOUND_ERR));
  }

  // get Node
  var oldNode = this._nodes[itemIndex];

  // throw Exception if Node is readonly
  if (this.ownerDocument.implementation.errorChecking && oldNode._readonly) {
    throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
  }

  // return removed node
  return this._removeChild(itemIndex);
};

/**
 * @method DOMNamedNodeMap.getNamedItemNS - Retrieves a node specified by name
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  namespaceURI : string - the namespace URI of the required node
 * @param  localName    : string - the local name of the required node
 *
 * @return : DOMNode
 */
DOMNamedNodeMap.prototype.getNamedItemNS = function DOMNamedNodeMap_getNamedItemNS(namespaceURI, localName) {
  var ret = null;

  // test that Named Node exists
  var itemIndex = this._findNamedItemNSIndex(namespaceURI, localName);

  if (itemIndex > -1) {                          // found it!
    ret = this._nodes[itemIndex];                // return NamedNode
  }

  return ret;                                    // if node is not found, default value null is returned
};

/**
 * @method DOMNamedNodeMap.setNamedItemNS - Adds a node using
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  arg : string - A node to store in a named node map.
 *   The node will later be accessible using the value of the nodeName attribute of the node.
 *   If a node with that name is already present in the map, it is replaced by the new one.
 *
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if this NamedNodeMap is readonly.
 * @throws : DOMException - WRONG_DOCUMENT_ERR: Raised if arg was created from a different document than the one that created this map.
 * @throws : DOMException - INUSE_ATTRIBUTE_ERR: Raised if arg is an Attr that is already an attribute of another Element object.
 *   The DOM user must explicitly clone Attr nodes to re-use them in other elements.
 *
 * @return : DOMNode - If the new Node replaces an existing node with the same name the previously existing Node is returned,
 *   otherwise null is returned
 */
DOMNamedNodeMap.prototype.setNamedItemNS = function DOMNamedNodeMap_setNamedItemNS(arg) {
  // test for exceptions
  if (this.ownerDocument.implementation.errorChecking) {
    // throw Exception if DOMNamedNodeMap is readonly
    if (this._readonly || (this.parentNode && this.parentNode._readonly)) {
      throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
    }

    // throw Exception if arg was not created by this Document
    if (this.ownerDocument != arg.ownerDocument) {
      throw(new DOMException(DOMException.WRONG_DOCUMENT_ERR));
    }

    // throw Exception if arg is already an attribute of another Element object
    if (arg.ownerElement && (arg.ownerElement != this.parentNode)) {
      throw(new DOMException(DOMException.INUSE_ATTRIBUTE_ERR));
    }
  }

  // get item index
  var itemIndex = this._findNamedItemNSIndex(arg.namespaceURI, arg.localName);
  var ret = null;

  if (itemIndex > -1) {                          // found it!
    ret = this._nodes[itemIndex];                // use existing Attribute
    // throw Exception if DOMAttr is readonly
    if (this.ownerDocument.implementation.errorChecking && ret._readonly) {
      throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
    }
    else {
      this._nodes[itemIndex] = arg;                // over-write existing NamedNode
    }
  }
  else {
    this._nodes[this.length] = arg;              // add new NamedNode
  }

  this.length = this._nodes.length;              // update length

  arg.ownerElement = this.parentNode;


  return ret;                                    // return old node or null
};

/**
 * @method DOMNamedNodeMap.removeNamedItemNS - Removes a node specified by name.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  namespaceURI : string - the namespace URI of the required node
 * @param  localName    : string - the local name of the required node
 *
 * @throws : DOMException - NOT_FOUND_ERR: Raised if there is no node with the specified namespaceURI and localName in this map.
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if this NamedNodeMap is readonly.
 *
 * @return : DOMNode - The node removed from the map or null if no node with such a name exists.
 */
DOMNamedNodeMap.prototype.removeNamedItemNS = function DOMNamedNodeMap_removeNamedItemNS(namespaceURI, localName) {
  var ret = null;

  // test for exceptions
  // throw Exception if DOMNamedNodeMap is readonly
  if (this.ownerDocument.implementation.errorChecking && (this._readonly || (this.parentNode && this.parentNode._readonly))) {
    throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
  }

  // get item index
  var itemIndex = this._findNamedItemNSIndex(namespaceURI, localName);

  // throw Exception if there is no matching node in this map
  if (this.ownerDocument.implementation.errorChecking && (itemIndex < 0)) {
    throw(new DOMException(DOMException.NOT_FOUND_ERR));
  }

  // get Node
  var oldNode = this._nodes[itemIndex];

  // throw Exception if Node is readonly
  if (this.ownerDocument.implementation.errorChecking && oldNode._readonly) {
    throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
  }

  return this._removeChild(itemIndex);             // return removed node
};

/**
 * @method DOMNamedNodeMap._findNamedItemIndex - find the item index of the node with the specified name
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  name : string - the name of the required node
 *
 * @return : int
 */
DOMNamedNodeMap.prototype._findNamedItemIndex = function DOMNamedNodeMap__findNamedItemIndex(name) {
  var ret = -1;

  // loop through all nodes
  for (var i=0; i<this._nodes.length; i++) {
    // compare name to each node's nodeName
    if (this._nodes[i].name == name) {         // found it!
      ret = i;
      break;
    }
  }

  return ret;                                    // if node is not found, default value -1 is returned
};

/**
 * @method DOMNamedNodeMap._findNamedItemNSIndex - find the item index of the node with the specified namespaceURI and localName
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  namespaceURI : string - the namespace URI of the required node
 * @param  localName    : string - the local name of the required node
 *
 * @return : int
 */
DOMNamedNodeMap.prototype._findNamedItemNSIndex = function DOMNamedNodeMap__findNamedItemNSIndex(namespaceURI, localName) {
  var ret = -1;

  // test that localName is not null
  if (localName) {
    // loop through all nodes
    for (var i=0; i<this._nodes.length; i++) {
      // compare name to each node's namespaceURI and localName
      if ((this._nodes[i].namespaceURI == namespaceURI) && (this._nodes[i].localName == localName)) {
        ret = i;                                 // found it!
        break;
      }
    }
  }

  return ret;                                    // if node is not found, default value -1 is returned
};

/**
 * @method DOMNamedNodeMap._hasAttribute - Returns true if specified node exists
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  name : string - the name of the required node
 *
 * @return : boolean
 */
DOMNamedNodeMap.prototype._hasAttribute = function DOMNamedNodeMap__hasAttribute(name) {
  var ret = false;

  // test that Named Node exists
  var itemIndex = this._findNamedItemIndex(name);

  if (itemIndex > -1) {                          // found it!
    ret = true;                                  // return true
  }

  return ret;                                    // if node is not found, default value false is returned
}

/**
 * @method DOMNamedNodeMap._hasAttributeNS - Returns true if specified node exists
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  namespaceURI : string - the namespace URI of the required node
 * @param  localName    : string - the local name of the required node
 *
 * @return : boolean
 */
DOMNamedNodeMap.prototype._hasAttributeNS = function DOMNamedNodeMap__hasAttributeNS(namespaceURI, localName) {
  var ret = false;

  // test that Named Node exists
  var itemIndex = this._findNamedItemNSIndex(namespaceURI, localName);

  if (itemIndex > -1) {                          // found it!
    ret = true;                                  // return true
  }

  return ret;                                    // if node is not found, default value false is returned
}

/**
 * @method DOMNamedNodeMap._cloneNodes - Returns a NamedNodeMap containing clones of the Nodes in this NamedNodeMap
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  parentNode : DOMNode - the new parent of the cloned NodeList
 *
 * @return : DOMNamedNodeMap - NamedNodeMap containing clones of the Nodes in this DOMNamedNodeMap
 */
DOMNamedNodeMap.prototype._cloneNodes = function DOMNamedNodeMap__cloneNodes(parentNode) {
  var cloneNamedNodeMap = new DOMNamedNodeMap(this.ownerDocument, parentNode);

  // create list containing clones of all children
  for (var i=0; i < this._nodes.length; i++) {
    cloneNamedNodeMap._appendChild(this._nodes[i].cloneNode(false));
  }

  return cloneNamedNodeMap;
};

/**
 * @method DOMNamedNodeMap.toString - Serialize this NodeMap into an XML string
 *
 * @author Jon van Noort (jon@webarcana.com.au) and David Joham (djoham@yahoo.com)
 *
 * @return : string
 */
DOMNamedNodeMap.prototype.toString = function DOMNamedNodeMap_toString() {
  var ret = "";

  // create string containing concatenation of all (but last) Attribute string values (separated by spaces)
  for (var i=0; i < this.length -1; i++) {
    ret += this._nodes[i].toString() +" ";
  }

  // add last Attribute to string (without trailing space)
  if (this.length > 0) {
    ret += this._nodes[this.length -1].toString();
  }

  return ret;
};

/**
 * @class  DOMNamespaceNodeMap - used to represent collections of namespace nodes that can be accessed by name
 *  typically a set of Element attributes
 *
 * @extends DOMNamedNodeMap
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  ownerDocument : DOMDocument - the ownerDocument
 * @param  parentNode    : DOMNode - the node that the DOMNamespaceNodeMap is attached to (or null)
 */
DOMNamespaceNodeMap = function(ownerDocument, parentNode) {
  this._class = addClass(this._class, "DOMNamespaceNodeMap");
  this.DOMNamedNodeMap = DOMNamedNodeMap;
  this.DOMNamedNodeMap(ownerDocument, parentNode);
};
DOMNamespaceNodeMap.prototype = new DOMNamedNodeMap;

/**
 * @method DOMNamespaceNodeMap._findNamedItemIndex - find the item index of the node with the specified localName
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  localName : string - the localName of the required node
 *
 * @return : int
 */
DOMNamespaceNodeMap.prototype._findNamedItemIndex = function DOMNamespaceNodeMap__findNamedItemIndex(localName) {
  var ret = -1;

  // loop through all nodes
  for (var i=0; i<this._nodes.length; i++) {
    // compare name to each node's nodeName
    if (this._nodes[i].localName == localName) {         // found it!
      ret = i;
      break;
    }
  }

  return ret;                                    // if node is not found, default value -1 is returned
};


/**
 * @method DOMNamespaceNodeMap._cloneNodes - Returns a NamespaceNodeMap containing clones of the Nodes in this NamespaceNodeMap
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  parentNode : DOMNode - the new parent of the cloned NodeList
 *
 * @return : DOMNamespaceNodeMap - NamespaceNodeMap containing clones of the Nodes in this NamespaceNodeMap
 */
DOMNamespaceNodeMap.prototype._cloneNodes = function DOMNamespaceNodeMap__cloneNodes(parentNode) {
  var cloneNamespaceNodeMap = new DOMNamespaceNodeMap(this.ownerDocument, parentNode);

  // create list containing clones of all children
  for (var i=0; i < this._nodes.length; i++) {
    cloneNamespaceNodeMap._appendChild(this._nodes[i].cloneNode(false));
  }

  return cloneNamespaceNodeMap;
};

/**
 * @method DOMNamespaceNodeMap.toString - Serialize this NamespaceNodeMap into an XML string
 *
 * @author Jon van Noort (jon@webarcana.com.au) and David Joham (djoham@yahoo.com)
 *
 * @return : string
 */
DOMNamespaceNodeMap.prototype.toString = function DOMNamespaceNodeMap_toString() {
  var ret = "";

  // identify namespaces declared local to this Element (ie, not inherited)
  for (var ind = 0; ind < this._nodes.length; ind++) {
    // if namespace declaration does not exist in the containing node's, parentNode's namespaces
    var ns = null;
    try {
        var ns = this.parentNode.parentNode._namespaces.getNamedItem(this._nodes[ind].localName);
    }
    catch (e) {
        //breaking to prevent default namespace being inserted into return value
        break;
    }
    if (!(ns && (""+ ns.nodeValue == ""+ this._nodes[ind].nodeValue))) {
      // display the namespace declaration
      ret += this._nodes[ind].toString() +" ";
    }
  }

  return ret;
};

/**
 * @class  DOMNode - The Node interface is the primary datatype for the entire Document Object Model.
 *   It represents a single node in the document tree.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  ownerDocument : DOMDocument - The Document object associated with this node.
 */
DOMNode = function(ownerDocument) {
  this._class = addClass(this._class, "DOMNode");

  if (ownerDocument) {
    this._id = ownerDocument._genId();           // generate unique internal id
  }

  this.namespaceURI = "";                        // The namespace URI of this node (Level 2)
  this.prefix       = "";                        // The namespace prefix of this node (Level 2)
  this.localName    = "";                        // The localName of this node (Level 2)

  this.nodeName = "";                            // The name of this node
  this.nodeValue = "";                           // The value of this node
  this.nodeType = 0;                             // A code representing the type of the underlying object

  // The parent of this node. All nodes, except Document, DocumentFragment, and Attr may have a parent.
  // However, if a node has just been created and not yet added to the tree, or if it has been removed from the tree, this is null
  this.parentNode      = null;

  // A NodeList that contains all children of this node. If there are no children, this is a NodeList containing no nodes.
  // The content of the returned NodeList is "live" in the sense that, for instance, changes to the children of the node object
  // that it was created from are immediately reflected in the nodes returned by the NodeList accessors;
  // it is not a static snapshot of the content of the node. This is true for every NodeList, including the ones returned by the getElementsByTagName method.
  this.childNodes      = new DOMNodeList(ownerDocument, this);

  this.firstChild      = null;                   // The first child of this node. If there is no such node, this is null
  this.lastChild       = null;                   // The last child of this node. If there is no such node, this is null.
  this.previousSibling = null;                   // The node immediately preceding this node. If there is no such node, this is null.
  this.nextSibling     = null;                   // The node immediately following this node. If there is no such node, this is null.

  this.attributes = new DOMNamedNodeMap(ownerDocument, this);   // A NamedNodeMap containing the attributes of this node (if it is an Element) or null otherwise.
  this.ownerDocument   = ownerDocument;          // The Document object associated with this node
  this._namespaces = new DOMNamespaceNodeMap(ownerDocument, this);  // The namespaces in scope for this node

  this._readonly = false;
};

// nodeType constants
DOMNode.ELEMENT_NODE                = 1;
DOMNode.ATTRIBUTE_NODE              = 2;
DOMNode.TEXT_NODE                   = 3;
DOMNode.CDATA_SECTION_NODE          = 4;
DOMNode.ENTITY_REFERENCE_NODE       = 5;
DOMNode.ENTITY_NODE                 = 6;
DOMNode.PROCESSING_INSTRUCTION_NODE = 7;
DOMNode.COMMENT_NODE                = 8;
DOMNode.DOCUMENT_NODE               = 9;
DOMNode.DOCUMENT_TYPE_NODE          = 10;
DOMNode.DOCUMENT_FRAGMENT_NODE      = 11;
DOMNode.NOTATION_NODE               = 12;
DOMNode.NAMESPACE_NODE              = 13;

/**
 * @method DOMNode.hasAttributes
 *
 * @author Jon van Noort (jon@webarcana.com.au) & David Joham (djoham@yahoo.com)
 *
 * @return : boolean
 */
DOMNode.prototype.hasAttributes = function DOMNode_hasAttributes() {
    if (this.attributes.length == 0) {
        return false;
    }
    else {
        return true;
    }
};

/**
 * @method DOMNode.getNodeName - Java style gettor for .nodeName
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : string
 */
DOMNode.prototype.getNodeName = function DOMNode_getNodeName() {
  return this.nodeName;
};

/**
 * @method DOMNode.getNodeValue - Java style gettor for .NodeValue
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : string
 */
DOMNode.prototype.getNodeValue = function DOMNode_getNodeValue() {
  return this.nodeValue;
};

/**
 * @method DOMNode.setNodeValue - Java style settor for .NodeValue
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  nodeValue : string - unique internal id
 */
DOMNode.prototype.setNodeValue = function DOMNode_setNodeValue(nodeValue) {
  // throw Exception if DOMNode is readonly
  if (this.ownerDocument.implementation.errorChecking && this._readonly) {
    throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
  }

  this.nodeValue = nodeValue;
};

/**
 * @method DOMNode.getNodeType - Java style gettor for .nodeType
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : int
 */
DOMNode.prototype.getNodeType = function DOMNode_getNodeType() {
  return this.nodeType;
};

/**
 * @method DOMNode.getParentNode - Java style gettor for .parentNode
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : DOMNode
 */
DOMNode.prototype.getParentNode = function DOMNode_getParentNode() {
  return this.parentNode;
};

/**
 * @method DOMNode.getChildNodes - Java style gettor for .childNodes
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : DOMNodeList
 */
DOMNode.prototype.getChildNodes = function DOMNode_getChildNodes() {
  return this.childNodes;
};

/**
 * @method DOMNode.getFirstChild - Java style gettor for .firstChild
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : DOMNode
 */
DOMNode.prototype.getFirstChild = function DOMNode_getFirstChild() {
  return this.firstChild;
};

/**
 * @method DOMNode.getLastChild - Java style gettor for .lastChild
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : DOMNode
 */
DOMNode.prototype.getLastChild = function DOMNode_getLastChild() {
  return this.lastChild;
};

/**
 * @method DOMNode.getPreviousSibling - Java style gettor for .previousSibling
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : DOMNode
 */
DOMNode.prototype.getPreviousSibling = function DOMNode_getPreviousSibling() {
  return this.previousSibling;
};

/**
 * @method DOMNode.getNextSibling - Java style gettor for .nextSibling
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : DOMNode
 */
DOMNode.prototype.getNextSibling = function DOMNode_getNextSibling() {
  return this.nextSibling;
};

/**
 * @method DOMNode.getAttributes - Java style gettor for .attributes
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : DOMNamedNodeList
 */
DOMNode.prototype.getAttributes = function DOMNode_getAttributes() {
  return this.attributes;
};

/**
 * @method DOMNode.getOwnerDocument - Java style gettor for .ownerDocument
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : DOMDocument
 */
DOMNode.prototype.getOwnerDocument = function DOMNode_getOwnerDocument() {
  return this.ownerDocument;
};

/**
 * @method DOMNode.getNamespaceURI - Java style gettor for .namespaceURI
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : String
 */
DOMNode.prototype.getNamespaceURI = function DOMNode_getNamespaceURI() {
  return this.namespaceURI;
};

/**
 * @method DOMNode.getPrefix - Java style gettor for .prefix
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : String
 */
DOMNode.prototype.getPrefix = function DOMNode_getPrefix() {
  return this.prefix;
};

/**
 * @method DOMNode.setPrefix - Java style settor for .prefix
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param   prefix : String
 *
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if this Node is readonly.
 * @throws : DOMException - INVALID_CHARACTER_ERR: Raised if the string contains an illegal character
 * @throws : DOMException - NAMESPACE_ERR: Raised if the Namespace is invalid
 *
 */
DOMNode.prototype.setPrefix = function DOMNode_setPrefix(prefix) {
  // test for exceptions
  if (this.ownerDocument.implementation.errorChecking) {
    // throw Exception if DOMNode is readonly
    if (this._readonly) {
      throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
    }

    // throw Exception if the prefix string contains an illegal character
    if (!this.ownerDocument.implementation._isValidName(prefix)) {
      throw(new DOMException(DOMException.INVALID_CHARACTER_ERR));
    }

    // throw Exception if the Namespace is invalid;
    //  if the specified prefix is malformed,
    //  if the namespaceURI of this node is null,
    //  if the specified prefix is "xml" and the namespaceURI of this node is
    //   different from "http://www.w3.org/XML/1998/namespace",
    if (!this.ownerDocument._isValidNamespace(this.namespaceURI, prefix +":"+ this.localName)) {
      throw(new DOMException(DOMException.NAMESPACE_ERR));
    }

    // throw Exception if we are trying to make the attribute look like a namespace declaration;
    //  if this node is an attribute and the specified prefix is "xmlns"
    //   and the namespaceURI of this node is different from "http://www.w3.org/2000/xmlns/",
    if ((prefix == "xmlns") && (this.namespaceURI != "http://www.w3.org/2000/xmlns/")) {
      throw(new DOMException(DOMException.NAMESPACE_ERR));
    }

    // throw Exception if we are trying to make the attribute look like a default namespace declaration;
    //  if this node is an attribute and the qualifiedName of this node is "xmlns" [Namespaces].
    if ((prefix == "") && (this.localName == "xmlns")) {
      throw(new DOMException(DOMException.NAMESPACE_ERR));
    }
  }

  // update prefix
  this.prefix = prefix;

  // update nodeName (QName)
  if (this.prefix != "") {
    this.nodeName = this.prefix +":"+ this.localName;
  }
  else {
    this.nodeName = this.localName;  // no prefix, therefore nodeName is simply localName
  }
};

/**
 * @method DOMNode.getLocalName - Java style gettor for .localName
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : String
 */
DOMNode.prototype.getLocalName = function DOMNode_getLocalName() {
  return this.localName;
};

/**
 * @method DOMNode.insertBefore - Inserts the node newChild before the existing child node refChild.
 *   If refChild is null, insert newChild at the end of the list of children.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  newChild : DOMNode - The node to insert.
 * @param  refChild : DOMNode - The reference node, i.e., the node before which the new node must be inserted
 *
 * @throws : DOMException - HIERARCHY_REQUEST_ERR: Raised if the node to insert is one of this node's ancestors
 * @throws : DOMException - WRONG_DOCUMENT_ERR: Raised if arg was created from a different document than the one that created this map.
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if this Node is readonly.
 * @throws : DOMException - NOT_FOUND_ERR: Raised if there is no node named name in this map.
 *
 * @return : DOMNode - The node being inserted.
 */
DOMNode.prototype.insertBefore = function DOMNode_insertBefore(newChild, refChild) {
  var prevNode;

  // test for exceptions
  if (this.ownerDocument.implementation.errorChecking) {
    // throw Exception if DOMNode is readonly
    if (this._readonly) {
      throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
    }

    // throw Exception if newChild was not created by this Document
    if (this.ownerDocument != newChild.ownerDocument) {
      throw(new DOMException(DOMException.WRONG_DOCUMENT_ERR));
    }

    // throw Exception if the node is an ancestor
    if (this._isAncestor(newChild)) {
      throw(new DOMException(DOMException.HIERARCHY_REQUEST_ERR));
    }
  }

  if (refChild) {                                // if refChild is specified, insert before it
    // find index of refChild
    var itemIndex = this.childNodes._findItemIndex(refChild._id);

    // throw Exception if there is no child node with this id
    if (this.ownerDocument.implementation.errorChecking && (itemIndex < 0)) {
      throw(new DOMException(DOMException.NOT_FOUND_ERR));
    }

    // if the newChild is already in the tree,
    var newChildParent = newChild.parentNode;
    if (newChildParent) {
      // remove it
      newChildParent.removeChild(newChild);
    }

    // insert newChild into childNodes
    this.childNodes._insertBefore(newChild, this.childNodes._findItemIndex(refChild._id));

    // do node pointer surgery
    prevNode = refChild.previousSibling;

    // handle DocumentFragment
    if (newChild.nodeType == DOMNode.DOCUMENT_FRAGMENT_NODE) {
      if (newChild.childNodes._nodes.length > 0) {
        // set the parentNode of DocumentFragment's children
        for (var ind = 0; ind < newChild.childNodes._nodes.length; ind++) {
          newChild.childNodes._nodes[ind].parentNode = this;
        }

        // link refChild to last child of DocumentFragment
        refChild.previousSibling = newChild.childNodes._nodes[newChild.childNodes._nodes.length-1];
      }
    }
    else {
      newChild.parentNode = this;                // set the parentNode of the newChild
      refChild.previousSibling = newChild;       // link refChild to newChild
    }
  }
  else {                                         // otherwise, append to end
    prevNode = this.lastChild;
    this.appendChild(newChild);
  }

  if (newChild.nodeType == DOMNode.DOCUMENT_FRAGMENT_NODE) {
    // do node pointer surgery for DocumentFragment
    if (newChild.childNodes._nodes.length > 0) {
      if (prevNode) {
        prevNode.nextSibling = newChild.childNodes._nodes[0];
      }
      else {                                         // this is the first child in the list
        this.firstChild = newChild.childNodes._nodes[0];
      }

      newChild.childNodes._nodes[0].previousSibling = prevNode;
      newChild.childNodes._nodes[newChild.childNodes._nodes.length-1].nextSibling = refChild;
    }
  }
  else {
    // do node pointer surgery for newChild
    if (prevNode) {
      prevNode.nextSibling = newChild;
    }
    else {                                         // this is the first child in the list
      this.firstChild = newChild;
    }

    newChild.previousSibling = prevNode;
    newChild.nextSibling     = refChild;
  }

  return newChild;
};

/**
 * @method DOMNode.replaceChild - Replaces the child node oldChild with newChild in the list of children,
 *   and returns the oldChild node.
 *   If the newChild is already in the tree, it is first removed.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  newChild : DOMNode - The node to insert.
 * @param  oldChild : DOMNode - The node being replaced in the list.
 *
 * @throws : DOMException - HIERARCHY_REQUEST_ERR: Raised if the node to insert is one of this node's ancestors
 * @throws : DOMException - WRONG_DOCUMENT_ERR: Raised if arg was created from a different document than the one that created this map.
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if this Node is readonly.
 * @throws : DOMException - NOT_FOUND_ERR: Raised if there is no node named name in this map.
 *
 * @return : DOMNode - The node that was replaced
 */
DOMNode.prototype.replaceChild = function DOMNode_replaceChild(newChild, oldChild) {
  var ret = null;

  // test for exceptions
  if (this.ownerDocument.implementation.errorChecking) {
    // throw Exception if DOMNode is readonly
    if (this._readonly) {
      throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
    }

    // throw Exception if newChild was not created by this Document
    if (this.ownerDocument != newChild.ownerDocument) {
      throw(new DOMException(DOMException.WRONG_DOCUMENT_ERR));
    }

    // throw Exception if the node is an ancestor
    if (this._isAncestor(newChild)) {
      throw(new DOMException(DOMException.HIERARCHY_REQUEST_ERR));
    }
  }

  // get index of oldChild
  var index = this.childNodes._findItemIndex(oldChild._id);

  // throw Exception if there is no child node with this id
  if (this.ownerDocument.implementation.errorChecking && (index < 0)) {
    throw(new DOMException(DOMException.NOT_FOUND_ERR));
  }

  // if the newChild is already in the tree,
  var newChildParent = newChild.parentNode;
  if (newChildParent) {
    // remove it
    newChildParent.removeChild(newChild);
  }

  // add newChild to childNodes
  ret = this.childNodes._replaceChild(newChild, index);


  if (newChild.nodeType == DOMNode.DOCUMENT_FRAGMENT_NODE) {
    // do node pointer surgery for Document Fragment
    if (newChild.childNodes._nodes.length > 0) {
      for (var ind = 0; ind < newChild.childNodes._nodes.length; ind++) {
        newChild.childNodes._nodes[ind].parentNode = this;
      }

      if (oldChild.previousSibling) {
        oldChild.previousSibling.nextSibling = newChild.childNodes._nodes[0];
      }
      else {
        this.firstChild = newChild.childNodes._nodes[0];
      }

      if (oldChild.nextSibling) {
        oldChild.nextSibling.previousSibling = newChild;
      }
      else {
        this.lastChild = newChild.childNodes._nodes[newChild.childNodes._nodes.length-1];
      }

      newChild.childNodes._nodes[0].previousSibling = oldChild.previousSibling;
      newChild.childNodes._nodes[newChild.childNodes._nodes.length-1].nextSibling = oldChild.nextSibling;
    }
  }
  else {
    // do node pointer surgery for newChild
    newChild.parentNode = this;

    if (oldChild.previousSibling) {
      oldChild.previousSibling.nextSibling = newChild;
    }
    else {
      this.firstChild = newChild;
    }
    if (oldChild.nextSibling) {
      oldChild.nextSibling.previousSibling = newChild;
    }
    else {
      this.lastChild = newChild;
    }
    newChild.previousSibling = oldChild.previousSibling;
    newChild.nextSibling = oldChild.nextSibling;
  }
  return ret;
};

/**
 * @method DOMNode.removeChild - Removes the child node indicated by oldChild from the list of children, and returns it.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  oldChild : DOMNode - The node being removed.
 *
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if this Node is readonly.
 * @throws : DOMException - NOT_FOUND_ERR: Raised if there is no node named name in this map.
 *
 * @return : DOMNode - The node being removed.
 */
DOMNode.prototype.removeChild = function DOMNode_removeChild(oldChild) {
  // throw Exception if DOMNamedNodeMap is readonly
  if (this.ownerDocument.implementation.errorChecking && (this._readonly || oldChild._readonly)) {
    throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
  }

  // get index of oldChild
  var itemIndex = this.childNodes._findItemIndex(oldChild._id);

  // throw Exception if there is no child node with this id
  if (this.ownerDocument.implementation.errorChecking && (itemIndex < 0)) {
    throw(new DOMException(DOMException.NOT_FOUND_ERR));
  }

  // remove oldChild from childNodes
  this.childNodes._removeChild(itemIndex);

  // do node pointer surgery
  oldChild.parentNode = null;

  if (oldChild.previousSibling) {
    oldChild.previousSibling.nextSibling = oldChild.nextSibling;
  }
  else {
    this.firstChild = oldChild.nextSibling;
  }
  if (oldChild.nextSibling) {
    oldChild.nextSibling.previousSibling = oldChild.previousSibling;
  }
  else {
    this.lastChild = oldChild.previousSibling;
  }

  oldChild.previousSibling = null;
  oldChild.nextSibling = null;
  return oldChild;
};

/**
 * @method DOMNode.appendChild - Adds the node newChild to the end of the list of children of this node.
 *   If the newChild is already in the tree, it is first removed.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  newChild : DOMNode - The node to add
 *
 * @throws : DOMException - HIERARCHY_REQUEST_ERR: Raised if the node to insert is one of this node's ancestors
 * @throws : DOMException - WRONG_DOCUMENT_ERR: Raised if arg was created from a different document than the one that created this map.
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if this Node is readonly.
 *
 * @return : DOMNode - The node added
 */
DOMNode.prototype.appendChild = function DOMNode_appendChild(newChild) {
  // test for exceptions
  if (this.ownerDocument.implementation.errorChecking) {
    // throw Exception if Node is readonly
    if (this._readonly) {
      throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
    }

    // throw Exception if arg was not created by this Document
    if (this.ownerDocument != newChild.ownerDocument) {
      throw(new DOMException(DOMException.WRONG_DOCUMENT_ERR));
    }

    // throw Exception if the node is an ancestor
    if (this._isAncestor(newChild)) {
      throw(new DOMException(DOMException.HIERARCHY_REQUEST_ERR));
    }
  }

  // if the newChild is already in the tree,
  var newChildParent = newChild.parentNode;
  if (newChildParent) {
    // remove it
    newChildParent.removeChild(newChild);
  }

  // add newChild to childNodes
  this.childNodes._appendChild(newChild);

  if (newChild.nodeType == DOMNode.DOCUMENT_FRAGMENT_NODE) {
    // do node pointer surgery for DocumentFragment
    if (newChild.childNodes._nodes.length > 0) {
      for (var ind = 0; ind < newChild.childNodes._nodes.length; ind++) {
        newChild.childNodes._nodes[ind].parentNode = this;
      }

      if (this.lastChild) {
        this.lastChild.nextSibling = newChild.childNodes._nodes[0];
        newChild.childNodes._nodes[0].previousSibling = this.lastChild;
        this.lastChild = newChild.childNodes._nodes[newChild.childNodes._nodes.length-1];
      }
      else {
        this.lastChild = newChild.childNodes._nodes[newChild.childNodes._nodes.length-1];
        this.firstChild = newChild.childNodes._nodes[0];
      }
    }
  }
  else {
    // do node pointer surgery for newChild
    newChild.parentNode = this;
    if (this.lastChild) {
      this.lastChild.nextSibling = newChild;
      newChild.previousSibling = this.lastChild;
      this.lastChild = newChild;
    }
    else {
      this.lastChild = newChild;
      this.firstChild = newChild;
    }
  }

  return newChild;
};

/**
 * @method DOMNode.hasChildNodes - This is a convenience method to allow easy determination of whether a node has any children.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : boolean - true if the node has any children, false if the node has no children
 */
DOMNode.prototype.hasChildNodes = function DOMNode_hasChildNodes() {
  return (this.childNodes.length > 0);
};

/**
 * @method DOMNode.cloneNode - Returns a duplicate of this node, i.e., serves as a generic copy constructor for nodes.
 *   The duplicate node has no parent (parentNode returns null.).
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  deep : boolean - If true, recursively clone the subtree under the specified node;
 *   if false, clone only the node itself (and its attributes, if it is an Element).
 *
 * @return : DOMNode
 */
DOMNode.prototype.cloneNode = function DOMNode_cloneNode(deep) {
  // use importNode to clone this Node
  //do not throw any exceptions
  try {
     return this.ownerDocument.importNode(this, deep);
  }
  catch (e) {
     //there shouldn't be any exceptions, but if there are, return null
     return null;
  }
};

/**
 * @method DOMNode.normalize - Puts all Text nodes in the full depth of the sub-tree underneath this Element into a "normal" form
 *   where only markup (e.g., tags, comments, processing instructions, CDATA sections, and entity references) separates Text nodes,
 *   i.e., there are no adjacent Text nodes.
 *
 * @author Jon van Noort (jon@webarcana.com.au), David Joham (djoham@yahoo.com) and Scott Severtson
 */
DOMNode.prototype.normalize = function DOMNode_normalize() {
  var inode;
  var nodesToRemove = new DOMNodeList();

  if (this.nodeType == DOMNode.ELEMENT_NODE || this.nodeType == DOMNode.DOCUMENT_NODE) {
    var adjacentTextNode = null;

    // loop through all childNodes
    for(var i = 0; i < this.childNodes.length; i++) {
      inode = this.childNodes.item(i);

      if (inode.nodeType == DOMNode.TEXT_NODE) { // this node is a text node
        if (inode.length < 1) {                  // this text node is empty
          nodesToRemove._appendChild(inode);      // add this node to the list of nodes to be remove
        }
        else {
          if (adjacentTextNode) {                // if previous node was also text
            adjacentTextNode.appendData(inode.data);     // merge the data in adjacent text nodes
            nodesToRemove._appendChild(inode);    // add this node to the list of nodes to be removed
          }
          else {
              adjacentTextNode = inode;              // remember this node for next cycle
          }
        }
      }
      else {
        adjacentTextNode = null;                 // (soon to be) previous node is not a text node
        inode.normalize();                       // normalise non Text childNodes
      }
    }

    // remove redundant Text Nodes
    for(var i = 0; i < nodesToRemove.length; i++) {
      inode = nodesToRemove.item(i);
      inode.parentNode.removeChild(inode);
    }
  }
};

/**
 * @method DOMNode.isSupported - Test if the DOM implementation implements a specific feature
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  feature : string - The package name of the feature to test. the legal only values are "XML" and "CORE" (case-insensitive).
 * @param  version : string - This is the version number of the package name to test. In Level 1, this is the string "1.0".
 *
 * @return : boolean
 */
DOMNode.prototype.isSupported = function DOMNode_isSupported(feature, version) {
  // use Implementation.hasFeature to determin if this feature is supported
  return this.ownerDocument.implementation.hasFeature(feature, version);
}

/**
 * @method DOMNode.getElementsByTagName - Returns a NodeList of all the Elements with a given tag name
 *   in the order in which they would be encountered in a preorder traversal of the Document tree.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  tagname : string - The name of the tag to match on. The special value "*" matches all tags
 *
 * @return : DOMNodeList
 */
DOMNode.prototype.getElementsByTagName = function DOMNode_getElementsByTagName(tagname) {
  // delegate to _getElementsByTagNameRecursive
  return this._getElementsByTagNameRecursive(tagname, new DOMNodeList(this.ownerDocument));
};

/**
 * @method DOMNode._getElementsByTagNameRecursive - implements getElementsByTagName()
 *
 * @author Jon van Noort (jon@webarcana.com.au), David Joham (djoham@yahoo.com) and Scott Severtson
 *
 * @param  tagname  : string      - The name of the tag to match on. The special value "*" matches all tags
 * @param  nodeList : DOMNodeList - The accumulating list of matching nodes
 *
 * @return : DOMNodeList
 */
DOMNode.prototype._getElementsByTagNameRecursive = function DOMNode__getElementsByTagNameRecursive(tagname, nodeList) {
  if (this.nodeType == DOMNode.ELEMENT_NODE || this.nodeType == DOMNode.DOCUMENT_NODE) {

    if((this.nodeName == tagname) || (tagname == "*")) {
      nodeList._appendChild(this);               // add matching node to nodeList
    }

    // recurse childNodes
    for(var i = 0; i < this.childNodes.length; i++) {
      nodeList = this.childNodes.item(i)._getElementsByTagNameRecursive(tagname, nodeList);
    }
  }

  return nodeList;
};

/**
 * @method DOMNode.getXML - Returns the String XML of the node and all of its children
 *
 * @author Jon van Noort (jon@webarcana.com.au) and David Joham (djoham@yahoo.com)
 *
 * @return : string - XML String of the XML of the node and all of its children
 */
DOMNode.prototype.getXML = function DOMNode_getXML() {
  return this.toString();
}


/**
 * @method DOMNode.getElementsByTagNameNS - Returns a NodeList of all the Elements with a given namespaceURI and localName
 *   in the order in which they would be encountered in a preorder traversal of the Document tree.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  namespaceURI : string - the namespace URI of the required node
 * @param  localName    : string - the local name of the required node
 *
 * @return : DOMNodeList
 */
DOMNode.prototype.getElementsByTagNameNS = function DOMNode_getElementsByTagNameNS(namespaceURI, localName) {
  // delegate to _getElementsByTagNameNSRecursive
  return this._getElementsByTagNameNSRecursive(namespaceURI, localName, new DOMNodeList(this.ownerDocument));
};

/**
 * @method DOMNode._getElementsByTagNameNSRecursive - implements getElementsByTagName()
 *
 * @author Jon van Noort (jon@webarcana.com.au), David Joham (djoham@yahoo.com) and Scott Severtson
 *
 * @param  namespaceURI : string - the namespace URI of the required node
 * @param  localName    : string - the local name of the required node
 * @param  nodeList     : DOMNodeList - The accumulating list of matching nodes
 *
 * @return : DOMNodeList
 */
DOMNode.prototype._getElementsByTagNameNSRecursive = function DOMNode__getElementsByTagNameNSRecursive(namespaceURI, localName, nodeList) {
  if (this.nodeType == DOMNode.ELEMENT_NODE || this.nodeType == DOMNode.DOCUMENT_NODE) {

    if (((this.namespaceURI == namespaceURI) || (namespaceURI == "*")) && ((this.localName == localName) || (localName == "*"))) {
      nodeList._appendChild(this);               // add matching node to nodeList
    }

    // recurse childNodes
    for(var i = 0; i < this.childNodes.length; i++) {
      nodeList = this.childNodes.item(i)._getElementsByTagNameNSRecursive(namespaceURI, localName, nodeList);
    }
  }

  return nodeList;
};

/**
 * @method DOMNode._isAncestor - returns true if node is ancestor of this
 *
 * @author Jon van Noort (jon@webarcana.com.au), David Joham (djoham@yahoo.com) and Scott Severtson
 *
 * @param  node         : DOMNode - The candidate ancestor node
 *
 * @return : boolean
 */
DOMNode.prototype._isAncestor = function DOMNode__isAncestor(node) {
  // if this node matches, return true,
  // otherwise recurse up (if there is a parentNode)
  return ((this == node) || ((this.parentNode) && (this.parentNode._isAncestor(node))));
}

/**
 * @method DOMNode.importNode - Imports a node from another document to this document.
 *   The returned node has no parent; (parentNode is null).
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  importedNode : Node - The Node to be imported
 * @param  deep         : boolean - If true, recursively clone the subtree under the specified node;
 *   if false, clone only the node itself (and its attributes, if it is an Element).
 *
 * @return : DOMNode
 */
DOMNode.prototype.importNode = function DOMNode_importNode(importedNode, deep) {
  var importNode;

  //there is no need to perform namespace checks since everything has already gone through them
  //in order to have gotten into the DOM in the first place. The following line
  //turns namespace checking off in ._isValidNamespace
  this.getOwnerDocument()._performingImportNodeOperation = true;

  try {
    if (importedNode.nodeType == DOMNode.ELEMENT_NODE) {
        if (!this.ownerDocument.implementation.namespaceAware) {
        // create a local Element (with the name of the importedNode)
        importNode = this.ownerDocument.createElement(importedNode.tagName);

        // create attributes matching those of the importedNode
        for(var i = 0; i < importedNode.attributes.length; i++) {
            importNode.setAttribute(importedNode.attributes.item(i).name, importedNode.attributes.item(i).value);
        }
        }
        else {
        // create a local Element (with the name & namespaceURI of the importedNode)
        importNode = this.ownerDocument.createElementNS(importedNode.namespaceURI, importedNode.nodeName);

        // create attributes matching those of the importedNode
        for(var i = 0; i < importedNode.attributes.length; i++) {
            importNode.setAttributeNS(importedNode.attributes.item(i).namespaceURI, importedNode.attributes.item(i).name, importedNode.attributes.item(i).value);
        }

        // create namespace definitions matching those of the importedNode
        for(var i = 0; i < importedNode._namespaces.length; i++) {
            importNode._namespaces._nodes[i] = this.ownerDocument.createNamespace(importedNode._namespaces.item(i).localName);
            importNode._namespaces._nodes[i].setValue(importedNode._namespaces.item(i).value);
        }
        }
    }
    else if (importedNode.nodeType == DOMNode.ATTRIBUTE_NODE) {
        if (!this.ownerDocument.implementation.namespaceAware) {
        // create a local Attribute (with the name of the importedAttribute)
        importNode = this.ownerDocument.createAttribute(importedNode.name);
        }
        else {
        // create a local Attribute (with the name & namespaceURI of the importedAttribute)
        importNode = this.ownerDocument.createAttributeNS(importedNode.namespaceURI, importedNode.nodeName);

        // create namespace definitions matching those of the importedAttribute
        for(var i = 0; i < importedNode._namespaces.length; i++) {
            importNode._namespaces._nodes[i] = this.ownerDocument.createNamespace(importedNode._namespaces.item(i).localName);
            importNode._namespaces._nodes[i].setValue(importedNode._namespaces.item(i).value);
        }
        }

        // set the value of the local Attribute to match that of the importedAttribute
        importNode.setValue(importedNode.value);
    }
    else if (importedNode.nodeType == DOMNode.DOCUMENT_FRAGMENT) {
        // create a local DocumentFragment
        importNode = this.ownerDocument.createDocumentFragment();
    }
    else if (importedNode.nodeType == DOMNode.NAMESPACE_NODE) {
        // create a local NamespaceNode (with the same name & value as the importedNode)
        importNode = this.ownerDocument.createNamespace(importedNode.nodeName);
        importNode.setValue(importedNode.value);
    }
    else if (importedNode.nodeType == DOMNode.TEXT_NODE) {
        // create a local TextNode (with the same data as the importedNode)
        importNode = this.ownerDocument.createTextNode(importedNode.data);
    }
    else if (importedNode.nodeType == DOMNode.CDATA_SECTION_NODE) {
        // create a local CDATANode (with the same data as the importedNode)
        importNode = this.ownerDocument.createCDATASection(importedNode.data);
    }
    else if (importedNode.nodeType == DOMNode.PROCESSING_INSTRUCTION_NODE) {
        // create a local ProcessingInstruction (with the same target & data as the importedNode)
        importNode = this.ownerDocument.createProcessingInstruction(importedNode.target, importedNode.data);
    }
    else if (importedNode.nodeType == DOMNode.COMMENT_NODE) {
        // create a local Comment (with the same data as the importedNode)
        importNode = this.ownerDocument.createComment(importedNode.data);
    }
    else {  // throw Exception if nodeType is not supported
        throw(new DOMException(DOMException.NOT_SUPPORTED_ERR));
    }

    if (deep) {                                    // recurse childNodes
        for(var i = 0; i < importedNode.childNodes.length; i++) {
        importNode.appendChild(this.ownerDocument.importNode(importedNode.childNodes.item(i), true));
        }
    }

    //reset _performingImportNodeOperation
    this.getOwnerDocument()._performingImportNodeOperation = false;
    return importNode;
  }
  catch (eAny) {
    //reset _performingImportNodeOperation
    this.getOwnerDocument()._performingImportNodeOperation = false;

    //re-throw the exception
    throw eAny;
  }//djotemp
};

/**
 * @method DOMNode.escapeString - escape special characters
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  str : string - The string to be escaped
 *
 * @return : string - The escaped string
 */
DOMNode.prototype.__escapeString = function DOMNode__escapeString(str) {

  //the sax processor already has this function. Just wrap it
  return __escapeString(str);
};

/**
 * @method DOMNode.unescapeString - unescape special characters
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  str : string - The string to be unescaped
 *
 * @return : string - The unescaped string
 */
DOMNode.prototype.__unescapeString = function DOMNode__unescapeString(str) {

  //the sax processor already has this function. Just wrap it
  return __unescapeString(str);
};



/**
 * @class  DOMDocument - The Document interface represents the entire HTML or XML document.
 *   Conceptually, it is the root of the document tree, and provides the primary access to the document's data.
 *
 * @extends DOMNode
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  implementation : DOMImplementation - the creator Implementation
 */
DOMDocument = function(implementation) {
  this._class = addClass(this._class, "DOMDocument");
  this.DOMNode = DOMNode;
  this.DOMNode(this);

  this.doctype = null;                           // The Document Type Declaration (see DocumentType) associated with this document
  this.implementation = implementation;          // The DOMImplementation object that handles this document.
  this.documentElement = null;                   // This is a convenience attribute that allows direct access to the child node that is the root element of the document
  this.all  = new Array();                       // The list of all Elements

  this.nodeName  = "#document";
  this.nodeType = DOMNode.DOCUMENT_NODE;
  this._id = 0;
  this._lastId = 0;
  this._parseComplete = false;                   // initially false, set to true by parser

  this.ownerDocument = this;

  this._performingImportNodeOperation = false;
};
DOMDocument.prototype = new DOMNode;

/**
 * @method DOMDocument.getDoctype - Java style gettor for .doctype
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : DOMDocument
 */
DOMDocument.prototype.getDoctype = function DOMDocument_getDoctype() {
  return this.doctype;
};

/**
 * @method DOMDocument.getImplementation - Java style gettor for .implementation
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : DOMImplementation
 */
DOMDocument.prototype.getImplementation = function DOMDocument_implementation() {
  return this.implementation;
};

/**
 * @method DOMDocument.getDocumentElement - Java style gettor for .documentElement
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : DOMDocumentElement
 */
DOMDocument.prototype.getDocumentElement = function DOMDocument_getDocumentElement() {
  return this.documentElement;
};

/**
 * @method DOMDocument.createElement - Creates an element of the type specified.
 *   Note that the instance returned implements the Element interface,
 *   so attributes can be specified directly on the returned object.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  tagName : string - The name of the element type to instantiate.
 *
 * @throws : DOMException - INVALID_CHARACTER_ERR: Raised if the string contains an illegal character
 *
 * @return : DOMElement - The new Element object.
 */
DOMDocument.prototype.createElement = function DOMDocument_createElement(tagName) {
  // throw Exception if the tagName string contains an illegal character
  if (this.ownerDocument.implementation.errorChecking && (!this.ownerDocument.implementation._isValidName(tagName))) {
    throw(new DOMException(DOMException.INVALID_CHARACTER_ERR));
  }

  // create DOMElement specifying 'this' as ownerDocument
  var node = new DOMElement(this);

  // assign values to properties (and aliases)
  node.tagName  = tagName;
  node.nodeName = tagName;

  // add Element to 'all' collection
  this.all[this.all.length] = node;

  return node;
};

/**
 * @method DOMDocument.createDocumentFragment - CCreates an empty DocumentFragment object.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : DOMDocumentFragment - The new DocumentFragment object
 */
DOMDocument.prototype.createDocumentFragment = function DOMDocument_createDocumentFragment() {
  // create DOMDocumentFragment specifying 'this' as ownerDocument
  var node = new DOMDocumentFragment(this);

  return node;
};

/**
 * @method DOMDocument.createTextNode - Creates a Text node given the specified string.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  data : string - The data for the node.
 *
 * @return : DOMText - The new Text object.
 */
DOMDocument.prototype.createTextNode = function DOMDocument_createTextNode(data) {
  // create DOMText specifying 'this' as ownerDocument
  var node = new DOMText(this);

  // assign values to properties (and aliases)
  node.data      = data;
  node.nodeValue = data;

  // set initial length
  node.length    = data.length;

  return node;
};

/**
 * @method DOMDocument.createComment - Creates a Text node given the specified string.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  data : string - The data for the node.
 *
 * @return : DOMComment - The new Comment object.
 */
DOMDocument.prototype.createComment = function DOMDocument_createComment(data) {
  // create DOMComment specifying 'this' as ownerDocument
  var node = new DOMComment(this);

  // assign values to properties (and aliases)
  node.data      = data;
  node.nodeValue = data;

  // set initial length
  node.length    = data.length;

  return node;
};

/**
 * @method DOMDocument.createCDATASection - Creates a CDATASection node whose value is the specified string.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  data : string - The data for the node.
 *
 * @return : DOMCDATASection - The new CDATASection object.
 */
DOMDocument.prototype.createCDATASection = function DOMDocument_createCDATASection(data) {
  // create DOMCDATASection specifying 'this' as ownerDocument
  var node = new DOMCDATASection(this);

  // assign values to properties (and aliases)
  node.data      = data;
  node.nodeValue = data;

  // set initial length
  node.length    = data.length;

  return node;
};

/**
 * @method DOMDocument.createProcessingInstruction - Creates a ProcessingInstruction node given the specified target and data strings.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  target : string - The target part of the processing instruction.
 * @param  data   : string - The data for the node.
 *
 * @throws : DOMException - INVALID_CHARACTER_ERR: Raised if the string contains an illegal character
 *
 * @return : DOMProcessingInstruction - The new ProcessingInstruction object.
 */
DOMDocument.prototype.createProcessingInstruction = function DOMDocument_createProcessingInstruction(target, data) {
  // throw Exception if the target string contains an illegal character
  if (this.ownerDocument.implementation.errorChecking && (!this.implementation._isValidName(target))) {
    throw(new DOMException(DOMException.INVALID_CHARACTER_ERR));
  }

  // create DOMProcessingInstruction specifying 'this' as ownerDocument
  var node = new DOMProcessingInstruction(this);

  // assign values to properties (and aliases)
  node.target    = target;
  node.nodeName  = target;
  node.data      = data;
  node.nodeValue = data;

  // set initial length
  node.length    = data.length;

  return node;
};

/**
 * @method DOMDocument.createAttribute - Creates an Attr of the given name
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  name : string - The name of the attribute.
 *
 * @throws : DOMException - INVALID_CHARACTER_ERR: Raised if the string contains an illegal character
 *
 * @return : DOMAttr - The new Attr object.
 */
DOMDocument.prototype.createAttribute = function DOMDocument_createAttribute(name) {
  // throw Exception if the name string contains an illegal character
  if (this.ownerDocument.implementation.errorChecking && (!this.ownerDocument.implementation._isValidName(name))) {
    throw(new DOMException(DOMException.INVALID_CHARACTER_ERR));
  }

  // create DOMAttr specifying 'this' as ownerDocument
  var node = new DOMAttr(this);

  // assign values to properties (and aliases)
  node.name     = name;
  node.nodeName = name;

  return node;
};

/**
 * @method DOMDocument.createElementNS - Creates an element of the type specified,
 *   within the specified namespace.
 *   Note that the instance returned implements the Element interface,
 *   so attributes can be specified directly on the returned object.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  namespaceURI  : string - The namespace URI of the element.
 * @param  qualifiedName : string - The qualified name of the element type to instantiate.
 *
 * @throws : DOMException - NAMESPACE_ERR: Raised if the Namespace is invalid
 * @throws : DOMException - INVALID_CHARACTER_ERR: Raised if the string contains an illegal character
 *
 * @return : DOMElement - The new Element object.
 */
DOMDocument.prototype.createElementNS = function DOMDocument_createElementNS(namespaceURI, qualifiedName) {
  // test for exceptions
  if (this.ownerDocument.implementation.errorChecking) {
    // throw Exception if the Namespace is invalid
    if (!this.ownerDocument._isValidNamespace(namespaceURI, qualifiedName)) {
      throw(new DOMException(DOMException.NAMESPACE_ERR));
    }

    // throw Exception if the qualifiedName string contains an illegal character
    if (!this.ownerDocument.implementation._isValidName(qualifiedName)) {
      throw(new DOMException(DOMException.INVALID_CHARACTER_ERR));
    }
  }

  // create DOMElement specifying 'this' as ownerDocument
  var node  = new DOMElement(this);
  var qname = this.implementation._parseQName(qualifiedName);

  // assign values to properties (and aliases)
  node.nodeName     = qualifiedName;
  node.namespaceURI = namespaceURI;
  node.prefix       = qname.prefix;
  node.localName    = qname.localName;
  node.tagName      = qualifiedName;

  // add Element to 'all' collection
  this.all[this.all.length] = node;

  return node;
};

/**
 * @method DOMDocument.createAttributeNS - Creates an Attr of the given name
 *   within the specified namespace.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  namespaceURI  : string - The namespace URI of the attribute.
 * @param  qualifiedName : string - The qualified name of the attribute.
 *
 * @throws : DOMException - NAMESPACE_ERR: Raised if the Namespace is invalid
 * @throws : DOMException - INVALID_CHARACTER_ERR: Raised if the string contains an illegal character
 *
 * @return : DOMAttr - The new Attr object.
 */
DOMDocument.prototype.createAttributeNS = function DOMDocument_createAttributeNS(namespaceURI, qualifiedName) {
  // test for exceptions
  if (this.ownerDocument.implementation.errorChecking) {
    // throw Exception if the Namespace is invalid
    if (!this.ownerDocument._isValidNamespace(namespaceURI, qualifiedName, true)) {
      throw(new DOMException(DOMException.NAMESPACE_ERR));
    }

    // throw Exception if the qualifiedName string contains an illegal character
    if (!this.ownerDocument.implementation._isValidName(qualifiedName)) {
      throw(new DOMException(DOMException.INVALID_CHARACTER_ERR));
    }
  }

  // create DOMAttr specifying 'this' as ownerDocument
  var node  = new DOMAttr(this);
  var qname = this.implementation._parseQName(qualifiedName);

  // assign values to properties (and aliases)
  node.nodeName     = qualifiedName
  node.namespaceURI = namespaceURI
  node.prefix       = qname.prefix;
  node.localName    = qname.localName;
  node.name         = qualifiedName
  node.nodeValue    = "";

  return node;
};

/**
 * @method DOMDocument.createNamespace - Creates an Namespace of the given name
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  qualifiedName : string - The qualified name of the attribute.
 *
 * @return : DOMNamespace - The new Namespace object.
 */
DOMDocument.prototype.createNamespace = function DOMDocument_createNamespace(qualifiedName) {
  // create DOMNamespace specifying 'this' as ownerDocument
  var node  = new DOMNamespace(this);
  var qname = this.implementation._parseQName(qualifiedName);

  // assign values to properties (and aliases)
  node.nodeName     = qualifiedName
  node.prefix       = qname.prefix;
  node.localName    = qname.localName;
  node.name         = qualifiedName
  node.nodeValue    = "";

  return node;
};

/**
 * @method DOMDocument.getElementById - Return the Element whose ID is given by elementId
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  elementId : string - The unique ID of the Element
 *
 * @return : DOMElement - The requested DOMElement
 */
DOMDocument.prototype.getElementById = function DOMDocument_getElementById(elementId) {
//  return this._ids[elementId];
  retNode = null;

  // loop through all Elements in the 'all' collection
  for (var i=0; i < this.all.length; i++) {
    var node = this.all[i];

    // if id matches & node is alive (ie, connected (in)directly to the documentElement)
    if ((node.id == elementId) && (node._isAncestor(node.ownerDocument.documentElement))) {
      retNode = node;
      break;
    }
  }

  return retNode;
};



/**
 * @method DOMDocument._genId - generate a unique internal id
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : string - The unique (serial) id
 */
DOMDocument.prototype._genId = function DOMDocument__genId() {
  this._lastId += 1;                             // increment lastId (to generate unique id)

  return this._lastId;
};


/**
 * @method DOMDocument._isValidNamespace - test if Namespace is valid
 *  ie, not valid if;
 *    the qualifiedName is malformed, or
 *    the qualifiedName has a prefix and the namespaceURI is null, or
 *    the qualifiedName has a prefix that is "xml" and the namespaceURI is
 *     different from "http://www.w3.org/XML/1998/namespace" [Namespaces].
 *
 * @author Jon van Noort (jon@webarcana.com.au), David Joham (djoham@yahoo.com) and Scott Severtson
 *
 * @param  namespaceURI  : string - the namespace URI
 * @param  qualifiedName : string - the QName
 * @Param  isAttribute   : boolean - true, if the requesting node is an Attr
 *
 * @return : boolean
 */
DOMDocument.prototype._isValidNamespace = function DOMDocument__isValidNamespace(namespaceURI, qualifiedName, isAttribute) {

  if (this._performingImportNodeOperation == true) {
    //we're doing an importNode operation (or a cloneNode) - in both cases, there
    //is no need to perform any namespace checking since the nodes have to have been valid
    //to have gotten into the DOM in the first place
    return true;
  }

  var valid = true;
  // parse QName
  var qName = this.implementation._parseQName(qualifiedName);


  //only check for namespaces if we're finished parsing
  if (this._parseComplete == true) {

    // if the qualifiedName is malformed
    if (qName.localName.indexOf(":") > -1 ){
        valid = false;
    }

    if ((valid) && (!isAttribute)) {
        // if the namespaceURI is not null
        if (!namespaceURI) {
        valid = false;
        }
    }

    // if the qualifiedName has a prefix
    if ((valid) && (qName.prefix == "")) {
        valid = false;
    }

  }

  // if the qualifiedName has a prefix that is "xml" and the namespaceURI is
  //  different from "http://www.w3.org/XML/1998/namespace" [Namespaces].
  if ((valid) && (qName.prefix == "xml") && (namespaceURI != "http://www.w3.org/XML/1998/namespace")) {
    valid = false;
  }

  return valid;
}

/**
 * @method DOMDocument.toString - Serialize the document into an XML string
 *
 * @author David Joham (djoham@yahoo.com)
 *
 * @return : string
 */
DOMDocument.prototype.toString = function DOMDocument_toString() {
  return "" + this.childNodes;
} // end function getXML


/**
 * @class  DOMElement - By far the vast majority of objects (apart from text) that authors encounter
 *   when traversing a document are Element nodes.
 *
 * @extends DOMNode
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  ownerDocument : DOMDocument - The Document object associated with this node.
 */
DOMElement = function(ownerDocument) {
  this._class = addClass(this._class, "DOMElement");
  this.DOMNode  = DOMNode;
  this.DOMNode(ownerDocument);

  this.tagName = "";                             // The name of the element.
  this.id = "";                                  // the ID of the element

  this.nodeType = DOMNode.ELEMENT_NODE;
};
DOMElement.prototype = new DOMNode;

/**
 * @method DOMElement.getTagName - Java style gettor for .TagName
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : string
 */
DOMElement.prototype.getTagName = function DOMElement_getTagName() {
  return this.tagName;
};

/**
 * @method DOMElement.getAttribute - Retrieves an attribute value by name
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  name : string - The name of the attribute to retrieve
 *
 * @return : string - The Attr value as a string, or the empty string if that attribute does not have a specified value.
 */
DOMElement.prototype.getAttribute = function DOMElement_getAttribute(name) {
  var ret = "";

  // if attribute exists, use it
  var attr = this.attributes.getNamedItem(name);

  if (attr) {
    ret = attr.value;
  }

  return ret; // if Attribute exists, return its value, otherwise, return ""
};

/**
 * @method DOMElement.setAttribute - Retrieves an attribute value by name
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  name  : string - The name of the attribute to create or alter
 * @param  value : string - Value to set in string form
 *
 * @throws : DOMException - INVALID_CHARACTER_ERR: Raised if the string contains an illegal character
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if the Attribute is readonly.
 */
DOMElement.prototype.setAttribute = function DOMElement_setAttribute(name, value) {
  // if attribute exists, use it
  var attr = this.attributes.getNamedItem(name);

  if (!attr) {
    attr = this.ownerDocument.createAttribute(name);  // otherwise create it
  }

  var value = new String(value);

  // test for exceptions
  if (this.ownerDocument.implementation.errorChecking) {
    // throw Exception if Attribute is readonly
    if (attr._readonly) {
      throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
    }

    // throw Exception if the value string contains an illegal character
    if (!this.ownerDocument.implementation._isValidString(value)) {
      throw(new DOMException(DOMException.INVALID_CHARACTER_ERR));
    }
  }

  if (this.ownerDocument.implementation._isIdDeclaration(name)) {
    this.id = value;  // cache ID for getElementById()
  }

  // assign values to properties (and aliases)
  attr.value     = value;
  attr.nodeValue = value;

  // update .specified
  if (value.length > 0) {
    attr.specified = true;
  }
  else {
    attr.specified = false;
  }

  // add/replace Attribute in NamedNodeMap
  this.attributes.setNamedItem(attr);
};

/**
 * @method DOMElement.removeAttribute - Removes an attribute by name
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  name  : string - The name of the attribute to remove
 *
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if the Attrbute is readonly.
 */
DOMElement.prototype.removeAttribute = function DOMElement_removeAttribute(name) {
  // delegate to DOMNamedNodeMap.removeNamedItem
  return this.attributes.removeNamedItem(name);
};

/**
 * @method DOMElement.getAttributeNode - Retrieves an Attr node by name
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  name  : string - The name of the attribute to remove
 *
 * @return : DOMAttr - The Attr node with the specified attribute name or null if there is no such attribute.
 */
DOMElement.prototype.getAttributeNode = function DOMElement_getAttributeNode(name) {
  // delegate to DOMNamedNodeMap.getNamedItem
  return this.attributes.getNamedItem(name);
};

/**
 * @method DOMElement.setAttributeNode - Adds a new attribute
 *   If an attribute with that name is already present in the element, it is replaced by the new one
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  newAttr : DOMAttr - The attribute node to be attached
 *
 * @throws : DOMException - WRONG_DOCUMENT_ERR: Raised if arg was created from a different document than the one that created this map.
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if this Element is readonly.
 * @throws : DOMException - INUSE_ATTRIBUTE_ERR: Raised if arg is an Attr that is already an attribute of another Element object.
 *
 * @return : DOMAttr - If the newAttr attribute replaces an existing attribute with the same name,
 *   the previously existing Attr node is returned, otherwise null is returned.
 */
DOMElement.prototype.setAttributeNode = function DOMElement_setAttributeNode(newAttr) {
  // if this Attribute is an ID
  if (this.ownerDocument.implementation._isIdDeclaration(newAttr.name)) {
    this.id = newAttr.value;  // cache ID for getElementById()
  }

  // delegate to DOMNamedNodeMap.setNamedItem
  return this.attributes.setNamedItem(newAttr);
};

/**
 * @method DOMElement.removeAttributeNode - Removes the specified attribute
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  oldAttr  : DOMAttr - The Attr node to remove from the attribute list
 *
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if this Element is readonly.
 * @throws : DOMException - INUSE_ATTRIBUTE_ERR: Raised if arg is an Attr that is already an attribute of another Element object.
 *
 * @return : DOMAttr - The Attr node that was removed.
 */
DOMElement.prototype.removeAttributeNode = function DOMElement_removeAttributeNode(oldAttr) {
  // throw Exception if Attribute is readonly
  if (this.ownerDocument.implementation.errorChecking && oldAttr._readonly) {
    throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
  }

  // get item index
  var itemIndex = this.attributes._findItemIndex(oldAttr._id);

  // throw Exception if node does not exist in this map
  if (this.ownerDocument.implementation.errorChecking && (itemIndex < 0)) {
    throw(new DOMException(DOMException.NOT_FOUND_ERR));
  }

  return this.attributes._removeChild(itemIndex);
};

/**
 * @method DOMElement.getAttributeNS - Retrieves an attribute value by namespaceURI and localName
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  namespaceURI : string - the namespace URI of the required node
 * @param  localName    : string - the local name of the required node
 *
 * @return : string - The Attr value as a string, or the empty string if that attribute does not have a specified value.
 */
DOMElement.prototype.getAttributeNS = function DOMElement_getAttributeNS(namespaceURI, localName) {
  var ret = "";

  // delegate to DOMNAmedNodeMap.getNamedItemNS
  var attr = this.attributes.getNamedItemNS(namespaceURI, localName);


  if (attr) {
    ret = attr.value;
  }

  return ret;  // if Attribute exists, return its value, otherwise return ""
};

/**
 * @method DOMElement.setAttributeNS - Sets an attribute value by namespaceURI and localName
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  namespaceURI : string - the namespace URI of the required node
 * @param  qualifiedName : string - the qualified name of the required node
 * @param  value        : string - Value to set in string form
 *
 * @throws : DOMException - INVALID_CHARACTER_ERR: Raised if the string contains an illegal character
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if the Attrbute is readonly.
 * @throws : DOMException - NAMESPACE_ERR: Raised if the Namespace is invalid
 */
DOMElement.prototype.setAttributeNS = function DOMElement_setAttributeNS(namespaceURI, qualifiedName, value) {
  // call DOMNamedNodeMap.getNamedItem
  var attr = this.attributes.getNamedItem(namespaceURI, qualifiedName);

  if (!attr) {  // if Attribute exists, use it
    // otherwise create it
    attr = this.ownerDocument.createAttributeNS(namespaceURI, qualifiedName);
  }

  var value = new String(value);

  // test for exceptions
  if (this.ownerDocument.implementation.errorChecking) {
    // throw Exception if Attribute is readonly
    if (attr._readonly) {
      throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
    }

    // throw Exception if the Namespace is invalid
    if (!this.ownerDocument._isValidNamespace(namespaceURI, qualifiedName)) {
      throw(new DOMException(DOMException.NAMESPACE_ERR));
    }

    // throw Exception if the value string contains an illegal character
    if (!this.ownerDocument.implementation._isValidString(value)) {
      throw(new DOMException(DOMException.INVALID_CHARACTER_ERR));
    }
  }

  // if this Attribute is an ID
  if (this.ownerDocument.implementation._isIdDeclaration(name)) {
    this.id = value;  // cache ID for getElementById()
  }

  // assign values to properties (and aliases)
  attr.value     = value;
  attr.nodeValue = value;

  // update .specified
  if (value.length > 0) {
    attr.specified = true;
  }
  else {
    attr.specified = false;
  }

  // delegate to DOMNamedNodeMap.setNamedItem
  this.attributes.setNamedItemNS(attr);
};

/**
 * @method DOMElement.removeAttributeNS - Removes an attribute by namespaceURI and localName
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  namespaceURI : string - the namespace URI of the required node
 * @param  localName    : string - the local name of the required node
 *
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if the Attrbute is readonly.
 *
 * @return : DOMAttr
 */
DOMElement.prototype.removeAttributeNS = function DOMElement_removeAttributeNS(namespaceURI, localName) {
  // delegate to DOMNamedNodeMap.removeNamedItemNS
  return this.attributes.removeNamedItemNS(namespaceURI, localName);
};

/**
 * @method DOMElement.getAttributeNodeNS - Retrieves an Attr node by namespaceURI and localName
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  namespaceURI : string - the namespace URI of the required node
 * @param  localName    : string - the local name of the required node
 *
 * @return : DOMAttr - The Attr node with the specified attribute name or null if there is no such attribute.
 */
DOMElement.prototype.getAttributeNodeNS = function DOMElement_getAttributeNodeNS(namespaceURI, localName) {
  // delegate to DOMNamedNodeMap.getNamedItemNS
  return this.attributes.getNamedItemNS(namespaceURI, localName);
};

/**
 * @method DOMElement.setAttributeNodeNS - Adds a new attribute
 *   If an attribute with that name is already present in the element, it is replaced by the new one
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  newAttr      : DOMAttr - the attribute node to be attached
 *
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if the Attrbute is readonly.
 * @throws : DOMException - WRONG_DOCUMENT_ERR: Raised if arg was created from a different document than the one that created this map.
 * @throws : DOMException - INUSE_ATTRIBUTE_ERR: Raised if arg is an Attr that is already an attribute of another Element object.
 *  The DOM user must explicitly clone Attr nodes to re-use them in other elements.
 *
 * @return : DOMAttr - If the newAttr attribute replaces an existing attribute with the same name,
 *   the previously existing Attr node is returned, otherwise null is returned.
 */
DOMElement.prototype.setAttributeNodeNS = function DOMElement_setAttributeNodeNS(newAttr) {
  // if this Attribute is an ID
  if ((newAttr.prefix == "") &&  this.ownerDocument.implementation._isIdDeclaration(newAttr.name)) {
    this.id = newAttr.value;  // cache ID for getElementById()
  }

  // delegate to DOMNamedNodeMap.setNamedItemNS
  return this.attributes.setNamedItemNS(newAttr);
};

/**
 * @method DOMElement.hasAttribute - Returns true if specified node exists
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  name : string - the name of the required node
 *
 * @return : boolean
 */
DOMElement.prototype.hasAttribute = function DOMElement_hasAttribute(name) {
  // delegate to DOMNamedNodeMap._hasAttribute
  return this.attributes._hasAttribute(name);
}

/**
 * @method DOMElement.hasAttributeNS - Returns true if specified node exists
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  namespaceURI : string - the namespace URI of the required node
 * @param  localName    : string - the local name of the required node
 *
 * @return : boolean
 */
DOMElement.prototype.hasAttributeNS = function DOMElement_hasAttributeNS(namespaceURI, localName) {
  // delegate to DOMNamedNodeMap._hasAttributeNS
  return this.attributes._hasAttributeNS(namespaceURI, localName);
}

/**
 * @method DOMElement.toString - Serialize this Element and its children into an XML string
 *
 * @author Jon van Noort (jon@webarcana.com.au) and David Joham (djoham@yahoo.com)
 *
 * @return : string
 */
DOMElement.prototype.toString = function DOMElement_toString() {
  var ret = "";

  // serialize namespace declarations
  var ns = this._namespaces.toString();
  if (ns.length > 0) ns = " "+ ns;

  // serialize Attribute declarations
  var attrs = this.attributes.toString();
  if (attrs.length > 0) attrs = " "+ attrs;

  // serialize this Element
  ret += "<" + this.nodeName + ns + attrs +">";
  ret += this.childNodes.toString();;
  ret += "</" + this.nodeName+">";

  return ret;
}

/**
 * @class  DOMAttr - The Attr interface represents an attribute in an Element object
 *
 * @extends DOMNode
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  ownerDocument : DOMDocument - The Document object associated with this node.
 */
DOMAttr = function(ownerDocument) {
  this._class = addClass(this._class, "DOMAttr");
  this.DOMNode = DOMNode;
  this.DOMNode(ownerDocument);

  this.name      = "";                           // the name of this attribute

  // If this attribute was explicitly given a value in the original document, this is true; otherwise, it is false.
  // Note that the implementation is in charge of this attribute, not the user.
  // If the user changes the value of the attribute (even if it ends up having the same value as the default value)
  // then the specified flag is automatically flipped to true
  // (I wish! You will need to use setValue to 'automatically' update specified)
  this.specified = false;

  this.value     = "";                           // the value of the attribute is returned as a string

  this.nodeType  = DOMNode.ATTRIBUTE_NODE;

  this.ownerElement = null;                      // set when Attr is added to NamedNodeMap

  // disable childNodes
  this.childNodes = null;
  this.attributes = null;
};
DOMAttr.prototype = new DOMNode;

/**
 * @method DOMAttr.getName - Java style gettor for .name
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : string
 */
DOMAttr.prototype.getName = function DOMAttr_getName() {
  return this.nodeName;
};

/**
 * @method DOMAttr.getSpecified - Java style gettor for .specified
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : boolean
 */
DOMAttr.prototype.getSpecified = function DOMAttr_getSpecified() {
  return this.specified;
};

/**
 * @method DOMAttr.getValue - Java style gettor for .value
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : string
 */
DOMAttr.prototype.getValue = function DOMAttr_getValue() {
  return this.nodeValue;
};

/**
 * @method DOMAttr.setValue - Java style settor for .value
 *   alias for DOMAttr.setNodeValue
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  value : string - the new attribute value
 *
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if this Attribute is readonly.
 */
DOMAttr.prototype.setValue = function DOMAttr_setValue(value) {
  // throw Exception if Attribute is readonly
  if (this.ownerDocument.implementation.errorChecking && this._readonly) {
    throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
  }

  // delegate to setNodeValue
  this.setNodeValue(value);
};

/**
 * @method DOMAttr.setNodeValue - Java style settor for .nodeValue
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  value : string - the new attribute value
 */
DOMAttr.prototype.setNodeValue = function DOMAttr_setNodeValue(value) {
  this.nodeValue = new String(value);
  this.value     = this.nodeValue;
  this.specified = (this.value.length > 0);
};

/**
 * @method DOMAttr.toString - Serialize this Attr into an XML string
 *
 * @author Jon van Noort (jon@webarcana.com.au) and David Joham (djoham@yahoo.com)
 *
 * @return : string
 */
DOMAttr.prototype.toString = function DOMAttr_toString() {
  var ret = "";

  // serialize Attribute
  ret += this.nodeName +"=\""+ this.__escapeString(this.nodeValue) +"\"";

  return ret;
}

DOMAttr.prototype.getOwnerElement = function() {

    return this.ownerElement;

}

/**
 * @class  DOMNamespace - The Namespace interface represents an namespace in an Element object
 *
 * @extends DOMNode
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  ownerDocument : DOMDocument - The Document object associated with this node.
 */
DOMNamespace = function(ownerDocument) {
  this._class = addClass(this._class, "DOMNamespace");
  this.DOMNode = DOMNode;
  this.DOMNode(ownerDocument);

  this.name      = "";                           // the name of this attribute

  // If this attribute was explicitly given a value in the original document, this is true; otherwise, it is false.
  // Note that the implementation is in charge of this attribute, not the user.
  // If the user changes the value of the attribute (even if it ends up having the same value as the default value)
  // then the specified flag is automatically flipped to true
  // (I wish! You will need to use _setValue to 'automatically' update specified)
  this.specified = false;

  this.value     = "";                           // the value of the attribute is returned as a string

  this.nodeType  = DOMNode.NAMESPACE_NODE;
};
DOMNamespace.prototype = new DOMNode;

/**
 * @method DOMNamespace.getValue - Java style gettor for .value
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : string
 */
DOMNamespace.prototype.getValue = function DOMNamespace_getValue() {
  return this.nodeValue;
};

/**
 * @method DOMNamespace.setValue - utility function to set value (rather than direct assignment to .value)
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  value : string - the new namespace value
 */
DOMNamespace.prototype.setValue = function DOMNamespace_setValue(value) {
  // assign values to properties (and aliases)
  this.nodeValue = new String(value);
  this.value     = this.nodeValue;
};

/**
 * @method DOMNamespace.toString - Serialize this Attr into an XML string
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : string
 */
DOMNamespace.prototype.toString = function DOMNamespace_toString() {
  var ret = "";

  // serialize Namespace Declaration
  if (this.nodeName != "") {
    ret += this.nodeName +"=\""+ this.__escapeString(this.nodeValue) +"\"";
  }
  else {  // handle default namespace
    ret += "xmlns=\""+ this.__escapeString(this.nodeValue) +"\"";
  }

  return ret;
}

/**
 * @class  DOMCharacterData - parent abstract class for DOMText and DOMComment
 *
 * @extends DOMNode
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  ownerDocument : DOMDocument - The Document object associated with this node.
 */
DOMCharacterData = function(ownerDocument) {
  this._class = addClass(this._class, "DOMCharacterData");
  this.DOMNode  = DOMNode;
  this.DOMNode(ownerDocument);

  this.data   = "";
  this.length = 0;
};
DOMCharacterData.prototype = new DOMNode;

/**
 * @method DOMCharacterData.getData - Java style gettor for .data
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : string
 */
DOMCharacterData.prototype.getData = function DOMCharacterData_getData() {
  return this.nodeValue;
};

/**
 * @method DOMCharacterData.setData - Java style settor for .data
 *  alias for DOMCharacterData.setNodeValue
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  data : string - the character data
 *
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if this Attribute is readonly.
 */
DOMCharacterData.prototype.setData = function DOMCharacterData_setData(data) {
  // delegate to setNodeValue
  this.setNodeValue(data);
};

/**
 * @method DOMCharacterData.setNodeValue - Java style settor for .nodeValue
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  data : string - the node value
 *
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if this Attribute is readonly.
 */
DOMCharacterData.prototype.setNodeValue = function DOMCharacterData_setNodeValue(data) {
  // throw Exception if Attribute is readonly
  if (this.ownerDocument.implementation.errorChecking && this._readonly) {
    throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
  }

  // assign values to properties (and aliases)
  this.nodeValue = new String(data);
  this.data   = this.nodeValue;

  // update length
  this.length = this.nodeValue.length;
};

/**
 * @method DOMCharacterData.getLength - Java style gettor for .length
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : string
 */
DOMCharacterData.prototype.getLength = function DOMCharacterData_getLength() {
  return this.nodeValue.length;
};

/**
 * @method DOMCharacterData.substringData - Extracts a range of data from the node
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  offset : int - Start offset of substring to extract
 * @param  count  : int - The number of characters to extract
 *
 * @throws : DOMException - INDEX_SIZE_ERR: Raised if specified offset is negative or greater than the number of 16-bit units in data,
 *
 * @return : string - The specified substring.
 *   If the sum of offset and count exceeds the length, then all characters to the end of the data are returned.
 */
DOMCharacterData.prototype.substringData = function DOMCharacterData_substringData(offset, count) {
  var ret = null;

  if (this.data) {
    // throw Exception if offset is negative or greater than the data length,
    // or the count is negative
    if (this.ownerDocument.implementation.errorChecking && ((offset < 0) || (offset > this.data.length) || (count < 0))) {
      throw(new DOMException(DOMException.INDEX_SIZE_ERR));
    }

    // if count is not specified
    if (!count) {
      ret = this.data.substring(offset); // default to 'end of string'
    }
    else {
      ret = this.data.substring(offset, offset + count);
    }
  }

  return ret;
};

/**
 * @method DOMCharacterData.appendData - Append the string to the end of the character data of the node.
 *   Upon success, data provides access to the concatenation of data and the DOMString specified.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  arg : string - The string to append
 *
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if this CharacterData is readonly.
 */
DOMCharacterData.prototype.appendData    = function DOMCharacterData_appendData(arg) {
  // throw Exception if DOMCharacterData is readonly
  if (this.ownerDocument.implementation.errorChecking && this._readonly) {
    throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
  }

  // append data
  this.setData(""+ this.data + arg);
};

/**
 * @method DOMCharacterData.insertData - Insert a string at the specified character offset.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  offset : int    - The character offset at which to insert
 * @param  arg    : string - The string to insert
 *
 * @throws : DOMException - INDEX_SIZE_ERR: Raised if specified offset is negative or greater than the number of 16-bit units in data,
 *   or if the specified count is negative.
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if this CharacterData is readonly.
 */
DOMCharacterData.prototype.insertData    = function DOMCharacterData_insertData(offset, arg) {
  // throw Exception if DOMCharacterData is readonly
  if (this.ownerDocument.implementation.errorChecking && this._readonly) {
    throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
  }

  if (this.data) {
    // throw Exception if offset is negative or greater than the data length,
    if (this.ownerDocument.implementation.errorChecking && ((offset < 0) || (offset >  this.data.length))) {
      throw(new DOMException(DOMException.INDEX_SIZE_ERR));
    }

    // insert data
    this.setData(this.data.substring(0, offset).concat(arg, this.data.substring(offset)));
  }
  else {
    // throw Exception if offset is negative or greater than the data length,
    if (this.ownerDocument.implementation.errorChecking && (offset != 0)) {
      throw(new DOMException(DOMException.INDEX_SIZE_ERR));
    }

    // set data
    this.setData(arg);
  }
};

/**
 * @method DOMCharacterData.deleteData - Remove a range of characters from the node.
 *   Upon success, data and length reflect the change
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  offset : int - The offset from which to remove characters
 * @param  count  : int - The number of characters to delete.
 *   If the sum of offset and count exceeds length then all characters from offset to the end of the data are deleted
 *
 * @throws : DOMException - INDEX_SIZE_ERR: Raised if specified offset is negative or greater than the number of 16-bit units in data,
 *   or if the specified count is negative.
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if this CharacterData is readonly.
 */
DOMCharacterData.prototype.deleteData    = function DOMCharacterData_deleteData(offset, count) {
  // throw Exception if DOMCharacterData is readonly
  if (this.ownerDocument.implementation.errorChecking && this._readonly) {
    throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
  }

  if (this.data) {
    // throw Exception if offset is negative or greater than the data length,
    if (this.ownerDocument.implementation.errorChecking && ((offset < 0) || (offset >  this.data.length) || (count < 0))) {
      throw(new DOMException(DOMException.INDEX_SIZE_ERR));
    }

    // delete data
    if(!count || (offset + count) > this.data.length) {
      this.setData(this.data.substring(0, offset));
    }
    else {
      this.setData(this.data.substring(0, offset).concat(this.data.substring(offset + count)));
    }
  }
};

/**
 * @method DOMCharacterData.replaceData - Replace the characters starting at the specified character offset with the specified string
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  offset : int    - The offset from which to start replacing
 * @param  count  : int    - The number of characters to replace.
 *   If the sum of offset and count exceeds length, then all characters to the end of the data are replaced
 * @param  arg    : string - The string with which the range must be replaced
 *
 * @throws : DOMException - INDEX_SIZE_ERR: Raised if specified offset is negative or greater than the number of 16-bit units in data,
 *   or if the specified count is negative.
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if this CharacterData is readonly.
 */
DOMCharacterData.prototype.replaceData   = function DOMCharacterData_replaceData(offset, count, arg) {
  // throw Exception if DOMCharacterData is readonly
  if (this.ownerDocument.implementation.errorChecking && this._readonly) {
    throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
  }

  if (this.data) {
    // throw Exception if offset is negative or greater than the data length,
    if (this.ownerDocument.implementation.errorChecking && ((offset < 0) || (offset >  this.data.length) || (count < 0))) {
      throw(new DOMException(DOMException.INDEX_SIZE_ERR));
    }

    // replace data
    this.setData(this.data.substring(0, offset).concat(arg, this.data.substring(offset + count)));
  }
  else {
    // set data
    this.setData(arg);
  }
};

/**
 * @class  DOMText - The Text interface represents the textual content (termed character data in XML) of an Element or Attr.
 *   If there is no markup inside an element's content, the text is contained in a single object implementing the Text interface
 *   that is the only child of the element. If there is markup, it is parsed into a list of elements and Text nodes that form the
 *   list of children of the element.
 *
 * @extends DOMCharacterData
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  ownerDocument : DOMDocument - The Document object associated with this node.
 */
DOMText = function(ownerDocument) {
  this._class = addClass(this._class, "DOMText");
  this.DOMCharacterData  = DOMCharacterData;
  this.DOMCharacterData(ownerDocument);

  this.nodeName  = "#text";
  this.nodeType  = DOMNode.TEXT_NODE;
};
DOMText.prototype = new DOMCharacterData;

/**
 * @method DOMText.splitText - Breaks this Text node into two Text nodes at the specified offset,
 *   keeping both in the tree as siblings. This node then only contains all the content up to the offset point.
 *   And a new Text node, which is inserted as the next sibling of this node, contains all the content at and after the offset point.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  offset : int - The offset at which to split, starting from 0.
 *
 * @throws : DOMException - INDEX_SIZE_ERR: Raised if specified offset is negative or greater than the number of 16-bit units in data,
 * @throws : DOMException - NO_MODIFICATION_ALLOWED_ERR: Raised if this Text is readonly.
 *
 * @return : DOMText - The new Text node
 */
DOMText.prototype.splitText = function DOMText_splitText(offset) {
  var data, inode;

  // test for exceptions
  if (this.ownerDocument.implementation.errorChecking) {
    // throw Exception if Node is readonly
    if (this._readonly) {
      throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
    }

    // throw Exception if offset is negative or greater than the data length,
    if ((offset < 0) || (offset > this.data.length)) {
      throw(new DOMException(DOMException.INDEX_SIZE_ERR));
    }
  }

  if (this.parentNode) {
    // get remaining string (after offset)
    data  = this.substringData(offset);

    // create new TextNode with remaining string
    inode = this.ownerDocument.createTextNode(data);

    // attach new TextNode
    if (this.nextSibling) {
      this.parentNode.insertBefore(inode, this.nextSibling);
    }
    else {
      this.parentNode.appendChild(inode);
    }

    // remove remaining string from original TextNode
    this.deleteData(offset);
  }

  return inode;
};

/**
 * @method DOMText.toString - Serialize this Text into an XML string
 *
 * @author Jon van Noort (jon@webarcana.com.au) and David Joham (djoham@yahoo.com)
 *
 * @return : string
 */
DOMText.prototype.toString = function DOMText_toString() {
  return this.__escapeString(""+ this.nodeValue);
}

/**
 * @class  DOMCDATASection - CDATA sections are used to escape blocks of text containing characters that would otherwise be regarded as markup.
 *   The only delimiter that is recognized in a CDATA section is the "\]\]\>" string that ends the CDATA section
 *
 * @extends DOMCharacterData
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  ownerDocument : DOMDocument - The Document object associated with this node.
 */
DOMCDATASection = function(ownerDocument) {
  this._class = addClass(this._class, "DOMCDATASection");
  this.DOMCharacterData  = DOMCharacterData;
  this.DOMCharacterData(ownerDocument);

  this.nodeName  = "#cdata-section";
  this.nodeType  = DOMNode.CDATA_SECTION_NODE;
};
DOMCDATASection.prototype = new DOMCharacterData;

/**
 * @method DOMCDATASection.splitText - Breaks this CDATASection node into two CDATASection nodes at the specified offset,
 *   keeping both in the tree as siblings. This node then only contains all the content up to the offset point.
 *   And a new CDATASection node, which is inserted as the next sibling of this node, contains all the content at and after the offset point.
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  offset : int - The offset at which to split, starting from 0.
 *
 * @return : DOMCDATASection - The new CDATASection node
 */
DOMCDATASection.prototype.splitText = function DOMCDATASection_splitText(offset) {
  var data, inode;

  // test for exceptions
  if (this.ownerDocument.implementation.errorChecking) {
    // throw Exception if Node is readonly
    if (this._readonly) {
      throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
    }

    // throw Exception if offset is negative or greater than the data length,
    if ((offset < 0) || (offset > this.data.length)) {
      throw(new DOMException(DOMException.INDEX_SIZE_ERR));
    }
  }

  if(this.parentNode) {
    // get remaining string (after offset)
    data  = this.substringData(offset);

    // create new CDATANode with remaining string
    inode = this.ownerDocument.createCDATASection(data);

    // attach new CDATANode
    if (this.nextSibling) {
      this.parentNode.insertBefore(inode, this.nextSibling);
    }
    else {
      this.parentNode.appendChild(inode);
    }

     // remove remaining string from original CDATANode
    this.deleteData(offset);
  }

  return inode;
};

/**
 * @method DOMCDATASection.toString - Serialize this CDATASection into an XML string
 *
 * @author Jon van Noort (jon@webarcana.com.au) and David Joham (djoham@yahoo.com)
 *
 * @return : string
 */
DOMCDATASection.prototype.toString = function DOMCDATASection_toString() {
  var ret = "";
  //do NOT unescape the nodeValue string in CDATA sections!
  ret += "<![CDATA[" + this.nodeValue + "\]\]\>";

  return ret;
}

/**
 * @class  DOMComment - This represents the content of a comment, i.e., all the characters between the starting '<!--' and ending '-->'
 *
 * @extends DOMCharacterData
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  ownerDocument : DOMDocument - The Document object associated with this node.
 */
DOMComment = function(ownerDocument) {
  this._class = addClass(this._class, "DOMComment");
  this.DOMCharacterData  = DOMCharacterData;
  this.DOMCharacterData(ownerDocument);

  this.nodeName  = "#comment";
  this.nodeType  = DOMNode.COMMENT_NODE;
};
DOMComment.prototype = new DOMCharacterData;

/**
 * @method DOMComment.toString - Serialize this Comment into an XML string
 *
 * @author Jon van Noort (jon@webarcana.com.au) and David Joham (djoham@yahoo.com)
 *
 * @return : string
 */
DOMComment.prototype.toString = function DOMComment_toString() {
  var ret = "";

  ret += "<!--" + this.nodeValue + "-->";

  return ret;
}

/**
 * @class  DOMProcessingInstruction - The ProcessingInstruction interface represents a "processing instruction",
 *   used in XML as a way to keep processor-specific information in the text of the document
 *
 * @extends DOMNode
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  ownerDocument : DOMDocument - The Document object associated with this node.
 */
DOMProcessingInstruction = function(ownerDocument) {
  this._class = addClass(this._class, "DOMProcessingInstruction");
  this.DOMNode  = DOMNode;
  this.DOMNode(ownerDocument);

  // The target of this processing instruction.
  // XML defines this as being the first token following the markup that begins the processing instruction.
  this.target = "";

  // The content of this processing instruction.
  // This is from the first non white space character after the target to the character immediately preceding the ?>
  this.data   = "";

  this.nodeType  = DOMNode.PROCESSING_INSTRUCTION_NODE;
};
DOMProcessingInstruction.prototype = new DOMNode;

/**
 * @method DOMProcessingInstruction.getTarget - Java style gettor for .target
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : string
 */
DOMProcessingInstruction.prototype.getTarget = function DOMProcessingInstruction_getTarget() {
  return this.nodeName;
};

/**
 * @method DOMProcessingInstruction.getData - Java style gettor for .data
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @return : string
 */
DOMProcessingInstruction.prototype.getData = function DOMProcessingInstruction_getData() {
  return this.nodeValue;
};

/**
 * @method DOMProcessingInstruction.setData - Java style settor for .data
 *   alias for DOMProcessingInstruction.setNodeValue
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  data : string - The new data of this processing instruction.
 */
DOMProcessingInstruction.prototype.setData = function DOMProcessingInstruction_setData(data) {
  // delegate to setNodeValue
  this.setNodeValue(data);
};

/**
 * @method DOMProcessingInstruction.setNodeValue - Java style settor for .nodeValue
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  data : string - The new data of this processing instruction.
 */
DOMProcessingInstruction.prototype.setNodeValue = function DOMProcessingInstruction_setNodeValue(data) {
  // throw Exception if DOMNode is readonly
  if (this.ownerDocument.implementation.errorChecking && this._readonly) {
    throw(new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR));
  }

  // assign values to properties (and aliases)
  this.nodeValue = new String(data);
  this.data = this.nodeValue;
};

/**
 * @method DOMProcessingInstruction.toString - Serialize this ProcessingInstruction into an XML string
 *
 * @author Jon van Noort (jon@webarcana.com.au) and David Joham (djoham@yahoo.com)
 *
 * @return : string
 */
DOMProcessingInstruction.prototype.toString = function DOMProcessingInstruction_toString() {
  var ret = "";

  ret += "<?" + this.nodeName +" "+ this.nodeValue + " ?>";

  return ret;
}

/**
 * @class  DOMDocumentFragment - DocumentFragment is a "lightweight" or "minimal" Document object.
 *
 * @extends DOMNode
 *
 * @author Jon van Noort (jon@webarcana.com.au)
 *
 * @param  ownerDocument : DOMDocument - The Document object associated with this node.
 */
DOMDocumentFragment = function(ownerDocument) {
  this._class = addClass(this._class, "DOMDocumentFragment");
  this.DOMNode = DOMNode;
  this.DOMNode(ownerDocument);

  this.nodeName  = "#document-fragment";
  this.nodeType = DOMNode.DOCUMENT_FRAGMENT_NODE;
};
DOMDocumentFragment.prototype = new DOMNode;

/**
 * @method DOMDocumentFragment.toString - Serialize this DocumentFragment into an XML string
 *
 * @author David Joham (djoham@yahoo.com)
 *
 * @return : string
 */
DOMDocumentFragment.prototype.toString = function DOMDocumentFragment_toString() {
  var xml = "";
  var intCount = this.getChildNodes().getLength();

  // create string concatenating the serialized ChildNodes
  for (intLoop = 0; intLoop < intCount; intLoop++) {
    xml += this.getChildNodes().item(intLoop).toString();
  }

  return xml;
}

///////////////////////
//  NOT IMPLEMENTED  //
///////////////////////
DOMDocumentType    = function() { alert("DOMDocumentType.constructor(): Not Implemented"   ); };
DOMEntity          = function() { alert("DOMEntity.constructor(): Not Implemented"         ); };
DOMEntityReference = function() { alert("DOMEntityReference.constructor(): Not Implemented"); };
DOMNotation        = function() { alert("DOMNotation.constructor(): Not Implemented"       ); };


Strings = new Object()
Strings.WHITESPACE = " \t\n\r";
Strings.QUOTES = "\"'";

Strings.isEmpty = function Strings_isEmpty(strD) {
    return (strD == null) || (strD.length == 0);
};
Strings.indexOfNonWhitespace = function Strings_indexOfNonWhitespace(strD, iB, iE) {
  if(Strings.isEmpty(strD)) return -1;
  iB = iB || 0;
  iE = iE || strD.length;

  for(var i = iB; i < iE; i++)
    if(Strings.WHITESPACE.indexOf(strD.charAt(i)) == -1) {
      return i;
    }
  return -1;
};
Strings.lastIndexOfNonWhitespace = function Strings_lastIndexOfNonWhitespace(strD, iB, iE) {
  if(Strings.isEmpty(strD)) return -1;
  iB = iB || 0;
  iE = iE || strD.length;

  for(var i = iE - 1; i >= iB; i--)
    if(Strings.WHITESPACE.indexOf(strD.charAt(i)) == -1)
      return i;
  return -1;
};
Strings.indexOfWhitespace = function Strings_indexOfWhitespace(strD, iB, iE) {
  if(Strings.isEmpty(strD)) return -1;
  iB = iB || 0;
  iE = iE || strD.length;

  for(var i = iB; i < iE; i++)
    if(Strings.WHITESPACE.indexOf(strD.charAt(i)) != -1)
      return i;
  return -1;
};
Strings.replace = function Strings_replace(strD, iB, iE, strF, strR) {
  if(Strings.isEmpty(strD)) return "";
  iB = iB || 0;
  iE = iE || strD.length;

  return strD.substring(iB, iE).split(strF).join(strR);
};
Strings.getLineNumber = function Strings_getLineNumber(strD, iP) {
  if(Strings.isEmpty(strD)) return -1;
  iP = iP || strD.length;

  return strD.substring(0, iP).split("\n").length
};
Strings.getColumnNumber = function Strings_getColumnNumber(strD, iP) {
  if(Strings.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;
};


StringBuffer = function() {this._a=new Array();};
StringBuffer.prototype.append = function StringBuffer_append(d){this._a[this._a.length]=d;};
StringBuffer.prototype.toString = function StringBuffer_toString(){return this._a.join("");};

/**
 * Remove any text nodes that belong directly to this node (not 
 * children) and set text content. A more appropriate thing to do 
 * may be to remove all children.
 *
 * @param {String} text
 */
DOMElement.prototype.setTextContent = function(text) {

    var removeIdx = [];

    // get a list of nodes to remove
    for( var i = 0; i < this.childNodes.getLength(); i++ ) {
        var node = this.childNodes.item(i);

        if( node.nodeType == DOMNode.TEXT_NODE ) {
            removeIdx.push( i );
        }
    }

    // remove them
    for( var i = 0; i < removeIdx.length; i++ ) {
        var node = this.childNodes.item(removeIdx[i]);

        this.removeChild( node );
    }

    var textNode = this.ownerDocument.createTextNode( text );
    textNode = this.appendChild( textNode );

    return textNode;
}
// =========================================================================
//
// xmlEscape.js - api for the xmlEscape functions
//
// version 3.1
//
// =========================================================================
//
// Copyright (C) 2000 - 2002 Michael Houghton (mike@idle.org) and David Joham (djoham@yahoo.com)
//
// 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
//

function xmlEscapeXMLToHTML(xmlData) {
    /*************************************************************************************
    Function:       xmlEscapeXMLToHTML

    author:         xwisdom@yahoo.com

    description:
        Encodes XML data for use in a web page

    ************************************************************************************/
    var gt; 

    var str = xmlData;

    //replace < with �
    gt = -1;
    while (str.indexOf("<", gt + 1) > -1) {
        var gt = str.indexOf("<", gt + 1);
        var newStr = str.substr(0, gt);
        newStr += "�";
        newStr = newStr + str.substr(gt + 1, str.length);
        str = newStr;
    }

    //replace > with �
    gt = -1;
    while (str.indexOf(">", gt + 1) > -1) {
        var gt = str.indexOf(">", gt + 1);
        var newStr = str.substr(0, gt);
        newStr += "�";
        newStr = newStr + str.substr(gt + 1, str.length);
        str = newStr;
    }

    //replace & with �
    gt = -1;
    while (str.indexOf("&", gt + 1) > -1) {
        var gt = str.indexOf("&", gt + 1);
        var newStr = str.substr(0, gt);
        newStr += "�";
        newStr = newStr + str.substr(gt + 1, str.length);
        str = newStr;
    }

    return str

}  // end function xmlEscapeXMLToHTML


function xmlUnescapeHTMLToXML(xmlData)  {
    /*************************************************************************************
    Function:       xmlUnescapeHTMLToXML

    author:         xwisdom@yahoo.com

    description:
        Decodes XML previously encoded with xmlEscapeXMLToHTML

    ************************************************************************************/
    var str = xmlData;

    var gt;

    //replace � with <
    gt = -1;
    while (str.indexOf("�", gt + 1) > -1) {
        var gt = str.indexOf("�", gt + 1);
        var newStr = str.substr(0, gt);
        newStr += "<";
        newStr = newStr + str.substr(gt + 1, str.length);
        str = newStr;
    }


    //replace � with >
    gt = -1;
    while (str.indexOf("�", gt + 1) > -1) {
        var gt = str.indexOf("�", gt + 1);
        var newStr = str.substr(0, gt);
        newStr += ">";
        newStr = newStr + str.substr(gt + 1, str.length);
        str = newStr;
    }

    //replace � with &
    gt = -1;
    while (str.indexOf("�", gt + 1) > -1) {
        var gt = str.indexOf("�", gt + 1);
        var newStr = str.substr(0, gt);
        newStr += "&";
        newStr = newStr + str.substr(gt + 1, str.length);
        str = newStr;
    }

    return str

}// end function xmlUnescapeHTMLToXML

// =========================================================================
//
// xmlIO.js - api for the xmlIO functions
//
// version 3.1 
//
// =========================================================================
//
// Copyright (C) 2002 - 2003 David Joham (djoham@yahoo.com)
//
// 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

//NOTE: parts of this code were inspired by the book JavaScript Unleashed by R. Allen Wyke, Richard Wagner

//NOTE: parts of this code were inspired by http://developer.apple.com/internet/javascript/iframe.html


//NOTE: Part of this code was written on Feb 1, 2003 after I had learned of the destruction of the space shuttle Columbia.
//      My heart goes out to the families of the seven brave astronauts: 
//      Colonel Rick Husband, Lieutenant Colonel Michael Anderson, Commander Laurel Clark, Captain David Brown, 
//      Commander William McCool, Dr. Kalpana Chawla, and Colonel Ilan Ramon of the Israeli air force. 
//		May they rest in peace.

//		Hail Mary, full of grace. The Lord is with thee. 
//		Blessed art thou amongst women, and blessed is the fruit of thy womb, Jesus.
// 		Holy Mary, Mother of God, pray for us sinners,
//		now and at the hour of our death.
//		Amen
//
//		In memory of STS-107


var _xmlIOProxyGetLastLoadStatus = "";
var _xmlIOLoadLocalDataCallbackFunction = "";
var _xmlIOLoadlocalDataIFrameID = "";
var _xmlIOLoadLocalDataLastLoadStatus = "";
var _xmlIOLoadLocalDataRetryCount = 0;

/************************************************************************************************************
*************************************************************************************************************
*************************************************************************************************************
                        SAVING DATA TO THE CLIENTS HARD DRIVE VIA COOKES
*************************************************************************************************************
*************************************************************************************************************
*************************************************************************************************************/                        


function xmlIODeleteData(dataName) {
    /********************************************************************************
    Function: xmlIODeleteData

    Author: djoham@yahoo.com

    Description:
        Deletes the data from the cookie stream
    ********************************************************************************/
    //in order to ensure that the xmlIOGetData doesn't confuse this name with another,
    //(ie having two names like "xml1" and "xml10", 
    //the save routine wraped the name with colons. We must do the same
    dataName = ":::" + dataName + ":::";

    //expire by setting to date in past
    var expDate = new Date("January 01, 1980").toGMTString();
    document.cookie = dataName + "=;expires=" + expDate;


} // end function xmlIODeleteData


function xmlIOGetData(dataName){
    /********************************************************************************
    Function: xmlIOGetData

    Author: djoham@yahoo.com

    Description:
        retrieves the data from the cookie (if the data is there)
    ********************************************************************************/

    //in order to ensure that the xmlIOGetData doesn't confuse this name with another,
    //(ie having two names like "xml1" and "xml10", 
    //the save routine wraped the name with colons. We must do the same
    var origDataName = dataName;
    dataName = ":::" + dataName + ":::";

    var myCookie = " " + document.cookie + ";";
    var cookieName = " " + dataName;
    var cookieStart = myCookie.indexOf(cookieName);
    var cookieEnd;
	var retVal = "";
        
    if (cookieStart != -1){
        cookieStart += cookieName.length;
        cookieEnd = myCookie.indexOf(";", cookieStart);
        retVal = unescape(myCookie.substring((cookieStart+1), cookieEnd));
    }
    
    //if the return value is "", it's possible that the user has upgraded from XML for <SCRIPT> 2.0 to 2.1.
    //we need to check to see if that's the case and handle the request correctly
    //xml for <SCRIPT> used String.fromCharCode(171) and String.fromCharCode(187) as delimiters
    //rather than ":::". I'll just check for String.fromCharCode(171). If it's there, it's worth doing the lookup again.
    if (retVal == "" && document.cookie.indexOf(String.fromCharCode(171)) > -1) {
    	//do the lookup again, this time with the old 2.0 delimiters
        dataName = String.fromCharCode(171) + origDataName + String.fromCharCode(187);

        var myCookie = " " + document.cookie + ";";
        var cookieName = " " + dataName;
        var cookieStart = myCookie.indexOf(cookieName);
        var cookieEnd;
        if (cookieStart != -1){
            cookieStart += cookieName.length;
            cookieEnd = myCookie.indexOf(";", cookieStart);
        }

        var retVal = unescape(myCookie.substring((cookieStart+1), cookieEnd));
	}
            
    return retVal;

}  // end function xmlIOGetData

function xmlIOListSavedDataNames(){
    /********************************************************************************
    Function: xmlIOListSavedDataNames

    Author: djoham@yahoo.com

    Description:
        Returns an array of all the stored names in the XMLIO cookie database
    ********************************************************************************/
    var aryRet = new Array();
    
    var arySplit = document.cookie.split(":::");
    var intCount = arySplit.length;
    for (intLoop = 0; intLoop < intCount; intLoop ++) {
    	//if the split array element is empty or has an equal sign in it, we want to discard it
        if (arySplit[intLoop].indexOf("=") > -1 || arySplit[intLoop] == "") {
        	//do nothing
        }
        else {
        	//add it to the return array
            aryRet[aryRet.length] = arySplit[intLoop];
        }
    }
          
    return aryRet;
    
} // end xmlIOListSavedDataNames


function xmlIOSaveData(dataName, dataValue, expireDate){
    /********************************************************************************
    Function: xmlIOSaveData

    Author: djoham@yahoo.com

    Description:
        Saves the data to the cookie.

    expireDate is optional. If passed in, it must be a valid JavaScript
    Date object. If not passed in, the cookie will expire on Jan 1st
    10 years from when the function was called.
    ********************************************************************************/

    var expireDateGMT; // cookies need GMT values for their expiration date

    //check if expireDate has been passed in
    if (expireDate == null) {
        var x = new Date();  //today
        var z = x.getFullYear() + 10;  //plus 10 years
        var y = new Date("January 01, " + z);  // on Jan1
        expireDateGMT = y.toGMTString();
    }
    else {
        expireDateGMT = expireDate.toGMTString();
    }

    //in order to ensure that the xmlIOGetData doesn't confuse this name with another, 
    //(ie having two names like "xml1" and "xml10", wrap the name with colons
    dataName = ":::" + dataName + ":::";

    //set the cookie
    //document.cookie = dataName + "=" + escape(dataValue) + " ;expires=" + expireDateGMT;
    document.cookie = dataName + "=" + escape(dataValue) + " ;expires=" + expireDateGMT;

}  // end function xmlIOSaveData



/************************************************************************************************************
*************************************************************************************************************
*************************************************************************************************************
                        LOADING XML VIA THE PROXY SERVERS
*************************************************************************************************************
*************************************************************************************************************
*************************************************************************************************************/


function xmlIOProxyGetLastLoadStatus() {
    /********************************************************************************
    Function: xmlIOProxyGetLastLoadStatus

    Author: djoham@yahoo.com

    Description:
    		Returns xmlIOProxyGetLastLoadStatus, the last status report as reported
            in the getXMLfromIframe function
    ********************************************************************************/

	return _xmlIOProxyGetLastLoadStatus;


} // end xmlIOLoadXMLGetLastStatus


function xmlIOProxyLoadData(proxyURL, resourceID, callbackFunction, authenticationCode) {
    /********************************************************************************
    Function: xmlIOProxyLoadData

    Author: djoham@yahoo.com

    Description:
        makes the call to the proxy server (proxyURL) to get the XML requested (resourceID)
        and calls  the xmlIO function that reads the XML and returns it to the
        callback function (callBackFunc)
    ********************************************************************************/

    var dataSource = document.createElement("iframe");
    var guid = "guid" + new Date().getTime();
    dataSource.id = guid;
    dataSource.src = __trim(proxyURL, true, true) + "?resourceID=" + resourceID + "&guid=" + guid + "&callbackFunction=" + callbackFunction + "&authenticationCode=" + authenticationCode;
    dataSource.style.position = "absolute";
    dataSource.style.left = "-2000px";

    document.body.appendChild(dataSource);
    return guid;
    
} //end function xmlIOProxyLoadData


function __getXMLFromIFrame(callbackGUID, callbackFunction, proxyReturnCode, xml) {
    /********************************************************************************
    Function: __getXMLFromIFrame

    Author: djoham@yahoo.com

    Description:
		Gets called when the Iframe is finished loading. Unescapes the XML
        and calls the callback function 
    ********************************************************************************/

    var retXML = "";
    //clean up our mess from adding the IFRAMES
    try {
        document.body.removeChild(document.getElementById(callbackGUID));
    }
    catch (e) {
        //noop
    }
    
    try {
    	//The retXML is escaped. use the XML for <SCRIPT>'s unescape function.
        //Put the catch in here to make sure the programmer has included it
        retXML = xmlUnescapeHTMLToXML(xml)
    }
    catch (e) {
    	retXML = "The function xmlUnescapeHTMLToXML was not found. Did you include xmlEscape.js (from the jsTools directory) in your HTML file?";
        proxyReturnCode = "error";

    }

    //if the callback function is an empty string, the try-catch doesn't seem to work
    //the javascript just goes off into the weeds and never comes back
    if (callbackFunction == "") { 
        //in this case, the retXML gives a decent error string, so use it
        alert("xmlIO error in __getXMLFromIFrame:\nErrorString: " + retXML + "\nguid: " + callbackGUID + "\ncallbackFunction: " + callbackFunction + "\nproxyReturnCode: " + proxyReturnCode);
	}
    else {    
        // try to call the callback function. If it fails, punt and just set the status
        try {
            _xmlIOProxyGetLastLoadStatus = "xmlIOProxyLoadData-Called callbackFunction"; // must be before the following line
            eval(callbackFunction + "(retXML, callbackGUID, proxyReturnCode)");
        }
        catch (e) {
            _xmlIOProxyGetLastLoadStatus = "xmlIOProxyLoadData-Error: xmlIO error in __getXMLFromIFrame:\nErrorString: " + e.toString() + "\nguid: " + callbackGUID + "\ncallbackFunction: " + callbackFunction + "\nproxyReturnCode: " + proxyReturnCode;
        }
	}
} // end function __getXMLFromIFrame




/************************************************************************************************************
*************************************************************************************************************
*************************************************************************************************************
                        LOADING XML VIA LOCAL FILE ACCESS
         (files on the same web server or files on the local hard drive)
*************************************************************************************************************
*************************************************************************************************************
*************************************************************************************************************/

function xmlIOLoadLocalData(dataURL, callbackFunction) {
    /********************************************************************************
    Function: xmlIOLoadLocalData

    Author: djoham@yahoo.com

    Description:
            Loads up an IFRAME in the background with its src being dataURL
            then goes through an enourmous amount of stupid gymnastics to make
            sure it gets that data back to the calling javascript
    ********************************************************************************/

    _xmlIOLoadLocalDataCallbackFunction = callbackFunction;
    _xmlIOLoadLocalDataLastLoadStatus = "Starting xmlIOLoadLocalData";
    var _xmlIOLoadLocalDataRetryCount = 0;

    //create the IFrame and start the loading process
    var dataFrame;
    try {
        try {
            dataFrame = document.createElement("iframe");
            //dataFrame.src has to be here for konq 3.0x to work properly
            dataFrame.src = dataURL;
            dataFrame.id = new Date().valueOf();
            dataFrame.style.position = "absolute";
            dataFrame.style.left = "-2000px";
            _xmlIOLoadlocalDataIFrameID = dataFrame.id;
            document.body.appendChild(dataFrame);
        }
        catch (e) {
            _xmlIOLoadLocalDataLastLoadStatus = "ERROR: xmlIOLoadLocalData cannot be used in this browser";
        }
        if (!document.readyState) {
            //try to do the onload (mozilla goes here)
            dataFrame.onload = __loadLocalIFrameXML;
        }
        else {
            //else send it to the readystatechecker (everybody elses goes here)
            window.setTimeout("__readyStateChecker()", 250);
        }
    }
    catch (badFailure) {
        _xmlIOLoadLocalDataLastLoadStatus = badFailure;
    }

    _xmlIOLoadLocalDataLastLoadStatus = "Ending xmlIOLoadLocalData";


} // end function xmlIOLoadLocalData


function __readyStateChecker() {
    /********************************************************************************
    Function: __readyStateChecker

    Author: djoham@yahoo.com

    Description:
            checks the readState of the IFrame document. Tries again and again
            for 10 seconds before it gives up and sends control to the function
            that returns the XML back to the callback function

    *********************************************************************************/

    var dataFrame;

    _xmlIOLoadLocalDataLastLoadStatus = "Starting __readyStateChecker";

    try {
        //(konq, Opera and Moz go here)
        dataFrame = document.getElementById(_xmlIOLoadlocalDataIFrameID).contentDocument.readyState;
    }
    catch (imIE) {
        try {
            //(IE win or mac)
            dataFrame = document.getElementById(_xmlIOLoadlocalDataIFrameID).contentWindow.document.readyState;
        }
        catch (imIEMac) {
            try {
                //mac IE goes here - mac ie readystate doesn't seem to work,
                //so I just check for an empty innerHTML.
                //try for 10 seconds
                if (_xmlIOLoadLocalDataRetryCount < 41) {
                    if (__trim(window.frames[_xmlIOLoadlocalDataIFrameID].document.body.innerHTML, true, true) == "") {
                        _xmlIOLoadLocalDataRetryCount++;
                        _xmlIOLoadLocalDataLastLoadStatus = "Calling __readyStateChecker again";
                        window.setTimeout("__readyStateChecker()", 250);
                        return;
                    }
                    else {
                        //we have data - assume we have it all and call the IFrame XML getter
                        _xmlIOLoadLocalDataLastLoadStatus = "Calling __loadLocalIFrameXML";
                        __loadLocalIFrameXML();
                        return;
                    }
                }
                else {
                    //maybe the document is supposed to be empty?
                    _xmlIOLoadLocalDataLastLoadStatus = "__readyStateChecker TimeOut - calling __loadLocalIFrameXML anyway";
                    __loadLocalIFrameXML();
                }
                return;
            }
            catch (badFailure) {
                //punt
                _xmlIOLoadLocalDataLastLoadStatus = "__readStateChecker unrecoverable error: " + badFailure;
                return;
            }
        }
    }
    if (dataFrame.toLowerCase() != "complete" ) {
        //try for 10 seconds
        if (_xmlIOLoadLocalDataRetryCount > 40 ) {
            //giving up and calling the local IFrame XML getter
            _xmlIOLoadLocalDataLastLoadStatus = "__readyStateChecker TimeOut - calling __loadLocalIFrameXML anyway";
            __loadLocalIFrameXML();
        }
        else {
            //try again
            _xmlIOLoadLocalDataRetryCount++;
            _xmlIOLoadLocalDataLastLoadStatus = "Calling __readyStateChecker again";
            window.setTimeout("__readyStateChecker()", 250);
        }
    }
    else {
        _xmlIOLoadLocalDataLastLoadStatus = "Calling __loadLocalIFrameXML";
        __loadLocalIFrameXML();
    }

} // end function __readyStateChecker


function __loadLocalIFrameXML() {
    /********************************************************************************
    Function: __loadLocalIFrameXML

    Author: djoham@yahoo.com

    Description:
            Gets the XML from the IFrame created in xmlIOLoadLocalData,
            unescapes it and then sends it to the callback function

    *********************************************************************************/

    _xmlIOLoadLocalDataLastLoadStatus = "Starting __loadLocalIFrameXML";

    var xmlString = "";

    try {
        var dataFrame = document.getElementById(_xmlIOLoadlocalDataIFrameID);
        try {
            //(Konqueror, Opera and Mozilla get this)
            xmlString = dataFrame.contentDocument.body.innerHTML;
        }
        catch (imIE) {
            try {
                xmlString = dataFrame.contentWindow.document.body.innerHTML;
            }
            catch (imIEMac) {
                xmlString = window.frames[_xmlIOLoadlocalDataIFrameID].document.body.innerHTML;
            }
        }
        try {
            document.body.removeChild(dataFrame);
        }
        catch (badFailure) {
            _xmlIOLoadLocalDataLastLoadStatus = "__loadLocalIFrameXML unrecoverable error(1): " + badFailure;
        }
    }
    catch (badFailure2) {
        _xmlIOLoadLocalDataLastLoadStatus = "__loadLocalIFrameXML unrecoverable error(2): " + badFailure2;
    }

    try {
        //The xmlString is escaped. use the XML for <SCRIPT>'s unescape function.
        //Put the catch in here to make sure the programmer has included it
        xmlString = xmlUnescapeHTMLToXML(xmlString)
    }
    catch (e) {
        alert(e);
        _xmlIOLoadLocalDataLastLoadStatus = "__loadLocalIFrameXML unrecoverable error: The function xmlUnescapeHTMLToXML was not found. Did you include xmlEscape.js (from the jsTools directory) in your HTML file?";
    }

    try {

        eval(_xmlIOLoadLocalDataCallbackFunction + "(xmlString)");
        _xmlIOLoadLocalDataLastLoadStatus = "__loadLocalIFrameXML successfully called callback function";
    }
    catch (e) {
    	_xmlIOLoadLocalDataLastLoadStatus = "__loadLocalIFrameXML unrecoverable error: Is your callback function correct?";
    }


}

function xmlIOLoadLocalGetLastLoadStatus() {

    return _xmlIOLoadLocalDataLastLoadStatus;

} //end function xmlIOLoadLocalGetLastLoadStatus

/************************************************************************************************************
*************************************************************************************************************
*************************************************************************************************************
                                        Helper Functions
*************************************************************************************************************
*************************************************************************************************************
*************************************************************************************************************/

function __trim(trimString, leftTrim, rightTrim) {
    /*******************************************************************************************************************
    function: _isEmpty

    Author: may106@psu.edu

    Description:
        helper function to trip a string (trimString) of leading (leftTrim)
        and trailing (rightTrim) whitespace

		Copied from xmldom.js

    *********************************************************************************************************************/
	var whitespace = "\n\r\t ";

	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 __isEmpty(str) {
    /*******************************************************************************************************************
    function: __isEmpty

    Author: mike@idle.org

    Description:
        convenience function to identify an empty string
		copied from xmldom.js
    *********************************************************************************************************************/
    return (str==null) || (str.length==0);

} // end function __isEmpty

// =========================================================================
//
// xmlxpath.js - a partially W3C compliant XPath parser for XML for <SCRIPT>
//
// version 3.1
//
// =========================================================================
//
// Copyright (C) 2003 Jon van Noort <jon@webarcana.com.au> and David Joham <djoham@yahoo.com>
//
// 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 xmljs.sourceforge.net
//
// Contributor - Jon van Noort <jon@webarcana.com.au>
//
// Contains code derived from works by Mark Bosley & Fred Baptiste
//  (see: http://www.topxml.com/people/bosley/strlib.asp)
//
// Contains text (used within comments to methods) from the
//  XML Path Language (XPath) Version 1.0 W3C Recommendation
//  Copyright � 16 November 1999 World Wide Web Consortium,
//  (Massachusetts Institute of Technology,
//  European Research Consortium for Informatics and Mathematics, Keio University).
//  All Rights Reserved.
//  (see: http://www.w3.org/TR/xpath)
//


// =========================================================================
// CONSTANT DECLARATIONS:
// =========================================================================
DOMNode.NODE_TYPE_TEST = 1;
DOMNode.NODE_NAME_TEST = 2;

DOMNode.ANCESTOR_AXIS            = 1;
DOMNode.ANCESTOR_OR_SELF_AXIS    = 2;
DOMNode.ATTRIBUTE_AXIS           = 3;
DOMNode.CHILD_AXIS               = 4;
DOMNode.DESCENDANT_AXIS          = 5;
DOMNode.DESCENDANT_OR_SELF_AXIS  = 6;
DOMNode.FOLLOWING_AXIS           = 7;
DOMNode.FOLLOWING_SIBLING_AXIS   = 8;
DOMNode.NAMESPACE_AXIS           = 9;
DOMNode.PARENT_AXIS              = 10;
DOMNode.PRECEDING_AXIS           = 11;
DOMNode.PRECEDING_SIBLING_AXIS   = 12;
DOMNode.SELF_AXIS                = 13;

DOMNode.ROOT_AXIS                = 14;  // the self axis of the root node



// =========================================================================
//  XPATHNode Helpers 
// =========================================================================

// =========================================================================
// @method
// DOMNode.getStringValue - Return the concatenation of all of the DescandantOrSelf Text nodes
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return 
// : string 
// 
// ========================================================================= 
DOMNode.prototype.getStringValue = function() {
  var thisBranch = this._getDescendantOrSelfAxis();
  var textNodes = thisBranch.getTypedItems("text");
  var stringValue = "";

  if (textNodes.length > 0) {
    stringValue += textNodes.item(0).nodeValue;

    for (var i=1; i < textNodes.length ; i++) {
      stringValue += " "+ textNodes.item(i).nodeValue;  // separate TextNodes with spaces
    }
  }

  return stringValue;
};

DOMNode.prototype._filterByAttributeExistance = function(expr, containerNodeSet) {

    try {
        //handle [@id] condition
        if (expr.indexOf("*") < 0) {
            var attributeName = expr.substr(1, expr.length);
            var attribute = this.getAttributes().getNamedItem(attributeName);

            if (attribute != null) {
                return true;
            }
            else {
                return false;
            }
        }

        //handle [@*] situation
        else {
            if (this.getAttributes().getLength() > 0 ) {
                return true;
            }
            else {
                return false;
            }
        }
    }
    catch (e) {
        return false;
    }
}

DOMNode.prototype._filterByAttributeValue = function(expr, containerNodeSet) {

    //handle situation such as [@id=1], [@id='1'] and/or [@id="1"]
    try {
        //get some locations we'll need later
        var nameStart = expr.indexOf("@") + 1;
        var equalsStart = expr.indexOf("=");

        //get the attribute name we're looking for
        var attributeName = expr.substr(nameStart, expr.indexOf("=", nameStart) - 1);

        //get the value we're looking for
        var valueTesting = expr.substr(equalsStart + 1, expr.length);

        //handle situations like [@id="1"] and [@id='1']
        if (valueTesting.charAt(0) == "\"" || valueTesting.charAt(0) == "'") {
            valueTesting = valueTesting.substr(1, valueTesting.length);
        }

        if (valueTesting.charAt(valueTesting.length - 1) == "\"" || valueTesting.charAt(valueTesting.length - 1) == "'") {
            valueTesting = valueTesting.substr(0, valueTesting.length -1);
        }

        //now I have the attribute name. Try to get it
        var attribute = this.getAttributes().getNamedItem(attributeName);

        //test for equality
        if (attribute.getValue() == valueTesting) {
            return true;
        }
        else {
            return false;
        }
    }
    catch (e) {
        return false
    }

}

DOMNode.prototype._filterByAttribute = function(expr, containerNodeSet) {

    //handle searches for the existance of any attributes
    if (expr == "@*") {
        return this._filterByAttributeExistance(expr, containerNodeSet);
    }

    //test to see if they just want elements with a specific attribute [@id]
    if (expr.indexOf("=") < 0) {
        return this._filterByAttributeExistance(expr, containerNodeSet);
    }

    //if we got here, we're looking for an attribute value
    return this._filterByAttributeValue(expr, containerNodeSet);

}

DOMNode.prototype._filterByNot = function(expr, containerNodeSet) {

    //handle something like [not(<expression>)]
    //first, remove the "not part
    expr = expr.substr(4, expr.length);

    //find the end of the not
    var endNotLocation = this._findExpressionEnd(expr, "(", ")");

    //get the full expression
    expr = expr.substr(0, endNotLocation);

    //now return the not of the filter value
    return !this._filter(expr, containerNodeSet);
}

DOMNode.prototype._findExpressionEnd = function(expression, startCharacter, endCharacter) {

    //make sure to handle the case where we have nested expressions
    var startCharacterNum = 0;
    var endExpressionLocation = 0;
    var intCount = expression.length;


    for (intLoop = 0; intLoop < intCount; intLoop++) {
        var character = expression.charAt(intLoop);
        switch(character) {
            case startCharacter:
                startCharacterNum++;
                break;

            case endCharacter:
                if (startCharacterNum == 0) {
                    endExpressionLocation = intLoop;
                }
                else {
                    startCharacterNum--;
                }
                break;
        }

        if (endExpressionLocation != 0) {
            break;
        }
    }

    return endExpressionLocation;

}

DOMNode.prototype._filterByLocation = function(expr, containerNodeSet) {

    //handle situation like [1]
    var item = 0 + expr - 1; //xpath is 1 based, w3cdom is 0 based
    //compare ourselves to the list
    if (this == containerNodeSet.item(item)) {
        return true;
    }
    else {
        return false;
    }

}

DOMNode.prototype._filterByLast = function(expr, containerNodeSet) {

    //handle situation likd [last()]
    if (this == containerNodeSet.item(containerNodeSet.length -1)) {
        return true;
    }
    else {
        return false;
    }
}

DOMNode.prototype._filterByCount = function(expr, containerNodeSet) {

    //get the count we're looking for
    var countStart = expr.indexOf("=") + 1;
    var countStr = expr.substr(countStart, expr.length);

    var countInt = parseInt(countStr);

    //separate out the "count(" part
    expr = expr.substr(6, expr.length);

    //get the expression
    expr = expr.substr(0, this._findExpressionEnd(expr, "(", ")"));
    //if expr is "*", the logic here doesn't work. Just return false
    //the reason it doesn't work is that while we may find a node under us
    //that has the proper number of children, we can only return true
    //or false for *this* node. So, if we find something that matches,
    //we'll be returning false for the wrong node.
    //TODO: fix this somehow
    if (expr == "*") {
        return false;
    }

    //otherwise, give it a go...
    var tmpNodeSet = this.selectNodeSet(expr);
    var tmpNodeSetLength = tmpNodeSet.length;

    if (tmpNodeSetLength == countInt) {
        return true;
    }
    else {
        return false;
    }

}

DOMNode.prototype._filterByName = function(expr, containerNodeSet) {

    //get the name we're looking for
    //start at the first equal sign
    var equalLocation = expr.indexOf("=");

    //get the next character. This should be either a single or double quote
    var quoteChar = expr.charAt(equalLocation + 1);

    //our name will be the text after the quoteChar until the next quoteChar is found
    var name = "";
    for (intLoop = equalLocation + 2; intLoop < expr.length; intLoop++) {
        if (expr.charAt(intLoop) == quoteChar) {
            break;
        }
        else {
            name += expr.charAt(intLoop);
        }
    }

    if (this.getNodeName() == name) {
        return true;
    }
    else {
        return false;
    }

}

DOMNode.prototype._filterByPosition = function(expr, containerNodeSet) {

    //find the location I'm looking for
    var equalsLocation = expr.indexOf("=");
    var tmpPos = expr.substr(equalsLocation + 1, expr.length);

    //I need to check for position()=last()
    if (tmpPos.indexOf("last()") == 0) {
        //see if we're the last dude in the list
        if (this == containerNodeSet.item(containerNodeSet.length -1)) {
            return true;
        }
        else {
            return false;
        }
    }

    //OK, it wasn't last so that means there's a numeric number here. Find it
    var intCount = tmpPos.length;
    var positionStr = "";
    for (intLoop = 0; intLoop < intCount; intLoop++) {
        if (isNaN(positionStr + tmpPos.charAt(intLoop)) == false) {
            positionStr+=tmpPos.charAt(intLoop);
        }
        else {
            break;
        }
    }

    //OK, we have the position, get the int value and do the comparison
    var positionInt = parseInt(positionStr) - 1; //minus 1 because xpath is 1 based but w3cdom is 0 based
    if (this == containerNodeSet.item(positionInt) ) {
        return true;
    }
    else {
        return false;
    }

}

// =========================================================================
// @method
// DOMNode._filter - Returns true if the expression evaluates to true
//                      and otherwise returns false
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @param
// expr : string - The XPath Predicate Expression
// containerNodeSet : XPATHNodeSet - the NodeSet containing this node
//
// @return
// : boolean
//
// =========================================================================
DOMNode.prototype._filter = function(expr, containerNodeSet) {

  expr = trim(expr, true, true);

  //handle not()
  if (expr.indexOf("not(") == 0) {
    return this._filterByNot(expr, containerNodeSet);
  }

  //handle count()
  if (expr.indexOf("count(") == 0) {
    return this._filterByCount(expr, containerNodeSet);
  }

  //handle name()
  if (expr.indexOf("name(") == 0) {
    return this._filterByName(expr, containerNodeSet);
  }

  //handle [position() = 1]
  if (expr.indexOf("position(") == 0) {
    return this._filterByPosition(expr, containerNodeSet);
  }


  //handle attribute searches
  if (expr.indexOf("@") > -1 ) {
    return this._filterByAttribute(expr, containerNodeSet);
  }


  //handle the case of something like [1]
  if (isNaN(expr) == false) {
    return this._filterByLocation(expr, containerNodeSet);
  }

  //handle [last()]
  if (expr == "last()") {
    return this._filterByLast(expr, containerNodeSet);
  }


}


// =========================================================================
// @method
// DOMNode._getAxis - Return a NodeSet containing all Nodes in the specified Axis
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @param
// axisConst : int - the id of the Axis Type
//
// @return
// : XPATHNodeSet
//
// =========================================================================
DOMNode.prototype._getAxis = function(axisConst) {
  if (axisConst == DOMNode.ANCESTOR_AXIS)                return this._getAncestorAxis();
  else if (axisConst == DOMNode.ANCESTOR_OR_SELF_AXIS)   return this._getAncestorOrSelfAxis();
  else if (axisConst == DOMNode.ATTRIBUTE_AXIS)          return this._getAttributeAxis();
  else if (axisConst == DOMNode.CHILD_AXIS)              return this._getChildAxis();
  else if (axisConst == DOMNode.DESCENDANT_AXIS)         return this._getDescendantAxis();
  else if (axisConst == DOMNode.DESCENDANT_OR_SELF_AXIS) return this._getDescendantOrSelfAxis();
  else if (axisConst == DOMNode.FOLLOWING_AXIS)          return this._getFollowingAxis();
  else if (axisConst == DOMNode.FOLLOWING_SIBLING_AXIS)  return this._getFollowingSiblingAxis();
  else if (axisConst == DOMNode.NAMESPACE_AXIS)          return this._getNamespaceAxis();
  else if (axisConst == DOMNode.PARENT_AXIS)             return this._getParentAxis();
  else if (axisConst == DOMNode.PRECEDING_AXIS)          return this._getPrecedingAxis();
  else if (axisConst == DOMNode.PRECEDING_SIBLING_AXIS)  return this._getPrecedingSiblingAxis();
  else if (axisConst == DOMNode.SELF_AXIS)               return this._getSelfAxis();
  else if (axisConst == DOMNode.ROOT_AXIS)               return this._getRootAxis();


  else {
    alert('Error in DOMNode._getAxis: Attempted to get unknown axis type '+ axisConst);
    return null;
  }
};


// =========================================================================
// The ancestor, descendant, following, preceding and self axes partition a document
// (ignoring attribute and namespace nodes): they do not overlap and together they contain
// all the nodes in the document.
// =========================================================================

// =========================================================================
// @method
// DOMNode._getAncestorAxis - Return a NodeSet containing all Nodes in ancestor axis.
//   The ancestor axis contains the ancestors of the context node; the ancestors of the
//   context node consist of the parent of context node and the parent's parent and so on;
//   thus, the ancestor axis will always include the root node, unless the context node is
//   the root node.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return
// : XPATHNodeSet
//
// =========================================================================
DOMNode.prototype._getAncestorAxis = function() {
  var parentNode = this.parentNode;

  if (parentNode.nodeType != DOMNode.DOCUMENT_NODE ) {
    return this.parentNode._getAncestorOrSelfAxis();
  }
  else {
    return new XPATHNodeSet(this.ownerDocument, this.parentNode, null);  // return empty NodeSet
  }
};


// =========================================================================
// @method
// DOMNode._getAncestorOrSelfAxis - Return a NodeSet containing all Nodes in ancestor or self axes.
//   The ancestor-or-self axis contains the context node and the ancestors of the
//   context node; thus, the ancestor axis will always include the root node.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return
// : XPATHNodeSet
//
// =========================================================================
DOMNode.prototype._getAncestorOrSelfAxis = function() {
  return this._getSelfAxis().union(this._getAncestorAxis());
};


// =========================================================================
// @method
// DOMNode._getAttributeAxis - Return a NodeSet containing all Nodes in the attribute axis.
//   The attribute axis contains the attributes of the context node; the axis will
//   be empty unless the context node is an element.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return
// : XPATHNodeSet
//
// =========================================================================
DOMNode.prototype._getAttributeAxis = function() {
  return new XPATHNodeSet(this.ownerDocument, this.parentNode, this.attributes);
};


// =========================================================================
// @method
// DOMNode._getChildAxis - Return a NodeSet containing all Nodes in the child axis.
//   The child axis contains the children of the context node.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return
// : XPATHNodeSet
//
// =========================================================================
DOMNode.prototype._getChildAxis = function() {
  return new XPATHNodeSet(this.ownerDocument, this.parentNode, this.childNodes);
};


// =========================================================================
// @method
// DOMNode._getDescendantAxis - Return a NodeSet containing all Nodes in the descendant axis.
//   The descendant axis contains the descendants of the context node; a descendant is
//   a child or a child of a child and so on; thus the descendant axis never contains
//   attribute or namespace nodes.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return
// : XPATHNodeSet
//
// =========================================================================
DOMNode.prototype._getDescendantAxis = function() {
  var descendantNodeSet = new XPATHNodeSet(this.ownerDocument, this.parentNode, null);

  for (var i=0; i < this.childNodes.length; i++) {
    descendantNodeSet.union(this.childNodes.item(i)._getDescendantOrSelfAxis());
  }

  return descendantNodeSet;
};

// =========================================================================
// @method
// DOMNode._getReversedDescendantAxis - Return a NodeSet containing all Nodes in the descendant axis,
//   in Reverse Document Order .
//   The descendant axis contains the descendants of the context node; a descendant is
//   a child or a child of a child and so on; thus the descendant axis never contains
//   attribute or namespace nodes.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return
// : XPATHNodeSet
//
// =========================================================================
DOMNode.prototype._getReversedDescendantAxis = function() {
  var descendantNodeSet = new XPATHNodeSet(this.ownerDocument, this.parentNode, null);

  for (var i=this.childNodes.length -1; i >= 0; i--) {
    descendantNodeSet.union(this.childNodes.item(i)._getReversedDescendantOrSelfAxis());
  }

  return descendantNodeSet;
};


// =========================================================================
// @method
// DOMNode._getDescendantOrSelfAxis - Return a NodeSet containing all Nodes in the descendant or self axes.
//   The descendant-or-self axis contains the context node and the descendants of the context node.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return
// : XPATHNodeSet
//
// =========================================================================
DOMNode.prototype._getDescendantOrSelfAxis = function() {
  return this._getSelfAxis().union(this._getDescendantAxis());
};


// =========================================================================
// @method
// DOMNode._getReversedDescendantOrSelfAxis - Return a NodeSet containing all Nodes in the descendant or self axes,
//   in Reverse Document Order.
//   The descendant-or-self axis contains the context node and the descendants of the context node.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return
// : XPATHNodeSet
//
// =========================================================================
DOMNode.prototype._getReversedDescendantOrSelfAxis = function() {
  return this._getSelfAxis().union(this._getReversedDescendantAxis());
};


// =========================================================================
// @method
// DOMNode._getFollowingAxis - Return a NodeSet containing all Nodes in the following axes.
//   The following axis contains all nodes in the same document as the context node that
//   are after the context node in document order, excluding any descendants and excluding
//   attribute nodes and namespace nodes.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return
// : XPATHNodeSet
//
// =========================================================================
DOMNode.prototype._getFollowingAxis = function() {
  var followingNodeSet = new XPATHNodeSet(this.ownerDocument, this.parentNode, null);

  if (this.nextSibling) {
    followingNodeSet._appendChild(this.nextSibling);
    followingNodeSet.union(this.nextSibling._getDescendantAxis());
    followingNodeSet.union(this.nextSibling._getFollowingAxis());
  }

  return followingNodeSet;
};


// =========================================================================
// @method
// DOMNode._getFollowingSiblingAxis - Return a NodeSet containing all sibling Nodes in the following axes.
//   The following-sibling axis contains all the following siblings of the context node;
//   if the context node is an attribute node or namespace node, the following-sibling axis is empty.
//   ie, only the nodes that are sibling to the context node, not their descandants
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return
// : XPATHNodeSet
//
// =========================================================================
DOMNode.prototype._getFollowingSiblingAxis = function() {
  var followingSiblingNodeSet = new XPATHNodeSet(this.ownerDocument, this.parentNode, null);

  if (this.nextSibling) {
    followingSiblingNodeSet._appendChild(this.nextSibling);
    followingSiblingNodeSet.union(this.nextSibling._getFollowingSiblingAxis());
  }

  return followingSiblingNodeSet;
};


// =========================================================================
// @method
// DOMNode._getNamespaceAxis - Return a NodeSet containing nodes in the Namespace axis.
//   the namespace axis contains the namespace nodes of the context node; the axis will
//   be empty unless the context node is an element.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return
// : XPATHNodeSet
//
// =========================================================================
// DOMNode.prototype._getNamespaceAxis = function() { };


// =========================================================================
// @method
// DOMNode._getParentAxis - Return a NodeSet containing the parent node of the context node.
//   The parent axis contains the parent of the context node, if there is one.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return
// : XPATHNodeSet
//
// =========================================================================
DOMNode.prototype._getParentAxis = function() {
  var parentNodeSet = new XPATHNodeSet(this.ownerDocument, this.parentNode, null);
  var parentNode = this.parentNode;

  if (parentNode) {
    parentNodeSet._appendChild(parentNode);
  }

  return parentNodeSet;
};


// =========================================================================
// @method
// DOMNode._getPrecedingAxis - Return a NodeSet containing all Nodes in the preceding axis.
//   The preceding axis contains all nodes in the same document as the context node that
//   are before the context node in document order, excluding any ancestors and excluding
//   attribute nodes and namespace nodes.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return
// : XPATHNodeSet
//
// =========================================================================
DOMNode.prototype._getPrecedingAxis = function() {
  var precedingNodeSet = new XPATHNodeSet(this.ownerDocument, this.parentNode, null);

  if (this.previousSibling) {
    precedingNodeSet.union(this.previousSibling._getReversedDescendantAxis());
    precedingNodeSet._appendChild(this.previousSibling);
    precedingNodeSet.union(this.previousSibling._getPrecedingAxis());
  }

  return precedingNodeSet;
};


// =========================================================================
// @method
// DOMNode._getPrecedingSiblingAxis - Return a NodeSet containing all sibling Nodes in the preceding axis.
//   The preceding axis contains all nodes in the same document as the context node that
//   are before the context node in document order, excluding any ancestors and excluding
//   attribute nodes and namespace nodes.
//   ie, only the nodes that are sibling to the context node, not their descandants
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return
// : XPATHNodeSet
//
// =========================================================================
DOMNode.prototype._getPrecedingSiblingAxis = function() {
  var precedingSiblingNodeSet = new XPATHNodeSet(this.ownerDocument, this.parentNode, null);

  if (this.previousSibling) {
    precedingSiblingNodeSet._appendChild(this.previousSibling);
    precedingSiblingNodeSet.union(this.previousSibling._getPrecedingSiblingAxis());
  }

  return precedingSiblingNodeSet;
};


// =========================================================================
// @method
// DOMNode._getSelfAxis - Return a NodeSet containing the context node.
//   The self axis contains just the context node itself.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return
// : XPATHNodeSet
//
// =========================================================================
DOMNode.prototype._getSelfAxis = function() {
  var selfNodeSet = new XPATHNodeSet(this.ownerDocument, this.parentNode, null);
  selfNodeSet._appendChild(this);

  return selfNodeSet;
};


// =========================================================================
// @method
// DOMNode._getRootAxis - Return a NodeSet containing the root node.
//   The root axis contains just the root node itself.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return
// : XPATHNodeSet
//
// =========================================================================
DOMNode.prototype._getRootAxis = function() {
  var rootNodeSet = new XPATHNodeSet(this.ownerDocument, this.parentNode, null);
  rootNodeSet._appendChild(this.documentElement);

  return rootNodeSet;
};


// =========================================================================
// @method
// DOMNode.selectNodeSet - Returns NodeSet containing nodes matching XPath expression
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @param
// locationPath : string - the XPath expression
//
// @return
// : XPATHNodeSet
//
// =========================================================================
DOMNode.prototype.selectNodeSet = function (locationPath) {
  // transform '//' root-recursive reference before split
  locationPath = locationPath.replace(/^\//g, 'root::');
  var result;
  try {
    result = this.selectNodeSet_recursive(locationPath);
    return result;
  }
  catch (e) {
    return null;
  }

}

// =========================================================================
// @method
// DOMNode.selectNodeSet_recursive - Returns NodeSet containing nodes matching XPath expression
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @param
// locationPath : string - the XPath expression
// 
// @return 
// : XPATHNodeSet
// 
// ========================================================================= 
DOMNode.prototype.selectNodeSet_recursive = function (locationPath) {

  // split LocationPath into Steps on '/'
  var locationSteps = locationPath.split('/');
  var candidateNodeSet;
  var resultNodeSet = new XPATHNodeSet(this.ownerDocument, this.parentNode);

  // get first step

  if (locationSteps.length > 0) {

    /* original code - didn't work in IE 5.x for the mac
    var stepStr = locationSteps.shift();
    */

    /* new code. Slower, but more compatible. */
    var stepStr = locationSteps[0];
    locationSteps = __removeFirstArrayElement(locationSteps);
    /*end new code*/

    var stepObj = this._parseStep(stepStr);

    // parse Step (Axis, NodeTest, PredicateList)
    var axisStr          = stepObj.axis;
    var nodeTestStr      = stepObj.nodeTest;
    var predicateListStr = stepObj.predicateList;

    // get AxisType
    var axisType = this._parseAxis(axisStr);

    // parse NodeTest (type, value)
    var nodeTestObj = this._parseNodeTest(nodeTestStr);
    var nodeTestType  = nodeTestObj.type;
    var nodeTestValue = nodeTestObj.value;

    // Build NodeSet with Nodes from AxisSpecifier
    candidateNodeSet = this._getAxis(axisType);

    // Filter NodeSet by NodeTest
    if (nodeTestType == DOMNode.NODE_TYPE_TEST) {
      candidateNodeSet = candidateNodeSet.getTypedItems(nodeTestValue);
    }  
    else if (nodeTestType == DOMNode.NODE_NAME_TEST) {
      candidateNodeSet = candidateNodeSet.getNamedItems(nodeTestValue);
    }

    // parse Predicates
    var predicateList = this._parsePredicates(predicateListStr);
        
    // apply each predicate in turn
    for (predicate in predicateList) {
      // filter NodeSet by applying Predicate
      candidateNodeSet = candidateNodeSet.filter(predicateList[predicate]);
    }
    
    // recursively apply remaining steps in Location Path
    if (locationSteps.length > 0) {
      var remainingLocationPath = locationSteps.join('/');
      candidateNodeSet = candidateNodeSet.selectNodeSet_recursive(remainingLocationPath);
    }
  }  
  
  return candidateNodeSet;
};


// =========================================================================
// @method
// DOMNode._nodeTypeIs - Returns true if the node is of specified type 
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @param 
// node : DOMNode - the Node to be tested
// type : int - the id of the required type
// 
// @return 
// : boolean 
// 
// ========================================================================= 
DOMNode.prototype._nodeTypeIs = function(node, type) {
  return (node.nodeType == type);
};
  

// =========================================================================
// @method
// DOMNode._nodeNameIs - Returns true if the node has the specified name 
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @param 
// node : DOMNode - the Node to be tested
// name : string - the name of the required node(s)
// 
// @return 
// : boolean 
// 
// ========================================================================= 
DOMNode.prototype._nodeNameIs = function(node, name) {
  return (node.nodeName == name);
};

// =========================================================================
// @method
// DOMNode._parseStep - Parse the LocationStep
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @param 
// step : string - the LocationStep expression, ie, the string between containing '/'s
// 
// @return 
// (
//   axis : string - the axis
//   nodeTest : string - the name or type of node
//   predicateList : string - zero or more Predicate Expressions, ie, the expressions contained within '[..]'
// )
// 
// ========================================================================= 
DOMNode.prototype._parseStep = function(step) {
  var resultStep = new Object();
  resultStep.axis = "";
  var nodeTestStartInd = 0;      // start of NodeTest string defaults to starting at begining of string
  
  // test existance of AxisSpecifier
  var axisEndInd = step.indexOf('::');
  if (axisEndInd > -1) {
    resultStep.axis = step.substring(0, axisEndInd);                              // extract axis
    nodeTestStartInd = axisEndInd +2;
  }
  
  // test existance of Predicates
  resultStep.predicateList = "";
  var predicateStartInd = step.indexOf('[');
  if (predicateStartInd > -1) {
    resultStep.predicateList = step.substring(predicateStartInd);             // extract predicateList
    resultStep.nodeTest = step.substring(nodeTestStartInd, predicateStartInd);    // extract nodeTest
  }
  else {
    resultStep.nodeTest = step.substring(nodeTestStartInd);                       // extract nodeTest
  }

  if (resultStep.nodeTest.indexOf('@') == 0) {                                    // '@' is shorthand for
    resultStep.axis = 'attribute';                                                // axis is atttribute
    resultStep.nodeTest = resultStep.nodeTest.substring(1);                       // remove '@' from string
  }

  if (resultStep.nodeTest.length == 0) {                                          // '//' is shorthand for
    resultStep.axis = 'descendant-or-self';                                       // axis is descendant-or-self
  }

  if (resultStep.nodeTest == '..') {                                              // '..' is shorthand for
    resultStep.axis = 'parent';                                                   // axis is parent
    resultStep.nodeTest = 'node()';
  }

  if (resultStep.nodeTest == '.') {                                               // '.' is shorthand for
    resultStep.axis = 'self';                                                     // axis is self
    resultStep.nodeTest = 'node()';
  }

  return resultStep;
};

// =========================================================================
// @method
// DOMNode._parseAxis - Parse the Axis, return axis type id
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @param 
// axisStr : string - the axis string
// 
// @return 
// : int
// ========================================================================= 
DOMNode.prototype._parseAxis = function(axisStr) {

  var returnAxisType = DOMNode.CHILD_AXIS;  // axis defaults to "child"
  // convert axis & alias strings to axisType consts
  if (axisStr == 'ancestor')                returnAxisType = DOMNode.ANCESTOR_AXIS;
  else if (axisStr == 'ancestor-or-self')   returnAxisType = DOMNode.ANCESTOR_OR_SELF_AXIS;
  else if (axisStr == 'attribute')          returnAxisType = DOMNode.ATTRIBUTE_AXIS;
  else if (axisStr == 'child')              returnAxisType = DOMNode.CHILD_AXIS;
  else if (axisStr == 'descendant')         returnAxisType = DOMNode.DESCENDANT_AXIS;
  else if (axisStr == 'descendant-or-self') returnAxisType = DOMNode.DESCENDANT_OR_SELF_AXIS;
  else if (axisStr == 'following')          returnAxisType = DOMNode.FOLLOWING_AXIS;
  else if (axisStr == 'following-sibling')  returnAxisType = DOMNode.FOLLOWING_SIBLING_AXIS;
  else if (axisStr == 'namespace')          returnAxisType = DOMNode.NAMESPACE_AXIS;
  else if (axisStr == 'parent')             returnAxisType = DOMNode.PARENT_AXIS;
  else if (axisStr == 'preceding')          returnAxisType = DOMNode.PRECEDING_AXIS;
  else if (axisStr == 'preceding-sibling')  returnAxisType = DOMNode.PRECEDING_SIBLING_AXIS;
  else if (axisStr == 'self')               returnAxisType = DOMNode.SELF_AXIS
  else if (axisStr == 'root')               returnAxisType = DOMNode.ROOT_AXIS


  return returnAxisType;
}


// =========================================================================
// @method
// DOMNode._parseNodeTest - Parse the NodeTest, return axis type id
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @param 
// nodeTestStr : string - the node test string
// 
// @return
// (
//   type : int - the nodeTest type id
//   value : string - the string of the nodeName or nodeType (depending on type)
// )
// ========================================================================= 
DOMNode.prototype._parseNodeTest = function(nodeTestStr) {
  var returnNodeTestObj = new Object();
  
  if (nodeTestStr.length == 0) {
    returnNodeTestObj.type = DOMNode.NODE_TYPE_TEST
    returnNodeTestObj.value = 'node';
  }
  else {
    var funInd = nodeTestStr.indexOf('(');
    if (funInd > -1) {
      returnNodeTestObj.type = DOMNode.NODE_TYPE_TEST
      returnNodeTestObj.value = nodeTestStr.substring(0, funInd);
    }
    else {
      returnNodeTestObj.type = DOMNode.NODE_NAME_TEST
      returnNodeTestObj.value = nodeTestStr;
    }
  }
    
  return returnNodeTestObj;
};


// =========================================================================
// @method
// DOMNode._parsePredicates - Parse the PredicateList, return array of Predicate Expression strings
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @param 
// predicateListStr : string - the string containing list of (stacked) predicates
// 
// @return 
// [
//   : string - the string of a Predicate Expression (sans enclosing '[]'s)
// ]
// ========================================================================= 
DOMNode.prototype._parsePredicates = function(predicateListStr) {
  var returnPredicateArray = new Array();
  
  if (predicateListStr.length > 0) {
    // remove top & tail square brackets to simplify the following split
    var firstOpenBracket = predicateListStr.indexOf('[');
    var lastCloseBracket = predicateListStr.lastIndexOf(']');

    predicateListStr = predicateListStr.substring(firstOpenBracket+1, lastCloseBracket);
    
    // split predicate list on ']['
    returnPredicateArray = predicateListStr.split('][');
  }
  
  return returnPredicateArray;
}


// =========================================================================
// @class
// XPATHNodeSet - Container of Nodes selected by XPath (sub)expression
//
// @extends
// DOMNodeList
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @param 
// doc : string - the dom document that actually contains the Node data
// arr : [int]  - initial set of DOMNode _id(s)
// ========================================================================= 
XPATHNodeSet = function(ownerDocument, parentNode, nodeList) {
  this.DOMNodeList = DOMNodeList;
  this.DOMNodeList(ownerDocument, parentNode);
   if (nodeList) {
     for (var i=0; i < nodeList.length; i++) {
	    this._appendChild(nodeList.item(i));
	 }
   }

};
XPATHNodeSet.prototype = new DOMNodeList();


// =========================================================================
// @method
// XPATHNodeSet.selectNodeSet_recursive - Returns NodeSet containing nodes matching XPath expression
//   for all Nodes within NodeSet.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @param 
// xpath : string - the XPath expression
// 
// @return 
// : XPATHNodeSet 
// 
// ========================================================================= 
XPATHNodeSet.prototype.selectNodeSet_recursive = function(xpath) {
  var selectedNodeSet = new XPATHNodeSet(this.ownerDocument);

  for (var i=0; i < this.length; i++) {
    var candidateNode = this.item(i);
    selectedNodeSet.union(candidateNode.selectNodeSet_recursive(xpath));
  }
  
  return selectedNodeSet;
};

// =========================================================================
// @method
// XPATHNodeSet.getNamedItems - Returns NodeSet containing all nodes with specified name
//   selected from the Nodes within the NodeSet.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @param 
// xpath : string - the XPath expression
// 
// @return 
// : XPATHNodeSet 
// 
// ========================================================================= 
XPATHNodeSet.prototype.getNamedItems = function(nodeName) {
  var namedItemsNodeSet = new XPATHNodeSet(this.ownerDocument);

  for (var i=0; i < this.length; i++) {
    var candidateNode = this.item(i);
    if ((nodeName == '*') || (candidateNode.nodeName == nodeName)) {
      namedItemsNodeSet._appendChild(candidateNode);
    }
  }
  
  return namedItemsNodeSet;
};

// =========================================================================
// @method
// XPATHNodeSet.getTypedItems - Returns NodeSet containing all nodes with specified type
//   selected from the Nodes within the NodeSet.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @param 
// nodeType : int - the type id 
// 
// @return 
// : XPATHNodeSet 
// 
// ========================================================================= 
XPATHNodeSet.prototype.getTypedItems = function(nodeType) {
  var typedItemsNodeSet = new XPATHNodeSet(this.ownerDocument);
  var nodeTypeId;
  
       if (nodeType.toLowerCase() == "node")    { nodeTypeId = 0; }
  else if (nodeType.toLowerCase() == "text")    { nodeTypeId = DOMNode.TEXT_NODE; }
  else if (nodeType.toLowerCase() == "comment") { nodeTypeId = DOMNode.COMMENT_NODE; }
  else if (nodeType.toLowerCase() == "processing-instruction") { nodeTypeId = DOMNode.PROCESSING_INSTRUCTION_NODE; }
  
  for (var i=0; i < this.length; i++) {
    var candidateNode = this.item(i);
    if ((nodeTypeId == 0) || (candidateNode.nodeType == nodeTypeId)) {
      typedItemsNodeSet._appendChild(candidateNode);
    }
  }
  
  return typedItemsNodeSet;
};




// =========================================================================
// @method
// XPATHNodeSet._getAxis - Returns a NodeSet containing the concatenation of all of the nodes 
//   in the specified Axis for each node in the current NodeSet.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @param 
// axisConst : int - the Axis type id
// 
// @return 
// : XPATHNodeSet
// 
// ========================================================================= 
XPATHNodeSet.prototype._getAxis = function(axisConst) { 
  if (axisConst == DOMNode.ANCESTOR_AXIS)                return this._getAncestorAxis();
  else if (axisConst == DOMNode.ANCESTOR_OR_SELF_AXIS)   return this._getAncestorOrSelfAxis();
  else if (axisConst == DOMNode.ATTRIBUTE_AXIS)          return this._getAttributeAxis();
  else if (axisConst == DOMNode.CHILD_AXIS)              return this._getChildAxis();
  else if (axisConst == DOMNode.DESCENDANT_AXIS)         return this._getDescendantAxis();
  else if (axisConst == DOMNode.DESCENDANT_OR_SELF_AXIS) return this._getDescendantOrSelfAxis();
  else if (axisConst == DOMNode.FOLLOWING_AXIS)          return this._getFollowingAxis();
  else if (axisConst == DOMNode.FOLLOWING_SIBLING_AXIS)  return this._getFollowingSiblingAxis();
  else if (axisConst == DOMNode.NAMESPACE_AXIS)          return this._getNamespaceAxis();
  else if (axisConst == DOMNode.PARENT_AXIS)             return this._getParentAxis();
  else if (axisConst == DOMNode.PRECEDING_AXIS)          return this._getPrecedingAxis();
  else if (axisConst == DOMNode.PRECEDING_SIBLING_AXIS)  return this._getPrecedingSiblingAxis();
  else if (axisConst == DOMNode.SELF_AXIS)               return this._getSelfAxis();
  else if (axisConst == DOMNode.ROOT_AXIS)               return this._getRootAxis();

  else {
    alert('Error in XPATHNodeSet._getAxis: Attempted to get unknown axis type '+ axisConst);
    return null;
  }
};

   
// =========================================================================
// @method
// XPATHNodeSet._getAncestorAxis - Return a NodeSet containing the concatenation of all of the nodes
//   in the ancestor Axis for each node in the current NodeSet.
//   The ancestor axis contains the ancestors of the context node; the ancestors of the
//   context node consist of the parent of context node and the parent's parent and so on; 
//   thus, the ancestor axis will always include the root node, unless the context node is
//   the root node.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return 
// : XPATHNodeSet 
// 
// ========================================================================= 
XPATHNodeSet.prototype._getAncestorAxis = function() {
  var ancestorAxisNodeSet = new XPATHNodeSet(this.ownerDocument);

  for (var i=0; i < this.length; i++) {
    ancestorAxisNodeSet.union(this.item(i)._getDescendantAxis());
  }
};


// =========================================================================
// @method
// DOMNode._getAncestorOrSelfAxis - Return a NodeSet  containing the concatenation of all of the nodes
//   in the ancestor or self Axis for each node in the current NodeSet.
//   The ancestor-or-self axis contains the context node and the ancestors of the
//   context node; thus, the ancestor axis will always include the root node.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return 
// : XPATHNodeSet 
// 
// ========================================================================= 
XPATHNodeSet.prototype._getAncestorOrSelfAxis = function() {
  var ancestorOrSelfAxisNodeSet = new XPATHNodeSet(this.ownerDocument);

  for (var i=0; i < this.length; i++) {
    ancestorOrSelfAxisNodeSet.union(this.item(i)._getAncestorOrSelfAxis());
  }
};


// =========================================================================
// @method
// DOMNode._getAttributeAxis - Return a NodeSet  containing the concatenation of all of the nodes 
//   in the attribute Axis for each node in the current NodeSet.
//   The attribute axis contains the attributes of the context node; the axis will 
//   be empty unless the context node is an element.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return 
// : XPATHNodeSet 
// 
// ========================================================================= 
XPATHNodeSet.prototype._getAttributeAxis = function() { 
  var attributeAxisNodeSet = new XPATHNodeSet(this.ownerDocument);

  for (var i=0; i < this.length; i++) {
    attributeAxisNodeSet.union(this.item(i)._getAttributeAxis());
  }
};


// =========================================================================
// @method
// DOMNode._getChildAxis - Return a NodeSet  containing the concatenation of all of the nodes 
//   in the child Axis for each node in the current NodeSet.
//   The child axis contains the children of the context node.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return 
// : XPATHNodeSet 
// 
// ========================================================================= 
XPATHNodeSet.prototype._getChildAxis = function() { 
  var childNodeSet = new XPATHNodeSet(this.ownerDocument);

  for (var i=0; i < this.length; i++) {
    childNodeSet.union(this.item(i)._getChildAxis());
  }
};


// =========================================================================
// @method
// DOMNode._getDescendantAxis - Return a NodeSet  containing the concatenation of all of the nodes 
//   in the descendant Axis for each node in the current NodeSet.
//   The descendant axis contains the descendants of the context node; a descendant is 
//   a child or a child of a child and so on; thus the descendant axis never contains 
//   attribute or namespace nodes.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return 
// : XPATHNodeSet 
// 
// ========================================================================= 
XPATHNodeSet.prototype._getDescendantAxis = function() { 
  var descendantNodeSet = new XPATHNodeSet(this.ownerDocument);

  for (var i=0; i < this.length; i++) {
    descendantNodeSet.union(this.item(i)._getDescendantAxis());
  }
};



// =========================================================================
// @method
// DOMNode._getReversedDescendantAxis - Return a NodeSet  containing the concatenation of all of the nodes 
//   in the descendant Axis for each node in the current NodeSet in Reverse Document Order.
//   The descendant axis contains the descendants of the context node; a descendant is 
//   a child or a child of a child and so on; thus the descendant axis never contains 
//   attribute or namespace nodes.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return 
// : XPATHNodeSet 
// 
// ========================================================================= 
XPATHNodeSet.prototype._getReversedDescendantAxis = function() { 
  var descendantNodeSet = new XPATHNodeSet(this.ownerDocument);

  for (var i=this.length-1; i >= 0 ; i--) {
    descendantNodeSet.union(this.item(i)._getReversedDescendantAxis());
  }
};


// =========================================================================
// @method
// DOMNode._getDescendantOrSelfAxis - Return a NodeSet  containing the concatenation of all of the nodes 
//   in the descendant or self  Axis for each node in the current NodeSet.
//   The descendant-or-self axis contains the context node and the descendants of the context node.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return 
// : XPATHNodeSet 
// 
// ========================================================================= 
XPATHNodeSet.prototype._getDescendantOrSelfAxis = function() { 
  var descendantOrSelfNodeSet = new XPATHNodeSet(this.ownerDocument);

  for (var i=0; i < this.length; i++) {
    descendantOrSelfNodeSet.union(this.item(i)._getDescendantOrSelfAxis());
  }
};


// =========================================================================
// @method
// DOMNode._getFollowingAxis - Return a NodeSet  containing the concatenation of all of the nodes 
//   in the following Axis for each node in the current NodeSet.
//   The following axis contains all nodes in the same document as the context node that 
//   are after the context node in document order, excluding any descendants and excluding 
//   attribute nodes and namespace nodes.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return 
// : XPATHNodeSet 
// 
// ========================================================================= 
XPATHNodeSet.prototype._getFollowingAxis = function() { 
  var followingNodeSet = new XPATHNodeSet(this.ownerDocument);

  for (var i=0; i < this.length; i++) {
    followingNodeSet.union(this.item(i)._getFollowingAxis());
  }
};


// =========================================================================
// @method
// DOMNode._getFollowingSiblingAxis - Return a NodeSet  containing the concatenation of all of the nodes 
//   in the following-sibling Axis for each node in the current NodeSet.
//   The following-sibling axis contains all the following siblings of the context node; 
//   if the context node is an attribute node or namespace node, the following-sibling axis is empty.
//   ie, only the nodes that are sibling to the context node, not their descandants
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return 
// : XPATHNodeSet 
// 
// ========================================================================= 
XPATHNodeSet.prototype._getFollowingSiblingAxis = function() { 
  var followingSibilingNodeSet = new XPATHNodeSet(this.ownerDocument);

  for (var i=0; i < this.length; i++) {
    followingSibilingNodeSet.union(this.item(i)._getFollowingSiblingAxis());
  }
};


// =========================================================================
// @method
// DOMNode._getNamespaceAxis - Return a NodeSet  containing the concatenation of all of the nodes 
//   in the namespace Axis for each node in the current NodeSet.
//   the namespace axis contains the namespace nodes of the context node; the axis will 
//   be empty unless the context node is an element.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return 
// : XPATHNodeSet 
// 
// ========================================================================= 
// XPATHNodeSet.prototype._getNamespaceAxis = function() { };
//  NOT IMPLEMENTED



// =========================================================================
// @method
// DOMNode._getParentAxis - Return a NodeSet containing the concatenation of all of the nodes 
//   in the parent Axis for each node in the current NodeSet.
//   The parent axis contains the parent of the context node, if there is one.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return 
// : XPATHNodeSet 
// 
// ========================================================================= 
XPATHNodeSet.prototype._getParentAxis = function() {
  var parentNodeSet = new XPATHNodeSet(this.ownerDocument);

  for (var i=0; i < this.length; i++) {
    parentNodeSet.union(this.item(i)._getParentAxis());
  }
};


// =========================================================================
// @method
// DOMNode._getPrecedingAxis - Return a NodeSet containing the concatenation of all of the nodes 
//   in the preceding Axis for each node in the current NodeSet.
//   The preceding axis contains all nodes in the same document as the context node that 
//   are before the context node in document order, excluding any ancestors and excluding
//   attribute nodes and namespace nodes.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return 
// : XPATHNodeSet 
// 
// ========================================================================= 
XPATHNodeSet.prototype._getPrecedingAxis = function() { 
  var precedingNodeSet = new XPATHNodeSet(this.ownerDocument);

  for (var i=0; i < this.length; i++) {
    precedingNodeSet.union(this.item(i)._getPrecedingAxis());
  }
};


// =========================================================================
// @method
// DOMNode._getPrecedingSiblingAxis - Return a NodeSet  containing the concatenation of all of the nodes 
//   in the preceding Axis for each node in the current NodeSet.
//   The preceding axis contains all nodes in the same document as the context node that 
//   are before the context node in document order, excluding any ancestors and excluding 
//   attribute nodes and namespace nodes.
//   ie, only the nodes that are sibling to the context node, not their descandants
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return 
// : XPATHNodeSet 
// 
// ========================================================================= 
XPATHNodeSet.prototype._getPrecedingSiblingAxis = function() { 
  var precedingSiblingNodeSet = new XPATHNodeSet(this.ownerDocument);

  for (var i=0; i < this.length; i++) {
    precedingSiblingNodeSet.union(this.item(i)._getPrecedingSiblingAxis());
  }
};


// =========================================================================
// @method
// DOMNode._getSelfAxis - Return a NodeSet  containing the concatenation of all of the nodes 
//   in the self Axis for each node in the current NodeSet.
//   The self axis contains just the context node itself.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return 
// : XPATHNodeSet 
// 
// ========================================================================= 
XPATHNodeSet.prototype._getSelfAxis = function() { 
  var selfNodeSet = new XPATHNodeSet(this.ownerDocument);

  for (var i=0; i < this.length; i++) {
    selfNodeSet.union(this.item(i)._getSelfAxis());
  }
};


// =========================================================================
// @method
// DOMNode.union - Return a NodeSet containing the concatenation of the current NodeSet
//   and the supplied NodeSet
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @param
// nodeSet : XPATHNodeSet - the NodeSet to be appended to this NodeSet
//
// @return
// : XPATHNodeSet
//
// ========================================================================= 
XPATHNodeSet.prototype.union = function(nodeSet) {
  for (var i=0; i < nodeSet.length; i++) {
    this._appendChild(nodeSet.item(i));
  }

  return this;
};


// =========================================================================
// @method
// DOMNode._getContainingNodeSet - Return this NodeSet.
//   Note: this method is called from within the evaluation of a Predicate Expression,
//   so that the Context Node can be advised of its containing NodeSet.
//   This is required for the purposes of functions like poisition().
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @return 
// : XPATHNodeSet 
// 
// ========================================================================= 
XPATHNodeSet.prototype._getContainingNodeSet = function() { 
  return this;
};

XPATHNodeSet.prototype.getLength = function() {
    return this.length;
}

// =========================================================================
// @method
// XPATHNodeSet.filter - Returns a NodeSet containing the Nodes within the current NodeSet
//   that satisfy the Predicate Expression.
//
// @author
// Jon van Noort (jon@webarcana.com.au)
//
// @param
// expressionStr : string - the XPathe Predicate Expression
//
// @return
// : XPATHNodeSet
//
// =========================================================================
XPATHNodeSet.prototype.filter = function(expressionStr) {
  var matchingNodeSet = new XPATHNodeSet(this.ownerDocument);

  for (var i=0; i < this.length; i++) {
    if (this.item(i)._filter(expressionStr, this)) {
      matchingNodeSet._appendChild(this.item(i));
    }
  }

  return matchingNodeSet;
};


function __removeFirstArrayElement(oldArray) {
    var newArray = new Array();

    try {
        for (intLoop = 1; intLoop < oldArray.length; intLoop++) {
            newArray[newArray.length] = oldArray[intLoop];
        }
    }
    catch (e) {
        //no op
    }
    return newArray;

}



