//
// See the file LICENSE for redistribution information.
//
// Copyright (c) 2002-2003
//	Sleepycat Software.  All rights reserved.
//

static const char revid[] = "$Id: Document.cpp,v 1.98 2003/12/10 12:21:48 merrells Exp $";

#include "dbxml_config.h"
#include "dbxml/XmlPortability.hpp"
#include "dbxml/XmlNamespace.hpp"
#include "dbxml/XmlException.hpp"
#include "dbxml/XmlValue.hpp"
#include "dbxml/XmlResults.hpp"
#include "dbxml/XmlQueryContext.hpp"
#include "Document.hpp"
#include "Container.hpp"
#include "OperationContext.hpp"
#include "UTF8.hpp"
#include "Value.hpp"
#include "QueryContext.hpp"
#include "Modify.hpp"
#include "Results.hpp"

#include "db_utils.h"

#include <set>

#if defined(DBXML_DOM_XERCES2)
#include <xercesc/sax/ErrorHandler.hpp>
#include <xercesc/sax/EntityResolver.hpp>
#include <xercesc/sax/SAXParseException.hpp>
#include <xercesc/dom/DOM.hpp>
#include <xercesc/dom/DOMException.hpp>
#include <xercesc/parsers/XercesDOMParser.hpp>
#include <xercesc/util/PlatformUtils.hpp>
#include <xercesc/util/XMLException.hpp>
#include <xercesc/framework/MemBufInputSource.hpp>
#include <xercesc/framework/MemBufFormatTarget.hpp>
#if defined(XERCES_HAS_CPP_NAMESPACE)
  XERCES_CPP_NAMESPACE_USE
#endif
#endif

#if defined(DBXML_XPATH_PATHAN)
#include <pathan/XPathEvaluator.hpp>
#include <pathan/XPathNSResolver.hpp>
#include <pathan/XPathResult.hpp>
#include <pathan/ext/XPathEvaluatorExt.hpp>
#endif

using namespace DbXml;

MetaDatum::MetaDatum()
	: type_(XmlValue::NONE),
	dbt_(0)
{}

MetaDatum::MetaDatum(const Name &name, XmlValue::Type type)
	: name_(name),
	type_(type),
	dbt_(0)
{}

MetaDatum::MetaDatum(const Name &name, XmlValue::Type type, DbtOut **dbt)
	: name_(name),
	type_(type),
	dbt_(*dbt)
{
	*dbt = 0;
}

MetaDatum::~MetaDatum()
{
	delete dbt_;
}

const DbtOut *MetaDatum::getDbt() const
{
	return dbt_;
}

void MetaDatum::setDbt(DbtOut **dbt) // Note: Consumes the Dbt
{
	delete dbt_;
	dbt_ = *dbt;
	*dbt = 0;
}

const char *MetaDatum::getValue() const
{
	const char *r = 0;
	if (dbt_->get_size() > 0) {
		r = (const char *)dbt_->get_data();
	}
	return r;
}

XmlValue::Type MetaDatum::getType() const
{
	return type_;
}

const Name &MetaDatum::getName() const
{
	return name_;
}

void MetaDatum::decodeKeyDbt(const DbtOut &key, bool swap, ID &did, ID &nid, XmlValue::Type &type)
{
	// Key
	//  did nid type
	u_int32_t *p = (u_int32_t *)key.get_data();
	u_int32_t id1, id2;
	id1 = *p++;
	id2 = *p++;
	if (swap) {
		M_32_SWAP(id1);
		M_32_SWAP(id2);
	}
	did = id1;
	nid = id2;
	type = (XmlValue::Type) * ((unsigned char*)p);
}

int MetaDatum::setThisFromDbt(OperationContext &context, const Container &container, const DbtOut &key, DbtOut **value, bool swap)
{
	// Key
	ID did, nid;
	decodeKeyDbt(key, swap, did, nid, type_);
	int err = container.lookupName(context.txn(), context.key(), context.data(), nid, name_);
	// Value
	dbt_ = *value;
	*value = 0;
	return err;
}

void MetaDatum::setKeyDbt(const ID &did, const ID &nid, XmlValue::Type type, DbtOut &key, bool swap)
{
	// Key
	u_int32_t id1 = did.raw();
	u_int32_t id2 = nid.raw();
	if (swap) {
		M_32_SWAP(id1);
		M_32_SWAP(id2);
	}
	size_t l = (sizeof(u_int32_t) * 2) + (type == XmlValue::NONE ? 0 : 1);
	key.set(0, l);
	Buffer b(key.get_data(), l, /*wrapper=*/true);
	b.write(&id1, sizeof(u_int32_t));
	b.write(&id2, sizeof(u_int32_t));
	if (type != XmlValue::NONE) {
		unsigned char t = type;
		b.write(&t, sizeof(t));
	}
}

void MetaDatum::setValueDbtFromThis(DbtIn &value, bool swap)
{
	UNUSED(swap);

	value.set_data(dbt_->get_data());
	value.set_size(dbt_->get_size());
}

bool MetaDatum::insertIntoDocument() const
{
	return type_ == XmlValue::STRING || type_ == XmlValue::NUMBER || type_ == XmlValue::BOOLEAN;
}

void MetaDatum::asValue(XmlValue &value) const
{
	value = XmlValue(type_, *dbt_);
}

// Document

Document::Document()
	: id_(0),
	content_(Name::dbxml_colon_content, XmlValue::DOCUMENT),
	name_(Name::dbxml_colon_name, XmlValue::STRING),
	document_(0)
{
	XMLPlatformUtils::Initialize();
}

Document::~Document()
{
	MetaData::iterator i;
	for (i = metaData_.begin();i != metaData_.end();++i) {
		if (*i != &content_ && *i != &name_)
			delete *i;
	}
	if (document_)
		document_->release();
	XMLPlatformUtils::Terminate();
}

void Document::setMetaData(const Name &name, const XmlValue &value)
{
	switch (value.getType(0)) {
	case XmlValue::STRING:
	case XmlValue::NUMBER:
	case XmlValue::BOOLEAN:
	case XmlValue::DOCUMENT:
		break;
	case XmlValue::NODE:
		throw XmlException(XmlException::INVALID_VALUE,
				   "setMetaData expects a typed value, not a node");
		break;
	case XmlValue::VARIABLE:
		throw XmlException(XmlException::INVALID_VALUE,
				   "setMetaData expects a typed value, not a variable");
		break;
	case XmlValue::NONE:
	default:
		throw XmlException(XmlException::INVALID_VALUE,
				   "setMetaData expects a typed value");
		break;
	}
	std::string v(value.asString(0));
	DbtOut dbt((void*)v.c_str(), v.length() + 1);
	setMetaData(name, value.getType(0), dbt); // Include the terminating 0
}

void Document::setMetaData(const Name &name, XmlValue::Type type, const Dbt &value)
{
	DbtOut *dbt = new DbtOut(value);
	setMetaDataPtr(new MetaDatum(name, type, &dbt));
}

void Document::setMetaDataPtr(MetaDatum *pmd)
{
	MetaData::iterator i;
	for (i = metaData_.begin();i != metaData_.end();++i) {
		if ((*i)->getName() == pmd->getName()) {
			*i = pmd;
			return ;
		}
	}
	metaData_.push_back(pmd);
}

bool Document::getMetaData(const Name &name, XmlValue &value)
{
	const MetaDatum *md = getMetaDataPtr(name);
	if (md != 0) {
		md->asValue(value);
	}
	return md != 0;
}

bool Document::getMetaData(const Name &name, Dbt &value)
{
	const MetaDatum *md = getMetaDataPtr(name);
	if (md != 0) {
		const Dbt *dbt = md->getDbt();
		value.set_data(dbt->get_data());
		value.set_size(dbt->get_size());
	}
	return (md != 0);
}

const MetaDatum *Document::getMetaDataPtr(const Name &name) const
{
	const MetaDatum *r = 0;
	MetaData::const_iterator i;
	for (i = metaData_.begin();r == 0 && i != metaData_.end();++i) {
		if ((*i)->getName() == name) {
			r = *i;
		}
	}
	return r;
}

std::string Document::getName() const
{
	std::string r;
	const Dbt *dbt = name_.getDbt();
	if (dbt != 0) {
		r.assign((const char *)dbt->get_data(), dbt->get_size() - 1); // Note the trailing '\0' is stored.
	}
	return r;
}

void Document::setName(const std::string &name)
{
	DbtOut *dbt = new DbtOut(name.c_str(), name.length() + 1); // Store the trailing '\0'
	if (name_.getDbt() == 0)
		metaData_.push_back(&name_);
	name_.setDbt(&dbt); // Note: Consumes Dbt.
}

void Document::setContent(const Dbt &content)
{
	DbtOut *dbt = new DbtOut(content);
	if (content_.getDbt() == 0)
		metaData_.push_back(&content_);
	content_.setDbt(&dbt); // Note: Consumes Dbt.
}

const Dbt *Document::getContent() const
{
	return content_.getDbt();
}

int Document::setThisFromDbt(OperationContext &context, const Container &container, const DbtOut &key, DbtOut **value, bool swap) // Note: Consumes value
{
	int err = 0;
	ID nid;
	XmlValue::Type type;
	MetaDatum::decodeKeyDbt(key, swap, id_, nid, type);
	if (nid == container.getNIDForContent()) {
		if (content_.getDbt() == 0)
			metaData_.push_back(&content_);
		content_.setDbt(value);
	} else if (nid == container.getNIDForName()) {
		if (name_.getDbt() == 0)
			metaData_.push_back(&name_);
		name_.setDbt(value); // Note: Consumes Dbt.
	} else {
		MetaDatum *p = new MetaDatum();
		err = p->setThisFromDbt(context, container, key, value, swap); // Note: Consumes Dbt.
		metaData_.push_back(p);
	}
	return err;
}

class DbXmlErrorHandler : public ErrorHandler
{
public:
	DbXmlErrorHandler() : error_(false), line_(0)
	{}
	~DbXmlErrorHandler()
	{}
	virtual void warning(const SAXParseException& toCatch)
	{
		UNUSED(toCatch);
	}
	virtual void error(const SAXParseException& toCatch)
	{
		error_ = true;
		file_ = XMLChToUTF8(toCatch.getSystemId()).str();
		line_ = toCatch.getLineNumber();
		message_ = XMLChToUTF8(toCatch.getMessage()).str();
	}
	virtual void fatalError(const SAXParseException& toCatch)
	{
		error_ = true;
		file_ = XMLChToUTF8(toCatch.getSystemId()).str();
		line_ = toCatch.getLineNumber();
		message_ = XMLChToUTF8(toCatch.getMessage()).str();
	}
	virtual void resetErrors()
	{
		error_ = false;
	}
	bool parseError() const
	{
		return error_;
	}
	int line() const
	{
		return line_;
	}
	const std::string &file() const
	{
		return file_;
	}
	const std::string &message() const
	{
		return message_;
	}
private :
	DbXmlErrorHandler(const DbXmlErrorHandler&);
	void operator=(const DbXmlErrorHandler&);
	bool error_;
	int line_;
	std::string file_;
	std::string message_;
};

class DbXmlEntityResolver : public EntityResolver
{
public:
	DbXmlEntityResolver()
	{}
	~DbXmlEntityResolver()
	{}
	virtual InputSource* resolveEntity(const XMLCh* const publicId, const XMLCh* const systemId)
	{
		UNUSED(publicId);
		UNUSED(systemId);
		return new MemBufInputSource(0, 0, "", false);
	}
private :
	DbXmlEntityResolver(const DbXmlEntityResolver&);
	void operator=(const DbXmlEntityResolver&);
};


// simple wrapper for parsing
static void parseDoc(XercesDOMParser &xmlParser, const XMLByte* const srcBytes,
		     const unsigned int byteCount, const char * const id)
{
	DbXmlErrorHandler eh;
	DbXmlEntityResolver er;
	try {
		xmlParser.setErrorHandler(&eh);
		xmlParser.setEntityResolver(&er);
		xmlParser.setValidationScheme(XercesDOMParser::Val_Never);
		xmlParser.setDoNamespaces(true);
		MemBufInputSource theInputSource(srcBytes, byteCount, id, false);
		xmlParser.parse(theInputSource);
	} catch (XMLException &e) {
		throw XmlException(XmlException::DOM_PARSER_ERROR, XMLChToUTF8(e.getMessage()).str(), e.getSrcFile(), e.getSrcLine());
	}
	if (eh.parseError()) {
		throw XmlException(XmlException::DOM_PARSER_ERROR, eh.message(), eh.file().c_str(), eh.line());
	}
}

XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument *Document::getDOM(XercesDOMParser &xmlParser, bool withMetaData, bool adopt) const
{
	if (document_ == 0) {
		const Dbt *dbt = content_.getDbt();
		if (dbt != 0) {
			// Parse the document into a DOM tree.
			parseDoc(xmlParser, (XMLByte*)dbt->get_data(), dbt->get_size(), getName().c_str());
			// Use adoptDocument so the document outlives the parser.
			document_ = xmlParser.adoptDocument();

			if (withMetaData) {
				domMetaData(false /* means insert */);
			}
		}
	}
 	XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument *r= document_;
 	if(adopt) document_= 0;
	return r;
}

// gmf: this is cribbed from Xerces DOMElementImpl
static void domSetAttributeNS(DOMElement *element, const XMLCh *fNamespaceURI,
			      const XMLCh *qualifiedName, const XMLCh *fValue)
{
    DOMAttr* newAttr = element->getAttributeNodeNS(fNamespaceURI, qualifiedName);
    if (!newAttr) {
	    newAttr = element->getOwnerDocument()->createAttributeNS(fNamespaceURI, qualifiedName);
    }
    newAttr->setNodeValue(fValue);
    element->setAttributeNodeNS(newAttr);
}


// add or remove document metadata as DOM attributes on Document node
void Document::domMetaData(bool remove) const
{
	// Get the root element of the document.
	DOMElement *root = document_->getDocumentElement();
	// Add/remove the document meta-data as attributes of the root element.
	std::set<std::string> namespaces; // jcm - slow :-(
	MetaData::const_iterator i;
	for (i = metaData_.begin();i != metaData_.end();++i) {
		const MetaDatum *md = *i;
		const Name &name = md->getName();
		if (md->insertIntoDocument()) {
			if (name.hasPrefix() && name.hasURI()) {
				if (namespaces.find(name.getPrefix()) == namespaces.end()) // jcm - slow :-(
				{
					addMetaDataNamespaceAttribute(root, md->getName(), remove);
					namespaces.insert(name.getPrefix()); // jcm - slow :-(
				}
				addMetaDataAttribute(root, md->getName(), md->getValue(), remove);
			}
		}
	}
	if (namespaces.find(Name::dbxml_colon_id.getPrefix()) == namespaces.end()) // jcm - slow :-(
	{
		addMetaDataNamespaceAttribute(root, Name::dbxml_colon_id, remove);
	}
	char buf[16];
	::sprintf(buf, "%d", id_.raw());
	addMetaDataAttribute(root, Name::dbxml_colon_id, buf, remove);
}

void Document::addMetaDataNamespaceAttribute(DOMElement *document, const Name &name, bool remove) const
{
	// declare the namespace of the meta-data attribute
	XMLCh *attrURI = XMLString::transcode("http://www.w3.org/2000/xmlns/");
	if (remove) {
		XMLCh *attrName = XMLString::transcode(name.getPrefix());
		document->removeAttributeNS(attrURI, attrName);
		XMLString::release(&attrName);
		XMLString::release(&attrURI);
		return;
	}
	string xmlns_prefix("xmlns:");
	xmlns_prefix += name.getPrefix();
	XMLCh *attrName = XMLString::transcode(xmlns_prefix.c_str());
	XMLCh *attrValue = XMLString::transcode(name.getURI());
	domSetAttributeNS(document, attrURI, attrName, attrValue);
	XMLString::release(&attrValue);
	XMLString::release(&attrName);
	XMLString::release(&attrURI);
}

void Document::addMetaDataAttribute(DOMElement *document, const Name &name, const char *value, bool remove) const
{
	// add the meta-data attribute
	XMLCh *attrURI = XMLString::transcode(name.getURI());
	if (remove) {
		XMLCh *attrName = XMLString::transcode(name.getName());
		document->removeAttributeNS(attrURI, attrName);
		XMLString::release(&attrName);
		XMLString::release(&attrURI);
		return;
	}
	XMLCh *attrName = XMLString::transcode(name.getPrefixName().c_str());
	XMLCh *attrValue = XMLString::transcode(value);
	domSetAttributeNS(document, attrURI, attrName, attrValue);
	XMLString::release(&attrValue);
	XMLString::release(&attrName);
	XMLString::release(&attrURI);
}

void Document::queryWithXPath(const XmlDocument &xmlDocument, const std::string &xpath, Results &results, QueryContext &context) const
{
	if(context.getReturnType()==XmlQueryContext::ResultDocuments ||
		context.getReturnType()==XmlQueryContext::CandidateDocuments) {
		XmlValue value(xmlDocument);
		results.add(value);
	} else {
		XPathNSResolver *docNSResolver = 0;
		XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument *nsDocument = 0;
		DOMImplementation *factory = DOMImplementation::getImplementation();
		XPathExpression *parsedExpression = 0;
		try {
			nsDocument = factory->createDocument(NULL, NULL, NULL);
			context.setupXPathQueryContext(context.getXPathEvaluator(), nsDocument, &docNSResolver);
			UTF8ToXMLCh xmlch(xpath);
			parsedExpression = context.getXPathEvaluator().createExpression(xmlch.str(), docNSResolver);
			XmlValueVector vv;
			evaluateXPath(xmlDocument, context, *parsedExpression, vv, /*project=*/true);
			results.add(vv);
			nsDocument->release();
			delete docNSResolver;
			delete parsedExpression;
		} catch (const XPathException &e) {
			nsDocument->release();
			delete parsedExpression;
			delete docNSResolver;
			throw XmlException(XmlException::XPATH_EVALUATION_ERROR, e.getString().c_str());
		} catch(...) {
			nsDocument->release();
			delete parsedExpression;
			delete docNSResolver;
			throw;
		}
	}
}

bool Document::evaluateXPath(const XmlDocument &xmlDocument, QueryContext &qc, const XPathExpression &xpath, XmlValueVector &vv, bool project) const
{
	bool r = false; // xpath produced nothing
	//
	// The expression is evaluated on the document.
	// The result is placed in the variable result.
	//
	XPathResult::resultType rt = XPathResult::ANY_TYPE;
	//
	//	XPathResult::resultType rt = (project ?
	//	    XPathResult::ANY_TYPE :
	//	    XPathResult::ANY_UNORDERED_NODE_TYPE);
	//
	// jcm: It'd be good if we could say this instead,
	// but we don't know what the return type of the
	// expression is. ANY_UNORDERED_NODE_TYPE is good
	// for matching documents because it returns when
	// the first matching node is found.
	//
	XercesDOMParser &xmlParser = qc.getDOMParser();
	XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument *document = getDOM(xmlParser, qc.getWithMetaData(), false);
	auto_ptr<XPathResult> result(xpath.evaluate(document, rt, 0));

	// Retreive the nodeset result of the XPath expression
	if (project) {
		switch (result->getResultType()) {
		case XPathResult::NUMBER_TYPE:
			vv.push_back(result->getNumberValue());
			r = true;
			break;
		case XPathResult::STRING_TYPE:
			vv.push_back(XMLChToUTF8(result->getStringValue()).str());
			r = true;
			break;
		case XPathResult::BOOLEAN_TYPE:
			vv.push_back(result->getBooleanValue());
			r = true;
			break;
		default:
			{
			// 
			if(qc.getReturnType()==XmlQueryContext::ResultValues) {
				// Return 'dead' nodes.
				XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument *tmpDocument= &qc.getTemporaryDocument();
				DOMNode *n;
				while ((n = result->iterateNext()) != 0) {
					vv.push_back(XmlValue(*tmpDocument->importNode(n, true), 0, &qc));
					r= true;
				}
			} else {
				// Return 'live' nodes.
				DOMNode *n;
				while ((n = result->iterateNext()) != 0) {
					vv.push_back(XmlValue(*n, &xmlDocument, &qc));
					r = true;
				}
			}
			}
			break;
		}
	} else {
		switch (result->getResultType()) {
		case XPathResult::NUMBER_TYPE:
			r = (result->getNumberValue() > 0);
			break;
		case XPathResult::STRING_TYPE:
			{
			const XMLCh *s = result->getStringValue();
			r = s != 0 && s[0] != '\0';
			}
			break;
		case XPathResult::BOOLEAN_TYPE:
			r = result->getBooleanValue();
			break;
		default:
			r = (result->iterateNext() != 0);
			break;
		}
	}
	return r;
}

void Document::modifyDocument(XmlDocument &xmlDocument, const Modify &modify)
{
	bool newContext = false;
	XmlQueryContext::ReturnType origRetType;
	XmlQueryContext::EvaluationType origEvalType;
	const std::string *query = modify.getXPath();
	XmlModify::ModificationType operation = modify.getOperation();
	XmlModify::XmlObject type = modify.getType();
	const std::string *name = modify.getName();
	const std::string *content = modify.getContent();
	int location = modify.getLocation();

	modify.clearOps();
	XmlQueryContext *context = modify.getContext();
	if (!context) {
		newContext = true;
		context = new XmlQueryContext(XmlQueryContext::ResultDocumentsAndValues);
	} else {
		// modify context to ensure proper return/eval types for update.
		// need to reset later, which means catching any exceptions thrown,
		// and re-throwing after fixup.
		origRetType = context->getReturnType();
		origEvalType = context->getEvaluationType();
		context->setReturnType(XmlQueryContext::ResultDocumentsAndValues);
		context->setEvaluationType(XmlQueryContext::Lazy);
	}

	Results results(*context, NULL);
	try {
		// perform the query that targets the nodes
		queryWithXPath(xmlDocument, *query, results, *context);
		DOMNode *domContent = NULL;
		XmlValue curValue;
		results.next(curValue);
		if (!curValue.isNull()) {
			// create content once for something not simple
			if ((modify.getType() != XmlModify::Attribute) &&
			    (operation == XmlModify::InsertBefore ||
			     operation == XmlModify::InsertAfter ||
			     operation == XmlModify::Append)) {
				size_t sz; Results::SizeType ty;
				results.size(sz, ty);
				if (sz > 1) {
					domContent = createContent(curValue.asNode()->getOwnerDocument(),
								   modify);
				}
			}
		}
		while (!curValue.isNull()) {
			updateDocNode(curValue, modify, domContent);
			results.next(curValue);
		}
		if (domContent)
			domContent->release();
		// remove metadata if it's been added
		if (context->getWithMetaData())
			domMetaData(true);
		// rewrite the document content from DOM tree
		setContentFromDOM(modify.getNewEncoding());
	} catch (...) {
		if (!newContext) {
			context->setReturnType(origRetType);
			context->setEvaluationType(origEvalType);
		} else {
			delete context;
		}
		throw; // simple re-throw
	}
	if (!newContext) {
		// restore original state of context
		context->setReturnType(origRetType);
		context->setEvaluationType(origEvalType);
	} else {
		delete context;
	}
}

// create a simple attribute.  Attribute ordering is
// not maintained
static void insertAttribute(DOMNode *target, const Modify &modify)
{
	const std::string *name = modify.getName();
	const std::string *content = modify.getContent();
	if (!name || !content)
		throw XmlException(XmlException::INVALID_VALUE,
				   "modifyDocument: attribute creation requires name and value");
	if (target->getNodeType() != DOMNode::ELEMENT_NODE)
		throw XmlException(XmlException::INVALID_VALUE,
				   "modifyDocument: attributes can only be added to elements");
	UTF8ToXMLCh newName(*name);
	UTF8ToXMLCh newContent(*content);
	((DOMElement*)target)->setAttribute(newName.str(), newContent.str());
}

void Document::updateDocNode(XmlValue &curValue, const Modify &modify, DOMNode *domContent)
{
	DOMNode *curNode = curValue.asNode();
	DOMNode *parentNode = NULL;
	DOMNode *targ = NULL;
	bool clone = true;
	switch (modify.getOperation()) {
	case XmlModify::Remove:
	{
		DOMElement *containingNode;
		// ignore type, name, content
		if (curNode->getNodeType() == DOMNode::ATTRIBUTE_NODE) {
			containingNode = ((DOMAttr*)curNode)->getOwnerElement();
			containingNode->removeAttributeNode((DOMAttr*)curNode);
		} else {
			parentNode = curNode->getParentNode();
			if (parentNode->getNodeType() == DOMNode::DOCUMENT_NODE) {
				throw XmlException(XmlException::INVALID_VALUE,
						   "modifyDocument: cannot remove the document root");
			} else {
				parentNode->removeChild(curNode);
			}
		}
	}
	break;
	case XmlModify::Update:
	{
		UTF8ToXMLCh *newContent = NULL;
		if (modify.getContent())
			newContent = new UTF8ToXMLCh(*modify.getContent());
		// in-place content change.  Type-dependent behavior
		switch (curNode->getNodeType()) {
		case DOMNode::ELEMENT_NODE:
		{
			// remove old text nodes
			DOMNodeList *list = curNode->getChildNodes();
			unsigned int i = 0;
			while (i < list->getLength()) {
				DOMNode *tnode = list->item(i);
				if (tnode->getNodeType() == DOMNode::TEXT_NODE) {
					DOMNode *old = curNode->removeChild(tnode);
					if (old)
						old->release();
				} else
					++i;
			}
			if (newContent)
				curNode->appendChild(curNode->getOwnerDocument()->createTextNode(newContent->str()));
		}
		break;
		case DOMNode::TEXT_NODE:
			if (newContent)
				curNode->setNodeValue(newContent->str());
			else
				curNode->getParentNode()->removeChild(curNode);
			break;
		case DOMNode::ATTRIBUTE_NODE:
			if (newContent) {
				((DOMAttr*) curNode)->setValue(newContent->str());
			}
			break;
		case DOMNode::COMMENT_NODE:
			if (newContent) {
				((DOMComment*) curNode)->setData(newContent->str());
			}
			break;
		default:
			if (newContent) {
				curNode->setNodeValue(newContent->str());
			}
			break;
		};
		if (newContent)
			delete newContent;
	}
	break;
	case XmlModify::InsertBefore:
	{
		parentNode = curNode->getParentNode();
		if (parentNode->getNodeType() == DOMNode::DOCUMENT_NODE) {
			throw XmlException(XmlException::INVALID_VALUE,
					   "modifyDocument: Cannot create a sibling to the document root");
		}
		if (modify.getType() == XmlModify::Attribute) {
			insertAttribute(parentNode, modify);
		} else {
			// insert as sibling, before current
			if (!domContent) {
				clone = false;
				domContent = createContent(curNode->getOwnerDocument(), modify);
			}
			targ = (clone ? domContent->cloneNode(true) : domContent);
			parentNode->insertBefore(targ, curNode);
		}
	}
	break;
	case XmlModify::InsertAfter:
	{
		parentNode = curNode->getParentNode();
		if (parentNode->getNodeType() == DOMNode::DOCUMENT_NODE) {
			throw XmlException(XmlException::INVALID_VALUE,
					   "modifyDocument: Cannot create a sibling to the document root");
		}
		if (modify.getType() == XmlModify::Attribute) {
			insertAttribute(parentNode, modify);
		} else {
			// insert as sibling, after current, must emulate, no DOM method
			if (!domContent) {
				clone = false;
				domContent = createContent(curNode->getOwnerDocument(), modify);
			}
			DOMNode *next = curNode->getNextSibling();
			targ = (clone ? domContent->cloneNode(true) : domContent);
			if (next)
				parentNode->insertBefore(targ, next);
			else
				parentNode->appendChild(targ);
		}
	}
	break;
	case XmlModify::Append:
	{
		if (curNode->getNodeType() != DOMNode::ELEMENT_NODE)
			throw XmlException(XmlException::INVALID_VALUE,
					   "modifyDocument: Append operation is only valid for Element targets");
		if (modify.getType() == XmlModify::Attribute) {
			insertAttribute(curNode, modify);
		} else {
			// insert as child node, requires location argument
			if (!domContent) {
				clone = false;
				domContent = createContent(curNode->getOwnerDocument(), modify);
			}
			DOMNodeList *children = curNode->getChildNodes();
			targ = (clone ? domContent->cloneNode(true) : domContent);
			if (modify.getLocation() == -1 || modify.getLocation() >= (int)children->getLength())
				curNode->appendChild(targ);
			else
				curNode->insertBefore(targ, children->item(modify.getLocation()));
		}
	}
	break;
	case XmlModify::Rename:
	{
		if (!modify.getName())
			throw XmlException(XmlException::INVALID_VALUE,
					   "modifyDocument: rename requires name");
		UTF8ToXMLCh newName(*modify.getName());
		// rename element, attribute or PI (only things with names)
		// gmf: Xerces has experimental support for rename of elements and attrs,
		// so it is used.  Be prepared to reimplement if it changes.
		if ((curNode->getNodeType() == DOMNode::ATTRIBUTE_NODE) ||
		    (curNode->getNodeType() == DOMNode::ELEMENT_NODE)) {
			curNode->getOwnerDocument()->renameNode(curNode,
								curNode->getNamespaceURI(),
								newName.str());
		} else if (curNode->getNodeType() == DOMNode::PROCESSING_INSTRUCTION_NODE) {
			DOMProcessingInstruction *newNode =
				curNode->getOwnerDocument()->createProcessingInstruction(newName.str(), curNode->getNodeValue());
			curNode->getParentNode()->replaceChild(newNode, curNode);
		} else {
			throw XmlException(XmlException::INVALID_VALUE,
					   "modifyDocument: can only rename elements, attributes, and ProcessingInstructionss");
		}
	}
	break;
	};
	modify.incrOps();
}

// create DOM content for node creation (insert*, append)
// GMF: deal with Namespaces in attr creation
DOMNode * Document::createContent(DOMDocument *doc, const Modify &modify) const
{
	const std::string *name = modify.getName();
	const std::string *content = modify.getContent();
	DOMNode *newNode = NULL;
	switch (modify.getType()) {
	case XmlModify::Attribute: // gmf: this case is not used for now
	{
		if (!name || !content)
			throw XmlException(XmlException::INVALID_VALUE,
					   "modifyDocument: attribute creation requires name and value");
		UTF8ToXMLCh newName(*name);
		UTF8ToXMLCh newContent(*content);
		newNode = doc->createAttribute(newName.str());
		if (newNode)
			newNode->setNodeValue(newContent.str());
	}
	break;
	case XmlModify::Element:
	{
		if (!name && !content)
			throw XmlException(XmlException::INVALID_VALUE,
					   "modifyDocument: element creation requires name or content");
		// look for complex (elements in) text first (gmf: is strchr ok?)
		if (content && strchr(content->c_str(), '<')) {
			// will create either a single node, or document fragment to
			// contain children.  Either way, insertion will work correctly
			newNode = createComplexContent(doc, name, content);
		} else {
			if (!name) {
				throw XmlException(XmlException::INVALID_VALUE,
						   "modifyDocument: element creation with simple text requires name");
			}
			UTF8ToXMLCh newName(*name);
			newNode = doc->createElement(newName.str());
			if (newNode && content) {
				UTF8ToXMLCh newContent(*content);
				newNode->appendChild(doc->createTextNode(newContent.str()));
			}
		}
	}
	break;
	case XmlModify::Text:
	{
		if (!content)
			throw XmlException(XmlException::INVALID_VALUE,
					   "modifyDocument: text node creation requires content");
		UTF8ToXMLCh newContent(*content);
		newNode = doc->createTextNode(newContent.str());
	}
	break;
	case XmlModify::ProcessingInstruction:
	{
		if (!name || !content)
			throw XmlException(XmlException::INVALID_VALUE,
					   "modifyDocument: Processing Instruction creation requires name and value");
		UTF8ToXMLCh newName(*name);
		UTF8ToXMLCh newContent(*content);
		newNode = doc->createProcessingInstruction(newName.str(), newContent.str());
	}
	break;
	case XmlModify::Comment:
	{
		if (!content)
			throw XmlException(XmlException::INVALID_VALUE,
					   "modifyDocument: comment node creation requires content");
		UTF8ToXMLCh newContent(*content);
		newNode = doc->createComment(newContent.str());
	}
	break;
	default:
		throw XmlException(XmlException::INVALID_VALUE,
				   "modifyDocument: can only create content for elements, attributes, text, comments and ProcessingInstructions");
	};
	return newNode;
}

// content contains elements and must be parsed.
// name may be null.  In both cases, use a document fragment
// to contain the new DOM (gmf: does fragment node get properly deleted?)
DOMNode * Document::createComplexContent(DOMDocument *doc, const std::string *name, const std::string *content) const
{
	DOMNode *target = NULL;  // target for content
	DOMDocumentFragment *frag = doc->createDocumentFragment();
	if (name) {
		UTF8ToXMLCh newName(*name);
		target = doc->createElement(newName.str());
		frag->appendChild(target);
	} else {
		target = frag;
	}
	// construct a fake document for parser
	std::string newContent = std::string("<fake>") + *content + std::string("</fake>");
	XercesDOMParser xmlParser;
	parseDoc(xmlParser, (XMLByte*) newContent.data(), newContent.length(), "fake");
	// don't adopt the document; child nodes will be relocated.  root is <fake>
	DOMElement *root = xmlParser.getDocument()->getDocumentElement();
	DOMNode *current = root->getFirstChild();
	while (current) {
		target->appendChild(doc->importNode(current, true));
		current = current->getNextSibling();
	}
	return frag;
}

void Document::setContentFromDOM(const std::string *newEncoding)
{
	DOMWriter *writer = document_->getImplementation()->createDOMWriter();
	MemBufFormatTarget *myFormatTarget = new MemBufFormatTarget();
	if (newEncoding) {
		UTF8ToXMLCh enc(*newEncoding);
		writer->setEncoding(enc.str());  // copies the memory
	}
	try {
		writer->writeNode(myFormatTarget, *document_);
		// replace content.  code is from ::setContent(), above
		DbtOut *dbt = new DbtOut(myFormatTarget->getRawBuffer(), myFormatTarget->getLen());
		if (content_.getDbt() == 0)
			metaData_.push_back(&content_);
		content_.setDbt(&dbt); // Note: Consumes Dbt.
	} catch (...) {
		delete writer;
		delete myFormatTarget;
		throw;
	}
	delete writer;
	delete myFormatTarget;
}


