// Copyright (c) 2002 David Muse
// See the COPYING file for more information.

#define EXCLUDE_RUDIMENTS_TEMPLATE_IMPLEMENTATIONS
#include <rudiments/xmldomnode.h>
#include <rudiments/charstring.h>
#include <rudiments/xmldom.h>

#include <stdio.h>

#ifdef RUDIMENTS_NAMESPACE
namespace rudiments {
#endif

class xmldomnodeprivate {
	friend class xmldomnode;
	private:
		xmldom		*_dom;
		bool		_cascade;
		xmldomnodetype	_type;
		const char	*_nodename;
		const char	*_nodevalue;
		xmldomnode	*_parent;
		xmldomnode	*_next;
		xmldomnode	*_previous;
		int		_childcount;
		xmldomnode	*_firstchild;
		xmldomnode	*_lastchild;
		int		_attributecount;
		xmldomnode	*_firstattribute;
		xmldomnode	*_lastattribute;
		xmldomnode	*_nullnode;
		bool		_isnullnode;
};

xmldomnode::xmldomnode(xmldom *dom, xmldomnode *nullnode) {
	init(nullnode);
	pvt->_dom=dom;
	pvt->_type=NULL_XMLDOMNODETYPE;
	pvt->_nodename=NULL;
	pvt->_nodevalue=NULL;
}

xmldomnode::xmldomnode(xmldom *dom,
			xmldomnode *nullnode, xmldomnodetype type,
			const char *name, const char *value) {
	init(nullnode);
	pvt->_dom=dom;
	pvt->_type=type;
	setName(name);
	setValue(value);
}

void xmldomnode::init(xmldomnode *nullnode) {
	pvt=new xmldomnodeprivate;
	pvt->_nullnode=nullnode;
	pvt->_parent=nullnode;
	pvt->_next=nullnode;
	pvt->_previous=nullnode;
	pvt->_firstchild=NULL;
	pvt->_lastchild=NULL;
	pvt->_childcount=0;
	pvt->_firstattribute=NULL;
	pvt->_lastattribute=NULL;
	pvt->_attributecount=0;
	pvt->_cascade=true;
	pvt->_isnullnode=false;
}

xmldomnode::~xmldomnode() {
	xmldomnode	*current;
	if (pvt->_cascade) {
		// delete child nodes
		current=pvt->_firstchild;
		while (current && !current->isNullNode()) {
			pvt->_lastchild=current->pvt->_next;
			delete current;
			current=pvt->_lastchild;
		}
	}
	// delete attributes
	current=pvt->_firstattribute;
	while (current && !current->isNullNode()) {
		pvt->_lastattribute=current->pvt->_next;
		delete current;
		current=pvt->_lastattribute;
	}
	pvt->_dom->unCacheString(pvt->_nodename);
	pvt->_dom->unCacheString(pvt->_nodevalue);
	delete pvt;
}

xmldomnode *xmldomnode::createNullNode(xmldom *dom) {
	xmldomnode	*nn=new xmldomnode(dom,NULL);
	nn->pvt->_parent=nn;
	nn->pvt->_next=nn;
	nn->pvt->_previous=nn;
	nn->pvt->_isnullnode=true;
	nn->pvt->_nullnode=nn;
	return nn;
}

xmldomnode *xmldomnode::getPreviousTagSibling() const {
	xmldomnode	*node=getPreviousSibling();
	while (!node->isNullNode() && node->getType()!=TAG_XMLDOMNODETYPE) {
		node=node->getPreviousSibling();
	}
	return node;
}

xmldomnode *xmldomnode::getPreviousTagSibling(const char *name) const {
	for (xmldomnode *current=getPreviousTagSibling();
			current && !current->isNullNode();
				current=current->getPreviousTagSibling()) {
		const char	*nm=current->getName();
		if ((name && nm && !charstring::compare(name,nm)) || !name) {
			return current;
		}
	}
	return pvt->_nullnode;
}

xmldomnode *xmldomnode::getPreviousTagSibling(const char *name,
					const char *attributename,
					const char *attributevalue) const {
	for (xmldomnode *current=getPreviousTagSibling();
			current && !current->isNullNode();
				current=current->getPreviousTagSibling()) {
		const char	*nm=current->getName();
		if ((name && nm && !charstring::compare(name,nm)) || !name) {
			const char	*value=current->
						getAttribute(attributename)->
						getValue();
			if (value &&
				!charstring::compare(value,attributevalue)) {
				return current;
			}
		}
	}
	return pvt->_nullnode;
}

xmldomnode *xmldomnode::getNextTagSibling() const {
	xmldomnode	*node=getNextSibling();
	while (!node->isNullNode() && node->getType()!=TAG_XMLDOMNODETYPE) {
		node=node->getNextSibling();
	}
	return node;
}

xmldomnode *xmldomnode::getNextTagSibling(const char *name) const {
	for (xmldomnode *current=getNextTagSibling();
			current && !current->isNullNode();
				current=current->getNextTagSibling()) {
		const char	*nm=current->getName();
		if ((name && nm && !charstring::compare(name,nm)) || !name) {
			return current;
		}
	}
	return pvt->_nullnode;
}

xmldomnode *xmldomnode::getNextTagSibling(const char *name,
					const char *attributename,
					const char *attributevalue) const {
	for (xmldomnode *current=getNextTagSibling();
			current && !current->isNullNode();
				current=current->getNextTagSibling()) {
		const char	*nm=current->getName();
		if ((name && nm && !charstring::compare(name,nm)) || !name) {
			const char	*value=current->
						getAttribute(attributename)->
						getValue();
			if (value &&
				!charstring::compare(value,attributevalue)) {
				return current;
			}
		}
	}
	return pvt->_nullnode;
}

xmldomnode *xmldomnode::getFirstTagChild() const {
	xmldomnode	*node=getChild(0);
	return (node->getType()==TAG_XMLDOMNODETYPE)?
					node:node->getNextTagSibling();
}

xmldomnode *xmldomnode::getFirstTagChild(const char *name) const {
	xmldomnode	*node=getChild(name);
	return (node->getType()==TAG_XMLDOMNODETYPE)?
					node:node->getNextTagSibling(name);
}

xmldomnode *xmldomnode::getFirstTagChild(const char *name,
					const char *attributename,
					const char *attributevalue) const {
	xmldomnode	*node=getChild(name,attributename,attributevalue);
	return (node->getType()==TAG_XMLDOMNODETYPE)?
		node:node->getNextTagSibling(name,attributename,attributevalue);
}

xmldomnode *xmldomnode::getChild(const char *name,
					const char *attributename,
					const char *attributevalue) const {
	for (xmldomnode *current=pvt->_firstchild;
			current && !current->isNullNode();
				current=current->pvt->_next) {
		const char	*nm=current->getName();
		if ((name && nm && !charstring::compare(name,nm)) || !name) {
			const char	*value=current->
						getAttribute(attributename)->
						getValue();
			if (value &&
				!charstring::compare(value,attributevalue)) {
				return current;
			}
		}
	}
	return pvt->_nullnode;
}

bool xmldomnode::insertText(const char *value, int position) {
	xmldomnode	*text=new xmldomnode(pvt->_dom,pvt->_nullnode);
	text->setName("text");
	text->setValue(value);
	return insertNode(text,position,TEXT_XMLDOMNODETYPE,
				&pvt->_firstchild,&pvt->_lastchild,
				&pvt->_childcount);
}

bool xmldomnode::insertAttribute(const char *name, const char *value,
								int position) {
	xmldomnode	*attribute=new xmldomnode(pvt->_dom,pvt->_nullnode);
	attribute->setName(name);
	attribute->setValue(value);
	return insertNode(attribute,position,
				ATTRIBUTE_XMLDOMNODETYPE,
				&pvt->_firstattribute,
				&pvt->_lastattribute,
				&pvt->_attributecount);
}

stringbuffer *xmldomnode::xml(stringbuffer *string) const {
	stringbuffer	*output=string;
	if (!string) {
		output=new stringbuffer();
	}
	xmldomnode	*current;
	if (pvt->_type==ROOT_XMLDOMNODETYPE) {
		current=pvt->_firstchild;
		for (int i=0; i<pvt->_childcount; i++) {
			current->xml(output);
			current=current->pvt->_next;
		}
	} else if (pvt->_type==TAG_XMLDOMNODETYPE) {
		output->append("<");
		output->append(pvt->_nodename);
		current=pvt->_firstattribute;
		for (int i=0; i<pvt->_attributecount; i++) {
			output->append(" ");
			current->xml(output);
			current=current->pvt->_next;
		}
		if (pvt->_childcount) {
			output->append(">");
			current=pvt->_firstchild;
			for (int i=0; i<pvt->_childcount; i++) {
				current->xml(output);
				current=current->pvt->_next;
			}
			output->append("</");
			output->append(pvt->_nodename);
			output->append(">");
		} else {
			if (pvt->_nodename[0]=='?') {
				output->append("?>");
			} else if (pvt->_nodename[0]=='!') {
				output->append(">");
			} else {
				output->append("/>");
			}
		}
	} else if (pvt->_type==TEXT_XMLDOMNODETYPE) {
		output->append(pvt->_nodevalue);
	} else if (pvt->_type==ATTRIBUTE_XMLDOMNODETYPE) {
		if (pvt->_parent->pvt->_nodename[0]=='!') {
			output->append("\"");
			output->append(pvt->_nodevalue);
			output->append("\"");
		} else {
			output->append(pvt->_nodename);
			output->append("=\"");
			output->append(pvt->_nodevalue);
			output->append("\"");
		}
	} else if (pvt->_type==COMMENT_XMLDOMNODETYPE) {
		output->append("<!--");
		output->append(pvt->_nodevalue);
		output->append("-->");
	} else if (pvt->_type==CDATA_XMLDOMNODETYPE) {
		output->append("<![CDATA[");
		output->append(pvt->_nodevalue);
		output->append("]]>");
	}
	return output;
}

xmldomnode *xmldomnode::getNode(xmldomnode *first, int position,
					const char *name, int count) const {
	if (!first || position>=count) {
		return pvt->_nullnode;
	}
	xmldomnode	*current=first;
	if (name) {
		for (int i=0; i<count; i++) {
			if (!charstring::compare(
					current->pvt->_nodename,name)) {
				break;
			}
			current=current->pvt->_next;
		}
	} else {
		for (int i=0; i<position; i++) {
			current=current->pvt->_next;
		}
	}
	return current;
}

bool xmldomnode::insertNode(xmldomnode *node, int position,
				xmldomnodetype type,
				xmldomnode **first, xmldomnode **last,
				int *count) {
	if (position>(*count)) {
		return false;
	}
	node->pvt->_parent=this;
	node->pvt->_type=type;
	xmldomnode	*atpos=getNode(*first,position,NULL,*count);
	xmldomnode	*beforepos=getNode(*first,position-1,NULL,*count);
	if (atpos) {
		node->pvt->_next=atpos;
		atpos->pvt->_previous=node;
	}
	if (beforepos) {
		node->pvt->_previous=beforepos;
		beforepos->pvt->_next=node;
	}
	if (position==0) {
		(*first)=node;
	}
	if (position==(*count)) {
		(*last)=node;
	}
	(*count)++;
	return true;
}

bool xmldomnode::deleteNode(xmldomnode *node, int position, const char *name,
				xmldomnode **first, xmldomnode **last,
				int *count) {

	if (position>(*count)) {
		return false;
	}
	xmldomnode	*current=*first;
	if (node || name) {
		while (current &&
			(name && charstring::compare(
					current->pvt->_nodename,name)) ||
			(node && current!=node)) {
			current=current->pvt->_next;
		}
	} else {
		for (int i=0; i<position; i++) {
			current=current->pvt->_next;
		}
	}
	if (current) {
		if (current->pvt->_previous) {
			current->pvt->_previous->pvt->_next=current->pvt->_next;
		}
		if (current->pvt->_next) {
			current->pvt->_next->pvt->_previous=
					current->pvt->_previous;
		}
		if (current==*first) {
			*first=current->pvt->_next;
		}
		if (current==*last) {
			*last=current->pvt->_previous;
		}
		delete current;
		(*count)--;
		return true;
	}
	return false;
}

bool xmldomnode::appendChild(xmldomnode *child) {
	return insertChild(child,getChildCount());
}

bool xmldomnode::appendText(const char *value) {
	return insertText(value,getChildCount());
}

bool xmldomnode::appendAttribute(xmldomnode *attribute) {
	return insertAttribute(attribute,getAttributeCount());
}

bool xmldomnode::appendAttribute(const char *name, const char *value) {
	return insertAttribute(name,value,getAttributeCount());
}

constnamevaluepairs *xmldomnode::getAttributes() const {

	if (pvt->_isnullnode) {
		return NULL;
	}

	constnamevaluepairs	*nvp=new constnamevaluepairs();
	for (int i=0; i<pvt->_attributecount; i++) {
		nvp->setData(getAttribute(i)->getName(),
				getAttribute(i)->getValue());
	}
	return nvp;
}

void xmldomnode::cascadeOnDelete() {
	pvt->_cascade=true;
}

void xmldomnode::dontCascadeOnDelete() {
	pvt->_cascade=false;
}

xmldomnodetype xmldomnode::getType() const {
	return pvt->_type;
}

const char *xmldomnode::getName() const {
	return pvt->_nodename;
}

const char *xmldomnode::getValue() const {
	return pvt->_nodevalue;
}

xmldomnode *xmldomnode::getParent() const {
	return pvt->_parent;
}

xmldomnode *xmldomnode::getPreviousSibling() const {
	return pvt->_previous;
}

xmldomnode *xmldomnode::getNextSibling() const {
	return pvt->_next;
}

xmldomnode *xmldomnode::getNullNode() const {
	return pvt->_nullnode;
}

bool xmldomnode::isNullNode() const {
	return pvt->_isnullnode;
}

int xmldomnode::getChildCount() const {
	return pvt->_childcount;
}

xmldomnode *xmldomnode::getChild(int position) const {
	return getNode(pvt->_firstchild,position,NULL,pvt->_childcount);
}

xmldomnode *xmldomnode::getChild(const char *name) const {
	return getNode(pvt->_firstchild,0,name,pvt->_childcount);
}

int xmldomnode::getAttributeCount() const {
	return pvt->_attributecount;
}

xmldomnode *xmldomnode::getAttribute(int position) const {
	return getNode(pvt->_firstattribute,position,NULL,pvt->_attributecount);
}

xmldomnode *xmldomnode::getAttribute(const char *name) const {
	return getNode(pvt->_firstattribute,0,name,pvt->_attributecount);
}

const char *xmldomnode::getAttributeValue(int position) const {
	return getAttribute(position)->getValue();
}

const char *xmldomnode::getAttributeValue(const char *name) const {
	return getAttribute(name)->getValue();
}

void xmldomnode::setType(xmldomnodetype type) {
	pvt->_type=type;
}

void xmldomnode::setName(const char *name) {
	pvt->_dom->unCacheString(pvt->_nodename);
	pvt->_nodename=pvt->_dom->cacheString(name);
}

void xmldomnode::setValue(const char *value) {
	pvt->_dom->unCacheString(pvt->_nodevalue);
	pvt->_nodevalue=pvt->_dom->cacheString(value);
}

void xmldomnode::setParent(xmldomnode *parent) {
	pvt->_parent=parent;
}

void xmldomnode::setPreviousSibling(xmldomnode *previous) {
	pvt->_previous=previous;
}

void xmldomnode::setNextSibling(xmldomnode *next) {
	pvt->_next=next;
}

bool xmldomnode::insertChild(xmldomnode *child, int position) {
	return insertNode(child,position,
				child->pvt->_type,
				&pvt->_firstchild,
				&pvt->_lastchild,
				&pvt->_childcount);
}

bool xmldomnode::insertAttribute(xmldomnode *attribute, int position) {
	return insertNode(attribute,position,
				ATTRIBUTE_XMLDOMNODETYPE,
				&pvt->_firstattribute,
				&pvt->_lastattribute,
				&pvt->_attributecount);
}

bool xmldomnode::deleteChild(int position) {
	return deleteNode(NULL,position,NULL,
				&pvt->_firstchild,
				&pvt->_lastchild,
				&pvt->_childcount);
}

bool xmldomnode::deleteChild(xmldomnode *child) {
	return deleteNode(child,0,NULL,
				&pvt->_firstchild,
				&pvt->_lastchild,
				&pvt->_childcount);
}

bool xmldomnode::deleteAttribute(int position) {
	return deleteNode(NULL,position,NULL,
				&pvt->_firstattribute,
				&pvt->_lastattribute,
				&pvt->_attributecount);
}

bool xmldomnode::deleteAttribute(const char *name) {
	return deleteNode(NULL,0,name,
				&pvt->_firstattribute,
				&pvt->_lastattribute,
				&pvt->_attributecount);
}

bool xmldomnode::deleteAttribute(xmldomnode *attribute) {
	return deleteNode(attribute,0,NULL,
				&pvt->_firstattribute,
				&pvt->_lastattribute,
				&pvt->_attributecount);
}

stringbuffer *xmldomnode::xml() const {
	return xml(NULL);
}

stringbuffer *xmldomnode::getPath() const {

	// Path: /element[index]/...

	// run up the tree, counting parent nodes
	int	ancestors=0;
	const xmldomnode	*node=this;
	while (!node->isNullNode() && node->getType()!=ROOT_XMLDOMNODETYPE) {
		ancestors++;
		node=node->getParent();
	}

	// create pointers to the names of each parent node
	const char	**names=new const char *[ancestors];
	uint32_t	*indices=new uint32_t[ancestors];
	node=this;
	for (int index=ancestors-1; index>=0; index--) {

		// get the name
		names[index]=node->getName();

		// figure out which sibling this node is
		indices[index]=0;
		for (xmldomnode *siblingnode=
				node->getPreviousTagSibling(names[index]);
			(!siblingnode->isNullNode() &&
				siblingnode->getType()!=ROOT_XMLDOMNODETYPE);
			siblingnode=siblingnode->
				getPreviousTagSibling(names[index])) {
			indices[index]++;
		}
		
		node=node->getParent();
	}

	// run through the list of parent node names and indices,
	// append them all to the path
	stringbuffer	*path=new stringbuffer();
	for (int index=0; index<ancestors; index++) {
		path->append('/')->append(names[index]);
		path->append('[')->append(indices[index])->append(']');
	}

	delete[] names;
	delete[] indices;

	return path;
}

xmldomnode *xmldomnode::getChildByPath(const char *path) const {

	// Path: /element[index]/...
	const xmldomnode	*node=this;
	stringbuffer		name;
	stringbuffer		indexstring;
	stringbuffer		*buffer=&name;
	const char		*ptr=path;
	for (;;) {

		if (!*ptr || *ptr=='@') {
			break;
		} else if (*ptr=='/') {
			buffer=&name;
		} else if (*ptr=='[') {
			buffer=&indexstring;
		} else if (*ptr==']') {

			// get the first child
			node=node->getChild(name.getString());

			// now skip until we get the specified index
			uint64_t	index=
				charstring::toUnsignedInteger(
						indexstring.getString());
			for (uint64_t i=0; i<index; i++) {
				node=node->getNextTagSibling(name.getString());
			}

			// reset the buffers
			name.clear();
			indexstring.clear();

		} else {
			buffer->append(*ptr);
		}

		ptr++;
	}

	return const_cast<xmldomnode *>(node);
}

xmldomnode *xmldomnode::getAttributeByPath(const char *path,
						int position) const {
	return getChildByPath(path)->getAttribute(position);
}

xmldomnode *xmldomnode::getAttributeByPath(const char *path,
						const char *name) const {
	return getChildByPath(path)->getAttribute(name);
}

const char *xmldomnode::getAttributeValueByPath(const char *path,
						int position) const {
	return getChildByPath(path)->getAttributeValue(position);
}

const char *xmldomnode::getAttributeValueByPath(const char *path,
						const char *name) const {
	return getChildByPath(path)->getAttributeValue(name);
}

#ifdef RUDIMENTS_NAMESPACE
}
#endif


syntax highlighted by Code2HTML, v. 0.9.1