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