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