Avoid assert() when loading xml: Throw an XMLerror if attribute_value fails.
[ardour.git] / libs / pbd / xml++.cc
index 6554bed9ee12c7e02812297f2251d79ef69b8fe3..c18d36f065addd195ca543ca4f8e053c011aa274 100644 (file)
@@ -6,12 +6,15 @@
  */
 
 #include <iostream>
+
+#include "pbd/stacktrace.h"
 #include "pbd/xml++.h"
+
 #include <libxml/debugXML.h>
 #include <libxml/xpath.h>
 #include <libxml/xpathInternals.h>
 
-#define XML_VERSION "1.0"
+xmlChar* xml_version = xmlCharStrdup("1.0");
 
 using namespace std;
 
@@ -42,7 +45,7 @@ XMLTree::XMLTree(const XMLTree* from)
        , _doc (xmlCopyDoc (from->_doc, 1))
        , _compression(from->compression())
 {
-       
+
 }
 
 XMLTree::~XMLTree()
@@ -82,26 +85,23 @@ XMLTree::read_internal(bool validate)
                _doc = 0;
        }
 
-       xmlParserCtxtPtr ctxt = NULL; /* the parser context */
+       /* create a parser context */
+       xmlParserCtxtPtr ctxt = xmlNewParserCtxt();
+       if (ctxt == NULL) {
+               return false;
+       }
 
        xmlKeepBlanksDefault(0);
        /* parse the file, activating the DTD validation option */
        if (validate) {
-               /* create a parser context */
-               ctxt = xmlNewParserCtxt();
-               if (ctxt == NULL) {
-                       return false;
-               }
                _doc = xmlCtxtReadFile(ctxt, _filename.c_str(), NULL, XML_PARSE_DTDVALID);
        } else {
-               _doc = xmlParseFile(_filename.c_str());
+               _doc = xmlCtxtReadFile(ctxt, _filename.c_str(), NULL, XML_PARSE_HUGE);
        }
-       
+
        /* check if parsing suceeded */
        if (_doc == NULL) {
-               if (validate) {
-                       xmlFreeParserCtxt(ctxt);
-               }
+               xmlFreeParserCtxt(ctxt);
                return false;
        } else {
                /* check if validation suceeded */
@@ -114,15 +114,13 @@ XMLTree::read_internal(bool validate)
        _root = readnode(xmlDocGetRootElement(_doc));
 
        /* free up the parser context */
-       if (validate) {
-               xmlFreeParserCtxt(ctxt);
-       }
-       
+       xmlFreeParserCtxt(ctxt);
+
        return true;
 }
 
 bool
-XMLTree::read_buffer(const string& buffer)
+XMLTree::read_buffer(const string& buffer, bool to_tree_doc)
 {
        xmlDocPtr doc;
 
@@ -137,7 +135,14 @@ XMLTree::read_buffer(const string& buffer)
        }
 
        _root = readnode(xmlDocGetRootElement(doc));
-       xmlFreeDoc(doc);
+       if (to_tree_doc) {
+               if (_doc) {
+                       xmlFreeDoc (_doc);
+               }
+               _doc = doc;
+       } else {
+               xmlFreeDoc (doc);
+       }
 
        return true;
 }
@@ -151,10 +156,24 @@ XMLTree::write() const
        int result;
 
        xmlKeepBlanksDefault(0);
-       doc = xmlNewDoc((xmlChar*) XML_VERSION);
+       doc = xmlNewDoc(xml_version);
        xmlSetDocCompressMode(doc, _compression);
        writenode(doc, _root, doc->children, 1);
        result = xmlSaveFormatFileEnc(_filename.c_str(), doc, "UTF-8", 1);
+#ifndef NDEBUG
+       if (result == -1) {
+               xmlErrorPtr xerr = xmlGetLastError ();
+               if (!xerr) {
+                       std::cerr << "unknown XML error during xmlSaveFormatFileEnc()." << std::endl;
+               } else {
+                       std::cerr << "xmlSaveFormatFileEnc: error"
+                               << " domain: " << xerr->domain
+                               << " code: " << xerr->code
+                               << " msg: " << xerr->message
+                               << std::endl;
+               }
+       }
+#endif
        xmlFreeDoc(doc);
 
        if (result == -1) {
@@ -167,15 +186,17 @@ XMLTree::write() const
 void
 XMLTree::debug(FILE* out) const
 {
+#ifdef LIBXML_DEBUG_ENABLED
        xmlDocPtr doc;
        XMLNodeList children;
 
        xmlKeepBlanksDefault(0);
-       doc = xmlNewDoc((xmlChar*) XML_VERSION);
+       doc = xmlNewDoc(xml_version);
        xmlSetDocCompressMode(doc, _compression);
        writenode(doc, _root, doc->children, 1);
        xmlDebugDumpDocument (out, doc);
        xmlFreeDoc(doc);
+#endif
 }
 
 const string&
@@ -188,7 +209,7 @@ XMLTree::write_buffer() const
        XMLNodeList children;
 
        xmlKeepBlanksDefault(0);
-       doc = xmlNewDoc((xmlChar*) XML_VERSION);
+       doc = xmlNewDoc(xml_version);
        xmlSetDocCompressMode(doc, _compression);
        writenode(doc, _root, doc->children, 1);
        xmlDocDumpMemory(doc, (xmlChar **) & ptr, &len);
@@ -201,10 +222,13 @@ XMLTree::write_buffer() const
        return retval;
 }
 
+static const int PROPERTY_RESERVE_COUNT = 16;
+
 XMLNode::XMLNode(const string& n)
        : _name(n)
        , _is_content(false)
 {
+       _proplist.reserve (PROPERTY_RESERVE_COUNT);
 }
 
 XMLNode::XMLNode(const string& n, const string& c)
@@ -212,10 +236,12 @@ XMLNode::XMLNode(const string& n, const string& c)
        , _is_content(true)
        , _content(c)
 {
+       _proplist.reserve (PROPERTY_RESERVE_COUNT);
 }
 
 XMLNode::XMLNode(const XMLNode& from)
 {
+       _proplist.reserve (PROPERTY_RESERVE_COUNT);
        *this = from;
 }
 
@@ -231,7 +257,6 @@ XMLNode::clear_lists ()
        XMLPropertyIterator curprop;
 
        _selected_children.clear ();
-       _propmap.clear ();
 
        for (curchild = _children.begin(); curchild != _children.end(); ++curchild) {
                delete *curchild;
@@ -246,33 +271,94 @@ XMLNode::clear_lists ()
        _proplist.clear ();
 }
 
-XMLNode& 
+XMLNode&
 XMLNode::operator= (const XMLNode& from)
 {
-       if (&from != this) {
-
-               XMLPropertyList props;
-               XMLPropertyIterator curprop;
-               XMLNodeList nodes;
-               XMLNodeIterator curnode;
-               
-               clear_lists ();
-
-               _name = from.name();
-               set_content(from.content());
-               
-               props = from.properties();
-               for (curprop = props.begin(); curprop != props.end(); ++curprop) {
-                       add_property((*curprop)->name().c_str(), (*curprop)->value());
+       if (&from == this) {
+               return *this;
+       }
+
+       clear_lists ();
+
+       _name = from.name ();
+       set_content (from.content ());
+
+       const XMLPropertyList& props = from.properties ();
+
+       for (XMLPropertyConstIterator prop_iter = props.begin (); prop_iter != props.end (); ++prop_iter) {
+               set_property ((*prop_iter)->name ().c_str (), (*prop_iter)->value ());
+       }
+
+       const XMLNodeList& nodes = from.children ();
+       for (XMLNodeConstIterator child_iter = nodes.begin (); child_iter != nodes.end (); ++child_iter) {
+               add_child_copy (**child_iter);
+       }
+
+       return *this;
+}
+
+bool
+XMLNode::operator== (const XMLNode& other) const
+{
+       if (is_content () != other.is_content ()) {
+               return false;
+       }
+
+       if (is_content ()) {
+               if (content () != other.content ()) {
+                       return false;
                }
-               
-               nodes = from.children();
-               for (curnode = nodes.begin(); curnode != nodes.end(); ++curnode) {
-                       add_child_copy(**curnode);
+       } else {
+               if (name () != other.name ()) {
+                       return false;
                }
        }
 
-       return *this;
+       XMLPropertyList const& other_properties = other.properties ();
+
+       if (_proplist.size () != other_properties.size ()) {
+               return false;
+       }
+
+       XMLPropertyConstIterator our_prop_iter = _proplist.begin();
+       XMLPropertyConstIterator other_prop_iter = other_properties.begin();
+
+       while (our_prop_iter != _proplist.end ()) {
+               XMLProperty const* our_prop = *our_prop_iter;
+               XMLProperty const* other_prop = *other_prop_iter;
+               if (our_prop->name () != other_prop->name () || our_prop->value () != other_prop->value ()) {
+                       return false;
+               }
+               ++our_prop_iter;
+               ++other_prop_iter;
+       }
+
+       XMLNodeList const& other_children = other.children();
+
+       if (_children.size() != other_children.size()) {
+               return false;
+       }
+
+       XMLNodeConstIterator our_child_iter = _children.begin ();
+       XMLNodeConstIterator other_child_iter = other_children.begin ();
+
+       while (our_child_iter != _children.end()) {
+               XMLNode const* our_child = *our_child_iter;
+               XMLNode const* other_child = *other_child_iter;
+
+               if (*our_child != *other_child) {
+                       return false;
+               }
+               ++our_child_iter;
+               ++other_child_iter;
+       }
+       return true;
+}
+
+bool
+XMLNode::operator!= (const XMLNode& other) const
+{
+       return !(*this == other);
 }
 
 const string&
@@ -358,16 +444,16 @@ XMLTree::find(const string xpath, XMLNode* node) const
        xmlDocPtr doc = 0;
 
        if (node) {
-               doc = xmlNewDoc((xmlChar*) XML_VERSION);
+               doc = xmlNewDoc(xml_version);
                writenode(doc, node, doc->children, 1);
                ctxt = xmlXPathNewContext(doc);
        } else {
                ctxt = xmlXPathNewContext(_doc);
        }
-       
+
        boost::shared_ptr<XMLSharedNodeList> result =
                boost::shared_ptr<XMLSharedNodeList>(find_impl(ctxt, xpath));
-       
+
        xmlXPathFreeContext(ctxt);
        if (doc) {
                xmlFreeDoc (doc);
@@ -380,10 +466,16 @@ std::string
 XMLNode::attribute_value()
 {
        XMLNodeList children = this->children();
-       assert(!_is_content);
-       assert(children.size() == 1);
+       if (_is_content)
+               throw XMLException("XMLNode: attribute_value failed (is_content) for requested node: " + name());
+
+       if (children.size() != 1)
+               throw XMLException("XMLNode: attribute_value failed (children.size != 1) for requested node: " + name());
+
        XMLNode* child = *(children.begin());
-       assert(child->is_content());
+       if (!child->is_content())
+               throw XMLException("XMLNode: attribute_value failed (!child->is_content()) for requested node: " + name());
+
        return child->content();
 }
 
@@ -393,77 +485,127 @@ XMLNode::add_content(const string& c)
        return add_child_copy(XMLNode (string(), c));
 }
 
-XMLProperty*
-XMLNode::property(const char* n)
+XMLProperty const *
+XMLNode::property(const char* name) const
 {
-       string ns(n);
-       map<string,XMLProperty*>::iterator iter;
+       XMLPropertyConstIterator iter = _proplist.begin();
 
-       if ((iter = _propmap.find(ns)) != _propmap.end()) {
-               return iter->second;
+       while (iter != _proplist.end()) {
+               if ((*iter)->name() == name) {
+                       return *iter;
+               }
+               ++iter;
        }
 
        return 0;
 }
 
-XMLProperty*
-XMLNode::property(const string& ns)
+XMLProperty const *
+XMLNode::property(const string& name) const
 {
-       map<string,XMLProperty*>::iterator iter;
+       XMLPropertyConstIterator iter = _proplist.begin();
 
-       if ((iter = _propmap.find(ns)) != _propmap.end()) {
-               return iter->second;
+       while (iter != _proplist.end()) {
+               if ((*iter)->name() == name) {
+                       return *iter;
+               }
+               ++iter;
        }
-
        return 0;
 }
 
-XMLProperty*
-XMLNode::add_property(const char* n, const string& v)
+XMLProperty *
+XMLNode::property(const char* name)
 {
-       string ns(n);
-        map<string,XMLProperty*>::iterator iter;
-       
-        if ((iter = _propmap.find(ns)) != _propmap.end()) {
-                iter->second->set_value (v);
-                return iter->second;
+       XMLPropertyIterator iter = _proplist.begin();
+
+       while (iter != _proplist.end()) {
+               if ((*iter)->name() == name) {
+                       return *iter;
+               }
+               ++iter;
        }
+       return 0;
+}
 
-       XMLProperty* tmp = new XMLProperty(ns, v);
+XMLProperty *
+XMLNode::property(const string& name)
+{
+       XMLPropertyIterator iter = _proplist.begin();
 
-       if (!tmp) {
-               return 0;
+       while (iter != _proplist.end()) {
+               if ((*iter)->name() == name) {
+                       return *iter;
+               }
+               ++iter;
        }
 
-       _propmap[tmp->name()] = tmp;
-       _proplist.insert(_proplist.end(), tmp);
+       return 0;
+}
 
-       return tmp;
+bool
+XMLNode::has_property_with_value (const string& name, const string& value) const
+{
+       XMLPropertyConstIterator iter = _proplist.begin();
+
+       while (iter != _proplist.end()) {
+               if ((*iter)->name() == name && (*iter)->value() == value) {
+                       return true;
+               }
+               ++iter;
+       }
+       return false;
 }
 
-XMLProperty*
-XMLNode::add_property(const char* n, const char* v)
+bool
+XMLNode::set_property(const char* name, const string& value)
 {
-       string vs(v);
-       return add_property(n, vs);
+       XMLPropertyIterator iter = _proplist.begin();
+
+       while (iter != _proplist.end()) {
+               if ((*iter)->name() == name) {
+                       (*iter)->set_value (value);
+                       return *iter;
+               }
+               ++iter;
+       }
+
+       XMLProperty* new_property = new XMLProperty(name, value);
+
+       if (!new_property) {
+               return 0;
+       }
+
+       _proplist.insert(_proplist.end(), new_property);
+
+       return new_property;
 }
 
-XMLProperty*
-XMLNode::add_property(const char* name, const long value)
+bool
+XMLNode::get_property(const char* name, std::string& value) const
 {
-       char str[64];
-       snprintf(str, sizeof(str), "%ld", value);
-       return add_property(name, str);
+       XMLProperty const* const prop = property (name);
+       if (!prop)
+               return false;
+
+       value = prop->value ();
+
+       return true;
 }
 
 void
-XMLNode::remove_property(const string& n)
+XMLNode::remove_property(const string& name)
 {
-       if (_propmap.find(n) != _propmap.end()) {
-               XMLProperty* p = _propmap[n];
-               _proplist.remove (p);
-               delete p;
-               _propmap.erase(n);
+       XMLPropertyIterator iter = _proplist.begin();
+
+       while (iter != _proplist.end()) {
+               if ((*iter)->name() == name) {
+                       XMLProperty* property = *iter;
+                       _proplist.erase (iter);
+                       delete property;
+                       break;
+               }
+               ++iter;
        }
 }
 
@@ -481,15 +623,12 @@ void
 XMLNode::remove_nodes(const string& n)
 {
        XMLNodeIterator i = _children.begin();
-       XMLNodeIterator tmp;
-
        while (i != _children.end()) {
-               tmp = i;
-               ++tmp;
                if ((*i)->name() == n) {
-                       _children.erase (i);
+                       i = _children.erase (i);
+               } else {
+                       ++i;
                }
-               i = tmp;
        }
 }
 
@@ -497,16 +636,14 @@ void
 XMLNode::remove_nodes_and_delete(const string& n)
 {
        XMLNodeIterator i = _children.begin();
-       XMLNodeIterator tmp;
 
        while (i != _children.end()) {
-               tmp = i;
-               ++tmp;
                if ((*i)->name() == n) {
                        delete *i;
-                       _children.erase (i);
+                       i = _children.erase (i);
+               } else {
+                       ++i;
                }
-               i = tmp;
        }
 }
 
@@ -514,20 +651,32 @@ void
 XMLNode::remove_nodes_and_delete(const string& propname, const string& val)
 {
        XMLNodeIterator i = _children.begin();
-       XMLNodeIterator tmp;
-       XMLProperty* prop;
+       XMLProperty const * prop;
 
        while (i != _children.end()) {
-               tmp = i;
-               ++tmp;
-
                prop = (*i)->property(propname);
                if (prop && prop->value() == val) {
                        delete *i;
-                       _children.erase(i);
+                       i = _children.erase(i);
+               } else {
+                       ++i;
                }
+       }
+}
 
-               i = tmp;
+void
+XMLNode::remove_node_and_delete(const string& n, const string& propname,
+ const string& val)
+{
+       for (XMLNodeIterator i = _children.begin(); i != _children.end(); ++i) {
+               if ((*i)->name() == n) {
+                       XMLProperty const * prop = (*i)->property (propname);
+                       if (prop && prop->value() == val) {
+                               delete *i;
+                               _children.erase (i);
+                               break;
+                       }
+               }
        }
 }
 
@@ -535,12 +684,6 @@ XMLProperty::XMLProperty(const string& n, const string& v)
        : _name(n)
        , _value(v)
 {
-       // Normalize property name (replace '_' with '-' as old session are inconsistent)
-       for (size_t i = 0; i < _name.length(); ++i) {
-               if (_name[i] == '_') {
-                       _name[i] = '-';
-               }
-       }
 }
 
 XMLProperty::~XMLProperty()
@@ -556,7 +699,7 @@ readnode(xmlNodePtr node)
        xmlAttrPtr attr;
 
        if (node->name) {
-               name = (char*)node->name;
+               name = (const char*)node->name;
        }
 
        tmp = new XMLNode(name);
@@ -566,7 +709,7 @@ readnode(xmlNodePtr node)
                if (attr->children) {
                        content = (char*)attr->children->content;
                }
-               tmp->add_property((char*)attr->name, content);
+               tmp->set_property((const char*)attr->name, content);
        }
 
        if (node->content) {
@@ -585,16 +728,12 @@ readnode(xmlNodePtr node)
 static void
 writenode(xmlDocPtr doc, XMLNode* n, xmlNodePtr p, int root = 0)
 {
-       XMLPropertyList props;
-       XMLPropertyIterator curprop;
-       XMLNodeList children;
-       XMLNodeIterator curchild;
        xmlNodePtr node;
 
        if (root) {
-               node = doc->children = xmlNewDocNode(doc, 0, (xmlChar*) n->name().c_str(), 0);
+               node = doc->children = xmlNewDocNode(doc, 0, (const xmlChar*) n->name().c_str(), 0);
        } else {
-               node = xmlNewChild(p, 0, (xmlChar*) n->name().c_str(), 0);
+               node = xmlNewChild(p, 0, (const xmlChar*) n->name().c_str(), 0);
        }
 
        if (n->is_content()) {
@@ -602,14 +741,18 @@ writenode(xmlDocPtr doc, XMLNode* n, xmlNodePtr p, int root = 0)
                xmlNodeSetContentLen(node, (const xmlChar*)n->content().c_str(), n->content().length());
        }
 
-       props = n->properties();
-       for (curprop = props.begin(); curprop != props.end(); ++curprop) {
-               xmlSetProp(node, (xmlChar*) (*curprop)->name().c_str(), (xmlChar*) (*curprop)->value().c_str());
+       const XMLPropertyList& props = n->properties();
+
+       for (XMLPropertyConstIterator prop_iter = props.begin (); prop_iter != props.end ();
+            ++prop_iter) {
+               xmlSetProp (node, (const xmlChar*)(*prop_iter)->name ().c_str (),
+                           (const xmlChar*)(*prop_iter)->value ().c_str ());
        }
 
-       children = n->children();
-       for (curchild = children.begin(); curchild != children.end(); ++curchild) {
-               writenode(doc, *curchild, node);
+       const XMLNodeList& children = n->children ();
+       for (XMLNodeConstIterator child_iter = children.begin (); child_iter != children.end ();
+            ++child_iter) {
+               writenode (doc, *child_iter, node);
        }
 }
 
@@ -660,11 +803,11 @@ XMLNode::dump (ostream& s, string p) const
                        s << " " << (*i)->name() << "=\"" << (*i)->value() << "\"";
                }
                s << ">\n";
-               
+
                for (XMLNodeList::const_iterator i = _children.begin(); i != _children.end(); ++i) {
                        (*i)->dump (s, p + "  ");
                }
-               
+
                s << p << "</" << _name << ">\n";
        }
 }