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