remove debugging output
[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         map<string,XMLProperty*>::iterator iter;
381         
382         if ((iter = _propmap.find(ns)) != _propmap.end()) {
383                 iter->second->set_value (v);
384                 return iter->second;
385         }
386
387         XMLProperty* tmp = new XMLProperty(ns, v);
388
389         if (!tmp) {
390                 return 0;
391         }
392
393         _propmap[tmp->name()] = tmp;
394         _proplist.insert(_proplist.end(), tmp);
395
396         return tmp;
397 }
398
399 XMLProperty*
400 XMLNode::add_property(const char* n, const char* v)
401 {
402         string vs(v);
403         return add_property(n, vs);
404 }
405
406 XMLProperty*
407 XMLNode::add_property(const char* name, const long value)
408 {
409         char str[64];
410         snprintf(str, sizeof(str), "%ld", value);
411         return add_property(name, str);
412 }
413
414 void
415 XMLNode::remove_property(const string& n)
416 {
417         if (_propmap.find(n) != _propmap.end()) {
418                 XMLProperty* p = _propmap[n];
419                 _proplist.remove (p);
420                 delete p;
421                 _propmap.erase(n);
422         }
423 }
424
425 /** Remove any property with the given name from this node and its children */
426 void
427 XMLNode::remove_property_recursively(const string& n)
428 {
429         remove_property (n);
430         for (XMLNodeIterator i = _children.begin(); i != _children.end(); ++i) {
431                 (*i)->remove_property_recursively (n);
432         }
433 }
434
435 void
436 XMLNode::remove_nodes(const string& n)
437 {
438         XMLNodeIterator i = _children.begin();
439         XMLNodeIterator tmp;
440
441         while (i != _children.end()) {
442                 tmp = i;
443                 ++tmp;
444                 if ((*i)->name() == n) {
445                         _children.erase (i);
446                 }
447                 i = tmp;
448         }
449 }
450
451 void
452 XMLNode::remove_nodes_and_delete(const string& n)
453 {
454         XMLNodeIterator i = _children.begin();
455         XMLNodeIterator tmp;
456
457         while (i != _children.end()) {
458                 tmp = i;
459                 ++tmp;
460                 if ((*i)->name() == n) {
461                         delete *i;
462                         _children.erase (i);
463                 }
464                 i = tmp;
465         }
466 }
467
468 void
469 XMLNode::remove_nodes_and_delete(const string& propname, const string& val)
470 {
471         XMLNodeIterator i = _children.begin();
472         XMLNodeIterator tmp;
473         XMLProperty* prop;
474
475         while (i != _children.end()) {
476                 tmp = i;
477                 ++tmp;
478
479                 prop = (*i)->property(propname);
480                 if (prop && prop->value() == val) {
481                         delete *i;
482                         _children.erase(i);
483                 }
484
485                 i = tmp;
486         }
487 }
488
489 XMLProperty::XMLProperty(const string& n, const string& v)
490         : _name(n)
491         , _value(v)
492 {
493         // Normalize property name (replace '_' with '-' as old session are inconsistent)
494         for (size_t i = 0; i < _name.length(); ++i) {
495                 if (_name[i] == '_') {
496                         _name[i] = '-';
497                 }
498         }
499 }
500
501 XMLProperty::~XMLProperty()
502 {
503 }
504
505 static XMLNode*
506 readnode(xmlNodePtr node)
507 {
508         string name, content;
509         xmlNodePtr child;
510         XMLNode* tmp;
511         xmlAttrPtr attr;
512
513         if (node->name) {
514                 name = (char*)node->name;
515         }
516
517         tmp = new XMLNode(name);
518
519         for (attr = node->properties; attr; attr = attr->next) {
520                 content = "";
521                 if (attr->children) {
522                         content = (char*)attr->children->content;
523                 }
524                 tmp->add_property((char*)attr->name, content);
525         }
526
527         if (node->content) {
528                 tmp->set_content((char*)node->content);
529         } else {
530                 tmp->set_content(string());
531         }
532
533         for (child = node->children; child; child = child->next) {
534                 tmp->add_child_nocopy (*readnode(child));
535         }
536
537         return tmp;
538 }
539
540 static void
541 writenode(xmlDocPtr doc, XMLNode* n, xmlNodePtr p, int root = 0)
542 {
543         XMLPropertyList props;
544         XMLPropertyIterator curprop;
545         XMLNodeList children;
546         XMLNodeIterator curchild;
547         xmlNodePtr node;
548
549         if (root) {
550                 node = doc->children = xmlNewDocNode(doc, 0, (xmlChar*) n->name().c_str(), 0);
551         } else {
552                 node = xmlNewChild(p, 0, (xmlChar*) n->name().c_str(), 0);
553         }
554
555         if (n->is_content()) {
556                 node->type = XML_TEXT_NODE;
557                 xmlNodeSetContentLen(node, (const xmlChar*)n->content().c_str(), n->content().length());
558         }
559
560         props = n->properties();
561         for (curprop = props.begin(); curprop != props.end(); ++curprop) {
562                 xmlSetProp(node, (xmlChar*) (*curprop)->name().c_str(), (xmlChar*) (*curprop)->value().c_str());
563         }
564
565         children = n->children();
566         for (curchild = children.begin(); curchild != children.end(); ++curchild) {
567                 writenode(doc, *curchild, node);
568         }
569 }
570
571 static XMLSharedNodeList* find_impl(xmlXPathContext* ctxt, const string& xpath)
572 {
573         xmlXPathObject* result = xmlXPathEval((const xmlChar*)xpath.c_str(), ctxt);
574
575         if (!result) {
576                 xmlXPathFreeContext(ctxt);
577                 xmlFreeDoc(ctxt->doc);
578
579                 throw XMLException("Invalid XPath: " + xpath);
580         }
581
582         if (result->type != XPATH_NODESET) {
583                 xmlXPathFreeObject(result);
584                 xmlXPathFreeContext(ctxt);
585                 xmlFreeDoc(ctxt->doc);
586
587                 throw XMLException("Only nodeset result types are supported.");
588         }
589
590         xmlNodeSet* nodeset = result->nodesetval;
591         XMLSharedNodeList* nodes = new XMLSharedNodeList();
592         if (nodeset) {
593                 for (int i = 0; i < nodeset->nodeNr; ++i) {
594                         XMLNode* node = readnode(nodeset->nodeTab[i]);
595                         nodes->push_back(boost::shared_ptr<XMLNode>(node));
596                 }
597         } else {
598                 // return empty set
599         }
600
601         xmlXPathFreeObject(result);
602
603         return nodes;
604 }
605
606 /** Dump a node, its properties and children to a stream */
607 void
608 XMLNode::dump (ostream& s, string p) const
609 {
610         s << p << _name << " ";
611         for (XMLPropertyList::const_iterator i = _proplist.begin(); i != _proplist.end(); ++i) {
612                 s << (*i)->name() << "=" << (*i)->value() << " ";
613         }
614         s << "\n";
615         
616         for (XMLNodeList::const_iterator i = _children.begin(); i != _children.end(); ++i) {
617                 (*i)->dump (s, p + "  ");
618         }
619 }