OGS
ConfigTree.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Copyright (c) OpenGeoSys Community (opengeosys.org)
2// SPDX-License-Identifier: BSD-3-Clause
3
4#include "ConfigTree.h"
5
6#include <cstddef>
7#include <forward_list>
8#include <utility>
9#include <vector>
10
11#include "Error.h"
12#include "Logging.h"
13
14// Explicitly instantiate the boost::property_tree::ptree which is a typedef to
15// the following basic_ptree.
16template class boost::property_tree::basic_ptree<std::string, std::string,
17 std::less<>>;
18
21static std::forward_list<std::string> configtree_destructor_error_messages;
22
23namespace BaseLib
24{
25const char ConfigTree::pathseparator = '/';
26const std::string ConfigTree::key_chars_start = "abcdefghijklmnopqrstuvwxyz";
27const std::string ConfigTree::key_chars = key_chars_start + "_0123456789";
28
30 std::string filename,
31 Callback error_cb,
32 Callback warning_cb)
33 : top_level_tree_(std::make_shared<PTree>(std::move(top_level_tree))),
35 onerror_(std::move(error_cb)),
36 onwarning_(std::move(warning_cb))
37{
38 if (!onerror_)
39 {
40 OGS_FATAL("ConfigTree: No valid error handler provided.");
41 }
42 if (!onwarning_)
43 {
44 OGS_FATAL("ConfigTree: No valid warning handler provided.");
45 }
46
47 std::filesystem::path const filepath{filename};
48 std::error_code ec;
49 auto absolute_filepath = absolute(filepath, ec);
50 if (ec)
51 {
52 DBUG(
53 "ConfigTree: could not get the absolute path of '{}'. Using the "
54 "path unmodified. Error code = {}",
55 filepath.string(), ec.value());
56 absolute_filepath = filepath;
57 }
58
59 // store absolute path s.t. we are safe if somebody changes the working
60 // directory
61 filepath_ = absolute_filepath;
62 DBUG("ConfigTree: file path is {}.", filepath_.string());
63
64 if (!exists(projectDirectory()))
65 {
66 DBUG("ConfigTree: The project directory '{}' does not exist.",
67 projectDirectory().string());
68 }
69}
70
71ConfigTree::ConfigTree(PTree const& tree, ConfigTree const& parent,
72 std::string const& root)
74 tree_(&tree),
75 path_(joinPaths(parent.path_, root)),
76 filepath_(parent.filepath_),
77 onerror_(parent.onerror_),
79{
80 checkKeyname(root);
81}
82
84 : top_level_tree_(std::move(other.top_level_tree_)),
85 tree_(other.tree_),
86 path_(std::move(other.path_)),
87 filepath_(std::move(other.filepath_)),
88 visited_params_(std::move(other.visited_params_)),
90 onerror_(std::move(other.onerror_)),
91 onwarning_(std::move(other.onwarning_))
92{
93 other.tree_ = nullptr;
94}
95
97{
98 if (std::uncaught_exceptions() > 0)
99 {
100 /* If the stack unwinds the check below shall be suppressed in order to
101 * not accumulate false-positive configuration errors.
102 */
103 return;
104 }
105
106 try
107 {
109 }
110 catch (std::exception& e)
111 {
112 ERR("{:s}", e.what());
113 configtree_destructor_error_messages.emplace_front(e.what());
114 }
115}
116
118{
120
121 top_level_tree_ = std::move(other.top_level_tree_);
122 tree_ = other.tree_;
123 other.tree_ = nullptr;
124 path_ = std::move(other.path_);
125 filepath_ = std::move(other.filepath_);
126 visited_params_ = std::move(other.visited_params_);
127 have_read_data_ = other.have_read_data_;
128 onerror_ = std::move(other.onerror_);
129 onwarning_ = std::move(other.onwarning_);
130
131 return *this;
132}
133
134ConfigTree ConfigTree::getConfigParameter(std::string const& param) const
135{
136 auto ct = getConfigSubtree(param);
137 if (ct.hasChildren())
138 {
139 error("Requested parameter <" + param + "> actually is a subtree.");
140 }
141 return ct;
142}
143
145 std::string const& param) const
146{
147 auto ct = getConfigSubtreeOptional(param);
148 if (ct && ct->hasChildren())
149 {
150 error("Requested parameter <" + param + "> actually is a subtree.");
151 }
152 return ct;
153}
154
156 const std::string& param) const
157{
158 checkUnique(param);
159 markVisited(param, Attr::TAG, true);
160
161 auto p = tree_->equal_range(param);
162
163 return Range<ParameterIterator>(ParameterIterator(p.first, param, *this),
164 ParameterIterator(p.second, param, *this));
165}
166
167void ConfigTree::checkConfigParameter(std::string const& param,
168 std::string_view const value) const
169{
170 auto const parameter_value = getConfigParameter<std::string>(param);
171 if (parameter_value != value)
172 {
173 error("For the tag <" + param + "> expected to read value '" +
174 value.data() + "', but got '" + parameter_value + "'.");
175 }
176}
177
178ConfigTree ConfigTree::getConfigSubtree(std::string const& root) const
179{
180 if (auto t = getConfigSubtreeOptional(root))
181 {
182 return std::move(*t);
183 }
184 error("Key <" + root + "> has not been found.");
185}
186
187std::optional<ConfigTree> ConfigTree::getConfigSubtreeOptional(
188 std::string const& root) const
189{
190 checkUnique(root);
191
192 if (auto subtree = tree_->get_child_optional(root))
193 {
194 markVisited(root, Attr::TAG, false);
195 return ConfigTree(*subtree, *this, root);
196 }
197 markVisited(root, Attr::TAG, true);
198 return std::nullopt;
199}
200
202 std::string const& root) const
203{
204 checkUnique(root);
205 markVisited(root, Attr::TAG, true);
206
207 auto p = tree_->equal_range(root);
208
209 return Range<SubtreeIterator>(SubtreeIterator(p.first, root, *this),
210 SubtreeIterator(p.second, root, *this));
211}
212
214{
215 Children children;
216
217 // Hand out every child as its own subtree, mirroring getConfigSubtree():
218 // mark each child tag as consumed in this (parent) tree so the parent does
219 // not warn about the tags, then return a ConfigTree per child. Each child
220 // validates its own content on destruction, so any child whose sub-children
221 // / attributes / immediate data are left unread warns exactly like an
222 // unread getConfigSubtree() result. <xmlattr> is skipped: attribute
223 // storage is not a child element. Duplicate tags are handled by
224 // markVisited(), which increments the consumed count once per occurrence.
225 for (auto const& [tag, subtree] : *tree_)
226 {
227 if (tag == "<xmlattr>")
228 {
229 continue;
230 }
231 markVisited(tag, Attr::TAG, false);
232 children.emplace_back(tag, Child{ConfigTree(subtree, *this, tag)});
233 }
234
235 return children;
236}
237
238void ConfigTree::ignoreConfigParameter(const std::string& param) const
239{
240 checkUnique(param);
241 // if not found, peek only
242 bool peek_only = tree_->find(param) == tree_->not_found();
243 markVisited(param, Attr::TAG, peek_only);
244}
245
246void ConfigTree::ignoreConfigAttribute(const std::string& attr) const
247{
248 checkUniqueAttr(attr);
249
250 // Exercise: Guess what not! (hint: if not found, peek only)
251 // Btw. (not a hint) tree_->find() does not seem to work here.
252 bool peek_only = !tree_->get_child_optional("<xmlattr>." + attr);
253
254 markVisited(attr, Attr::ATTR, peek_only);
255}
256
257void ConfigTree::ignoreConfigParameterAll(const std::string& param) const
258{
259 checkUnique(param);
260 auto& ct = markVisited(param, Attr::TAG, true);
261
262 auto p = tree_->equal_range(param);
263 for (auto it = p.first; it != p.second; ++it)
264 {
265 ++ct.count;
266 }
267}
268
269std::filesystem::path ConfigTree::projectDirectory() const
270{
271 return filepath_.parent_path();
272}
273
274void ConfigTree::error(const std::string& message) const
275{
276 onerror_(filepath_.string(), path_, message);
277 OGS_FATAL(
278 "ConfigTree: The error handler does not break out of the normal "
279 "control flow.");
280}
281
282void ConfigTree::warning(const std::string& message) const
283{
284 onwarning_(filepath_.string(), path_, message);
285}
286
287void ConfigTree::onerror(const std::string& filename, const std::string& path,
288 const std::string& message)
289{
290 OGS_FATAL("ConfigTree: In file `{:s}' at path <{:s}>: {:s}", filename, path,
291 message);
292}
293
294void ConfigTree::onwarning(const std::string& filename, const std::string& path,
295 const std::string& message)
296{
297 WARN("ConfigTree: In file `{:s}' at path <{:s}>: {:s}", filename, path,
298 message);
299}
300
302{
304 {
305 return;
306 }
307
308 ERR("ConfigTree: There have been errors when parsing the configuration "
309 "file(s):");
310
311 for (auto const& msg : configtree_destructor_error_messages)
312 {
313 ERR("{:s}", msg);
314 }
315
316 // Clear the error messages to avoid duplicate printing
318
319 OGS_FATAL("There have been errors when parsing the configuration file(s).");
320}
321
322std::string ConfigTree::shortString(const std::string& s)
323{
324 const std::size_t maxlen = 100;
325
326 if (s.size() < maxlen)
327 {
328 return s;
329 }
330
331 return s.substr(0, maxlen - 3) + "...";
332}
333
334void ConfigTree::checkKeyname(std::string const& key) const
335{
336 if (key.empty())
337 {
338 error("Search for empty key.");
339 }
340 else if (key_chars_start.find(key.front()) == std::string::npos)
341 {
342 error("Key <" + key + "> starts with an illegal character.");
343 }
344 else if (key.find_first_not_of(key_chars, 1) != std::string::npos)
345 {
346 error("Key <" + key + "> contains illegal characters.");
347 }
348 else if (key.find("__") != std::string::npos)
349 {
350 // This is illegal because we use parameter names to generate doxygen
351 // page names. Thereby "__" acts as a separator character. Choosing
352 // other separators is not possible because of observed limitations
353 // for valid doxygen page names.
354 error("Key <" + key + "> contains double underscore.");
355 }
356}
357
358std::string ConfigTree::joinPaths(const std::string& p1,
359 const std::string& p2) const
360{
361 if (p2.empty())
362 {
363 error("Second path to be joined is empty.");
364 }
365
366 if (p1.empty())
367 {
368 return p2;
369 }
370
371 return p1 + pathseparator + p2;
372}
373
374void ConfigTree::checkUnique(const std::string& key) const
375{
376 checkKeyname(key);
377
378 if (visited_params_.find({Attr::TAG, key}) != visited_params_.end())
379 {
380 error("Key <" + key + "> has already been processed.");
381 }
382}
383
384void ConfigTree::checkUniqueAttr(const std::string& attr) const
385{
386 // Workaround for handling attributes with xml namespaces and uppercase
387 // letters.
388 if (attr.find(':') != std::string::npos)
389 {
390 auto pos = decltype(std::string::npos){0};
391
392 // Replace colon and uppercase letters with an allowed character 'a'.
393 // That means, attributes containing a colon are also allowed to contain
394 // uppercase letters.
395 auto attr2 = attr;
396 do
397 {
398 pos = attr2.find_first_of(":ABCDEFGHIJKLMNOPQRSTUVWXYZ", pos);
399 if (pos != std::string::npos)
400 {
401 attr2[pos] = 'a';
402 }
403 } while (pos != std::string::npos);
404
405 checkKeyname(attr2);
406 }
407 else
408 {
409 checkKeyname(attr);
410 }
411
412 if (visited_params_.find({Attr::ATTR, attr}) != visited_params_.end())
413 {
414 error("Attribute '" + attr + "' has already been processed.");
415 }
416}
417
419 Attr const is_attr,
420 bool const peek_only) const
421{
422 return markVisited<ConfigTree>(key, is_attr, peek_only);
423}
424
426 std::string const& key) const
427{
428 auto const type = std::type_index(typeid(nullptr));
429
430 auto p = visited_params_.emplace(std::make_pair(is_attr, key),
431 CountType{-1, type});
432
433 if (!p.second)
434 { // no insertion happened
435 auto& v = p.first->second;
436 --v.count;
437 }
438}
439
441{
442 auto const& tree = *tree_;
443 if (tree.begin() == tree.end())
444 {
445 return false; // no children
446 }
447 if (tree.front().first == "<xmlattr>" && (++tree.begin()) == tree.end())
448 {
449 return false; // only attributes
450 }
451
452 return true;
453}
454
456{
457 if (!tree_)
458 {
459 return;
460 }
461
462 // Note: due to a limitation in boost::property_tree it is not possible
463 // to discriminate between <tag></tag> and <tag/> in the input file.
464 // In both cases data() will be empty.
465 if ((!have_read_data_) && !tree_->data().empty())
466 {
467 warning("The immediate data `" + shortString(tree_->data()) +
468 "' of this tag has not been read.");
469 }
470
471 // iterate over children
472 for (auto const& p : *tree_)
473 {
474 if (p.first != "<xmlattr>")
475 { // attributes are handled below
477 }
478 }
479
480 // iterate over attributes
481 if (auto attrs = tree_->get_child_optional("<xmlattr>"))
482 {
483 for (auto const& p : *attrs)
484 {
486 }
487 }
488
489 for (auto const& p : visited_params_)
490 {
491 auto const& tag = p.first.second;
492 auto const& count = p.second.count;
493
494 switch (p.first.first)
495 {
496 case Attr::ATTR:
497 if (count > 0)
498 {
499 warning("XML attribute '" + tag + "' has been read " +
500 std::to_string(count) +
501 " time(s) more than it was present in the "
502 "configuration tree.");
503 }
504 else if (count < 0)
505 {
506 warning("XML attribute '" + tag + "' has been read " +
507 std::to_string(-count) +
508 " time(s) less than it was present in the "
509 "configuration tree.");
510 }
511 break;
512 case Attr::TAG:
513 if (count > 0)
514 {
515 warning("Key <" + tag + "> has been read " +
516 std::to_string(count) +
517 " time(s) more than it was present in the "
518 "configuration tree.");
519 }
520 else if (count < 0)
521 {
522 warning("Key <" + tag + "> has been read " +
523 std::to_string(-count) +
524 " time(s) less than it was present in the "
525 "configuration tree.");
526 }
527 }
528 }
529
530 // The following invalidates this instance, s.t. it can not be read from it
531 // anymore, but it also prevents double-checking.
532 tree_ = nullptr;
533}
534
536{
537 conf.checkAndInvalidate();
538}
539
541{
542 if (conf)
543 {
544 conf->checkAndInvalidate();
545 }
546}
547
548void checkAndInvalidate(std::unique_ptr<ConfigTree> const& conf)
549{
550 if (conf)
551 {
552 conf->checkAndInvalidate();
553 }
554}
555
556} // namespace BaseLib
static std::forward_list< std::string > configtree_destructor_error_messages
#define OGS_FATAL(...)
Definition Error.h:10
void DBUG(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:22
void ERR(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:40
void WARN(fmt::format_string< Args... > fmt, Args &&... args)
Definition Logging.h:34
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:684
static const char pathseparator
Character separating two path components.
Definition ConfigTree.h:687
Children getAllChildren() const
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:677
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:656
void error(std::string const &message) const
std::optional< ConfigTree > getConfigSubtreeOptional(std::string const &root) const
std::filesystem::path projectDirectory() const
std::optional< T > getConfigParameterOptional(std::string const &param) const
void ignoreConfigAttribute(std::string const &attr) const
std::filesystem::path filepath_
The path of the file from which this tree has been read.
Definition ConfigTree.h:665
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:583
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:690
ConfigTree getConfigSubtree(std::string const &root) const
std::string path_
A path printed in error/warning messages.
Definition ConfigTree.h:662
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:683
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.
std::vector< std::pair< std::string, Child > > Children
Return type of getAllChildren(): an owning vector of (tag, view) pairs.
Definition ConfigTree.h:503
PTree const * tree_
The wrapped tree.
Definition ConfigTree.h:659
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:249
ConfigTree & operator=(ConfigTree const &)=delete
copying is not compatible with the semantics of this class
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:258
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:693
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...