cleanup up cleanup at session destruction; clarify the meaning of 3 signals (DropRefe...
[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 "pbd/xml++.h"
9 #include <libxml/debugXML.h>
10 #include <libxml/xpath.h>
11 #include <libxml/xpathInternals.h>
12
13 #define XML_VERSION "1.0"
14
15 using namespace std;
16
17 static XMLNode*           readnode(xmlNodePtr);
18 static void               writenode(xmlDocPtr, XMLNode*, xmlNodePtr, int);
19 static XMLSharedNodeList* find_impl(xmlXPathContext* ctxt, const string& xpath);
20
21 XMLTree::XMLTree()
22         : _filename()
23         , _root(0)
24         , _compression(0)
25 {
26 }
27
28 XMLTree::XMLTree(const string& fn, bool validate)
29         : _filename(fn)
30         , _root(0)
31         , _compression(0)
32 {
33         read_internal(validate);
34 }
35
36 XMLTree::XMLTree(const XMLTree* from)
37         : _filename(from->filename())
38         , _root(new XMLNode(*from->root()))
39         , _compression(from->compression())
40 {
41 }
42
43 XMLTree::~XMLTree()
44 {
45         delete _root;
46 }
47
48 int
49 XMLTree::set_compression(int c)
50 {
51         if (c > 9) {
52                 c = 9;
53         } else if (c < 0) {
54                 c = 0;
55         }
56
57         _compression = c;
58
59         return _compression;
60 }
61
62 bool
63 XMLTree::read_internal(bool validate)
64 {
65         //shouldnt be used anywhere ATM, remove if so!
66         assert(!validate);
67
68         delete _root;
69         _root = 0;
70
71         xmlParserCtxtPtr ctxt = NULL; /* the parser context */
72         xmlDocPtr doc; /* the resulting document tree */
73
74         xmlKeepBlanksDefault(0);
75         /* parse the file, activating the DTD validation option */
76         if (validate) {
77                 /* create a parser context */
78                 ctxt = xmlNewParserCtxt();
79                 if (ctxt == NULL) {
80                         return false;
81                 }
82                 doc = xmlCtxtReadFile(ctxt, _filename.c_str(), NULL, XML_PARSE_DTDVALID);
83         } else {
84                 doc = xmlParseFile(_filename.c_str());
85         }
86
87         /* check if parsing suceeded */
88         if (doc == NULL) {
89                 if (validate) {
90                         xmlFreeParserCtxt(ctxt);
91                 }
92                 return false;
93         } else {
94                 /* check if validation suceeded */
95                 if (validate && ctxt->valid == 0) {
96                         xmlFreeParserCtxt(ctxt);
97                         xmlFreeDoc(doc);
98                         xmlCleanupParser();
99                         throw XMLException("Failed to validate document " + _filename);
100                 }
101         }
102
103         _root = readnode(xmlDocGetRootElement(doc));
104
105         /* free up the parser context */
106         if (validate) {
107                 xmlFreeParserCtxt(ctxt);
108         }
109         xmlFreeDoc(doc);
110         xmlCleanupParser();
111
112         return true;
113 }
114
115 bool
116 XMLTree::read_buffer(const string& buffer)
117 {
118         xmlDocPtr doc;
119
120         _filename = "";
121
122         delete _root;
123         _root = 0;
124
125         doc = xmlParseMemory((char*)buffer.c_str(), buffer.length());
126         if (!doc) {
127                 return false;
128         }
129
130         _root = readnode(xmlDocGetRootElement(doc));
131         xmlFreeDoc(doc);
132
133         return true;
134 }
135
136
137 bool
138 XMLTree::write() const
139 {
140         xmlDocPtr doc;
141         XMLNodeList children;
142         int result;
143
144         xmlKeepBlanksDefault(0);
145         doc = xmlNewDoc((xmlChar*) XML_VERSION);
146         xmlSetDocCompressMode(doc, _compression);
147         writenode(doc, _root, doc->children, 1);
148         result = xmlSaveFormatFileEnc(_filename.c_str(), doc, "UTF-8", 1);
149         xmlFreeDoc(doc);
150
151         if (result == -1) {
152                 return false;
153         }
154
155         return true;
156 }
157
158 void
159 XMLTree::debug(FILE* out) const
160 {
161         xmlDocPtr doc;
162         XMLNodeList children;
163
164         xmlKeepBlanksDefault(0);
165         doc = xmlNewDoc((xmlChar*) XML_VERSION);
166         xmlSetDocCompressMode(doc, _compression);
167         writenode(doc, _root, doc->children, 1);
168         xmlDebugDumpDocument (out, doc);
169         xmlFreeDoc(doc);
170 }
171
172 const string&
173 XMLTree::write_buffer() const
174 {
175         static string retval;
176         char* ptr;
177         int len;
178         xmlDocPtr doc;
179         XMLNodeList children;
180
181         xmlKeepBlanksDefault(0);
182         doc = xmlNewDoc((xmlChar*) XML_VERSION);
183         xmlSetDocCompressMode(doc, _compression);
184         writenode(doc, _root, doc->children, 1);
185         xmlDocDumpMemory(doc, (xmlChar **) & ptr, &len);
186         xmlFreeDoc(doc);
187
188         retval = ptr;
189
190         free(ptr);
191
192         return retval;
193 }
194
195 XMLNode::XMLNode(const string& n)
196         : _name(n)
197         , _is_content(false)
198 {
199 }
200
201 XMLNode::XMLNode(const string& n, const string& c)
202         : _name(n)
203         , _is_content(true)
204         , _content(c)
205 {
206 }
207
208 XMLNode::XMLNode(const XMLNode& from)
209 {
210         XMLPropertyList props;
211         XMLPropertyIterator curprop;
212         XMLNodeList nodes;
213         XMLNodeIterator curnode;
214
215         _name = from.name();
216         set_content(from.content());
217
218         props = from.properties();
219         for (curprop = props.begin(); curprop != props.end(); ++curprop) {
220                 add_property((*curprop)->name().c_str(), (*curprop)->value());
221         }
222
223         nodes = from.children();
224         for (curnode = nodes.begin(); curnode != nodes.end(); ++curnode) {
225                 add_child_copy(**curnode);
226         }
227 }
228
229 XMLNode::~XMLNode()
230 {
231         XMLNodeIterator curchild;
232         XMLPropertyIterator curprop;
233
234         for (curchild = _children.begin(); curchild != _children.end(); ++curchild) {
235                 delete *curchild;
236         }
237
238         for (curprop = _proplist.begin(); curprop != _proplist.end(); ++curprop) {
239                 delete *curprop;
240         }
241 }
242
243 const string&
244 XMLNode::set_content(const string& c)
245 {
246         if (c.empty()) {
247                 _is_content = false;
248         } else {
249                 _is_content = true;
250         }
251
252         _content = c;
253
254         return _content;
255 }
256
257 XMLNode*
258 XMLNode::child (const char* name) const
259 {
260         /* returns first child matching name */
261
262         XMLNodeConstIterator cur;
263
264         if (name == 0) {
265                 return 0;
266         }
267
268         for (cur = _children.begin(); cur != _children.end(); ++cur) {
269                 if ((*cur)->name() == name) {
270                         return *cur;
271                 }
272         }
273
274         return 0;
275 }
276
277 const XMLNodeList&
278 XMLNode::children(const string& n) const
279 {
280         /* returns all children matching name */
281
282         XMLNodeConstIterator cur;
283
284         if (n.empty()) {
285                 return _children;
286         }
287
288         _selected_children.clear();
289
290         for (cur = _children.begin(); cur != _children.end(); ++cur) {
291                 if ((*cur)->name() == n) {
292                         _selected_children.insert(_selected_children.end(), *cur);
293                 }
294         }
295
296         return _selected_children;
297 }
298
299 XMLNode*
300 XMLNode::add_child(const char* n)
301 {
302         return add_child_copy(XMLNode (n));
303 }
304
305 void
306 XMLNode::add_child_nocopy(XMLNode& n)
307 {
308         _children.insert(_children.end(), &n);
309 }
310
311 XMLNode*
312 XMLNode::add_child_copy(const XMLNode& n)
313 {
314         XMLNode *copy = new XMLNode(n);
315         _children.insert(_children.end(), copy);
316         return copy;
317 }
318
319 boost::shared_ptr<XMLSharedNodeList>
320 XMLNode::find(const string xpath) const
321 {
322         xmlDocPtr doc = xmlNewDoc((xmlChar*) XML_VERSION);
323         writenode(doc, (XMLNode*)this, doc->children, 1);
324         xmlXPathContext* ctxt = xmlXPathNewContext(doc);
325
326         boost::shared_ptr<XMLSharedNodeList> result =
327             boost::shared_ptr<XMLSharedNodeList>(find_impl(ctxt, xpath));
328
329         xmlXPathFreeContext(ctxt);
330         xmlFreeDoc(doc);
331
332         return result;
333 }
334
335 std::string
336 XMLNode::attribute_value()
337 {
338         XMLNodeList children = this->children();
339         assert(!_is_content);
340         assert(children.size() == 1);
341         XMLNode* child = *(children.begin());
342         assert(child->is_content());
343         return child->content();
344 }
345
346 XMLNode*
347 XMLNode::add_content(const string& c)
348 {
349         return add_child_copy(XMLNode (string(), c));
350 }
351
352 XMLProperty*
353 XMLNode::property(const char* n)
354 {
355         string ns(n);
356         map<string,XMLProperty*>::iterator iter;
357
358         if ((iter = _propmap.find(ns)) != _propmap.end()) {
359                 return iter->second;
360         }
361
362         return 0;
363 }
364
365 XMLProperty*
366 XMLNode::property(const string& ns)
367 {
368         map<string,XMLProperty*>::iterator iter;
369
370         if ((iter = _propmap.find(ns)) != _propmap.end()) {
371                 return iter->second;
372         }
373
374         return 0;
375 }
376
377 XMLProperty*
378 XMLNode::add_property(const char* n, const string& v)
379 {
380         string ns(n);
381         if (_propmap.find(ns) != _propmap.end()) {
382                 remove_property(ns);
383         }
384
385         XMLProperty* tmp = new XMLProperty(ns, v);
386
387         if (!tmp) {
388                 return 0;
389         }
390
391         _propmap[tmp->name()] = tmp;
392         _proplist.insert(_proplist.end(), tmp);
393
394         return tmp;
395 }
396
397 XMLProperty*
398 XMLNode::add_property(const char* n, const char* v)
399 {
400         string vs(v);
401         return add_property(n, vs);
402 }
403
404 XMLProperty*
405 XMLNode::add_property(const char* name, const long value)
406 {
407         static char str[1024];
408         snprintf(str, 1024, "%ld", value);
409         return add_property(name, str);
410 }
411
412 void
413 XMLNode::remove_property(const string& n)
414 {
415         if (_propmap.find(n) != _propmap.end()) {
416                 XMLProperty* p = _propmap[n];
417                 _proplist.remove (p);
418                 delete p;
419                 _propmap.erase(n);
420         }
421 }
422
423 void
424 XMLNode::remove_nodes(const string& n)
425 {
426         XMLNodeIterator i = _children.begin();
427         XMLNodeIterator tmp;
428
429         while (i != _children.end()) {
430                 tmp = i;
431                 ++tmp;
432                 if ((*i)->name() == n) {
433                         _children.erase (i);
434                 }
435                 i = tmp;
436         }
437 }
438
439 void
440 XMLNode::remove_nodes_and_delete(const string& n)
441 {
442         XMLNodeIterator i = _children.begin();
443         XMLNodeIterator tmp;
444
445         while (i != _children.end()) {
446                 tmp = i;
447                 ++tmp;
448                 if ((*i)->name() == n) {
449                         delete *i;
450                         _children.erase (i);
451                 }
452                 i = tmp;
453         }
454 }
455
456 void
457 XMLNode::remove_nodes_and_delete(const string& propname, const string& val)
458 {
459         XMLNodeIterator i = _children.begin();
460         XMLNodeIterator tmp;
461         XMLProperty* prop;
462
463         while (i != _children.end()) {
464                 tmp = i;
465                 ++tmp;
466
467                 prop = (*i)->property(propname);
468                 if (prop && prop->value() == val) {
469                         delete *i;
470                         _children.erase(i);
471                 }
472
473                 i = tmp;
474         }
475 }
476
477 XMLProperty::XMLProperty(const string& n, const string& v)
478         : _name(n)
479         , _value(v)
480 {
481         // Normalize property name (replace '_' with '-' as old session are inconsistent)
482         for (size_t i = 0; i < _name.length(); ++i) {
483                 if (_name[i] == '_') {
484                         _name[i] = '-';
485                 }
486         }
487 }
488
489 XMLProperty::~XMLProperty()
490 {
491 }
492
493 static XMLNode*
494 readnode(xmlNodePtr node)
495 {
496         string name, content;
497         xmlNodePtr child;
498         XMLNode* tmp;
499         xmlAttrPtr attr;
500
501         if (node->name) {
502                 name = (char*)node->name;
503         }
504
505         tmp = new XMLNode(name);
506
507         for (attr = node->properties; attr; attr = attr->next) {
508                 content = "";
509                 if (attr->children) {
510                         content = (char*)attr->children->content;
511                 }
512                 tmp->add_property((char*)attr->name, content);
513         }
514
515         if (node->content) {
516                 tmp->set_content((char*)node->content);
517         } else {
518                 tmp->set_content(string());
519         }
520
521         for (child = node->children; child; child = child->next) {
522                 tmp->add_child_nocopy (*readnode(child));
523         }
524
525         return tmp;
526 }
527
528 static void
529 writenode(xmlDocPtr doc, XMLNode* n, xmlNodePtr p, int root = 0)
530 {
531         XMLPropertyList props;
532         XMLPropertyIterator curprop;
533         XMLNodeList children;
534         XMLNodeIterator curchild;
535         xmlNodePtr node;
536
537         if (root) {
538                 node = doc->children = xmlNewDocNode(doc, 0, (xmlChar*) n->name().c_str(), 0);
539         } else {
540                 node = xmlNewChild(p, 0, (xmlChar*) n->name().c_str(), 0);
541         }
542
543         if (n->is_content()) {
544                 node->type = XML_TEXT_NODE;
545                 xmlNodeSetContentLen(node, (const xmlChar*)n->content().c_str(), n->content().length());
546         }
547
548         props = n->properties();
549         for (curprop = props.begin(); curprop != props.end(); ++curprop) {
550                 xmlSetProp(node, (xmlChar*) (*curprop)->name().c_str(), (xmlChar*) (*curprop)->value().c_str());
551         }
552
553         children = n->children();
554         for (curchild = children.begin(); curchild != children.end(); ++curchild) {
555                 writenode(doc, *curchild, node);
556         }
557 }
558
559 static XMLSharedNodeList* find_impl(xmlXPathContext* ctxt, const string& xpath)
560 {
561         xmlXPathObject* result = xmlXPathEval((const xmlChar*)xpath.c_str(), ctxt);
562
563         if (!result) {
564                 xmlXPathFreeContext(ctxt);
565                 xmlFreeDoc(ctxt->doc);
566
567                 throw XMLException("Invalid XPath: " + xpath);
568         }
569
570         if (result->type != XPATH_NODESET) {
571                 xmlXPathFreeObject(result);
572                 xmlXPathFreeContext(ctxt);
573                 xmlFreeDoc(ctxt->doc);
574
575                 throw XMLException("Only nodeset result types are supported.");
576         }
577
578         xmlNodeSet* nodeset = result->nodesetval;
579         XMLSharedNodeList* nodes = new XMLSharedNodeList();
580         if (nodeset) {
581                 for (int i = 0; i < nodeset->nodeNr; ++i) {
582                         XMLNode* node = readnode(nodeset->nodeTab[i]);
583                         nodes->push_back(boost::shared_ptr<XMLNode>(node));
584                 }
585         } else {
586                 // return empty set
587         }
588
589         xmlXPathFreeObject(result);
590
591         return nodes;
592 }
593