/***************************************************************************
                          SIMPLY POWERFUL TOOLKIT (SPTK)
                          CXmlDocType.cpp  -  description
                             -------------------
    begin                : Tue June 27 2006
    based on the code    : Mikko Lahteenmaki <Laza@Flashmail.com>
    copyright            : (C) 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/cxml>

using namespace std;
using namespace sptk;

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"
};

typedef map<std::string,struct entity *> CEntityMap;

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

class CEntityCache {
	CEntityMap hash;
public:

	CEntityCache(struct entity entities[]) : hash() {
		struct entity *ent = entities;
		for(; ent->name; ent++) {
			hash[ent->name] = ent;
		}
	}

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

static const CEntityCache xml_entities(builtin_ent_xml);
static const CEntityCache html_entities(builtin_ent_html);

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) {
	entity* table;
	if (m_html) 
		table = builtin_ent_html;
	else 
		table = builtin_ent_xml;

	bool replaced = false;

	const char* ptr = str;
	while (*ptr) {
		bool found=false;
		for (entity* ent = table; ent->name; ent++) {
			if (!strncmp(ptr, ent->replacement, ent->replacement_len)) {
				ret += '&';
				ret += ent->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;
				}
			}
			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);
}
