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 #include <limits>
32 #include <iomanip>
33 using namespace std;
34 
35 #include <minizip/zip.h> // for saving compressed files
36 #ifdef _WIN32
37 #include <iowin32.h>
38 #else
39 #include <unistd.h>
40 #endif
41 
42 // Some helper functions for working with libxml
43 namespace {
getCurrentLineNumber(xmlTextReaderPtr reader)44 daeInt getCurrentLineNumber(xmlTextReaderPtr reader) {
45 #if LIBXML_VERSION >= 20620
46     return xmlTextReaderGetParserLineNumber(reader);
47 #else
48     return -1;
49 #endif
50 }
51 
52 #ifdef _WIN32
53 static const char s_filesep = '\\';
54 #else
55 static const char s_filesep = '/';
56 #endif
57 
58 // Return value should be freed by caller with delete[]. Passed in value should not
59 // be null.
utf8ToLatin1(const xmlChar * utf8)60 xmlChar* utf8ToLatin1(const xmlChar* utf8) {
61     int inLen = xmlStrlen(utf8);
62     int outLen = (inLen+1) * 2;
63     xmlChar* latin1 = new xmlChar[outLen];
64     int numBytes = UTF8Toisolat1(latin1, &outLen, utf8, &inLen);
65     if (numBytes < 0) {
66         // Failed. Return an empty string instead.
67         numBytes = 0;
68     }
69     latin1[numBytes] = '\0';
70     return latin1;
71 }
72 
73 // Return value should be freed by caller with delete[].
latin1ToUtf8(const string & latin1)74 xmlChar* latin1ToUtf8(const string& latin1) {
75     int inLen = (int)latin1.length();
76     int outLen = (inLen+1) * 2;
77     xmlChar* utf8 = new xmlChar[outLen];
78     int numBytes = isolat1ToUTF8(utf8, &outLen, (xmlChar*)latin1.c_str(), &inLen);
79     if (numBytes < 0)
80         // Failed. Return an empty string instead.
81         numBytes = 0;
82 
83     utf8[numBytes] = '\0';
84     return utf8;
85 }
86 
87 // wrapper that automatically closes the zip file handle
88 class zipFileHandler
89 {
90 public:
zipFileHandler()91     zipFileHandler() {
92         zf = NULL;
93     }
~zipFileHandler()94     ~zipFileHandler() {
95         if( !!zf ) {
96             int errclose = zipClose(zf,NULL);
97             if (errclose != ZIP_OK) {
98                 ostringstream msg;
99                 msg << "zipClose error" << errclose << "\n";
100                 daeErrorHandler::get()->handleError(msg.str().c_str());
101             }
102         }
103     }
104     zipFile zf;
105 };
106 
107 class xmlBufferHandler
108 {
109 public:
xmlBufferHandler()110     xmlBufferHandler() {
111         buf = NULL;
112     }
~xmlBufferHandler()113     ~xmlBufferHandler() {
114         if( !!buf ) {
115             xmlBufferFree(buf);
116         }
117     }
118     xmlBufferPtr buf;
119 };
120 
121 typedef pair<daeString, daeString> stringPair;
122 
123 // The attributes vector passed in should be empty. If 'encoding' is anything
124 // other than utf8 the caller should free the returned attribute value
125 // strings. The 'freeAttrValues' function is provided for that purpose.
packageCurrentAttributes(xmlTextReaderPtr reader,DAE::charEncoding encoding,vector<stringPair> & attributes)126 void packageCurrentAttributes(xmlTextReaderPtr reader,
127                               DAE::charEncoding encoding,
128                               /* out */ vector<stringPair>& attributes) {
129     int numAttributes = xmlTextReaderAttributeCount(reader);
130     if (numAttributes == -1 || numAttributes == 0)
131         return;
132     attributes.reserve(numAttributes);
133 
134     while (xmlTextReaderMoveToNextAttribute(reader) == 1) {
135         const xmlChar* xmlName = xmlTextReaderConstName(reader);
136         const xmlChar* xmlValue = xmlTextReaderConstValue(reader);
137         if (encoding == DAE::Latin1)
138             attributes.push_back(stringPair((daeString)xmlName, (daeString)utf8ToLatin1(xmlValue)));
139         else
140             attributes.push_back(stringPair((daeString)xmlName, (daeString)xmlValue));
141     }
142 }
143 
freeAttrValues(vector<stringPair> & pairs)144 void freeAttrValues(vector<stringPair>& pairs) {
145     for(size_t i=0, size=pairs.size(); i<size; ++i) {
146         delete[] pairs[i].second;
147         pairs[i].second = 0;
148     }
149 }
150 }
151 
daeLIBXMLPlugin(DAE & dae)152 daeLIBXMLPlugin::daeLIBXMLPlugin(DAE& dae) : dae(dae), rawRelPath(dae)
153 {
154     supportedProtocols.push_back("*");
155     xmlInitParser();
156     rawFile = NULL;
157     rawByteCount = 0;
158     saveRawFile = false;
159     writer = NULL;
160 }
161 
~daeLIBXMLPlugin()162 daeLIBXMLPlugin::~daeLIBXMLPlugin()
163 {
164     if( !writer ) {
165         xmlFreeTextWriter( writer );
166         writer = NULL;
167     }
168     xmlCleanupParser();
169 }
170 
setOption(daeString option,daeString value)171 daeInt daeLIBXMLPlugin::setOption( daeString option, daeString value )
172 {
173     if ( strcmp( option, "saveRawBinary" ) == 0 )
174     {
175         if ( strcmp( value, "true" ) == 0 || strcmp( value, "TRUE" ) == 0 )
176         {
177             saveRawFile = true;
178         }
179         else
180         {
181             saveRawFile = false;
182         }
183         return DAE_OK;
184     }
185     return DAE_ERR_INVALID_CALL;
186 }
187 
getOption(daeString option)188 daeString daeLIBXMLPlugin::getOption( daeString option )
189 {
190     if ( strcmp( option, "saveRawBinary" ) == 0 )
191     {
192         if ( saveRawFile )
193         {
194             return "true";
195         }
196         return "false";
197     }
198     return NULL;
199 }
200 
201 namespace {
libxmlErrorHandler(void * arg,const char * msg,xmlParserSeverities severity,xmlTextReaderLocatorPtr locator)202 void libxmlErrorHandler(void* arg,
203                         const char* msg,
204                         xmlParserSeverities severity,
205                         xmlTextReaderLocatorPtr locator) {
206     if(severity == XML_PARSER_SEVERITY_VALIDITY_WARNING  ||
207        severity == XML_PARSER_SEVERITY_WARNING) {
208         daeErrorHandler::get()->handleWarning(msg);
209     }
210     else
211         daeErrorHandler::get()->handleError(msg);
212 }
213 }
214 
215 #if LIBXML_VERSION < 20700
216 #ifndef XML_PARSE_HUGE
217 #define XML_PARSE_HUGE 0
218 #endif
219 #endif
220 
221 // A simple structure to help alloc/free xmlTextReader objects
222 struct xmlTextReaderHelper {
xmlTextReaderHelperxmlTextReaderHelper223     xmlTextReaderHelper(const daeURI& uri) {
224         if((reader = xmlReaderForFile(cdom::fixUriForLibxml(uri.str()).c_str(), NULL, XML_PARSE_HUGE)))
225             xmlTextReaderSetErrorHandler(reader, libxmlErrorHandler, NULL);
226     }
227 
xmlTextReaderHelperxmlTextReaderHelper228     xmlTextReaderHelper(daeString buffer, const daeURI& baseUri) {
229         if((reader = xmlReaderForDoc((xmlChar*)buffer, cdom::fixUriForLibxml(baseUri.str()).c_str(), NULL, XML_PARSE_HUGE)))
230             xmlTextReaderSetErrorHandler(reader, libxmlErrorHandler, NULL);
231     };
232 
~xmlTextReaderHelperxmlTextReaderHelper233     ~xmlTextReaderHelper() {
234         if (reader)
235             xmlFreeTextReader(reader);
236     }
237 
238     xmlTextReaderPtr reader;
239 };
240 
readFromFile(const daeURI & uri)241 daeElementRef daeLIBXMLPlugin::readFromFile(const daeURI& uri) {
242     xmlTextReaderHelper readerHelper(uri);
243     if (!readerHelper.reader) {
244         daeErrorHandler::get()->handleError((string("Failed to open ") + uri.str() +
245                                              " in daeLIBXMLPlugin::readFromFile\n").c_str());
246         return NULL;
247     }
248     return read(readerHelper.reader);
249 }
250 
readFromMemory(daeString buffer,const daeURI & baseUri)251 daeElementRef daeLIBXMLPlugin::readFromMemory(daeString buffer, const daeURI& baseUri) {
252     xmlTextReaderHelper readerHelper(buffer, baseUri);
253     if (!readerHelper.reader) {
254         daeErrorHandler::get()->handleError("Failed to open XML document from memory buffer in "
255                                             "daeLIBXMLPlugin::readFromMemory\n");
256         return NULL;
257     }
258     return read(readerHelper.reader);
259 }
260 
read(_xmlTextReader * reader)261 daeElementRef daeLIBXMLPlugin::read(_xmlTextReader* reader) {
262     // Drop everything up to the first element. In the future, we should try to store header comments somewhere.
263     while(xmlTextReaderNodeType(reader) != XML_READER_TYPE_ELEMENT)
264     {
265         if (xmlTextReaderRead(reader) != 1) {
266             daeErrorHandler::get()->handleError("Error parsing XML in daeLIBXMLPlugin::read\n");
267             return NULL;
268         }
269     }
270 
271     int readRetVal = 0;
272     return readElement(reader, NULL, readRetVal);
273 }
274 
readElement(_xmlTextReader * reader,daeElement * parentElement,int & readRetVal)275 daeElementRef daeLIBXMLPlugin::readElement(_xmlTextReader* reader,
276                                            daeElement* parentElement,
277                                            /* out */ int& readRetVal) {
278     assert(xmlTextReaderNodeType(reader) == XML_READER_TYPE_ELEMENT);
279     daeString elementName = (daeString)xmlTextReaderConstName(reader);
280     bool empty = xmlTextReaderIsEmptyElement(reader) != 0;
281 
282     vector<attrPair> attributes;
283     packageCurrentAttributes(reader, dae.getCharEncoding(), /* out */ attributes);
284 
285     daeElementRef element = beginReadElement(parentElement, elementName, attributes, getCurrentLineNumber(reader));
286     if (dae.getCharEncoding() != DAE::Utf8) {
287         freeAttrValues(attributes);
288     }
289 
290     if (!element) {
291         // We couldn't create the element. beginReadElement already printed an error message. Just make sure
292         // to skip ahead past the bad element.
293         xmlTextReaderNext(reader);
294         return NULL;
295     }
296 
297     if ((readRetVal = xmlTextReaderRead(reader)) == -1) {
298         return NULL;
299     }
300     if (empty) {
301         return element;
302     }
303     int nodeType = xmlTextReaderNodeType(reader);
304     while (readRetVal == 1  &&  nodeType != XML_READER_TYPE_END_ELEMENT) {
305         if (nodeType == XML_READER_TYPE_ELEMENT) {
306             element->placeElement(readElement(reader, element, readRetVal));
307         }
308         else if (nodeType == XML_READER_TYPE_TEXT) {
309             const xmlChar* xmlText = xmlTextReaderConstValue(reader);
310             if (dae.getCharEncoding() == DAE::Latin1) {
311                 xmlText = utf8ToLatin1(xmlText);
312             }
313             readElementText(element, (daeString)xmlText, getCurrentLineNumber(reader));
314             if (dae.getCharEncoding() == DAE::Latin1) {
315                 delete[] xmlText;
316             }
317             readRetVal = xmlTextReaderRead(reader);
318         }
319         else {
320             readRetVal = xmlTextReaderRead(reader);
321         }
322         nodeType = xmlTextReaderNodeType(reader);
323     }
324 
325     if (nodeType == XML_READER_TYPE_END_ELEMENT) {
326         readRetVal = xmlTextReaderRead(reader);
327     }
328     if (readRetVal == -1) { // Something went wrong (bad xml probably)
329         return NULL;
330     }
331 
332     return element;
333 }
334 
write(const daeURI & name,daeDocument * document,daeBool replace)335 daeInt daeLIBXMLPlugin::write(const daeURI& name, daeDocument *document, daeBool replace)
336 {
337     // Make sure database and document are both set
338     if (!database) {
339         return DAE_ERR_INVALID_CALL;
340     }
341     if(!document) {
342         return DAE_ERR_COLLECTION_DOES_NOT_EXIST;
343     }
344     // Convert the URI to a file path, to see if we're about to overwrite a file
345     string file = cdom::uriToNativePath(name.str());
346     if (file.empty()  &&  saveRawFile)
347     {
348         daeErrorHandler::get()->handleError( "can't get path in write\n" );
349         return DAE_ERR_BACKEND_IO;
350     }
351 
352     // If replace=false, don't replace existing files
353     if(!replace)
354     {
355         // Using "stat" would be better, but it's not available on all platforms
356         FILE *tempfd = fopen(file.c_str(), "r");
357         if(tempfd != NULL)
358         {
359             // File exists, return error
360             fclose(tempfd);
361             return DAE_ERR_BACKEND_FILE_EXISTS;
362         }
363         fclose(tempfd);
364     }
365     if ( saveRawFile )
366     {
367         string rawFilePath = file + ".raw";
368         if ( !replace )
369         {
370             rawFile = fopen(rawFilePath.c_str(), "rb" );
371             if ( rawFile != NULL )
372             {
373                 fclose(rawFile);
374                 return DAE_ERR_BACKEND_FILE_EXISTS;
375             }
376             fclose(rawFile);
377         }
378         rawFile = fopen(rawFilePath.c_str(), "wb");
379         if ( rawFile == NULL )
380         {
381             return DAE_ERR_BACKEND_IO;
382         }
383         rawRelPath.set(cdom::nativePathToUri(rawFilePath));
384         rawRelPath.makeRelativeTo( &name );
385     }
386 
387     std::string fileName = cdom::uriToNativePath(name.str());
388     bool bcompress = fileName.size() >= 4 && fileName[fileName.size()-4] == '.' && ::tolower(fileName[fileName.size()-3]) == 'z' && ::tolower(fileName[fileName.size()-2]) == 'a' && ::tolower(fileName[fileName.size()-1]) == 'e';
389 
390     int err=0;
391     xmlBufferHandler bufhandler;
392 
393     if( bcompress ) {
394         // taken from http://xmlsoft.org/examples/testWriter.c
395         // Create a new XML buffer, to which the XML document will be written
396         bufhandler.buf = xmlBufferCreate();
397         if (!bufhandler.buf) {
398             ostringstream msg;
399             msg << "daeLIBXMLPlugin::write(" << name.str() << ") testXmlwriterMemory: Error creating the xml buffer\n";
400             daeErrorHandler::get()->handleError(msg.str().c_str());
401             return DAE_ERR_BACKEND_IO;
402         }
403 
404         // Create a new XmlWriter for memory, with no compression. Remark: there is no compression for this kind of xmlTextWriter
405         writer = xmlNewTextWriterMemory(bufhandler.buf, 0);
406     }
407     else {
408         // Open the file we will write to
409         writer = xmlNewTextWriterFilename(cdom::fixUriForLibxml(name.str()).c_str(), 0);
410     }
411 
412     if (!writer) {
413         ostringstream msg;
414         msg << "daeLIBXMLPlugin::write(" << name.str() << ") Error creating the xml writer\n";
415         daeErrorHandler::get()->handleError(msg.str().c_str());
416         return DAE_ERR_BACKEND_IO;
417     }
418     err = xmlTextWriterSetIndentString( writer, (const xmlChar*)"\t" ); // Don't change this to spaces
419     if( err < 0 ) {
420     }
421     err = xmlTextWriterSetIndent( writer, 1 ); // Turns indentation on
422     if( err < 0 ) {
423     }
424     err = xmlTextWriterStartDocument( writer, "1.0", "UTF-8", NULL );
425     if( err < 0 ) {
426     }
427 
428     writeElement( document->getDomRoot() );
429 
430     xmlTextWriterEndDocument( writer );
431     xmlTextWriterFlush( writer );
432     xmlFreeTextWriter( writer );
433     writer = NULL; // reset pointer
434 
435     if( bcompress ) {
436         std::string savefilenameinzip;
437         size_t namestart = fileName.find_last_of(s_filesep);
438         if( namestart == string::npos ) {
439             namestart = 0;
440         }
441         else {
442             namestart+=1;
443         }
444         if(namestart+4>=fileName.size()) {
445             daeErrorHandler::get()->handleError("invalid fileName when removing zae extension");
446             return DAE_ERR_BACKEND_IO;
447         }
448         savefilenameinzip = fileName.substr(namestart,fileName.size()-namestart-4);
449         savefilenameinzip += ".dae";
450 
451         zipFileHandler zfh;
452 #ifdef _WIN32
453         zlib_filefunc64_def ffunc;
454         fill_win32_filefunc64A(&ffunc);
455         zfh.zf = zipOpen2_64(fileName.c_str(),APPEND_STATUS_CREATE,NULL,&ffunc);
456 #else
457         zfh.zf = zipOpen64(fileName.c_str(),APPEND_STATUS_CREATE);
458 #endif
459         if (!zfh.zf) {
460             ostringstream msg;
461             msg << "daeLIBXMLPlugin::write(" << name.str() << ") Error opening zip file for writing\n";
462             daeErrorHandler::get()->handleError(msg.str().c_str());
463             return DAE_ERR_BACKEND_IO;
464         }
465 
466         time_t curtime = time(NULL);
467         struct tm* timeofday = localtime(&curtime);
468         zip_fileinfo zi;
469         zi.tmz_date.tm_sec = timeofday->tm_sec;
470         zi.tmz_date.tm_min = timeofday->tm_min;
471         zi.tmz_date.tm_hour = timeofday->tm_hour;
472         zi.tmz_date.tm_mday = timeofday->tm_mday;
473         zi.tmz_date.tm_mon = timeofday->tm_mon;
474         zi.tmz_date.tm_year = timeofday->tm_year;
475         zi.dosDate = 0;
476         zi.internal_fa = 0;
477         zi.external_fa = 0;
478 
479         int zip64 = bufhandler.buf->use >= 0xffffffff;
480 
481         char* password=NULL;
482         unsigned long crcFile=0;
483         int opt_compress_level = 9;
484         err = zipOpenNewFileInZip3_64(zfh.zf,savefilenameinzip.c_str(),&zi,NULL,0,NULL,0,"collada file generated by collada-dom",Z_DEFLATED, opt_compress_level,0,-MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,password,crcFile, zip64);
485         if (err != ZIP_OK) {
486             ostringstream msg;
487             msg << "daeLIBXMLPlugin::write(" << name.str() << ") zipOpenNewFileInZip3_64 error" << err << "\n";
488             daeErrorHandler::get()->handleError(msg.str().c_str());
489             return DAE_ERR_BACKEND_IO;
490         }
491 
492         err = zipWriteInFileInZip (zfh.zf,bufhandler.buf->content, bufhandler.buf->use);
493         if (err<0) {
494             ostringstream msg;
495             msg << "daeLIBXMLPlugin::write(" << name.str() << ") zipWriteInFileInZip error for dae file " << err << "\n";
496             daeErrorHandler::get()->handleError(msg.str().c_str());
497             return DAE_ERR_BACKEND_IO;
498         }
499         err = zipCloseFileInZip(zfh.zf);
500         if (err!=ZIP_OK) {
501             ostringstream msg;
502             msg << "daeLIBXMLPlugin::write(" << name.str() << ") zipCloseFileInZip error for dae file " << err << "\n";
503             daeErrorHandler::get()->handleError(msg.str().c_str());
504             return DAE_ERR_BACKEND_IO;
505         }
506 
507         // add the manifest
508         string smanifest = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<dae_root>./";
509         smanifest += savefilenameinzip;
510         smanifest += "</dae_root>\n";
511         err = zipOpenNewFileInZip3_64(zfh.zf,"manifest.xml",&zi,NULL,0,NULL,0,NULL,Z_DEFLATED, opt_compress_level,0,-MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,password,crcFile, zip64);
512         if (err != ZIP_OK) {
513             ostringstream msg;
514             msg << "daeLIBXMLPlugin::write(" << name.str() << ") zipOpenNewFileInZip3_64 error for manifest.xml file " << err << "\n";
515             daeErrorHandler::get()->handleError(msg.str().c_str());
516             return DAE_ERR_BACKEND_IO;
517         }
518 
519         err = zipWriteInFileInZip (zfh.zf,&smanifest[0],smanifest.size());
520         if (err != ZIP_OK) {
521             ostringstream msg;
522             msg << "daeLIBXMLPlugin::write(" << name.str() << ") zipWriteInFileInZip error for manifest.xml file " << err << "\n";
523             daeErrorHandler::get()->handleError(msg.str().c_str());
524             return DAE_ERR_BACKEND_IO;
525         }
526 
527         err = zipCloseFileInZip(zfh.zf);
528         if (err != ZIP_OK) {
529             ostringstream msg;
530             msg << "daeLIBXMLPlugin::write(" << name.str() << ") zipCloseFileInZip error for manifest.xml file " << err << "\n";
531             daeErrorHandler::get()->handleError(msg.str().c_str());
532             return DAE_ERR_BACKEND_IO;
533         }
534     }
535 
536     if ( saveRawFile && rawFile != NULL )
537     {
538         fclose( rawFile );
539     }
540 
541     return DAE_OK;
542 }
543 
writeToMemory(std::vector<char> & output,daeDocument * document)544 daeInt daeLIBXMLPlugin::writeToMemory(std::vector<char>& output, daeDocument *document)
545 {
546     // Make sure database and document are both set
547     if (!database) {
548         return DAE_ERR_INVALID_CALL;
549     }
550     if(!document) {
551         return DAE_ERR_COLLECTION_DOES_NOT_EXIST;
552     }
553 
554     int err=0;
555     xmlBufferHandler bufhandler;
556 
557     // taken from http://xmlsoft.org/examples/testWriter.c
558     // Create a new XML buffer, to which the XML document will be written
559     bufhandler.buf = xmlBufferCreate();
560     if (!bufhandler.buf) {
561         ostringstream msg;
562         msg << "daeLIBXMLPlugin::writeToMemory() testXmlwriterMemory: Error creating the xml buffer\n";
563         daeErrorHandler::get()->handleError(msg.str().c_str());
564         return DAE_ERR_BACKEND_IO;
565     }
566 
567     // Create a new XmlWriter for memory, with no compression. Remark: there is no compression for this kind of xmlTextWriter
568     writer = xmlNewTextWriterMemory(bufhandler.buf, 0);
569     if (!writer) {
570         ostringstream msg;
571         msg << "daeLIBXMLPlugin::writeToMemory() Error creating the xml writer\n";
572         daeErrorHandler::get()->handleError(msg.str().c_str());
573         return DAE_ERR_BACKEND_IO;
574     }
575     err = xmlTextWriterSetIndentString( writer, (const xmlChar*)"\t" ); // Don't change this to spaces
576     if( err < 0 ) {
577     }
578     err = xmlTextWriterSetIndent( writer, 1 ); // Turns indentation on
579     if( err < 0 ) {
580     }
581     err = xmlTextWriterStartDocument( writer, "1.0", "UTF-8", NULL );
582     if( err < 0 ) {
583     }
584 
585     writeElement( document->getDomRoot() );
586 
587     xmlTextWriterEndDocument( writer );
588     xmlTextWriterFlush( writer );
589     xmlFreeTextWriter( writer );
590     writer = NULL; // reset pointer
591 
592     if( bufhandler.buf->use == 0 ) {
593         return DAE_ERR_BACKEND_IO;
594     }
595 
596     output.resize(bufhandler.buf->use);
597     memcpy(&output[0], bufhandler.buf->content, bufhandler.buf->use);
598     return DAE_OK;
599 }
600 
writeElement(daeElement * element)601 void daeLIBXMLPlugin::writeElement( daeElement* element )
602 {
603     daeMetaElement* _meta = element->getMeta();
604 
605     //intercept <source> elements for special handling
606     if ( saveRawFile )
607     {
608         if ( strcmp( element->getTypeName(), "source" ) == 0 )
609         {
610             daeElementRefArray children;
611             element->getChildren( children );
612             bool validArray = false, teqCommon = false;
613             for ( unsigned int i = 0; i < children.getCount(); i++ )
614             {
615                 if ( strcmp( children[i]->getTypeName(), "float_array" ) == 0 ||
616                      strcmp( children[i]->getTypeName(), "int_array" ) == 0 )
617                 {
618                     validArray = true;
619                 }
620                 else if ( strcmp( children[i]->getTypeName(), "technique_common" ) == 0 )
621                 {
622                     teqCommon = true;
623                 }
624             }
625             if ( validArray && teqCommon )
626             {
627                 writeRawSource( element );
628                 return;
629             }
630         }
631     }
632 
633     if (!_meta->getIsTransparent() ) {
634         xmlTextWriterStartElement(writer, (xmlChar*)element->getElementName());
635         daeMetaAttributeRefArray& attrs = _meta->getMetaAttributes();
636 
637         int acnt = (int)attrs.getCount();
638 
639         for(int i=0; i<acnt; i++) {
640             writeAttribute(attrs[i], element);
641         }
642     }
643     writeValue(element);
644 
645     daeElementRefArray children;
646     element->getChildren( children );
647     for ( size_t x = 0; x < children.getCount(); x++ ) {
648         writeElement( children.get(x) );
649     }
650 
651     /*if (_meta->getContents() != NULL) {
652         daeElementRefArray* era = (daeElementRefArray*)_meta->getContents()->getWritableMemory(element);
653         int elemCnt = (int)era->getCount();
654         for(int i = 0; i < elemCnt; i++) {
655             daeElementRef elem = (daeElementRef)era->get(i);
656             if (elem != NULL) {
657                 writeElement( elem );
658             }
659         }
660        }
661        else
662        {
663         daeMetaElementAttributeArray& children = _meta->getMetaElements();
664         int cnt = (int)children.getCount();
665         for(int i=0;i<cnt;i++) {
666             daeMetaElement *type = children[i]->getElementType();
667             if ( !type->getIsAbstract() ) {
668                 for (int c = 0; c < children[i]->getCount(element); c++ ) {
669                     writeElement( *(daeElementRef*)children[i]->get(element,c) );
670                 }
671             }
672         }
673        }*/
674     if (!_meta->getIsTransparent() ) {
675         xmlTextWriterEndElement(writer);
676     }
677 }
678 
writeAttribute(daeMetaAttribute * attr,daeElement * element)679 void daeLIBXMLPlugin::writeAttribute( daeMetaAttribute* attr, daeElement* element)
680 {
681     ostringstream buffer;
682 #ifdef COLLADA_DOM_DAEFLOAT_IS64
683     buffer << std::setprecision(std::numeric_limits<PLATFORM_FLOAT64>::digits10+1); // set the default precision to daeFloat digit
684 #endif
685     attr->memoryToString(element, buffer);
686     string str = buffer.str();
687 
688     // Don't write the attribute if
689     //  - The attribute isn't required AND
690     //     - The attribute has no default value and the current value is ""
691     //     - The attribute has a default value and the current value matches the default
692     if (!attr->getIsRequired()) {
693         if(!attr->getDefaultValue()  &&  str.empty())
694             return;
695         if(attr->getDefaultValue()  &&  attr->compareToDefault(element) == 0)
696             return;
697     }
698 
699     xmlTextWriterStartAttribute(writer, (xmlChar*)(daeString)attr->getName());
700     xmlChar* utf8 = (xmlChar*)str.c_str();
701     if (dae.getCharEncoding() == DAE::Latin1)
702         utf8 = latin1ToUtf8(str);
703     xmlTextWriterWriteString(writer, utf8);
704     if (dae.getCharEncoding() == DAE::Latin1)
705         delete[] utf8;
706 
707     xmlTextWriterEndAttribute(writer);
708 }
709 
writeValue(daeElement * element)710 void daeLIBXMLPlugin::writeValue(daeElement* element) {
711     if (daeMetaAttribute* attr = element->getMeta()->getValueAttribute()) {
712         ostringstream buffer;
713 #ifdef COLLADA_DOM_DAEFLOAT_IS64
714         buffer << std::setprecision(std::numeric_limits<PLATFORM_FLOAT64>::digits10+1); // set the default precision to daeFloat digit
715 #endif
716         attr->memoryToString(element, buffer);
717         string s = buffer.str();
718         if (!s.empty()) {
719             xmlChar* str = (xmlChar*)s.c_str();
720             if (dae.getCharEncoding() == DAE::Latin1)
721                 str = latin1ToUtf8(s);
722             xmlTextWriterWriteString(writer, (xmlChar*)s.c_str());
723             if (dae.getCharEncoding() == DAE::Latin1)
724                 delete[] str;
725         }
726     }
727 }
728 
writeRawSource(daeElement * src)729 void daeLIBXMLPlugin::writeRawSource( daeElement *src )
730 {
731     daeElementRef newSrc = src->clone();
732     daeElementRef array = NULL;
733     daeElement *accessor = NULL;
734     daeElementRefArray children;
735     newSrc->getChildren( children );
736     bool isInt = false;
737     for ( int i = 0; i < (int)children.getCount(); i++ )
738     {
739         if ( strcmp( children[i]->getTypeName(), "float_array" ) == 0 )
740         {
741             array = children[i];
742             newSrc->removeChildElement( array );
743         }
744         else if ( strcmp( children[i]->getTypeName(), "int_array" ) == 0 )
745         {
746             array = children[i];
747             isInt = true;
748             newSrc->removeChildElement( array );
749         }
750         else if ( strcmp( children[i]->getTypeName(), "technique_common" ) == 0 )
751         {
752             children[i]->getChildren( children );
753         }
754         else if ( strcmp( children[i]->getTypeName(), "accessor" ) == 0 )
755         {
756             accessor = children[i];
757         }
758     }
759 
760     daeULong *countPtr = (daeULong*)array->getAttributeValue( "count" );
761     daeULong count = countPtr != NULL ? *countPtr : 0;
762 
763     daeULong *stridePtr = (daeULong*)accessor->getAttributeValue( "stride" );
764     daeULong stride = stridePtr != NULL ? *stridePtr : 1;
765 
766     children.clear();
767     accessor->getChildren( children );
768     if ( children.getCount() > stride ) {
769         *stridePtr = children.getCount();
770     }
771 
772     daeFixedName newURI;
773     sprintf( newURI, "%s#%ld", rawRelPath.getOriginalURI(), rawByteCount );
774     accessor->setAttribute( "source", newURI );
775 
776     daeArray *valArray = (daeArray*)array->getValuePointer();
777 
778     //TODO: pay attention to precision for the array.
779     if ( isInt )
780     {
781         for( size_t i = 0; i < count; i++ )
782         {
783             daeInt tmp = (daeInt)*(daeLong*)(valArray->getRaw(i));
784             rawByteCount += (unsigned long)(fwrite( &tmp, sizeof(daeInt), 1, rawFile ) * sizeof(daeInt));
785         }
786     }
787     else
788     {
789         for( size_t i = 0; i < count; i++ )
790         {
791             daeFloat tmp = (daeFloat)*(daeDouble*)(valArray->getRaw(i));
792             rawByteCount += (unsigned long)(fwrite( &tmp, sizeof(daeFloat), 1, rawFile ) * sizeof(daeFloat));
793         }
794     }
795 
796     writeElement( newSrc );
797 }
798 
799 #endif // DOM_INCLUDE_LIBXML
800