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