// =================================================================================================
// Copyright 2007 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE:  Adobe permits you to use, modify, and distribute this file in accordance with the terms
// of the Adobe license agreement accompanying it.
// =================================================================================================

#include "public/include/XMP_Environment.h"	// ! Must be the first #include!
#include "source/XMLParserAdapter.hpp"

#include <map>
#include <cstring>
#include <cstdio>

// ! Can't include XMP..._Impl.hpp - used by both Core and Files.
#define XMP_LitNMatch(s,l,n)	(std::strncmp((s),(l),(n)) == 0)

#if XMP_WinBuild
	#define snprintf _snprintf
	#pragma warning ( disable : 4996 )	// snprintf is safe
#endif

// =================================================================================================

#if 0	// Pattern for iterating over the children or attributes:
	for ( size_t xxNum = 0, xxLim = _node_->_offspring_->size(); xxNum < xxLim; ++xxNum ) {
		const XML_NodePtr _curr_ = _node_->_offspring_[xxNum];
	}
#endif

// =================================================================================================
// XML_Node::IsWhitespaceNode
//===========================

bool XML_Node::IsWhitespaceNode() const
{
	if ( this->kind != kCDataNode ) return false;

	for ( size_t i = 0; i < this->value.size(); ++i ) {
		unsigned char ch = this->value[i];
		if ( IsWhitespaceChar ( ch ) ) continue;
		// *** Add checks for other whitespace characters.
		return false;	// All the checks failed, this isn't whitespace.
	}

	return true;

}	// XML_Node::IsWhitespaceNode

// =================================================================================================
// XML_Node::IsLeafContentNode
//============================

bool XML_Node::IsLeafContentNode() const
{
	if ( this->kind != kElemNode ) return false;
	if ( this->content.size() == 0 ) return true;
	if ( this->content.size() > 1 ) return false;
	if ( this->content[0]->kind != kCDataNode ) return false;

	return true;

}	// XML_Node::IsLeafContentNode

// =================================================================================================
// XML_Node::IsEmptyLeafNode
//==========================

bool XML_Node::IsEmptyLeafNode() const
{

	if ( (this->kind != kElemNode) || (this->content.size() != 0) ) return false;
	return true;

}	// XML_Node::IsEmptyLeafNode

// =================================================================================================
// XML_Node::GetAttrValue
//=======================

XMP_StringPtr XML_Node::GetAttrValue ( XMP_StringPtr attrName ) const
{

	for ( size_t i = 0, aLim = this->attrs.size(); i < aLim; ++i ) {
		XML_Node * attrPtr = this->attrs[i];
		if ( ! attrPtr->ns.empty() ) continue;	// This form of GetAttrValue is for attrs in no namespace.
		if ( attrPtr->name == attrName ) return attrPtr->value.c_str();
	}

	return 0;	// Not found.

}	// XML_Node::GetAttrValue

// =================================================================================================
// XML_Node::SetAttrValue
//=======================

void XML_Node::SetAttrValue ( XMP_StringPtr attrName, XMP_StringPtr attrValue )
{

	for ( size_t i = 0, aLim = this->attrs.size(); i < aLim; ++i ) {
		XML_Node * attrPtr = this->attrs[i];
		if ( ! attrPtr->ns.empty() ) continue;	// This form of SetAttrValue is for attrs in no namespace.
		if ( attrPtr->name == attrName ) {
			attrPtr->value = attrValue;
			return;
		}
	}

}	// XML_Node::SetAttrValue

// =================================================================================================
// XML_Node::GetLeafContentValue
//==============================

XMP_StringPtr XML_Node::GetLeafContentValue() const
{
	if ( (! this->IsLeafContentNode()) || this->content.empty() ) return "";

	return this->content[0]->value.c_str();

}	// XML_Node::GetLeafContentValue

// =================================================================================================
// XML_Node::GetLeafContentValue
//==============================

std::string* XML_Node::GetLeafContentPtr() const
{
	if ( (! this->IsLeafContentNode()) || this->content.empty() ) return 0;

	return &this->content[0]->value;

}	// XML_Node::GetLeafContentValue

// =================================================================================================
// XML_Node::SetLeafContentValue
//==============================

void XML_Node::SetLeafContentValue ( XMP_StringPtr newValue )
{
	XML_Node * valueNode;

	if ( ! this->content.empty() ) {
		valueNode = this->content[0];
	} else {
		valueNode = new XML_Node ( this, "", kCDataNode );
		this->content.push_back ( valueNode );
	}

	valueNode->value = newValue;

}	// XML_Node::SetLeafContentValue

// =================================================================================================
// XML_Node::CountNamedElements
//=============================

size_t XML_Node::CountNamedElements ( XMP_StringPtr nsURI, XMP_StringPtr localName ) const
{
	size_t count = 0;

	for ( size_t i = 0, vLim = this->content.size(); i < vLim; ++i ) {
		const XML_Node & child = *this->content[i];
		if ( child.ns != nsURI ) continue;
		if ( strcmp ( localName, child.name.c_str()+child.nsPrefixLen ) != 0 ) continue;
		++count;
	}

	return count;

}	// XML_Node::CountNamedElements

// =================================================================================================
// XML_Node::GetNamedElement
//==========================

XML_NodePtr XML_Node::GetNamedElement ( XMP_StringPtr nsURI, XMP_StringPtr localName, size_t which /* = 0 */ )
{

	for ( size_t i = 0, vLim = this->content.size(); i < vLim; ++i ) {
		XML_Node * childPtr = this->content[i];
		if ( childPtr->ns != nsURI ) continue;
		if ( strcmp ( localName, childPtr->name.c_str()+childPtr->nsPrefixLen ) != 0 ) continue;
		if ( which == 0 ) return childPtr;
		--which;
	}

	return 0;	/// Not found.

}	// XML_Node::GetNamedElement

// =================================================================================================
// DumpNodeList
// ============

static const char * kNodeKinds[] = { "root", "elem", "attr", "cdata", "pi" };

static void DumpNodeList ( std::string * buffer, const XML_NodeVector & list, int indent )
{

	for ( size_t i = 0, limit = list.size(); i < limit; ++i ) {

		const XML_Node * node = list[i];

		for ( int t = indent; t > 0; --t ) *buffer += "  ";
		if ( node->IsWhitespaceNode() ) {
			*buffer += "-- whitespace --\n";
			continue;
		}

		*buffer += node->name;
		*buffer += " - ";
		*buffer += kNodeKinds[node->kind];
		if ( ! node->value.empty() ) {
			*buffer += ", value=\"";
			*buffer += node->value;
			*buffer += "\"";
		}
		if ( ! node->ns.empty() ) {
			*buffer += ", ns=\"";
			*buffer += node->ns;
			*buffer += "\"";
		}
		if ( node->nsPrefixLen != 0 ) {
			*buffer += ", prefixLen=";
			char numBuf [20];
			snprintf ( numBuf, sizeof(numBuf), "%d", (int)node->nsPrefixLen );
			*buffer += numBuf;
		}
		*buffer += "\n";

		if ( ! node->attrs.empty() ) {
			for ( int t = indent+1; t > 0; --t ) *buffer += "  ";
			*buffer += "attrs:\n";
			DumpNodeList ( buffer, node->attrs, indent+2 );
		}

		if ( ! node->content.empty() ) {
			DumpNodeList ( buffer, node->content, indent+1 );
		}

	}

}	// DumpNodeList

// =================================================================================================
// XML_Node::Dump
//===============

void XML_Node::Dump ( std::string * buffer )
{

	*buffer = "Dump of XML_Node tree\n";

	*buffer += "Root info: name=\"";
	*buffer += this->name;
	*buffer += "\", value=\"";
	*buffer += this->value;
	*buffer += "\", ns=\"";
	*buffer += this->ns;
	*buffer += "\", kind=";
	*buffer += kNodeKinds[this->kind];
	*buffer += "\n";

	if ( ! this->attrs.empty() ) {
		*buffer += "  attrs:\n";
		DumpNodeList ( buffer, this->attrs, 2 );
	}
	*buffer += "\n";

	DumpNodeList ( buffer, this->content, 0 );

}	// XML_Node::Dump

// =================================================================================================
// SerializeOneNode
// ================

static void SerializeOneNode ( std::string * buffer, const XML_Node & node )
{
	size_t i, limit;
	XMP_StringPtr namePtr = node.name.c_str();
	if ( XMP_LitNMatch ( namePtr, "_dflt_:", 7 ) ) namePtr += 7;	// Hack for default namespaces.

	switch ( node.kind ) {

		case kElemNode:
			*buffer += '<';
			*buffer += namePtr;
			for ( i = 0, limit = node.attrs.size(); i < limit; ++i ) {
				SerializeOneNode ( buffer, *node.attrs[i] );
			}
			if ( node.content.empty() ) {
				*buffer += "/>";
			} else {
				*buffer += '>';
				for ( i = 0, limit = node.content.size(); i < limit; ++i ) {
					SerializeOneNode ( buffer, *node.content[i] );
				}
				*buffer += "</";
				*buffer += namePtr;
				*buffer += '>';
			}
			break;

		case kAttrNode:
			*buffer += ' ';
			*buffer += namePtr;
			*buffer += "=\"";
			*buffer += node.value;
			*buffer += '"';
			break;

		case kCDataNode:
			*buffer += node.value;
			break;

		case kPINode:
			*buffer += node.value;	// *** Note that we're dropping PIs during the Expat parse.
			break;

	}

}	// SerializeOneNode

// =================================================================================================
// CollectNamespaceDecls
// =====================

typedef std::map < std::string, std::string > NamespaceMap;

static void CollectNamespaceDecls ( NamespaceMap * nsMap, const XML_Node & node )
{
	size_t i, limit;

	if ( ! node.ns.empty() ) {
		size_t nameMid = 0;
		while ( node.name[nameMid] != ':' ) ++nameMid;
		std::string prefix = node.name.substr ( 0, nameMid );
		(*nsMap)[prefix] = node.ns;
	}

	if ( node.kind == kElemNode ) {

		for ( i = 0, limit = node.attrs.size(); i < limit; ++i ) {
			CollectNamespaceDecls ( nsMap, *node.attrs[i] );
		}

		for ( i = 0, limit = node.content.size(); i < limit; ++i ) {
			const XML_Node & content = *node.content[i];
			if ( content.kind == kElemNode ) CollectNamespaceDecls ( nsMap, content );
		}

	}

}	// CollectNamespaceDecls

// =================================================================================================
// XML_Node::Serialize
//====================

void XML_Node::Serialize ( std::string * buffer )
{
	buffer->erase();

	if ( this->kind != kRootNode ) {

		SerializeOneNode ( buffer, *this );

	} else {

		// Do the outermost level here, in order to add the XML version and namespace declarations.

		*buffer += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";

		for ( size_t outer = 0, oLimit = this->content.size(); outer < oLimit; ++outer ) {

			const XML_Node & node = *this->content[outer];

			if ( node.kind != kElemNode ) {

				SerializeOneNode ( buffer, node );

			} else {

				XMP_StringPtr namePtr = node.name.c_str();
				if ( XMP_LitNMatch ( namePtr, "_dflt_:", 7 ) ) namePtr += 7;	// Hack for default namespaces.

				*buffer += '<';
				*buffer += namePtr;

				NamespaceMap nsMap;
				CollectNamespaceDecls ( &nsMap, node );
				NamespaceMap::iterator nsDecl = nsMap.begin();
				NamespaceMap::iterator nsEnd  = nsMap.end();
				for ( ; nsDecl != nsEnd; ++nsDecl ) {
					const std::string & prefix = nsDecl->first;
					*buffer += " xmlns";
					if ( prefix != "_dflt_" ) { *buffer += ':'; *buffer += prefix; }
					*buffer += "=\"";
					*buffer += nsDecl->second;
					*buffer += '"';
				}

				for ( size_t attr = 0, aLimit = node.attrs.size(); attr < aLimit; ++attr ) {
					SerializeOneNode ( buffer, *node.attrs[attr] );
				}

				if ( node.content.empty() ) {
					*buffer += "/>";
				} else {
					*buffer += '>';
					for ( size_t child = 0, cLimit = node.content.size(); child < cLimit; ++child ) {
						SerializeOneNode ( buffer, *node.content[child] );
					}
					*buffer += "</";
					*buffer += namePtr;
					*buffer += '>';
				}

			}

		}

	}


}	// XML_Node::Serialize

// =================================================================================================
// XML_Node::RemoveAttrs
//======================

void XML_Node::RemoveAttrs()
{

	for ( size_t i = 0, vLim = this->attrs.size(); i < vLim; ++i ) delete this->attrs[i];
	this->attrs.clear();

}	// XML_Node::RemoveAttrs

// =================================================================================================
// XML_Node::RemoveContent
//========================

void XML_Node::RemoveContent()
{

	for ( size_t i = 0, vLim = this->content.size(); i < vLim; ++i ) delete this->content[i];
	this->content.clear();

}	// XML_Node::RemoveContent

// =================================================================================================
// XML_Node::ClearNode
//====================

void XML_Node::ClearNode()
{

	this->kind = 0;
	this->ns.erase();
	this->name.erase();
	this->value.erase();

	this->RemoveAttrs();
	this->RemoveContent();

}	// XML_Node::ClearNode

// =================================================================================================
