Avoid assert() when loading xml: Throw an XMLerror if attribute_value fails.
[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, bool to_tree_doc)
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         if (to_tree_doc) {
139                 if (_doc) {
140                         xmlFreeDoc (_doc);
141                 }
142                 _doc = doc;
143         } else {
144                 xmlFreeDoc (doc);
145         }
146
147         return true;
148 }
149
150
151 bool
152 XMLTree::write() const
153 {
154         xmlDocPtr doc;
155         XMLNodeList children;
156         int result;
157
158         xmlKeepBlanksDefault(0);
159         doc = xmlNewDoc(xml_version);
160         xmlSetDocCompressMode(doc, _compression);
161         writenode(doc, _root, doc->children, 1);
162         result = xmlSaveFormatFileEnc(_filename.c_str(), doc, "UTF-8", 1);
163 #ifndef NDEBUG
164         if (result == -1) {
165                 xmlErrorPtr xerr = xmlGetLastError ();
166                 if (!xerr) {
167                         std::cerr << "unknown XML error during xmlSaveFormatFileEnc()." << std::endl;
168                 } else {
169                         std::cerr << "xmlSaveFormatFileEnc: error"
170                                 << " domain: " << xerr->domain
171                                 << " code: " << xerr->code
172                                 << " msg: " << xerr->message
173                                 << std::endl;
174                 }
175         }
176 #endif
177         xmlFreeDoc(doc);
178
179         if (result == -1) {
180                 return false;
181         }
182
183         return true;
184 }
185
186 void
187 XMLTree::debug(FILE* out) const
188 {
189 #ifdef LIBXML_DEBUG_ENABLED
190         xmlDocPtr doc;
191         XMLNodeList children;
192
193         xmlKeepBlanksDefault(0);
194         doc = xmlNewDoc(xml_version);
195         xmlSetDocCompressMode(doc, _compression);
196         writenode(doc, _root, doc->children, 1);
197         xmlDebugDumpDocument (out, doc);
198         xmlFreeDoc(doc);
199 #endif
200 }
201
202 const string&
203 XMLTree::write_buffer() const
204 {
205         static string retval;
206         char* ptr;
207         int len;
208         xmlDocPtr doc;
209         XMLNodeList children;
210
211         xmlKeepBlanksDefault(0);
212         doc = xmlNewDoc(xml_version);
213         xmlSetDocCompressMode(doc, _compression);
214         writenode(doc, _root, doc->children, 1);
215         xmlDocDumpMemory(doc, (xmlChar **) & ptr, &len);
216         xmlFreeDoc(doc);
217
218         retval = ptr;
219
220         free(ptr);
221
222         return retval;
223 }
224
225 static const int PROPERTY_RESERVE_COUNT = 16;
226
227 XMLNode::XMLNode(const string& n)
228         : _name(n)
229         , _is_content(false)
230 {
231         _proplist.reserve (PROPERTY_RESERVE_COUNT);
232 }
233
234 XMLNode::XMLNode(const string& n, const string& c)
235         : _name(n)
236         , _is_content(true)
237         , _content(c)
238 {
239         _proplist.reserve (PROPERTY_RESERVE_COUNT);
240 }
241
242 XMLNode::XMLNode(const XMLNode& from)
243 {
244         _proplist.reserve (PROPERTY_RESERVE_COUNT);
245         *this = from;
246 }
247
248 XMLNode::~XMLNode()
249 {
250         clear_lists ();
251 }
252
253 void
254 XMLNode::clear_lists ()
255 {
256         XMLNodeIterator curchild;
257         XMLPropertyIterator curprop;
258
259         _selected_children.clear ();
260
261         for (curchild = _children.begin(); curchild != _children.end(); ++curchild) {
262                 delete *curchild;
263         }
264
265         _children.clear ();
266
267         for (curprop = _proplist.begin(); curprop != _proplist.end(); ++curprop) {
268                 delete *curprop;
269         }
270
271         _proplist.clear ();
272 }
273
274 XMLNode&
275 XMLNode::operator= (const XMLNode& from)
276 {
277         if (&from == this) {
278                 return *this;
279         }
280
281         clear_lists ();
282
283         _name = from.name ();
284         set_content (from.content ());
285
286         const XMLPropertyList& props = from.properties ();
287
288         for (XMLPropertyConstIterator prop_iter = props.begin (); prop_iter != props.end (); ++prop_iter) {
289                 set_property ((*prop_iter)->name ().c_str (), (*prop_iter)->value ());
290         }
291
292         const XMLNodeList& nodes = from.children ();
293         for (XMLNodeConstIterator child_iter = nodes.begin (); child_iter != nodes.end (); ++child_iter) {
294                 add_child_copy (**child_iter);
295         }
296
297         return *this;
298 }
299
300 bool
301 XMLNode::operator== (const XMLNode& other) const
302 {
303         if (is_content () != other.is_content ()) {
304                 return false;
305         }
306
307         if (is_content ()) {
308                 if (content () != other.content ()) {
309                         return false;
310                 }
311         } else {
312                 if (name () != other.name ()) {
313                         return false;
314                 }
315         }
316
317         XMLPropertyList const& other_properties = other.properties ();
318
319         if (_proplist.size () != other_properties.size ()) {
320                 return false;
321         }
322
323         XMLPropertyConstIterator our_prop_iter = _proplist.begin();
324         XMLPropertyConstIterator other_prop_iter = other_properties.begin();
325
326         while (our_prop_iter != _proplist.end ()) {
327                 XMLProperty const* our_prop = *our_prop_iter;
328                 XMLProperty const* other_prop = *other_prop_iter;
329                 if (our_prop->name () != other_prop->name () || our_prop->value () != other_prop->value ()) {
330                         return false;
331                 }
332                 ++our_prop_iter;
333                 ++other_prop_iter;
334         }
335
336         XMLNodeList const& other_children = other.children();
337
338         if (_children.size() != other_children.size()) {
339                 return false;
340         }
341
342         XMLNodeConstIterator our_child_iter = _children.begin ();
343         XMLNodeConstIterator other_child_iter = other_children.begin ();
344
345         while (our_child_iter != _children.end()) {
346                 XMLNode const* our_child = *our_child_iter;
347                 XMLNode const* other_child = *other_child_iter;
348
349                 if (*our_child != *other_child) {
350                         return false;
351                 }
352                 ++our_child_iter;
353                 ++other_child_iter;
354         }
355         return true;
356 }
357
358 bool
359 XMLNode::operator!= (const XMLNode& other) const
360 {
361         return !(*this == other);
362 }
363
364 const string&
365 XMLNode::set_content(const string& c)
366 {
367         if (c.empty()) {
368                 _is_content = false;
369         } else {
370                 _is_content = true;
371         }
372
373         _content = c;
374
375         return _content;
376 }
377
378 XMLNode*
379 XMLNode::child (const char* name) const
380 {
381         /* returns first child matching name */
382
383         XMLNodeConstIterator cur;
384
385         if (name == 0) {
386                 return 0;
387         }
388
389         for (cur = _children.begin(); cur != _children.end(); ++cur) {
390                 if ((*cur)->name() == name) {
391                         return *cur;
392                 }
393         }
394
395         return 0;
396 }
397
398 const XMLNodeList&
399 XMLNode::children(const string& n) const
400 {
401         /* returns all children matching name */
402
403         XMLNodeConstIterator cur;
404
405         if (n.empty()) {
406                 return _children;
407         }
408
409         _selected_children.clear();
410
411         for (cur = _children.begin(); cur != _children.end(); ++cur) {
412                 if ((*cur)->name() == n) {
413                         _selected_children.insert(_selected_children.end(), *cur);
414                 }
415         }
416
417         return _selected_children;
418 }
419
420 XMLNode*
421 XMLNode::add_child(const char* n)
422 {
423         return add_child_copy(XMLNode (n));
424 }
425
426 void
427 XMLNode::add_child_nocopy(XMLNode& n)
428 {
429         _children.insert(_children.end(), &n);
430 }
431
432 XMLNode*
433 XMLNode::add_child_copy(const XMLNode& n)
434 {
435         XMLNode *copy = new XMLNode(n);
436         _children.insert(_children.end(), copy);
437         return copy;
438 }
439
440 boost::shared_ptr<XMLSharedNodeList>
441 XMLTree::find(const string xpath, XMLNode* node) const
442 {
443         xmlXPathContext* ctxt;
444         xmlDocPtr doc = 0;
445
446         if (node) {
447                 doc = xmlNewDoc(xml_version);
448                 writenode(doc, node, doc->children, 1);
449                 ctxt = xmlXPathNewContext(doc);
450         } else {
451                 ctxt = xmlXPathNewContext(_doc);
452         }
453
454         boost::shared_ptr<XMLSharedNodeList> result =
455                 boost::shared_ptr<XMLSharedNodeList>(find_impl(ctxt, xpath));
456
457         xmlXPathFreeContext(ctxt);
458         if (doc) {
459                 xmlFreeDoc (doc);
460         }
461
462         return result;
463 }
464
465 std::string
466 XMLNode::attribute_value()
467 {
468         XMLNodeList children = this->children();
469         if (_is_content)
470                 throw XMLException("XMLNode: attribute_value failed (is_content) for requested node: " + name());
471
472         if (children.size() != 1)
473                 throw XMLException("XMLNode: attribute_value failed (children.size != 1) for requested node: " + name());
474
475         XMLNode* child = *(children.begin());
476         if (!child->is_content())
477                 throw XMLException("XMLNode: attribute_value failed (!child->is_content()) for requested node: " + name());
478
479         return child->content();
480 }
481
482 XMLNode*
483 XMLNode::add_content(const string& c)
484 {
485         return add_child_copy(XMLNode (string(), c));
486 }
487
488 XMLProperty const *
489 XMLNode::property(const char* name) const
490 {
491         XMLPropertyConstIterator iter = _proplist.begin();
492
493         while (iter != _proplist.end()) {
494                 if ((*iter)->name() == name) {
495                         return *iter;
496                 }
497                 ++iter;
498         }
499
500         return 0;
501 }
502
503 XMLProperty const *
504 XMLNode::property(const string& name) const
505 {
506         XMLPropertyConstIterator iter = _proplist.begin();
507
508         while (iter != _proplist.end()) {
509                 if ((*iter)->name() == name) {
510                         return *iter;
511                 }
512                 ++iter;
513         }
514         return 0;
515 }
516
517 XMLProperty *
518 XMLNode::property(const char* name)
519 {
520         XMLPropertyIterator iter = _proplist.begin();
521
522         while (iter != _proplist.end()) {
523                 if ((*iter)->name() == name) {
524                         return *iter;
525                 }
526                 ++iter;
527         }
528         return 0;
529 }
530
531 XMLProperty *
532 XMLNode::property(const string& name)
533 {
534         XMLPropertyIterator iter = _proplist.begin();
535
536         while (iter != _proplist.end()) {
537                 if ((*iter)->name() == name) {
538                         return *iter;
539                 }
540                 ++iter;
541         }
542
543         return 0;
544 }
545
546 bool
547 XMLNode::has_property_with_value (const string& name, const string& value) const
548 {
549         XMLPropertyConstIterator iter = _proplist.begin();
550
551         while (iter != _proplist.end()) {
552                 if ((*iter)->name() == name && (*iter)->value() == value) {
553                         return true;
554                 }
555                 ++iter;
556         }
557         return false;
558 }
559
560 bool
561 XMLNode::set_property(const char* name, const string& value)
562 {
563         XMLPropertyIterator iter = _proplist.begin();
564
565         while (iter != _proplist.end()) {
566                 if ((*iter)->name() == name) {
567                         (*iter)->set_value (value);
568                         return *iter;
569                 }
570                 ++iter;
571         }
572
573         XMLProperty* new_property = new XMLProperty(name, value);
574
575         if (!new_property) {
576                 return 0;
577         }
578
579         _proplist.insert(_proplist.end(), new_property);
580
581         return new_property;
582 }
583
584 bool
585 XMLNode::get_property(const char* name, std::string& value) const
586 {
587         XMLProperty const* const prop = property (name);
588         if (!prop)
589                 return false;
590
591         value = prop->value ();
592
593         return true;
594 }
595
596 void
597 XMLNode::remove_property(const string& name)
598 {
599         XMLPropertyIterator iter = _proplist.begin();
600
601         while (iter != _proplist.end()) {
602                 if ((*iter)->name() == name) {
603                         XMLProperty* property = *iter;
604                         _proplist.erase (iter);
605                         delete property;
606                         break;
607                 }
608                 ++iter;
609         }
610 }
611
612 /** Remove any property with the given name from this node and its children */
613 void
614 XMLNode::remove_property_recursively(const string& n)
615 {
616         remove_property (n);
617         for (XMLNodeIterator i = _children.begin(); i != _children.end(); ++i) {
618                 (*i)->remove_property_recursively (n);
619         }
620 }
621
622 void
623 XMLNode::remove_nodes(const string& n)
624 {
625         XMLNodeIterator i = _children.begin();
626         while (i != _children.end()) {
627                 if ((*i)->name() == n) {
628                         i = _children.erase (i);
629                 } else {
630                         ++i;
631                 }
632         }
633 }
634
635 void
636 XMLNode::remove_nodes_and_delete(const string& n)
637 {
638         XMLNodeIterator i = _children.begin();
639
640         while (i != _children.end()) {
641                 if ((*i)->name() == n) {
642                         delete *i;
643                         i = _children.erase (i);
644                 } else {
645                         ++i;
646                 }
647         }
648 }
649
650 void
651 XMLNode::remove_nodes_and_delete(const string& propname, const string& val)
652 {
653         XMLNodeIterator i = _children.begin();
654         XMLProperty const * prop;
655
656         while (i != _children.end()) {
657                 prop = (*i)->property(propname);
658                 if (prop && prop->value() == val) {
659                         delete *i;
660                         i = _children.erase(i);
661                 } else {
662                         ++i;
663                 }
664         }
665 }
666
667 void
668 XMLNode::remove_node_and_delete(const string& n, const string& propname,
669  const string& val)
670 {
671         for (XMLNodeIterator i = _children.begin(); i != _children.end(); ++i) {
672                 if ((*i)->name() == n) {
673                         XMLProperty const * prop = (*i)->property (propname);
674                         if (prop && prop->value() == val) {
675                                 delete *i;
676                                 _children.erase (i);
677                                 break;
678                         }
679                 }
680         }
681 }
682
683 XMLProperty::XMLProperty(const string& n, const string& v)
684         : _name(n)
685         , _value(v)
686 {
687 }
688
689 XMLProperty::~XMLProperty()
690 {
691 }
692
693 static XMLNode*
694 readnode(xmlNodePtr node)
695 {
696         string name, content;
697         xmlNodePtr child;
698         XMLNode* tmp;
699         xmlAttrPtr attr;
700
701         if (node->name) {
702                 name = (const char*)node->name;
703         }
704
705         tmp = new XMLNode(name);
706
707         for (attr = node->properties; attr; attr = attr->next) {
708                 content = "";
709                 if (attr->children) {
710                         content = (char*)attr->children->content;
711                 }
712                 tmp->set_property((const char*)attr->name, content);
713         }
714
715         if (node->content) {
716                 tmp->set_content((char*)node->content);
717         } else {
718                 tmp->set_content(string());
719         }
720
721         for (child = node->children; child; child = child->next) {
722                 tmp->add_child_nocopy (*readnode(child));
723         }
724
725         return tmp;
726 }
727
728 static void
729 writenode(xmlDocPtr doc, XMLNode* n, xmlNodePtr p, int root = 0)
730 {
731         xmlNodePtr node;
732
733         if (root) {
734                 node = doc->children = xmlNewDocNode(doc, 0, (const xmlChar*) n->name().c_str(), 0);
735         } else {
736                 node = xmlNewChild(p, 0, (const xmlChar*) n->name().c_str(), 0);
737         }
738
739         if (n->is_content()) {
740                 node->type = XML_TEXT_NODE;
741                 xmlNodeSetContentLen(node, (const xmlChar*)n->content().c_str(), n->content().length());
742         }
743
744         const XMLPropertyList& props = n->properties();
745
746         for (XMLPropertyConstIterator prop_iter = props.begin (); prop_iter != props.end ();
747              ++prop_iter) {
748                 xmlSetProp (node, (const xmlChar*)(*prop_iter)->name ().c_str (),
749                             (const xmlChar*)(*prop_iter)->value ().c_str ());
750         }
751
752         const XMLNodeList& children = n->children ();
753         for (XMLNodeConstIterator child_iter = children.begin (); child_iter != children.end ();
754              ++child_iter) {
755                 writenode (doc, *child_iter, node);
756         }
757 }
758
759 static XMLSharedNodeList* find_impl(xmlXPathContext* ctxt, const string& xpath)
760 {
761         xmlXPathObject* result = xmlXPathEval((const xmlChar*)xpath.c_str(), ctxt);
762
763         if (!result) {
764                 xmlXPathFreeContext(ctxt);
765                 xmlFreeDoc(ctxt->doc);
766
767                 throw XMLException("Invalid XPath: " + xpath);
768         }
769
770         if (result->type != XPATH_NODESET) {
771                 xmlXPathFreeObject(result);
772                 xmlXPathFreeContext(ctxt);
773                 xmlFreeDoc(ctxt->doc);
774
775                 throw XMLException("Only nodeset result types are supported.");
776         }
777
778         xmlNodeSet* nodeset = result->nodesetval;
779         XMLSharedNodeList* nodes = new XMLSharedNodeList();
780         if (nodeset) {
781                 for (int i = 0; i < nodeset->nodeNr; ++i) {
782                         XMLNode* node = readnode(nodeset->nodeTab[i]);
783                         nodes->push_back(boost::shared_ptr<XMLNode>(node));
784                 }
785         } else {
786                 // return empty set
787         }
788
789         xmlXPathFreeObject(result);
790
791         return nodes;
792 }
793
794 /** Dump a node, its properties and children to a stream */
795 void
796 XMLNode::dump (ostream& s, string p) const
797 {
798         if (_is_content) {
799                 s << p << "  " << content() << "\n";
800         } else {
801                 s << p << "<" << _name;
802                 for (XMLPropertyList::const_iterator i = _proplist.begin(); i != _proplist.end(); ++i) {
803                         s << " " << (*i)->name() << "=\"" << (*i)->value() << "\"";
804                 }
805                 s << ">\n";
806
807                 for (XMLNodeList::const_iterator i = _children.begin(); i != _children.end(); ++i) {
808                         (*i)->dump (s, p + "  ");
809                 }
810
811                 s << p << "</" << _name << ">\n";
812         }
813 }