summaryrefslogtreecommitdiff
path: root/zenxml/bind.h
diff options
context:
space:
mode:
Diffstat (limited to 'zenxml/bind.h')
-rw-r--r--zenxml/bind.h389
1 files changed, 389 insertions, 0 deletions
diff --git a/zenxml/bind.h b/zenxml/bind.h
new file mode 100644
index 00000000..b297b607
--- /dev/null
+++ b/zenxml/bind.h
@@ -0,0 +1,389 @@
+// **************************************************************************
+// * This file is part of the zenXML project. It is distributed under the *
+// * Boost Software License: http://www.boost.org/LICENSE_1_0.txt *
+// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved *
+// **************************************************************************
+
+#ifndef ZEN_XML_BIND_HEADER_9081740816593478258435
+#define ZEN_XML_BIND_HEADER_9081740816593478258435
+
+#include <set>
+#include "cvrt_struc.h"
+#include "parser.h"
+#include "io.h"
+
+namespace zen
+{
+/**
+\file
+\brief Map user data types to XML
+*/
+
+///Load XML document from a file
+/**
+Convenience function that does nothing more than calling loadStream() and parse().
+
+\tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ...
+\param filename Input file name
+\param doc The XML document to load
+\throw XmlFileError
+\throw XmlParsingError
+*/
+template <class String> inline
+void load(const String& filename, XmlDoc& doc) //throw XmlFileError, XmlParsingError
+{
+ std::string stream = loadStream(filename); //throw XmlFileError
+ parse(stream, doc); //throw XmlParsingError
+}
+
+
+///Save XML document to a file
+/**
+Convenience function that does nothing more than calling serialize() and saveStream().
+
+\tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ...
+\param doc The XML document to save
+\param filename Output file name
+\param lineBreak Line break, default: carriage return + new line
+\param indent Indentation, default: four space characters
+\throw XmlFileError
+*/
+template <class String> inline
+void save(const XmlDoc& doc,
+ const String& filename,
+ const std::string& lineBreak = "\r\n",
+ const std::string& indent = " ") //throw XmlFileError
+{
+ std::string stream = serialize(doc, lineBreak, indent); //throw ()
+ saveStream(stream, filename); //throw XmlFileError
+}
+
+
+///Proxy class to conveniently convert user data into XML structure
+class XmlOut
+{
+public:
+ ///Construct an output proxy for an XML document
+ /**
+ \code
+ zen::XmlDoc doc;
+
+ zen::XmlOut out(doc);
+ out["elem1"](value1); //
+ out["elem2"](value2); //write data of variables "value1", "value2", "value3" into XML elements
+ out["elem3"](value3); //
+
+ save(doc, "out.xml"); //throw XmlFileError
+ \endcode
+ Output:
+ \verbatim
+ <?xml version="1.0" encoding="UTF-8"?>
+ <Root>
+ <elem1>1</elem1>
+ <elem2>2.000000</elem2>
+ <elem3>-3</elem3>
+ </Root>
+ \endverbatim
+ */
+ XmlOut(XmlDoc& doc) : ref_(&doc.root()) {}
+ ///Construct an output proxy for a single XML element
+ /**
+ \sa XmlOut(XmlDoc& doc)
+ */
+ XmlOut(XmlElement& element) : ref_(&element) {}
+
+ ///Retrieve a handle to an XML child element for writing
+ /**
+ The child element will be created if it is not yet existing.
+ \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ...
+ \param name The name of the child element
+ */
+ template <class String>
+ XmlOut operator[](const String& name) const
+ {
+ const std::string utf8name = utfCvrtTo<std::string>(name);
+ XmlElement* child = ref_->getChild(utf8name);
+ return child ? *child : ref_->addChild(utf8name);
+ }
+
+ ///Write user data to the underlying XML element
+ /**
+ This conversion requires a specialization of zen::writeText() or zen::writeStruc() for type T.
+ \tparam T User type that is converted into an XML element value.
+ */
+ template <class T>
+ void operator()(const T& value) { writeStruc(value, *ref_); }
+
+ ///Write user data to an XML attribute
+ /**
+ This conversion requires a specialization of zen::writeText() for type T.
+ \code
+ zen::XmlDoc doc;
+
+ zen::XmlOut out(doc);
+ out["elem"].attribute("attr1", value1); //
+ out["elem"].attribute("attr2", value2); //write data of variables "value1", "value2", "value3" into XML attributes
+ out["elem"].attribute("attr3", value3); //
+
+ save(doc, "out.xml"); //throw XmlFileError
+ \endcode
+ Output:
+ \verbatim
+ <?xml version="1.0" encoding="UTF-8"?>
+ <Root>
+ <elem attr1="1" attr2="2.000000" attr3="-3"/>
+ </Root>
+ \endverbatim
+
+ \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ...
+ \tparam T User type that is converted into an XML attribute value.
+ \sa XmlElement::setAttribute()
+ */
+ template <class String, class T>
+ void attribute(const String& name, const T& value) { ref_->setAttribute(name, value); }
+
+ ///Return a reference to the underlying Xml element
+ XmlElement& ref() { return *ref_; }
+
+private:
+ XmlElement* ref_; //always bound!
+};
+
+
+///Proxy class to conveniently convert XML structure to user data
+class XmlIn
+{
+ class ErrorLog;
+ struct ConversionToBool { int dummy; };
+
+public:
+ ///Construct an input proxy for an XML document
+ /**
+ \code
+ zen::XmlDoc doc;
+ ... //load document
+ zen::XmlIn in(doc);
+ in["elem1"](value1); //
+ in["elem2"](value2); //write data of XML elements into variables "value1", "value2", "value3"
+ in["elem3"](value3); //
+ \endcode
+ */
+ XmlIn(const XmlDoc& doc) : refIndex(0), log(std::make_shared<ErrorLog>()) { refList.push_back(&doc.root()); }
+ ///Construct an input proxy for a single XML element, may be nullptr
+ /**
+ \sa XmlIn(const XmlDoc& doc)
+ */
+ XmlIn(const XmlElement* element) : refIndex(0), log(std::make_shared<ErrorLog>()) { refList.push_back(element); }
+ ///Construct an input proxy for a single XML element
+ /**
+ \sa XmlIn(const XmlDoc& doc)
+ */
+ XmlIn(const XmlElement& element) : refIndex(0), log(std::make_shared<ErrorLog>()) { refList.push_back(&element); }
+
+ ///Retrieve a handle to an XML child element for reading
+ /**
+ It is \b not an error if the child element does not exist, but a subsequent conversion to user data will fail.
+ \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ...
+ \param name The name of the child element
+ */
+ template <class String>
+ XmlIn operator[](const String& name) const
+ {
+ std::vector<const XmlElement*> childList;
+
+ if (refIndex < refList.size())
+ {
+ auto iterPair = refList[refIndex]->getChildren(name);
+ std::for_each(iterPair.first, iterPair.second,
+ [&](const XmlElement & child) { childList.push_back(&child); });
+ }
+
+ return XmlIn(childList, childList.empty() ? getChildNameFormatted(name) : std::string(), log);
+ }
+
+ ///Refer to next sibling element with the same name
+ /**
+ <b>Example:</b> Loop over all XML child elements named "Item"
+ \verbatim
+ <?xml version="1.0" encoding="UTF-8"?>
+ <Root>
+ <Item>1</Item>
+ <Item>3</Item>
+ <Item>5</Item>
+ </Root>
+ \endverbatim
+
+ \code
+ zen::XmlIn in(doc);
+ ...
+ for (zen::XmlIn child = in["Item"]; child; child.next())
+ {
+ ...
+ }
+ \endcode
+ */
+ void next() { ++refIndex; }
+
+ ///Read user data from the underlying XML element
+ /**
+ This conversion requires a specialization of zen::readText() or zen::readStruc() for type T.
+ \tparam T User type that receives the data
+ \return "true" if data was read successfully
+ */
+ template <class T>
+ bool operator()(T& value) const
+ {
+ if (refIndex < refList.size())
+ {
+ bool success = readStruc(*refList[refIndex], value);
+ if (!success)
+ log->notifyConversionError(getNameFormatted());
+ return success;
+ }
+ else
+ {
+ log->notifyMissingElement(getNameFormatted());
+ return false;
+ }
+ }
+
+ ///Read user data from an XML attribute
+ /**
+ This conversion requires a specialization of zen::readText() for type T.
+
+ \code
+ zen::XmlDoc doc;
+ ... //load document
+ zen::XmlIn in(doc);
+ in["elem"].attribute("attr1", value1); //
+ in["elem"].attribute("attr2", value2); //write data of XML attributes into variables "value1", "value2", "value3"
+ in["elem"].attribute("attr3", value3); //
+ \endcode
+
+ \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ...
+ \tparam T User type that is converted into an XML attribute value.
+ \sa XmlElement::getAttribute()
+ */
+ template <class String, class T>
+ bool attribute(const String& name, T& value) const
+ {
+ if (refIndex < refList.size())
+ {
+ bool success = refList[refIndex]->getAttribute(name, value);
+ if (!success)
+ log->notifyMissingAttribute(getNameFormatted(), utfCvrtTo<std::string>(name));
+ return success;
+ }
+ else
+ {
+ log->notifyMissingElement(getNameFormatted());
+ return false;
+ }
+ }
+
+ ///Return a pointer to the underlying Xml element, may be nullptr
+ const XmlElement* get() const { return refIndex < refList.size() ? refList[refIndex] : nullptr; }
+
+ ///Test whether underlying XML element exists
+ /**
+ \code
+ XmlIn in(doc);
+ XmlIn child = in["elem1"];
+ if (child)
+ ...
+ \endcode
+ Use member pointer as implicit conversion to bool (C++ Templates - Vandevoorde/Josuttis; chapter 20)
+ */
+ operator int ConversionToBool::* () const { return get() ? &ConversionToBool::dummy : nullptr; }
+
+ ///Notifies errors while mapping the XML to user data
+ /**
+ Error logging is shared by each hiearchy of XmlIn proxy instances that are created from each other. Consequently it doesn't matter which instance you query for errors:
+ \code
+ XmlIn in(doc);
+ XmlIn inItem = in["item1"];
+
+ int value = 0;
+ inItem(value); //assume this conversion failed
+
+ assert(in.errorsOccured() == inItem.errorsOccured());
+ assert(in.getErrorsAs<std::string>() == inItem.getErrorsAs<std::string>());
+ \endcode
+
+ Note that error logging is \b NOT global, but owned by all instances of a hierarchy of XmlIn proxies.
+ Therefore it's safe to use unrelated XmlIn proxies in multiple threads.
+ \n\n
+ However be aware that the chain of connected proxy instances will be broken once you call XmlIn::get() to retrieve the underlying pointer.
+ Errors that occur when working with this pointer are not logged by the original set of related instances.
+ */
+ bool errorsOccured() const { return !log->elementList().empty(); }
+
+ ///Get a list of XML element and attribute names which failed to convert to user data.
+ /**
+ \tparam String Arbitrary string class: e.g. std::string, std::wstring, wxString, MyStringClass, ...
+ \returns A list of XML element and attribute names, empty list if no errors occured.
+ */
+ template <class String>
+ std::vector<String> getErrorsAs() const
+ {
+ std::vector<String> output;
+ const auto& elements = log->elementList();
+ std::transform(elements.begin(), elements.end(), std::back_inserter(output), [](const std::string& str) { return utfCvrtTo<String>(str); });
+ return output;
+ }
+
+private:
+ XmlIn(const std::vector<const XmlElement*>& siblingList, const std::string& elementNameFmt, const std::shared_ptr<ErrorLog>& sharedlog) :
+ refList(siblingList), refIndex(0), formattedName(elementNameFmt), log(sharedlog)
+ { assert((!siblingList.empty() && elementNameFmt.empty()) || (siblingList.empty() && !elementNameFmt.empty())); }
+
+ static std::string getNameFormatted(const XmlElement& elem) //"<Root> <Level1> <Level2>"
+ {
+ return (elem.parent() ? getNameFormatted(*elem.parent()) + " " : std::string()) + "<" + elem.getNameAs<std::string>() + ">";
+ }
+
+ std::string getNameFormatted() const
+ {
+ if (refIndex < refList.size())
+ {
+ assert(formattedName.empty());
+ return getNameFormatted(*refList[refIndex]);
+ }
+ else
+ return formattedName;
+ }
+
+ std::string getChildNameFormatted(const std::string& childName) const
+ {
+ std::string parentName = getNameFormatted();
+ return (parentName.empty() ? std::string() : (parentName + " ")) + "<" + childName + ">";
+ }
+
+ class ErrorLog
+ {
+ public:
+ void notifyConversionError (const std::string& formattedName) { insert(formattedName); }
+ void notifyMissingElement (const std::string& formattedName) { insert(formattedName); }
+ void notifyMissingAttribute(const std::string& formattedName, const std::string& attribName) { insert(formattedName + " @" + attribName); }
+
+ const std::vector<std::string>& elementList() const { return failedElements; }
+
+ private:
+ void insert(const std::string& newVal)
+ {
+ if (usedElements.insert(newVal).second)
+ failedElements.push_back(newVal);
+ }
+
+ std::vector<std::string> failedElements; //unique list of failed elements
+ std::set<std::string> usedElements;
+ };
+
+ std::vector<const XmlElement*> refList; //all sibling elements with same name (all pointers bound!)
+ size_t refIndex; //this sibling's index in refList
+ std::string formattedName; //contains full and formatted element name if (and only if) refList is empty
+ std::shared_ptr<ErrorLog> log; //always bound
+};
+}
+
+#endif //ZEN_XML_BIND_HEADER_9081740816593478258435
bgstack15