diff options
author | B Stack <bgstack15@gmail.com> | 2018-10-16 17:33:51 -0400 |
---|---|---|
committer | B Stack <bgstack15@gmail.com> | 2018-10-16 17:33:51 -0400 |
commit | 878a41d3be13da2a654df74f2a35ea8b295c8a13 (patch) | |
tree | 89b2a018482c164bdd8ecac5c76b19a08f420dec /zenXml/zenxml | |
parent | Merge branch '10.4' into 'master' (diff) | |
download | FreeFileSync-878a41d3be13da2a654df74f2a35ea8b295c8a13.tar.gz FreeFileSync-878a41d3be13da2a654df74f2a35ea8b295c8a13.tar.bz2 FreeFileSync-878a41d3be13da2a654df74f2a35ea8b295c8a13.zip |
10.5
Diffstat (limited to 'zenXml/zenxml')
-rwxr-xr-x | zenXml/zenxml/cvrt_text.h | 6 | ||||
-rwxr-xr-x | zenXml/zenxml/parser.h | 196 | ||||
-rwxr-xr-x | zenXml/zenxml/xml.h | 440 |
3 files changed, 536 insertions, 106 deletions
diff --git a/zenXml/zenxml/cvrt_text.h b/zenXml/zenxml/cvrt_text.h index c6dab8c2..51b23173 100755 --- a/zenXml/zenxml/cvrt_text.h +++ b/zenXml/zenxml/cvrt_text.h @@ -22,9 +22,9 @@ It is \b not required to call these functions directly. They are implicitly used 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 + - strings - std::string, std::wstring, char*, wchar_t*, char, wchar_t, etc..., all STL-compatible-string-classes + - numbers - int, double, float, bool, long, etc..., all built-in numbers + - STL containers - std::map, std::set, std::vector, std::list, etc..., all STL-compatible-containers - std::pair You can add support for additional types via template specialization. \n\n diff --git a/zenXml/zenxml/parser.h b/zenXml/zenxml/parser.h index a3975297..a90d163a 100755 --- a/zenXml/zenxml/parser.h +++ b/zenXml/zenxml/parser.h @@ -11,7 +11,6 @@ #include <cstddef> //ptrdiff_t; req. on Linux #include <zen/string_tools.h> #include "dom.h" -#include "error.h" namespace zen @@ -28,12 +27,13 @@ namespace zen \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 = " "); //noexcept +std::string serializeXml(const XmlDoc& doc, + const std::string& lineBreak = "\r\n", + const std::string& indent = " "); //noexcept + ///Exception thrown due to an XML parsing error -struct XmlParsingError : public XmlError +struct XmlParsingError { XmlParsingError(size_t rowNo, size_t colNo) : row(rowNo), col(colNo) {} ///Input file row where the parsing error occured (zero-based) @@ -42,14 +42,13 @@ struct XmlParsingError : public XmlError const size_t col; // }; - ///Load XML document from a byte stream /** \param stream Input byte stream \returns Output XML document \throw XmlParsingError */ -XmlDoc parse(const std::string& stream); //throw XmlParsingError +XmlDoc parseXml(const std::string& stream); //throw XmlParsingError @@ -71,9 +70,9 @@ XmlDoc parse(const std::string& stream); //throw XmlParsingError //---------------------------- implementation ---------------------------- -//see: http://www.w3.org/TR/xml/ +//see: https://www.w3.org/TR/xml/ -namespace impl +namespace xml_impl { 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 @@ -91,7 +90,7 @@ std::string normalize(const std::string& str, Predicate pred) //pred: unary func { if (c == '\'') output += "'"; - else if (c == '\"') + else if (c == '"') output += """; else { @@ -111,7 +110,7 @@ std::string normalize(const std::string& str, Predicate pred) //pred: unary func inline std::string normalizeName(const std::string& str) { - const std::string nameFmt = normalize(str, [](char c) { return isWhiteSpace(c) || c == '=' || c == '/' || c == '\'' || c == '\"'; }); + const std::string nameFmt = normalize(str, [](char c) { return isWhiteSpace(c) || c == '=' || c == '/' || c == '\'' || c == '"'; }); assert(!nameFmt.empty()); return nameFmt; } @@ -125,7 +124,7 @@ std::string normalizeElementValue(const std::string& str) inline std::string normalizeAttribValue(const std::string& str) { - return normalize(str, [](char c) { return static_cast<unsigned char>(c) < 32 || c == '\'' || c == '\"'; }); + return normalize(str, [](char c) { return static_cast<unsigned char>(c) < 32 || c == '\'' || c == '"'; }); } @@ -163,10 +162,12 @@ std::string denormalize(const std::string& str) else if (checkEntity(it, str.end(), "'")) output += '\''; else if (checkEntity(it, str.end(), """)) - output += '\"'; + output += '"'; else if (str.end() - it >= 6 && it[1] == '#' && it[2] == 'x' && + isHexDigit(it[3]) && + isHexDigit(it[4]) && it[5] == ';') { output += unhexify(it[3], it[4]); @@ -203,7 +204,7 @@ void serialize(const XmlElement& element, std::string& stream, auto attr = element.getAttributes(); for (auto it = attr.first; it != attr.second; ++it) - stream += ' ' + normalizeName(it->name) + "=\"" + normalizeAttribValue(it->value) + '\"'; + stream += ' ' + normalizeName(it->name) + "=\"" + normalizeAttribValue(it->value) + '"'; auto iterPair = element.getChildren(); if (iterPair.first != iterPair.second) //structured element @@ -229,34 +230,30 @@ void serialize(const XmlElement& element, std::string& stream, stream += "/>" + lineBreak; } } +} +} -std::string serialize(const XmlDoc& doc, - const std::string& lineBreak, - const std::string& indent) +inline +std::string serializeXml(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) + '\"'; + version = " version=\"" + xml_impl::normalizeAttribValue(version) + '"'; std::string encoding = doc.getEncodingAs<std::string>(); if (!encoding.empty()) - encoding = " encoding=\"" + normalizeAttribValue(encoding) + '\"'; + encoding = " encoding=\"" + xml_impl::normalizeAttribValue(encoding) + '"'; std::string standalone = doc.getStandaloneAs<std::string>(); if (!standalone.empty()) - standalone = " standalone=\"" + normalizeAttribValue(standalone) + '\"'; + standalone = " standalone=\"" + xml_impl::normalizeAttribValue(standalone) + '"'; std::string output = "<?xml" + version + encoding + standalone + "?>" + lineBreak; - serialize(doc.root(), output, lineBreak, indent, 0); + xml_impl::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 impl::serialize(doc, lineBreak, indent); } /* Grammar for XML parser @@ -282,7 +279,7 @@ pm-expression: element-list-expression */ -namespace impl +namespace xml_impl { struct Token { @@ -310,53 +307,53 @@ struct Token class Scanner { public: - Scanner(const std::string& stream) : stream_(stream), pos(stream_.begin()) + Scanner(const std::string& stream) : stream_(stream), pos_(stream_.begin()) { if (zen::startsWith(stream_, BYTE_ORDER_MARK_UTF8)) - pos += strLength(BYTE_ORDER_MARK_UTF8); + pos_ += strLength(BYTE_ORDER_MARK_UTF8); } - Token nextToken() //throw XmlParsingError + Token getNextToken() //throw XmlParsingError { //skip whitespace - pos = std::find_if(pos, stream_.end(), [](char c) { return !zen::isWhiteSpace(c); }); + pos_ = std::find_if(pos_, stream_.end(), std::not_fn(isWhiteSpace<char>)); - if (pos == stream_.end()) + if (pos_ == stream_.end()) return Token::TK_END; //skip XML comments - if (startsWith(xmlCommentBegin)) + if (startsWith(xmlCommentBegin_)) { - auto it = std::search(pos + xmlCommentBegin.size(), stream_.end(), xmlCommentEnd.begin(), xmlCommentEnd.end()); + auto it = std::search(pos_ + xmlCommentBegin_.size(), stream_.end(), xmlCommentEnd_.begin(), xmlCommentEnd_.end()); if (it != stream_.end()) { - pos = it + xmlCommentEnd.size(); - return nextToken(); + pos_ = it + xmlCommentEnd_.size(); + return getNextToken(); //throw XmlParsingError } } - for (auto it = tokens.begin(); it != tokens.end(); ++it) + for (auto it = tokens_.begin(); it != tokens_.end(); ++it) if (startsWith(it->first)) { - pos += it->first.size(); + pos_ += it->first.size(); return it->second; } - auto nameEnd = std::find_if(pos, stream_.end(), [](char c) + const auto itNameEnd = std::find_if(pos_, stream_.end(), [](char c) { return c == '<' || c == '>' || c == '=' || c == '/' || c == '\'' || - c == '\"' || - zen::isWhiteSpace(c); + c == '"' || + isWhiteSpace(c); }); - if (nameEnd != pos) + if (itNameEnd != pos_) { - std::string name(&*pos, nameEnd - pos); - pos = nameEnd; + std::string name(pos_, itNameEnd); + pos_ = itNameEnd; return denormalize(name); } @@ -366,34 +363,34 @@ public: std::string extractElementValue() { - auto it = std::find_if(pos, stream_.end(), [](char c) + auto it = std::find_if(pos_, stream_.end(), [](char c) { return c == '<' || c == '>'; }); - std::string output(pos, it); - pos = it; + std::string output(pos_, it); + pos_ = it; return denormalize(output); } std::string extractAttributeValue() { - auto it = std::find_if(pos, stream_.end(), [](char c) + auto it = std::find_if(pos_, stream_.end(), [](char c) { return c == '<' || c == '>' || c == '\'' || - c == '\"'; + c == '"'; }); - std::string output(pos, it); - pos = it; + std::string output(pos_, it); + pos_ = it; return 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 + 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 } @@ -401,13 +398,13 @@ public: size_t posCol() const //current col beginning with 0 { //seek beginning of line - for (auto it = pos; it != stream_.begin(); ) + for (auto it = pos_; it != stream_.begin(); ) { --it; if (*it == '\r' || *it == '\n') - return pos - it - 1; + return pos_ - it - 1; } - return pos - stream_.begin(); + return pos_ - stream_.begin(); } private: @@ -416,13 +413,11 @@ private: bool startsWith(const std::string& prefix) const { - if (stream_.end() - pos < static_cast<ptrdiff_t>(prefix.size())) - return false; - return std::equal(prefix.begin(), prefix.end(), pos); + return zen::startsWith(StringRef<const char>(pos_, stream_.end()), prefix); } using TokenList = std::vector<std::pair<std::string, Token::Type>>; - const TokenList tokens + const TokenList tokens_ { { "<?xml", Token::TK_DECL_BEGIN }, { "?>", Token::TK_DECL_END }, @@ -435,11 +430,11 @@ private: { "\'", Token::TK_QUOTE }, }; - const std::string xmlCommentBegin = "<!--"; - const std::string xmlCommentEnd = "-->"; + const std::string xmlCommentBegin_ = "<!--"; + const std::string xmlCommentEnd_ = "-->"; const std::string stream_; - std::string::const_iterator pos; + std::string::const_iterator pos_; }; @@ -448,7 +443,7 @@ class XmlParser public: XmlParser(const std::string& stream) : scn_(stream), - tk_(scn_.nextToken()) {} + tk_(scn_.getNextToken()) {} //throw XmlParsingError XmlDoc parse() //throw XmlParsingError { @@ -457,19 +452,19 @@ public: //declaration (optional) if (token().type == Token::TK_DECL_BEGIN) { - nextToken(); + nextToken(); //throw XmlParsingError while (token().type == Token::TK_NAME) { std::string attribName = token().name; - nextToken(); + nextToken(); //throw XmlParsingError - consumeToken(Token::TK_EQUAL); - expectToken(Token::TK_QUOTE); + consumeToken(Token::TK_EQUAL); //throw XmlParsingError + expectToken (Token::TK_QUOTE); // std::string attribValue = scn_.extractAttributeValue(); - nextToken(); + nextToken(); //throw XmlParsingError - consumeToken(Token::TK_QUOTE); + consumeToken(Token::TK_QUOTE); //throw XmlParsingError if (attribName == "version") doc.setVersion(attribValue); @@ -478,7 +473,7 @@ public: else if (attribName == "standalone") doc.setStandalone(attribValue); } - consumeToken(Token::TK_DECL_END); + consumeToken(Token::TK_DECL_END); //throw XmlParsingError } XmlElement dummy; @@ -488,7 +483,7 @@ public: if (itPair.first != itPair.second) doc.root().swapSubtree(*itPair.first); - expectToken(Token::TK_END); + expectToken(Token::TK_END); //throw XmlParsingError return doc; } @@ -500,11 +495,11 @@ private: { while (token().type == Token::TK_LESS) { - nextToken(); + nextToken(); //throw XmlParsingError - expectToken(Token::TK_NAME); + expectToken(Token::TK_NAME); //throw XmlParsingError std::string elementName = token().name; - nextToken(); + nextToken(); //throw XmlParsingError XmlElement& newElement = parent.addChild(elementName); @@ -512,28 +507,28 @@ private: if (token().type == Token::TK_SLASH_GREATER) //empty element { - nextToken(); + nextToken(); //throw XmlParsingError continue; } - expectToken(Token::TK_GREATER); + expectToken(Token::TK_GREATER); //throw XmlParsingError std::string elementValue = scn_.extractElementValue(); - nextToken(); + nextToken(); //throw XmlParsingError //no support for mixed-mode content - if (token().type == Token::TK_LESS) //structured element + if (token().type == Token::TK_LESS) //structure-element parseChildElements(newElement); - else //value element + else //value-element newElement.setValue(elementValue); - consumeToken(Token::TK_LESS_SLASH); + consumeToken(Token::TK_LESS_SLASH); //throw XmlParsingError - if (token().type != Token::TK_NAME || - elementName != token().name) + expectToken(Token::TK_NAME); //throw XmlParsingError + if (token().name != elementName) throw XmlParsingError(scn_.posRow(), scn_.posCol()); - nextToken(); + nextToken(); //throw XmlParsingError - consumeToken(Token::TK_GREATER); + consumeToken(Token::TK_GREATER); //throw XmlParsingError } } @@ -542,26 +537,21 @@ private: while (token().type == Token::TK_NAME) { std::string attribName = token().name; - nextToken(); + nextToken(); //throw XmlParsingError - consumeToken(Token::TK_EQUAL); - expectToken(Token::TK_QUOTE); + consumeToken(Token::TK_EQUAL); //throw XmlParsingError + expectToken (Token::TK_QUOTE); // std::string attribValue = scn_.extractAttributeValue(); - nextToken(); + nextToken(); //throw XmlParsingError - consumeToken(Token::TK_QUOTE); + consumeToken(Token::TK_QUOTE); //throw XmlParsingError 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 nextToken() { tk_ = scn_.getNextToken(); } //throw XmlParsingError void expectToken(Token::Type t) //throw XmlParsingError { @@ -569,15 +559,21 @@ private: throw XmlParsingError(scn_.posRow(), scn_.posCol()); } + void consumeToken(Token::Type t) //throw XmlParsingError + { + expectToken(t); //throw XmlParsingError + nextToken(); // + } + Scanner scn_; Token tk_; }; } inline -XmlDoc parse(const std::string& stream) //throw XmlParsingError +XmlDoc parseXml(const std::string& stream) //throw XmlParsingError { - return impl::XmlParser(stream).parse(); //throw XmlParsingError + return xml_impl::XmlParser(stream).parse(); //throw XmlParsingError } } diff --git a/zenXml/zenxml/xml.h b/zenXml/zenxml/xml.h index 9510645a..5eddc462 100755 --- a/zenXml/zenxml/xml.h +++ b/zenXml/zenxml/xml.h @@ -1,4 +1,4 @@ -// ***************************************************************************** +// ***************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * // * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * @@ -7,10 +7,444 @@ #ifndef XML_H_349578228034572457454554 #define XML_H_349578228034572457454554 -#include "bind.h" +#include <set> +#include <zen/file_io.h> +#include <zen/file_access.h> +#include "cvrt_struc.h" +#include "parser.h" /// The zen::Xml namespace -namespace zen {} +namespace zen +{ +/** +\file +\brief Save and load byte streams from files +*/ + +///Load XML document from a file +/** +Load and parse XML byte stream. Quick-exit if (potentially large) input file is not an XML. + +\param filePath Input file path +\returns The loaded XML document +\throw FileError +*/ +namespace +{ +XmlDoc loadXml(const Zstring& filePath) //throw FileError +{ + FileInput fileIn(filePath, nullptr /*notifyUnbufferedIO*/); //throw FileError, ErrorFileLocked + const size_t blockSize = fileIn.getBlockSize(); + const std::string xmlPrefix = "<?xml version="; + bool xmlPrefixChecked = false; + + std::string buffer; + for (;;) + { + buffer.resize(buffer.size() + blockSize); + const size_t bytesRead = fileIn.read(&*(buffer.end() - blockSize), blockSize); //throw FileError, ErrorFileLocked, (X); return "bytesToRead" bytes unless end of stream! + buffer.resize(buffer.size() - blockSize + bytesRead); //caveat: unsigned arithmetics + + //quick test whether input is an XML: avoid loading large binary files up front! + if (!xmlPrefixChecked && buffer.size() >= xmlPrefix.size() + strLength(BYTE_ORDER_MARK_UTF8)) + { + xmlPrefixChecked = true; + if (!startsWith(buffer, xmlPrefix) && + !startsWith(buffer, BYTE_ORDER_MARK_UTF8 + xmlPrefix)) //allow BOM! + throw FileError(replaceCpy(_("File %x does not contain a valid configuration."), L"%x", fmtPath(filePath))); + } + + if (bytesRead < blockSize) //end of file + break; + } + + try + { + return parseXml(buffer); //throw XmlParsingError + } + catch (const XmlParsingError& e) + { + throw FileError( + replaceCpy(replaceCpy(replaceCpy(_("Error parsing file %x, row %y, column %z."), + L"%x", fmtPath(filePath)), + L"%y", numberTo<std::wstring>(e.row + 1)), + L"%z", numberTo<std::wstring>(e.col + 1))); + } +} +} + + +///Save XML document to a file +/** +Serialize XML to byte stream and save to file. + +\param doc The XML document to save +\param filePath Output file path +\throw FileError +*/ +inline +void saveXml(const XmlDoc& doc, const Zstring& filePath) //throw FileError +{ + const std::string stream = serializeXml(doc); //noexcept + + try //only update XML file if there are changes + { + if (getFileSize(filePath) == stream.size()) //throw FileError + if (loadBinContainer<std::string>(filePath, nullptr /*notifyUnbufferedIO*/) == stream) //throw FileError + return; + } + catch (FileError&) {} + + saveBinContainer(filePath, stream, nullptr /*notifyUnbufferedIO*/); //throw FileError +} + + +///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"]( 1); // + out["elem2"]( 2); //write data into XML elements + out["elem3"](-3); // + + saveXml(doc, "out.xml"); //throw FileError + \endcode + Output: + \verbatim + <?xml version="1.0" encoding="utf-8"?> + <Root> + <elem1>1</elem1> + <elem2>2</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 = utfTo<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", 1); // + out["elem"].attribute("attr2", 2); //write data into XML attributes + out["elem"].attribute("attr3", -3); // + + saveXml(doc, "out.xml"); //throw FileError + \endcode + Output: + \verbatim + <?xml version="1.0" encoding="utf-8"?> + <Root> + <elem attr1="1" attr2="2" attr3="-3"/> + </Root> + \endverbatim + + \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 + \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; + +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); //read data from XML elements into variables "value1", "value2", "value3" + in["elem3"](value3); // + \endcode + */ + XmlIn(const XmlDoc& doc) { 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) { refList_.push_back(element); } + ///Construct an input proxy for a single XML element + /** + \sa XmlIn(const XmlDoc& doc) + */ + XmlIn(const XmlElement& element) { 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 only later if a conversion to user data is attempted. + \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); //read data from 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 String-convertible user data type: e.g. any string-like type, all built-in arithmetic numbers + \returns "true" if the attribute was found and the conversion to the output value was successful. + \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(), utfTo<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 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) + */ + explicit operator bool() const { return get() != 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); //let's assume this conversion failed + + assert(in.haveErrors() == inItem.haveErrors()); + 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 haveErrors() 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 utfTo<String>(str); }); + return output; + } + +private: + XmlIn(const std::vector<const XmlElement*>& siblingList, const std::string& elementNameFmt, const std::shared_ptr<ErrorLog>& sharedlog) : + refList_(siblingList), 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& 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 (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_ = 0; //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_{ std::make_shared<ErrorLog>() }; //always bound +}; + + +///Check XML input proxy for errors and map to FileError exception +/** +\param xmlInput XML input proxy +\param filePath Input file path +\throw FileError +*/ +inline +void checkXmlMappingErrors(const XmlIn& xmlInput, const Zstring& filePath) //throw FileError +{ + if (xmlInput.haveErrors()) + { + std::wstring msg = _("The following XML elements could not be read:") + L"\n"; + for (const std::wstring& elem : xmlInput.getErrorsAs<std::wstring>()) + msg += L"\n" + elem; + + throw FileError(replaceCpy(_("Configuration file %x is incomplete. The missing elements will be set to their default values."), L"%x", fmtPath(filePath)) + L"\n\n" + msg); + } +} +} #endif //XML_H_349578228034572457454554 |