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