1 /*
2   ZynAddSubFX - a software synthesizer
3 
4   XMLwrapper.cpp - XML wrapper
5   Copyright (C) 2003-2005 Nasca Octavian Paul
6   Copyright (C) 2009-2009 Mark McCurry
7   Author: Nasca Octavian Paul
8           Mark McCurry
9 
10   This program is free software; you can redistribute it and/or
11   modify it under the terms of the GNU General Public License
12   as published by the Free Software Foundation; either version 2
13   of the License, or (at your option) any later version.
14 */
15 
16 #include "XMLwrapper.h"
17 #include <cstring>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <cstdarg>
21 #include <zlib.h>
22 #include <iostream>
23 #include <sstream>
24 
25 #include "globals.h"
26 #include "Util.h"
27 
28 using namespace std;
29 
30 namespace zyn {
31 
32 int  xml_k   = 0;
33 bool verbose = false;
34 
XMLwrapper_whitespace_callback(mxml_node_t * node,int where)35 const char *XMLwrapper_whitespace_callback(mxml_node_t *node, int where)
36 {
37     const char *name = mxmlGetElement(node);
38 
39     if((where == MXML_WS_BEFORE_OPEN) && (!strcmp(name, "?xml")))
40         return NULL;
41     if((where == MXML_WS_BEFORE_CLOSE) && (!strcmp(name, "string")))
42         return NULL;
43 
44     if((where == MXML_WS_BEFORE_OPEN) || (where == MXML_WS_BEFORE_CLOSE))
45         /*	const char *tmp=node->value.element.name;
46             if (tmp!=NULL) {
47                 if ((strstr(tmp,"par")!=tmp)&&(strstr(tmp,"string")!=tmp)) {
48                 printf("%s ",tmp);
49                 if (where==MXML_WS_BEFORE_OPEN) xml_k++;
50                 if (where==MXML_WS_BEFORE_CLOSE) xml_k--;
51                 if (xml_k>=STACKSIZE) xml_k=STACKSIZE-1;
52                 if (xml_k<0) xml_k=0;
53                 printf("%d\n",xml_k);
54                 printf("\n");
55                 };
56 
57             };
58             int i=0;
59             for (i=1;i<xml_k;i++) tabs[i]='\t';
60             tabs[0]='\n';tabs[i+1]='\0';
61             if (where==MXML_WS_BEFORE_OPEN) return(tabs);
62                 else return("\n");
63         */
64         return "\n";
65     ;
66 
67     return 0;
68 }
69 
70 //temporary const overload of mxmlFindElement
mxmlFindElement(const mxml_node_t * node,const mxml_node_t * top,const char * name,const char * attr,const char * value,int descend)71 const mxml_node_t *mxmlFindElement(const mxml_node_t *node,
72                                    const mxml_node_t *top,
73                                    const char *name,
74                                    const char *attr,
75                                    const char *value,
76                                    int descend)
77 {
78     return const_cast<const mxml_node_t *>(mxmlFindElement(
79                                                const_cast<mxml_node_t *>(node),
80                                                const_cast<mxml_node_t *>(top),
81                                                name, attr, value, descend));
82 }
83 
84 //temporary const overload of mxmlElementGetAttr
mxmlElementGetAttr(const mxml_node_t * node,const char * name)85 const char *mxmlElementGetAttr(const mxml_node_t *node, const char *name)
86 {
87     return mxmlElementGetAttr(const_cast<mxml_node_t *>(node), name);
88 }
89 
XMLwrapper()90 XMLwrapper::XMLwrapper()
91 {
92     minimal = true;
93     SaveFullXml=false;
94 
95     node = tree = mxmlNewElement(MXML_NO_PARENT,
96                                  "?xml version=\"1.0f\" encoding=\"UTF-8\"?");
97     /*  for mxml 2.1f (and older)
98         tree=mxmlNewElement(MXML_NO_PARENT,"?xml");
99         mxmlElementSetAttr(tree,"version","1.0f");
100         mxmlElementSetAttr(tree,"encoding","UTF-8");
101     */
102 
103     mxml_node_t *doctype = mxmlNewElement(tree, "!DOCTYPE");
104     mxmlElementSetAttr(doctype, "ZynAddSubFX-data", NULL);
105 
106     node = root = addparams("ZynAddSubFX-data", 4,
107                             "version-major", stringFrom<int>(
108                                 version.get_major()).c_str(),
109                             "version-minor", stringFrom<int>(
110                                 version.get_minor()).c_str(),
111                             "version-revision",
112                             stringFrom<int>(version.get_revision()).c_str(),
113                             "ZynAddSubFX-author", "Nasca Octavian Paul");
114 
115     //make the empty branch that will contain the information parameters
116     info = addparams("INFORMATION", 0);
117 
118     //save zynaddsubfx specifications
119     beginbranch("BASE_PARAMETERS");
120     addpar("max_midi_parts", NUM_MIDI_PARTS);
121     addpar("max_kit_items_per_instrument", NUM_KIT_ITEMS);
122 
123     addpar("max_system_effects", NUM_SYS_EFX);
124     addpar("max_insertion_effects", NUM_INS_EFX);
125     addpar("max_instrument_effects", NUM_PART_EFX);
126 
127     addpar("max_addsynth_voices", NUM_VOICES);
128     endbranch();
129 }
130 
131 void
cleanup(void)132 XMLwrapper::cleanup(void)
133 {
134     if(tree)
135         mxmlDelete(tree);
136 
137     /* make sure freed memory is not referenced */
138     tree = 0;
139     node = 0;
140     root = 0;
141 }
142 
~XMLwrapper()143 XMLwrapper::~XMLwrapper()
144 {
145     cleanup();
146 }
147 
setPadSynth(bool enabled)148 void XMLwrapper::setPadSynth(bool enabled)
149 {
150     /**@bug this might create multiple nodes when only one is needed*/
151     mxml_node_t *oldnode = node;
152     node = info;
153     //Info storing
154     addparbool("PADsynth_used", enabled);
155     node = oldnode;
156 }
157 
hasPadSynth() const158 bool XMLwrapper::hasPadSynth() const
159 {
160     /**Right now this has a copied implementation of setparbool, so this should
161      * be reworked as XMLwrapper evolves*/
162     mxml_node_t *tmp = mxmlFindElement(tree,
163                                        tree,
164                                        "INFORMATION",
165                                        NULL,
166                                        NULL,
167                                        MXML_DESCEND);
168 
169     mxml_node_t *parameter = mxmlFindElement(tmp,
170                                              tmp,
171                                              "par_bool",
172                                              "name",
173                                              "PADsynth_used",
174                                              MXML_DESCEND_FIRST);
175     if(parameter == NULL) //no information available
176         return false;
177 
178     const char *strval = mxmlElementGetAttr(parameter, "value");
179     if(strval == NULL) //no information available
180         return false;
181 
182     if((strval[0] == 'Y') || (strval[0] == 'y'))
183         return true;
184     else
185         return false;
186 }
187 
188 
189 /* SAVE XML members */
190 
saveXMLfile(const string & filename,int compression) const191 int XMLwrapper::saveXMLfile(const string &filename, int compression) const
192 {
193     char *xmldata = getXMLdata();
194     if(xmldata == NULL)
195         return -2;
196 
197     int result      = dosavefile(filename.c_str(), compression, xmldata);
198 
199     free(xmldata);
200     return result;
201 }
202 
getXMLdata() const203 char *XMLwrapper::getXMLdata() const
204 {
205     xml_k = 0;
206 
207     char *xmldata = mxmlSaveAllocString(tree, XMLwrapper_whitespace_callback);
208 
209     return xmldata;
210 }
211 
212 
dosavefile(const char * filename,int compression,const char * xmldata) const213 int XMLwrapper::dosavefile(const char *filename,
214                            int compression,
215                            const char *xmldata) const
216 {
217     if(compression == 0) {
218         FILE *file;
219         file = fopen(filename, "w");
220         if(file == NULL)
221             return -1;
222         fputs(xmldata, file);
223         fclose(file);
224     }
225     else {
226         if(compression > 9)
227             compression = 9;
228         if(compression < 1)
229             compression = 1;
230         char options[10];
231         snprintf(options, 10, "wb%d", compression);
232 
233         gzFile gzfile;
234         gzfile = gzopen(filename, options);
235         if(gzfile == NULL)
236             return -1;
237         gzputs(gzfile, xmldata);
238         gzclose(gzfile);
239     }
240 
241     return 0;
242 }
243 
244 
245 
addpar(const string & name,int val)246 void XMLwrapper::addpar(const string &name, int val)
247 {
248     addparams("par", 2, "name", name.c_str(), "value", stringFrom<int>(
249                   val).c_str());
250 }
251 
addparreal(const string & name,float val)252 void XMLwrapper::addparreal(const string &name, float val)
253 {
254     union { float in; uint32_t out; } convert;
255     char buf[11];
256     convert.in = val;
257     sprintf(buf, "0x%.8X", convert.out);
258     addparams("par_real", 3, "name", name.c_str(), "value",
259               stringFrom<float>(val).c_str(), "exact_value", buf);
260 }
261 
addparbool(const string & name,int val)262 void XMLwrapper::addparbool(const string &name, int val)
263 {
264     if(val != 0)
265         addparams("par_bool", 2, "name", name.c_str(), "value", "yes");
266     else
267         addparams("par_bool", 2, "name", name.c_str(), "value", "no");
268 }
269 
addparstr(const string & name,const string & val)270 void XMLwrapper::addparstr(const string &name, const string &val)
271 {
272     mxml_node_t *element = mxmlNewElement(node, "string");
273     mxmlElementSetAttr(element, "name", name.c_str());
274     mxmlNewText(element, 0, val.c_str());
275 }
276 
277 
beginbranch(const string & name)278 void XMLwrapper::beginbranch(const string &name)
279 {
280     if(verbose)
281         cout << "beginbranch()" << name << endl;
282     node = addparams(name.c_str(), 0);
283 }
284 
beginbranch(const string & name,int id)285 void XMLwrapper::beginbranch(const string &name, int id)
286 {
287     if(verbose)
288         cout << "beginbranch(" << id << ")" << name << endl;
289     node = addparams(name.c_str(), 1, "id", stringFrom<int>(id).c_str());
290 }
291 
endbranch()292 void XMLwrapper::endbranch()
293 {
294     if(verbose)
295         cout << "endbranch()" << node << "-" << mxmlGetElement(node)
296              << " To "
297              << mxmlGetParent(node) << "-" << mxmlGetElement(mxmlGetParent(node)) << endl;
298     node = mxmlGetParent(node);
299 }
300 
301 
302 //workaround for memory leak
trimLeadingWhite(const char * c)303 const char *trimLeadingWhite(const char *c)
304 {
305     while(isspace(*c))
306         ++c;
307     return c;
308 }
309 
310 /* LOAD XML members */
311 
loadXMLfile(const string & filename)312 int XMLwrapper::loadXMLfile(const string &filename)
313 {
314     cleanup();
315 
316     const char *xmldata = doloadfile(filename);
317     if(xmldata == NULL)
318         return -1;  //the file could not be loaded or uncompressed
319 
320     root = tree = mxmlLoadString(NULL, trimLeadingWhite(
321                                      xmldata), MXML_OPAQUE_CALLBACK);
322 
323     delete[] xmldata;
324 
325     if(tree == NULL)
326         return -2;  //this is not XML
327 
328     node = root = mxmlFindElement(tree,
329                                   tree,
330                                   "ZynAddSubFX-data",
331                                   NULL,
332                                   NULL,
333                                   MXML_DESCEND);
334     if(root == NULL)
335         return -3;  //the XML doesn't embbed zynaddsubfx data
336 
337     //fetch version information
338     _fileversion.set_major(stringTo<int>(mxmlElementGetAttr(root, "version-major")));
339     _fileversion.set_minor(stringTo<int>(mxmlElementGetAttr(root, "version-minor")));
340     _fileversion.set_revision(
341         stringTo<int>(mxmlElementGetAttr(root, "version-revision")));
342 
343     if(verbose)
344         cout << "loadXMLfile() version: " << _fileversion << endl;
345 
346     return 0;
347 }
348 
349 
doloadfile(const string & filename) const350 char *XMLwrapper::doloadfile(const string &filename) const
351 {
352     char  *xmldata = NULL;
353     gzFile gzfile  = gzopen(filename.c_str(), "rb");
354 
355     if(gzfile != NULL) { //The possibly compressed file opened
356         stringstream strBuf;             //reading stream
357         const int    bufSize = 500;      //fetch size
358         char fetchBuf[bufSize + 1];      //fetch buffer
359         int  read = 0;                   //chars read in last fetch
360 
361         fetchBuf[bufSize] = 0; //force null termination
362 
363         while(bufSize == (read = gzread(gzfile, fetchBuf, bufSize)))
364             strBuf << fetchBuf;
365 
366         fetchBuf[read] = 0; //Truncate last partial read
367         strBuf << fetchBuf;
368 
369         gzclose(gzfile);
370 
371         //Place data in output format
372         string tmp = strBuf.str();
373         xmldata = new char[tmp.size() + 1];
374         strncpy(xmldata, tmp.c_str(), tmp.size() + 1);
375     }
376 
377     return xmldata;
378 }
379 
putXMLdata(const char * xmldata)380 bool XMLwrapper::putXMLdata(const char *xmldata)
381 {
382     cleanup();
383 
384     if(xmldata == NULL)
385         return false;
386 
387     root = tree = mxmlLoadString(NULL, trimLeadingWhite(
388                                      xmldata), MXML_OPAQUE_CALLBACK);
389     if(tree == NULL)
390         return false;
391 
392     node = root = mxmlFindElement(tree,
393                                   tree,
394                                   "ZynAddSubFX-data",
395                                   NULL,
396                                   NULL,
397                                   MXML_DESCEND);
398     if(root == NULL)
399         return false;
400 
401     //fetch version information
402     _fileversion.set_major(stringTo<int>(mxmlElementGetAttr(root, "version-major")));
403     _fileversion.set_minor(stringTo<int>(mxmlElementGetAttr(root, "version-minor")));
404     _fileversion.set_revision(
405         stringTo<int>(mxmlElementGetAttr(root, "version-revision")));
406 
407     return true;
408 }
409 
410 
411 
enterbranch(const string & name)412 int XMLwrapper::enterbranch(const string &name)
413 {
414     if(verbose)
415         cout << "enterbranch() " << name << endl;
416     mxml_node_t *tmp = mxmlFindElement(node, node,
417                                        name.c_str(), NULL, NULL,
418                                        MXML_DESCEND_FIRST);
419     if(tmp == NULL)
420         return 0;
421 
422     node = tmp;
423     return 1;
424 }
425 
enterbranch(const string & name,int id)426 int XMLwrapper::enterbranch(const string &name, int id)
427 {
428     if(verbose)
429         cout << "enterbranch(" << id << ") " << name << endl;
430     mxml_node_t *tmp = mxmlFindElement(node, node,
431                                        name.c_str(), "id", stringFrom<int>(
432                                            id).c_str(), MXML_DESCEND_FIRST);
433     if(tmp == NULL)
434         return 0;
435 
436     node = tmp;
437     return 1;
438 }
439 
440 
exitbranch()441 void XMLwrapper::exitbranch()
442 {
443     if(verbose)
444         cout << "exitbranch()" << node << "-" << mxmlGetElement(node)
445              << " To "
446              << mxmlGetParent(node) << "-" << mxmlGetElement(mxmlGetParent(node)) << endl;
447     node = mxmlGetParent(node);
448 }
449 
450 
getbranchid(int min,int max) const451 int XMLwrapper::getbranchid(int min, int max) const
452 {
453     int id = stringTo<int>(mxmlElementGetAttr(node, "id"));
454     if((min == 0) && (max == 0))
455         return id;
456 
457     if(id < min)
458         id = min;
459     else
460     if(id > max)
461         id = max;
462 
463     return id;
464 }
465 
getpar(const string & name,int defaultpar,int min,int max) const466 int XMLwrapper::getpar(const string &name, int defaultpar, int min,
467                        int max) const
468 {
469     const mxml_node_t *tmp = mxmlFindElement(node,
470                                              node,
471                                              "par",
472                                              "name",
473                                              name.c_str(),
474                                              MXML_DESCEND_FIRST);
475 
476     if(tmp == NULL)
477         return defaultpar;
478 
479     const char *strval = mxmlElementGetAttr(tmp, "value");
480     if(strval == NULL)
481         return defaultpar;
482 
483     int val = stringTo<int>(strval);
484     if(val < min)
485         val = min;
486     else
487     if(val > max)
488         val = max;
489 
490     return val;
491 }
492 
getpar127(const string & name,int defaultpar) const493 int XMLwrapper::getpar127(const string &name, int defaultpar) const
494 {
495     return getpar(name, defaultpar, 0, 127);
496 }
497 
getparbool(const string & name,int defaultpar) const498 int XMLwrapper::getparbool(const string &name, int defaultpar) const
499 {
500     const mxml_node_t *tmp = mxmlFindElement(node,
501                                              node,
502                                              "par_bool",
503                                              "name",
504                                              name.c_str(),
505                                              MXML_DESCEND_FIRST);
506 
507     if(tmp == NULL)
508         return defaultpar;
509 
510     const char *strval = mxmlElementGetAttr(tmp, "value");
511     if(strval == NULL)
512         return defaultpar;
513 
514     if((strval[0] == 'Y') || (strval[0] == 'y'))
515         return 1;
516     else
517         return 0;
518 }
519 
getparstr(const string & name,char * par,int maxstrlen) const520 void XMLwrapper::getparstr(const string &name, char *par, int maxstrlen) const
521 {
522     ZERO(par, maxstrlen);
523     mxml_node_t *tmp = mxmlFindElement(node,
524                                        node,
525                                        "string",
526                                        "name",
527                                         name.c_str(),
528                                         MXML_DESCEND_FIRST);
529 
530     if(tmp == NULL)
531         return;
532     if(mxmlGetFirstChild(tmp) == NULL)
533         return;
534     if(mxmlGetType(mxmlGetFirstChild(tmp)) == MXML_OPAQUE) {
535         snprintf(par, maxstrlen, "%s", mxmlGetOpaque(mxmlGetFirstChild(tmp)));
536         return;
537     }
538     if((mxmlGetType(mxmlGetFirstChild(tmp)) == MXML_TEXT)
539        && (mxmlGetFirstChild(tmp) != NULL)) {
540         snprintf(par, maxstrlen, "%s", mxmlGetText(mxmlGetFirstChild(tmp),NULL));
541         return;
542     }
543 }
544 
getparstr(const string & name,const std::string & defaultpar) const545 string XMLwrapper::getparstr(const string &name,
546                              const std::string &defaultpar) const
547 {
548     mxml_node_t *tmp = mxmlFindElement(node,
549                                        node,
550                                        "string",
551                                        "name",
552                                        name.c_str(),
553                                        MXML_DESCEND_FIRST);
554 
555     if((tmp == NULL) || (mxmlGetFirstChild(tmp) == NULL))
556         return defaultpar;
557 
558     if(mxmlGetType(mxmlGetFirstChild(tmp)) == MXML_OPAQUE
559        && (mxmlGetOpaque(mxmlGetFirstChild(tmp)) != NULL))
560         return mxmlGetOpaque(mxmlGetFirstChild(tmp));
561 
562     if(mxmlGetType(mxmlGetFirstChild(tmp)) == MXML_TEXT
563        && (mxmlGetText(mxmlGetFirstChild(tmp),NULL) != NULL))
564         return mxmlGetText(mxmlGetFirstChild(tmp),NULL);
565 
566     return defaultpar;
567 }
568 
hasparreal(const char * name) const569 bool XMLwrapper::hasparreal(const char *name) const
570 {
571     const mxml_node_t *tmp = mxmlFindElement(node,
572                                              node,
573                                              "par_real",
574                                              "name",
575                                              name,
576                                              MXML_DESCEND_FIRST);
577     return tmp != nullptr;
578 }
579 
getparreal(const char * name,float defaultpar) const580 float XMLwrapper::getparreal(const char *name, float defaultpar) const
581 {
582     const mxml_node_t *tmp = mxmlFindElement(node,
583                                              node,
584                                              "par_real",
585                                              "name",
586                                              name,
587                                              MXML_DESCEND_FIRST);
588     if(tmp == NULL)
589         return defaultpar;
590 
591     const char *strval = mxmlElementGetAttr(tmp, "exact_value");
592     if (strval != NULL) {
593         union { float out; uint32_t in; } convert;
594         sscanf(strval+2, "%x", &convert.in);
595         return convert.out;
596     }
597 
598     strval = mxmlElementGetAttr(tmp, "value");
599     if(strval == NULL)
600         return defaultpar;
601 
602     return stringTo<float>(strval);
603 }
604 
getparreal(const char * name,float defaultpar,float min,float max) const605 float XMLwrapper::getparreal(const char *name,
606                              float defaultpar,
607                              float min,
608                              float max) const
609 {
610     float result = getparreal(name, defaultpar);
611 
612     if(result < min)
613         result = min;
614     else
615     if(result > max)
616         result = max;
617     return result;
618 }
619 
620 
621 /** Private members **/
622 
addparams(const char * name,unsigned int params,...) const623 mxml_node_t *XMLwrapper::addparams(const char *name, unsigned int params,
624                                    ...) const
625 {
626     /**@todo make this function send out a good error message if something goes
627      * wrong**/
628     mxml_node_t *element = mxmlNewElement(node, name);
629 
630     if(params) {
631         va_list variableList;
632         va_start(variableList, params);
633 
634         const char *ParamName;
635         const char *ParamValue;
636         while(params--) {
637             ParamName  = va_arg(variableList, const char *);
638             ParamValue = va_arg(variableList, const char *);
639             if(verbose)
640                 cout << "addparams()[" << params << "]=" << name << " "
641                      << ParamName << "=\"" << ParamValue << "\"" << endl;
642             mxmlElementSetAttr(element, ParamName, ParamValue);
643         }
644         va_end(variableList);
645     }
646     return element;
647 }
648 
XmlNode(std::string name_)649 XmlNode::XmlNode(std::string name_)
650     :name(name_)
651 {}
652 
operator [](std::string name)653 std::string &XmlNode::operator[](std::string name)
654 {
655     //fetch an existing one
656     for(auto &a:attrs)
657         if(a.name == name)
658             return a.value;
659 
660     //create a new one
661     attrs.push_back({name, ""});
662     return attrs[attrs.size()-1].value;
663 }
664 
has(std::string name_)665 bool XmlNode::has(std::string name_)
666 {
667     //fetch an existing one
668     for(auto &a:attrs)
669         if(a.name == name_)
670             return true;
671     return false;
672 }
673 
add(const XmlNode & node_)674 void XMLwrapper::add(const XmlNode &node_)
675 {
676     mxml_node_t *element = mxmlNewElement(node, node_.name.c_str());
677     for(auto attr:node_.attrs)
678         mxmlElementSetAttr(element, attr.name.c_str(),
679                 attr.value.c_str());
680 }
681 
getBranch(void) const682 std::vector<XmlNode> XMLwrapper::getBranch(void) const
683 {
684     std::vector<XmlNode> res;
685     mxml_node_t *current = mxmlGetFirstChild(node);
686     while(current) {
687         if(mxmlGetType(current) == MXML_ELEMENT) {
688 #if MXML_MAJOR_VERSION == 3
689             XmlNode n(mxmlGetElement(current));
690             int count = mxmlElementGetAttrCount(current);
691             const char *name;
692             const char *attrib;
693             for(int i = 0; i < count; ++i) {
694                 attrib = mxmlElementGetAttrByIndex(current, i, &name);
695                 if(name)
696                     n[name] = attrib;
697             }
698 #else
699             auto elm = current->value.element;
700             XmlNode n(elm.name);
701             for(int i = 0; i < elm.num_attrs; ++i) {
702                 auto &attr = elm.attrs[i];
703                 n[attr.name] = attr.value;
704             }
705 #endif
706             res.push_back(n);
707         }
708         current = mxmlWalkNext(current, node, MXML_NO_DESCEND);
709     }
710     return res;
711 }
712 
713 }
714