/***************************************************************************
                          SIMPLY POWERFUL TOOLKIT (SPTK)
                          CXmlDoc.cpp  -  description
                             -------------------
    begin                : Sun May 22 2003
    based on the code    : Mikko Lahteenmaki <Laza@Flashmail.com>
    copyright            : (C) 2003-2005 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 <ctype.h>
#include <stdlib.h>

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

using namespace std;

int CXmlDoc::m_indent_spaces = 2;

//////////////////////////

CXmlDoc::CXmlDoc()
: CXmlNode("#document", DOM_DOCUMENT, 0)
{
   m_parent = 0;     
   m_document = this;
}

CXmlDoc::CXmlDoc(const char *name, const char *public_id, const char *system_id)
: CXmlNode("#document",DOM_DOCUMENT,0), m_doctype(name,public_id,system_id)
{
   m_parent = 0;     
   m_document = this;
}

CXmlNode *CXmlDoc::rootNode()
{
   for(unsigned n=0; n < children(); n++) {
      CXmlNode *nd = child(n);
      if(nd->type()==DOM_ELEMENT)
         return nd;
   }
   return 0;
}

void CXmlDoc::clear()
{
   CXmlNode::clear();
}

CXmlNode *CXmlDoc::createElement(const char *tagname) {
   CXmlNode *node = new CXmlNode(tagname, DOM_ELEMENT, this);
   return node;
}

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

void CXmlDoc::save(CBuffer &buffer,int /*indent*/) const {
   std::string tmp;
   unsigned n;
   CXmlNode *xml_pi = 0;

   // Write XML PI
   for (n=0; n<children(); n++) {
      if(child(n)->isPI() && upperCase(child(n)->name())=="XML") {
         xml_pi = child(n);
         xml_pi->save(buffer);
      }
   }

   if (!docType().name().empty()) {
      buffer.append("<!DOCTYPE " + docType().name());
      if(!docType().systemID().empty()) {
         buffer.append(" SYSTEM \"" + docType().systemID() + "\"");

         if(!docType().publicID().empty()) {
            buffer.append(" PUBLIC \"" + docType().publicID() + "\"");
         }
      }
      if(docType().entities().size() > 0) {
         buffer.append(" [\n",3);
         const CXmlEntities& entities = docType().entities();
         CXmlEntities::const_iterator it = entities.begin();
         for(; it != entities.end(); it++) {
            buffer.append("<!ENTITY " + it->first + " \"" + it->second + "\">\n");
         }
         buffer.append("]",1);
      }
      buffer.append(">\n",2);
   }

    // call save() method of the first (and hopefully only) node in xmldocument
   for(n=0; n<children(); n++) {
      CXmlNode *nd = child(n);
      if(nd == xml_pi) continue;
      nd->save(buffer, 0);
   }
}

//////////////////////////////////////

CXmlDocType::CXmlDocType(const char *name, const char *public_id, const char *system_id)
{
   m_html = false;
   m_name = name;
   if(public_id) m_public_id = public_id;
   if(system_id) m_system_id = system_id;
}

struct entity {
   const char *name;
   int replacement_len;
   const char *replacement;
};

struct entity builtin_ent_html[] = {
#include "html_entities.h"
};
int html_ent_size = (int)(sizeof(builtin_ent_html)/sizeof(builtin_ent_html[0]));

class HTML_EntityCache {
   map<std::string,struct entity *> hash;
public:

   HTML_EntityCache() : hash() {
      for(int n=0; n<html_ent_size; n++) {
         struct entity *ent = &builtin_ent_html[n];
         hash[ent->name] = ent;
      }
   }

   const struct entity *find(const std::string &ent) const {
      map<std::string,struct entity *>::const_iterator itor = hash.find(ent);
      if (itor != hash.end())
         return itor->second;
      return 0L;
   }
};

static const HTML_EntityCache html_entities;

struct entity builtin_ent_xml[] =
{
   { "amp", 1, "&" },
   { "lt",  1, "<" },
   { "gt",  1, ">" },
   { "apos",  1, "'" },
   { "quot",  1, "\"" }
};

int xml_ent_size = (int)(sizeof(builtin_ent_xml)/sizeof(builtin_ent_xml[0]));

class XML_EntityCache
{
   map<std::string,struct entity *> hash;
public:

   XML_EntityCache() : hash() {
      for(int n=0; n<xml_ent_size; n++) {
         struct entity *ent = &builtin_ent_xml[n];
         hash[ent->name] = ent;
      }
   }

   const struct entity *find(const std::string &ent) const {
      map<std::string,struct entity *>::const_iterator itor = hash.find(ent);
      if (itor != hash.end()) return itor->second;
      return 0L;
   }
};
static const XML_EntityCache xml_entities;


bool CXmlDocType::decodeEntities(const char *str, std::string &ret)
{
    //Fast check..
   if(!strchr(str, '&')) return false;

   int str_len = strlen(str);
   bool replaced = false;

   for(int n=0; n<str_len; n++) 
   {
      if(str[n]=='&') {
         int end=n+1;
         for(; end < str_len; end++) {
            if(str[end]==';')
               break;
         }
         std::string ent(str+n+1, end-(n+1));
         std::string rep = getReplacement(ent.c_str());
         if(rep!=ent) {
            n += ent.length()+1;
            ret += rep;
            replaced = true;
         } else
            ret += str[n];
      } else
         ret += str[n];
   }

   return replaced;
}

bool CXmlDocType::encodeEntities(const char *str, std::string &ret) {
   int num;
   struct entity *table;
   if(m_html) {
      num = html_ent_size;
      table = builtin_ent_html;
   } else {
      num = xml_ent_size;
      table = builtin_ent_xml;
   }

   bool replaced = false;

   const char *ptr = str;
   while(*ptr) {
      bool found=false;
      for (int i = 0; i < num; i++) {
         if(!strncmp(ptr, table[i].replacement, table[i].replacement_len)) {
            ret += '&';
            ret += table[i].name;
            ret += ';';
            found = replaced = true;
         }
      }

      if(!found) {
         CXmlEntities::iterator it = m_entities.begin();
         for(; it != m_entities.end(); it++) {
            std::string& val = it->second;
            unsigned len = val.length();
            if(!strncmp(ptr, val.c_str(), len)) {
               ret += '&';
               ret += it->first;
               ret += ';';
               found = replaced = true;
               ptr += len-1;
            }
         }
      }

      if(!found)
         ret += *ptr;

      ptr++;
   }

   return replaced;
}

std::string CXmlDocType::getReplacement(const char *name) const {
   // &#123; style entity..
   if(name[0]=='#') {
      char tmp[6]; 
      if(isdigit(name[1])) {
         tmp[0] = (char)strtol(name+1, NULL, 10);
         tmp[1] = '\0';
         return std::string(tmp,2);
      } else if(name[1]=='x' || name[1]=='X') {
         tmp[0] = (char)strtol(name+2, NULL, 16);
         tmp[1] = '\0';
         return std::string(tmp,2);
      }
   }

   // Find in built-ins, see entities.h
   const struct entity *entity;
   if (m_html)
      entity = html_entities.find(name);
   else
      entity = xml_entities.find(name);
   if (entity) return std::string(entity->replacement, entity->replacement_len);

   // Find in custom attributes
   CXmlEntities::const_iterator itor = m_entities.find(name);
   if (itor != m_entities.end())
      return itor->second;

   return name;
}

bool CXmlDocType::hasEntity(const char *name) const {
   std::string tmp = getReplacement(name);
   return (tmp!=name);
}
