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