1 /*
2   MusicXML Library
3   Copyright (C) Grame 2006-2013
4 
5   This Source Code Form is subject to the terms of the Mozilla Public
6   License, v. 2.0. If a copy of the MPL was not distributed with this
7   file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 
9   Grame Research Laboratory, 11, cours de Verdun Gensoul 69002 Lyon - France
10   research@grame.fr
11 */
12 
13 #include <string.h> // for strlen()
14 #include <regex>
15 
16 #include <iomanip> // for setw()
17 
18 #include "xml.h"
19 #include "xmlfile.h"
20 #include "xmlreader.h"
21 
22 #include "messagesHandling.h"
23 
24 #include "musicXML2MxmlTreeInterface.h"
25 
26 #include "oahOah.h"
27 #include "generalOah.h"
28 
29 #include "setTraceOahIfDesired.h"
30 #ifdef TRACE_OAH
31   #include "traceOah.h"
32 #endif
33 
34 #include "musicXMLOah.h"
35 
36 
37 using namespace std;
38 
39 namespace MusicXML2
40 {
41 
42 //_______________________________________________________________________________
displayXMLDeclaration(TXMLDecl * xmlDeclaration,indentedOstream & logOstream)43 void displayXMLDeclaration (
44   TXMLDecl*        xmlDeclaration,
45   indentedOstream& logOstream)
46 {
47   string xmlVersion    = xmlDeclaration->getVersion ();
48   string xmlEncoding   = xmlDeclaration->getEncoding ();
49   int    xmlStandalone = xmlDeclaration->getStandalone ();
50 
51   const int fieldWidth = 14;
52 
53   logOstream <<
54     "XML Declaration:" <<
55     endl;
56 
57   gIndenter++;
58 
59   logOstream << left <<
60     setw (fieldWidth) <<
61     "xmlVersion" << " = \"" << xmlVersion << "\"" <<
62     endl <<
63     setw (fieldWidth) <<
64     "xmlEncoding" << " = \"" << xmlEncoding << "\"" <<
65     endl  <<
66     setw (fieldWidth) <<
67     "xmlStandalone" << " = \"" << xmlStandalone << "\"" <<
68     endl <<
69     endl;
70 
71   gIndenter--;
72 }
73 
74 //_______________________________________________________________________________
displayDocumentType(TDocType * documentType,indentedOstream & logOstream)75 void displayDocumentType (
76   TDocType*        documentType,
77   indentedOstream& logOstream)
78 {
79   const int fieldWidth = 16;
80 
81   logOstream <<
82     "Document Type:" <<
83     endl;
84 
85   gIndenter++;
86 
87   std::string xmlStartElement = documentType->getStartElement ();
88   bool        xmlPublic       = documentType->getPublic ();
89   std::string xmlPubLitteral  = documentType->getPubLitteral ();
90   std::string xmlSysLitteral  = documentType->getSysLitteral ();
91 
92   logOstream << left <<
93     setw (fieldWidth) <<
94     "xmlStartElement" << " = \"" << xmlStartElement << "\"" <<
95     endl <<
96     setw (fieldWidth) <<
97     "xmlPublic" << " = \"" << xmlPublic << "\"" <<
98     endl  <<
99     setw (fieldWidth) <<
100     "xmlPubLitteral" << " = \"" << xmlPubLitteral << "\"" <<
101     endl  <<
102     setw (fieldWidth) <<
103     "xmlSysLitteral" << " = \"" << xmlSysLitteral << "\"" <<
104     endl <<
105     endl;
106 
107   gIndenter--;
108 }
109 
110 //_______________________________________________________________________________
uncompressMXLFile(string mxlFileName,indentedOstream & logOstream)111 string uncompressMXLFile (
112   string           mxlFileName,
113   indentedOstream& logOstream)
114 {
115   string fileBaseName = baseName (mxlFileName);
116 
117   logOstream <<
118     "The compressed file name is '" <<
119     mxlFileName <<
120     "'" <<
121     endl <<
122     endl;
123 
124   string uncompressedFileName;
125 
126 #ifdef WIN32
127   // JMI
128 #else
129   {
130     // build shell command to list the contents of the uncompress file
131     stringstream s1;
132 
133     s1 <<
134       "unzip -l " <<
135       mxlFileName;
136 
137     string listContentsShellCommand = s1.str ();
138 
139     if (true) {
140       logOstream <<
141         "Listing the contents of the compressed file '" <<
142         mxlFileName <<
143         "' with command:" <<
144         endl;
145 
146       gIndenter++;
147 
148       logOstream <<
149         listContentsShellCommand <<
150         endl <<
151         endl;
152 
153       gIndenter--;
154     }
155 
156       // create a stream to receive the result of listContentsShellCommand
157     FILE* inputStream =
158       popen (
159         listContentsShellCommand.c_str (),
160         "r");
161 
162     if (inputStream == nullptr) {
163       stringstream s;
164 
165       s <<
166         "Cannot list the contents of compressed file '" <<
167         mxlFileName <<
168         "' with 'popen ()'";
169 
170       msrInternalError (
171         gOahOah->fInputSourceName,
172         0, // inputLineNumber
173         __FILE__, __LINE__,
174         s.str ());
175     }
176 
177     else {
178       string contentsList;
179 
180       // read the list from inputStream
181       char tampon [1024];
182 
183       while (
184         ! feof (inputStream)
185           &&
186         ! ferror (inputStream)
187           &&
188         fgets (tampon, sizeof (tampon), inputStream) != NULL
189         ) {
190         // append the contents of tampon to contentsList
191         contentsList += tampon;
192       } // while
193       // terminate the string in tampon
194       tampon [strlen (tampon) -1] = '\0';
195 
196       // close the stream
197       if (pclose (inputStream) < 0) {
198         msrInternalError (
199           gOahOah->fInputSourceName,
200           0, // inputLineNumber
201           __FILE__, __LINE__,
202           "Cannot close the input stream after 'popen ()'");
203       }
204 
205       logOstream <<
206         "The contents of the compressed file '" <<
207         mxlFileName <<
208         "' is:" <<
209         endl;
210 
211       gIndenter++;
212 
213       logOstream <<
214         contentsList <<
215         endl;
216 
217       gIndenter--;
218 
219       // analyze the contents list
220       list<string> linesList;
221 
222       istringstream inputStream (contentsList);
223       string        currentLine;
224 
225       while (getline (inputStream, currentLine)) {
226 
227         if (inputStream.eof ()) break;
228 
229 #ifdef TRACE_OAH
230         {
231           logOstream <<
232             "*** currentLine:" <<
233             endl;
234 
235           gIndenter++;
236 
237           logOstream <<
238             currentLine <<
239             endl;
240 
241           gIndenter--;
242         }
243 #endif
244 
245         /*
246         user@lilydev: ~/libmusicxml-git/files/samples/musicxml > unzip -l UnofficialTestSuite/90a-Compressed-MusicXML.mxl
247         Archive:  UnofficialTestSuite/90a-Compressed-MusicXML.mxl
248           Length      Date    Time    Name
249         ---------  ---------- -----   ----
250                 0  2007-11-14 16:04   META-INF/
251               246  2007-11-14 16:02   META-INF/container.xml
252              2494  2008-11-14 23:03   20a-Compressed-MusicXML.xml
253             30903  2007-11-14 15:51   20a-Compressed-MusicXML.pdf
254         ---------                     -------
255             33643                     4 files
256         */
257 
258         string regularExpression (
259           "[[:space:]]*"
260           ".*"   // length
261           "[[:space:]]+"
262           ".*"   // date
263           "[[:space:]]+"
264           ".*"   // time
265           "[[:space:]]+"
266           "(.*)" // name
267           );
268 
269         regex  e (regularExpression);
270         smatch sm;
271 
272         regex_match (currentLine, sm, e);
273 
274         if (sm.size ()) {
275 #ifdef TRACE_OAH
276           if (gTraceOah->fTracePasses) { // JMI ???
277             logOstream <<
278               "There are " << sm.size () - 1 << " match(es) " <<
279               "with regex '" << regularExpression <<
280               "':" <<
281               endl;
282 
283             for (unsigned i = 1; i < sm.size (); ++i) {
284               logOstream <<
285                 "[" << sm [i] << "] " <<
286                 endl;
287             } // for
288 
289             logOstream <<
290               endl <<
291               endl;
292           }
293 #endif
294 
295           string stringFromLine = sm [1];
296 
297           // has stringFromLine a ".xml" suffix?
298           size_t
299             posInString =
300               stringFromLine.rfind (".xml");
301 
302           // JMI if (posInString == stringFromLine.size () - 4) {
303           if (posInString != stringFromLine.npos) {  // JMI STRANGISSIMO!!!
304     //      if (posInString != stringFromLine.npos && stringFromLine != "files") {  // JMI STRANGISSIMO!!!
305             // yes, this is a MusicXML file
306 
307             // is this file part of META-INF?
308             size_t
309               posInString =
310                 stringFromLine.find ("META-INF");
311 
312             if (posInString == stringFromLine.npos) {
313               // no, this is an actual MusicXML file
314 
315               if (uncompressedFileName.size ()) {
316                 stringstream s;
317 
318                 s <<
319                   "Compressed file '" << mxlFileName <<
320                   "' contains multiple MusicMXL files" <<
321                   ", found '" << uncompressedFileName <<
322                   "' and then '" << stringFromLine << "'";
323 
324                 msrInternalError (
325                   gOahOah->fInputSourceName,
326                   0, // inputLineNumber
327                   __FILE__, __LINE__,
328                   s.str ());
329               }
330 
331               else {
332                 // we've got the uncompressed file name
333                 uncompressedFileName = stringFromLine;
334 
335                 logOstream <<
336                   "The uncompressed file name is '" <<
337                   uncompressedFileName <<
338                   "'" <<
339                   endl <<
340                   endl;
341               }
342             }
343           }
344         }
345       } // while
346     }
347   }
348 
349   {
350     // build shell command to uncompress the file
351     stringstream s2;
352 
353     s2 <<
354       "unzip -u -d /tmp " <<
355       mxlFileName;
356 
357     string uncompressShellCommand = s2.str ();
358 
359     if (true) {
360       logOstream <<
361         "Uncompressing '" <<
362         mxlFileName <<
363         "' into '/tmp/" <<
364         uncompressedFileName <<
365         "' with command:" <<
366         endl;
367 
368       gIndenter++;
369 
370       logOstream <<
371         uncompressShellCommand <<
372         endl <<
373         endl;
374 
375       gIndenter--;
376     }
377 
378     // create a stream to receive the result of uncompressShellCommand
379     FILE* inputStream =
380       popen (
381         uncompressShellCommand.c_str (),
382         "r");
383 
384     if (inputStream == nullptr) {
385       stringstream s;
386 
387       s <<
388         "Cannot uncompress the file '" <<
389         mxlFileName <<
390         "' with 'popen ()'";
391 
392       msrInternalError (
393         gOahOah->fInputSourceName,
394         0, // inputLineNumber
395         __FILE__, __LINE__,
396         s.str ());
397     }
398   }
399 #endif
400 
401   return uncompressedFileName;
402 }
403 
404 //_______________________________________________________________________________
musicXMLFile2mxmlTree(const char * fileName,S_musicXMLOah mxmlOpts,indentedOstream & logOstream)405 EXP Sxmlelement musicXMLFile2mxmlTree (
406   const char*       fileName,
407   S_musicXMLOah mxmlOpts,
408   indentedOstream&  logOstream)
409 {
410   clock_t startClock = clock ();
411 
412   string fileNameAsString = fileName;
413 
414 #ifdef TRACE_OAH
415   if (gTraceOah->fTracePasses) {
416     string separator =
417       "%--------------------------------------------------------------";
418 
419     logOstream <<
420       endl <<
421       separator <<
422       endl <<
423       gTab <<
424       "Pass 1: building the xmlelement tree from \"" << fileNameAsString << "\"" <<
425       endl <<
426       separator <<
427       endl <<
428       endl;
429   }
430 #endif
431 
432   // has the input file name a ".mxl" suffix?
433   size_t
434     posInString =
435       fileNameAsString.rfind (".mxl");
436 
437   if (posInString == fileNameAsString.size () - 4) {
438  // JMI  if (posInString != fileNameAsString.npos) {
439     // yes, this is a compressed file
440 
441     /* JMI OS dependent
442     string uncompressedFileName =
443       uncompressMXLFile (
444         fileNameAsString,
445         logOstream);
446 
447     // the incompressed file in /tmp will be handled
448     // instead of the compressed one
449     fileName = uncompressedFileName.c_str ();
450     */
451 
452     stringstream s;
453 
454     s <<
455       "you should uncompress this file prior to running xml2ly";
456 
457     msrMusicXMLError (
458       gOahOah->fInputSourceName,
459       1, // inputLineNumber,
460       __FILE__, __LINE__,
461       s.str ());
462 
463     exit (38);
464   }
465 
466   // read the input MusicXML data from the file
467   xmlreader r;
468 
469   SXMLFile xmlFile = r.read (fileName);
470 
471   // has there been a problem?
472   if (! xmlFile) {
473     return Sxmlelement (0);
474   }
475 
476 #ifdef TRACE_OAH
477   if (gMusicXMLOah->fTraceEncoding) {
478     logOstream <<
479       endl <<
480       "!!!!! xmlFile contents from file:" <<
481       endl <<
482       endl;
483 
484     xmlFile->print (logOstream);
485 
486     logOstream <<
487       endl <<
488       endl;
489   }
490 #endif
491 
492   // get the xmlDecl
493   TXMLDecl * xmlDecl = xmlFile->getXMLDecl ();
494 
495 #ifdef TRACE_OAH
496   if (gMusicXMLOah->fTraceEncoding) {
497     logOstream <<
498       endl <<
499       "!!!!! xmlDecl contents from file:" <<
500       endl <<
501       endl;
502     xmlDecl->print (logOstream);
503 
504     displayXMLDeclaration (
505       xmlDecl,
506       logOstream);
507   }
508 #endif
509 
510 #ifdef TRACE_OAH
511   // get the docType
512   TDocType * docType = xmlFile->getDocType ();
513 
514   if (gMusicXMLOah->fTraceEncoding) {
515     logOstream <<
516       endl <<
517       "!!!!! docType from file:" <<
518       endl <<
519       endl;
520     docType->print (logOstream);
521 
522     displayDocumentType (
523       docType,
524       logOstream);
525   }
526 #endif
527 
528   // get the encoding type
529   string encoding = xmlDecl->getEncoding ();
530 
531   // build the xmlelement tree
532   Sxmlelement mxmlTree;
533 
534   // should the encoding be converted to UTF-8?
535   string desiredEncoding = "UTF-8";
536 
537   if (encoding == desiredEncoding) {
538 #ifdef TRACE_OAH
539     if (gTraceOah->fTracePasses) {
540       logOstream <<
541         "% MusicXML data uses \"" <<
542         desiredEncoding <<
543         "\" encoding" <<
544         endl;
545     }
546 #endif
547   }
548 
549   else if (encoding.size () == 0) {
550     stringstream s;
551 
552     s <<
553       "MusicXML data in this file" <<
554       " doesn't contain any encoding specification; assuming it is UTF-8";
555 
556     msrMusicXMLWarning (
557       gOahOah->fInputSourceName,
558       1, // inputLineNumber,
559       s.str ());
560   }
561 
562   else {
563     stringstream s;
564 
565     s <<
566       "you should convert this file to " <<
567       desiredEncoding <<
568       "\" encoding prior to running xml2ly" <<
569       ", for example with iconv or using a text editor - handling it as is";
570 
571     msrMusicXMLWarning (
572       gOahOah->fInputSourceName,
573       1, // inputLineNumber,
574       s.str ());
575   }
576 
577   mxmlTree = xmlFile->elements ();
578 
579   clock_t endClock = clock ();
580 
581   // register time spent
582   timing::gTiming.appendTimingItem (
583     "Pass 1",
584     "build xmlelement tree from file",
585     timingItem::kMandatory,
586     startClock,
587     endClock);
588 
589   return mxmlTree;
590 }
591 
592 //_______________________________________________________________________________
musicXMLFd2mxmlTree(FILE * fd,S_musicXMLOah mxmlOpts,indentedOstream & logOstream)593 EXP Sxmlelement musicXMLFd2mxmlTree (
594   FILE*             fd,
595   S_musicXMLOah mxmlOpts,
596   indentedOstream&  logOstream)
597 {
598   clock_t startClock = clock ();
599 
600 #ifdef TRACE_OAH
601   if (gTraceOah->fTracePasses) {
602     string separator =
603       "%--------------------------------------------------------------";
604 
605     logOstream <<
606       endl <<
607       separator <<
608       endl <<
609       gTab <<
610       "Pass 1: building the xmlelement tree from standard input" <<
611       endl <<
612       separator <<
613       endl;
614   }
615 #endif
616 
617   // read the input MusicXML data
618   xmlreader r;
619 
620   SXMLFile xmlFile = r.read (fd);
621 
622   // has there been a problem?
623   if (! xmlFile) {
624     return Sxmlelement (0);
625   }
626 
627 #ifdef TRACE_OAH
628   if (gMusicXMLOah->fTraceEncoding) {
629     logOstream <<
630       "!!!!! xmlFile contents from stream:" <<
631       endl;
632     xmlFile->print (logOstream);
633     logOstream << endl;
634   }
635 #endif
636 
637   // get the xmlDecl
638   TXMLDecl *xmlDecl = xmlFile->getXMLDecl ();
639 
640 #ifdef TRACE_OAH
641   if (gMusicXMLOah->fTraceEncoding) {
642     logOstream <<
643       endl <<
644       "xmlDecl contents:" <<
645       endl <<
646       endl;
647     xmlDecl->print (logOstream);
648 
649     displayXMLDeclaration (
650       xmlDecl,
651       logOstream);
652   }
653 #endif
654 
655 #ifdef TRACE_OAH
656   // get the docType
657   TDocType * docType = xmlFile->getDocType ();
658 
659   if (gMusicXMLOah->fTraceEncoding) {
660     logOstream <<
661       endl <<
662       "!!!!! docType from stream:" <<
663       endl <<
664       endl;
665     docType->print (logOstream);
666 
667     displayDocumentType (
668       docType,
669       logOstream);
670   }
671 #endif
672 
673   // get the encoding type
674   string encoding = xmlDecl->getEncoding ();
675 
676   // should the encoding be converted to UTF-8?
677   string desiredEncoding = "UTF-8";
678 
679   logOstream <<
680     "% MusicXML data uses \"" <<
681     desiredEncoding <<
682     "\" encoding" <<
683     ", desired encoding is \"" << desiredEncoding << "\"" <<
684     endl;
685 
686   if (encoding != desiredEncoding) {
687      stringstream s;
688 
689     s <<
690       "you should convert this stream to " <<
691       desiredEncoding <<
692       "\" encoding prior to running xml2ly" <<
693       ", for example with iconv or using a text editor - handling it as is";
694 
695     msrMusicXMLWarning (
696       gOahOah->fInputSourceName,
697       1, // inputLineNumber,
698       s.str ());
699   }
700 
701   clock_t endClock = clock ();
702 
703   // register time spent
704   timing::gTiming.appendTimingItem (
705     "Pass 1",
706     "build xmlelement tree from standard input",
707     timingItem::kMandatory,
708     startClock,
709     endClock);
710 
711   // fetch mxmlTree
712   Sxmlelement mxmlTree = xmlFile->elements ();
713 
714   return mxmlTree;
715 }
716 
717 //_______________________________________________________________________________
musicXMLString2mxmlTree(const char * buffer,S_musicXMLOah mxmlOpts,indentedOstream & logOstream)718 EXP Sxmlelement musicXMLString2mxmlTree (
719   const char*       buffer,
720   S_musicXMLOah mxmlOpts,
721   indentedOstream&  logOstream)
722 {
723   clock_t startClock = clock ();
724 
725 #ifdef TRACE_OAH
726   if (gTraceOah->fTracePasses) {
727     string separator =
728       "%--------------------------------------------------------------";
729 
730     logOstream <<
731       endl <<
732       separator <<
733       endl <<
734       gTab <<
735       "Pass 1: building the xmlelement tree from a buffer" <<
736       endl <<
737       separator <<
738       endl;
739   }
740 #endif
741 
742   xmlreader r;
743 
744   SXMLFile xmlFile = r.readbuff (buffer);
745 
746   clock_t endClock = clock ();
747 
748   // register time spent
749   timing::gTiming.appendTimingItem (
750     "Pass 1",
751     "build xmlelement tree from buffer",
752     timingItem::kMandatory,
753     startClock,
754     endClock);
755 
756   // fetch mxmlTree
757   Sxmlelement mxmlTree = xmlFile->elements();
758 
759   return mxmlTree;
760 }
761 
762 
763 } // namespace
764