Remove use of xmlCleanupParser as suggested in the libxml2 docs. Fixes #3047.
[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 using namespace std;
16
17 static XMLNode*           readnode(xmlNodePtr);
18 static void               writenode(xmlDocPtr, XMLNode*, xmlNodePtr, int);
19 static XMLSharedNodeList* find_impl(xmlXPathContext* ctxt, const string& xpath);
20
21 XMLTree::XMLTree()
22         : _filename()
23         , _root(0)
24         , _compression(0)
25 {
26 }
27
28 XMLTree::XMLTree(const string& fn, bool validate)
29         : _filename(fn)
30         , _root(0)
31         , _compression(0)
32 {
33         read_internal(validate);
34 }
35
36 XMLTree::XMLTree(const XMLTree* from)
37         : _filename(from->filename())
38         , _root(new XMLNode(*from->root()))
39         , _compression(from->compression())
40 {
41 }
42
43 XMLTree::~XMLTree()
44 {
45         delete _root;
46 }
47
48 int
49 XMLTree::set_compression(int c)
50 {
51         if (c > 9) {
52                 c = 9;
53         } else if (c < 0) {
54                 c = 0;
55         }
56
57         _compression = c;
58
59         return _compression;
60 }
61
62 bool
63 XMLTree::read_internal(bool validate)
64 {
65         //shouldnt be used anywhere ATM, remove if so!
66         assert(!validate);
67
68         delete _root;
69         _root = 0;
70
71         xmlParserCtxtPtr ctxt = NULL; /* 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                         throw XMLException("Failed to validate document " + _filename);
99                 }
100         }
101
102         _root = readnode(xmlDocGetRootElement(doc));
103
104         /* free up the parser context */
105         if (validate) {
106                 xmlFreeParserCtxt(ctxt);
107         }
108         xmlFreeDoc(doc);
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                 XMLProperty* p = _propmap[n];
415                 _proplist.remove (p);
416                 delete p;
417                 _propmap.erase(n);
418         }
419 }
420
421 void
422 XMLNode::remove_nodes(const string& n)
423 {
424         XMLNodeIterator i = _children.begin();
425         XMLNodeIterator tmp;
426
427         while (i != _children.end()) {
428                 tmp = i;
429                 ++tmp;
430                 if ((*i)->name() == n) {
431                         _children.erase (i);
432                 }
433                 i = tmp;
434         }
435 }
436
437 void
438 XMLNode::remove_nodes_and_delete(const string& n)
439 {
440         XMLNodeIterator i = _children.begin();
441         XMLNodeIterator tmp;
442
443         while (i != _children.end()) {
444                 tmp = i;
445                 ++tmp;
446                 if ((*i)->name() == n) {
447                         delete *i;
448                         _children.erase (i);
449                 }
450                 i = tmp;
451         }
452 }
453
454 void
455 XMLNode::remove_nodes_and_delete(const string& propname, const string& val)
456 {
457         XMLNodeIterator i = _children.begin();
458         XMLNodeIterator tmp;
459         XMLProperty* prop;
460
461         while (i != _children.end()) {
462                 tmp = i;
463                 ++tmp;
464
465                 prop = (*i)->property(propname);
466                 if (prop && prop->value() == val) {
467                         delete *i;
468                         _children.erase(i);
469                 }
470
471                 i = tmp;
472         }
473 }
474
475 XMLProperty::XMLProperty(const string& n, const string& v)
476         : _name(n)
477         , _value(v)
478 {
479         // Normalize property name (replace '_' with '-' as old session are inconsistent)
480         for (size_t i = 0; i < _name.length(); ++i) {
481                 if (_name[i] == '_') {
482                         _name[i] = '-';
483                 }
484         }
485 }
486
487 XMLProperty::~XMLProperty()
488 {
489 }
490
491 static XMLNode*
492 readnode(xmlNodePtr node)
493 {
494         string name, content;
495         xmlNodePtr child;
496         XMLNode* tmp;
497         xmlAttrPtr attr;
498
499         if (node->name) {
500                 name = (char*)node->name;
501         }
502
503         tmp = new XMLNode(name);
504
505         for (attr = node->properties; attr; attr = attr->next) {
506                 content = "";
507                 if (attr->children) {
508                         content = (char*)attr->children->content;
509                 }
510                 tmp->add_property((char*)attr->name, content);
511         }
512
513         if (node->content) {
514                 tmp->set_content((char*)node->content);
515         } else {
516                 tmp->set_content(string());
517         }
518
519         for (child = node->children; child; child = child->next) {
520                 tmp->add_child_nocopy (*readnode(child));
521         }
522
523         return tmp;
524 }
525
526 static void
527 writenode(xmlDocPtr doc, XMLNode* n, xmlNodePtr p, int root = 0)
528 {
529         XMLPropertyList props;
530         XMLPropertyIterator curprop;
531         XMLNodeList children;
532         XMLNodeIterator curchild;
533         xmlNodePtr node;
534
535         if (root) {
536                 node = doc->children = xmlNewDocNode(doc, 0, (xmlChar*) n->name().c_str(), 0);
537         } else {
538                 node = xmlNewChild(p, 0, (xmlChar*) n->name().c_str(), 0);
539         }
540
541         if (n->is_content()) {
542                 node->type = XML_TEXT_NODE;
543                 xmlNodeSetContentLen(node, (const xmlChar*)n->content().c_str(), n->content().length());
544         }
545
546         props = n->properties();
547         for (curprop = props.begin(); curprop != props.end(); ++curprop) {
548                 xmlSetProp(node, (xmlChar*) (*curprop)->name().c_str(), (xmlChar*) (*curprop)->value().c_str());
549         }
550
551         children = n->children();
552         for (curchild = children.begin(); curchild != children.end(); ++curchild) {
553                 writenode(doc, *curchild, node);
554         }
555 }
556
557 static XMLSharedNodeList* find_impl(xmlXPathContext* ctxt, const string& xpath)
558 {
559         xmlXPathObject* result = xmlXPathEval((const xmlChar*)xpath.c_str(), ctxt);
560
561         if (!result) {
562                 xmlXPathFreeContext(ctxt);
563                 xmlFreeDoc(ctxt->doc);
564
565                 throw XMLException("Invalid XPath: " + xpath);
566         }
567
568         if (result->type != XPATH_NODESET) {
569                 xmlXPathFreeObject(result);
570                 xmlXPathFreeContext(ctxt);
571                 xmlFreeDoc(ctxt->doc);
572
573                 throw XMLException("Only nodeset result types are supported.");
574         }
575
576         xmlNodeSet* nodeset = result->nodesetval;
577         XMLSharedNodeList* nodes = new XMLSharedNodeList();
578         if (nodeset) {
579                 for (int i = 0; i < nodeset->nodeNr; ++i) {
580                         XMLNode* node = readnode(nodeset->nodeTab[i]);
581                         nodes->push_back(boost::shared_ptr<XMLNode>(node));
582                 }
583         } else {
584                 // return empty set
585         }
586
587         xmlXPathFreeObject(result);
588
589         return nodes;
590 }
591