//
// Copyright (c) 2004 Russell E. Gibson
// email: russg@rnstech.com
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is furnished
// to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#include <algorithm>
#include <locale>
#include "root.hpp"
using namespace root;
//=============================================================================
// Local functions
namespace
{
// Called on reading to clear out whitespace and control characters from
// an XML_TEXT_NODE. The idea is to get the actual value of an element
// rather than the whitespace used in formatting the file its in.
xmlChar* trim(xmlChar* t)
{
std::locale l = std::locale();
while (*t && (std::iscntrl(*t, l) || *t == ' '))
++t;
return t;
}
// Called on reading to clear out whitespace and control characters from
// an XML_CDATA_SECTION_NODE. The idea is to get the actual value of an
// unless its followed by an end of line first.
xmlChar* trim_cdata(xmlChar* t)
{
std::locale l = std::locale();
xmlChar* lt = t;
while (*t)
{
// Found end of line?
if (*t == '\r')
{
// Skip it
++t;
// Is it followed by linefeed?
if (*t != '\n')
return t;
// Yes, so skip it too
++t;
return t;
}
// Found linefed (unix eol)?
if (*t == '\n')
return ++t;
// Is it not a whitespace or control char?
if (!std::iscntrl(*t, l) && !std::isspace(*t, l))
return lt;
++t;
}
return t;
}
// Functor to call write on each child element
template <typename T>
class writer
{
public:
writer(xmlTextWriterPtr w)
:
_w(w)
{
}
void operator()(const T& t)
{
t.write(_w);
}
private:
xmlTextWriterPtr _w;
};
}
//=============================================================================
// Element class Child implementation
Child::Child(const char* f)
{
// Read in document
xmlDocPtr d = xmlParseFile(f);
if (!d)
throw std::runtime_error("file parse failed");
try
{
// Initialize from root node
xmlNodePtr r = xmlDocGetRootElement(d);
if (r)
read(r);
// Clean up
xmlFreeDoc(d);
}
catch (...)
{
xmlFreeDoc(d);
throw;
}
}
Child::Child(xmlNodePtr n)
{
// Initialize from node
read(n);
}
Child& Child::operator=(const Child& r)
{
if (this != &r)
{
_number = r._number;
_values = r._values;
}
return *this;
}
void Child::read(xmlNodePtr n)
{
// Make sure node is this element
if (xmlStrcmp(n->name, reinterpret_cast<const xmlChar*>("child")) != 0)
throw std::runtime_error("expecting node 'child'");
// Get attributes if provided
xmlChar* a = xmlGetProp(n, reinterpret_cast<xmlChar*>("number"));
if (a)
{
_number = reinterpret_cast<const char*>(a);
xmlFree(a);
}
// Retreive value
for (xmlNodePtr c = n->children; c; c = c->next)
{
switch (c->type)
{
case XML_TEXT_NODE:
{
xmlChar* t = trim(c->content);
if (*t)
{
_values.push_back(std::make_pair(false,
reinterpret_cast<const char*>(t)));
}
}
break;
case XML_CDATA_SECTION_NODE:
{
xmlChar* t = trim_cdata(c->content);
if (*t)
{
_values.push_back(std::make_pair(false,
reinterpret_cast<const char*>(t)));
}
}
break;
case XML_ELEMENT_NODE:
_values.push_back(std::make_pair(true, std::string()));
break;
}
}
}
void Child::value(std::string& v) const
{
const VALUES::const_iterator end = _values.end();
for (VALUES::const_iterator i = _values.begin(); i != end; ++i)
v += i->second;
}
void Child::write(const char* f, bool i, const char* t) const
{
// Create a new writer
xmlTextWriterPtr w = xmlNewTextWriterFilename(f, 0);
if (!w)
throw std::runtime_error("unable to create xmlTextWriter");
// Set indentation if desired
if (i)
{
xmlTextWriterSetIndent(w, 1);
xmlTextWriterSetIndentString(w, reinterpret_cast<xmlChar*>(t ? t : "\t"));
}
try
{
// Create document
int r = xmlTextWriterStartDocument(w, "1.0", "iso8859-1", "yes");
if (r < 0)
throw std::runtime_error("unable to start document");
// Add the child elements
write(w);
// Close the writer
xmlFreeTextWriter(w);
}
catch (...)
{
xmlFreeTextWriter(w);
throw;
}
}
void Child::write(xmlTextWriterPtr w) const
{
// Create element
int r = xmlTextWriterStartElement(w, reinterpret_cast<const xmlChar*>("child"));
if (r < 0)
throw std::runtime_error("creation of element 'child' failed");
// Add attribute number if needed
if (!_number.empty())
{
r = xmlTextWriterWriteAttribute(w, reinterpret_cast<const xmlChar*>("number"),
reinterpret_cast<const xmlChar*>(_number.c_str()));
if (r < 0)
{
throw std::runtime_error("addition of attribute 'number' "
"to element 'child' failed");
}
}
// Write value if needed
std::string v;
value(v);
if (!v.empty())
{
r = xmlTextWriterWriteString(w, reinterpret_cast<const xmlChar*>(v.c_str()));
if (r < 0)
throw std::runtime_error("failed writing 'child' value");
}
// All done!
r = xmlTextWriterEndElement(w);
if (r < 0)
throw std::runtime_error("failed closing 'child' element");
}
//=============================================================================
// Element class Element implementation
Element::Element(const char* f)
{
// Read in document
xmlDocPtr d = xmlParseFile(f);
if (!d)
throw std::runtime_error("file parse failed");
try
{
// Initialize from root node
xmlNodePtr r = xmlDocGetRootElement(d);
if (r)
read(r);
// Clean up
xmlFreeDoc(d);
}
catch (...)
{
xmlFreeDoc(d);
throw;
}
}
Element::Element(xmlNodePtr n)
{
// Initialize from node
read(n);
}
Element& Element::operator=(const Element& r)
{
if (this != &r)
{
_childs = r._childs;
_values = r._values;
}
return *this;
}
void Element::read(xmlNodePtr n)
{
// Make sure node is this element
if (xmlStrcmp(n->name, reinterpret_cast<const xmlChar*>("element")) != 0)
throw std::runtime_error("expecting node 'element'");
// Read the child elements
for (xmlNodePtr s = n->children; s; s = s->next)
{
// Ignore non-element children
if (s->type != XML_ELEMENT_NODE)
continue;
if (xmlStrcmp(s->name, reinterpret_cast<const xmlChar*>("child")) == 0)
{
_childs.push_back(Child(s));
continue;
}
}
// Retreive value
for (xmlNodePtr c = n->children; c; c = c->next)
{
switch (c->type)
{
case XML_TEXT_NODE:
{
xmlChar* t = trim(c->content);
if (*t)
{
_values.push_back(std::make_pair(false,
reinterpret_cast<const char*>(t)));
}
}
break;
case XML_CDATA_SECTION_NODE:
{
xmlChar* t = trim_cdata(c->content);
if (*t)
{
_values.push_back(std::make_pair(false,
reinterpret_cast<const char*>(t)));
}
}
break;
case XML_ELEMENT_NODE:
_values.push_back(std::make_pair(true, std::string()));
break;
}
}
}
void Element::value(std::string& v) const
{
const VALUES::const_iterator end = _values.end();
for (VALUES::const_iterator i = _values.begin(); i != end; ++i)
v += i->second;
}
void Element::write(const char* f, bool i, const char* t) const
{
// Create a new writer
xmlTextWriterPtr w = xmlNewTextWriterFilename(f, 0);
if (!w)
throw std::runtime_error("unable to create xmlTextWriter");
// Set indentation if desired
if (i)
{
xmlTextWriterSetIndent(w, 1);
xmlTextWriterSetIndentString(w, reinterpret_cast<xmlChar*>(t ? t : "\t"));
}
try
{
// Create document
int r = xmlTextWriterStartDocument(w, "1.0", "iso8859-1", "yes");
if (r < 0)
throw std::runtime_error("unable to start document");
// Add the child elements
write(w);
// Close the writer
xmlFreeTextWriter(w);
}
catch (...)
{
xmlFreeTextWriter(w);
throw;
}
}
void Element::write(xmlTextWriterPtr w) const
{
// Create element
int r = xmlTextWriterStartElement(w, reinterpret_cast<const xmlChar*>("element"));
if (r < 0)
throw std::runtime_error("creation of element 'element' failed");
// Write value if needed
std::string v;
value(v);
if (!v.empty())
{
r = xmlTextWriterWriteString(w, reinterpret_cast<const xmlChar*>(v.c_str()));
if (r < 0)
throw std::runtime_error("failed writing 'element' value");
}
// Add child elements
std::for_each(_childs.begin(), _childs.end(), writer<Child>(w));
// All done!
r = xmlTextWriterEndElement(w);
if (r < 0)
throw std::runtime_error("failed closing 'element' element");
}
//=============================================================================
// Element class Root implementation
Root::Root(const char* f)
{
// Read in document
xmlDocPtr d = xmlParseFile(f);
if (!d)
throw std::runtime_error("file parse failed");
try
{
// Initialize from root node
xmlNodePtr r = xmlDocGetRootElement(d);
if (r)
read(r);
// Clean up
xmlFreeDoc(d);
}
catch (...)
{
xmlFreeDoc(d);
throw;
}
}
Root::Root(xmlNodePtr n)
{
// Initialize from node
read(n);
}
Root& Root::operator=(const Root& r)
{
if (this != &r)
{
_elements = r._elements;
_values = r._values;
}
return *this;
}
void Root::read(xmlNodePtr n)
{
// Make sure node is this element
if (xmlStrcmp(n->name, reinterpret_cast<const xmlChar*>("root")) != 0)
throw std::runtime_error("expecting node 'root'");
// Read the child elements
for (xmlNodePtr s = n->children; s; s = s->next)
{
// Ignore non-element children
if (s->type != XML_ELEMENT_NODE)
continue;
if (xmlStrcmp(s->name, reinterpret_cast<const xmlChar*>("element")) == 0)
{
_elements.push_back(Element(s));
continue;
}
}
// Retreive value
for (xmlNodePtr c = n->children; c; c = c->next)
{
switch (c->type)
{
case XML_TEXT_NODE:
{
xmlChar* t = trim(c->content);
if (*t)
{
_values.push_back(std::make_pair(false,
reinterpret_cast<const char*>(t)));
}
}
break;
case XML_CDATA_SECTION_NODE:
{
xmlChar* t = trim_cdata(c->content);
if (*t)
{
_values.push_back(std::make_pair(false,
reinterpret_cast<const char*>(t)));
}
}
break;
case XML_ELEMENT_NODE:
_values.push_back(std::make_pair(true, std::string()));
break;
}
}
}
void Root::value(std::string& v) const
{
const VALUES::const_iterator end = _values.end();
for (VALUES::const_iterator i = _values.begin(); i != end; ++i)
v += i->second;
}
void Root::write(const char* f, bool i, const char* t) const
{
// Create a new writer
xmlTextWriterPtr w = xmlNewTextWriterFilename(f, 0);
if (!w)
throw std::runtime_error("unable to create xmlTextWriter");
// Set indentation if desired
if (i)
{
xmlTextWriterSetIndent(w, 1);
xmlTextWriterSetIndentString(w, reinterpret_cast<xmlChar*>(t ? t : "\t"));
}
try
{
// Create document
int r = xmlTextWriterStartDocument(w, "1.0", "iso8859-1", "yes");
if (r < 0)
throw std::runtime_error("unable to start document");
// Add the child elements
write(w);
// Close the writer
xmlFreeTextWriter(w);
}
catch (...)
{
xmlFreeTextWriter(w);
throw;
}
}
void Root::write(xmlTextWriterPtr w) const
{
// Create element
int r = xmlTextWriterStartElement(w, reinterpret_cast<const xmlChar*>("root"));
if (r < 0)
throw std::runtime_error("creation of element 'root' failed");
// Write value if needed
std::string v;
value(v);
if (!v.empty())
{
r = xmlTextWriterWriteString(w, reinterpret_cast<const xmlChar*>(v.c_str()));
if (r < 0)
throw std::runtime_error("failed writing 'root' value");
}
// Add child elements
std::for_each(_elements.begin(), _elements.end(), writer<Element>(w));
// All done!
r = xmlTextWriterEndElement(w);
if (r < 0)
throw std::runtime_error("failed closing 'root' element");
}