Normalize XML property name style, preserving old session loading (on load _ will...
[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         if (_root) {
43                 delete _root;
44         }
45 }
46
47 int 
48 XMLTree::set_compression(int c)
49 {
50         if (c > 9) {
51                 c = 9;
52         } else if (c < 0) {
53                 c = 0;
54         }
55         
56         _compression = c;
57         
58         return _compression;
59 }
60
61 bool 
62 XMLTree::read_internal(bool validate)
63 {
64         //shouldnt be used anywhere ATM, remove if so!
65         assert(!validate);
66         if (_root) {
67                 delete _root;
68                 _root = 0;
69         }
70         
71         xmlParserCtxtPtr ctxt; /* 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         if (_root) {
123                 delete _root;
124                 _root = 0;
125         }
126         
127         doc = xmlParseMemory((char *) buffer.c_str(), buffer.length());
128         if (!doc) {
129                 return false;
130         }
131         
132         _root = readnode(xmlDocGetRootElement(doc));
133         xmlFreeDoc(doc);
134         
135         return true;
136 }
137
138
139 bool 
140 XMLTree::write(void) const
141 {
142         xmlDocPtr doc;
143         XMLNodeList children;
144         int result;
145         
146         xmlKeepBlanksDefault(0);
147         doc = xmlNewDoc((xmlChar *) XML_VERSION);
148         xmlSetDocCompressMode(doc, _compression);
149         writenode(doc, _root, doc->children, 1);
150         result = xmlSaveFormatFileEnc(_filename.c_str(), doc, "UTF-8", 1);
151         xmlFreeDoc(doc);
152         
153         if (result == -1) {
154                 return false;
155         }
156         
157         return true;
158 }
159
160 void
161 XMLTree::debug(FILE* out) const
162 {
163         xmlDocPtr doc;
164         XMLNodeList children;
165
166         xmlKeepBlanksDefault(0);
167         doc = xmlNewDoc((xmlChar *) XML_VERSION);
168         xmlSetDocCompressMode(doc, _compression);
169         writenode(doc, _root, doc->children, 1);
170         xmlDebugDumpDocument (out, doc);
171         xmlFreeDoc(doc);
172 }
173
174 const string & 
175 XMLTree::write_buffer(void) const
176 {
177         static string retval;
178         char *ptr;
179         int len;
180         xmlDocPtr doc;
181         XMLNodeList children;
182         
183         xmlKeepBlanksDefault(0);
184         doc = xmlNewDoc((xmlChar *) XML_VERSION);
185         xmlSetDocCompressMode(doc, _compression);
186         writenode(doc, _root, doc->children, 1);
187         xmlDocDumpMemory(doc, (xmlChar **) & ptr, &len);
188         xmlFreeDoc(doc);
189         
190         retval = ptr;
191         
192         free(ptr);
193         
194         return retval;
195 }
196
197 XMLNode::XMLNode(const string & n)
198         :  _name(n), _is_content(false), _content(string())
199 {
200 }
201
202 XMLNode::XMLNode(const string & n, const string & c)
203         :_name(n), _is_content(true), _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                 _proplist.remove(_propmap[n]);
416                 _propmap.erase(n);
417         }
418 }
419
420 void 
421 XMLNode::remove_nodes(const string & n)
422 {
423         XMLNodeIterator i = _children.begin();
424         XMLNodeIterator tmp;
425         
426         while (i != _children.end()) {
427                 tmp = i;
428                 ++tmp;
429                 if ((*i)->name() == n) {
430                         _children.erase (i);
431                 }
432                 i = tmp;
433         }
434 }
435
436 void 
437 XMLNode::remove_nodes_and_delete(const string & n)
438 {
439         XMLNodeIterator i = _children.begin();
440         XMLNodeIterator tmp;
441         
442         while (i != _children.end()) {
443                 tmp = i;
444                 ++tmp;
445                 if ((*i)->name() == n) {
446                         delete *i;
447                         _children.erase (i);
448                 }
449                 i = tmp;
450         }
451 }
452
453 void
454 XMLNode::remove_nodes_and_delete(const string& propname, const string& val) 
455 {
456         XMLNodeIterator i = _children.begin();
457         XMLNodeIterator tmp;
458         XMLProperty* prop;
459
460         while (i != _children.end()) {
461                 tmp = i;
462                 ++tmp;
463
464                 prop = (*i)->property(propname);
465                 if(prop && prop->value() == val) {
466                         delete *i;
467                         _children.erase(i);
468                 }
469
470                 i = tmp;
471         }
472 }
473
474 XMLProperty::XMLProperty(const string &n, const string &v)
475         : _name(n), 
476         _value(v) 
477
478         // Normalize property name (replace '_' with '-' as old session are inconsistent)
479         for (size_t i = 0; i < _name.length(); ++i) {
480                 if (_name[i] == '_') {
481                         _name[i] = '-';
482                 }
483         }
484 }
485
486 XMLProperty::~XMLProperty()
487 {
488 }
489
490 static XMLNode *
491 readnode(xmlNodePtr node)
492 {
493         string name, content;
494         xmlNodePtr child;
495         XMLNode *tmp;
496         xmlAttrPtr attr;
497         
498         if (node->name) {
499                 name = (char *) node->name;
500         }
501         
502         tmp = new XMLNode(name);
503         
504         for (attr = node->properties; attr; attr = attr->next) {
505                 content = "";
506                 if (attr->children) {
507                         content = (char *) attr->children->content;
508                 }
509                 tmp->add_property((char *) attr->name, content);
510         }
511         
512         if (node->content) {
513                 tmp->set_content((char *) node->content);
514         } else {
515                 tmp->set_content(string());
516         }
517         
518         for (child = node->children; child; child = child->next) {
519                 tmp->add_child_nocopy (*readnode(child));
520         }
521         
522         return tmp;
523 }
524
525 static void 
526 writenode(xmlDocPtr doc, XMLNode * n, xmlNodePtr p, int root = 0)
527 {
528         XMLPropertyList props;
529         XMLPropertyIterator curprop;
530         XMLNodeList children;
531         XMLNodeIterator curchild;
532         xmlNodePtr node;
533         
534         if (root) {
535                 node = doc->children = xmlNewDocNode(doc, 0, (xmlChar *) n->name().c_str(), 0);
536         } else {
537                 node = xmlNewChild(p, 0, (xmlChar *) n->name().c_str(), 0);
538         }
539                 
540         if (n->is_content()) {
541                 node->type = XML_TEXT_NODE;
542                 xmlNodeSetContentLen(node, (const xmlChar *) n->content().c_str(), n->content().length());
543         }
544         
545         props = n->properties();
546         for (curprop = props.begin(); curprop != props.end(); ++curprop) {
547                 xmlSetProp(node, (xmlChar *) (*curprop)->name().c_str(), (xmlChar *) (*curprop)->value().c_str());
548         }
549                 
550         children = n->children();
551         for (curchild = children.begin(); curchild != children.end(); ++curchild) {
552                 writenode(doc, *curchild, node);
553         }
554 }
555
556 static XMLSharedNodeList* find_impl(xmlXPathContext* ctxt, const string xpath)
557 {
558         xmlXPathObject* result = xmlXPathEval((const xmlChar*)xpath.c_str(), ctxt);
559
560         if(!result)
561         {
562                 xmlXPathFreeContext(ctxt);
563                 xmlFreeDoc(ctxt->doc);
564
565                 throw XMLException("Invalid XPath: " + xpath);
566         }
567
568         if(result->type != XPATH_NODESET)
569         {
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         {
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         }
586         else
587         {
588                 // return empty set
589         }
590
591         xmlXPathFreeObject(result);
592
593         return nodes;
594 }
595