Home | History | Annotate | Download | only in LIBXMLPlugin
      1 /*
      2 * Copyright 2006 Sony Computer Entertainment Inc.
      3 *
      4 * Licensed under the MIT Open Source License, for details please see license.txt or the website
      5 * http://www.opensource.org/licenses/mit-license.php
      6 *
      7 */
      8 
      9 // The user can choose whether or not to include libxml support in the DOM. Supporting libxml will
     10 // require linking against it. By default libxml support is included.
     11 #if defined(DOM_INCLUDE_LIBXML)
     12 
     13 // This is a rework of the XML plugin that contains a complete interface to libxml2 "readXML"
     14 // This is intended to be a seperate plugin but I'm starting out by rewriting it in daeLIBXMLPlugin
     15 // because I'm not sure if all the plugin handling stuff has been tested.  Once I get a working
     16 // plugin I'll look into renaming it so the old daeLIBXMLPlugin can coexist with it.
     17 //
     18 #include <string>
     19 #include <sstream>
     20 #include <modules/daeLIBXMLPlugin.h>
     21 #include <dae.h>
     22 #include <dom.h>
     23 #include <dae/daeDatabase.h>
     24 #include <dae/daeMetaElement.h>
     25 #include <libxml/xmlreader.h>
     26 #include <libxml/xmlwriter.h>
     27 #include <libxml/xmlmemory.h>
     28 #include <dae/daeErrorHandler.h>
     29 #include <dae/daeMetaElementAttribute.h>
     30 
     31 using namespace std;
     32 
     33 
     34 // Some helper functions for working with libxml
     35 namespace {
     36 	daeInt getCurrentLineNumber(xmlTextReaderPtr reader) {
     37 #if LIBXML_VERSION >= 20620
     38 	return xmlTextReaderGetParserLineNumber(reader);
     39 #else
     40 	return -1;
     41 #endif
     42 	}
     43 
     44 	// Return value should be freed by caller with delete[]. Passed in value should not
     45 	// be null.
     46 	xmlChar* utf8ToLatin1(const xmlChar* utf8) {
     47 		int inLen = xmlStrlen(utf8);
     48 		int outLen = (inLen+1) * 2;
     49 		xmlChar* latin1 = new xmlChar[outLen];
     50 		int numBytes = UTF8Toisolat1(latin1, &outLen, utf8, &inLen);
     51 		if (numBytes < 0)
     52 			// Failed. Return an empty string instead.
     53 			numBytes = 0;
     54 
     55 		latin1[numBytes] = '\0';
     56 		return latin1;
     57 	}
     58 
     59 	// Return value should be freed by caller with delete[].
     60 	xmlChar* latin1ToUtf8(const string& latin1) {
     61 		int inLen = (int)latin1.length();
     62 		int outLen = (inLen+1) * 2;
     63 		xmlChar* utf8 = new xmlChar[outLen];
     64 		int numBytes = isolat1ToUTF8(utf8, &outLen, (xmlChar*)latin1.c_str(), &inLen);
     65 		if (numBytes < 0)
     66 			// Failed. Return an empty string instead.
     67 			numBytes = 0;
     68 
     69 		utf8[numBytes] = '\0';
     70 		return utf8;
     71 	}
     72 
     73 	typedef pair<daeString, daeString> stringPair;
     74 
     75 	// The attributes vector passed in should be empty. If 'encoding' is anything
     76 	// other than utf8 the caller should free the returned attribute value
     77 	// strings. The 'freeAttrValues' function is provided for that purpose.
     78 	void packageCurrentAttributes(xmlTextReaderPtr reader,
     79 	                              DAE::charEncoding encoding,
     80 	                              /* out */ vector<stringPair>& attributes) {
     81 		int numAttributes = xmlTextReaderAttributeCount(reader);
     82 		if (numAttributes == -1 || numAttributes == 0)
     83 			return;
     84 		attributes.reserve(numAttributes);
     85 
     86 		while (xmlTextReaderMoveToNextAttribute(reader) == 1) {
     87 			const xmlChar* xmlName = xmlTextReaderConstName(reader);
     88 			const xmlChar* xmlValue = xmlTextReaderConstValue(reader);
     89 			if (encoding == DAE::Latin1)
     90 				attributes.push_back(stringPair((daeString)xmlName, (daeString)utf8ToLatin1(xmlValue)));
     91 			else
     92 				attributes.push_back(stringPair((daeString)xmlName, (daeString)xmlValue));
     93 		}
     94 	}
     95 
     96 	void freeAttrValues(vector<stringPair>& pairs) {
     97 		for(size_t i=0, size=pairs.size(); i<size; ++i) {
     98 			delete[] pairs[i].second;
     99 			pairs[i].second = 0;
    100 		}
    101 	}
    102 }
    103 
    104 daeLIBXMLPlugin::daeLIBXMLPlugin(DAE& dae) : dae(dae), rawRelPath(dae)
    105 {
    106 	supportedProtocols.push_back("*");
    107 	xmlInitParser();
    108 	rawFile = NULL;
    109 	rawByteCount = 0;
    110 	saveRawFile = false;
    111 }
    112 
    113 daeLIBXMLPlugin::~daeLIBXMLPlugin()
    114 {
    115 	 xmlCleanupParser();
    116 }
    117 
    118 daeInt daeLIBXMLPlugin::setOption( daeString option, daeString value )
    119 {
    120 	if ( strcmp( option, "saveRawBinary" ) == 0 )
    121 	{
    122 		if ( strcmp( value, "true" ) == 0 || strcmp( value, "TRUE" ) == 0 )
    123 		{
    124 			saveRawFile = true;
    125 		}
    126 		else
    127 		{
    128 			saveRawFile = false;
    129 		}
    130 		return DAE_OK;
    131 	}
    132 	return DAE_ERR_INVALID_CALL;
    133 }
    134 
    135 daeString daeLIBXMLPlugin::getOption( daeString option )
    136 {
    137 	if ( strcmp( option, "saveRawBinary" ) == 0 )
    138 	{
    139 		if ( saveRawFile )
    140 		{
    141 			return "true";
    142 		}
    143 		return "false";
    144 	}
    145 	return NULL;
    146 }
    147 
    148 namespace {
    149 	void libxmlErrorHandler(void* arg,
    150 	                        const char* msg,
    151 	                        xmlParserSeverities severity,
    152 	                        xmlTextReaderLocatorPtr locator) {
    153 		if(severity == XML_PARSER_SEVERITY_VALIDITY_WARNING  ||
    154 		   severity == XML_PARSER_SEVERITY_WARNING) {
    155 			daeErrorHandler::get()->handleWarning(msg);
    156 		}
    157 		else
    158 			daeErrorHandler::get()->handleError(msg);
    159 	}
    160 }
    161 
    162 // A simple structure to help alloc/free xmlTextReader objects
    163 struct xmlTextReaderHelper {
    164 	xmlTextReaderHelper(const daeURI& uri) {
    165 		if((reader = xmlReaderForFile(cdom::fixUriForLibxml(uri.str()).c_str(), NULL, 0)))
    166 		   xmlTextReaderSetErrorHandler(reader, libxmlErrorHandler, NULL);
    167 	}
    168 
    169 	xmlTextReaderHelper(daeString buffer, const daeURI& baseUri) {
    170 		if((reader = xmlReaderForDoc((xmlChar*)buffer, cdom::fixUriForLibxml(baseUri.str()).c_str(), NULL, 0)))
    171 			xmlTextReaderSetErrorHandler(reader, libxmlErrorHandler, NULL);
    172 	};
    173 
    174 	~xmlTextReaderHelper() {
    175 		if (reader)
    176 			xmlFreeTextReader(reader);
    177 	}
    178 
    179 	xmlTextReaderPtr reader;
    180 };
    181 
    182 daeElementRef daeLIBXMLPlugin::readFromFile(const daeURI& uri) {
    183 	xmlTextReaderHelper readerHelper(uri);
    184 	if (!readerHelper.reader) {
    185 		daeErrorHandler::get()->handleError((string("Failed to open ") + uri.str() +
    186 		                                    " in daeLIBXMLPlugin::readFromFile\n").c_str());
    187 		return NULL;
    188 	}
    189 	return read(readerHelper.reader);
    190 }
    191 
    192 daeElementRef daeLIBXMLPlugin::readFromMemory(daeString buffer, const daeURI& baseUri) {
    193 	xmlTextReaderHelper readerHelper(buffer, baseUri);
    194 	if (!readerHelper.reader) {
    195 		daeErrorHandler::get()->handleError("Failed to open XML document from memory buffer in "
    196 		                                    "daeLIBXMLPlugin::readFromMemory\n");
    197 		return NULL;
    198 	}
    199 	return read(readerHelper.reader);
    200 }
    201 
    202 daeElementRef daeLIBXMLPlugin::read(_xmlTextReader* reader) {
    203 	// Drop everything up to the first element. In the future, we should try to store header comments somewhere.
    204 	while(xmlTextReaderNodeType(reader) != XML_READER_TYPE_ELEMENT)
    205 	{
    206 		if (xmlTextReaderRead(reader) != 1) {
    207 			daeErrorHandler::get()->handleError("Error parsing XML in daeLIBXMLPlugin::read\n");
    208 			return NULL;
    209 		}
    210 	}
    211 
    212 	int readRetVal = 0;
    213 	return readElement(reader, NULL, readRetVal);
    214 }
    215 
    216 daeElementRef daeLIBXMLPlugin::readElement(_xmlTextReader* reader,
    217                                            daeElement* parentElement,
    218                                            /* out */ int& readRetVal) {
    219 	assert(xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT);
    220 	daeString elementName = (daeString)xmlTextReaderConstName(reader);
    221 	bool empty = xmlTextReaderIsEmptyElement(reader) != 0;
    222 
    223 	vector<attrPair> attributes;
    224 	packageCurrentAttributes(reader, dae.getCharEncoding(), /* out */ attributes);
    225 
    226 	daeElementRef element = beginReadElement(parentElement, elementName, attributes, getCurrentLineNumber(reader));
    227 	if (dae.getCharEncoding() != DAE::Utf8)
    228 		freeAttrValues(attributes);
    229 
    230 	if (!element) {
    231 		// We couldn't create the element. beginReadElement already printed an error message. Just make sure
    232 		// to skip ahead past the bad element.
    233 		xmlTextReaderNext(reader);
    234 		return NULL;
    235 	}
    236 
    237 	if ((readRetVal = xmlTextReaderRead(reader)) == -1)
    238 		return NULL;
    239 	if (empty)
    240 		return element;
    241 
    242 	int nodeType = xmlTextReaderNodeType(reader);
    243 	while (readRetVal == 1  &&  nodeType != XML_READER_TYPE_END_ELEMENT) {
    244 		if (nodeType == XML_READER_TYPE_ELEMENT) {
    245 			element->placeElement(readElement(reader, element, readRetVal));
    246 		}
    247 		else if (nodeType == XML_READER_TYPE_TEXT) {
    248 			const xmlChar* xmlText = xmlTextReaderConstValue(reader);
    249 			if (dae.getCharEncoding() == DAE::Latin1)
    250 				xmlText = utf8ToLatin1(xmlText);
    251 			readElementText(element, (daeString)xmlText, getCurrentLineNumber(reader));
    252 			if (dae.getCharEncoding() == DAE::Latin1)
    253 				delete[] xmlText;
    254 
    255 			readRetVal = xmlTextReaderRead(reader);
    256 		}
    257 		else
    258 			readRetVal = xmlTextReaderRead(reader);
    259 
    260 		nodeType = xmlTextReaderNodeType(reader);
    261 	}
    262 
    263 	if (nodeType == XML_READER_TYPE_END_ELEMENT)
    264 		readRetVal = xmlTextReaderRead(reader);
    265 
    266 	if (readRetVal == -1) // Something went wrong (bad xml probably)
    267 		return NULL;
    268 
    269 	return element;
    270 }
    271 
    272 daeInt daeLIBXMLPlugin::write(const daeURI& name, daeDocument *document, daeBool replace)
    273 {
    274 	// Make sure database and document are both set
    275 	if (!database)
    276 		return DAE_ERR_INVALID_CALL;
    277 	if(!document)
    278 		return DAE_ERR_COLLECTION_DOES_NOT_EXIST;
    279 
    280 	// Convert the URI to a file path, to see if we're about to overwrite a file
    281 	string file = cdom::uriToNativePath(name.str());
    282 	if (file.empty()  &&  saveRawFile)
    283 	{
    284 		daeErrorHandler::get()->handleError( "can't get path in write\n" );
    285 		return DAE_ERR_BACKEND_IO;
    286 	}
    287 
    288 	// If replace=false, don't replace existing files
    289 	if(!replace)
    290 	{
    291 		// Using "stat" would be better, but it's not available on all platforms
    292 		FILE *tempfd = fopen(file.c_str(), "r");
    293 		if(tempfd != NULL)
    294 		{
    295 			// File exists, return error
    296 			fclose(tempfd);
    297 			return DAE_ERR_BACKEND_FILE_EXISTS;
    298 		}
    299 		fclose(tempfd);
    300 	}
    301 	if ( saveRawFile )
    302 	{
    303 		string rawFilePath = file + ".raw";
    304 		if ( !replace )
    305 		{
    306 			rawFile = fopen(rawFilePath.c_str(), "rb" );
    307 			if ( rawFile != NULL )
    308 			{
    309 				fclose(rawFile);
    310 				return DAE_ERR_BACKEND_FILE_EXISTS;
    311 			}
    312 			fclose(rawFile);
    313 		}
    314 		rawFile = fopen(rawFilePath.c_str(), "wb");
    315 		if ( rawFile == NULL )
    316 		{
    317 			return DAE_ERR_BACKEND_IO;
    318 		}
    319 		rawRelPath.set(cdom::nativePathToUri(rawFilePath));
    320 		rawRelPath.makeRelativeTo( &name );
    321 	}
    322 
    323 	// Open the file we will write to
    324 	writer = xmlNewTextWriterFilename(cdom::fixUriForLibxml(name.str()).c_str(), 0);
    325 	if ( !writer ) {
    326 		ostringstream msg;
    327 		msg << "daeLIBXMLPlugin::write(" << name.str() << ") failed\n";
    328 		daeErrorHandler::get()->handleError(msg.str().c_str());
    329 		return DAE_ERR_BACKEND_IO;
    330 	}
    331 	xmlTextWriterSetIndentString( writer, (const xmlChar*)"\t" ); // Don't change this to spaces
    332 	xmlTextWriterSetIndent( writer, 1 ); // Turns indentation on
    333     xmlTextWriterStartDocument( writer, "1.0", "UTF-8", NULL );
    334 
    335 	writeElement( document->getDomRoot() );
    336 
    337 	xmlTextWriterEndDocument( writer );
    338 	xmlTextWriterFlush( writer );
    339 	xmlFreeTextWriter( writer );
    340 
    341 	if ( saveRawFile && rawFile != NULL )
    342 	{
    343 		fclose( rawFile );
    344 	}
    345 
    346 	return DAE_OK;
    347 }
    348 
    349 void daeLIBXMLPlugin::writeElement( daeElement* element )
    350 {
    351 	daeMetaElement* _meta = element->getMeta();
    352 
    353 	//intercept <source> elements for special handling
    354 	if ( saveRawFile )
    355 	{
    356 		if ( strcmp( element->getTypeName(), "source" ) == 0 )
    357 		{
    358 			daeElementRefArray children;
    359 			element->getChildren( children );
    360 			bool validArray = false, teqCommon = false;
    361 			for ( unsigned int i = 0; i < children.getCount(); i++ )
    362 			{
    363 				if ( strcmp( children[i]->getTypeName(), "float_array" ) == 0 ||
    364 					 strcmp( children[i]->getTypeName(), "int_array" ) == 0 )
    365 				{
    366 					validArray = true;
    367 				}
    368 				else if ( strcmp( children[i]->getTypeName(), "technique_common" ) == 0 )
    369 				{
    370 					teqCommon = true;
    371 				}
    372 			}
    373 			if ( validArray && teqCommon )
    374 			{
    375 				writeRawSource( element );
    376 				return;
    377 			}
    378 		}
    379 	}
    380 
    381 	if (!_meta->getIsTransparent() ) {
    382 		xmlTextWriterStartElement(writer, (xmlChar*)element->getElementName());
    383 		daeMetaAttributeRefArray& attrs = _meta->getMetaAttributes();
    384 
    385 		int acnt = (int)attrs.getCount();
    386 
    387 		for(int i=0;i<acnt;i++) {
    388 			writeAttribute(attrs[i], element);
    389 		}
    390 	}
    391 	writeValue(element);
    392 
    393 	daeElementRefArray children;
    394 	element->getChildren( children );
    395 	for ( size_t x = 0; x < children.getCount(); x++ ) {
    396 		writeElement( children.get(x) );
    397 	}
    398 
    399 	/*if (_meta->getContents() != NULL) {
    400 		daeElementRefArray* era = (daeElementRefArray*)_meta->getContents()->getWritableMemory(element);
    401 		int elemCnt = (int)era->getCount();
    402 		for(int i = 0; i < elemCnt; i++) {
    403 			daeElementRef elem = (daeElementRef)era->get(i);
    404 			if (elem != NULL) {
    405 				writeElement( elem );
    406 			}
    407 		}
    408 	}
    409 	else
    410 	{
    411 		daeMetaElementAttributeArray& children = _meta->getMetaElements();
    412 		int cnt = (int)children.getCount();
    413 		for(int i=0;i<cnt;i++) {
    414 			daeMetaElement *type = children[i]->getElementType();
    415 			if ( !type->getIsAbstract() ) {
    416 				for (int c = 0; c < children[i]->getCount(element); c++ ) {
    417 					writeElement( *(daeElementRef*)children[i]->get(element,c) );
    418 				}
    419 			}
    420 		}
    421 	}*/
    422 	if (!_meta->getIsTransparent() ) {
    423 		xmlTextWriterEndElement(writer);
    424 	}
    425 }
    426 
    427 void daeLIBXMLPlugin::writeAttribute( daeMetaAttribute* attr, daeElement* element)
    428 {
    429 	ostringstream buffer;
    430 	attr->memoryToString(element, buffer);
    431 	string str = buffer.str();
    432 
    433 	// Don't write the attribute if
    434 	//  - The attribute isn't required AND
    435 	//     - The attribute has no default value and the current value is ""
    436 	//     - The attribute has a default value and the current value matches the default
    437 	if (!attr->getIsRequired()) {
    438 		if(!attr->getDefaultValue()  &&  str.empty())
    439 			return;
    440 		if(attr->getDefaultValue()  &&  attr->compareToDefault(element) == 0)
    441 			return;
    442 	}
    443 
    444 	xmlTextWriterStartAttribute(writer, (xmlChar*)(daeString)attr->getName());
    445 	xmlChar* utf8 = (xmlChar*)str.c_str();
    446 	if (dae.getCharEncoding() == DAE::Latin1)
    447 		utf8 = latin1ToUtf8(str);
    448 	xmlTextWriterWriteString(writer, utf8);
    449 	if (dae.getCharEncoding() == DAE::Latin1)
    450 		delete[] utf8;
    451 
    452 	xmlTextWriterEndAttribute(writer);
    453 }
    454 
    455 void daeLIBXMLPlugin::writeValue(daeElement* element) {
    456 	if (daeMetaAttribute* attr = element->getMeta()->getValueAttribute()) {
    457 		ostringstream buffer;
    458 		attr->memoryToString(element, buffer);
    459 		string s = buffer.str();
    460 		if (!s.empty()) {
    461 			xmlChar* str = (xmlChar*)s.c_str();
    462 			if (dae.getCharEncoding() == DAE::Latin1)
    463 				str = latin1ToUtf8(s);
    464 			xmlTextWriterWriteString(writer, (xmlChar*)s.c_str());
    465 			if (dae.getCharEncoding() == DAE::Latin1)
    466 				delete[] str;
    467 		}
    468 	}
    469 }
    470 
    471 void daeLIBXMLPlugin::writeRawSource( daeElement *src )
    472 {
    473 	daeElementRef newSrc = src->clone();
    474 	daeElementRef array = NULL;
    475 	daeElement *accessor = NULL;
    476 	daeElementRefArray children;
    477 	newSrc->getChildren( children );
    478 	bool isInt = false;
    479 	for ( int i = 0; i < (int)children.getCount(); i++ )
    480 	{
    481 		if ( strcmp( children[i]->getTypeName(), "float_array" ) == 0 )
    482 		{
    483 			array = children[i];
    484 			newSrc->removeChildElement( array );
    485 		}
    486 		else if ( strcmp( children[i]->getTypeName(), "int_array" ) == 0 )
    487 		{
    488 			array = children[i];
    489 			isInt = true;
    490 			newSrc->removeChildElement( array );
    491 		}
    492 		else if ( strcmp( children[i]->getTypeName(), "technique_common" ) == 0 )
    493 		{
    494 			children[i]->getChildren( children );
    495 		}
    496 		else if ( strcmp( children[i]->getTypeName(), "accessor" ) == 0 )
    497 		{
    498 			accessor = children[i];
    499 		}
    500 	}
    501 
    502 	daeULong *countPtr = (daeULong*)array->getAttributeValue( "count" );
    503 	daeULong count = countPtr != NULL ? *countPtr : 0;
    504 
    505 	daeULong *stridePtr = (daeULong*)accessor->getAttributeValue( "stride" );
    506 	daeULong stride = stridePtr != NULL ? *stridePtr : 1;
    507 
    508 	children.clear();
    509 	accessor->getChildren( children );
    510 	if ( children.getCount() > stride ) {
    511 		*stridePtr = children.getCount();
    512 	}
    513 
    514 	daeFixedName newURI;
    515 	sprintf( newURI, "%s#%ld", rawRelPath.getOriginalURI(), rawByteCount );
    516 	accessor->setAttribute( "source", newURI );
    517 
    518 	daeArray *valArray = (daeArray*)array->getValuePointer();
    519 
    520 	//TODO: pay attention to precision for the array.
    521 	if ( isInt )
    522 	{
    523 		for( size_t i = 0; i < count; i++ )
    524 		{
    525 			daeInt tmp = (daeInt)*(daeLong*)(valArray->getRaw(i));
    526 			rawByteCount += (unsigned long)(fwrite( &tmp, sizeof(daeInt), 1, rawFile ) * sizeof(daeInt));
    527 		}
    528 	}
    529 	else
    530 	{
    531 		for( size_t i = 0; i < count; i++ )
    532 		{
    533 			daeFloat tmp = (daeFloat)*(daeDouble*)(valArray->getRaw(i));
    534 			rawByteCount += (unsigned long)(fwrite( &tmp, sizeof(daeFloat), 1, rawFile ) * sizeof(daeFloat));
    535 		}
    536 	}
    537 
    538 	writeElement( newSrc );
    539 }
    540 
    541 #endif // DOM_INCLUDE_LIBXML
    542