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