/***************************************************************************
                          SIMPLY POWERFUL TOOLKIT (SPTK)
                          CXmlNode.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/cxml>
#include <algorithm>

using namespace std;
using namespace sptk;

const string CXmlNode::emptyString;
const string CXmlText::nodeNameString("#text");
const string CXmlComment::nodeNameString("#comment");
const string CXmlCDataSection::nodeNameString("#cdata-section");

CXmlNodeList CXmlNode::emptyNodes;

CXmlNode::CXmlNode(CXmlNode& parent)
: m_document(parent.document()), attributes(this) {
   parent.push_back(this);
}

CXmlNode::CXmlNode(CXmlDoc& doc)
: m_document(&doc), attributes(this) {
	m_parent = 0;
}

CXmlNode::~CXmlNode() {
	clear();
}

void CXmlNode::parent(CXmlNode *p) { 
	if (m_parent == p) return;
	if (m_parent) 
	   m_parent->unlink(p);
	
	m_parent = p; 

	if (m_parent) 
		m_parent->push_back(this);
}

void CXmlNode::select(CXmlNodeVector& nodes,std::string name) const {
	nodes.clear();
	const_iterator itor = begin();
	const_iterator iend = end();
	for (; itor != iend; itor++) 
		nodes.push_back(*itor);
}

void CXmlNode::copy(const CXmlNode& node) {
	name(node.name());
	value(node.value());
	attributes = node.attributes;

	const_iterator itor = node.begin();
	const_iterator iend = node.end();
	for (; itor != iend; itor++) {
		const CXmlNode* node = *itor;
		switch (node->type()) {
			case DOM_ELEMENT: {
					CXmlNode* element = new CXmlElement(this,"");
					element->copy(*node);
				}
				break;
			case DOM_PI:
				new CXmlPI(*this,node->name(),node->value());
				break;
			case DOM_TEXT:
				new CXmlText(*this,node->value());
				break;
			case DOM_CDATA_SECTION:
				new CXmlCDataSection(*this,node->value());
				break;
			case DOM_COMMENT:
				new CXmlComment(*this,node->value());
				break;
			default:
				break;
		}
	}
}

void CXmlNode::clear() {
	attributes.clear();
}

std::string CXmlNode::text() const {
	std::string ret;

	if (type() & (DOM_TEXT|DOM_CDATA_SECTION))
		ret += value();
	else {
		const_iterator itor = begin();
		const_iterator iend = end();
		for (; itor != iend; itor++ ) {
			CXmlNode *np = *itor;
			if (np->type() & (DOM_TEXT|DOM_CDATA_SECTION))
				ret += np->value();
		}
	}

	return ret;
}

void CXmlNode::save(CBuffer &buffer, int indent) const {
	std::string tmp;

   // output indendation spaces
	if (indent > 0)
		buffer.append(std::string(indent,' '));

	if (type() == DOM_ELEMENT) {
        // Output tag name
		buffer.append("<");
		buffer.append(name());
		if (attributes.size()) {
      // Output attributes
			CXmlAttributes::const_iterator it = attributes.begin();
			for (; it != attributes.end(); it++) {
				std::string real_id, real_val;
				if (!document()->docType().encodeEntities(it->first, real_id))
					real_id = it->first;
				if (!document()->docType().encodeEntities(it->second.c_str(), real_val))
					real_val = it->second;

				buffer.append(" " + real_id + "=\"" + real_val + "\"");
			}
		}
	}

   // depending on the nodetype, do output
	switch(type()) {
		case DOM_PI:
			buffer.append("<?" + std::string(name()) + " " + value() + "?>\n");
			break;

		case DOM_TEXT: {
				std::string text;
				if(!document()->docType().encodeEntities(value().c_str(), text)) {
					buffer.append(value());
				} else {
					buffer.append(text);
				}
			}
			break;

		case DOM_CDATA_SECTION:
         // output all subnodes
			buffer.append("<![CDATA[" + value() + "]]>\n");
			break;

		case DOM_COMMENT:
         // output all subnodes
			buffer.append("<!-- " + value() + " -->\n");
			break;

		case DOM_ELEMENT: {
				if (size()) {
					bool only_cdata;
					const_iterator itor = begin();
					const_iterator iend = end();
					CXmlNode *nd = *itor;
					if (size()==1 && nd->type() == DOM_TEXT) {
						only_cdata = true;
						buffer.append(">",1);
					} else {
						only_cdata = false;
						buffer.append(">\n",2);
					}

               // output all subnodes
					for(; itor != iend; itor++) {
						CXmlNode *np = *itor;
						if (only_cdata)
							np->save(buffer, -1);
						else {
							np->save(buffer, indent + CXmlDoc::indentSpaces());
							if(buffer.data()[buffer.bytes()-1] != '\n')
								buffer.append('\n');
						}
					}
               // output indendation spaces
					if (!only_cdata && indent > 0)
						buffer.append(std::string(indent,' '));

               // output closing tag
					buffer.append("</");
					buffer.append(name());
					buffer.append(">\n");

				} else {
               //LEAF
					buffer.append("/>\n",3);
				}
			}
			break;

		default: // unknown nodetype
			break;
	}
}

CXmlNode *CXmlNode::findFirst(std::string aname,bool recursively) const {
	for (const_iterator itor = begin(); itor != end(); itor++) {
		CXmlNode *node = *itor;
		if (node->name() == aname)
			return node;
		if (recursively && node->size()) {
			CXmlNode *cnode = node->findFirst(aname,true);
			if (cnode) return cnode;
		}
	}
	return 0L;
}

CXmlNode *CXmlNode::findOrCreate(std::string aname,bool recursively) {
	CXmlNode *node = findFirst(aname,recursively);
	if (node) return node;
	return new CXmlElement(*this,aname);
}

bool CXmlNode::hasAttributes() const {
	return attributes.size() != 0; 
}

bool CXmlNode::hasAttribute(const char *attr) const { 
	return attributes.hasAttribute(attr); 
}

CXmlAttribute CXmlNode::getAttribute(std::string attr,const char *defaultValue) const {
	return attributes.getAttribute(attr,defaultValue); 
}

void CXmlNode::setAttribute(const char *attr, CXmlAttribute value,const char *defaultValue) { 
	attributes.setAttribute(attr,value,defaultValue); 
}

void CXmlNode::setAttribute(const std::string& attr, CXmlAttribute value,const char *defaultValue) { 
	attributes.setAttribute(attr.c_str(),value,defaultValue); 
}

void CXmlElement::name(const std::string& name) {
	m_name = &m_document->shareString(name.c_str());
}

void CXmlElement::name(const char *name) {
	m_name = &m_document->shareString(name);
}

void CXmlElement::insert(iterator itor,CXmlNode* node) {
   m_nodes.insert(itor,node);
   node->m_parent = this;
}

void CXmlElement::push_back(CXmlNode* node) {
   m_nodes.insert(m_nodes.end(),node);
   node->m_parent = this;
}

void CXmlElement::unlink(CXmlNode* node) {
   iterator itor = find(begin(),end(),node);
   if (itor == end()) return;
   m_nodes.erase(itor);
}

void CXmlElement::remove(CXmlNode* node) {
   iterator itor = find(begin(),end(),node);
   if (itor == end()) return;
   delete *itor;
   m_nodes.erase(itor);
}

void CXmlElement::clear() {
   CXmlNode::clear();
	m_nodes.clear();
}

void CXmlPI::name(const std::string& name) {
	m_name = &m_document->shareString(name.c_str());
}

void CXmlPI::name(const char *name) {
	m_name = &m_document->shareString(name);
}


