OGS
ConfigTree.cpp
Go to the documentation of this file.
1
11#include "ConfigTree.h"
12
13#include <cstddef>
14#include <forward_list>
15#include <utility>
16
17#include "Error.h"
18#include "Logging.h"
19
20// Explicitly instantiate the boost::property_tree::ptree which is a typedef to
21// the following basic_ptree.
22template class boost::property_tree::basic_ptree<std::string, std::string,
23 std::less<>>;
24
27static std::forward_list<std::string> configtree_destructor_error_messages;
28
29namespace BaseLib
30{
31const char ConfigTree::pathseparator = '/';
32const std::string ConfigTree::key_chars_start = "abcdefghijklmnopqrstuvwxyz";
33const std::string ConfigTree::key_chars = key_chars_start + "_0123456789";
34
36 std::string filename,
37 Callback error_cb,
38 Callback warning_cb)
39 : top_level_tree_(std::make_shared<PTree>(std::move(top_level_tree))),
40 tree_(top_level_tree_.get()),
41 filename_(std::move(filename)),
42 onerror_(std::move(error_cb)),
43 onwarning_(std::move(warning_cb))
44{
45 if (!onerror_)
46 {
47 OGS_FATAL("ConfigTree: No valid error handler provided.");
48 }
49 if (!onwarning_)
50 {
51 OGS_FATAL("ConfigTree: No valid warning handler provided.");
52 }
53}
54
55ConfigTree::ConfigTree(PTree const& tree, ConfigTree const& parent,
56 std::string const& root)
57 : top_level_tree_(parent.top_level_tree_),
58 tree_(&tree),
59 path_(joinPaths(parent.path_, root)),
60 filename_(parent.filename_),
61 onerror_(parent.onerror_),
62 onwarning_(parent.onwarning_)
63{
64 checkKeyname(root);
65}
66
68 : top_level_tree_(std::move(other.top_level_tree_)),
69 tree_(other.tree_),
70 path_(std::move(other.path_)),
71 filename_(std::move(other.filename_)),
72 visited_params_(std::move(other.visited_params_)),
73 have_read_data_(other.have_read_data_),
74 onerror_(std::move(other.onerror_)),
75 onwarning_(std::move(other.onwarning_))
76{
77 other.tree_ = nullptr;
78}
79
81{
82 if (std::uncaught_exceptions() > 0)
83 {
84 /* If the stack unwinds the check below shall be suppressed in order to
85 * not accumulate false-positive configuration errors.
86 */
87 return;
88 }
89
90 try
91 {
93 }
94 catch (std::exception& e)
95 {
96 ERR("{:s}", e.what());
97 configtree_destructor_error_messages.push_front(e.what());
98 }
99}
100
102{
104
105 top_level_tree_ = std::move(other.top_level_tree_);
106 tree_ = other.tree_;
107 other.tree_ = nullptr;
108 path_ = std::move(other.path_);
109 filename_ = std::move(other.filename_);
110 visited_params_ = std::move(other.visited_params_);
111 have_read_data_ = other.have_read_data_;
112 onerror_ = std::move(other.onerror_);
113 onwarning_ = std::move(other.onwarning_);
114
115 return *this;
116}
117
118ConfigTree ConfigTree::getConfigParameter(std::string const& param) const
119{
120 auto ct = getConfigSubtree(param);
121 if (ct.hasChildren())
122 {
123 error("Requested parameter <" + param + "> actually is a subtree.");
124 }
125 return ct;
126}
127
129 std::string const& param) const
130{
131 auto ct = getConfigSubtreeOptional(param);
132 if (ct && ct->hasChildren())
133 {
134 error("Requested parameter <" + param + "> actually is a subtree.");
135 }
136 return ct;
137}
138
140 const std::string& param) const
141{
142 checkUnique(param);
143 markVisited(param, Attr::TAG, true);
144
145 auto p = tree_->equal_range(param);
146
147 return Range<ParameterIterator>(ParameterIterator(p.first, param, *this),
148 ParameterIterator(p.second, param, *this));
149}
150
151void ConfigTree::checkConfigParameter(std::string const& param,
152 std::string_view const value) const
153{
154 auto const parameter_value = getConfigParameter<std::string>(param);
155 if (parameter_value != value)
156 {
157 error("For the tag <" + param + "> expected to read value '" +
158 value.data() + "', but got '" + parameter_value + "'.");
159 }
160}
161
162ConfigTree ConfigTree::getConfigSubtree(std::string const& root) const
163{
164 if (auto t = getConfigSubtreeOptional(root))
165 {
166 return std::move(*t);
167 }
168 error("Key <" + root + "> has not been found.");
169}
170
171std::optional<ConfigTree> ConfigTree::getConfigSubtreeOptional(
172 std::string const& root) const
173{
174 checkUnique(root);
175
176 if (auto subtree = tree_->get_child_optional(root))
177 {
178 markVisited(root, Attr::TAG, false);
179 return ConfigTree(*subtree, *this, root);
180 }
181 markVisited(root, Attr::TAG, true);
182 return std::nullopt;
183}
184
186 std::string const& root) const
187{
188 checkUnique(root);
189 markVisited(root, Attr::TAG, true);
190
191 auto p = tree_->equal_range(root);
192
193 return Range<SubtreeIterator>(SubtreeIterator(p.first, root, *this),
194 SubtreeIterator(p.second, root, *this));
195}
196
197void ConfigTree::ignoreConfigParameter(const std::string& param) const
198{
199 checkUnique(param);
200 // if not found, peek only
201 bool peek_only = tree_->find(param) == tree_->not_found();
202 markVisited(param, Attr::TAG, peek_only);
203}
204
205void ConfigTree::ignoreConfigAttribute(const std::string& attr) const
206{
207 checkUniqueAttr(attr);
208
209 // Exercise: Guess what not! (hint: if not found, peek only)
210 // Btw. (not a hint) tree_->find() does not seem to work here.
211 bool peek_only = !tree_->get_child_optional("<xmlattr>." + attr);
212
213 markVisited(attr, Attr::ATTR, peek_only);
214}
215
216void ConfigTree::ignoreConfigParameterAll(const std::string& param) const
217{
218 checkUnique(param);
219 auto& ct = markVisited(param, Attr::TAG, true);
220
221 auto p = tree_->equal_range(param);
222 for (auto it = p.first; it != p.second; ++it)
223 {
224 ++ct.count;
225 }
226}
227
228void ConfigTree::error(const std::string& message) const
229{
230 onerror_(filename_, path_, message);
231 OGS_FATAL(
232 "ConfigTree: The error handler does not break out of the normal "
233 "control flow.");
234}
235
236void ConfigTree::warning(const std::string& message) const
237{
238 onwarning_(filename_, path_, message);
239}
240
241void ConfigTree::onerror(const std::string& filename, const std::string& path,
242 const std::string& message)
243{
244 OGS_FATAL("ConfigTree: In file `{:s}' at path <{:s}>: {:s}", filename, path,
245 message);
246}
247
248void ConfigTree::onwarning(const std::string& filename, const std::string& path,
249 const std::string& message)
250{
251 WARN("ConfigTree: In file `{:s}' at path <{:s}>: {:s}", filename, path,
252 message);
253}
254
256{
258 {
259 return;
260 }
261
262 ERR("ConfigTree: There have been errors when parsing the configuration "
263 "file(s):");
264
265 for (auto const& msg : configtree_destructor_error_messages)
266 {
267 ERR("{:s}", msg);
268 }
269
270 OGS_FATAL("There have been errors when parsing the configuration file(s).");
271}
272
273std::string ConfigTree::shortString(const std::string& s)
274{
275 const std::size_t maxlen = 100;
276
277 if (s.size() < maxlen)
278 {
279 return s;
280 }
281
282 return s.substr(0, maxlen - 3) + "...";
283}
284
285void ConfigTree::checkKeyname(std::string const& key) const
286{
287 if (key.empty())
288 {
289 error("Search for empty key.");
290 }
291 else if (key_chars_start.find(key.front()) == std::string::npos)
292 {
293 error("Key <" + key + "> starts with an illegal character.");
294 }
295 else if (key.find_first_not_of(key_chars, 1) != std::string::npos)
296 {
297 error("Key <" + key + "> contains illegal characters.");
298 }
299 else if (key.find("__") != std::string::npos)
300 {
301 // This is illegal because we use parameter names to generate doxygen
302 // page names. Thereby "__" acts as a separator character. Choosing
303 // other separators is not possible because of observed limitations
304 // for valid doxygen page names.
305 error("Key <" + key + "> contains double underscore.");
306 }
307}
308
309std::string ConfigTree::joinPaths(const std::string& p1,
310 const std::string& p2) const
311{
312 if (p2.empty())
313 {
314 error("Second path to be joined is empty.");
315 }
316
317 if (p1.empty())
318 {
319 return p2;
320 }
321
322 return p1 + pathseparator + p2;
323}
324
325void ConfigTree::checkUnique(const std::string& key) const
326{
327 checkKeyname(key);
328
329 if (visited_params_.find({Attr::TAG, key}) != visited_params_.end())
330 {
331 error("Key <" + key + "> has already been processed.");
332 }
333}
334
335void ConfigTree::checkUniqueAttr(const std::string& attr) const
336{
337 // Workaround for handling attributes with xml namespaces and uppercase
338 // letters.
339 if (attr.find(':') != std::string::npos)
340 {
341 auto pos = decltype(std::string::npos){0};
342
343 // Replace colon and uppercase letters with an allowed character 'a'.
344 // That means, attributes containing a colon are also allowed to contain
345 // uppercase letters.
346 auto attr2 = attr;
347 do
348 {
349 pos = attr2.find_first_of(":ABCDEFGHIJKLMNOPQRSTUVWXYZ", pos);
350 if (pos != std::string::npos)
351 {
352 attr2[pos] = 'a';
353 }
354 } while (pos != std::string::npos);
355
356 checkKeyname(attr2);
357 }
358 else
359 {
360 checkKeyname(attr);
361 }
362
363 if (visited_params_.find({Attr::ATTR, attr}) != visited_params_.end())
364 {
365 error("Attribute '" + attr + "' has already been processed.");
366 }
367}
368
370 Attr const is_attr,
371 bool const peek_only) const
372{
373 return markVisited<ConfigTree>(key, is_attr, peek_only);
374}
375
377 std::string const& key) const
378{
379 auto const type = std::type_index(typeid(nullptr));
380
381 auto p = visited_params_.emplace(std::make_pair(is_attr, key),
382 CountType{-1, type});
383
384 if (!p.second)
385 { // no insertion happened
386 auto& v = p.first->second;
387 --v.count;
388 }
389}
390
392{
393 auto const& tree = *tree_;
394 if (tree.begin() == tree.end())
395 {
396 return false; // no children
397 }
398 if (tree.front().first == "<xmlattr>" && (++tree.begin()) == tree.end())
399 {
400 return false; // only attributes
401 }
402
403 return true;
404}
405
407{
408 if (!tree_)
409 {
410 return;
411 }
412
413 // Note: due to a limitation in boost::property_tree it is not possible
414 // to discriminate between <tag></tag> and <tag/> in the input file.
415 // In both cases data() will be empty.
416 if ((!have_read_data_) && !tree_->data().empty())
417 {
418 warning("The immediate data `" + shortString(tree_->data()) +
419 "' of this tag has not been read.");
420 }
421
422 // iterate over children
423 for (auto const& p : *tree_)
424 {
425 if (p.first != "<xmlattr>")
426 { // attributes are handled below
428 }
429 }
430
431 // iterate over attributes
432 if (auto attrs = tree_->get_child_optional("<xmlattr>"))
433 {
434 for (auto const& p : *attrs)
435 {
437 }
438 }
439
440 for (auto const& p : visited_params_)
441 {
442 auto const& tag = p.first.second;
443 auto const& count = p.second.count;
444
445 switch (p.first.first)
446 {
447 case Attr::ATTR:
448 if (count > 0)
449 {
450 warning("XML attribute '" + tag + "' has been read " +
451 std::to_string(count) +
452 " time(s) more than it was present in the "
453 "configuration tree.");
454 }
455 else if (count < 0)
456 {
457 warning("XML attribute '" + tag + "' has been read " +
458 std::to_string(-count) +
459 " time(s) less than it was present in the "
460 "configuration tree.");
461 }
462 break;
463 case Attr::TAG:
464 if (count > 0)
465 {
466 warning("Key <" + tag + "> has been read " +
467 std::to_string(count) +
468 " time(s) more than it was present in the "
469 "configuration tree.");
470 }
471 else if (count < 0)
472 {
473 warning("Key <" + tag + "> has been read " +
474 std::to_string(-count) +
475 " time(s) less than it was present in the "
476 "configuration tree.");
477 }
478 }
479 }
480
481 // The following invalidates this instance, s.t. it can not be read from it
482 // anymore, but it also prevents double-checking.
483 tree_ = nullptr;
484}
485
487{
488 conf.checkAndInvalidate();
489}
490
492{
493 if (conf)
494 {
495 conf->checkAndInvalidate();
496 }
497}
498
499void checkAndInvalidate(std::unique_ptr<ConfigTree> const& conf)
500{
501 if (conf)
502 {
503 conf->checkAndInvalidate();
504 }
505}
506
507} // namespace BaseLib
static std::forward_list< std::string > configtree_destructor_error_messages
#define OGS_FATAL(...)
Definition Error.h:26
void ERR(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:45
void WARN(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:40
void markVisitedDecrement(Attr const is_attr, std::string const &key) const
void ignoreConfigParameter(std::string const &param) const
static void onerror(std::string const &filename, std::string const &path, std::string const &message)
static void assertNoSwallowedErrors()
Asserts that there have not been any errors reported in the destructor.
Callback onwarning_
Custom warning callback.
Definition ConfigTree.h:673
static const char pathseparator
Character separating two path components.
Definition ConfigTree.h:676
void checkUniqueAttr(std::string const &attr) const
Asserts that the attribute attr has not been read yet.
std::map< KeyType, CountType > visited_params_
Definition ConfigTree.h:666
static std::string shortString(std::string const &s)
returns a short string at suitable for error/warning messages
std::shared_ptr< PTree const > top_level_tree_
Definition ConfigTree.h:645
void error(std::string const &message) const
std::optional< ConfigTree > getConfigSubtreeOptional(std::string const &root) const
std::optional< T > getConfigParameterOptional(std::string const &param) const
void ignoreConfigAttribute(std::string const &attr) const
void checkUnique(std::string const &key) const
Asserts that the key has not been read yet.
T getConfigParameter(std::string const &param) const
Attr
Used to indicate if dealing with XML tags or XML attributes.
Definition ConfigTree.h:572
Range< SubtreeIterator > getConfigSubtreeList(std::string const &root) const
static const std::string key_chars_start
Set of allowed characters as the first letter of a key name.
Definition ConfigTree.h:679
ConfigTree getConfigSubtree(std::string const &root) const
std::string path_
A path printed in error/warning messages.
Definition ConfigTree.h:651
CountType & markVisited(std::string const &key, Attr const is_attr, bool peek_only) const
void ignoreConfigParameterAll(std::string const &param) const
Callback onerror_
Custom error callback.
Definition ConfigTree.h:672
std::string joinPaths(std::string const &p1, std::string const &p2) const
Used to generate the path of a subtree.
Range< ValueIterator< T > > getConfigParameterList(std::string const &param) const
void warning(std::string const &message) const
bool hasChildren() const
Checks if this tree has any children.
PTree const * tree_
The wrapped tree.
Definition ConfigTree.h:648
ConfigTree(PTree &&top_level_tree, std::string filename, Callback error_cb, Callback warning_cb)
void checkConfigParameter(std::string const &param, std::string_view const value) const
boost::property_tree::ptree PTree
The tree being wrapped by this class.
Definition ConfigTree.h:255
ConfigTree & operator=(ConfigTree const &)=delete
copying is not compatible with the semantics of this class
std::string filename_
The path of the file from which this tree has been read.
Definition ConfigTree.h:654
void checkKeyname(std::string const &key) const
Checks if key complies with the rules [a-z0-9_].
std::function< void(const std::string &filename, const std::string &path, const std::string &message)> Callback
Definition ConfigTree.h:264
static void onwarning(std::string const &filename, std::string const &path, std::string const &message)
static const std::string key_chars
Set of allowed characters in a key name.
Definition ConfigTree.h:682
Wraps a pair of iterators for use as a range in range-based for-loops.
void checkAndInvalidate(ConfigTree &conf)
This is an overloaded member function, provided for convenience. It differs from the above function o...
std::string joinPaths(std::string const &pathA, std::string const &pathB)