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