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