summaryrefslogtreecommitdiff
path: root/zenXml
diff options
context:
space:
mode:
authorB Stack <bgstack15@gmail.com>2018-10-17 02:11:26 +0000
committerB Stack <bgstack15@gmail.com>2018-10-17 02:11:26 +0000
commitf70f8f961ef8f4d909266f71310e3515f25928e6 (patch)
tree89b2a018482c164bdd8ecac5c76b19a08f420dec /zenXml
parentMerge branch '10.4' into 'master' (diff)
parent10.5 (diff)
downloadFreeFileSync-f70f8f961ef8f4d909266f71310e3515f25928e6.tar.gz
FreeFileSync-f70f8f961ef8f4d909266f71310e3515f25928e6.tar.bz2
FreeFileSync-f70f8f961ef8f4d909266f71310e3515f25928e6.zip
Merge branch '10.5' into 'master'10.5
10.5 See merge request opensource-tracking/FreeFileSync!2
Diffstat (limited to 'zenXml')
-rwxr-xr-xzenXml/zenxml/cvrt_text.h6
-rwxr-xr-xzenXml/zenxml/parser.h196
-rwxr-xr-xzenXml/zenxml/xml.h440
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 += "&apos;";
- else if (c == '\"')
+ else if (c == '"')
output += "&quot;";
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(), "&apos;"))
output += '\'';
else if (checkEntity(it, str.end(), "&quot;"))
- 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
bgstack15