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

#include <rudiments/dtd.h>
#include <rudiments/charstring.h>

#include <stdio.h>

#ifdef RUDIMENTS_NAMESPACE
namespace rudiments {
#endif

class dtdprivate {
	friend class dtd;
	private:
		xmldom	_xmld;
		xmldom	_xmldtd;

		stringbuffer	_err;
};

dtd::dtd() {
	pvt=new dtdprivate;
}

dtd::~dtd() {
	delete pvt;
}

bool dtd::parseFile(const char *filename) {
	if (!pvt->_xmld.parseFile(filename)) {
		pvt->_err.clear();
		pvt->_err.append(pvt->_xmld.getError());
		return false;
	}
	if (!parseDtd()) {
		return false;
	}
	return true;
}

bool dtd::parseString(const char *string) {
	if (!pvt->_xmld.parseString(string)) {
		pvt->_err.clear();
		pvt->_err.append(pvt->_xmld.getError());
		return false;
	}
	if (!parseDtd()) {
		return false;
	}
	return true;
}

bool dtd::parseDtd() {

	// create the new tree
	pvt->_xmldtd.createRootNode();
	pvt->_xmldtd.getRootNode()->cascadeOnDelete();
	pvt->_xmldtd.getRootNode()->appendChild(
		new xmldomnode(&pvt->_xmldtd,
				pvt->_xmldtd.getRootNode()->getNullNode(),
						TAG_XMLDOMNODETYPE,"dtd",NULL));

	// step through tags
	xmldomnode	*root=pvt->_xmld.getRootNode();
	xmldomnode	*currentnode=root->getFirstTagChild();
	while (!currentnode->isNullNode()) {

		// for each element, create a new element node,
		// for each attribute, create a new attribute node
		if ((!charstring::compare(currentnode->getName(),
							"!ELEMENT") &&
					!newElement(currentnode)) ||
			(!charstring::compare(currentnode->getName(),
							"!ATTLIST") &&
					!newAttribute(currentnode))) {
				return false;
		}

		// move on to the next tag
		currentnode=currentnode->getNextTagSibling();
	}

	// success
	return true;
}

bool dtd::newElement(xmldomnode *node) {

	// sanity check
	if (node->getAttributeCount()<2) {
		nodeError(node);
		return false;
	}

	// append a carriage return just for looks
	xmldomnode	*dtdnode=pvt->_xmldtd.getRootNode()->getChild("dtd");
	dtdnode->appendText("\n");

	// create an "element" element and add it to the tree
	xmldomnode	*element=new xmldomnode(&pvt->_xmldtd,
					dtdnode->getNullNode(),
					TAG_XMLDOMNODETYPE,"element",NULL);
	element->cascadeOnDelete();
	if (!dtdnode->appendChild(element)) {
		nodeError(node);
		delete element;
		return false;
	}

	// add a name attribute to the element
	const char	*name=node->getAttribute(0)->getName();
	if (!element->appendAttribute("name",name)) {
		nodeError(node);
		return false;
	}

	// add the list of valid child elements to the tree
	const char	*list=node->getAttribute(1)->getName();
	if (parseList(list,element,1,1,',',"child")) {
		return true;
	}
	nodeError(node);
	return false;
}

bool dtd::parseList(const char *list, xmldomnode *node,
					int checkcount, int indent,
					char delimiter,
					const char *name) {

	// return failure for a NULL list and success for "EMPTY"
	if (!list) {
		return false;
	} else if (!charstring::compare(list,"EMPTY")) {
		return true;
	} else {

		// parse the list, it should be "delimiter" seperated values
		const char	*ptr1=list;
		const char	*ptr2;
		int	length;
		char	*value;
		char	count[2];
		count[1]='\0';
		for (;;) {
	
			// look for the specified delimiter
			ptr2=charstring::findFirst(ptr1,delimiter);

			// if we don't find the delimiter,
			// use the end of the string instead 
			if (!ptr2) {
				ptr2=list+charstring::length(list);
			}
	
			// parse out the value, including the * or +
			length=ptr2-ptr1;
			value=new char[length+1];
			value[length]='\0';
			charstring::copy(value,ptr1,length);

			// if specified, evaluate any trailing *'s or +'s and
			// truncate them
			if (checkcount) {
				count[0]=value[charstring::length(value)-1];
				if (count[0]!='*' && count[0]!='+') {
					count[0]='1';
				} else {
					value[charstring::length(value)-1]='\0';
				}
			}


			// append a carriage return just for looks
			node->appendText("\n");

			// indent for looks as well
			for (int i=0; i<indent; i++) {
				node->appendText("	");
			}

			// create a new element and add it to the tree
			xmldomnode	*newtag=new xmldomnode(
					&pvt->_xmldtd,
					pvt->_xmldtd.getRootNode()->
							getNullNode(),
					TAG_XMLDOMNODETYPE,name,NULL);
			newtag->cascadeOnDelete();
			if (!node->appendChild(newtag)) {
				delete newtag;
				delete[] value;
				return false;
			}

			// give the new element a name attribute and a count
			// attribute if that was specified
			if (!newtag->appendAttribute("name",value) ||
				(checkcount &&
				!newtag->appendAttribute("count",count))) {
				return false;
			}

			// clean up
			delete[] value;
	
			// break if we're at the end of the string
			if (!*ptr2) {
				break;
			}
	
			// move on to the next value
			ptr1=ptr2+1;
		}

		// return success
		return true;
	}
}

bool dtd::newAttribute(xmldomnode *node) {

	// sanity check
	if (node->getAttributeCount()<4) {
		nodeError(node);
		return false;
	}

	// get the appropriate element to add this attribute to
	xmldomnode	*element=pvt->_xmldtd.getRootNode()->getChild("dtd")->
						getChild("element","name",
							node->getAttribute(0)->
								getName());
	if (element->isNullNode()) {
		nodeError(node);
		return false;
	}

	// create a new "attribute" element and add it to the tree
	element->appendText("\n	");
	xmldomnode	*attribute=new xmldomnode(
					&pvt->_xmldtd,
					pvt->_xmldtd.getRootNode()->
							getNullNode(),
					TAG_XMLDOMNODETYPE,"attribute",NULL);
	attribute->cascadeOnDelete();
	if (!element->appendChild(attribute)) {
		nodeError(node);
		return false;
	}

	// give the new element name and default value attributes
	if (!attribute->appendAttribute("name",
				node->getAttribute(1)->getName()) ||
		!attribute->appendAttribute("default",
				node->getAttribute(3)->getName())) {
		nodeError(node);
		return false;
	}

	// insert the list of valid values or none if CDATA
	const char	*values=node->getAttribute(2)->getName();
	if (!charstring::compare(values,"CDATA")) {
		return true;
	}
	if (parseList(values,attribute,0,2,'|',"value")) {
		return true;
	}
	nodeError(node);
	return false;
}

xmldomnode *dtd::xml() {
	return pvt->_xmldtd.getRootNode();
}

const char *dtd::getError() {
	return pvt->_err.getString();
}

void dtd::nodeError(xmldomnode *node) {
	stringbuffer	*xml=node->xml();
	pvt->_err.append("error processing:\n");
	pvt->_err.append(xml->getString());
	delete xml;
}

#ifdef RUDIMENTS_NAMESPACE
}
#endif


syntax highlighted by Code2HTML, v. 0.9.1