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