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& root) const
119{
120 auto ct = getConfigSubtree(root);
121 if (ct.hasChildren())
122 {
123 error("Requested parameter <" + root + "> actually is a subtree.");
124 }
125 return ct;
126}
127
129 std::string const& root) const
130{
131 auto ct = getConfigSubtreeOptional(root);
132 if (ct && ct->hasChildren())
133 {
134 error("Requested parameter <" + root + "> 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
151ConfigTree ConfigTree::getConfigSubtree(std::string const& root) const
152{
153 if (auto t = getConfigSubtreeOptional(root))
154 {
155 return std::move(*t);
156 }
157 error("Key <" + root + "> has not been found.");
158}
159
160std::optional<ConfigTree> ConfigTree::getConfigSubtreeOptional(
161 std::string const& root) const
162{
163 checkUnique(root);
164
165 if (auto subtree = tree_->get_child_optional(root))
166 {
167 markVisited(root, Attr::TAG, false);
168 return ConfigTree(*subtree, *this, root);
169 }
170 markVisited(root, Attr::TAG, true);
171 return std::nullopt;
172}
173
175 std::string const& root) const
176{
177 checkUnique(root);
178 markVisited(root, Attr::TAG, true);
179
180 auto p = tree_->equal_range(root);
181
182 return Range<SubtreeIterator>(SubtreeIterator(p.first, root, *this),
183 SubtreeIterator(p.second, root, *this));
184}
185
186void ConfigTree::ignoreConfigParameter(const std::string& param) const
187{
188 checkUnique(param);
189 // if not found, peek only
190 bool peek_only = tree_->find(param) == tree_->not_found();
191 markVisited(param, Attr::TAG, peek_only);
192}
193
194void ConfigTree::ignoreConfigAttribute(const std::string& attr) const
195{
196 checkUniqueAttr(attr);
197
198 // Exercise: Guess what not! (hint: if not found, peek only)
199 // Btw. (not a hint) tree_->find() does not seem to work here.
200 bool peek_only = !tree_->get_child_optional("<xmlattr>." + attr);
201
202 markVisited(attr, Attr::ATTR, peek_only);
203}
204
205void ConfigTree::ignoreConfigParameterAll(const std::string& param) const
206{
207 checkUnique(param);
208 auto& ct = markVisited(param, Attr::TAG, true);
209
210 auto p = tree_->equal_range(param);
211 for (auto it = p.first; it != p.second; ++it)
212 {
213 ++ct.count;
214 }
215}
216
217void ConfigTree::error(const std::string& message) const
218{
219 onerror_(filename_, path_, message);
220 OGS_FATAL(
221 "ConfigTree: The error handler does not break out of the normal "
222 "control flow.");
223}
224
225void ConfigTree::warning(const std::string& message) const
226{
227 onwarning_(filename_, path_, message);
228}
229
230void ConfigTree::onerror(const std::string& filename, const std::string& path,
231 const std::string& message)
232{
233 OGS_FATAL("ConfigTree: In file `{:s}' at path <{:s}>: {:s}", filename, path,
234 message);
235}
236
237void ConfigTree::onwarning(const std::string& filename, const std::string& path,
238 const std::string& message)
239{
240 WARN("ConfigTree: In file `{:s}' at path <{:s}>: {:s}", filename, path,
241 message);
242}
243
245{
247 {
248 return;
249 }
250
251 ERR("ConfigTree: There have been errors when parsing the configuration "
252 "file(s):");
253
254 for (auto const& msg : configtree_destructor_error_messages)
255 {
256 ERR("{:s}", msg);
257 }
258
259 OGS_FATAL("There have been errors when parsing the configuration file(s).");
260}
261
262std::string ConfigTree::shortString(const std::string& s)
263{
264 const std::size_t maxlen = 100;
265
266 if (s.size() < maxlen)
267 {
268 return s;
269 }
270
271 return s.substr(0, maxlen - 3) + "...";
272}
273
274void ConfigTree::checkKeyname(std::string const& key) const
275{
276 if (key.empty())
277 {
278 error("Search for empty key.");
279 }
280 else if (key_chars_start.find(key.front()) == std::string::npos)
281 {
282 error("Key <" + key + "> starts with an illegal character.");
283 }
284 else if (key.find_first_not_of(key_chars, 1) != std::string::npos)
285 {
286 error("Key <" + key + "> contains illegal characters.");
287 }
288 else if (key.find("__") != std::string::npos)
289 {
290 // This is illegal because we use parameter names to generate doxygen
291 // page names. Thereby "__" acts as a separator character. Choosing
292 // other separators is not possible because of observed limitations
293 // for valid doxygen page names.
294 error("Key <" + key + "> contains double underscore.");
295 }
296}
297
298std::string ConfigTree::joinPaths(const std::string& p1,
299 const std::string& p2) const
300{
301 if (p2.empty())
302 {
303 error("Second path to be joined is empty.");
304 }
305
306 if (p1.empty())
307 {
308 return p2;
309 }
310
311 return p1 + pathseparator + p2;
312}
313
314void ConfigTree::checkUnique(const std::string& key) const
315{
316 checkKeyname(key);
317
318 if (visited_params_.find({Attr::TAG, key}) != visited_params_.end())
319 {
320 error("Key <" + key + "> has already been processed.");
321 }
322}
323
324void ConfigTree::checkUniqueAttr(const std::string& attr) const
325{
326 // Workaround for handling attributes with xml namespaces and uppercase
327 // letters.
328 if (attr.find(':') != std::string::npos)
329 {
330 auto pos = decltype(std::string::npos){0};
331
332 // Replace colon and uppercase letters with an allowed character 'a'.
333 // That means, attributes containing a colon are also allowed to contain
334 // uppercase letters.
335 auto attr2 = attr;
336 do
337 {
338 pos = attr2.find_first_of(":ABCDEFGHIJKLMNOPQRSTUVWXYZ", pos);
339 if (pos != std::string::npos)
340 {
341 attr2[pos] = 'a';
342 }
343 } while (pos != std::string::npos);
344
345 checkKeyname(attr2);
346 }
347 else
348 {
349 checkKeyname(attr);
350 }
351
352 if (visited_params_.find({Attr::ATTR, attr}) != visited_params_.end())
353 {
354 error("Attribute '" + attr + "' has already been processed.");
355 }
356}
357
359 Attr const is_attr,
360 bool const peek_only) const
361{
362 return markVisited<ConfigTree>(key, is_attr, peek_only);
363}
364
366 std::string const& key) const
367{
368 auto const type = std::type_index(typeid(nullptr));
369
370 auto p = visited_params_.emplace(std::make_pair(is_attr, key),
371 CountType{-1, type});
372
373 if (!p.second)
374 { // no insertion happened
375 auto& v = p.first->second;
376 --v.count;
377 }
378}
379
381{
382 auto const& tree = *tree_;
383 if (tree.begin() == tree.end())
384 {
385 return false; // no children
386 }
387 if (tree.front().first == "<xmlattr>" && (++tree.begin()) == tree.end())
388 {
389 return false; // only attributes
390 }
391
392 return true;
393}
394
396{
397 if (!tree_)
398 {
399 return;
400 }
401
402 // Note: due to a limitation in boost::property_tree it is not possible
403 // to discriminate between <tag></tag> and <tag/> in the input file.
404 // In both cases data() will be empty.
405 if ((!have_read_data_) && !tree_->data().empty())
406 {
407 warning("The immediate data `" + shortString(tree_->data()) +
408 "' of this tag has not been read.");
409 }
410
411 // iterate over children
412 for (auto const& p : *tree_)
413 {
414 if (p.first != "<xmlattr>")
415 { // attributes are handled below
417 }
418 }
419
420 // iterate over attributes
421 if (auto attrs = tree_->get_child_optional("<xmlattr>"))
422 {
423 for (auto const& p : *attrs)
424 {
426 }
427 }
428
429 for (auto const& p : visited_params_)
430 {
431 auto const& tag = p.first.second;
432 auto const& count = p.second.count;
433
434 switch (p.first.first)
435 {
436 case Attr::ATTR:
437 if (count > 0)
438 {
439 warning("XML attribute '" + tag + "' has been read " +
440 std::to_string(count) +
441 " time(s) more than it was present in the "
442 "configuration tree.");
443 }
444 else if (count < 0)
445 {
446 warning("XML attribute '" + tag + "' has been read " +
447 std::to_string(-count) +
448 " time(s) less than it was present in the "
449 "configuration tree.");
450 }
451 break;
452 case Attr::TAG:
453 if (count > 0)
454 {
455 warning("Key <" + tag + "> has been read " +
456 std::to_string(count) +
457 " time(s) more than it was present in the "
458 "configuration tree.");
459 }
460 else if (count < 0)
461 {
462 warning("Key <" + tag + "> has been read " +
463 std::to_string(-count) +
464 " time(s) less than it was present in the "
465 "configuration tree.");
466 }
467 }
468 }
469
470 // The following invalidates this instance, s.t. it can not be read from it
471 // anymore, but it also prevents double-checking.
472 tree_ = nullptr;
473}
474
476{
477 conf.checkAndInvalidate();
478}
479
481{
482 if (conf)
483 {
484 conf->checkAndInvalidate();
485 }
486}
487
488void checkAndInvalidate(std::unique_ptr<ConfigTree> const& conf)
489{
490 if (conf)
491 {
492 conf->checkAndInvalidate();
493 }
494}
495
496} // namespace BaseLib
static std::forward_list< std::string > configtree_destructor_error_messages
Definition: ConfigTree.cpp:27
#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
Definition: ConfigTree.cpp:365
void ignoreConfigParameter(std::string const &param) const
Definition: ConfigTree.cpp:186
static void onerror(std::string const &filename, std::string const &path, std::string const &message)
Definition: ConfigTree.cpp:230
static void assertNoSwallowedErrors()
Asserts that there have not been any errors reported in the destructor.
Definition: ConfigTree.cpp:244
Callback onwarning_
Custom warning callback.
Definition: ConfigTree.h:675
static const char pathseparator
Character separating two path components.
Definition: ConfigTree.h:678
void checkUniqueAttr(std::string const &attr) const
Asserts that the attribute attr has not been read yet.
Definition: ConfigTree.cpp:324
std::map< KeyType, CountType > visited_params_
Definition: ConfigTree.h:668
static std::string shortString(std::string const &s)
returns a short string at suitable for error/warning messages
Definition: ConfigTree.cpp:262
std::shared_ptr< PTree const > top_level_tree_
Definition: ConfigTree.h:647
void error(std::string const &message) const
Definition: ConfigTree.cpp:217
std::optional< ConfigTree > getConfigSubtreeOptional(std::string const &root) const
Definition: ConfigTree.cpp:160
std::optional< T > getConfigParameterOptional(std::string const &param) const
void ignoreConfigAttribute(std::string const &attr) const
Definition: ConfigTree.cpp:194
void checkUnique(std::string const &key) const
Asserts that the key has not been read yet.
Definition: ConfigTree.cpp:314
T getConfigParameter(std::string const &param) const
Attr
Used to indicate if dealing with XML tags or XML attributes.
Definition: ConfigTree.h:574
Range< SubtreeIterator > getConfigSubtreeList(std::string const &root) const
Definition: ConfigTree.cpp:174
static const std::string key_chars_start
Set of allowed characters as the first letter of a key name.
Definition: ConfigTree.h:681
std::function< void(const std::string &filename, const std::string &path, const std::string &message)> Callback
Definition: ConfigTree.h:264
ConfigTree getConfigSubtree(std::string const &root) const
Definition: ConfigTree.cpp:151
std::string path_
A path printed in error/warning messages.
Definition: ConfigTree.h:653
CountType & markVisited(std::string const &key, Attr const is_attr, bool peek_only) const
void ignoreConfigParameterAll(std::string const &param) const
Definition: ConfigTree.cpp:205
Callback onerror_
Custom error callback.
Definition: ConfigTree.h:674
std::string joinPaths(std::string const &p1, std::string const &p2) const
Used to generate the path of a subtree.
Definition: ConfigTree.cpp:298
Range< ValueIterator< T > > getConfigParameterList(std::string const &param) const
void warning(std::string const &message) const
Definition: ConfigTree.cpp:225
bool hasChildren() const
Checks if this tree has any children.
Definition: ConfigTree.cpp:380
PTree const * tree_
The wrapped tree.
Definition: ConfigTree.h:650
ConfigTree(PTree &&top_level_tree, std::string filename, Callback error_cb, Callback warning_cb)
Definition: ConfigTree.cpp:35
boost::property_tree::ptree PTree
The tree being wrapped by this class.
Definition: ConfigTree.h:253
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:656
void checkKeyname(std::string const &key) const
Checks if key complies with the rules [a-z0-9_].
Definition: ConfigTree.cpp:274
static void onwarning(std::string const &filename, std::string const &path, std::string const &message)
Definition: ConfigTree.cpp:237
static const std::string key_chars
Set of allowed characters in a key name.
Definition: ConfigTree.h:684
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...
Definition: ConfigTree.cpp:475
std::string joinPaths(std::string const &pathA, std::string const &pathB)
Definition: FileTools.cpp:217