Remove PropertyMap from XMLNode class
[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
249         for (curchild = _children.begin(); curchild != _children.end(); ++curchild) {
250                 delete *curchild;
251         }
252
253         _children.clear ();
254
255         for (curprop = _proplist.begin(); curprop != _proplist.end(); ++curprop) {
256                 delete *curprop;
257         }
258
259         _proplist.clear ();
260 }
261
262 XMLNode&
263 XMLNode::operator= (const XMLNode& from)
264 {
265         if (&from == this) {
266                 return *this;
267         }
268
269         clear_lists ();
270
271         _name = from.name ();
272         set_content (from.content ());
273
274         const XMLPropertyList& props = from.properties ();
275
276         for (XMLPropertyConstIterator prop_iter = props.begin (); prop_iter != props.end (); ++prop_iter) {
277                 add_property ((*prop_iter)->name ().c_str (), (*prop_iter)->value ());
278         }
279
280         const XMLNodeList& nodes = from.children ();
281         for (XMLNodeConstIterator child_iter = nodes.begin (); child_iter != nodes.end (); ++child_iter) {
282                 add_child_copy (**child_iter);
283         }
284
285         return *this;
286 }
287
288 bool
289 XMLNode::operator== (const XMLNode& other) const
290 {
291         if (is_content () != other.is_content ()) {
292                 return false;
293         }
294
295         if (is_content ()) {
296                 if (content () != other.content ()) {
297                         return false;
298                 }
299         } else {
300                 if (name () != other.name ()) {
301                         return false;
302                 }
303         }
304
305         XMLPropertyList const& other_properties = other.properties ();
306
307         if (_proplist.size () != other_properties.size ()) {
308                 return false;
309         }
310
311         XMLPropertyConstIterator our_prop_iter = _proplist.begin();
312         XMLPropertyConstIterator other_prop_iter = other_properties.begin();
313
314         while (our_prop_iter != _proplist.end ()) {
315                 XMLProperty const* our_prop = *our_prop_iter;
316                 XMLProperty const* other_prop = *other_prop_iter;
317                 if (our_prop->name () != other_prop->name () || our_prop->value () != other_prop->value ()) {
318                         return false;
319                 }
320                 ++our_prop_iter;
321                 ++other_prop_iter;
322         }
323
324         XMLNodeList const& other_children = other.children();
325
326         if (_children.size() != other_children.size()) {
327                 return false;
328         }
329
330         XMLNodeConstIterator our_child_iter = _children.begin ();
331         XMLNodeConstIterator other_child_iter = other_children.begin ();
332
333         while (our_child_iter != _children.end()) {
334                 XMLNode const* our_child = *our_child_iter;
335                 XMLNode const* other_child = *other_child_iter;
336
337                 if (*our_child != *other_child) {
338                         return false;
339                 }
340                 ++our_child_iter;
341                 ++other_child_iter;
342         }
343         return true;
344 }
345
346 bool
347 XMLNode::operator!= (const XMLNode& other) const
348 {
349         return !(*this == other);
350 }
351
352 const string&
353 XMLNode::set_content(const string& c)
354 {
355         if (c.empty()) {
356                 _is_content = false;
357         } else {
358                 _is_content = true;
359         }
360
361         _content = c;
362
363         return _content;
364 }
365
366 XMLNode*
367 XMLNode::child (const char* name) const
368 {
369         /* returns first child matching name */
370
371         XMLNodeConstIterator cur;
372
373         if (name == 0) {
374                 return 0;
375         }
376
377         for (cur = _children.begin(); cur != _children.end(); ++cur) {
378                 if ((*cur)->name() == name) {
379                         return *cur;
380                 }
381         }
382
383         return 0;
384 }
385
386 const XMLNodeList&
387 XMLNode::children(const string& n) const
388 {
389         /* returns all children matching name */
390
391         XMLNodeConstIterator cur;
392
393         if (n.empty()) {
394                 return _children;
395         }
396
397         _selected_children.clear();
398
399         for (cur = _children.begin(); cur != _children.end(); ++cur) {
400                 if ((*cur)->name() == n) {
401                         _selected_children.insert(_selected_children.end(), *cur);
402                 }
403         }
404
405         return _selected_children;
406 }
407
408 XMLNode*
409 XMLNode::add_child(const char* n)
410 {
411         return add_child_copy(XMLNode (n));
412 }
413
414 void
415 XMLNode::add_child_nocopy(XMLNode& n)
416 {
417         _children.insert(_children.end(), &n);
418 }
419
420 XMLNode*
421 XMLNode::add_child_copy(const XMLNode& n)
422 {
423         XMLNode *copy = new XMLNode(n);
424         _children.insert(_children.end(), copy);
425         return copy;
426 }
427
428 boost::shared_ptr<XMLSharedNodeList>
429 XMLTree::find(const string xpath, XMLNode* node) const
430 {
431         xmlXPathContext* ctxt;
432         xmlDocPtr doc = 0;
433
434         if (node) {
435                 doc = xmlNewDoc(xml_version);
436                 writenode(doc, node, doc->children, 1);
437                 ctxt = xmlXPathNewContext(doc);
438         } else {
439                 ctxt = xmlXPathNewContext(_doc);
440         }
441
442         boost::shared_ptr<XMLSharedNodeList> result =
443                 boost::shared_ptr<XMLSharedNodeList>(find_impl(ctxt, xpath));
444
445         xmlXPathFreeContext(ctxt);
446         if (doc) {
447                 xmlFreeDoc (doc);
448         }
449
450         return result;
451 }
452
453 std::string
454 XMLNode::attribute_value()
455 {
456         XMLNodeList children = this->children();
457         assert(!_is_content);
458         assert(children.size() == 1);
459         XMLNode* child = *(children.begin());
460         assert(child->is_content());
461         return child->content();
462 }
463
464 XMLNode*
465 XMLNode::add_content(const string& c)
466 {
467         return add_child_copy(XMLNode (string(), c));
468 }
469
470 XMLProperty const *
471 XMLNode::property(const char* name) const
472 {
473         XMLPropertyConstIterator iter = _proplist.begin();
474
475         while (iter != _proplist.end()) {
476                 if ((*iter)->name() == name) {
477                         return *iter;
478                 }
479                 ++iter;
480         }
481
482         return 0;
483 }
484
485 XMLProperty const *
486 XMLNode::property(const string& name) const
487 {
488         XMLPropertyConstIterator iter = _proplist.begin();
489
490         while (iter != _proplist.end()) {
491                 if ((*iter)->name() == name) {
492                         return *iter;
493                 }
494                 ++iter;
495         }
496         return 0;
497 }
498
499 XMLProperty *
500 XMLNode::property(const char* name)
501 {
502         XMLPropertyIterator iter = _proplist.begin();
503
504         while (iter != _proplist.end()) {
505                 if ((*iter)->name() == name) {
506                         return *iter;
507                 }
508                 ++iter;
509         }
510         return 0;
511 }
512
513 XMLProperty *
514 XMLNode::property(const string& name)
515 {
516         XMLPropertyIterator iter = _proplist.begin();
517
518         while (iter != _proplist.end()) {
519                 if ((*iter)->name() == name) {
520                         return *iter;
521                 }
522                 ++iter;
523         }
524
525         return 0;
526 }
527
528 bool
529 XMLNode::has_property_with_value (const string& name, const string& value) const
530 {
531         XMLPropertyConstIterator iter = _proplist.begin();
532
533         while (iter != _proplist.end()) {
534                 if ((*iter)->name() == name && (*iter)->value() == value) {
535                         return true;
536                 }
537                 ++iter;
538         }
539         return false;
540 }
541
542 XMLProperty*
543 XMLNode::add_property(const char* name, const string& value)
544 {
545         XMLPropertyIterator iter = _proplist.begin();
546
547         while (iter != _proplist.end()) {
548                 if ((*iter)->name() == name) {
549                         (*iter)->set_value (value);
550                         return *iter;
551                 }
552                 ++iter;
553         }
554
555         XMLProperty* new_property = new XMLProperty(name, value);
556
557         if (!new_property) {
558                 return 0;
559         }
560
561         _proplist.insert(_proplist.end(), new_property);
562
563         return new_property;
564 }
565
566 XMLProperty*
567 XMLNode::add_property(const char* n, const char* v)
568 {
569         string vs(v);
570         return add_property(n, vs);
571 }
572
573 XMLProperty*
574 XMLNode::add_property(const char* name, const long value)
575 {
576         char str[64];
577         snprintf(str, sizeof(str), "%ld", value);
578         return add_property(name, str);
579 }
580
581 void
582 XMLNode::remove_property(const string& name)
583 {
584         XMLPropertyIterator iter = _proplist.begin();
585
586         while (iter != _proplist.end()) {
587                 if ((*iter)->name() == name) {
588                         XMLProperty* property = *iter;
589                         _proplist.erase (iter);
590                         delete property;
591                         break;
592                 }
593                 ++iter;
594         }
595 }
596
597 /** Remove any property with the given name from this node and its children */
598 void
599 XMLNode::remove_property_recursively(const string& n)
600 {
601         remove_property (n);
602         for (XMLNodeIterator i = _children.begin(); i != _children.end(); ++i) {
603                 (*i)->remove_property_recursively (n);
604         }
605 }
606
607 void
608 XMLNode::remove_nodes(const string& n)
609 {
610         XMLNodeIterator i = _children.begin();
611         while (i != _children.end()) {
612                 if ((*i)->name() == n) {
613                         i = _children.erase (i);
614                 } else {
615                         ++i;
616                 }
617         }
618 }
619
620 void
621 XMLNode::remove_nodes_and_delete(const string& n)
622 {
623         XMLNodeIterator i = _children.begin();
624
625         while (i != _children.end()) {
626                 if ((*i)->name() == n) {
627                         delete *i;
628                         i = _children.erase (i);
629                 } else {
630                         ++i;
631                 }
632         }
633 }
634
635 void
636 XMLNode::remove_nodes_and_delete(const string& propname, const string& val)
637 {
638         XMLNodeIterator i = _children.begin();
639         XMLProperty const * prop;
640
641         while (i != _children.end()) {
642                 prop = (*i)->property(propname);
643                 if (prop && prop->value() == val) {
644                         delete *i;
645                         i = _children.erase(i);
646                 } else {
647                         ++i;
648                 }
649         }
650 }
651
652 XMLProperty::XMLProperty(const string& n, const string& v)
653         : _name(n)
654         , _value(v)
655 {
656         // Normalize property name (replace '_' with '-' as old session are inconsistent)
657         for (size_t i = 0; i < _name.length(); ++i) {
658                 if (_name[i] == '_') {
659                         _name[i] = '-';
660                 }
661         }
662 }
663
664 XMLProperty::~XMLProperty()
665 {
666 }
667
668 static XMLNode*
669 readnode(xmlNodePtr node)
670 {
671         string name, content;
672         xmlNodePtr child;
673         XMLNode* tmp;
674         xmlAttrPtr attr;
675
676         if (node->name) {
677                 name = (const char*)node->name;
678         }
679
680         tmp = new XMLNode(name);
681
682         for (attr = node->properties; attr; attr = attr->next) {
683                 content = "";
684                 if (attr->children) {
685                         content = (char*)attr->children->content;
686                 }
687                 tmp->add_property((const char*)attr->name, content);
688         }
689
690         if (node->content) {
691                 tmp->set_content((char*)node->content);
692         } else {
693                 tmp->set_content(string());
694         }
695
696         for (child = node->children; child; child = child->next) {
697                 tmp->add_child_nocopy (*readnode(child));
698         }
699
700         return tmp;
701 }
702
703 static void
704 writenode(xmlDocPtr doc, XMLNode* n, xmlNodePtr p, int root = 0)
705 {
706         xmlNodePtr node;
707
708         if (root) {
709                 node = doc->children = xmlNewDocNode(doc, 0, (const xmlChar*) n->name().c_str(), 0);
710         } else {
711                 node = xmlNewChild(p, 0, (const xmlChar*) n->name().c_str(), 0);
712         }
713
714         if (n->is_content()) {
715                 node->type = XML_TEXT_NODE;
716                 xmlNodeSetContentLen(node, (const xmlChar*)n->content().c_str(), n->content().length());
717         }
718
719         const XMLPropertyList& props = n->properties();
720
721         for (XMLPropertyConstIterator prop_iter = props.begin (); prop_iter != props.end ();
722              ++prop_iter) {
723                 xmlSetProp (node, (const xmlChar*)(*prop_iter)->name ().c_str (),
724                             (const xmlChar*)(*prop_iter)->value ().c_str ());
725         }
726
727         const XMLNodeList& children = n->children ();
728         for (XMLNodeConstIterator child_iter = children.begin (); child_iter != children.end ();
729              ++child_iter) {
730                 writenode (doc, *child_iter, node);
731         }
732 }
733
734 static XMLSharedNodeList* find_impl(xmlXPathContext* ctxt, const string& xpath)
735 {
736         xmlXPathObject* result = xmlXPathEval((const xmlChar*)xpath.c_str(), ctxt);
737
738         if (!result) {
739                 xmlXPathFreeContext(ctxt);
740                 xmlFreeDoc(ctxt->doc);
741
742                 throw XMLException("Invalid XPath: " + xpath);
743         }
744
745         if (result->type != XPATH_NODESET) {
746                 xmlXPathFreeObject(result);
747                 xmlXPathFreeContext(ctxt);
748                 xmlFreeDoc(ctxt->doc);
749
750                 throw XMLException("Only nodeset result types are supported.");
751         }
752
753         xmlNodeSet* nodeset = result->nodesetval;
754         XMLSharedNodeList* nodes = new XMLSharedNodeList();
755         if (nodeset) {
756                 for (int i = 0; i < nodeset->nodeNr; ++i) {
757                         XMLNode* node = readnode(nodeset->nodeTab[i]);
758                         nodes->push_back(boost::shared_ptr<XMLNode>(node));
759                 }
760         } else {
761                 // return empty set
762         }
763
764         xmlXPathFreeObject(result);
765
766         return nodes;
767 }
768
769 /** Dump a node, its properties and children to a stream */
770 void
771 XMLNode::dump (ostream& s, string p) const
772 {
773         if (_is_content) {
774                 s << p << "  " << content() << "\n";
775         } else {
776                 s << p << "<" << _name;
777                 for (XMLPropertyList::const_iterator i = _proplist.begin(); i != _proplist.end(); ++i) {
778                         s << " " << (*i)->name() << "=\"" << (*i)->value() << "\"";
779                 }
780                 s << ">\n";
781
782                 for (XMLNodeList::const_iterator i = _children.begin(); i != _children.end(); ++i) {
783                         (*i)->dump (s, p + "  ");
784                 }
785
786                 s << p << "</" << _name << ">\n";
787         }
788 }