Spurious character in GPL boilerplate.
[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 void
426 XMLNode::remove_nodes(const string& n)
427 {
428         XMLNodeIterator i = _children.begin();
429         XMLNodeIterator tmp;
430
431         while (i != _children.end()) {
432                 tmp = i;
433                 ++tmp;
434                 if ((*i)->name() == n) {
435                         _children.erase (i);
436                 }
437                 i = tmp;
438         }
439 }
440
441 void
442 XMLNode::remove_nodes_and_delete(const string& n)
443 {
444         XMLNodeIterator i = _children.begin();
445         XMLNodeIterator tmp;
446
447         while (i != _children.end()) {
448                 tmp = i;
449                 ++tmp;
450                 if ((*i)->name() == n) {
451                         delete *i;
452                         _children.erase (i);
453                 }
454                 i = tmp;
455         }
456 }
457
458 void
459 XMLNode::remove_nodes_and_delete(const string& propname, const string& val)
460 {
461         XMLNodeIterator i = _children.begin();
462         XMLNodeIterator tmp;
463         XMLProperty* prop;
464
465         while (i != _children.end()) {
466                 tmp = i;
467                 ++tmp;
468
469                 prop = (*i)->property(propname);
470                 if (prop && prop->value() == val) {
471                         delete *i;
472                         _children.erase(i);
473                 }
474
475                 i = tmp;
476         }
477 }
478
479 XMLProperty::XMLProperty(const string& n, const string& v)
480         : _name(n)
481         , _value(v)
482 {
483         // Normalize property name (replace '_' with '-' as old session are inconsistent)
484         for (size_t i = 0; i < _name.length(); ++i) {
485                 if (_name[i] == '_') {
486                         _name[i] = '-';
487                 }
488         }
489 }
490
491 XMLProperty::~XMLProperty()
492 {
493 }
494
495 static XMLNode*
496 readnode(xmlNodePtr node)
497 {
498         string name, content;
499         xmlNodePtr child;
500         XMLNode* tmp;
501         xmlAttrPtr attr;
502
503         if (node->name) {
504                 name = (char*)node->name;
505         }
506
507         tmp = new XMLNode(name);
508
509         for (attr = node->properties; attr; attr = attr->next) {
510                 content = "";
511                 if (attr->children) {
512                         content = (char*)attr->children->content;
513                 }
514                 tmp->add_property((char*)attr->name, content);
515         }
516
517         if (node->content) {
518                 tmp->set_content((char*)node->content);
519         } else {
520                 tmp->set_content(string());
521         }
522
523         for (child = node->children; child; child = child->next) {
524                 tmp->add_child_nocopy (*readnode(child));
525         }
526
527         return tmp;
528 }
529
530 static void
531 writenode(xmlDocPtr doc, XMLNode* n, xmlNodePtr p, int root = 0)
532 {
533         XMLPropertyList props;
534         XMLPropertyIterator curprop;
535         XMLNodeList children;
536         XMLNodeIterator curchild;
537         xmlNodePtr node;
538
539         if (root) {
540                 node = doc->children = xmlNewDocNode(doc, 0, (xmlChar*) n->name().c_str(), 0);
541         } else {
542                 node = xmlNewChild(p, 0, (xmlChar*) n->name().c_str(), 0);
543         }
544
545         if (n->is_content()) {
546                 node->type = XML_TEXT_NODE;
547                 xmlNodeSetContentLen(node, (const xmlChar*)n->content().c_str(), n->content().length());
548         }
549
550         props = n->properties();
551         for (curprop = props.begin(); curprop != props.end(); ++curprop) {
552                 xmlSetProp(node, (xmlChar*) (*curprop)->name().c_str(), (xmlChar*) (*curprop)->value().c_str());
553         }
554
555         children = n->children();
556         for (curchild = children.begin(); curchild != children.end(); ++curchild) {
557                 writenode(doc, *curchild, node);
558         }
559 }
560
561 static XMLSharedNodeList* find_impl(xmlXPathContext* ctxt, const string& xpath)
562 {
563         xmlXPathObject* result = xmlXPathEval((const xmlChar*)xpath.c_str(), ctxt);
564
565         if (!result) {
566                 xmlXPathFreeContext(ctxt);
567                 xmlFreeDoc(ctxt->doc);
568
569                 throw XMLException("Invalid XPath: " + xpath);
570         }
571
572         if (result->type != XPATH_NODESET) {
573                 xmlXPathFreeObject(result);
574                 xmlXPathFreeContext(ctxt);
575                 xmlFreeDoc(ctxt->doc);
576
577                 throw XMLException("Only nodeset result types are supported.");
578         }
579
580         xmlNodeSet* nodeset = result->nodesetval;
581         XMLSharedNodeList* nodes = new XMLSharedNodeList();
582         if (nodeset) {
583                 for (int i = 0; i < nodeset->nodeNr; ++i) {
584                         XMLNode* node = readnode(nodeset->nodeTab[i]);
585                         nodes->push_back(boost::shared_ptr<XMLNode>(node));
586                 }
587         } else {
588                 // return empty set
589         }
590
591         xmlXPathFreeObject(result);
592
593         return nodes;
594 }
595
596 /** Dump a node, its properties and children to a stream */
597 void
598 XMLNode::dump (ostream& s, string p) const
599 {
600         s << p << _name << " ";
601         for (XMLPropertyList::const_iterator i = _proplist.begin(); i != _proplist.end(); ++i) {
602                 s << (*i)->name() << "=" << (*i)->value() << " ";
603         }
604         s << "\n";
605         
606         for (XMLNodeList::const_iterator i = _children.begin(); i != _children.end(); ++i) {
607                 (*i)->dump (s, p + "  ");
608         }
609 }