Tim Mayberry
6e467153a0
These are now unused and functionality is replaced by XMLNode::set_property set_property is a better name as a node can only have properties with unique names and the property will be set or reset(if it already exists). Changing the name also makes it easier to transition and test the new API.
798 lines
15 KiB
C++
798 lines
15 KiB
C++
/* xml++.cc
|
|
* libxml++ and this file are copyright (C) 2000 by Ari Johnson, and
|
|
* are covered by the GNU Lesser General Public License, which should be
|
|
* included with libxml++ as the file COPYING.
|
|
* Modified for Ardour and released under the same terms.
|
|
*/
|
|
|
|
#include <iostream>
|
|
|
|
#include "pbd/stacktrace.h"
|
|
#include "pbd/xml++.h"
|
|
|
|
#include <libxml/debugXML.h>
|
|
#include <libxml/xpath.h>
|
|
#include <libxml/xpathInternals.h>
|
|
|
|
xmlChar* xml_version = xmlCharStrdup("1.0");
|
|
|
|
using namespace std;
|
|
|
|
static XMLNode* readnode(xmlNodePtr);
|
|
static void writenode(xmlDocPtr, XMLNode*, xmlNodePtr, int);
|
|
static XMLSharedNodeList* find_impl(xmlXPathContext* ctxt, const string& xpath);
|
|
|
|
XMLTree::XMLTree()
|
|
: _filename()
|
|
, _root(0)
|
|
, _doc (0)
|
|
, _compression(0)
|
|
{
|
|
}
|
|
|
|
XMLTree::XMLTree(const string& fn, bool validate)
|
|
: _filename(fn)
|
|
, _root(0)
|
|
, _doc (0)
|
|
, _compression(0)
|
|
{
|
|
read_internal(validate);
|
|
}
|
|
|
|
XMLTree::XMLTree(const XMLTree* from)
|
|
: _filename(from->filename())
|
|
, _root(new XMLNode(*from->root()))
|
|
, _doc (xmlCopyDoc (from->_doc, 1))
|
|
, _compression(from->compression())
|
|
{
|
|
|
|
}
|
|
|
|
XMLTree::~XMLTree()
|
|
{
|
|
delete _root;
|
|
|
|
if (_doc) {
|
|
xmlFreeDoc (_doc);
|
|
}
|
|
}
|
|
|
|
int
|
|
XMLTree::set_compression(int c)
|
|
{
|
|
if (c > 9) {
|
|
c = 9;
|
|
} else if (c < 0) {
|
|
c = 0;
|
|
}
|
|
|
|
_compression = c;
|
|
|
|
return _compression;
|
|
}
|
|
|
|
bool
|
|
XMLTree::read_internal(bool validate)
|
|
{
|
|
//shouldnt be used anywhere ATM, remove if so!
|
|
assert(!validate);
|
|
|
|
delete _root;
|
|
_root = 0;
|
|
|
|
if (_doc) {
|
|
xmlFreeDoc (_doc);
|
|
_doc = 0;
|
|
}
|
|
|
|
/* create a parser context */
|
|
xmlParserCtxtPtr ctxt = xmlNewParserCtxt();
|
|
if (ctxt == NULL) {
|
|
return false;
|
|
}
|
|
|
|
xmlKeepBlanksDefault(0);
|
|
/* parse the file, activating the DTD validation option */
|
|
if (validate) {
|
|
_doc = xmlCtxtReadFile(ctxt, _filename.c_str(), NULL, XML_PARSE_DTDVALID);
|
|
} else {
|
|
_doc = xmlCtxtReadFile(ctxt, _filename.c_str(), NULL, XML_PARSE_HUGE);
|
|
}
|
|
|
|
/* check if parsing suceeded */
|
|
if (_doc == NULL) {
|
|
xmlFreeParserCtxt(ctxt);
|
|
return false;
|
|
} else {
|
|
/* check if validation suceeded */
|
|
if (validate && ctxt->valid == 0) {
|
|
xmlFreeParserCtxt(ctxt);
|
|
throw XMLException("Failed to validate document " + _filename);
|
|
}
|
|
}
|
|
|
|
_root = readnode(xmlDocGetRootElement(_doc));
|
|
|
|
/* free up the parser context */
|
|
xmlFreeParserCtxt(ctxt);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
XMLTree::read_buffer(const string& buffer, bool to_tree_doc)
|
|
{
|
|
xmlDocPtr doc;
|
|
|
|
_filename = "";
|
|
|
|
delete _root;
|
|
_root = 0;
|
|
|
|
doc = xmlParseMemory(const_cast<char*>(buffer.c_str()), buffer.length());
|
|
if (!doc) {
|
|
return false;
|
|
}
|
|
|
|
_root = readnode(xmlDocGetRootElement(doc));
|
|
if (to_tree_doc) {
|
|
if (_doc) {
|
|
xmlFreeDoc (_doc);
|
|
}
|
|
_doc = doc;
|
|
} else {
|
|
xmlFreeDoc (doc);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool
|
|
XMLTree::write() const
|
|
{
|
|
xmlDocPtr doc;
|
|
XMLNodeList children;
|
|
int result;
|
|
|
|
xmlKeepBlanksDefault(0);
|
|
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) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
XMLTree::debug(FILE* out) const
|
|
{
|
|
#ifdef LIBXML_DEBUG_ENABLED
|
|
xmlDocPtr doc;
|
|
XMLNodeList children;
|
|
|
|
xmlKeepBlanksDefault(0);
|
|
doc = xmlNewDoc(xml_version);
|
|
xmlSetDocCompressMode(doc, _compression);
|
|
writenode(doc, _root, doc->children, 1);
|
|
xmlDebugDumpDocument (out, doc);
|
|
xmlFreeDoc(doc);
|
|
#endif
|
|
}
|
|
|
|
const string&
|
|
XMLTree::write_buffer() const
|
|
{
|
|
static string retval;
|
|
char* ptr;
|
|
int len;
|
|
xmlDocPtr doc;
|
|
XMLNodeList children;
|
|
|
|
xmlKeepBlanksDefault(0);
|
|
doc = xmlNewDoc(xml_version);
|
|
xmlSetDocCompressMode(doc, _compression);
|
|
writenode(doc, _root, doc->children, 1);
|
|
xmlDocDumpMemory(doc, (xmlChar **) & ptr, &len);
|
|
xmlFreeDoc(doc);
|
|
|
|
retval = ptr;
|
|
|
|
free(ptr);
|
|
|
|
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)
|
|
: _name(n)
|
|
, _is_content(true)
|
|
, _content(c)
|
|
{
|
|
_proplist.reserve (PROPERTY_RESERVE_COUNT);
|
|
}
|
|
|
|
XMLNode::XMLNode(const XMLNode& from)
|
|
{
|
|
_proplist.reserve (PROPERTY_RESERVE_COUNT);
|
|
*this = from;
|
|
}
|
|
|
|
XMLNode::~XMLNode()
|
|
{
|
|
clear_lists ();
|
|
}
|
|
|
|
void
|
|
XMLNode::clear_lists ()
|
|
{
|
|
XMLNodeIterator curchild;
|
|
XMLPropertyIterator curprop;
|
|
|
|
_selected_children.clear ();
|
|
|
|
for (curchild = _children.begin(); curchild != _children.end(); ++curchild) {
|
|
delete *curchild;
|
|
}
|
|
|
|
_children.clear ();
|
|
|
|
for (curprop = _proplist.begin(); curprop != _proplist.end(); ++curprop) {
|
|
delete *curprop;
|
|
}
|
|
|
|
_proplist.clear ();
|
|
}
|
|
|
|
XMLNode&
|
|
XMLNode::operator= (const XMLNode& from)
|
|
{
|
|
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;
|
|
}
|
|
} else {
|
|
if (name () != other.name ()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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&
|
|
XMLNode::set_content(const string& c)
|
|
{
|
|
if (c.empty()) {
|
|
_is_content = false;
|
|
} else {
|
|
_is_content = true;
|
|
}
|
|
|
|
_content = c;
|
|
|
|
return _content;
|
|
}
|
|
|
|
XMLNode*
|
|
XMLNode::child (const char* name) const
|
|
{
|
|
/* returns first child matching name */
|
|
|
|
XMLNodeConstIterator cur;
|
|
|
|
if (name == 0) {
|
|
return 0;
|
|
}
|
|
|
|
for (cur = _children.begin(); cur != _children.end(); ++cur) {
|
|
if ((*cur)->name() == name) {
|
|
return *cur;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
const XMLNodeList&
|
|
XMLNode::children(const string& n) const
|
|
{
|
|
/* returns all children matching name */
|
|
|
|
XMLNodeConstIterator cur;
|
|
|
|
if (n.empty()) {
|
|
return _children;
|
|
}
|
|
|
|
_selected_children.clear();
|
|
|
|
for (cur = _children.begin(); cur != _children.end(); ++cur) {
|
|
if ((*cur)->name() == n) {
|
|
_selected_children.insert(_selected_children.end(), *cur);
|
|
}
|
|
}
|
|
|
|
return _selected_children;
|
|
}
|
|
|
|
XMLNode*
|
|
XMLNode::add_child(const char* n)
|
|
{
|
|
return add_child_copy(XMLNode (n));
|
|
}
|
|
|
|
void
|
|
XMLNode::add_child_nocopy(XMLNode& n)
|
|
{
|
|
_children.insert(_children.end(), &n);
|
|
}
|
|
|
|
XMLNode*
|
|
XMLNode::add_child_copy(const XMLNode& n)
|
|
{
|
|
XMLNode *copy = new XMLNode(n);
|
|
_children.insert(_children.end(), copy);
|
|
return copy;
|
|
}
|
|
|
|
boost::shared_ptr<XMLSharedNodeList>
|
|
XMLTree::find(const string xpath, XMLNode* node) const
|
|
{
|
|
xmlXPathContext* ctxt;
|
|
xmlDocPtr doc = 0;
|
|
|
|
if (node) {
|
|
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);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
std::string
|
|
XMLNode::attribute_value()
|
|
{
|
|
XMLNodeList children = this->children();
|
|
assert(!_is_content);
|
|
assert(children.size() == 1);
|
|
XMLNode* child = *(children.begin());
|
|
assert(child->is_content());
|
|
return child->content();
|
|
}
|
|
|
|
XMLNode*
|
|
XMLNode::add_content(const string& c)
|
|
{
|
|
return add_child_copy(XMLNode (string(), c));
|
|
}
|
|
|
|
XMLProperty const *
|
|
XMLNode::property(const char* name) const
|
|
{
|
|
XMLPropertyConstIterator iter = _proplist.begin();
|
|
|
|
while (iter != _proplist.end()) {
|
|
if ((*iter)->name() == name) {
|
|
return *iter;
|
|
}
|
|
++iter;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
XMLProperty const *
|
|
XMLNode::property(const string& name) const
|
|
{
|
|
XMLPropertyConstIterator iter = _proplist.begin();
|
|
|
|
while (iter != _proplist.end()) {
|
|
if ((*iter)->name() == name) {
|
|
return *iter;
|
|
}
|
|
++iter;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
XMLProperty *
|
|
XMLNode::property(const char* name)
|
|
{
|
|
XMLPropertyIterator iter = _proplist.begin();
|
|
|
|
while (iter != _proplist.end()) {
|
|
if ((*iter)->name() == name) {
|
|
return *iter;
|
|
}
|
|
++iter;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
XMLProperty *
|
|
XMLNode::property(const string& name)
|
|
{
|
|
XMLPropertyIterator iter = _proplist.begin();
|
|
|
|
while (iter != _proplist.end()) {
|
|
if ((*iter)->name() == name) {
|
|
return *iter;
|
|
}
|
|
++iter;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool
|
|
XMLNode::set_property(const char* name, const string& value)
|
|
{
|
|
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;
|
|
}
|
|
|
|
bool
|
|
XMLNode::get_property(const char* name, std::string& value) const
|
|
{
|
|
XMLProperty const* const prop = property (name);
|
|
if (!prop)
|
|
return false;
|
|
|
|
value = prop->value ();
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
XMLNode::remove_property(const string& name)
|
|
{
|
|
XMLPropertyIterator iter = _proplist.begin();
|
|
|
|
while (iter != _proplist.end()) {
|
|
if ((*iter)->name() == name) {
|
|
XMLProperty* property = *iter;
|
|
_proplist.erase (iter);
|
|
delete property;
|
|
break;
|
|
}
|
|
++iter;
|
|
}
|
|
}
|
|
|
|
/** Remove any property with the given name from this node and its children */
|
|
void
|
|
XMLNode::remove_property_recursively(const string& n)
|
|
{
|
|
remove_property (n);
|
|
for (XMLNodeIterator i = _children.begin(); i != _children.end(); ++i) {
|
|
(*i)->remove_property_recursively (n);
|
|
}
|
|
}
|
|
|
|
void
|
|
XMLNode::remove_nodes(const string& n)
|
|
{
|
|
XMLNodeIterator i = _children.begin();
|
|
while (i != _children.end()) {
|
|
if ((*i)->name() == n) {
|
|
i = _children.erase (i);
|
|
} else {
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
XMLNode::remove_nodes_and_delete(const string& n)
|
|
{
|
|
XMLNodeIterator i = _children.begin();
|
|
|
|
while (i != _children.end()) {
|
|
if ((*i)->name() == n) {
|
|
delete *i;
|
|
i = _children.erase (i);
|
|
} else {
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
XMLNode::remove_nodes_and_delete(const string& propname, const string& val)
|
|
{
|
|
XMLNodeIterator i = _children.begin();
|
|
XMLProperty const * prop;
|
|
|
|
while (i != _children.end()) {
|
|
prop = (*i)->property(propname);
|
|
if (prop && prop->value() == val) {
|
|
delete *i;
|
|
i = _children.erase(i);
|
|
} else {
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
|
|
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()
|
|
{
|
|
}
|
|
|
|
static XMLNode*
|
|
readnode(xmlNodePtr node)
|
|
{
|
|
string name, content;
|
|
xmlNodePtr child;
|
|
XMLNode* tmp;
|
|
xmlAttrPtr attr;
|
|
|
|
if (node->name) {
|
|
name = (const char*)node->name;
|
|
}
|
|
|
|
tmp = new XMLNode(name);
|
|
|
|
for (attr = node->properties; attr; attr = attr->next) {
|
|
content = "";
|
|
if (attr->children) {
|
|
content = (char*)attr->children->content;
|
|
}
|
|
tmp->set_property((const char*)attr->name, content);
|
|
}
|
|
|
|
if (node->content) {
|
|
tmp->set_content((char*)node->content);
|
|
} else {
|
|
tmp->set_content(string());
|
|
}
|
|
|
|
for (child = node->children; child; child = child->next) {
|
|
tmp->add_child_nocopy (*readnode(child));
|
|
}
|
|
|
|
return tmp;
|
|
}
|
|
|
|
static void
|
|
writenode(xmlDocPtr doc, XMLNode* n, xmlNodePtr p, int root = 0)
|
|
{
|
|
xmlNodePtr node;
|
|
|
|
if (root) {
|
|
node = doc->children = xmlNewDocNode(doc, 0, (const xmlChar*) n->name().c_str(), 0);
|
|
} else {
|
|
node = xmlNewChild(p, 0, (const xmlChar*) n->name().c_str(), 0);
|
|
}
|
|
|
|
if (n->is_content()) {
|
|
node->type = XML_TEXT_NODE;
|
|
xmlNodeSetContentLen(node, (const xmlChar*)n->content().c_str(), n->content().length());
|
|
}
|
|
|
|
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 ());
|
|
}
|
|
|
|
const XMLNodeList& children = n->children ();
|
|
for (XMLNodeConstIterator child_iter = children.begin (); child_iter != children.end ();
|
|
++child_iter) {
|
|
writenode (doc, *child_iter, node);
|
|
}
|
|
}
|
|
|
|
static XMLSharedNodeList* find_impl(xmlXPathContext* ctxt, const string& xpath)
|
|
{
|
|
xmlXPathObject* result = xmlXPathEval((const xmlChar*)xpath.c_str(), ctxt);
|
|
|
|
if (!result) {
|
|
xmlXPathFreeContext(ctxt);
|
|
xmlFreeDoc(ctxt->doc);
|
|
|
|
throw XMLException("Invalid XPath: " + xpath);
|
|
}
|
|
|
|
if (result->type != XPATH_NODESET) {
|
|
xmlXPathFreeObject(result);
|
|
xmlXPathFreeContext(ctxt);
|
|
xmlFreeDoc(ctxt->doc);
|
|
|
|
throw XMLException("Only nodeset result types are supported.");
|
|
}
|
|
|
|
xmlNodeSet* nodeset = result->nodesetval;
|
|
XMLSharedNodeList* nodes = new XMLSharedNodeList();
|
|
if (nodeset) {
|
|
for (int i = 0; i < nodeset->nodeNr; ++i) {
|
|
XMLNode* node = readnode(nodeset->nodeTab[i]);
|
|
nodes->push_back(boost::shared_ptr<XMLNode>(node));
|
|
}
|
|
} else {
|
|
// return empty set
|
|
}
|
|
|
|
xmlXPathFreeObject(result);
|
|
|
|
return nodes;
|
|
}
|
|
|
|
/** Dump a node, its properties and children to a stream */
|
|
void
|
|
XMLNode::dump (ostream& s, string p) const
|
|
{
|
|
if (_is_content) {
|
|
s << p << " " << content() << "\n";
|
|
} else {
|
|
s << p << "<" << _name;
|
|
for (XMLPropertyList::const_iterator i = _proplist.begin(); i != _proplist.end(); ++i) {
|
|
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";
|
|
}
|
|
}
|