diff options
Diffstat (limited to 'zenXml/zenxml/xml.h')
-rw-r--r-- | zenXml/zenxml/xml.h | 246 |
1 files changed, 105 insertions, 141 deletions
diff --git a/zenXml/zenxml/xml.h b/zenXml/zenxml/xml.h index 8b86a49f..d4748bca 100644 --- a/zenXml/zenxml/xml.h +++ b/zenXml/zenxml/xml.h @@ -109,7 +109,7 @@ class XmlOut public: ///Construct an output proxy for an XML document /** - \code + \code zen::XmlDoc doc; zen::XmlOut out(doc); @@ -118,18 +118,18 @@ public: out["elem3"](-3); // saveXml(doc, "out.xml"); //throw FileError - \endcode - Output: - \verbatim + \endcode + Output: + \verbatim <?xml version="1.0" encoding="utf-8"?> <Root> <elem1>1</elem1> <elem2>2</elem2> <elem3>-3</elem3> </Root> - \endverbatim + \endverbatim */ - explicit XmlOut(XmlDoc& doc) : ref_(&doc.root()) {} + explicit XmlOut(XmlDoc& doc) : ref_(doc.root()) {} ///Retrieve a handle to an XML child element for writing /** @@ -138,8 +138,8 @@ public: */ XmlOut operator[](std::string name) const { - XmlElement* child = ref_->getChild(name); - return XmlOut(child ? *child : ref_->addChild(std::move(name))); + XmlElement* child = ref_.getChild(name); + return XmlOut(child ? *child : ref_.addChild(std::move(name))); } ///Retrieve a handle to an XML child element for writing @@ -150,7 +150,7 @@ public: */ XmlOut addChild(std::string name) const { - return XmlOut(ref_->addChild(std::move(name))); + return XmlOut(ref_.addChild(std::move(name))); } ///Write user data to the underlying XML element @@ -159,12 +159,12 @@ public: \tparam T User type that is converted into an XML element value. */ template <class T> - void operator()(const T& value) { writeStruc(value, *ref_); } + 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 + \code zen::XmlDoc doc; zen::XmlOut out(doc); @@ -173,53 +173,50 @@ public: out["elem"].attribute("attr3", -3); // saveXml(doc, "out.xml"); //throw FileError - \endcode + \endcode Output: - \verbatim + \verbatim <?xml version="1.0" encoding="utf-8"?> <Root> <elem attr1="1" attr2="2" attr3="-3"/> </Root> - \endverbatim + \endverbatim \tparam T String-convertible user data type: e.g. any string-like type, all built-in arithmetic numbers \sa XmlElement::setAttribute() */ template <class T> - void attribute(std::string name, const T& value) { ref_->setAttribute(std::move(name), value); } + void attribute(std::string name, const T& value) { ref_.setAttribute(std::move(name), value); } private: ///Construct an output proxy for a single XML element /** \sa XmlOut(XmlDoc& doc) */ - explicit XmlOut(XmlElement& element) : ref_(&element) {} + explicit XmlOut(XmlElement& element) : ref_(element) {} - XmlElement* ref_; //always bound! + XmlElement& ref_; }; ///Proxy class to conveniently convert XML structure to user data class XmlIn { - class ErrorLog; + struct ErrorLog; public: ///Construct an input proxy for an XML document /** - \code + \code zen::XmlDoc doc; ... //load document zen::XmlIn in(doc); in["elem1"](value1); // in["elem2"](value2); //read data from XML elements into variables "value1", "value2", "value3" in["elem3"](value3); // - \endcode + \endcode */ - explicit XmlIn(const XmlDoc& doc) : nodeNameFormatted_('<' + doc.root().getName() + '>') - { - refList_.push_back(&doc.root()); - } + explicit XmlIn(const XmlDoc& doc) : XmlIn(&doc.root(), '<' + doc.root().getName() + '>', makeSharedRef<ErrorLog>()) {} ///Retrieve a handle to an XML child element for reading /** @@ -228,52 +225,59 @@ public: */ XmlIn operator[](const std::string& name) const { - std::vector<const XmlElement*> childList; - - if (const XmlElement* elem = get()) - { - auto itPair = elem->getChildren(name); - std::for_each(itPair.first, itPair.second, [&](const XmlElement& child) - { childList.push_back(&child); }); - } - - return XmlIn(childList, getChildNameFormatted(name), log_); + return XmlIn(elem_ ? elem_->getChild(name) : nullptr, elementNameFmt_ + " <" + name + '>', log_); } - ///Refer to next sibling element with the same name + ///Iterate over XML child elements /** - <b>Example:</b> Loop over all XML child elements named "Item" + <b>Example:</b> Loop over all XML child elements \verbatim - <?xml version="1.0" encoding="utf-8"?> - <Root> - <Item>1</Item> - <Item>3</Item> - <Item>5</Item> - </Root> + <?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()) - { + zen::XmlIn in(doc); ... - } + in.visitChildren([&](const XmlIn& inChild) + { + ... + }); \endcode */ - void next() { ++refIndex_; } + template <class Function> + void visitChildren(Function fun) + { + if (!elem_) + logMissingElement(); + else if (std::string value; elem_->getValue(value) && !value.empty()) + logConversionError(); //have XML value element, not container! + else + { + auto [it, itEnd] = elem_->getChildren(); + size_t childIdx = 0; + std::for_each(it, itEnd, [&](const XmlElement& child) + { + fun(XmlIn(&child, elementNameFmt_ + " <" + child.getName() + ">[" + numberTo<std::string>(++childIdx) + ']', log_)); + }); + } + } ///Test whether the 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) + \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) */ - explicit operator bool() const { return get() != nullptr; } + explicit operator bool() const { return elem_; } ///Read user data from the underlying XML element /** @@ -284,25 +288,22 @@ public: template <class T> bool operator()(T& value) const { - if (const XmlElement* elem = get()) + if (elem_) { - if (readStruc(*elem, value)) + if (readStruc(*elem_, value)) return true; - log_.ref().notifyConversionError(getNameFormatted()); + logConversionError(); } else - log_.ref().notifyMissingElement(getNameFormatted()); + logMissingElement(); return false; } bool hasAttribute(const std::string& name) const { - if (const XmlElement* elem = get()) - if (elem->hasAttribute(name)) - return true; - return false; + return elem_ && elem_->hasAttribute(name); } ///Read user data from an XML attribute @@ -326,15 +327,15 @@ public: template <class T> bool attribute(const std::string& name, T& value) const { - if (const XmlElement* elem = get()) + if (elem_) { - if (elem->getAttribute(name, value)) + if (elem_->getAttribute(name, value)) return true; - log_.ref().notifyMissingAttribute(getNameFormatted(), name); + logMissingAttribute(name); } else - log_.ref().notifyMissingElement(getNameFormatted()); + logMissingElement(); return false; } @@ -342,104 +343,67 @@ public: ///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 + \code XmlIn in(doc); XmlIn inItem = in["item1"]; int value = 0; inItem(value); //let's assume this conversion failed - assert(in.haveErrors() == inItem.haveErrors()); - assert(in.getErrorsAs<std::string>() == inItem.getErrorsAs<std::string>()); - \endcode + assert(in.getErrors() == inItem.getErrors()); + \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. + 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 different threads. */ ///Get a list of XML element and attribute names which failed to convert to user data. /** - \returns A list of XML element and attribute names, empty list if no errors occured. + \returns A list of XML element and attribute names, empty if no errors occured. */ - std::vector<std::wstring> getErrors() const - { - std::vector<std::wstring> output; + const std::wstring& getErrors() const { return log_.ref().failedElements; } - for (const std::string& str : log_.ref().elementList()) - output.push_back(utfTo<std::wstring>(str)); - - return output; + ///Retrieve the name of this XML element. + /** + \returns Name of the XML element. + */ + const std::string* getName() const + { + if (elem_) + return &elem_->getName(); + return nullptr; } private: - XmlIn(const std::vector<const XmlElement*>& siblingList, - const std::string& nodeNameFormatted, - const SharedRef<ErrorLog>& sharedlog) : refList_(siblingList), nodeNameFormatted_(nodeNameFormatted), log_(sharedlog) {} - - ///Return a pointer to the underlying Xml element, may be nullptr - const XmlElement* get() const { return refIndex_ < refList_.size() ? refList_[refIndex_] : nullptr; } - - std::string getNameFormatted() const //"<Root> <Level1> <Level2>" - { - if (refIndex_ == 0 && refList_.size() <= 1) - return nodeNameFormatted_; - else - return nodeNameFormatted_ + '[' + numberTo<std::string>(refIndex_ + 1) + ']'; - } + XmlIn(const XmlElement* elem, + const std::string& elementNameFmt, + const SharedRef<ErrorLog>& sharedlog) : log_(sharedlog), elem_(elem), elementNameFmt_(elementNameFmt) {} - std::string getChildNameFormatted(const std::string& childName) const + struct ErrorLog { - return getNameFormatted() + " <" + childName + '>'; - } + std::wstring failedElements; //unique list of failed elements + std::unordered_set<std::string> usedElements; + }; - class ErrorLog + void logElementError(const std::string& elementName) const { - public: - void notifyConversionError (const std::string& displayName) { insert(displayName); } - void notifyMissingElement (const std::string& displayName) { insert(displayName); } - void notifyMissingAttribute(const std::string& displayName, const std::string& attribName) { insert(displayName + " @" + attribName); } - - const std::vector<std::string>& elementList() const { return failedElements; } - - private: - void insert(const std::string& newVal) + if (const auto [it, inserted] = log_.ref().usedElements.insert(elementName); + inserted) { - if (usedElements.insert(newVal).second) - failedElements.push_back(newVal); + if (!log_.ref().failedElements.empty()) + log_.ref().failedElements += L'\n'; + log_.ref().failedElements += utfTo<std::wstring>(elementName); } + } - std::vector<std::string> failedElements; //unique list of failed elements - std::set<std::string> usedElements; - }; + void logConversionError() const { logElementError(elementNameFmt_); } + void logMissingElement() const { logElementError(elementNameFmt_); } + void logMissingAttribute(const std::string& attribName) const { logElementError(elementNameFmt_ + " @" + attribName); } - std::vector<const XmlElement*> refList_; //all sibling elements with same name (all pointers bound!) - size_t refIndex_ = 0; //this sibling's index in refList_ - std::string nodeNameFormatted_; - mutable SharedRef<ErrorLog> log_ = makeSharedRef<ErrorLog>(); + mutable SharedRef<ErrorLog> log_; + const XmlElement* elem_; + std::string elementNameFmt_; //e.g. "<Root> <Child> <List>[1]" }; - - -///Check XML input proxy for errors and map to FileError exception -/** -\param xmlInput XML input proxy -\throw FileError -*/ -inline -void checkXmlMappingErrors(const XmlIn& xmlInput) //throw FileError -{ - if (const std::vector<std::wstring>& errors = xmlInput.getErrors(); - !errors.empty()) - { - std::wstring msg = _("The following XML elements could not be read:") + L'\n'; - for (const std::wstring& elem : errors) - msg += L'\n' + elem; - - throw FileError(msg); - } -} } #endif //XML_H_349578228034572457454554 |