text: /*
  * Copyright 2006 Sony Computer Entertainment Inc.
  *
  * Licensed under the MIT Open Source License, for details please see license.txt or the website
  * http://www.opensource.org/licenses/mit-license.php
  *
  */ 
  
  // The user can choose whether or not to include libxml support in the DOM. Supporting libxml will
  // require linking against it. By default libxml support is included.
  #if defined(DOM_INCLUDE_LIBXML)
  
  // This is a rework of the XML plugin that contains a complete interface to libxml2 "readXML"
  // This is intended to be a seperate plugin but I'm starting out by rewriting it in daeLIBXMLPlugin
  // because I'm not sure if all the plugin handling stuff has been tested.  Once I get a working
  // plugin I'll look into renaming it so the old daeLIBXMLPlugin can coexist with it.
  //
  #include <string>
  #include <sstream>
  #include <modules/daeLIBXMLPlugin.h>
  #include <dae.h>
  #include <dom.h>
  #include <dae/daeDatabase.h>
  #include <dae/daeMetaElement.h>
  #include <libxml/xmlreader.h>
  #include <libxml/xmlwriter.h>
  #include <libxml/xmlmemory.h>
  #include <dae/daeErrorHandler.h>
  #include <dae/daeMetaElementAttribute.h>
  
  using namespace std;
  
  
  // Some helper functions for working with libxml
  namespace {
  	daeInt getCurrentLineNumber(xmlTextReaderPtr reader) {
  #if LIBXML_VERSION >= 20620
  	return xmlTextReaderGetParserLineNumber(reader);
  #else
  	return -1;
  #endif
  	}
  
  	// Return value should be freed by caller with delete[]. Passed in value should not
  	// be null.
  	xmlChar* utf8ToLatin1(const xmlChar* utf8) {
  		int inLen = xmlStrlen(utf8);
  		int outLen = (inLen+1) * 2;
  		xmlChar* latin1 = new xmlChar[outLen];
  		int numBytes = UTF8Toisolat1(latin1, &outLen, utf8, &inLen);
  		if (numBytes < 0)
  			// Failed. Return an empty string instead.
  			numBytes = 0;
  
  		latin1[numBytes] = '\0';
  		return latin1;
  	}
  
  	// Return value should be freed by caller with delete[].
  	xmlChar* latin1ToUtf8(const string& latin1) {
  		int inLen = (int)latin1.length();
  		int outLen = (inLen+1) * 2;
  		xmlChar* utf8 = new xmlChar[outLen];
  		int numBytes = isolat1ToUTF8(utf8, &outLen, (xmlChar*)latin1.c_str(), &inLen);
  		if (numBytes < 0)
  			// Failed. Return an empty string instead.
  			numBytes = 0;
  
  		utf8[numBytes] = '\0';
  		return utf8;
  	}
  
  	typedef pair<daeString, daeString> stringPair;
  	
  	// The attributes vector passed in should be empty. If 'encoding' is anything
  	// other than utf8 the caller should free the returned attribute value
  	// strings. The 'freeAttrValues' function is provided for that purpose.
  	void packageCurrentAttributes(xmlTextReaderPtr reader,
  	                              DAE::charEncoding encoding,
  	                              /* out */ vector<stringPair>& attributes) {
  		int numAttributes = xmlTextReaderAttributeCount(reader);
  		if (numAttributes == -1 || numAttributes == 0)
  			return;
  		attributes.reserve(numAttributes);
  		
  		while (xmlTextReaderMoveToNextAttribute(reader) == 1) {
  			const xmlChar* xmlName = xmlTextReaderConstName(reader);
  			const xmlChar* xmlValue = xmlTextReaderConstValue(reader);
  			if (encoding == DAE::Latin1)
  				attributes.push_back(stringPair((daeString)xmlName, (daeString)utf8ToLatin1(xmlValue)));
  			else
  				attributes.push_back(stringPair((daeString)xmlName, (daeString)xmlValue));
  		}
  	}
  
  	void freeAttrValues(vector<stringPair>& pairs) {
  		for(size_t i=0, size=pairs.size(); i<size; ++i) {
  			delete[] pairs[i].second;
  			pairs[i].second = 0;
  		}
  	}
  }
  
  daeLIBXMLPlugin::daeLIBXMLPlugin(DAE& dae) : dae(dae), rawRelPath(dae)
  {
  	supportedProtocols.push_back("*");
  	xmlInitParser();
  	rawFile = NULL;
  	rawByteCount = 0;
  	saveRawFile = false;
  }
  
  daeLIBXMLPlugin::~daeLIBXMLPlugin()
  {
  	 xmlCleanupParser();
  }
  
  daeInt daeLIBXMLPlugin::setOption( daeString option, daeString value )
  {
  	if ( strcmp( option, "saveRawBinary" ) == 0 )
  	{
  		if ( strcmp( value, "true" ) == 0 || strcmp( value, "TRUE" ) == 0 )
  		{
  			saveRawFile = true;
  		}
  		else
  		{
  			saveRawFile = false;
  		}
  		return DAE_OK;
  	}
  	return DAE_ERR_INVALID_CALL;
  }
  
  daeString daeLIBXMLPlugin::getOption( daeString option )
  {
  	if ( strcmp( option, "saveRawBinary" ) == 0 )
  	{
  		if ( saveRawFile )
  		{
  			return "true";
  		}
  		return "false";
  	}
  	return NULL;
  }
  
  namespace {
  	void libxmlErrorHandler(void* arg,
  	                        const char* msg,
  	                        xmlParserSeverities severity,
  	                        xmlTextReaderLocatorPtr locator) {
  		if(severity == XML_PARSER_SEVERITY_VALIDITY_WARNING  ||
  		   severity == XML_PARSER_SEVERITY_WARNING) {
  			daeErrorHandler::get()->handleWarning(msg);
  		}
  		else
  			daeErrorHandler::get()->handleError(msg);
  	}
  }
  
  // A simple structure to help alloc/free xmlTextReader objects
  struct xmlTextReaderHelper {
  	xmlTextReaderHelper(const daeURI& uri) {
  		if((reader = xmlReaderForFile(cdom::fixUriForLibxml(uri.str()).c_str(), NULL, 0)))
  		   xmlTextReaderSetErrorHandler(reader, libxmlErrorHandler, NULL);
  	}
  
  	xmlTextReaderHelper(daeString buffer, const daeURI& baseUri) {
  		if((reader = xmlReaderForDoc((xmlChar*)buffer, cdom::fixUriForLibxml(baseUri.str()).c_str(), NULL, 0)))
  			xmlTextReaderSetErrorHandler(reader, libxmlErrorHandler, NULL);
  	};
  
  	~xmlTextReaderHelper() {
  		if (reader)
  			xmlFreeTextReader(reader);
  	}
  
  	xmlTextReaderPtr reader;
  };
  
  daeElementRef daeLIBXMLPlugin::readFromFile(const daeURI& uri) {
  	xmlTextReaderHelper readerHelper(uri);
  	if (!readerHelper.reader) {
  		daeErrorHandler::get()->handleError((string("Failed to open ") + uri.str() +
  		                                    " in daeLIBXMLPlugin::readFromFile\n").c_str());
  		return NULL;
  	}
  	return read(readerHelper.reader);
  }
  
  daeElementRef daeLIBXMLPlugin::readFromMemory(daeString buffer, const daeURI& baseUri) {
  	xmlTextReaderHelper readerHelper(buffer, baseUri);
  	if (!readerHelper.reader) {
  		daeErrorHandler::get()->handleError("Failed to open XML document from memory buffer in "
  		                                    "daeLIBXMLPlugin::readFromMemory\n");
  		return NULL;
  	}
  	return read(readerHelper.reader);
  }
  
  daeElementRef daeLIBXMLPlugin::read(_xmlTextReader* reader) {
  	// Drop everything up to the first element. In the future, we should try to store header comments somewhere.
  	while(xmlTextReaderNodeType(reader) != XML_READER_TYPE_ELEMENT)
  	{
  		if (xmlTextReaderRead(reader) != 1) {
  			daeErrorHandler::get()->handleError("Error parsing XML in daeLIBXMLPlugin::read\n");
  			return NULL;
  		}
  	}
  
  	int readRetVal = 0;
  	return readElement(reader, NULL, readRetVal);
  }
  
  daeElementRef daeLIBXMLPlugin::readElement(_xmlTextReader* reader,
                                             daeElement* parentElement,
                                             /* out */ int& readRetVal) {
  	assert(xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT);
  	daeString elementName = (daeString)xmlTextReaderConstName(reader);
  	bool empty = xmlTextReaderIsEmptyElement(reader) != 0;
  
  	vector<attrPair> attributes;
  	packageCurrentAttributes(reader, dae.getCharEncoding(), /* out */ attributes);
  	
  	daeElementRef element = beginReadElement(parentElement, elementName, attributes, getCurrentLineNumber(reader));
  	if (dae.getCharEncoding() != DAE::Utf8)
  		freeAttrValues(attributes);
  
  	if (!element) {
  		// We couldn't create the element. beginReadElement already printed an error message. Just make sure
  		// to skip ahead past the bad element.
  		xmlTextReaderNext(reader);
  		return NULL;
  	}
  
  	if ((readRetVal = xmlTextReaderRead(reader)) == -1)
  		return NULL;
  	if (empty)
  		return element;
  
  	int nodeType = xmlTextReaderNodeType(reader);
  	while (readRetVal == 1  &&  nodeType != XML_READER_TYPE_END_ELEMENT) {
  		if (nodeType == XML_READER_TYPE_ELEMENT) {
  			element->placeElement(readElement(reader, element, readRetVal));
  		}
  		else if (nodeType == XML_READER_TYPE_TEXT) {
  			const xmlChar* xmlText = xmlTextReaderConstValue(reader);
  			if (dae.getCharEncoding() == DAE::Latin1)
  				xmlText = utf8ToLatin1(xmlText);
  			readElementText(element, (daeString)xmlText, getCurrentLineNumber(reader));
  			if (dae.getCharEncoding() == DAE::Latin1)
  				delete[] xmlText;
  
  			readRetVal = xmlTextReaderRead(reader);
  		}
  		else
  			readRetVal = xmlTextReaderRead(reader);
  
  		nodeType = xmlTextReaderNodeType(reader);
  	}
  
  	if (nodeType == XML_READER_TYPE_END_ELEMENT)
  		readRetVal = xmlTextReaderRead(reader);
  
  	if (readRetVal == -1) // Something went wrong (bad xml probably)
  		return NULL;
  
  	return element;
  }
  
  daeInt daeLIBXMLPlugin::write(const daeURI& name, daeDocument *document, daeBool replace)
  {
  	// Make sure database and document are both set
  	if (!database)
  		return DAE_ERR_INVALID_CALL;
  	if(!document)
  		return DAE_ERR_COLLECTION_DOES_NOT_EXIST;
  
  	// Convert the URI to a file path, to see if we're about to overwrite a file
  	string file = cdom::uriToNativePath(name.str());
  	if (file.empty()  &&  saveRawFile)
  	{
  		daeErrorHandler::get()->handleError( "can't get path in write\n" );
  		return DAE_ERR_BACKEND_IO;
  	}
  	
  	// If replace=false, don't replace existing files
  	if(!replace)
  	{
  		// Using "stat" would be better, but it's not available on all platforms
  		FILE *tempfd = fopen(file.c_str(), "r");
  		if(tempfd != NULL)
  		{
  			// File exists, return error
  			fclose(tempfd);
  			return DAE_ERR_BACKEND_FILE_EXISTS;
  		}
  		fclose(tempfd);
  	}
  	if ( saveRawFile )
  	{
  		string rawFilePath = file + ".raw";
  		if ( !replace )
  		{
  			rawFile = fopen(rawFilePath.c_str(), "rb" );
  			if ( rawFile != NULL )
  			{
  				fclose(rawFile);
  				return DAE_ERR_BACKEND_FILE_EXISTS;
  			}
  			fclose(rawFile);
  		}
  		rawFile = fopen(rawFilePath.c_str(), "wb");
  		if ( rawFile == NULL )
  		{
  			return DAE_ERR_BACKEND_IO;
  		}
  		rawRelPath.set(cdom::nativePathToUri(rawFilePath));
  		rawRelPath.makeRelativeTo( &name );
  	}
  
  	// Open the file we will write to
  	writer = xmlNewTextWriterFilename(cdom::fixUriForLibxml(name.str()).c_str(), 0);
  	if ( !writer ) {
  		ostringstream msg;
  		msg << "daeLIBXMLPlugin::write(" << name.str() << ") failed\n";
  		daeErrorHandler::get()->handleError(msg.str().c_str());
  		return DAE_ERR_BACKEND_IO;
  	}
  	xmlTextWriterSetIndentString( writer, (const xmlChar*)"\t" ); // Don't change this to spaces
  	xmlTextWriterSetIndent( writer, 1 ); // Turns indentation on
      xmlTextWriterStartDocument( writer, "1.0", "UTF-8", NULL );
  	
  	writeElement( document->getDomRoot() );
  	
  	xmlTextWriterEndDocument( writer );
  	xmlTextWriterFlush( writer );
  	xmlFreeTextWriter( writer );
  
  	if ( saveRawFile && rawFile != NULL )
  	{
  		fclose( rawFile );
  	}
  
  	return DAE_OK;
  }
  
  void daeLIBXMLPlugin::writeElement( daeElement* element )
  {
  	daeMetaElement* _meta = element->getMeta();
  
  	//intercept <source> elements for special handling
  	if ( saveRawFile )
  	{
  		if ( strcmp( element->getTypeName(), "source" ) == 0 )
  		{
  			daeElementRefArray children;
  			element->getChildren( children );
  			bool validArray = false, teqCommon = false;
  			for ( unsigned int i = 0; i < children.getCount(); i++ )
  			{
  				if ( strcmp( children[i]->getTypeName(), "float_array" ) == 0 || 
  					 strcmp( children[i]->getTypeName(), "int_array" ) == 0 )
  				{
  					validArray = true;
  				}
  				else if ( strcmp( children[i]->getTypeName(), "technique_common" ) == 0 )
  				{
  					teqCommon = true;
  				}
  			}
  			if ( validArray && teqCommon )
  			{
  				writeRawSource( element );
  				return;
  			}
  		}
  	}
  
  	if (!_meta->getIsTransparent() ) {
  		xmlTextWriterStartElement(writer, (xmlChar*)element->getElementName());
  		daeMetaAttributeRefArray& attrs = _meta->getMetaAttributes();
  		
  		int acnt = (int)attrs.getCount();
  		
  		for(int i=0;i<acnt;i++) {
  			writeAttribute(attrs[i], element);
  		}
  	}
  	writeValue(element);
  	
  	daeElementRefArray children;
  	element->getChildren( children );
  	for ( size_t x = 0; x < children.getCount(); x++ ) {
  		writeElement( children.get(x) );
  	}
  
  	/*if (_meta->getContents() != NULL) {
  		daeElementRefArray* era = (daeElementRefArray*)_meta->getContents()->getWritableMemory(element);
  		int elemCnt = (int)era->getCount();
  		for(int i = 0; i < elemCnt; i++) {
  			daeElementRef elem = (daeElementRef)era->get(i);
  			if (elem != NULL) {
  				writeElement( elem );
  			}
  		}
  	}
  	else
  	{
  		daeMetaElementAttributeArray& children = _meta->getMetaElements();
  		int cnt = (int)children.getCount();
  		for(int i=0;i<cnt;i++) {
  			daeMetaElement *type = children[i]->getElementType();
  			if ( !type->getIsAbstract() ) {
  				for (int c = 0; c < children[i]->getCount(element); c++ ) {
  					writeElement( *(daeElementRef*)children[i]->get(element,c) );
  				}
  			}
  		}
  	}*/
  	if (!_meta->getIsTransparent() ) {
  		xmlTextWriterEndElement(writer);
  	}
  }
  
  void daeLIBXMLPlugin::writeAttribute( daeMetaAttribute* attr, daeElement* element)
  {
  	ostringstream buffer;
  	attr->memoryToString(element, buffer);
  	string str = buffer.str();
  
  	// Don't write the attribute if
  	//  - The attribute isn't required AND
  	//     - The attribute has no default value and the current value is ""
  	//     - The attribute has a default value and the current value matches the default
  	if (!attr->getIsRequired()) {
  		if(!attr->getDefaultValue()  &&  str.empty())
  			return;
  		if(attr->getDefaultValue()  &&  attr->compareToDefault(element) == 0)
  			return;
  	}
  
  	xmlTextWriterStartAttribute(writer, (xmlChar*)(daeString)attr->getName());
  	xmlChar* utf8 = (xmlChar*)str.c_str();
  	if (dae.getCharEncoding() == DAE::Latin1)
  		utf8 = latin1ToUtf8(str);
  	xmlTextWriterWriteString(writer, utf8);
  	if (dae.getCharEncoding() == DAE::Latin1)
  		delete[] utf8;
  	
  	xmlTextWriterEndAttribute(writer);
  }
  
  void daeLIBXMLPlugin::writeValue(daeElement* element) {
  	if (daeMetaAttribute* attr = element->getMeta()->getValueAttribute()) {
  		ostringstream buffer;
  		attr->memoryToString(element, buffer);
  		string s = buffer.str();
  		if (!s.empty()) {
  			xmlChar* str = (xmlChar*)s.c_str();
  			if (dae.getCharEncoding() == DAE::Latin1)
  				str = latin1ToUtf8(s);
  			xmlTextWriterWriteString(writer, (xmlChar*)s.c_str());
  			if (dae.getCharEncoding() == DAE::Latin1)
  				delete[] str;
  		}
  	}
  }
  
  void daeLIBXMLPlugin::writeRawSource( daeElement *src )
  {
  	daeElementRef newSrc = src->clone();
  	daeElementRef array = NULL;
  	daeElement *accessor = NULL;
  	daeElementRefArray children;
  	newSrc->getChildren( children );
  	bool isInt = false;
  	for ( int i = 0; i < (int)children.getCount(); i++ )
  	{
  		if ( strcmp( children[i]->getTypeName(), "float_array" ) == 0 )
  		{
  			array = children[i];
  			newSrc->removeChildElement( array );
  		}
  		else if ( strcmp( children[i]->getTypeName(), "int_array" ) == 0 )
  		{
  			array = children[i];
  			isInt = true;
  			newSrc->removeChildElement( array );
  		}
  		else if ( strcmp( children[i]->getTypeName(), "technique_common" ) == 0 )
  		{
  			children[i]->getChildren( children );
  		}
  		else if ( strcmp( children[i]->getTypeName(), "accessor" ) == 0 )
  		{
  			accessor = children[i];
  		}
  	}
  
  	daeULong *countPtr = (daeULong*)array->getAttributeValue( "count" );
  	daeULong count = countPtr != NULL ? *countPtr : 0;
  
  	daeULong *stridePtr = (daeULong*)accessor->getAttributeValue( "stride" );
  	daeULong stride = stridePtr != NULL ? *stridePtr : 1;
  
  	children.clear();
  	accessor->getChildren( children );
  	if ( children.getCount() > stride ) {
  		*stridePtr = children.getCount();
  	}
  
  	daeFixedName newURI;
  	sprintf( newURI, "%s#%ld", rawRelPath.getOriginalURI(), rawByteCount );
  	accessor->setAttribute( "source", newURI );
  
  	daeArray *valArray = (daeArray*)array->getValuePointer();
  
  	//TODO: pay attention to precision for the array.
  	if ( isInt )
  	{
  		for( size_t i = 0; i < count; i++ )
  		{
  			daeInt tmp = (daeInt)*(daeLong*)(valArray->getRaw(i));
  			rawByteCount += (unsigned long)(fwrite( &tmp, sizeof(daeInt), 1, rawFile ) * sizeof(daeInt));
  		}
  	}
  	else
  	{
  		for( size_t i = 0; i < count; i++ )
  		{
  			daeFloat tmp = (daeFloat)*(daeDouble*)(valArray->getRaw(i));
  			rawByteCount += (unsigned long)(fwrite( &tmp, sizeof(daeFloat), 1, rawFile ) * sizeof(daeFloat));
  		}
  	}
  
  	writeElement( newSrc );
  }
  
  #endif // DOM_INCLUDE_LIBXML
  