diff options
Diffstat (limited to 'zenXml/zenxml')
-rw-r--r-- | zenXml/zenxml/cvrt_struc.h | 4 | ||||
-rw-r--r-- | zenXml/zenxml/dom.h | 110 | ||||
-rw-r--r-- | zenXml/zenxml/parser.h | 14 | ||||
-rw-r--r-- | zenXml/zenxml/xml.h | 246 |
4 files changed, 141 insertions, 233 deletions
diff --git a/zenXml/zenxml/cvrt_struc.h b/zenXml/zenxml/cvrt_struc.h index 0bfe5996..40277556 100644 --- a/zenXml/zenxml/cvrt_struc.h +++ b/zenXml/zenxml/cvrt_struc.h @@ -124,9 +124,9 @@ struct ConvertElement<T, ValueType::stlContainer> value.clear(); bool success = true; - const auto itPair = input.getChildren("Item"); + auto [it, itEnd] = input.getChildren(); - std::for_each(itPair.first, itPair.second, [&](const XmlElement& xmlChild) + std::for_each(it, itEnd, [&](const XmlElement& xmlChild) { typename T::value_type childVal; if (zen::readStruc(xmlChild, childVal)) diff --git a/zenXml/zenxml/dom.h b/zenXml/zenxml/dom.h index cba12cfb..89320db1 100644 --- a/zenXml/zenxml/dom.h +++ b/zenXml/zenxml/dom.h @@ -82,8 +82,8 @@ public: it->second->value = std::move(attrValue); else { - auto itBack = attributes_.insert(attributes_.end(), {name, std::move(attrValue)}); - attributesByName.emplace(std::move(name), itBack); + attributes_.push_back({name, std::move(attrValue)}); + attributesByName.emplace(std::move(name), --attributes_.end()); } static_assert(std::is_same_v<decltype(attributes_), std::list<Attribute>>); //must NOT invalidate references used in "attributesByName"! } @@ -97,6 +97,7 @@ public: attributes_.erase(it->second); attributesByName.erase(it); } + else assert(false); } ///Create a new child element and return a reference to it. @@ -107,9 +108,9 @@ public: { childElements_.emplace_back(name, this); XmlElement& newElement = childElements_.back(); - childElementsByName_.emplace(std::move(name), &newElement); + childElementByName_.emplace(std::move(name), --childElements_.end()); - static_assert(std::is_same_v<decltype(childElements_), std::list<XmlElement>>); //must NOT invalidate references used in "childElementsByName_"! + static_assert(std::is_same_v<decltype(childElements_), std::list<XmlElement>>); //must NOT invalidate references used in "childElementByName_"! return newElement; } @@ -120,8 +121,8 @@ public: */ const XmlElement* getChild(const std::string& name) const { - auto it = childElementsByName_.find(name); - return it == childElementsByName_.end() ? nullptr : it->second; + auto it = childElementByName_.find(name); + return it == childElementByName_.end() ? nullptr : &*(it->second); } ///\sa getChild @@ -130,71 +131,16 @@ public: 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 : private AccessPolicy //get rid of shared_ptr indirection - { - public: - using iterator_category = std::input_iterator_tag; - using value_type = T; - using difference_type = ptrdiff_t; - using pointer = T*; - using reference = T&; - - 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_; } - T& operator* () const { return AccessPolicy::template objectRef<T>(it_); } - T* operator->() const { return &AccessPolicy::template objectRef<T>(it_); } - private: - IterTy it_; - }; - - struct AccessMapElement - { - template <class T, class IterTy> - T& objectRef(const IterTy& it) const { return *(it->second); } - }; - - using ChildIter2 = PtrIter<std::multimap<std::string, XmlElement*>::iterator, XmlElement, AccessMapElement>; - using ChildIterConst2 = PtrIter<std::multimap<std::string, XmlElement*>::const_iterator, const XmlElement, AccessMapElement>; - - ///Access all child elements with the given name via STL iterators. - /** - \code - auto itPair = 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 the child elements sequentially. - */ - std::pair<ChildIterConst2, ChildIterConst2> getChildren(const std::string& name) const { return childElementsByName_.equal_range(name); } - - ///\sa getChildren - std::pair<ChildIter2, ChildIter2> getChildren(const std::string& name) { return childElementsByName_.equal_range(name); } - - struct AccessListElement - { - template <class T, class IterTy> - T& objectRef(const IterTy& it) const { return *it; } - }; - - using ChildIter = PtrIter<std::list<XmlElement>::iterator, XmlElement, AccessListElement>; - using ChildIterConst = PtrIter<std::list<XmlElement>::const_iterator, const XmlElement, AccessListElement>; + using ChildIter = std::list<XmlElement>::iterator; + using ChildIterConst = std::list<XmlElement>::const_iterator; ///Access all child elements sequentially via STL iterators. /** - \code - auto itPair = elem.getChildren(); - std::for_each(itPair.first, itPair.second, - [](const XmlElement& child) { ... }); - \endcode - \return A pair of STL begin/end iterators to access all child elements sequentially. - */ + \code + auto [it, itEnd] = elem.getChildren(); + std::for_each(it, itEnd, [](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 {childElements_.begin(), childElements_.end()}; } ///\sa getChildren @@ -213,24 +159,24 @@ public: using AttrIter = std::list<Attribute>::const_iterator; /* -> disabled documentation extraction - \brief Get all attributes associated with the element. - \code + \brief Get all attributes associated with the element. + \code auto itPair = elem.getAttributes(); for (auto it = itPair.first; it != itPair.second; ++it) std::cout << std::string("name: ") + it->name + " value: " + it->value + '\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. */ + \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 {attributes_.begin(), attributes_.end()}; } //swap two elements while keeping references to parent. -> disabled documentation extraction void swapSubtree(XmlElement& other) noexcept { - name_ .swap(other.name_); - value_ .swap(other.value_); - attributes_ .swap(other.attributes_); - attributesByName .swap(other.attributesByName); - childElements_ .swap(other.childElements_); - childElementsByName_.swap(other.childElementsByName_); + name_ .swap(other.name_); + value_ .swap(other.value_); + attributes_ .swap(other.attributes_); + attributesByName .swap(other.attributesByName); + childElements_ .swap(other.childElements_); + childElementByName_.swap(other.childElementByName_); for (XmlElement& child : childElements_) child.parent_ = this; @@ -248,12 +194,10 @@ private: std::list<Attribute> attributes_; //attributes in order of creation std::unordered_map<std::string, std::list<Attribute>::iterator> attributesByName; //alternate view for lookup - std::list<XmlElement> childElements_; //child elements in order of creation - std::multimap<std::string, XmlElement*> childElementsByName_; //alternate view for lookup - //alternative: std::unordered_map => but let's keep std::map, so which guarantees consistent order of duplicate items! - //e.g. std::unordered_map on Linux inserts duplicates in reverse! + std::list<XmlElement> childElements_; //child elements in order of creation + std::unordered_map<std::string, std::list<XmlElement>::iterator> childElementByName_; //alternate view for lookup of (*first*) child by name - XmlElement* parent_ = nullptr; + XmlElement* parent_ = nullptr; //currently unused: YAGNI? }; diff --git a/zenXml/zenxml/parser.h b/zenXml/zenxml/parser.h index 8416c211..e089a86f 100644 --- a/zenXml/zenxml/parser.h +++ b/zenXml/zenxml/parser.h @@ -206,14 +206,14 @@ void serialize(const XmlElement& element, std::string& stream, for (auto it = attr.first; it != attr.second; ++it) stream += ' ' + normalizeName(it->name) + "=\"" + normalizeAttribValue(it->value) + '"'; - auto itPair = element.getChildren(); - if (itPair.first != itPair.second) //structured element + auto [it, itEnd] = element.getChildren(); + if (it != itEnd) //structured element { //no support for mixed-mode content stream += '>' + lineBreak; - std::for_each(itPair.first, itPair.second, - [&](const XmlElement& el) { serialize(el, stream, lineBreak, indent, indentLevel + 1); }); + std::for_each(it, itEnd, [&](const XmlElement& el) + { serialize(el, stream, lineBreak, indent, indentLevel + 1); }); for (size_t i = 0; i < indentLevel; ++i) stream += indent; @@ -483,9 +483,9 @@ public: XmlElement dummy; parseChildElements(dummy); - auto itPair = dummy.getChildren(); - if (itPair.first != itPair.second) - doc.root().swapSubtree(*itPair.first); + auto [it, itEnd] = dummy.getChildren(); + if (it != itEnd) + doc.root().swapSubtree(*it); expectToken(Token::TK_END); //throw XmlParsingError return doc; 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 |