/***************************************************************************
                          SIMPLY POWERFUL TOOLKIT (SPTK)
                          CXmlParser.cpp  -  description
                             -------------------
    begin                : Sun May 22 2003
    based on the code    : Mikko Lahteenmaki <Laza@Flashmail.com>
    copyright            : (C) 2003-2006 by Alexey S.Parshin
    email                : alexeyp@gmail.com
 ***************************************************************************/

/***************************************************************************
   This library is free software; you can redistribute it and/or modify it 
   under the terms of the GNU Library General Public License as published by
   the Free Software Foundation; either version 2 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 Library
   General Public License for more details.

   You should have received a copy of the GNU Library 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.

   Please report all bugs and problems to "alexeyp@gmail.com"
 ***************************************************************************/

#include <sptk3/CException.h>

#include <sptk3/CStrings.h>
#include <sptk3/xml/CXmlParser.h>
#include <sptk3/xml/CXmlDoc.h>
#include <sptk3/xml/CXmlNode.h>

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>

#include <map>

using namespace std;
using namespace sptk;

// 
// Our hardcoded DTD :)
// Will be replaced at some time in future.. 
//
// HTML 3.2 / 4.0 STD entities

// %list 
#define content_list    " UL OL DIR MENU "
// %preformatted 
#define content_preform    " PRE "
// %heading 
#define content_heading    " H1 H2 H3 H4 H5 H6 "
// %font 
#define content_font    " TT I B U S STRIKE BIG SMALL "
// %phrase 
#define content_phase      " EM STRONG DFN CODE SAMP KBD VAR CITE ACRONYM "
// %special 
#define content_special    " A IMG APPLET OBJECT FONT BASEFONT BR SCRIPT MAP Q SUB SUP SPAN BDO IFRAME "
// %formctrl
#define content_formctl    " INPUT SELECT TEXTAREA LABEL BUTTON "
// #PCDATA
#define content_cdata      " CDATA "
// %blocklevel
#define content_blocklevel " P " content_heading content_list content_preform " DL DIV CENTER NOSCRIPT NOFRAMES BLOCKQUOTE FORM ISINDEX HR TABLE FIELDSET ADDRESS "
// #empty
#define content_empty      0

// %inline
#define content_inline     content_cdata content_font content_phase content_special content_formctl
// %block
#define content_block      content_blocklevel content_inline

// Not std entities:
#define content_body    content_block " INS DEL "
#define content_pre        content_inline
#define content_head    " TITLE ISINDEX BASE SCRIPT STYLE META LINK "
#define content_table      " CAPTION TR COL COLGROUP THEAD TFOOT TBODY "

struct html_tag {
   const char *tag;
   const char *cont;
   bool require_endtag;
   bool is_block;
} html_tags[] = {
   { "A",            content_inline,      true, false },
   { "ACRONYM",      content_inline,      true, false },
   { "ADDRESS",      content_inline " P ", true, false },
   { "APPLET",       content_inline,      true, true },
   { "AREA",         content_empty,    false,   false },

   { "B",            content_inline,      true, false },
   { "BASE",         content_empty,    false,   false },
   { "BASEFONT",     content_empty,    false,   false },
   { "BDO",       content_inline,      true, true },
   { "BIG",       content_inline,      true, false },
   { "BLOCKQUOTE",      content_block,    true, true },
   { "BODY",         content_body,     false,   true },
   { "BR",           content_empty,    false,   false },
   { "BUTTON",       content_inline,      true, true },

   { "CAPTION",      content_inline,      true, true},
   { "CENTER",       content_body,     true, true },
   { "CITE",         content_inline,      true, false },
   { "CODE",         content_inline,      true, false },
   { "COL",       content_empty,    false,   false },
   { "COLGROUP",     content_empty,    false,   false },

   { "DD",           content_block,    false,   true },
   { "DEL",       content_inline,      true, true },
   { "DFN",       content_inline,      true, true },
   { "DIR",       " LI "content_block, true, true },
   { "DIV",       content_block,    true, true },
   { "DL",           " FORM DT DD ",      true, true },
   { "DT",           content_inline,      false,   true },

   { "EM",           content_inline,      true, false },

   // fieldset
   { "FONT",         content_inline,      true, false},
   { "FORM",         content_block " SCRIPT", true,   true },
   // frame
   // frameset

   { "H1",           content_inline,      true, true },
   { "H2",           content_inline,      true, true },
   { "H3",           content_inline,      true, true },
   { "H4",           content_inline,      true, true },
   { "H5",           content_inline,      true, true },
   { "H6",           content_inline,      true, true },
   { "HEAD",         content_head,     false,   true },
   { "HR",           content_empty,    false,   false },
   { "HTML",         " HEAD FRAMESET BODY ", false, true },

   { "I",            content_inline,      true, false },
   { "IMG",       content_empty,    false,   false },
   { "INPUT",        content_empty,    false,   false },
   { "ISINDEX",      content_empty,    false,   false },
   { "KBD",       content_inline,      true, false },

   { "LI",           content_block,    false,   true },
   { "LINK",         content_empty,    false,   false},
   { "LISTING",      content_cdata,    true, true },

   { "MAP",       " AREA ",         true, false },
   { "MENU",         content_block " LI ", true, true },
   { "META",         content_empty,    false,   false },

   { "NOSCRIPT",     content_block,    true, true },
   { "NOFRAMES",     content_body,     true, true },

   { "OL",           " FORM LI ",      true, true },
   { "OPTION",       content_cdata,    false,   true },

   { "P",            content_inline,      false,   true },
   { "PARAM",        content_empty,    false,   false },
   { "PLAINTEXT",    content_cdata,    false,   true },
   { "PRE",       content_pre,      true, true },

   { "SAMP",         content_inline,      true, false },
   { "SCRIPT",       content_cdata,    true, true },
   { "SELECT",       " OPTION ",       true, true },
   { "SMALL",        content_inline,      true, false },
   { "SPAN",         content_inline,      true, true },
   { "STRIKE",       content_inline,      true, false },
   { "STRONG",       content_inline,      true, false },
   { "STYLE",        content_cdata,    true, true },
   { "SUB",       content_inline,      true, false },
   { "SUP",       content_inline,      true, false },

   { "TABLE",        content_table,    true, true },
   { "TBODY",        " FORM TR ",      false,   true },
   { "TD",           content_body,     false,   true },
   { "TEXTAREA",     content_cdata,    true, false },
   { "TFOOT",        " FORM TR ",      false,   true },
   { "TH",           content_body,     false,   true },
   { "TITLE",        content_cdata,    true, false },
   { "TR",           " FORM TH TD ",      false,   true },
   { "TT",           content_inline,      true, false },

   { "U",            content_inline,      true, false },
   { "UL",           " FORM LI ",      true, true },

   { "VAR",       content_inline,      true, false },

   { "XMP",       content_cdata,    true, false },
};
static int tag_table_size = sizeof(html_tags) / sizeof(html_tags[0]);

class TagCache
{
   typedef std::map<std::string,struct html_tag *> CTagCacheMap;
public:
   std::map<std::string,struct html_tag *> tag_hash;

   TagCache() {
      for(int n = 0; n < tag_table_size; n++) {
         struct html_tag *tag = &html_tags[n];
         tag_hash[tag->tag] = tag;
      }
   }

   const struct html_tag *find(const std::string &tag) const {
      CTagCacheMap::const_iterator itor = tag_hash.find(tag);
      if (itor != tag_hash.end())
         return itor->second;
      return 0L;
   }
};

static const TagCache cache;

bool is_content(const std::string &tag, const std::string &content) 
{
   std::string search(" "+tag+" ");
   const struct html_tag *cont = cache.find(content);
   if(cont && cont->cont)
      return (strstr(cont->cont, search.c_str())!=0);
   return false;
}

bool require_endtag(const std::string &tag)
{
   const struct html_tag *cont = cache.find(tag);
   return (cont && cont->require_endtag);
}

bool is_html_leaf(const std::string &tag) 
{
   const struct html_tag *cont = cache.find(tag);
   if(cont) return (!cont->require_endtag && !cont->cont);
   return false;
}

bool is_html_block(const std::string &tag) 
{
   const struct html_tag *cont = cache.find(tag);
   return (cont && cont->is_block); 
}

bool is_html_tag(const std::string &tag) 
{
   const struct html_tag *cont = cache.find(tag);
   return (cont!=0);
}

///////////////////////////
// END OF HTML GLOBALS ////
///////////////////////////

// xmlparser methods
CXmlParser::CXmlParser(CXmlTokenizer &tok)
: tokenizer(tok)
{
   m_eventhandler = 0;

   m_htmlMode = false;    
   tag_end = false;
   force_ret = 0;
}

#define throw_error(error) throw CXmlException(error, tokenizer.locator())

bool CXmlParser::parseDocument(CXmlDoc *doc)
{
   if (handler()) {
      CXmlLocator *loc = 0;
      handler()->setLocator(&loc);
      if(loc) tokenizer.locator(loc);

      handler()->startDocument();
   }

   CXmlNode *subnode = 0;

   while (parseNode(doc, subnode)) {}

   // if successful, put node into nodelist
   if (handler()) 
      handler()->endDocument();  

   return true;
}

// parses the doctype
bool CXmlParser::parseDocType(CXmlDocType *doc)
{  
   doc->m_html = htmlMode();

   tokenizer.attr_mode(true);

   const std::string &token = (*tokenizer);
   bool next_public = false;
   bool next_system = false;

   for(int n=0; n<3; n++) {
      //token = (*tokenizer++);
      tokenizer++;

      if(tokenizer.eos()) {            
         tokenizer.attr_mode(false);
         return false;
      }     

      if(token=="[") {
         tokenizer.put_back();
         break;
      }

      if(token==">") {
         tokenizer.attr_mode(false);
         return true;
      }

      if(doc->m_name.empty()) {

         doc->m_name = token;

      } else if(next_public) {   

         doc->m_public_id = token;
         next_public = false;
         next_system = true;

      } else if(next_system) {

         doc->m_system_id = token;
         next_system = false;

      } else if(token=="PUBLIC") {

         next_public = true;

      } else if(token=="SYSTEM") {

         next_system = true;

      } 

   }  
   //printf("TOKEN %s %s %s\n", doc->name().c_str(), doc->publicID().c_str(), doc->systemID().c_str());

   do { 
      // read the complete tag till the closing '>'        
      tokenizer++;      

      if(tokenizer.eos()) {
         tokenizer.attr_mode(false);
         return false;
      }

      if (token == "[") {
         tokenizer++;
         do {
            if (token == "<") {              
               tokenizer++;               
               if(token == "!ENTITY") {                       

                  std::string key = *tokenizer++;
                  if(key == "%") {
                     continue; //Can't handle it...
                  }

                  const std::string &value = *tokenizer++;
                  doc->entities().setEntity(key.c_str(), value.c_str());

                  tokenizer++;
                  if(token != ">") {
                     tokenizer.attr_mode(false);
                     throw_error(XML_CLOSETAG_EXPECTED);
                  }
               } else if(token[0]=='-' && (*tokenizer)[1]=='-') {
                  // COMMENT??
                  // Ignore..
                  tokenizer.put_back();
                  CXmlNodePtr stub = 0;
                  if (!parseComment(0,stub)) {
                     tokenizer.attr_mode(false);
                     return false;
                  }
               }
            } // token == '<'

            tokenizer++;            

         } while(token != "]" && !tokenizer.eos());

         tokenizer++; //Jump over ']'
         break;
      } // token == ']'

   } while(token != ">");

   if(token != ">") {
      tokenizer.attr_mode(false);
      throw_error(XML_CLOSETAG_EXPECTED);
   }

   tokenizer.attr_mode(false);
   return true;
}

#define FORCE_CONTINUE 1
#define FORCE_RETURN   2

#define PRESERVE_FORMAT " PRE "

// parses the contents of the current node
bool CXmlParser::parseNode(CXmlNode *parentNode, CXmlNodePtr& newNode) {     
   std::string token1, token2;

   CXmlDoc*    doc = 0;
   if (parentNode) doc = parentNode->document();

   token1 = *tokenizer++;  
   if (tokenizer.eos())     
      return false;  

    // loop when we encounter a comment
   bool again;
   string newNodeName;
   string newNodeValue;
   do {
      again = false;       

      // check if we have cdata
      if (!is_literal(token1, true)) {        
         std::string cdata;

         while (!tokenizer.eos() && !is_literal(token1, true)) {               
            if (!cdata.empty()) cdata += ' ';
            cdata += token1;           
            token1 = *tokenizer++;
         }

         if (!tokenizer.pre_mode())
            cdata = trim(cdata);

         if (cdata.empty()) return false;        

         CXmlDocType *doctype;
         if (handler())  
            doctype = &handler()->docType();
         else        
            doctype = &doc->docType();

         std::string decoded;
         if (doctype->decodeEntities(cdata.c_str(), decoded)) 
         {           
            if (handler())  
               handler()->cdata(decoded);
            else        
               newNodeValue = decoded;
         } else {
            if (handler())  
               handler()->cdata(cdata);
            else        
               newNodeValue = cdata;
         }                        

         tokenizer.put_back();            

         if (!handler() && parentNode)                
            newNode = new CXmlText(parentNode,newNodeValue.c_str());

         return true;
      }     

      // no cdata, try to continue parsing node content
      if (token1 != "<")
         throw_error(XML_OPENTAG_CDATA_EXPECTED);

      // get node name
      token2 = *tokenizer++;
      if (is_literal(token2))  {
         // check the following literal
         switch (literal(token2)) {
            case '/': {
                  // closing '</...>' follows
                  // return, we have a closing node with no more content

                  if (htmlMode() && !parent_name.empty()) {
                     std::string closing_tag = upperCase((*tokenizer++));
                     if (parent_name != closing_tag) {
                        if(is_content(parent_name, closing_tag)) {
                           force_ret = FORCE_RETURN;
                        }
                        else if (!require_endtag(parent_name)) {
                           force_ret = FORCE_CONTINUE;
                           tokenizer++; //Skip '>'
                           return false;
                        }
                     }
                     tokenizer.put_back(); //Tag name
                  }

                  // Put back '</'
                  tokenizer.put_back( "/" );
                  tokenizer.put_back( "<" );

                  return false;
               }

            case '?': {
                  // Processing instruction!
                  token1 = *tokenizer++;

                  if (is_literal(token1)) 
                     throw_error(XML_PI_DOCTYPE_EXPECTED);

                  // parse processing instruction
                  std::string target( token1 );
                  std::string data;

                  tokenizer.cdata_mode(true);
                  tokenizer.prolog_mode(true);

                  tokenizer++;
                  while (!tokenizer.eos()) {
                     data += (*tokenizer);
                     tokenizer++;
                     if((*tokenizer) == "?" || (*tokenizer) == ">") {
                        break;
                     }
                  }

                  if (handler()) 
                     handler()->processingInstruction(target, data);
                  else if (parentNode)                
                     newNode = new CXmlPI(parentNode,target,data.c_str());

                  tokenizer.cdata_mode(false);
                  tokenizer.prolog_mode(false);

                  if (*tokenizer != "?")
                     throw_error(XML_PI_DOCTYPE_EXPECTED);

                  tokenizer++;
                  if (*tokenizer != ">")
                     throw_error(XML_CLOSETAG_EXPECTED);

                  return true;
               }

            case '!': {
                  token2 = *tokenizer++;
                // now a doctype tag or a comment may follow            

                  if (token2[0] == '-' && token2[1] == '-') {
                     tokenizer.put_back();
                     if (!parseComment(parentNode,newNode))
                        return false;
                     return true;

                  }
                  else 
                     if(token2[0] == '[') {

                        if(!strncmp(token2.c_str(), "[CDATA[", 7))
                        {
                           if (!handler() && parentNode)                 
                              newNode = new CXmlCDataSection(parentNode,"");

                           string cdata;

                           tokenizer.cdata_mode(true);
                           cdata += *tokenizer;
                           if (strncmp(token2.c_str(), "[CDATA[]]", 9) != 0) {
                              unsigned pos = cdata.find("]]");
                              if (pos == cdata.length()-2) {
                                 cdata = cdata.substr(0,cdata.length()-2) + " ";
                                 tokenizer.put_back("]]");
                              } else 
                                 cdata += ' ';
                           }
                           while(!tokenizer.eos()) {
                              if ((*tokenizer)[0]=='>'
                                 && !strncmp(cdata.c_str()+cdata.length()-3,"]]>", 3)
                                 )
                                 break;
                              cdata += *tokenizer++;
                           }
                           tokenizer.cdata_mode(false);
                           // end of the fix

                           if(!strncmp(cdata.c_str(), "[CDATA[", 7))
                              cdata.erase(cdata.begin(), cdata.begin()+7); //Delete "[CDATA["
                           cdata.erase(cdata.begin()+cdata.length()-3, cdata.end()); //Delete "]]>"

                           if (handler()) 
                              handler()->cdata(cdata);
                           else if (newNode)
                              newNode->value(cdata);
                           return true;
                        }

                    // Skip to next literal, cant do anything with this data :(
                        while(!tokenizer.eos() && !is_literal(*tokenizer++));
                        token1 = *tokenizer++;
                     } 
                     else if (upperCase(token2)=="DOCTYPE")
                     {              
                        CXmlDocType *dt;
                        if(handler()) {
                           dt = &handler()->docType();
                        } else {
                           dt = &doc->docType();
                        }
                        if(!parseDocType(dt))
                           return false;
                        token1 = *tokenizer++;
                     }

                // parse again, until we encounter some useful data
                  again = true;
               }
               break;

            default:
               throw_error(XML_TAGNAME_EXPECTED);
         }
      }
   } while(again && !tokenizer.eos()); 

   std::string tagname;
   // Store tagname
   if (htmlMode())
      tagname = upperCase(token2);
   else 
      tagname = token2;

   if (htmlMode() && !last_block.empty() && is_html_tag(tagname)) {
      const std::string &last_name = last_block;

      if ( (!require_endtag(last_name) && is_html_block(tagname))
         && (last_name==tagname || !is_content(tagname, last_name)) )
      {
         // Do fake end for HTML tag :)
         tokenizer.put_back(tagname);
         tokenizer.put_back("<");
         tag_end=true;
         return false;
      }
   }

   if (htmlMode()) {
      if (is_html_block(tagname)) {
         last_block = tagname;
      }
   }

   // insert tag name 
   newNodeName = tagname;    

   // parse attributes
   CXmlAttributes temp_map(parentNode);
   if (!handler()) {
      if (!parseAttributes(doc, &temp_map))
         return false;
   } else {
      if (!parseAttributes(0, &temp_map))
         return false;

      handler()->startNode(tagname, temp_map);
   }

   // check for leaf
   token1 = *tokenizer++;

   // HMTL, check for leafs
   if (htmlMode()) {
      if (is_html_leaf(tagname)) {
         if (handler()) 
            handler()->endNode(tagname);
         else if (parentNode) {               
            newNode = new CXmlElement(parentNode,newNodeName.c_str());
            if (temp_map.size())
               newNode->attributes = temp_map;
         }
         return true;
      }      

      std::string search(" ");
      search += tagname;
      search += ' ';
      if (strstr(PRESERVE_FORMAT, search.c_str()))
         tokenizer.pre_mode(true);
      else
         tokenizer.pre_mode(false);       
   }

   // XML
   if (token1 == "/") {
      // node has finished
      token2 = *tokenizer++;
      if (token2 != ">" )
         throw_error(XML_CLOSETAG_EXPECTED);

      if (handler()) 
         handler()->endNode(tagname);
      else if (parentNode) {               
         newNode = new CXmlElement(parentNode,newNodeName.c_str());
         if (temp_map.size())
            newNode->attributes = temp_map;
      }
      // return, let the caller continue to parse
      return true;
   }

   // now a closing bracket must follow
   if (token1 != ">")
      throw_error(XML_CLOSETAG_EXPECTED);

   bool xml_in_html = false;
   if (tagname=="XML") {
      htmlMode(false);
      xml_in_html = true;
   }

   // loop to parse all subnodes
   std::string saved_parent;
   if (htmlMode()) saved_parent = parent_name;

   if (!handler()) {
      newNode = new CXmlElement(parentNode,newNodeName.c_str());
      if (temp_map.size())
         newNode->attributes = temp_map;
      // TREE MODE
      for (;;) {
         CXmlNode *subnode = 0;

         tag_end = false; 
         force_ret = 0;

         parent_name = tagname;
         if (!parseNode(newNode, subnode)) {
            if (force_ret != 0) {
               switch (force_ret) {
                  case FORCE_RETURN:
                     parent_name = saved_parent;
                     return true;
                  case FORCE_CONTINUE:
                  default:
                     break;
               };
            } else
               break;
         }
      }

   } else {

      // SAX MODE
      while(true) {

         tag_end = false; 
         force_ret = 0;

         parent_name = tagname;
         CXmlNodePtr stub;
         if (!parseNode(0, stub)) {
            if (force_ret != 0) {
               switch (force_ret) {
                  case FORCE_RETURN:
                     handler()->endNode(tagname);
                     parent_name = saved_parent;
                     return true;
                  case FORCE_CONTINUE:
                  default:
                     break;
               };
            } else
               break;
         }
      }
   }

   if (htmlMode()) 
      parent_name = saved_parent;

   if (xml_in_html)
      htmlMode(true);

   if (htmlMode()) {
      if(tokenizer.pre_mode())
         tokenizer.pre_mode(false);
      last_block = parent_name;
   }

   if (tokenizer.eos()) return true;

   if (htmlMode() && tag_end) {
      if (handler()) handler()->endNode(tagname);
      return true;
   }

   // parse end tag
   std::string token5 = *tokenizer++;
   tokenizer++;

   if (token5 != "<" && *tokenizer != "/")
      throw_error(XML_OPENTAG_EXPECTED);

   token1 = *tokenizer++;

   if (is_literal(token1))
      throw_error(XML_TAGNAME_EXPECTED);

   // check if open and close tag names are identical
   if (!htmlMode() && token1 != tagname)
      throw_error(XML_TAGNAME_MISMATCH);

   if ( (*tokenizer++) != ">")
      throw_error(XML_CLOSETAG_EXPECTED);

   if (handler()) 
      handler()->endNode(tagname);

   return true;
}

// parses tag attributes
bool CXmlParser::parseAttributes(CXmlDoc *doc, CXmlAttributes *attr) {
   while (true) {

      tokenizer++;
      if (tokenizer.eos()) return false;

      if ((*tokenizer).empty()) continue;

      if ((*tokenizer) == ">" || (*tokenizer) == "/") {
         tokenizer.put_back();
         return true;
      }

      std::string name = *tokenizer;

      if ( (*tokenizer++) != "=") {
         if (htmlMode()) {
            tokenizer.put_back();
            (*attr)[name] = "";
            continue;
         } else {
            throw_error(XML_EQUAL_EXPECTED);
         }
      }

      tokenizer.attr_mode(true);
      if (is_literal( (*tokenizer++), true )) {
         tokenizer.attr_mode(false);
         throw_error(XML_VALUE_EXPECTED);
      }
      tokenizer.attr_mode(false);

      const std::string &value = (*tokenizer);

      CXmlDocType *doctype;
      if (handler())
         doctype = &handler()->docType();
      else doctype = &doc->docType();

      std::string decoded;
      // insert attribute into the map
      if (doctype->decodeEntities(value.c_str(), decoded)) {
         (*attr)[name] = decoded;
      } else {
         (*attr)[name] = value;
      }
   }
   return true;
}

bool CXmlParser::parseComment(CXmlNode *parentNode,CXmlNodePtr& newNode) {
   std::string comment;

   tokenizer.cdata_mode(true);
   while (true) {
      if (tokenizer.eos())
         throw_error(XML_COMMENT_END_MISSING);

      tokenizer++;

      if (comment[comment.length()-1]=='-' && comment[comment.length()-2]=='-') {
         if(*tokenizer == ">")
            break;
      }

      const std::string &part = (*tokenizer);

      if ( comment.length()==0 && (part[0]!='-' || part[1]!='-') )
         throw_error(XML_COMMENT_START_EXPECTED);

      comment+=part;
   }
   tokenizer.cdata_mode(false);

   if (parentNode) {
      comment.erase(comment.begin(), comment.begin()+2);
      comment.erase(comment.begin()+comment.length()-2, comment.end());
      newNode = new CXmlComment(parentNode,comment.c_str());
   } else if (handler()) {
      comment.erase(comment.begin(), comment.begin()+2);
      comment.erase(comment.begin()+comment.length()-2, comment.end());
      handler()->comment(comment);
   }

   return true;
}

void CXmlParser::createDOM(CXmlDoc& doc, CXmlTokenizer &tokenizer, CXmlLocator *locator, bool htmlMode) {
   tokenizer.locator(locator);

   CXmlParser parser(tokenizer);

   parser.htmlMode(htmlMode);
   parser.parseDocument(&doc);
}

void CXmlParser::createDOM(CXmlDoc& doc, const CBuffer& buffer, CXmlLocator *locator, bool htmlMode) {
   CXmlDefaultTokenizer tokenizer(buffer);
   createDOM(doc, tokenizer, locator, htmlMode);
}

bool CXmlParser::createSAX(CXmlHandler &handler, CXmlTokenizer &tokenizer, bool htmlMode) {
   CXmlParser parser(tokenizer);
   parser.handler(&handler);
   parser.htmlMode(htmlMode);

   parser.parseDocument(0);
   return true;
}

bool CXmlParser::createSAX(CXmlHandler &handler, const CBuffer& buffer, bool htmlMode) {
   CXmlDefaultTokenizer tokenizer(buffer);
   return createSAX(handler, tokenizer, htmlMode);
}

#define XMLERRORCODE(x,y)  case x: err = y; break;
const char *CXmlException::error_string(int error)
{
   const char *err="Unknown error";
   switch(error) {
      XMLERRORCODE(XML_NO_ERROR, "Operation success");
      XMLERRORCODE(XML_UNKNOWN, "Unspecified or unknown error");
      XMLERRORCODE(XML_OPENTAG_EXPECTED, "Expected an open tag literal '<'");
      XMLERRORCODE(XML_OPENTAG_CDATA_EXPECTED, "Expected a '<' or cdata");
      XMLERRORCODE(XML_CLOSETAG_EXPECTED, "Expected an '>' closing tag literal");
      XMLERRORCODE(XML_PI_DOCTYPE_EXPECTED, "Expected a processing instruction or doctype tag");
      XMLERRORCODE(XML_TAGNAME_EXPECTED, "Expected a tag name after '<' or '</'");
      XMLERRORCODE(XML_COMMENT_START_EXPECTED, "Expected '--' after comment '<!'");
      XMLERRORCODE(XML_COMMENT_END_MISSING, "Missing '-->' in comment");
      XMLERRORCODE(XML_SLASH_EXPECTED, "Expected a '/' after closing tag literal '<'");
      XMLERRORCODE(XML_TAGNAME_MISMATCH, "Tag name from start and end tag mismatch");
      XMLERRORCODE(XML_EQUAL_EXPECTED, "Expected '=' after attribute name");
      XMLERRORCODE(XML_VALUE_EXPECTED, "Expected value after 'a' in attribute");
   }
   return err;
}

CXmlException::CXmlException(int error, CXmlLocator *locator) { 
   if (locator) 
      m_message = "Error at line "
         + int2string(locator->line()) 
         + ", position "
         + int2string(locator->col()) 
         + ": ("
         + error_string(error)
         + ")";
   else
      m_message = error_string(error);
}
