make BasicUI (for control surfaces) loop toggle work like the GUI
[ardour.git] / libs / pbd / xml++.cc
1 /* xml++.cc
2  * libxml++ and this file are copyright (C) 2000 by Ari Johnson, and
3  * are covered by the GNU Lesser General Public License, which should be
4  * included with libxml++ as the file COPYING.
5  * Modified for Ardour and released under the same terms.
6  */
7
8 #include <iostream>
9 #include "pbd/xml++.h"
10 #include <libxml/debugXML.h>
11 #include <libxml/xpath.h>
12 #include <libxml/xpathInternals.h>
13
14 xmlChar* xml_version = xmlCharStrdup("1.0");
15
16 using namespace std;
17
18 static XMLNode*           readnode(xmlNodePtr);
19 static void               writenode(xmlDocPtr, XMLNode*, xmlNodePtr, int);
20 static XMLSharedNodeList* find_impl(xmlXPathContext* ctxt, const string& xpath);
21
22 XMLTree::XMLTree()
23         : _filename()
24         , _root(0)
25         , _doc (0)
26         , _compression(0)
27 {
28 }
29
30 XMLTree::XMLTree(const string& fn, bool validate)
31         : _filename(fn)
32         , _root(0)
33         , _doc (0)
34         , _compression(0)
35 {
36         read_internal(validate);
37 }
38
39 XMLTree::XMLTree(const XMLTree* from)
40         : _filename(from->filename())
41         , _root(new XMLNode(*from->root()))
42         , _doc (xmlCopyDoc (from->_doc, 1))
43         , _compression(from->compression())
44 {
45
46 }
47
48 XMLTree::~XMLTree()
49 {
50         delete _root;
51
52         if (_doc) {
53                 xmlFreeDoc (_doc);
54         }
55 }
56
57 int
58 XMLTree::set_compression(int c)
59 {
60         if (c > 9) {
61                 c = 9;
62         } else if (c < 0) {
63                 c = 0;
64         }
65
66         _compression = c;
67
68         return _compression;
69 }
70
71 bool
72 XMLTree::read_internal(bool validate)
73 {
74         //shouldnt be used anywhere ATM, remove if so!
75         assert(!validate);
76
77         delete _root;
78         _root = 0;
79
80         if (_doc) {
81                 xmlFreeDoc (_doc);
82                 _doc = 0;
83         }
84
85         /* create a parser context */
86         xmlParserCtxtPtr ctxt = xmlNewParserCtxt();
87         if (ctxt == NULL) {
88                 return false;
89         }
90
91         xmlKeepBlanksDefault(0);
92         /* parse the file, activating the DTD validation option */
93         if (validate) {
94                 _doc = xmlCtxtReadFile(ctxt, _filename.c_str(), NULL, XML_PARSE_DTDVALID);
95         } else {
96                 _doc = xmlCtxtReadFile(ctxt, _filename.c_str(), NULL, XML_PARSE_HUGE);
97         }
98
99         /* check if parsing suceeded */
100         if (_doc == NULL) {
101                 xmlFreeParserCtxt(ctxt);
102                 return false;
103         } else {
104                 /* check if validation suceeded */
105                 if (validate && ctxt->valid == 0) {
106                         xmlFreeParserCtxt(ctxt);
107                         throw XMLException("Failed to validate document " + _filename);
108                 }
109         }
110
111         _root = readnode(xmlDocGetRootElement(_doc));
112
113         /* free up the parser context */
114         xmlFreeParserCtxt(ctxt);
115
116         return true;
117 }
118
119 bool
120 XMLTree::read_buffer(const string& buffer)
121 {
122         xmlDocPtr doc;
123
124         _filename = "";
125
126         delete _root;
127         _root = 0;
128
129         doc = xmlParseMemory(const_cast<char*>(buffer.c_str()), buffer.length());
130         if (!doc) {
131                 return false;
132         }
133
134         _root = readnode(xmlDocGetRootElement(doc));
135         xmlFreeDoc(doc);
136
137         return true;
138 }
139
140
141 bool
142 XMLTree::write() const
143 {
144         xmlDocPtr doc;
145         XMLNodeList children;
146         int result;
147
148         xmlKeepBlanksDefault(0);
149         doc = xmlNewDoc(xml_version);
150         xmlSetDocCompressMode(doc, _compression);
151         writenode(doc, _root, doc->children, 1);
152         result = xmlSaveFormatFileEnc(_filename.c_str(), doc, "UTF-8", 1);
153 #ifndef NDEBUG
154         if (result == -1) {
155                 xmlErrorPtr xerr = xmlGetLastError ();
156                 if (!xerr) {
157                         std::cerr << "unknown XML error during xmlSaveFormatFileEnc()." << std::endl;
158                 } else {
159                         std::cerr << "xmlSaveFormatFileEnc: error"
160                                 << " domain: " << xerr->domain
161                                 << " code: " << xerr->code
162                                 << " msg: " << xerr->message
163                                 << std::endl;
164                 }
165         }
166 #endif
167         xmlFreeDoc(doc);
168
169         if (result == -1) {
170                 return false;
171         }
172
173         return true;
174 }
175
176 void
177 XMLTree::debug(FILE* out) const
178 {
179 #ifdef LIBXML_DEBUG_ENABLED
180         xmlDocPtr doc;
181         XMLNodeList children;
182
183         xmlKeepBlanksDefault(0);
184         doc = xmlNewDoc(xml_version);
185         xmlSetDocCompressMode(doc, _compression);
186         writenode(doc, _root, doc->children, 1);
187         xmlDebugDumpDocument (out, doc);
188         xmlFreeDoc(doc);
189 #endif
190 }
191
192 const string&
193 XMLTree::write_buffer() const
194 {
195         static string retval;
196         char* ptr;
197         int len;
198         xmlDocPtr doc;
199         XMLNodeList children;
200
201         xmlKeepBlanksDefault(0);
202         doc = xmlNewDoc(xml_version);
203         xmlSetDocCompressMode(doc, _compression);
204         writenode(doc, _root, doc->children, 1);
205         xmlDocDumpMemory(doc, (xmlChar **) & ptr, &len);
206         xmlFreeDoc(doc);
207
208         retval = ptr;
209
210         free(ptr);
211
212         return retval;
213 }
214
215 XMLNode::XMLNode(const string& n)
216         : _name(n)
217         , _is_content(false)
218 {
219 }
220
221 XMLNode::XMLNode(const string& n, const string& c)
222         : _name(n)
223         , _is_content(true)
224         , _content(c)
225 {
226 }
227
228 XMLNode::XMLNode(const XMLNode& from)
229 {
230         *this = from;
231 }
232
233 XMLNode::~XMLNode()
234 {
235         clear_lists ();
236 }
237
238 void
239 XMLNode::clear_lists ()
240 {
241         XMLNodeIterator curchild;
242         XMLPropertyIterator curprop;
243
244         _selected_children.clear ();
245         _propmap.clear ();
246
247         for (curchild = _children.begin(); curchild != _children.end(); ++curchild) {
248                 delete *curchild;
249         }
250
251         _children.clear ();
252
253         for (curprop = _proplist.begin(); curprop != _proplist.end(); ++curprop) {
254                 delete *curprop;
255         }
256
257         _proplist.clear ();
258 }
259
260 XMLNode&
261 XMLNode::operator= (const XMLNode& from)
262 {
263         if (&from != this) {
264
265                 XMLPropertyList props;
266                 XMLPropertyIterator curprop;
267                 XMLNodeList nodes;
268                 XMLNodeIterator curnode;
269
270                 clear_lists ();
271
272                 _name = from.name();
273                 set_content(from.content());
274
275                 props = from.properties();
276                 for (curprop = props.begin(); curprop != props.end(); ++curprop) {
277                         add_property((*curprop)->name().c_str(), (*curprop)->value());
278                 }
279
280                 nodes = from.children();
281                 for (curnode = nodes.begin(); curnode != nodes.end(); ++curnode) {
282                         add_child_copy(**curnode);
283                 }
284         }
285
286         return *this;
287 }
288
289 const string&
290 XMLNode::set_content(const string& c)
291 {
292         if (c.empty()) {
293                 _is_content = false;
294         } else {
295                 _is_content = true;
296         }
297
298         _content = c;
299
300         return _content;
301 }
302
303 XMLNode*
304 XMLNode::child (const char* name) const
305 {
306         /* returns first child matching name */
307
308         XMLNodeConstIterator cur;
309
310         if (name == 0) {
311                 return 0;
312         }
313
314         for (cur = _children.begin(); cur != _children.end(); ++cur) {
315                 if ((*cur)->name() == name) {
316                         return *cur;
317                 }
318         }
319
320         return 0;
321 }
322
323 const XMLNodeList&
324 XMLNode::children(const string& n) const
325 {
326         /* returns all children matching name */
327
328         XMLNodeConstIterator cur;
329
330         if (n.empty()) {
331                 return _children;
332         }
333
334         _selected_children.clear();
335
336         for (cur = _children.begin(); cur != _children.end(); ++cur) {
337                 if ((*cur)->name() == n) {
338                         _selected_children.insert(_selected_children.end(), *cur);
339                 }
340         }
341
342         return _selected_children;
343 }
344
345 XMLNode*
346 XMLNode::add_child(const char* n)
347 {
348         return add_child_copy(XMLNode (n));
349 }
350
351 void
352 XMLNode::add_child_nocopy(XMLNode& n)
353 {
354         _children.insert(_children.end(), &n);
355 }
356
357 XMLNode*
358 XMLNode::add_child_copy(const XMLNode& n)
359 {
360         XMLNode *copy = new XMLNode(n);
361         _children.insert(_children.end(), copy);
362         return copy;
363 }
364
365 boost::shared_ptr<XMLSharedNodeList>
366 XMLTree::find(const string xpath, XMLNode* node) const
367 {
368         xmlXPathContext* ctxt;
369         xmlDocPtr doc = 0;
370
371         if (node) {
372                 doc = xmlNewDoc(xml_version);
373                 writenode(doc, node, doc->children, 1);
374                 ctxt = xmlXPathNewContext(doc);
375         } else {
376                 ctxt = xmlXPathNewContext(_doc);
377         }
378
379         boost::shared_ptr<XMLSharedNodeList> result =
380                 boost::shared_ptr<XMLSharedNodeList>(find_impl(ctxt, xpath));
381
382         xmlXPathFreeContext(ctxt);
383         if (doc) {
384                 xmlFreeDoc (doc);
385         }
386
387         return result;
388 }
389
390 std::string
391 XMLNode::attribute_value()
392 {
393         XMLNodeList children = this->children();
394         assert(!_is_content);
395         assert(children.size() == 1);
396         XMLNode* child = *(children.begin());
397         assert(child->is_content());
398         return child->content();
399 }
400
401 XMLNode*
402 XMLNode::add_content(const string& c)
403 {
404         return add_child_copy(XMLNode (string(), c));
405 }
406
407 XMLProperty*
408 XMLNode::property(const char* n)
409 {
410         string ns(n);
411         map<string,XMLProperty*>::iterator iter;
412
413         if ((iter = _propmap.find(ns)) != _propmap.end()) {
414                 return iter->second;
415         }
416
417         return 0;
418 }
419
420 XMLProperty*
421 XMLNode::property(const string& ns)
422 {
423         map<string,XMLProperty*>::iterator iter;
424
425         if ((iter = _propmap.find(ns)) != _propmap.end()) {
426                 return iter->second;
427         }
428
429         return 0;
430 }
431
432 XMLProperty*
433 XMLNode::add_property(const char* n, const string& v)
434 {
435         string ns(n);
436         map<string,XMLProperty*>::iterator iter;
437
438         if ((iter = _propmap.find(ns)) != _propmap.end()) {
439                 iter->second->set_value (v);
440                 return iter->second;
441         }
442
443         XMLProperty* tmp = new XMLProperty(ns, v);
444
445         if (!tmp) {
446                 return 0;
447         }
448
449         _propmap[tmp->name()] = tmp;
450         _proplist.insert(_proplist.end(), tmp);
451
452         return tmp;
453 }
454
455 XMLProperty*
456 XMLNode::add_property(const char* n, const char* v)
457 {
458         string vs(v);
459         return add_property(n, vs);
460 }
461
462 XMLProperty*
463 XMLNode::add_property(const char* name, const long value)
464 {
465         char str[64];
466         snprintf(str, sizeof(str), "%ld", value);
467         return add_property(name, str);
468 }
469
470 void
471 XMLNode::remove_property(const string& n)
472 {
473         if (_propmap.find(n) != _propmap.end()) {
474                 XMLProperty* p = _propmap[n];
475                 XMLPropertyIterator i = std::find(_proplist.begin(), _proplist.end(), p);
476                 if (i != _proplist.end ()) {
477                         _proplist.erase (i);
478                 }
479                 delete p;
480                 _propmap.erase(n);
481         }
482 }
483
484 /** Remove any property with the given name from this node and its children */
485 void
486 XMLNode::remove_property_recursively(const string& n)
487 {
488         remove_property (n);
489         for (XMLNodeIterator i = _children.begin(); i != _children.end(); ++i) {
490                 (*i)->remove_property_recursively (n);
491         }
492 }
493
494 void
495 XMLNode::remove_nodes(const string& n)
496 {
497         XMLNodeIterator i = _children.begin();
498         while (i != _children.end()) {
499                 if ((*i)->name() == n) {
500                         i = _children.erase (i);
501                 } else {
502                         ++i;
503                 }
504         }
505 }
506
507 void
508 XMLNode::remove_nodes_and_delete(const string& n)
509 {
510         XMLNodeIterator i = _children.begin();
511
512         while (i != _children.end()) {
513                 if ((*i)->name() == n) {
514                         delete *i;
515                         i = _children.erase (i);
516                 } else {
517                         ++i;
518                 }
519         }
520 }
521
522 void
523 XMLNode::remove_nodes_and_delete(const string& propname, const string& val)
524 {
525         XMLNodeIterator i = _children.begin();
526         XMLProperty* prop;
527
528         while (i != _children.end()) {
529                 prop = (*i)->property(propname);
530                 if (prop && prop->value() == val) {
531                         delete *i;
532                         i = _children.erase(i);
533                 } else {
534                         ++i;
535                 }
536         }
537 }
538
539 XMLProperty::XMLProperty(const string& n, const string& v)
540         : _name(n)
541         , _value(v)
542 {
543         // Normalize property name (replace '_' with '-' as old session are inconsistent)
544         for (size_t i = 0; i < _name.length(); ++i) {
545                 if (_name[i] == '_') {
546                         _name[i] = '-';
547                 }
548         }
549 }
550
551 XMLProperty::~XMLProperty()
552 {
553 }
554
555 static XMLNode*
556 readnode(xmlNodePtr node)
557 {
558         string name, content;
559         xmlNodePtr child;
560         XMLNode* tmp;
561         xmlAttrPtr attr;
562
563         if (node->name) {
564                 name = (const char*)node->name;
565         }
566
567         tmp = new XMLNode(name);
568
569         for (attr = node->properties; attr; attr = attr->next) {
570                 content = "";
571                 if (attr->children) {
572                         content = (char*)attr->children->content;
573                 }
574                 tmp->add_property((const char*)attr->name, content);
575         }
576
577         if (node->content) {
578                 tmp->set_content((char*)node->content);
579         } else {
580                 tmp->set_content(string());
581         }
582
583         for (child = node->children; child; child = child->next) {
584                 tmp->add_child_nocopy (*readnode(child));
585         }
586
587         return tmp;
588 }
589
590 static void
591 writenode(xmlDocPtr doc, XMLNode* n, xmlNodePtr p, int root = 0)
592 {
593         XMLPropertyList props;
594         XMLPropertyIterator curprop;
595         XMLNodeList children;
596         XMLNodeIterator curchild;
597         xmlNodePtr node;
598
599         if (root) {
600                 node = doc->children = xmlNewDocNode(doc, 0, (const xmlChar*) n->name().c_str(), 0);
601         } else {
602                 node = xmlNewChild(p, 0, (const xmlChar*) n->name().c_str(), 0);
603         }
604
605         if (n->is_content()) {
606                 node->type = XML_TEXT_NODE;
607                 xmlNodeSetContentLen(node, (const xmlChar*)n->content().c_str(), n->content().length());
608         }
609
610         props = n->properties();
611         for (curprop = props.begin(); curprop != props.end(); ++curprop) {
612                 xmlSetProp(node, (const xmlChar*) (*curprop)->name().c_str(), (const xmlChar*) (*curprop)->value().c_str());
613         }
614
615         children = n->children();
616         for (curchild = children.begin(); curchild != children.end(); ++curchild) {
617                 writenode(doc, *curchild, node);
618         }
619 }
620
621 static XMLSharedNodeList* find_impl(xmlXPathContext* ctxt, const string& xpath)
622 {
623         xmlXPathObject* result = xmlXPathEval((const xmlChar*)xpath.c_str(), ctxt);
624
625         if (!result) {
626                 xmlXPathFreeContext(ctxt);
627                 xmlFreeDoc(ctxt->doc);
628
629                 throw XMLException("Invalid XPath: " + xpath);
630         }
631
632         if (result->type != XPATH_NODESET) {
633                 xmlXPathFreeObject(result);
634                 xmlXPathFreeContext(ctxt);
635                 xmlFreeDoc(ctxt->doc);
636
637                 throw XMLException("Only nodeset result types are supported.");
638         }
639
640         xmlNodeSet* nodeset = result->nodesetval;
641         XMLSharedNodeList* nodes = new XMLSharedNodeList();
642         if (nodeset) {
643                 for (int i = 0; i < nodeset->nodeNr; ++i) {
644                         XMLNode* node = readnode(nodeset->nodeTab[i]);
645                         nodes->push_back(boost::shared_ptr<XMLNode>(node));
646                 }
647         } else {
648                 // return empty set
649         }
650
651         xmlXPathFreeObject(result);
652
653         return nodes;
654 }
655
656 /** Dump a node, its properties and children to a stream */
657 void
658 XMLNode::dump (ostream& s, string p) const
659 {
660         if (_is_content) {
661                 s << p << "  " << content() << "\n";
662         } else {
663                 s << p << "<" << _name;
664                 for (XMLPropertyList::const_iterator i = _proplist.begin(); i != _proplist.end(); ++i) {
665                         s << " " << (*i)->name() << "=\"" << (*i)->value() << "\"";
666                 }
667                 s << ">\n";
668
669                 for (XMLNodeList::const_iterator i = _children.begin(); i != _children.end(); ++i) {
670                         (*i)->dump (s, p + "  ");
671                 }
672
673                 s << p << "</" << _name << ">\n";
674         }
675 }