diff options
Diffstat (limited to 'zenxml')
-rw-r--r-- | zenxml/bind.h | 389 | ||||
-rw-r--r-- | zenxml/cvrt_struc.h | 218 | ||||
-rw-r--r-- | zenxml/cvrt_text.h | 229 | ||||
-rw-r--r-- | zenxml/dom.h | 324 | ||||
-rw-r--r-- | zenxml/error.h | 19 | ||||
-rw-r--r-- | zenxml/io.h | 124 | ||||
-rw-r--r-- | zenxml/parser.h | 609 | ||||
-rw-r--r-- | zenxml/summary.dox | 680 | ||||
-rw-r--r-- | zenxml/unit_test.cpp | 95 | ||||
-rw-r--r-- | zenxml/xml.h | 15 |
10 files changed, 2702 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 diff --git a/zenxml/cvrt_struc.h b/zenxml/cvrt_struc.h new file mode 100644 index 00000000..5f7f4ad1 --- /dev/null +++ b/zenxml/cvrt_struc.h @@ -0,0 +1,218 @@ +// ************************************************************************** +// * 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_CONVERT_STRUC_HEADER_018727409908342709743 +#define ZEN_XML_CONVERT_STRUC_HEADER_018727409908342709743 + +#include "dom.h" + +namespace zen +{ +/** +\file +\brief Handle conversion of arbitrary types to and from XML elements. +See comments in cvrt_text.h +*/ + +///Convert XML element to structured user data +/** + \param input The input XML element. + \param value Conversion target value. + \return "true" if value was read successfully. +*/ +template <class T> bool readStruc(const XmlElement& input, T& value); +///Convert structured user data into an XML element +/** + \param value The value to be converted. + \param output The output XML element. +*/ +template <class T> void writeStruc(const T& value, XmlElement& output); + + + + + + + + + + + + + + + + + + + + + + + + +//------------------------------ implementation ------------------------------------- +namespace impl_2384343 +{ +ZEN_INIT_DETECT_MEMBER_TYPE(value_type); +ZEN_INIT_DETECT_MEMBER_TYPE(iterator); +ZEN_INIT_DETECT_MEMBER_TYPE(const_iterator); + +ZEN_INIT_DETECT_MEMBER(begin) // +ZEN_INIT_DETECT_MEMBER(end) //we don't know the exact declaration of the member attribute: may be in a base class! +ZEN_INIT_DETECT_MEMBER(insert) // +} + +template <typename T> +struct IsStlContainer : + StaticBool< + impl_2384343::HasMemberType_value_type <T>::value && + impl_2384343::HasMemberType_iterator <T>::value && + impl_2384343::HasMemberType_const_iterator<T>::value && + impl_2384343::HasMember_begin <T>::value && + impl_2384343::HasMember_end <T>::value && + impl_2384343::HasMember_insert <T>::value> {}; + + +namespace impl_2384343 +{ +ZEN_INIT_DETECT_MEMBER_TYPE(first_type); +ZEN_INIT_DETECT_MEMBER_TYPE(second_type); + +ZEN_INIT_DETECT_MEMBER(first) //we don't know the exact declaration of the member attribute: may be in a base class! +ZEN_INIT_DETECT_MEMBER(second) // +} + +template <typename T> +struct IsStlPair : + StaticBool< + impl_2384343::HasMemberType_first_type <T>::value && + impl_2384343::HasMemberType_second_type<T>::value && + impl_2384343::HasMember_first <T>::value && + impl_2384343::HasMember_second <T>::value> {}; + +//###################################################################################### + +//Conversion from arbitrary types to an XML element +enum ValueType +{ + VALUE_TYPE_STL_CONTAINER, + VALUE_TYPE_STL_PAIR, + VALUE_TYPE_OTHER, +}; + +template <class T> +struct GetValueType : StaticEnum<ValueType, + GetTextType<T>::value != TEXT_TYPE_OTHER ? VALUE_TYPE_OTHER : //some string classes are also STL containers, so check this first + IsStlContainer<T>::value ? VALUE_TYPE_STL_CONTAINER : + IsStlPair<T>::value ? VALUE_TYPE_STL_PAIR : + VALUE_TYPE_OTHER> {}; + + +template <class T, ValueType type> +struct ConvertElement; +/* -> expected interface +{ + void writeStruc(const T& value, XmlElement& output) const; + bool readStruc(const XmlElement& input, T& value) const; +}; +*/ + + +//partial specialization: handle conversion for all STL-container types! +template <class T> +struct ConvertElement<T, VALUE_TYPE_STL_CONTAINER> +{ + void writeStruc(const T& value, XmlElement& output) const + { + std::for_each(value.begin(), value.end(), + [&](const typename T::value_type & childVal) + { + XmlElement& newChild = output.addChild("Item"); + zen::writeStruc(childVal, newChild); + }); + } + bool readStruc(const XmlElement& input, T& value) const + { + bool success = true; + value.clear(); + + auto iterPair = input.getChildren("Item"); + for (auto iter = iterPair.first; iter != iterPair.second; ++iter) + { + typename T::value_type childVal; //MSVC 2010 bug: cannot put this into a lambda body + if (zen::readStruc(*iter, childVal)) + value.insert(value.end(), childVal); + else + success = false; + } + return success; + } +}; + + +//partial specialization: handle conversion for std::pair +template <class T> +struct ConvertElement<T, VALUE_TYPE_STL_PAIR> +{ + void writeStruc(const T& value, XmlElement& output) const + { + XmlElement& child1 = output.addChild("one"); //don't use "1st/2nd", this will confuse a few pedantic XML parsers + zen::writeStruc(value.first, child1); + + XmlElement& child2 = output.addChild("two"); + zen::writeStruc(value.second, child2); + } + bool readStruc(const XmlElement& input, T& value) const + { + bool success = true; + const XmlElement* child1 = input.getChild("one"); + if (!child1 || !zen::readStruc(*child1, value.first)) + success = false; + + const XmlElement* child2 = input.getChild("two"); + if (!child2 || !zen::readStruc(*child2, value.second)) + success = false; + + return success; + } +}; + + +//partial specialization: not a pure structured type, try text conversion (thereby respect user specializations of writeText()/readText()) +template <class T> +struct ConvertElement<T, VALUE_TYPE_OTHER> +{ + void writeStruc(const T& value, XmlElement& output) const + { + std::string tmp; + writeText(value, tmp); + output.setValue(tmp); + } + bool readStruc(const XmlElement& input, T& value) const + { + std::string rawStr; + input.getValue(rawStr); + return readText(rawStr, value); + } +}; + + +template <class T> inline +void writeStruc(const T& value, XmlElement& output) +{ + ConvertElement<T, GetValueType<T>::value>().writeStruc(value, output); +} + + +template <class T> inline +bool readStruc(const XmlElement& input, T& value) +{ + return ConvertElement<T, GetValueType<T>::value>().readStruc(input, value); +} +} + +#endif //ZEN_XML_CONVERT_STRUC_HEADER_018727409908342709743 diff --git a/zenxml/cvrt_text.h b/zenxml/cvrt_text.h new file mode 100644 index 00000000..a70c0813 --- /dev/null +++ b/zenxml/cvrt_text.h @@ -0,0 +1,229 @@ +// ************************************************************************** +// * 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_CONVERT_TEXT_HEADER_018727339083427097434 +#define ZEN_XML_CONVERT_TEXT_HEADER_018727339083427097434 + +#include <zen/utf.h> +#include <zen/string_tools.h> + +namespace zen +{ +/** +\file +\brief Handle conversion of string-convertible types to and from std::string. + +It is \b not required to call these functions directly. They are implicitly used by zen::XmlElement::getValue(), +zen::XmlElement::setValue(), zen::XmlElement::getAttribute() and zen::XmlElement::setAttribute(). +\n\n +Conversions for the following user types are supported by default: + - strings - std::string, std::wstring, char*, wchar_t*, char, wchar_t, ect..., all STL-compatible-string-classes + - numbers - int, double, float, bool, long, ect..., all built-in numbers + - STL containers - std::map, std::set, std::vector, std::list, ect..., all STL-compatible-containers + - std::pair + +You can add support for additional types via template specialization. \n\n +Specialize zen::readStruc() and zen::writeStruc() to enable conversion from structured user types to XML elements. +Specialize zen::readText() and zen::writeText() to enable conversion from string-convertible user types to std::string. +Prefer latter if possible since it does not only enable conversions from XML elements to user data, but also from and to XML attributes. +\n\n +<b> Example: </b> type "bool" +\code +namespace zen +{ +template <> inline +void writeText(const bool& value, std::string& output) +{ + output = value ? "true" : "false"; +} + +template <> inline +bool readText(const std::string& input, bool& value) +{ + std::string tmp = input; + zen::trim(tmp); + if (tmp == "true") + value = true; + else if (tmp == "false") + value = false; + else + return false; + return true; +} +} +\endcode +*/ + + +///Convert text to user data - used by XML elements and attributes +/** + \param input Input text. + \param value Conversion target value. + \return "true" if value was read successfully. +*/ +template <class T> bool readText(const std::string& input, T& value); +///Convert user data into text - used by XML elements and attributes +/** + \param value The value to be converted. + \param output Output text. +*/ +template <class T> void writeText(const T& value, std::string& output); + + +/* Different classes of data types: + +--------------------------- +| structured | readStruc/writeStruc - e.g. string-convertible types, STL containers, std::pair, structured user types +| ---------------------- | +| | string-convertible | | readText/writeText - e.g. string-like types, all built-in arithmetic numbers, bool +| | --------------- | | +| | | string-like | | | utfCvrtTo - e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ... +| | --------------- | | +| ---------------------- | +--------------------------- +*/ + + + + + + + + + + + + + + + + + + + + + + +//------------------------------ implementation ------------------------------------- + +//Conversion from arbitrary types to text (for use with XML elements and attributes) +enum TextType +{ + TEXT_TYPE_BOOL, + TEXT_TYPE_NUMBER, + TEXT_TYPE_STRING, + TEXT_TYPE_OTHER, +}; + +template <class T> +struct GetTextType : StaticEnum<TextType, + IsSameType<T, bool>::value ? TEXT_TYPE_BOOL : + IsStringLike<T>::value ? TEXT_TYPE_STRING : //string before number to correctly handle char/wchar_t -> this was an issue with Loki only! + IsArithmetic<T>::value ? TEXT_TYPE_NUMBER : // + TEXT_TYPE_OTHER> {}; + +//###################################################################################### + +template <class T, TextType type> +struct ConvertText; +/* -> expected interface +{ + void writeText(const T& value, std::string& output) const; + bool readText(const std::string& input, T& value) const; +}; +*/ + +//partial specialization: type bool +template <class T> +struct ConvertText<T, TEXT_TYPE_BOOL> +{ + void writeText(bool value, std::string& output) const + { + output = value ? "true" : "false"; + } + bool readText(const std::string& input, bool& value) const + { + std::string tmp = input; + zen::trim(tmp); + if (tmp == "true") + value = true; + else if (tmp == "false") + value = false; + else + return false; + return true; + } +}; + +//partial specialization: handle conversion for all built-in arithmetic types! +template <class T> +struct ConvertText<T, TEXT_TYPE_NUMBER> +{ + void writeText(const T& value, std::string& output) const + { + output = numberTo<std::string>(value); + } + bool readText(const std::string& input, T& value) const + { + value = stringTo<T>(input); + return true; + } +}; + +//partial specialization: handle conversion for all string-like types! +template <class T> +struct ConvertText<T, TEXT_TYPE_STRING> +{ + void writeText(const T& value, std::string& output) const + { + output = utfCvrtTo<std::string>(value); + } + bool readText(const std::string& input, T& value) const + { + value = utfCvrtTo<T>(input); + return true; + } +}; + + +//partial specialization: unknown type +template <class T> +struct ConvertText<T, TEXT_TYPE_OTHER> +{ + //########################################################################################################################################### + assert_static(sizeof(T) == -1); + /* + ATTENTION: The data type T is yet unknown to the zenXML framework! + + Please provide a specialization for T of the following two functions in order to handle conversions to XML elements and attributes + + template <> void zen::writeText(const T& value, std::string& output) + template <> bool zen::readText(const std::string& input, T& value) + + If T is structured and cannot be converted to a text representation specialize these two functions to allow at least for conversions to XML elements: + + template <> void zen::writeStruc(const T& value, XmlElement& output) + template <> bool zen::readStruc(const XmlElement& input, T& value) + */ + //########################################################################################################################################### +}; + + +template <class T> inline +void writeText(const T& value, std::string& output) +{ + ConvertText<T, GetTextType<T>::value>().writeText(value, output); +} + + +template <class T> inline +bool readText(const std::string& input, T& value) +{ + return ConvertText<T, GetTextType<T>::value>().readText(input, value); +} +} + +#endif //ZEN_XML_CONVERT_TEXT_HEADER_018727339083427097434 diff --git a/zenxml/dom.h b/zenxml/dom.h new file mode 100644 index 00000000..fbbd6fb0 --- /dev/null +++ b/zenxml/dom.h @@ -0,0 +1,324 @@ +// ************************************************************************** +// * 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_DOM_HEADER_82085720723894567204564256 +#define ZEN_XML_DOM_HEADER_82085720723894567204564256 + +#include <string> +#include <vector> +#include <memory> +#include <map> +#include "cvrt_text.h" //"readText/writeText" + +namespace zen +{ +class XmlDoc; + +/// An XML element +class XmlElement +{ + struct PrivateConstruction {}; +public: + //Construct an empty XML element + //This constructor should be private, however std::make_shared() requires public access + //Therefore at least prevent users from calling it via private dummy type PrivateConstruction + template <class String> + XmlElement(const String& name, XmlElement* parentElement, PrivateConstruction) : name_(utfCvrtTo<std::string>(name)), parent_(parentElement) {} + + ///Retrieve the name of this XML element. + /** + \tparam String Arbitrary string class: e.g. std::string, std::wstring, wxString, MyStringClass, ... + \returns Name of the XML element + */ + template <class String> + String getNameAs() const { return utfCvrtTo<String>(name_); } + + ///Get the value of this element as a user type. + /** + \tparam T Arbitrary user data type: e.g. any string class, all built-in arithmetic numbers, STL container, ... + \returns "true" if Xml element was successfully converted to value, cannot fail for string-like types + */ + template <class T> + bool getValue(T& value) const { return readStruc(*this, value); } + + ///Set the value of this element. + /** + \tparam T Arbitrary user data type: e.g. any string-like type, all built-in arithmetic numbers, STL container, ... + */ + template <class T> + void setValue(const T& value) { writeStruc(value, *this); } + + ///Retrieve an attribute by name. + /** + \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ... + \tparam T String-convertible user data type: e.g. any string class, all built-in arithmetic numbers + \param name The name of the attribute to retrieve. + \param value The value of the attribute converted to T. + \return "true" if value was retrieved successfully. + */ + template <class String, class T> + bool getAttribute(const String& name, T& value) const + { + auto it = attributes.find(utfCvrtTo<std::string>(name)); + return it == attributes.end() ? false : readText(it->second, value); + } + + ///Create or update an XML attribute. + /** + \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ... + \tparam T String-convertible user data type: e.g. any string-like type, all built-in arithmetic numbers + \param name The name of the attribute to create or update. + \param value The value to set. + */ + template <class String, class T> + void setAttribute(const String& name, const T& value) + { + std::string attrValue; + writeText(value, attrValue); + attributes[utfCvrtTo<std::string>(name)] = attrValue; + } //create or update + + ///Remove the attribute with the given name. + /** + \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ... + */ + template <class String> + void removeAttribute(const String& name) { attributes.erase(utfCvrtTo<std::string>(name)); } + + ///Create a new child element and return a reference to it. + /** + \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 to be created. + */ + template <class String> + XmlElement& addChild(const String& name) + { + std::string utf8Name = utfCvrtTo<std::string>(name); + auto newElement = std::make_shared<XmlElement>(utf8Name, this, PrivateConstruction()); + //std::shared_ptr<XmlElement> newElement(new XmlElement(utf8Name, this)); + childElements.push_back(newElement); + childElementsSorted.insert(std::make_pair(utf8Name, newElement)); + return *newElement; + } + + ///Retrieve a child element with the given name. + /** + \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 to be retrieved. + \return A pointer to the child element or nullptr if none was found + */ + template <class String> + const XmlElement* getChild(const String& name) const + { + auto it = childElementsSorted.find(utfCvrtTo<std::string>(name)); + return it == childElementsSorted.end() ? nullptr : &*(it->second); + } + + ///\sa getChild + template <class String> + XmlElement* getChild(const String& name) + { + return const_cast<XmlElement*>(static_cast<const XmlElement*>(this)->getChild(name)); + } + + template < class IterTy, //underlying iterator type + class T, //target object type + class AccessPolicy > //access policy: see AccessPtrMap + class PtrIter : public std::iterator<std::input_iterator_tag, T>, private AccessPolicy //get rid of shared_ptr indirection + { + public: + PtrIter(IterTy it) : it_(it) {} + PtrIter(const PtrIter& other) : it_(other.it_) {} + PtrIter& operator++() { ++it_; return *this; } + PtrIter operator++(int) { PtrIter tmp(*this); operator++(); return tmp; } + inline friend bool operator==(const PtrIter& lhs, const PtrIter& rhs) { return lhs.it_ == rhs.it_; } + inline friend bool operator!=(const PtrIter& lhs, const PtrIter& rhs) { return !(lhs == rhs); } + T& operator* () { return AccessPolicy::template objectRef<T>(it_); } + T* operator->() { return &AccessPolicy::template objectRef<T>(it_); } + private: + IterTy it_; + }; + + struct AccessPtrMap + { + template <class T, class IterTy> + T& objectRef(const IterTy& it) { return *(it->second); } + }; + + typedef PtrIter<std::multimap<std::string, std::shared_ptr<XmlElement>>::iterator, XmlElement, AccessPtrMap> ChildIter2; + typedef PtrIter<std::multimap<std::string, std::shared_ptr<XmlElement>>::const_iterator, const XmlElement, AccessPtrMap> ChildIterConst2; + + ///Access all child elements with the given name via STL iterators. + /** + \code + auto iterPair = elem.getChildren("Item"); + std::for_each(iterPair.first, iterPair.second, + [](const XmlElement& child) { ... }); + \endcode + \param name The name of the child elements to be retrieved. + \return A pair of STL begin/end iterators to access all child elements sequentially. + */ + template <class String> + std::pair<ChildIterConst2, ChildIterConst2> getChildren(const String& name) const { return childElementsSorted.equal_range(utfCvrtTo<std::string>(name)); } + + ///\sa getChildren + template <class String> + std::pair<ChildIter2, ChildIter2> getChildren(const String& name) { return childElementsSorted.equal_range(utfCvrtTo<std::string>(name)); } + + struct AccessPtrVec + { + template <class T, class IterTy> + T& objectRef(const IterTy& it) { return **it; } + }; + + typedef PtrIter<std::vector<std::shared_ptr<XmlElement>>::iterator, XmlElement, AccessPtrVec> ChildIter; + typedef PtrIter<std::vector<std::shared_ptr<XmlElement>>::const_iterator, const XmlElement, AccessPtrVec> ChildIterConst; + + ///Access all child elements sequentially via STL iterators. + /** + \code + auto iterPair = elem.getChildren(); + std::for_each(iterPair.first, iterPair.second, + [](const XmlElement& child) { ... }); + \endcode + \return A pair of STL begin/end iterators to access all child elements sequentially. + */ + std::pair<ChildIterConst, ChildIterConst> getChildren() const { return std::make_pair(childElements.begin(), childElements.end()); } + + ///\sa getChildren + std::pair<ChildIter, ChildIter> getChildren() { return std::make_pair(childElements.begin(), childElements.end()); } + + ///Get parent XML element, may be nullptr for root element + XmlElement* parent() { return parent_; }; + ///Get parent XML element, may be nullptr for root element + const XmlElement* parent() const { return parent_; }; + + + typedef std::map<std::string, std::string>::const_iterator AttrIter; + + /* -> disabled documentation extraction + \brief Get all attributes associated with the element. + \code + auto iterPair = elem.getAttributes(); + for (auto it = iterPair.first; it != iterPair.second; ++it) + std::cout << "name: " << it->first << " value: " << it->second << "\n"; + \endcode + \return A pair of STL begin/end iterators to access all attributes sequentially as a list of name/value pairs of std::string. + */ + std::pair<AttrIter, AttrIter> getAttributes() const { return std::make_pair(attributes.begin(), attributes.end()); } + + //Transactionally swap two elements. -> disabled documentation extraction + void swap(XmlElement& other) + { + name_ .swap(other.name_); + value_ .swap(other.value_); + attributes.swap(other.attributes); + childElements.swap(other.childElements); + childElementsSorted.swap(other.childElementsSorted); + //std::swap(parent_, other.parent_); -> parent is physical location; update children's parent reference instead: + std::for_each( childElements.begin(), childElements.end(), [&](const std::shared_ptr<XmlElement>& child) { child->parent_ = this; }); + std::for_each(other.childElements.begin(), other.childElements.end(), [&](const std::shared_ptr<XmlElement>& child) { child->parent_ = &other; }); + } + +private: + friend class XmlDoc; + + XmlElement(const XmlElement&); //not implemented + XmlElement& operator=(const XmlElement&); // + + std::string name_; + std::string value_; + std::map<std::string, std::string> attributes; + std::vector<std::shared_ptr<XmlElement>> childElements; //all child elements in order of creation + std::multimap<std::string, std::shared_ptr<XmlElement>> childElementsSorted; //alternate key: sorted by element name + XmlElement* parent_; +}; + + +//XmlElement::setValue<T>() calls zen::writeStruc() which calls XmlElement::setValue() ... => these two specializations end the circle +template <> inline +void XmlElement::setValue(const std::string& value) { value_ = value; } + +template <> inline +bool XmlElement::getValue(std::string& value) const { value = value_; return true; } + + +///The complete XML document +class XmlDoc +{ +public: + ///Default constructor setting up an empty XML document with a standard declaration: <?xml version="1.0" encoding="UTF-8" ?> + XmlDoc() : version_("1.0"), encoding_("UTF-8"), rootElement("Root", nullptr, XmlElement::PrivateConstruction()) {} + + //Setup an empty XML document + /** + \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ... + \param rootName The name of the XML document's root element + */ + template <class String> + XmlDoc(String rootName) : version_("1.0"), encoding_("UTF-8"), rootElement(rootName, nullptr, XmlElement::PrivateConstruction()) {} + + ///Get a const reference to the document's root element. + const XmlElement& root() const { return rootElement; } + ///Get a reference to the document's root element. + XmlElement& root() { return rootElement; } + + ///Get the version used in the XML declaration. + /** + \tparam String Arbitrary string class: e.g. std::string, std::wstring, wxString, MyStringClass, ... + */ + template <class String> + String getVersionAs() const { return utfCvrtTo<String>(version_); } + + ///Set the version used in the XML declaration. + /** + \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ... + */ + template <class String> + void setVersion(const String& version) { version_ = utfCvrtTo<std::string>(version); } + + ///Get the encoding used in the XML declaration. + /** + \tparam String Arbitrary string class: e.g. std::string, std::wstring, wxString, MyStringClass, ... + */ + template <class String> + String getEncodingAs() const { return utfCvrtTo<String>(encoding_); } + + ///Set the encoding used in the XML declaration. + /** + \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ... + */ + template <class String> + void setEncoding(const String& encoding) { encoding_ = utfCvrtTo<std::string>(encoding); } + + ///Get the standalone string used in the XML declaration. + /** + \tparam String Arbitrary string class: e.g. std::string, std::wstring, wxString, MyStringClass, ... + */ + template <class String> + String getStandaloneAs() const { return utfCvrtTo<String>(standalone_); } + + ///Set the standalone string used in the XML declaration. + /** + \tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ... + */ + template <class String> + void setStandalone(const String& standalone) { standalone_ = utfCvrtTo<std::string>(standalone); } + +private: + XmlDoc(const XmlDoc&); //not implemented, thanks to XmlElement::parent_ + XmlDoc& operator=(const XmlDoc&); // + + std::string version_; + std::string encoding_; + std::string standalone_; + + XmlElement rootElement; +}; + +} + +#endif //ZEN_XML_DOM_HEADER_82085720723894567204564256 diff --git a/zenxml/error.h b/zenxml/error.h new file mode 100644 index 00000000..a90dd35a --- /dev/null +++ b/zenxml/error.h @@ -0,0 +1,19 @@ +// ************************************************************************** +// * 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_ERROR_HEADER_018734618433021489473214873214 +#define ZEN_XML_ERROR_HEADER_018734618433021489473214873214 + +namespace zen +{ +///Exception base class for zenXML +struct XmlError +{ + virtual ~XmlError() {} +}; +} + +#endif //ZEN_XML_ERROR_HEADER_018734618433021489473214873214 diff --git a/zenxml/io.h b/zenxml/io.h new file mode 100644 index 00000000..4286ae6c --- /dev/null +++ b/zenxml/io.h @@ -0,0 +1,124 @@ +// ************************************************************************** +// * 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_IO_HEADER_8917640501480763248343343 +#define ZEN_XML_IO_HEADER_8917640501480763248343343 + +#include <cstdio> +#include <cerrno> +#include <zen/scope_guard.h> +#include <zen/utf.h> +#include "error.h" + +namespace zen +{ +/** +\file +\brief Save and load byte streams from files +*/ + +#if !defined(ZEN_PLATFORM_WINDOWS) && !defined(ZEN_PLATFORM_OTHER) +#error Please specify your platform: #define ZEN_PLATFORM_WINDOWS or ZEN_PLATFORM_OTHER +#endif + +///Exception thrown due to failed file I/O +struct XmlFileError : public XmlError +{ + typedef int ErrorCode; + + explicit XmlFileError(ErrorCode ec) : lastError(ec) {} + ///Native error code: errno + ErrorCode lastError; +}; + + +#ifdef ZEN_PLATFORM_WINDOWS +namespace implemenation //sad but true +{ +template <class String> inline +FILE* fopen(const String& filename, const wchar_t* mode) +{ +#ifdef _MSC_VER + FILE* handle = nullptr; + errno_t rv = ::_wfopen_s(&handle, utfCvrtTo<std::wstring>(filename).c_str(), mode); //more secure? + (void)rv; + return handle; +#else + return ::_wfopen(utfCvrtTo<std::wstring>(filename).c_str(), mode); +#endif +} +} +#endif + + +///Save byte stream to a file +/** +\tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ... +\param stream Input byte stream +\param filename Output file name +\throw XmlFileError +*/ +template <class String> +void saveStream(const std::string& stream, const String& filename) //throw XmlFileError +{ +#ifdef ZEN_PLATFORM_WINDOWS + FILE* handle = implemenation::fopen(utfCvrtTo<std::wstring>(filename).c_str(), L"wb"); +#else + FILE* handle = ::fopen(utfCvrtTo<std::string>(filename).c_str(), "w"); +#endif + if (handle == nullptr) + throw XmlFileError(errno); + ZEN_ON_SCOPE_EXIT(::fclose(handle)); + + const size_t bytesWritten = ::fwrite(stream.c_str(), 1, stream.size(), handle); + if (::ferror(handle) != 0) + throw XmlFileError(errno); + + (void)bytesWritten; + assert(bytesWritten == stream.size()); +} + + +///Load byte stream from a file +/** +\tparam String Arbitrary string-like type: e.g. std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ... +\param filename Input file name +\return Output byte stream +\throw XmlFileError +*/ +template <class String> +std::string loadStream(const String& filename) //throw XmlFileError +{ +#ifdef ZEN_PLATFORM_WINDOWS + FILE* handle = implemenation::fopen(utfCvrtTo<std::wstring>(filename).c_str(), L"rb"); +#else + FILE* handle = ::fopen(utfCvrtTo<std::string>(filename).c_str(), "r"); +#endif + if (handle == nullptr) + throw XmlFileError(errno); + ZEN_ON_SCOPE_EXIT(::fclose(handle)); + + std::string stream; + const size_t blockSize = 64 * 1024; + do + { + stream.resize(stream.size() + blockSize); //let's pray std::string implements exponential growth! + + const size_t bytesRead = ::fread(&*(stream.begin() + stream.size() - blockSize), 1, blockSize, handle); + if (::ferror(handle)) + throw XmlFileError(errno); + if (bytesRead > blockSize) + throw XmlFileError(0); + if (bytesRead < blockSize) + stream.resize(stream.size() - (blockSize - bytesRead)); //caveat: unsigned arithmetics + } + while (!::feof(handle)); + + return stream; +} +} + +#endif //ZEN_XML_IO_HEADER_8917640501480763248343343 diff --git a/zenxml/parser.h b/zenxml/parser.h new file mode 100644 index 00000000..823cd793 --- /dev/null +++ b/zenxml/parser.h @@ -0,0 +1,609 @@ +// ************************************************************************** +// * 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_PARSER_HEADER_81248670213764583021432 +#define ZEN_XML_PARSER_HEADER_81248670213764583021432 + +#include <cstdio> +#include <cstddef> //ptrdiff_t; req. on Linux +#include <zen/string_traits.h> +#include "dom.h" +#include "error.h" + +namespace zen +{ +/** +\file +\brief Convert an XML document object model (class XmlDoc) to and from a byte stream representation. +*/ + +///Save XML document as a byte stream +/** +\param doc Input XML document +\param lineBreak Line break, default: carriage return + new line +\param indent Indentation, default: four space characters +\return Output byte stream +*/ +std::string serialize(const XmlDoc& doc, + const std::string& lineBreak = "\r\n", + const std::string& indent = " "); //throw () + +///Exception thrown due to an XML parsing error +struct XmlParsingError : public XmlError +{ + XmlParsingError(size_t rowNo, size_t colNo) : row(rowNo), col(colNo) {} + ///Input file row where the parsing error occured + size_t row; //beginning with 0 + ///Input file column where the parsing error occured + size_t col; // +}; + + +///Load XML document from a byte stream +/** +\param stream Input byte stream +\param doc Output XML document +\throw XmlParsingError +*/ +void parse(const std::string& stream, XmlDoc& doc); //throw XmlParsingError + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +//---------------------------- implementation ---------------------------- +//see: http://www.w3.org/TR/xml/ + +namespace implementation +{ +inline +std::pair<char, char> hexify(unsigned char c) +{ + auto hexifyDigit = [](int num) -> char //input [0, 15], output 0-9, A-F + { + assert(0 <= num && num <= 15); //guaranteed by design below! + return static_cast<char>(num <= 9 ? //no signed/unsigned char problem here! + '0' + num : + 'A' + (num - 10)); + }; + return std::make_pair(hexifyDigit(c / 16), hexifyDigit(c % 16)); +} + + +inline +char unhexify(char high, char low) +{ + auto unhexifyDigit = [](char hex) -> int //input 0-9, a-f, A-F; output range: [0, 15] + { + if ('0' <= hex && hex <= '9') //no signed/unsigned char problem here! + return hex - '0'; + else if ('A' <= hex && hex <= 'F') + return (hex - 'A') + 10; + else if ('a' <= hex && hex <= 'f') + return (hex - 'a') + 10; + assert(false); + return 0; + }; + return static_cast<unsigned char>(16 * unhexifyDigit(high) + unhexifyDigit(low)); //[!] convert to unsigned char first, then to char (which may be signed) +}; + + +template <class Predicate> inline +std::string normalize(const std::string& str, Predicate pred) //pred: unary function taking a char, return true if value shall be encoded as hex +{ + std::string output; + std::for_each(str.begin(), str.end(), + [&](char c) + { + if (c == '&') // + output += "&"; + else if (c == '<') //normalization mandatory: http://www.w3.org/TR/xml/#syntax + output += "<"; + else if (c == '>') // + output += ">"; + else if (pred(c)) + { + if (c == '\'') + output += "'"; + else if (c == '\"') + output += """; + else + { + output += "&#x"; + const auto hexDigits = hexify(c); //hexify beats "printNumber<std::string>("&#x%02X;", c)" by a nice factor of 3! + output += hexDigits.first; + output += hexDigits.second; + output += ';'; + } + } + else + output += c; + }); + return output; +} + +inline +std::string normalizeName(const std::string& str) +{ + return normalize(str, [](char c) { return isWhiteSpace(c) || c == '=' || c == '/' || c == '\'' || c == '\"'; }); +} + +inline +std::string normalizeElementValue(const std::string& str) +{ + return normalize(str, [](char c) { return static_cast<unsigned char>(c) < 32; }); +} + +inline +std::string normalizeAttribValue(const std::string& str) +{ + return normalize(str, [](char c) { return static_cast<unsigned char>(c) < 32 || c == '\'' || c == '\"'; }); +} + + +namespace +{ +std::string denormalize(const std::string& str) +{ + std::string output; + for (auto it = str.begin(); it != str.end(); ++it) + { + const char c = *it; + + if (c == '&') + { + auto checkEntity = [&](const char* placeholder, char realVal) -> bool + { + size_t strLen = strLength(placeholder); + + if (str.end() - it >= static_cast<int>(strLen) && std::equal(it, it + strLen, placeholder)) + { + output += realVal; + it += strLen - 1; + return true; + } + return false; + }; + + if (checkEntity("&", '&')) + continue; + if (checkEntity("<", '<')) + continue; + if (checkEntity(">", '>')) + continue; + if (checkEntity("'", '\'')) + continue; + if (checkEntity(""", '\"')) + continue; + + if (str.end() - it >= 6 && + it[1] == '#' && + it[2] == 'x' && + it[5] == ';') + { + output += unhexify(it[3], it[4]); + it += 5; + continue; + //unhexify beats "::sscanf(&it[3], "%02X", &tmp)" by a factor of 3000 for ~250000 calls!!! + } + + output += c; //unexpected char! + } + else if (c == '\r') //map all end-of-line characters to \n http://www.w3.org/TR/xml/#sec-line-ends + { + auto itNext = it + 1; + if (itNext != str.end() && *itNext == '\n') + ++it; + output += '\n'; + } + else + output += c; + }; + return output; +} + + +void serialize(const XmlElement& element, std::string& stream, + const std::string& lineBreak, + const std::string& indent, + size_t indentLevel) +{ + const std::string& nameFmt = normalizeName(element.getNameAs<std::string>()); + + for (size_t i = 0; i < indentLevel; ++i) + stream += indent; + + stream += '<' + nameFmt; + + auto attr = element.getAttributes(); + for (auto it = attr.first; it != attr.second; ++it) + stream += ' ' + normalizeName(it->first) + "=\"" + normalizeAttribValue(it->second) + "\""; + + //no support for mixed-mode content + auto iterPair = element.getChildren(); + if (iterPair.first != iterPair.second) //structured element + { + stream += '>' + lineBreak; + + std::for_each(iterPair.first, iterPair.second, + [&](const XmlElement & el) { serialize(el, stream, lineBreak, indent, indentLevel + 1); }); + + for (size_t i = 0; i < indentLevel; ++i) + stream += indent; + stream += "</" + nameFmt + '>' + lineBreak; + } + else + { + std::string value; + element.getValue(value); + + if (!value.empty()) //value element + stream += '>' + normalizeElementValue(value) + "</" + nameFmt + '>' + lineBreak; + else //empty element + stream += "/>" + lineBreak; + } +} + +std::string serialize(const XmlDoc& doc, + const std::string& lineBreak, + const std::string& indent) +{ + std::string version = doc.getVersionAs<std::string>(); + if (!version.empty()) + version = " version=\"" + normalizeAttribValue(version) + "\""; + + std::string encoding = doc.getEncodingAs<std::string>(); + if (!encoding.empty()) + encoding = " encoding=\"" + normalizeAttribValue(encoding) + "\""; + + std::string standalone = doc.getStandaloneAs<std::string>(); + if (!standalone.empty()) + standalone = " standalone=\"" + normalizeAttribValue(standalone) + "\""; + + std::string output = "<?xml" + version + encoding + standalone + "?>" + lineBreak; + serialize(doc.root(), output, lineBreak, indent, 0); + return output; +} +} +} + +inline +std::string serialize(const XmlDoc& doc, + const std::string& lineBreak, + const std::string& indent) { return implementation::serialize(doc, lineBreak, indent); } + +/* +Grammar for XML parser +------------------------------- +document-expression: + <?xml version="1.0" encoding="UTF-8" standalone="yes"?> + element-expression: + +element-expression: + <string attributes-expression/> + <string attributes-expression> pm-expression </string> + +element-list-expression: + <empty> + element-expression element-list-expression + +attributes-expression: + <empty> + string="string" attributes-expression + +pm-expression: + string + element-list-expression +*/ + +namespace implementation +{ +struct Token +{ + enum Type + { + TK_LESS, + TK_GREATER, + TK_LESS_SLASH, + TK_SLASH_GREATER, + TK_EQUAL, + TK_QUOTE, + TK_DECL_BEGIN, + TK_DECL_END, + TK_NAME, + TK_END + }; + + Token(Type t) : type(t) {} + Token(const std::string& txt) : type(TK_NAME), name(txt) {} + + Type type; + std::string name; //filled if type == TK_NAME +}; + +class Scanner +{ +public: + Scanner(const std::string& stream) : stream_(stream), pos(stream_.begin()) + { + if (zen::startsWith(stream_, BYTE_ORDER_MARK_UTF8)) + pos += strLength(BYTE_ORDER_MARK_UTF8); + + tokens.push_back(std::make_pair("<?xml", Token::TK_DECL_BEGIN)); + tokens.push_back(std::make_pair("?>", Token::TK_DECL_END)); + tokens.push_back(std::make_pair("</", Token::TK_LESS_SLASH)); + tokens.push_back(std::make_pair("/>", Token::TK_SLASH_GREATER)); + tokens.push_back(std::make_pair("<" , Token::TK_LESS)); //evaluate after TK_DECL_BEGIN! + tokens.push_back(std::make_pair(">" , Token::TK_GREATER)); + tokens.push_back(std::make_pair("=" , Token::TK_EQUAL)); + tokens.push_back(std::make_pair("\"", Token::TK_QUOTE)); + tokens.push_back(std::make_pair("\'", Token::TK_QUOTE)); + } + + Token nextToken() //throw XmlParsingError + { + //skip whitespace + pos = std::find_if(pos, stream_.end(), [](char c) { return !zen::isWhiteSpace(c); }); + + if (pos == stream_.end()) + return Token::TK_END; + + for (auto it = tokens.begin(); it != tokens.end(); ++it) + if (startsWith(pos, it->first)) + { + pos += it->first.size(); + return it->second; + } + + auto nameEnd = std::find_if(pos, stream_.end(), [](char c) + { + return c == '<' || + c == '>' || + c == '=' || + c == '/' || + c == '\'' || + c == '\"' || + zen::isWhiteSpace(c); + }); + + if (nameEnd != pos) + { + std::string name(&*pos, nameEnd - pos); + pos = nameEnd; + return implementation::denormalize(name); + } + + //unknown token + throw XmlParsingError(posRow(), posCol()); + } + + std::string extractElementValue() + { + auto it = std::find_if(pos, stream_.end(), [](char c) + { + return c == '<' || + c == '>'; + }); + std::string output(pos, it); + pos = it; + return implementation::denormalize(output); + } + + std::string extractAttributeValue() + { + auto it = std::find_if(pos, stream_.end(), [](char c) + { + return c == '<' || + c == '>' || + c == '\'' || + c == '\"'; + }); + std::string output(pos, it); + pos = it; + return implementation::denormalize(output); + } + + size_t posRow() const //current row beginning with 0 + { + const size_t crSum = std::count(stream_.begin(), pos, '\r'); //carriage returns + const size_t nlSum = std::count(stream_.begin(), pos, '\n'); //new lines + assert(crSum == 0 || nlSum == 0 || crSum == nlSum); + return std::max(crSum, nlSum); //be compatible with Linux/Mac/Win + } + + size_t posCol() const //current col beginning with 0 + { + //seek beginning of line + for (auto it = pos; it != stream_.begin(); ) + { + --it; + if (*it == '\r' || *it == '\n') + return pos - it - 1; + } + return pos - stream_.begin(); + } + +private: + Scanner(const Scanner&); + Scanner& operator=(const Scanner&); + + bool startsWith(std::string::const_iterator it, const std::string& prefix) const + { + if (stream_.end() - it < static_cast<ptrdiff_t>(prefix.size())) + return false; + return std::equal(prefix.begin(), prefix.end(), it); + } + + typedef std::vector<std::pair<std::string, Token::Type> > TokenList; + TokenList tokens; + + const std::string stream_; + std::string::const_iterator pos; +}; + + +class XmlParser +{ +public: + XmlParser(const std::string& stream) : + scn(stream), + tk(scn.nextToken()) {} + + void parse(XmlDoc& doc) //throw XmlParsingError + { + //declaration (optional) + if (token().type == Token::TK_DECL_BEGIN) + { + nextToken(); + + while (token().type == Token::TK_NAME) + { + std::string attribName = token().name; + nextToken(); + + consumeToken(Token::TK_EQUAL); + expectToken(Token::TK_QUOTE); + std::string attribValue = scn.extractAttributeValue(); + nextToken(); + + consumeToken(Token::TK_QUOTE); + + if (attribName == "version") + doc.setVersion(attribValue); + else if (attribName == "encoding") + doc.setEncoding(attribValue); + else if (attribName == "standalone") + doc.setStandalone(attribValue); + } + consumeToken(Token::TK_DECL_END); + } + + XmlDoc dummy; + XmlElement& elemTmp = dummy.root(); + parseChildElements(elemTmp); + + auto iterPair = elemTmp.getChildren(); + if (iterPair.first != iterPair.second) + doc.root().swap(*iterPair.first); + + expectToken(Token::TK_END); + }; + +private: + XmlParser(const XmlParser&); + XmlParser& operator=(const XmlParser&); + + void parseChildElements(XmlElement& parent) + { + while (token().type == Token::TK_LESS) + { + nextToken(); + + expectToken(Token::TK_NAME); + std::string elementName = token().name; + nextToken(); + + XmlElement& newElement = parent.addChild(elementName); + + parseAttributes(newElement); + + if (token().type == Token::TK_SLASH_GREATER) //empty element + { + nextToken(); + continue; + } + + expectToken(Token::TK_GREATER); + std::string elementValue = scn.extractElementValue(); + nextToken(); + + //no support for mixed-mode content + if (token().type == Token::TK_LESS) //structured element + parseChildElements(newElement); + else //value element + newElement.setValue(elementValue); + + consumeToken(Token::TK_LESS_SLASH); + + if (token().type != Token::TK_NAME || + elementName != token().name) + throw XmlParsingError(scn.posRow(), scn.posCol()); + nextToken(); + + consumeToken(Token::TK_GREATER); + } + }; + + void parseAttributes(XmlElement& element) + { + while (token().type == Token::TK_NAME) + { + std::string attribName = token().name; + nextToken(); + + consumeToken(Token::TK_EQUAL); + expectToken(Token::TK_QUOTE); + std::string attribValue = scn.extractAttributeValue(); + nextToken(); + + consumeToken(Token::TK_QUOTE); + element.setAttribute(attribName, attribValue); + } + } + + const Token& token() const { return tk; } + void nextToken() { tk = scn.nextToken(); } + + void consumeToken(Token::Type t) //throw XmlParsingError + { + expectToken(t); //throw XmlParsingError + nextToken(); + } + + void expectToken(Token::Type t) //throw XmlParsingError + { + if (token().type != t) + throw XmlParsingError(scn.posRow(), scn.posCol()); + } + + Scanner scn; + Token tk; +}; +} + +inline +void parse(const std::string& stream, XmlDoc& doc) //throw XmlParsingError +{ + implementation::XmlParser(stream).parse(doc); //throw XmlParsingError +} +} + +#endif //ZEN_XML_PARSER_HEADER_81248670213764583021432 diff --git a/zenxml/summary.dox b/zenxml/summary.dox new file mode 100644 index 00000000..73a09bcd --- /dev/null +++ b/zenxml/summary.dox @@ -0,0 +1,680 @@ +// ************************************************************************** +// * 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 * +// ************************************************************************** + +/** +\mainpage Overview + +\li \ref sec_Rationale +\li \ref sec_Quick_Start +\li \ref sec_Supported_Platforms +\li \ref sec_Flexible_Programming_Model +\li \ref sec_Structured_XML_element_access +\li \ref sec_Access_XML_attributes +\li \ref sec_Automatic_conversion_built_in +\li \ref sec_Automatic_conversion_string +\li \ref sec_Automatic_conversion_STL +\li \ref sec_Support_user_defined +\li \ref sec_Structured_user_types +\li \ref sec_Type_Safety + +\section sec_Rationale Rationale + +zenXML is an XML library that enables serialization of structured user data in a convenient way. +Using compile-time information gathered by techniques of template metaprogramming it minimizes the manual overhead required and frees the user from applying fundamental type conversions +by himself. Basic data types such as \b all built-in arithmetic numbers, \b all kinds of string classes and "string-like" types, \b all types defined as STL containers are processed automatically. +Thereby a large number of recurring problems is finally solved by the library: +- generic number to string conversions +- generic char to wchar_t conversions for custom string classes in a platform independent manner +- serialization of STL container types +- simple integration: header-only, no extra dependencies, fully portable +- support (but not enforce) wide characters everywhere: for file names, XML element names, attribute names, values, ... +- integrate XML library with focus on elegance, minimal code size, flexibility and performance +- nonintrusive API: allow for internationalization, fine-granular error handling, and custom file I/O +- it's a toolkit, not a framework: different layers of software architecture offer, but do not enforce, specific programming models + +The design follows the philosophy of the Loki library: \n +http://loki-lib.sourceforge.net/index.php?n=Main.Philosophy + +\section sec_Quick_Start Quick Start + +1. Download zenXML: http://sourceforge.net/projects/zenxml + +2. Setup a preprocessor macro for your project to identify the platform (this is required for C-stream file IO only) +\code + ZEN_PLATFORM_WINDOWS + or + ZEN_PLATFORM_OTHER +\endcode + +3. For optimal performance define this global macro in release build: (following convention of the <tt>assert</tt> macro) +\code + NDEBUG +\endcode + +4. Include the main header: +\code +#include <zenxml/xml.h> +\endcode + +5. Start serializing user data: + +\code +size_t a = 10; +double b = 2.0; +int c = -1; +\endcode + +\code +zen::XmlDoc doc; //empty XML document + +zen::XmlOut out(doc); //fill the document via a data output proxy +out["elem1"](a); // +out["elem2"](b); //map data types to XML elements +out["elem3"](c); // + +try +{ + save(doc, "file.xml"); //throw zen::XmlFileError +} +catch (const zen::XmlFileError& e) { /* handle error */ } +\endcode + +The following XML file will be created: +\verbatim +<?xml version="1.0" encoding="UTF-8"?> +<Root> + <elem1>10</elem1> + <elem2>2.000000</elem2> + <elem3>-1</elem3> +</Root> +\endverbatim + +Load an XML file and map its content to user data: +\code +zen::XmlDoc doc; //empty XML document + +try +{ + load("file.xml", doc); //throw XmlFileError, XmlParsingError +} +catch (const zen::XmlError& e) { /* handle error */ } + +zen::XmlIn in(doc); //read document into user data via an input proxy +in["elem1"](a); // +in["elem2"](b); //map XML elements into user data +in["elem3"](c); // + +//check for mapping errors, i.e. missing elements or conversion errors: these MAY be considered warnings only +if (in.errorsOccured()) +{ + std::vector<std::wstring> failedElements = in.getErrorsAs<std::wstring>(); + /* show mapping errors */ +} +\endcode + + +\section sec_Supported_Platforms Supported Platforms + +zenXML is written in a platform independent manner and runs on any rudimentary C++11 compliant compiler. +It has been tested successfully under: + +- Windows: + -# Visual C++ 2010 - 32 bit + -# Visual C++ 2010 - 64 bit + -# MinGW: GCC 4.5.2 - 32 bit + +- Linux (Ubuntu): + -# GCC 4.5.2 - 32 bit + -# GCC 4.5.2 - 64 bit + +<b>Note:</b> In order to enable C++11 features in GCC it is required to specify either of the following compiler options: +\verbatim +-std=c++0x +-std=gnu++0x +\endverbatim + + +\section sec_Flexible_Programming_Model Flexible Programming Model + +Depending on what granularity of control is required in a particular application, zenXML allows the user to choose between full control or simplicity. +\n\n +The library is structured into the following parts, each of which can be used in isolation: +\n\n +\b \<File\> \n +|\n +| io.h\n +|\n +<b>\<Byte Stream\></b>\n +|\n +| parser.h\n +|\n +<b>\<Document Object Model\></b>\n +|\n +| bind.h\n +|\n +<b>\<C++ user data\></b> +\n\n + +- Save an XML document to memory +\code +zen::XmlDoc doc; + ... +std::string stream = serialize(doc); //throw () + +/* have fun with stream */ + +//default behavior - already available via zen::save() +saveStream(stream, "file.xml"); //throw XmlFileError +\endcode + +- Load XML document from memory +\code +/* get XML byte stream */ +//e.g. from a file - already available via zen::load() +std::string stream = loadStream("file.xml"); //throw XmlFileError + +zen::XmlDoc doc; +//parse byte stream into an XML document +parse(stream, doc); //throw XmlParsingError + +/* process XML document */ +\endcode + +- Fine-granular error checking +\code +zen::XmlIn in(doc); +//map XML elements into user data +if (!in["elem1"](a)) + throw MyCustomException(); +if (!in["elem2"](b)) + throw MyCustomException(); +if (!in["elem3"](c)) + throw MyCustomException(); + +//if (in.errorsOccured()) ... <- not required anymore since each conversion was already checked +\endcode + +- Document Object Model centered programming model +\n\n +The full power of type conversions which is available via the input/output proxy classes zen::XmlIn and zen::XmlOut is also available for the document object model! +\code +using namespace zen; +XmlDoc doc; + +XmlElement& child = doc.root().addChild("elem1"); +child.setValue(1234); + +zen::save(doc, "file.xml"); //throw XmlFileError +\endcode +\n +\code +using namespace zen; + +XmlDoc doc; +load("file.xml", doc); //throw XmlFileError, XmlParsingError + +XmlElement* child = doc.root().getChild("elem1"); +if (child) +{ + int value = -1; + if (!child->getValue(value)) + /* handle error */ +} +else + ... +\endcode + + +\section sec_Structured_XML_element_access Structured XML element access + +\code +//write value into one deeply nested XML element - note the different types used seamlessly: char[], wchar_t[], char, wchar_t, int +zen::XmlOut out(doc); +out["elemento1"][L"элемент2"][L"要素3"][L"στοιχείο4"]["elem5"][L"元素6"][L'元']['z'](-1234); +\endcode + +The resulting XML: +\verbatim +<?xml version="1.0" encoding="UTF-8"?> +<Root> + <elemento1> + <элемент2> + <要素3> + <στοιχείο4> + <elem5> + <元素6> + <元> + <z>-1234</z> + </元> + </元素6> + </elem5> + </στοιχείο4> + </要素3> + </элемент2> + </elemento1> +</Root> +\endverbatim + + +\section sec_Access_XML_attributes Access XML attributes + +\code +zen::XmlDoc doc; + +zen::XmlOut out(doc); +out["elem"].attribute("attr1", -1); // +out["elem"].attribute("attr2", 2.1); //write data into XML attributes +out["elem"].attribute("attr3", true); // + +save(doc, "file.xml"); //throw XmlFileError +\endcode + +The resulting XML: +\verbatim +<?xml version="1.0" encoding="UTF-8"?> +<Root> + <elem attr1="-1" attr2="2.1" attr3="true"/> +</Root> +\endverbatim + + +\section sec_Automatic_conversion_built_in Automatic conversion for built-in arithmetic types + +All built-in arithmetic types and <tt>bool</tt> are detected at compile time and a proper conversion is applied. +Common conversions for integer-like types such as <tt>long</tt>, <tt>long long</tt>, <tt>__int64</tt> or <tt>size_t</tt> as well as floating point types are optimized for maximum performance. + +\code +zen::XmlOut out(doc); + +out["int"] (-1234); +out["double"] (1.23); +out["float"] (4.56f); +out["usignlong"](1234UL); +out["bool"] (false); +\endcode + +The resulting XML: +\verbatim +<?xml version="1.0" encoding="UTF-8"?> +<Root> + <int>-1234</int> + <double>1.230000</double> + <float>4.560000</float> + <usignlong>1234</usignlong> + <bool>false</bool> +</Root> +\endverbatim + + +\section sec_Automatic_conversion_string Automatic conversion for string-like types + +The document object model of zenXML internally stores all names and values as a std::string. Consequently everything that is not a std::string but is "string-like" is converted automatically +into a std::string representation. By default zenXML accepts all character arrays like <tt>char[]</tt>, <tt>wchar_t[]</tt>, <tt>char*</tt>, <tt>wchar_t*</tt>, single characters like +<tt>char</tt>, <tt>wchar_t</tt>, standard string classes like <tt>std::string</tt>, <tt>std::wstring</tt> and user defined string classes. +If the input string is based on <tt>char</tt>, it will simply be copied and thereby preserves any local encodings. If the input string is based on <tt>wchar_t</tt> it will +be converted to an UTF-8 encoded <tt>std::string</tt>. The correct <tt>wchar_t</tt> encoding of the system will be detected at compile time, for example UTF-16 on Windows, +UTF-32 on certain Linux variants. + +<b>Note:</b> User defined string classes are implicitly supported if they fulfill the following string concept by defining: + -# A typedef named <tt>value_type</tt> for the underlying character type: must be \p char or \p wchar_t + -# A member function <tt>c_str()</tt> returning something that can be converted into a <tt>const value_type*</tt> + -# A member function <tt>length()</tt> returning the number of characters returned by <tt>c_str()</tt> + +\code +std::string elem1 = "elemento1"; +std::wstring elem2 = L"элемент2"; +wxString elem3 = L"要素3"; +MyString elem4 = L"στοιχείο4"; + +zen::XmlOut out(doc); + +out["string"] (elem1); +out["wstring"] (elem2); +out["wxString"] (elem3); +out["MyString"] (elem4); +out["char[6]"] ("elem5"); +out["wchar_t[4]"](L"元素6"); +out["wchar_t"] (L'元'); +out["char"] ('z'); +\endcode + +The resulting XML: +\verbatim +<?xml version="1.0" encoding="UTF-8"?> +<Root> + <string>elemento1</string> + <wstring>элемент2</wstring> + <wxString>要素3</wxString> + <MyString>στοιχείο4</MyString> + <char[6]>elem5</char[6]> + <wchar_t[4]>元素6</wchar_t[4]> + <wchar_t>元</wchar_t> + <char>z</char> +</Root> +\endverbatim + + +\section sec_Automatic_conversion_STL Automatic conversion for STL container types + +- User defined STL compatible types are implicitly supported if they fulfill the following container concept by defining: + -# A typedef named <tt>value_type</tt> for the underlying element type of the container + -# A typedef named <tt>iterator</tt> for a non-const iterator into the container + -# A typedef named <tt>const_iterator</tt> for a const iterator into the container +\n\n + -# A member function <tt>begin()</tt> returning an iterator pointing to the first element in the container + -# A member function <tt>end()</tt> returning an iterator pointing just after the last element in the container + -# A member function <tt>insert()</tt> with the signature <tt>iterator insert(iterator position, const value_type& x)</tt> + +- In order to support combinations of user types and STL containers such as <tt>std::vector<MyType></tt> or <tt>std::vector<std::list<MyType>></tt> it is sufficient to +integrate <tt>MyType</tt> into zenXML. \n +See \ref sec_Support_user_defined + +\code +std::deque <float> testDeque; +std::list <size_t> testList; +std::map <double, char> testMap; +std::multimap<short, double> testMultiMap; +std::set <int> testSet; +std::multiset<std::string> testMultiSet; +std::vector <wchar_t> testVector; +std::vector <std::list<wchar_t>> testVectorList; +std::pair <char, wchar_t> testPair; + +/* fill container */ + +zen::XmlOut out(doc); + +out["deque"] (testDeque); +out["list"] (testList); +out["map"] (testMap); +out["multimap"] (testMultiMap); +out["set"] (testSet); +out["multiset"] (testMultiSet); +out["vector"] (testVector); +out["vect_list"](testVectorList); +out["pair" ] (testPair); +\endcode + +The resulting XML: +\verbatim +<?xml version="1.0" encoding="UTF-8"?> +<Root> + <deque> + <Item>1.234000</Item> + <Item>5.678000</Item> + </deque> + <list> + <Item>1</Item> + <Item>2</Item> + </list> + <map> + <Item> + <one>1.100000</one> + <two>a</two> + </Item> + <Item> + <one>2.200000</one> + <two>b</two> + </Item> + </map> + <multimap> + <Item> + <one>3</one> + <two>99.000000</two> + </Item> + <Item> + <one>3</one> + <two>100.000000</two> + </Item> + <Item> + <one>4</one> + <two>101.000000</two> + </Item> + </multimap> + <set> + <Item>1</Item> + <Item>2</Item> + </set> + <multiset> + <Item>1</Item> + <Item>1</Item> + <Item>2</Item> + </multiset> + <vector> + <Item>Ä</Item> + <Item>Ö</Item> + </vector> + <vect_list> + <Item> + <Item>ä</Item> + <Item>ö</Item> + <Item>ü</Item> + </Item> + <Item> + <Item>ä</Item> + <Item>ö</Item> + <Item>ü</Item> + </Item> + </vect_list> + <pair> + <one>a</one> + <two>â</two> + </pair> +</Root> +\endverbatim + + +\section sec_Support_user_defined Support for user defined types + +User types can be integrated into zenXML by providing specializations of zen::readText() and zen::writeText() or zen::readStruc() and zen::writeStruc(). +The first pair should be used for all non-structured types that can be represented as a simple text string. This specialization is then used to convert the type to XML elements +and XML attributes. The second pair should be specialized for structured types that require an XML representation as a hierarchy of elements. This specialization is used when converting +the type to XML elements only. +\n\n +See section \ref sec_Type_Safety for a discussion of type categories. +\n\n +<b>Example: Specialization for an enum type</b> +\code +enum UnitTime +{ + UNIT_SECOND, + UNIT_MINUTE, + UNIT_HOUR +}; + +namespace zen +{ +template <> inline +void writeText(const UnitTime& value, std::string& output) +{ + switch (value) + { + case UNIT_SECOND: output = "second"; break; + case UNIT_MINUTE: output = "minute"; break; + case UNIT_HOUR: output = "hour" ; break; + } +} + +template <> inline +bool readText(const std::string& input, UnitTime& value) +{ + std::string tmp = input; + zen::trim(tmp); + if (tmp == "second") + value = UNIT_SECOND; + else if (tmp == "minute") + value = UNIT_MINUTE; + else if (tmp == "hour") + value = UNIT_HOUR; + else + return false; + return true; +} +} +\endcode + +<b>Example: Brute-force specialization for an enum type</b> +\code +namespace zen +{ +template <> inline +void writeText(const EnumType& value, std::string& output) +{ + output = zen::toString<std::string>(value); //treat enum as an integer +} + +template <> inline +bool readText(const std::string& input, EnumType& value) +{ + value = static_cast<EnumType>(zen::toNumber<int>(input)); //treat enum as an integer + return true; +} +} +\endcode + +<b>Example: Specialization for a structured user type</b> +\code +struct Config +{ + int a; + std::wstring b; +}; + +namespace zen +{ +template <> inline +void writeStruc(const Config& value, XmlElement& output) +{ + XmlOut out(output); + out["number" ](value.a); + out["address"](value.b); +} + +template <> inline +bool readStruc(const XmlElement& input, Config& value) +{ + XmlIn in(input); + bool rv1 = in["number" ](value.a); + bool rv2 = in["address"](value.b); + return rv1 && rv2; +} +} + +int main() +{ + Config cfg; + cfg.a = 2; + ... + std::vector<Config> cfgList; + cfgList.push_back(cfg); + + zen::XmlDoc doc; + zen::XmlOut out(doc); + out["config"](cfgList); + save(doc, "file.xml"); //throw XmlFileError +} +\endcode + +The resulting XML: +\verbatim +<?xml version="1.0" encoding="UTF-8"?> +<Root> + <config> + <Item> + <number>2</number> + <address>Abc 3</address> + </Item> + </config> +</Root> +\endverbatim + + +\section sec_Structured_user_types Structured user types + +Although it is possible to enable conversion of structured user types by specializing zen::readStruc() and zen::writeStruc() (see \ref sec_Support_user_defined), +this approach has one drawback: If a mapping error occurs when converting an XML element to structured user data, like one child-element is missing, +the input proxy class zen::XmlIn is only able to detect that the whole conversion failed. It cannot say which child-elements in particular failed to convert. +\n\n +Therefore it may be appropriate to convert structured types by calling subroutines in order to enable fine-granular logging: + +\code +void readConfig(const zen::XmlIn& in, Config& cfg) +{ + in["number" ](value.a); //failed conversion will now be logged for each single item by XmlIn + in["address"](value.b); //instead of once for the complete Config type! +} +\endcode +\n +\code +void readConfig(const wxString& filename, Config& cfg) +{ + zen::XmlDoc doc; //empty XML document + + try + { + load(filename, doc); //throw XmlFileError, XmlParsingError + } + catch (const zen::XmlError& e) { /* handle error */ } + + zen::XmlIn in(doc); + + zen::XmlIn inConfig = in["config"]; //get input proxy for child element "config" + + readConfig(inConfig, cfg); //map child element to user data by calling subroutine + + //check for mapping errors: errors occuring in subroutines are considered, too! + if (in.errorsOccured()) + /* show mapping errors */ +} +\endcode + + +\section sec_Type_Safety Type Safety + +zenXML heavily utilizes methods of compile-time introspection in order to free the user from managing basic type conversions by himself. +Thereby it is important to find the right balance between automatic conversions and type safety so that program correctness is not compromized. +In the context of XML processing three fundamental type categories can be recognized: + +- <b>string-like types</b>: <tt>std::string, wchar_t*, char[], wchar_t, wxString, MyStringClass, ...</tt> +- <b>to-string-convertible types</b>: any string-like type, all built-in arithmetic numbers, <tt>bool</tt> +- <b>structured types</b>: any to-string-convertible type, STL containers, <tt>std::pair</tt>, structured user types + +These categories can be seen as a sequence of inclusive sets: +\verbatim +----------------------------- +| structured | Used as: XML element value +| ------------------------- | Conversion via: readStruc(), writeStruc() - may be specialized for user-defined types! +| | to-string-convertible | | Used as: XML element/attribute value +| | --------------- | | Conversion via: readText(), writeText() - may be specialized for user-defined types! +| | | string-like | | | Used as: XML element/attribute value or element name +| | --------------- | | Conversion via: utfCvrtTo<>() +| ------------------------- | +----------------------------- +\endverbatim + +A practical implication of this design is that conversions that do not make sense in a particular context simply lead to compile-time errors: +\code +zen::XmlOut out(doc); +out[L'Z'](someValue); //fine: a wchar_t is acceptable as an element name +out[1234](someValue); //compiler error: an integer is NOT "string-like"! +\endcode +\n +\code +int valInt = 0; +std::vector<int> valVec; + +zen::XmlOut out(doc); +out["elem1"](valInt); //fine: both valInt and valVec can be converted to an XML element +out["elem2"](valVec); // + +out["elem"].attribute("attr1", valInt); //fine: an integer can be converted to an XML attribute +out["elem"].attribute("attr2", valVec); //compiler error: a std::vector<int> is NOT "to-string-convertible"! +\endcode + + \author \b Zenju + \n\n + <b>Email:</b> zenju AT gmx DOT de +*/
\ No newline at end of file diff --git a/zenxml/unit_test.cpp b/zenxml/unit_test.cpp new file mode 100644 index 00000000..a671c9ee --- /dev/null +++ b/zenxml/unit_test.cpp @@ -0,0 +1,95 @@ +// ************************************************************************** +// * 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 * +// ************************************************************************** + +#include <set> +#include <map> +#include <deque> +#include <vector> +#include <list> +#include <utility> +#include "xml.h" + +using namespace zen; + +namespace +{ +void unit_test() +{ + class Dummy {}; + + //compile time checks only + + assert_static(!IsStlContainer<wchar_t> ::value); + assert_static(!IsStlContainer<char> ::value); + assert_static(!IsStlContainer<Dummy> ::value); + assert_static(!IsStlContainer<NullType> ::value); + assert_static(IsStlContainer<std::set <int>> ::value); + assert_static(IsStlContainer<std::deque <float>> ::value); + assert_static(IsStlContainer<std::list <size_t>> ::value); + assert_static((IsStlContainer<std::map <double, char>> ::value)); + assert_static((IsStlContainer<std::multimap<short, double>>::value)); + assert_static(IsStlContainer <std::vector <wchar_t>> ::value); + assert_static((IsStlPair <std::pair<int, double>> ::value)); + assert_static(!IsStlPair<Dummy> ::value); + + assert_static(!IsStringLike<Dummy>::value); + assert_static(!IsStringLike<int>::value); + assert_static(!IsStringLike<double>::value); + assert_static(!IsStringLike<short>::value); + assert_static(IsStringLike<char>::value); + assert_static(IsStringLike<const wchar_t>::value); + assert_static(IsStringLike<const char>::value); + assert_static(IsStringLike<wchar_t>::value); + assert_static(IsStringLike<char*>::value); + assert_static(IsStringLike<wchar_t*>::value); + assert_static(IsStringLike<char* const>::value); + assert_static(IsStringLike<wchar_t* const>::value); + assert_static(IsStringLike<const char*>::value); + assert_static(IsStringLike<const char* const>::value); + assert_static(IsStringLike<const wchar_t*>::value); + assert_static(IsStringLike<const wchar_t* const>::value); + assert_static(IsStringLike<const char[4]>::value); + assert_static(IsStringLike<const wchar_t[4]>::value); + assert_static(IsStringLike<char[4]>::value); + assert_static(IsStringLike<wchar_t[4]>::value); + assert_static(IsStringLike<std::string>::value); + assert_static(IsStringLike<std::wstring>::value); + assert_static(IsStringLike<const std::string>::value); + assert_static(IsStringLike<const std::wstring>::value); + assert_static(IsStringLike<const std::string&>::value); + assert_static(IsStringLike<const std::wstring&>::value); + assert_static(IsStringLike<std::string&>::value); + assert_static(IsStringLike<std::wstring&>::value); + + assert_static(!(IsSameType<GetCharType<int>::Type, char>::value)); + assert_static(!(IsSameType<GetCharType<double>::Type, char>::value)); + assert_static(!(IsSameType<GetCharType<short>::Type, char>::value)); + assert_static((IsSameType<GetCharType<char>::Type, char>::value)); + assert_static((IsSameType<GetCharType<wchar_t>::Type, wchar_t>::value)); + assert_static((IsSameType<GetCharType<const char>::Type, char>::value)); + assert_static((IsSameType<GetCharType<const wchar_t>::Type, wchar_t>::value)); + assert_static((IsSameType<GetCharType<char*>::Type, char>::value)); + assert_static((IsSameType<GetCharType<wchar_t*>::Type, wchar_t>::value)); + assert_static((IsSameType<GetCharType<char* const>::Type, char>::value)); + assert_static((IsSameType<GetCharType<wchar_t* const>::Type, wchar_t>::value)); + assert_static((IsSameType<GetCharType<const char*>::Type, char>::value)); + assert_static((IsSameType<GetCharType<const char* const>::Type, char>::value)); + assert_static((IsSameType<GetCharType<const wchar_t*>::Type, wchar_t>::value)); + assert_static((IsSameType<GetCharType<const wchar_t* const>::Type, wchar_t>::value)); + assert_static((IsSameType<GetCharType<const char[4]>::Type, char>::value)); + assert_static((IsSameType<GetCharType<const wchar_t[4]>::Type, wchar_t>::value)); + assert_static((IsSameType<GetCharType<char[4]>::Type, char>::value)); + assert_static((IsSameType<GetCharType<wchar_t[4]>::Type, wchar_t>::value)); + assert_static((IsSameType<GetCharType<std::string>::Type, char>::value)); + assert_static((IsSameType<GetCharType<std::wstring>::Type, wchar_t>::value)); + assert_static((IsSameType<GetCharType<const std::string>::Type, char>::value)); + assert_static((IsSameType<GetCharType<const std::wstring>::Type, wchar_t>::value)); + assert_static((IsSameType<GetCharType<const std::string&>::Type, char>::value)); + assert_static((IsSameType<GetCharType<const std::wstring&>::Type, wchar_t>::value)); + assert_static((IsSameType<GetCharType<std::string&>::Type, char>::value)); + assert_static((IsSameType<GetCharType<std::wstring&>::Type, wchar_t>::value)); +} +} diff --git a/zenxml/xml.h b/zenxml/xml.h new file mode 100644 index 00000000..0fd954ae --- /dev/null +++ b/zenxml/xml.h @@ -0,0 +1,15 @@ +// ************************************************************************** +// * 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_HEADER_349578228034572457454554 +#define ZEN_XML_HEADER_349578228034572457454554 + +#include "bind.h" + +/// The zenXML namespace +namespace zen {} + +#endif //ZEN_XML_HEADER_349578228034572457454554
\ No newline at end of file |