8#include <ChimeraTK/Exception.h>
10#include <libxml++/libxml++.h>
17 template<
typename Element>
18 static Element prefix(std::string s, Element e) {
23 static std::unique_ptr<xmlpp::DomParser> createDomParser(
const std::string& fileName);
24 static std::string
root(
const std::string& flattened_name);
25 static std::string branchWithoutRoot(
const std::string& flattened_name);
26 static std::string branch(
const std::string& flattened_name);
27 static std::string leaf(
const std::string& flattened_name);
38 std::map<size_t, std::string>
values;
45 std::string _fileName;
46 std::unique_ptr<xmlpp::DomParser> _parser;
47 std::unique_ptr<VariableList> _variableList;
48 std::unique_ptr<ArrayList> _arrayList;
51 explicit ConfigParser(
const std::string& fileName) : _fileName(fileName), _parser(createDomParser(fileName)) {}
57 std::tuple<std::unique_ptr<VariableList>, std::unique_ptr<ArrayList>> parse();
58 xmlpp::Element* getRootNode(xmlpp::DomParser& parser);
59 [[noreturn]]
void error(
const std::string& message);
60 bool isVariable(
const xmlpp::Element* element);
61 bool isArray(
const xmlpp::Element* element);
62 bool isModule(
const xmlpp::Element* element);
63 static Variable parseVariable(
const xmlpp::Element* element);
64 Array parseArray(
const xmlpp::Element* element);
65 void parseModule(
const xmlpp::Element* element, std::string parent_name);
67 void validateValueNode(
const xmlpp::Element* valueElement);
68 std::map<size_t, std::string> gettArrayValues(
const xmlpp::Element* element);
83 void addChildNode(
const std::string& name) {
84 if(_children.find(name) == _children.end()) {
85 _children[name] = std::make_unique<ModuleTree>(
this, name,
"");
86 _childrenInOrder.push_back(name);
92 std::unordered_map<std::string, std::unique_ptr<ModuleTree>> _children;
95 std::list<std::string> _childrenInOrder;
106 const std::string& theValue,
bool& isProcessed)
111 template<
typename PAIR>
114 using T =
typename PAIR::first_type;
136 const std::map<size_t, std::string>& theValues,
bool& isProcessed)
141 template<
typename PAIR>
144 using T =
typename PAIR::first_type;
157 const std::map<size_t, std::string>&
values;
168 template<
typename PAIR>
171 using T =
typename PAIR::first_type;
173 size_t numberOfMatches{pair.second.count(
name)};
174 assert(numberOfMatches <= 1);
175 bool hasMatch{numberOfMatches == 1};
192 std::string typeOfVar;
200 if(typeOfVar != typeOfThis) {
201 auto msg =
"ConfigReader: Attempting to read scalar configuration variable '" + name +
"' with type '" +
202 typeOfThis +
"'. This does not match type '" + typeOfVar +
"' defined in the config file.";
203 std::cerr << msg << std::endl;
204 throw(ChimeraTK::logic_error(msg));
213 std::string typeOfVar;
221 if(typeOfVar != typeOfThis) {
222 throw(ChimeraTK::logic_error(
"ConfigReader: Attempting to read array configuration variable '" + name +
223 "' with type '" + typeOfThis +
"'. This does not match type '" + typeOfVar +
224 "' defined in the config file."));
233 T convertedValue = ChimeraTK::userTypeToUserType<T>(value);
235 auto moduleName = branch(name);
236 auto varName = leaf(name);
240 std::unordered_map<std::string, ConfigReader::Var<T>>& theMap = boost::fusion::at_key<T>(
_variableMap.table);
250 std::vector<T> Tvalues;
252 size_t expectedIndex = 0;
253 for(
const auto& value : values) {
255 if(value.first != expectedIndex) {
256 parsingError(
"Array index " + std::to_string(expectedIndex) +
" not found, but " + std::to_string(value.first) +
258 "Sparse arrays are not supported!");
263 T convertedValue = ChimeraTK::userTypeToUserType<T>(value.second);
266 Tvalues.push_back(convertedValue);
269 auto moduleName = branch(name);
270 auto arrayName = leaf(name);
271 auto* arrayOwner =
_moduleTree->lookup(moduleName);
274 std::unordered_map<std::string, ConfigReader::Array<T>>& theMap = boost::fusion::at_key<T>(
_arrayMap.table);
281 const std::unordered_set<std::string>& tags)
282 :
ApplicationModule(owner, name,
"Configuration read from file '" + fileName +
"'", tags), _fileName(fileName),
283 _moduleTree(std::make_unique<
ModuleTree>(this,
".",
"")) {
286 bool replacingDefaultConfig =
false;
292 throw ChimeraTK::logic_error(
"More than one explicit ConfigReader instances found. Unclear how to continue."
293 " Please update your application.");
295 std::cout <<
"Using your own ConfigReader module is deprecated. Please use the Application built-in config reader"
297 replacingDefaultConfig =
true;
301 bool doDisable =
false;
306 catch(ChimeraTK::runtime_error& ex) {
307 if(replacingDefaultConfig) {
309 throw ChimeraTK::logic_error(ex.what());
314 std::cout <<
"Could not load configuration " << fileName <<
", assuming no configuration wanted." << std::endl;
319 auto pathname = std::string(pairPathnameValue.first).substr(1);
320 auto value = pairPathnameValue.second;
324 using UserType =
decltype(v);
325 auto moduleName = branch(pathname);
326 auto varName = leaf(pathname);
328 auto& theMap = boost::fusion::at_key<UserType>(
_variableMap.table);
337 auto pathname = std::string(pairPathnameValue.first).substr(1);
338 auto& value = pairPathnameValue.second;
342 using UserType =
typename std::remove_reference<
decltype(v)>::type::value_type;
343 auto moduleName = branch(pathname);
344 auto arrayName = leaf(pathname);
345 auto* arrayOwner =
_moduleTree->lookup(moduleName);
346 auto& theMap = boost::fusion::at_key<UserType>(
_arrayMap.table);
360 auto fillVariableMap = [
this](
const Variable& var) {
361 bool processed{
false};
364 parsingError(
"Incorrect value '" + var.type +
"' for attribute 'type' of the 'variable' tag.");
370 bool processed{
false};
373 parsingError(
"Incorrect value '" + arr.type +
"' for attribute 'type' of the 'variable' tag.");
378 auto v = parser.getVariableList();
379 auto a = parser.getArrayList();
381 for(
const auto& var : *v) {
382 fillVariableMap(var);
384 for(
const auto& arr : *a) {
399 throw ChimeraTK::logic_error(
"ConfigReader: Error parsing the config file '" + _fileName +
"': " + message);
401 catch(ChimeraTK::logic_error&) {
409 auto*
module = _moduleTree->lookup(path);
418 auto* castedModule =
dynamic_cast<ModuleTree*
>(module);
428 template<
typename PAIR>
431 using T =
typename PAIR::first_type;
432 std::unordered_map<std::string, ConfigReader::Var<T>>& theMap =
436 for(
auto& pair : theMap) {
437 auto& var = pair.second;
438 var.accessor = var.value;
439 var.accessor.write();
452 template<
typename PAIR>
455 using T =
typename PAIR::first_type;
456 std::unordered_map<std::string, ConfigReader::Array<T>>& theMap =
460 for(
auto& pair : theMap) {
461 auto& var = pair.second;
462 var.accessor = var.value;
463 var.accessor.write();
481 if(flattened_module_name.empty()) {
485 return get(flattened_module_name);
492 for(
auto& [k, v] : _children) {
500 auto root_name = root(flattened_name);
501 auto remaining_branch_name = branchWithoutRoot(flattened_name);
505 auto r = _children.find(root_name);
506 if(r == _children.end() && !_sealed) {
507 addChildNode(root_name);
511 if(!remaining_branch_name.empty()) {
512 module = _children.at(root_name)->get(remaining_branch_name);
515 module = _children.at(root_name).get();
518 catch(std::out_of_range&) {
528 if(_variableList ==
nullptr) {
529 std::tie(_variableList, _arrayList) = parse();
531 return std::move(_variableList);
537 if(_arrayList !=
nullptr) {
538 std::tie(_variableList, _arrayList) = parse();
540 return std::move(_arrayList);
545 std::tuple<std::unique_ptr<VariableList>, std::unique_ptr<ArrayList>> ConfigParser::parse() {
546 auto*
const root = getRootNode(*_parser);
547 if(root->get_name() !=
"configuration") {
548 error(
"Expected 'configuration' tag instead of: " + root->get_name());
552 _variableList = std::make_unique<VariableList>();
553 _arrayList = std::make_unique<ArrayList>();
555 const auto* element =
dynamic_cast<const xmlpp::Element*
>(root);
556 std::string parent_module_name;
557 parseModule(element, parent_module_name);
559 return std::tuple<std::unique_ptr<VariableList>, std::unique_ptr<ArrayList>>{
560 std::move(_variableList), std::move(_arrayList)};
565 void ConfigParser::parseModule(
const xmlpp::Element* element, std::string parent_name) {
566 auto module_name = (element->get_name() ==
"configuration")
569 element->get_attribute(
"name")->get_value() +
"/";
571 parent_name += module_name;
573 for(
const auto& child : element->get_children()) {
574 element =
dynamic_cast<const xmlpp::Element*
>(child);
578 if(isVariable(element)) {
579 _variableList->emplace_back(prefix(parent_name, parseVariable(element)));
581 else if(isArray(element)) {
582 _arrayList->emplace_back(prefix(parent_name, parseArray(element)));
584 else if(isModule(element)) {
585 parseModule(element, parent_name);
588 error(
"Unknown tag: " + element->get_name());
595 Variable ConfigParser::parseVariable(
const xmlpp::Element* element) {
596 auto name = element->get_attribute(
"name")->get_value();
597 auto type = element->get_attribute(
"type")->get_value();
598 auto value = element->get_attribute(
"value")->get_value();
599 return Variable{name, type, value};
604 Array ConfigParser::parseArray(
const xmlpp::Element* element) {
605 auto name = element->get_attribute(
"name")->get_value();
606 auto type = element->get_attribute(
"type")->get_value();
607 std::map<size_t, std::string> values = gettArrayValues(element);
608 return Array{name, type, values};
613 xmlpp::Element* ConfigParser::getRootNode(xmlpp::DomParser& parser) {
614 auto*
root = parser.get_document()->get_root_node();
615 if(
root->get_name() !=
"configuration") {
616 error(
"Expected 'configuration' tag instead of: " +
root->get_name());
623 void ConfigParser::error(
const std::string& message) {
624 throw ChimeraTK::logic_error(
"ConfigReader: Error parsing the config file '" + _fileName +
"': " + message);
629 bool ConfigParser::isVariable(
const xmlpp::Element* element) {
630 if((element->get_name() ==
"variable") && element->get_attribute(
"value")) {
632 if(!element->get_attribute(
"name")) {
633 error(
"Missing attribute 'name' for the 'variable' tag.");
635 else if(!element->get_attribute(
"type")) {
636 error(
"Missing attribute 'type' for the 'variable' tag.");
645 bool ConfigParser::isArray(
const xmlpp::Element* element) {
646 if((element->get_name() ==
"variable") && !element->get_attribute(
"value")) {
648 if(!element->get_attribute(
"name")) {
649 error(
"Missing attribute 'name' for the 'variable' tag.");
651 else if(!element->get_attribute(
"type")) {
652 error(
"Missing attribute 'type' for the 'variable' tag.");
661 bool ConfigParser::isModule(
const xmlpp::Element* element) {
662 if(element->get_name() ==
"module") {
663 if(!element->get_attribute(
"name")) {
664 error(
"Missing attribute 'name' for the 'module' tag.");
673 std::map<size_t, std::string> ConfigParser::gettArrayValues(
const xmlpp::Element* element) {
674 bool valueFound =
false;
675 std::map<size_t, std::string> values;
677 for(
const auto& valueChild : element->get_children()) {
678 const auto* valueElement =
dynamic_cast<const xmlpp::Element*
>(valueChild);
682 validateValueNode(valueElement);
685 auto* index = valueElement->get_attribute(
"i");
686 auto* value = valueElement->get_attribute(
"v");
691 intIndex = std::stoi(index->get_value());
693 catch(std::exception& e) {
694 error(
"Cannot parse string '" + std::string(index->get_value()) +
"' as an index number: " + e.what());
696 values[intIndex] = value->get_value();
700 error(
"Each variable must have a value, either specified as an attribute or as child tags.");
707 void ConfigParser::validateValueNode(
const xmlpp::Element* valueElement) {
708 if(valueElement->get_name() !=
"value") {
709 error(
"Expected 'value' tag instead of: " + valueElement->get_name());
711 if(!valueElement->get_attribute(
"i")) {
712 error(
"Missing attribute 'index' for the 'value' tag.");
714 if(!valueElement->get_attribute(
"v")) {
715 error(
"Missing attribute 'value' for the 'value' tag.");
721 std::unique_ptr<xmlpp::DomParser> createDomParser(
const std::string& fileName) {
724 if(!std::filesystem::exists(fileName)) {
725 throw ChimeraTK::runtime_error(
"ConfigReader: " + fileName +
" does not exist");
729 return std::make_unique<xmlpp::DomParser>(fileName);
731 catch(xmlpp::exception& e) {
732 throw ChimeraTK::logic_error(
"ConfigReader: Error opening the config file '" + fileName +
"': " + e.what());
738 std::string
root(
const std::string& flattened_name) {
739 auto pos = flattened_name.find_first_of(
'/');
740 pos = (pos == std::string::npos) ? flattened_name.size() : pos;
741 return flattened_name.substr(0, pos);
746 std::string branchWithoutRoot(
const std::string& flattened_name) {
747 auto pos = flattened_name.find_first_of(
'/');
748 pos = (pos == std::string::npos) ? flattened_name.size() : pos + 1;
749 return flattened_name.substr(pos, flattened_name.size());
754 std::string branch(
const std::string& flattened_name) {
755 auto pos = flattened_name.find_last_of(
'/');
756 pos = (pos == std::string::npos) ? 0 : pos;
757 return flattened_name.substr(0, pos);
762 std::string leaf(
const std::string& flattened_name) {
763 auto pos = flattened_name.find_last_of(
'/');
764 return flattened_name.substr(pos + 1, flattened_name.size());
ConfigReader * _defaultConfigReader
static Application & getInstance()
Obtain instance of the application.
ConfigParser(const std::string &fileName)
std::unique_ptr< VariableList > getVariableList()
std::unique_ptr< ArrayList > getArrayList()
Generic module to read an XML config file and provide the defined values as constant variables.
void createVar(const std::string &name, const std::string &value)
Create an instance of Var<T> and place it on the variableMap.
std::list< std::string > getModules(const std::string &path={}) const
Returns a list of names of modules which are direct children of path.
bool checkVariable(std::string const &name, std::string const &type) const
Check if variable exists in the config and if type of var name in the config file matches the given t...
std::string _fileName
File name.
std::unique_ptr< ModuleTree > _moduleTree
List to hold VariableNodes corresponding to xml modules.
bool checkArray(std::string const &name, std::string const &type) const
Check if array exists in the config and if type of array name in the config file matches the given ty...
void prepare() override
Prepare the execution of the module.
ChimeraTK::TemplateUserTypeMapNoVoid< MapOfArray > _arrayMap
Type-depending map of vectors of arrays.
void construct(const std::string &fileName)
Helper function to avoid code duplication in constructors.
ChimeraTK::SingleTypeUserTypeMapNoVoid< const char * > _typeMap
Map assigning string type identifyers to C++ types.
ChimeraTK::TemplateUserTypeMapNoVoid< MapOfVar > _variableMap
Type-depending map of vectors of variables.
ConfigReader(ModuleGroup *owner, const std::string &name, const std::string &fileName, const std::unordered_set< std::string > &tags={})
void createArray(const std::string &name, const std::map< size_t, std::string > &values)
Create an instance of Array<T> and place it on the arrayMap.
void parsingError(const std::string &message) noexcept
throw a parsing error with more information
Base class for ApplicationModule and DeviceModule, to have a common interface for these module types.
EntityOwner * _owner
Owner of this instance.
void disable()
Disable the module such that it is not part of the Application.
static ConfigReader & appConfig()
Obtain the ConfigReader instance of the application.
EntityOwner * getOwner() const
ChimeraTK::Module * lookup(const std::string &flattened_module_name)
std::list< std::string > getChildList()
static std::map< ChimeraTK::RegisterPath, ChimeraTK::UserTypeTemplateVariantNoVoid< Vector > > _configArrays
static std::map< ChimeraTK::RegisterPath, ChimeraTK::UserTypeVariantNoVoid > _configScalars
VariableGroup()=default
Default constructor: Allows late initialisation of VariableGroups (e.g.
InvalidityTracer application module.
std::vector< Variable > VariableList
std::vector< Array > ArrayList
Functor to fill variableMap for arrays.
ArrayFunctorFill(ConfigReader *theOwner, const std::string &theType, const std::string &theName, const std::map< size_t, std::string > &theValues, bool &isProcessed)
const std::map< size_t, std::string > & values
void operator()(PAIR &) const
std::map< size_t, std::string > values
Class holding the values and the accessor for one configuration array.
Class holding the value and the accessor for one configuration variable.
Functor to fill variableMap.
FunctorFill(ConfigReader *theOwner, const std::string &theType, const std::string &theName, const std::string &theValue, bool &isProcessed)
const std::string & value
void operator()(PAIR &) const
FunctorGetTypeForName(const ConfigReader *theOwner, std::string const &theName, std::string &theType)
const ConfigReader * owner
bool operator()(PAIR const &pair) const
Functor to set values to the array accessors.
FunctorSetValuesArray(ConfigReader *theOwner)
void operator()(PAIR &) const
Functor to set values to the scalar accessors.
void operator()(PAIR &) const
FunctorSetValues(ConfigReader *theOwner)