/*
 * $Id: xbDOMParser.js,v 1.5 2003/09/14 21:22:26 bc Exp $
 *
 */

/* ***** BEGIN LICENSE BLOCK *****
 * The contents of this file are subject to the Mozilla Public License Version 
 * 1.1 (the "License"); you may not use this file except in compliance with 
 * the License. You may obtain a copy of the License at 
 * http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 * 
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bob Clary code.
 *
 * The Initial Developer of the Original Code is
 * Bob Clary.
 * Portions created by the Initial Developer are Copyright (C) 2000
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s): Bob Clary <http://bclary.com/>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 * 
 ***** END LICENSE BLOCK ***** */

xbParser_TOK_NONE          = 0;
xbParser_TOK_WHITE         = 1;
xbParser_TOK_OTHER         = 2;
xbParser_TOK_LT            = 3;
xbParser_TOK_GT            = 4;
xbParser_TOK_LT_SLASH      = 5;
xbParser_TOK_SLASH_GT      = 6;
xbParser_TOK_NMSP          = 7;
xbParser_TOK_SQUOTE        = 8;
xbParser_TOK_DQUOTE        = 9;
xbParser_TOK_STRING        = 10;
xbParser_TOK_COMMENT_START = 11;
xbParser_TOK_COMMENT_END   = 12;
xbParser_TOK_COMMENT       = 13; 
xbParser_TOK_CDATA_START   = 14;
xbParser_TOK_CDATA_END     = 15;
xbParser_TOK_CDATA         = 16; 
xbParser_TOK_IDENTIFIER    = 17;
xbParser_TOK_PI_START      = 18;
xbParser_TOK_PI_END        = 19;
xbParser_TOK_DOCTYPE       = 20;

_classes.registerClass('xbParser_Error')

function xbParser_Error(line, col, msg)
{
  _classes.defineClass('xbParser_Error', _prototype_func);
  
  this.init(line, col, msg);

  function _prototype_func()
  {
    xbParser_Error.prototype.init = init;
    function init(line, col, msg)
    {
      this.line = line;
      this.col  = col;
      this.msg  = msg;
    }
    
    xbParser_Error.prototype.toString = toString;
    function toString()
    {
      return 'Error at line: ' + this.line + ' col: ' + this.col + ' ' + this.msg;
    }
  }  
}

_classes.registerClass('xbParser_TokenPrefix');

function xbParser_TokenPrefix(str, type, isaPrefix)
{
  _classes.defineClass('xbParser_TokenPrefix', _prototype_func);
  
  this.init(str, type, isaPrefix);
  
  function _prototype_func()
  {
    xbParser_TokenPrefix.prototype.init    = init;
    function init(str, type, isaPrefix)
    {
      this.parentMethod('init');
      
      this.str = str;
      this.type = type;
      this.isaPrefix = isaPrefix;
    }
  }
}

_classes.registerClass('xbParser_TokenPrefixes');

function xbParser_TokenPrefixes()
{
  _classes.defineClass('xbParser_TokenPrefixes', _prototype_func);
  
  this.init();
  
  function _prototype_func()
  {
    xbParser_TokenPrefixes.prototype._prefixes = new Object();
    
    xbParser_TokenPrefixes.prototype.init = init;
    function init()
    {
      this.parentMethod('init');
      xbParser_TokenPrefixes._prefixes = new Object();
    }
    
    xbParser_TokenPrefixes.prototype.add = add;
    function add(str, type, isaPrefix)
    {
      this._prefixes[str] = new xbParser_TokenPrefix(str, type, isaPrefix);
    }
    
    xbParser_TokenPrefixes.prototype.find = find;
    function find(str)
    {
      return this._prefixes[str];
    }
  }
}

_classes.registerClass('xbParser_Token');

function xbParser_Token(value, type)
{
  _classes.defineClass('xbParser_Token', _prototype_func);
  
  this.init(value, type);
  
  function _prototype_func()
  {
    xbParser_Token.prototype.value  = null;
    xbParser_Token.prototype.type   = null;
    
    xbParser_Token.prefixes      = new xbParser_TokenPrefixes();
      
    // tokens that are not prefixes...
    xbParser_Token.prefixes.add('\'',        xbParser_TOK_SQUOTE,    false);
    xbParser_Token.prefixes.add('"',         xbParser_TOK_DQUOTE,    false);
    xbParser_Token.prefixes.add('>',         xbParser_TOK_GT,      false);    
    xbParser_Token.prefixes.add(':',         xbParser_TOK_NMSP,      false);    
            
    // tokens prefixed with <
    xbParser_Token.prefixes.add('<',         xbParser_TOK_LT,      true);
    xbParser_Token.prefixes.add('</',        xbParser_TOK_LT_SLASH,    false);
    xbParser_Token.prefixes.add('<!',        xbParser_TOK_NONE,      true);
    xbParser_Token.prefixes.add('<!-',       xbParser_TOK_NONE,      true);
    xbParser_Token.prefixes.add('<!--',      xbParser_TOK_COMMENT_START,  false);
    xbParser_Token.prefixes.add('<![',       xbParser_TOK_NONE,      true);
    xbParser_Token.prefixes.add('<![C',      xbParser_TOK_NONE,      true);
    xbParser_Token.prefixes.add('<![CD',     xbParser_TOK_NONE,      true);
    xbParser_Token.prefixes.add('<![CDA',    xbParser_TOK_NONE,      true);
    xbParser_Token.prefixes.add('<![CDAT',   xbParser_TOK_NONE,      true);
    xbParser_Token.prefixes.add('<![CDATA',  xbParser_TOK_NONE,      true);
    xbParser_Token.prefixes.add('<![CDATA[', xbParser_TOK_CDATA_START,  false);
    xbParser_Token.prefixes.add('<!D',       xbParser_TOK_NONE,  true);
    xbParser_Token.prefixes.add('<!DO',      xbParser_TOK_NONE,  true);
    xbParser_Token.prefixes.add('<!DOC',     xbParser_TOK_NONE,  true);
    xbParser_Token.prefixes.add('<!DOCT',    xbParser_TOK_NONE,  true);
    xbParser_Token.prefixes.add('<!DOCTY',   xbParser_TOK_NONE,  true);
    xbParser_Token.prefixes.add('<!DOCTYP',  xbParser_TOK_NONE,  true);
    xbParser_Token.prefixes.add('<!DOCTYPE', xbParser_TOK_DOCTYPE,  false);
    xbParser_Token.prefixes.add('<?',        xbParser_TOK_PI_START,    false);

    // tokens prefixed with ?
    xbParser_Token.prefixes.add('?',         xbParser_TOK_OTHER,      true);
    xbParser_Token.prefixes.add('?>',        xbParser_TOK_PI_END,    false);
        
    // tokens prefixed with /
    xbParser_Token.prefixes.add('/',         xbParser_TOK_OTHER,      true);
    xbParser_Token.prefixes.add('/>',        xbParser_TOK_SLASH_GT,    false);

    // tokens prefixed with ]
    xbParser_Token.prefixes.add(']',         xbParser_TOK_OTHER,      true);
    xbParser_Token.prefixes.add(']]',        xbParser_TOK_NONE,      true);
    xbParser_Token.prefixes.add(']]>',       xbParser_TOK_CDATA_END,    true);

    // tokens prefixed with -
    xbParser_Token.prefixes.add('-',         xbParser_TOK_OTHER,      true);
    xbParser_Token.prefixes.add('--',        xbParser_TOK_OTHER,      true);
    xbParser_Token.prefixes.add('-->',       xbParser_TOK_COMMENT_END,  false);

    xbParser_Token.prototype.init = init;
    function init(value, type)
    {
      this.parentMethod('init');
      this.value    = value;
      this.type    = type;
    }
  }
}
  
_classes.registerClass('xbParser');

function xbParser(sXML)
{
  _classes.defineClass('xbParser', _prototype_func);
  
  this.init(sXML);
  
  function _prototype_func()
  {
    xbParser.prototype._sXML          = null;
    xbParser.prototype._offset        = null;
    xbParser.prototype.ownerDocument  = null;
    
    xbParser.prototype.init = init;
    function init(sXML)
    {
      this.parentMethod('init');
      this._sXML         = sXML;
      this._offset       = 0;
      this.ownerDocument = xbDOM2GetDOMImplementation().createDocument(null, 'xbParser', null);
      this.errors        = new Array();
      this.line          = 0;
      this.col           = 0;
    }
    
    xbParser.prototype._getc = _getc;
    function _getc()
    {
      var c = '';
      
      if (this._offset < this._sXML.length)
      {
        c = this._sXML.charAt(this._offset++);
        
        ++this.col;
        
        if (c == '\n')
        {
          ++this.line;
          this.col = 0;
        }
      }
      
      return c;
    }
    
    xbParser.prototype._ungetc = _ungetc;
    function _ungetc()
    {
      if (this._offset < 1 || this._offset > this._sXML.length)
        throw( new xbException('Invalid Offset in xbParser._ungetc', 'xbDOMParser.js', 'xbParser::_ungetc'));
        
      --this._offset;
      --this.col;
      if (this.col < 0)
      {
        this.col = 0;
        --this.line;
      }
      
    }
    
    xbParser.prototype.getToken  = getToken;
    function getToken(token)
    {
      var c;
      var xbParser_TokenPrefix;

      token.value = '';
      token.type = xbParser_TOK_NONE;

      c = this._getc();
      if (!c)
        return false;
        
      // whitespace
      if (c.match(/\s/))
      {
        while (c.match(/\s/))
        {
          token.value += c;
          c = this._getc();
        }
        if (c)
          this._ungetc();
          
        token.type = xbParser_TOK_WHITE;
        return true;
      }

      // identifiers
      if (c.match(/\w/))
      {
        while (c.match(/\w/) || c == '-')
        {
          token.value += c;
          c = this._getc();
        }
        if (c)
          this._ungetc();
          
        token.type = xbParser_TOK_IDENTIFIER;
        return true;
      }
      
      this._ungetc();

      do
      {
        c = this._getc();
        token.value += c;
        xbParser_TokenPrefix = xbParser_Token.prefixes.find(token.value);
      }
      while (c && xbParser_TokenPrefix && xbParser_TokenPrefix.isaPrefix);
      
      if (xbParser_TokenPrefix && xbParser_TokenPrefix.type != xbParser_TOK_NONE)
      {
        token.type = xbParser_TokenPrefix.type;
        return true;
      }
  
      while(token.value.length > 1 && (!xbParser_TokenPrefix || xbParser_TokenPrefix.type == xbParser_TOK_NONE) )
      {
        this._ungetc();
        token.value = token.value.substr(0, token.value.length - 1);
        xbParser_TokenPrefix = xbParser_Token.prefixes.find(token.value);
      }
      
      if (!xbParser_TokenPrefix)
      {
        token.type = xbParser_TOK_OTHER;
        return true;
      }
      else
      {
        token.type = xbParser_TokenPrefix.type;
        return true;
      }
    }
    
    xbParser.prototype.ungetToken = ungetToken;
    function ungetToken(token)
    {
      var len = token.value.length;
      
      if (this._offset < len || this._offset > this._sXML.length)
        throw( new xbException('Invalid Offset in xbParser._ungetToken', 'xbDOMParser.js', 'xbParser::ungetToken'));

      this._offset -= len;
    }
    
    xbParser.prototype.getTerminatedSequence = getTerminatedSequence;
    function getTerminatedSequence(term)
    {
      var c        = this._getc();
      var currTerm = c;
      var value    = c;
            
      while (c)
      {
        if (c == '\\')
        {
          c = this._getc();
          value += c;            
          //currTerm += c;
        }
        if (currTerm == term)
          break;
          
        c = this._getc();
        value += c;
        currTerm += c;
        
        if (currTerm.length > term.length)
          currTerm = currTerm.substr(1, term.length);
      }
        
      if (currTerm != term)
        throw (new xbException('Sequence Not Terminated', 'xbDOMParser.js', 'xbParser::getTerminatedSequence'));
      
      value = value.substr(0, value.length - term.length);  
      return value;
    }

    xbParser.prototype.parse = parse;
    function parse(strict)
    {
      var ownerDocument          = this.ownerDocument;
      var root                   = ownerDocument.getDocumentElement();
      var node                   = ownerDocument;
      var token                  = new xbParser_Token('', xbParser_TOK_NONE);
      var attributeName          = null;
      var EXPECT_LT              = 0;
      var EXPECT_START_TAG_NAME  = 1;
      var EXPECT_ATTRIBUTE_NAME  = 2;
      var EXPECT_ATTRIBUTE_EQUAL = 3;
      var EXPECT_ATTRIBUTE_VALUE = 4;
      var EXPECT_BODY            = 5;
      var EXPECT_TEXT            = 6;
      var EXPECT_STOP_TAG_NAME   = 7;
      var EXPECT_GT              = 8;
      var EXPECT_CDATA_END       = 9;
      var CLOSE_ELEMENT          = 10;
      var EXPECT_SCRIPT          = 11;
      var EXPECT_START_PI_TARGET = 12;
      var EXPECT_PI_DATA         = 13;
      var state                  = EXPECT_LT;
      
      if (typeof(strict) == 'undefined')
        strict = true;
        
      this.getToken(token);
      
      while (token.type != xbParser_TOK_NONE)
      {
        var toktype = token.type;
        
        switch (state)
        {
        case EXPECT_LT:
          switch (toktype)
          {
          case xbParser_TOK_LT:
            state = EXPECT_START_TAG_NAME;
            break;

          case xbParser_TOK_COMMENT_START:
            token.type  = xbParser_TOK_COMMENT;
            token.value = this.getTerminatedSequence('-->');          
            ownerDocument.insertBefore(ownerDocument.createComment(token.value), root);
            break;
            
          case xbParser_TOK_PI_START:
            state = EXPECT_START_PI_TARGET;
            break;
            
          case xbParser_TOK_DOCTYPE:
            token.type  = xbParser_TOK_DOCTYPE;
            token.value = this.getTerminatedSequence('-->');          
            // XXX DOCTYPE parsing not yet supported
            break;

          default:
            if (token.type != xbParser_TOK_WHITE)
              throw (new xbException('text node not allowed as document child', 'xbDOMParser.js', 'xbParser::parse'));
            state = EXPECT_LT;
            break;
            
          }
          this.getToken(token);
          break;

        case EXPECT_START_TAG_NAME:
          switch (toktype)
          {
          case xbParser_TOK_IDENTIFIER:
            var newNode = ownerDocument.createElement(token.value);
            if (node.isEqualTo(ownerDocument))
            {
              ownerDocument.replaceChild(newNode, root);
              root = node = newNode;
            }
            else
            {
              node = node.appendChild(newNode);
            }
            state = EXPECT_ATTRIBUTE_NAME;
            break;

          default:
            throw (new xbException('expected tagname', 'xbDOMParser.js', 'xbParser::parse'));
            break;
          }
          this.getToken(token);
          break;

        case EXPECT_START_PI_TARGET:
          switch (toktype)
          {
          case xbParser_TOK_WHITE:
            break;
            
          case xbParser_TOK_IDENTIFIER:
            var pi = ownerDocument.createProcessingInstruction(token.value, '');
            if (node == ownerDocument)
              node = node.insertBefore(pi, node.getDocumentElement()); // hack to put before document element
            else
              node = node.appendChild(pi);
            state = EXPECT_PI_DATA;
            break;

          default:
            this.errors[this.errors.length] = new xbParser_Error(this.line, this.col, 'Expected PI target');
            throw (new xbException('expected PI target', 'xbDOMParser.js', 'xbParser::parse'));
            break;
          }
          this.getToken(token);
          break;
          
        case EXPECT_PI_DATA:
          switch(toktype)
          {
          case xbParser_TOK_WHITE:
            this.getToken(token);
            // deliberate fall through
          default:
            if (token.value != '?>')
            {
              token.value += this.getTerminatedSequence('?>');
              node.setData(node.getData() + token.value);
            }
            node = node.getParentNode();
            if (node.isEqualTo(ownerDocument))
              state = EXPECT_LT;
            else
              state = EXPECT_BODY;
            break;
          }
          // eat whitespace 
          do
          {
            this.getToken(token);
          } while (token.type == xbParser_TOK_WHITE)
          break;

        case EXPECT_ATTRIBUTE_NAME:
          switch (toktype)
          {
          case xbParser_TOK_WHITE:
            break;
            
          /*
          case xbParser_TOK_COMMENT_START:
            token.type  = xbParser_TOK_COMMENT;
            token.value = this.getTerminatedSequence('-->');          
            break;
            */
            
          case xbParser_TOK_GT:
            if (strict)
              state = EXPECT_BODY;
            else
            {
              var tagName = node.getTagName();
              
              if (tagName.toLowerCase() == 'meta')
                state = CLOSE_ELEMENT;
              else if (tagName.toLowerCase() == 'link')
                state = CLOSE_ELEMENT;
              else if (tagName.toLowerCase() == 'script')
              {
                node = node.appendChild( ownerDocument.createTextNode(''));
                state = EXPECT_SCRIPT;
              }
              else
                state = EXPECT_BODY;
            }
            break;
            
          case xbParser_TOK_SLASH_GT:
            state = CLOSE_ELEMENT;
            break;
            
          case xbParser_TOK_IDENTIFIER:
            attributeName = token.value;
            state = EXPECT_ATTRIBUTE_EQUAL;
            break;
            
          default:
            if (strict)
            { 
              this.errors[this.errors.length] = new xbParser_Error(this.line, this.col, 'Expected Attribute Name');
              throw (new xbException('expected Attribute Name', 'xbDOMParser.js', 'xbParser::parse'));
            }
              
            attributeName += token.value;
            break;
          }
          if (state != CLOSE_ELEMENT)
            this.getToken(token);
          break;
          
        case EXPECT_ATTRIBUTE_EQUAL:
          if (strict)
          {
            if (token.value != '=')
            {
              this.errors[this.errors.length] = new xbParser_Error(this.line, this.col, 'Expected Attribute');
              throw( new xbException('Expected Attribute = ', 'xbDOMParser.js', 'xbParser::parse'));
            }
              
            state = EXPECT_ATTRIBUTE_VALUE;
            this.getToken(token);
          }
          else
          {
            if (token.value != '=')
            {
              node.setAttributeNode(ownerDocument.createAttribute(attributeName));
              attributeName = null;
              state = EXPECT_ATTRIBUTE_NAME;
            }
            else
            {
              state = EXPECT_ATTRIBUTE_VALUE;
              this.getToken(token);
            }
          }
          break;

        case EXPECT_ATTRIBUTE_VALUE:
          switch(toktype)
          {
          case xbParser_TOK_SQUOTE:
          case xbParser_TOK_DQUOTE:
            token.type  = xbParser_TOK_STRING;
            token.value = this.getTerminatedSequence(token.value);
            
            var attr = ownerDocument.createAttribute(attributeName);
            attr.setValue(token.value);
            node.setAttributeNode(attr);
            attributeName = null;
            state = EXPECT_ATTRIBUTE_NAME;
            break;
            
          default:
            if (strict)
            {
              this.errors[this.errors.length] = new xbParser_Error(this.line, this.col, 'Expected string attribute value');
              throw (new xbException('Expected string attribute value', 'xbDOMParser.js', 'xbParser::parse'));
            }
            
            // collect remainder of non quoted attribute,... ie attr=func()
            var c;
            c = this._getc();
            while (c && c != '>' && c.match(/\S/))
            {
              token.value += c;
              c = this._getc();
            }
            if (c)
              this._ungetc(c);
            
            var attr = ownerDocument.createAttribute(attributeName);
            attr.setValue(token.value);
            node.setAttributeNode(attr);
            attributeName = null;
            state = EXPECT_ATTRIBUTE_NAME;
            break;
          }
          this.getToken(token);
          break;
          
        case EXPECT_BODY:
          switch(toktype)
          {
          case xbParser_TOK_LT:
            state = EXPECT_START_TAG_NAME;
            break;
            
          case xbParser_TOK_LT_SLASH:
            state = EXPECT_STOP_TAG_NAME;
            break;
            
          case xbParser_TOK_COMMENT_START:
            token.type  = xbParser_TOK_COMMENT;
            token.value = this.getTerminatedSequence('-->');          
            node.appendChild(ownerDocument.createComment(token.value));
            break;
            
          case xbParser_TOK_CDATA_START:
            token.type  = xbParser_TOK_CDATA;
            token.value = this.getTerminatedSequence(']]>');          
            node.appendChild(ownerDocument.createCDATASection(token.value));
            break;

          default:
            node.appendChild(ownerDocument.createTextNode(token.value));
            state = EXPECT_TEXT;
            break;
          }
          this.getToken(token);
          break;
          
        case EXPECT_TEXT:
          switch(toktype)
          {
          case xbParser_TOK_LT:
            if (node.isEqualTo(ownerDocument))
              state = EXPECT_LT;
            else
              state = EXPECT_BODY;
            break;
            
          case xbParser_TOK_LT_SLASH:
            if (node.isEqualTo(ownerDocument))
              state = EXPECT_LT;
            else
              state = EXPECT_BODY;

            break;

          case xbParser_TOK_COMMENT_START:
            token.type  = xbParser_TOK_COMMENT;
            token.value = this.getTerminatedSequence('-->');          

            node.appendChild(ownerDocument.createComment(token.value));
            if (node.isEqualTo(ownerDocument))
              state = EXPECT_LT;
            else
              state = EXPECT_BODY;

            this.getToken(token);
            break;

          default:
            node.appendChild(ownerDocument.createTextNode(token.value));
            this.getToken(token);
            break;
          }
          break;
          
        case EXPECT_SCRIPT:
          switch(toktype)
          {
          case xbParser_TOK_SQUOTE:
            token.value += this.getTerminatedSequence('\'') + '\'';          
            node.appendData(token.value);
            this.getToken(token);
            break;

          case xbParser_TOK_DQUOTE:
            token.value += this.getTerminatedSequence('"') + '"';          
            node.appendData(token.value);
            this.getToken(token);
            break;
            
          case xbParser_TOK_LT_SLASH:
            state = EXPECT_STOP_TAG_NAME;
            node = node.getParentNode();
            this.getToken(token);
            break;

          default:
            node.appendData(token.value);
            this.getToken(token);
            break;
          }
          break;
          
        case EXPECT_STOP_TAG_NAME:
          if (strict)
          {
            if (token.value != node.getTagName())
            {
              this.errors[this.errors.length] = new xbParser_Error(this.line, this.col, 'End Tag mismatch expecting ' + node.getTagName() + ' found ' + token.value);
              throw(new xbException('end tag mismatch', 'xbDOMParser.js', 'xbParser::parse'));
            }
  
            state = EXPECT_GT;
          }
          else
          {
            if (node.getTagName && token.value == node.getTagName())
              state = EXPECT_GT;
            else
            {
              // here we have found </tag> that doesn't match the current
              // node. that means we have an 'open' tag that must be closed
              // close 'open' node, then retry;
              node = node.getParentNode();
              this.ungetToken(token);
            }
          }
          this.getToken(token);
          break;
          
        case EXPECT_GT:
          if (token.value != '>')
          {
            this.errors[this.errors.length] = new xbParser_Error(this.line, this.col, 'missing >');
            throw ('missing >');
          }
            
          state = CLOSE_ELEMENT;
          break;

        case CLOSE_ELEMENT:  
          node = node.getParentNode();
          
          if (node.isEqualTo(ownerDocument))
            state = EXPECT_LT;
          else
            state = EXPECT_BODY;
          
          this.getToken(token);
          break;  
            
        default:
          throw(new xbException('invalid state in parse', 'xbDOMParser.js', 'xbParser::parse'));
          break;
        }
      }
      
      if (node.getNodeName() != '#text' && node.isEqualTo(root))
      {
        this.errors[this.errors.length] = new xbParser_Error(this.line, this.col, 'missing end tag node for ' + node.getNodeName());
        throw(new xbException('missing end tag', 'xbDOMParser.js', 'xbParser::parse'));
      }
        
      return ownerDocument;
    }    
  }
}

// eof: xbDOMParser.js

