7 #include <libxml++/libxml++.h>
14 template<
typename Element>
15 static Element prefix(std::string s, Element e) {
20 static std::unique_ptr<xmlpp::DomParser> createDomParser(
const std::string& fileName);
21 static std::string
root(
const std::string& flattened_name);
22 static std::string branchWithoutRoot(
const std::string& flattened_name);
23 static std::string branch(
const std::string& flattened_name);
24 static std::string leaf(
const std::string& flattened_name);
35 std::map<size_t, std::string>
values;
42 std::string _fileName;
43 std::unique_ptr<xmlpp::DomParser> _parser;
44 std::unique_ptr<VariableList> _variableList;
45 std::unique_ptr<ArrayList> _arrayList;
48 explicit ConfigParser(
const std::string& fileName) : _fileName(fileName), _parser(createDomParser(fileName)) {}
54 std::tuple<std::unique_ptr<VariableList>, std::unique_ptr<ArrayList>> parse();
55 xmlpp::Element* getRootNode(xmlpp::DomParser& parser);
56 [[noreturn]]
void error(
const std::string& message);
57 bool isVariable(
const xmlpp::Element* element);
58 bool isArray(
const xmlpp::Element* element);
59 bool isModule(
const xmlpp::Element* element);
60 static Variable parseVariable(
const xmlpp::Element* element);
61 Array parseArray(
const xmlpp::Element* element);
62 void parseModule(
const xmlpp::Element* element, std::string parent_name);
64 void validateValueNode(
const xmlpp::Element* valueElement);
65 std::map<size_t, std::string> gettArrayValues(
const xmlpp::Element* element);
80 void addChildNode(
const std::string& name) {
81 if(_children.find(name) == _children.end()) {
82 _children[name] = std::make_unique<ModuleTree>(
this, name,
"");
83 _childrenInOrder.push_back(name);
89 std::unordered_map<std::string, std::unique_ptr<ModuleTree>> _children;
92 std::list<std::string> _childrenInOrder;
103 const std::string& theValue,
bool& isProcessed)
108 template<
typename PAIR>
111 using T =
typename PAIR::first_type;
133 const std::map<size_t, std::string>& theValues,
bool& isProcessed)
138 template<
typename PAIR>
141 using T =
typename PAIR::first_type;
154 const std::map<size_t, std::string>&
values;
165 template<
typename PAIR>
168 using T =
typename PAIR::first_type;
170 size_t numberOfMatches{pair.second.count(
name)};
171 assert(numberOfMatches <= 1);
172 bool hasMatch{numberOfMatches == 1};
189 std::string typeOfVar;
194 auto msg =
"ConfigReader: Cannot find a scalar configuration variable of the name '" + name +
195 "' in the config file '" +
_fileName +
"'.";
196 std::cerr << msg << std::endl;
197 throw(ChimeraTK::logic_error(msg));
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));
211 std::string typeOfVar;
216 throw(ChimeraTK::logic_error(
"ConfigReader: Cannot find a array "
217 "configuration variable of the name '" +
218 name +
"' in the config file '" +
_fileName +
"'."));
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."));
232 T convertedValue = ChimeraTK::userTypeToUserType<T>(value);
234 auto moduleName = branch(name);
235 auto varName = leaf(name);
239 std::unordered_map<std::string, ConfigReader::Var<T>>& theMap = boost::fusion::at_key<T>(
_variableMap.table);
249 std::vector<T> Tvalues;
251 size_t expectedIndex = 0;
252 for(
const auto& value : values) {
254 if(value.first != expectedIndex) {
255 parsingError(
"Array index " + std::to_string(expectedIndex) +
" not found, but " + std::to_string(value.first) +
257 "Sparse arrays are not supported!");
262 T convertedValue = ChimeraTK::userTypeToUserType<T>(value.second);
265 Tvalues.push_back(convertedValue);
268 auto moduleName = branch(name);
269 auto arrayName = leaf(name);
270 auto* arrayOwner =
_moduleTree->lookup(moduleName);
273 std::unordered_map<std::string, ConfigReader::Array<T>>& theMap = boost::fusion::at_key<T>(
_arrayMap.table);
280 const std::unordered_set<std::string>& tags)
281 :
ApplicationModule(owner, name,
"Configuration read from file '" + fileName +
"'", tags), _fileName(fileName),
282 _moduleTree(std::make_unique<
ModuleTree>(this,
".",
"")) {
285 bool replacingDefaultConfig =
false;
291 throw ChimeraTK::logic_error(
"More than one explicit ConfigReader instances found. Unclear how to continue."
292 " Please update your application.");
294 std::cout <<
"Using your own ConfigReader module is deprecated. Please use the Application built-in config reader"
296 replacingDefaultConfig =
true;
300 bool doDisable =
false;
305 catch(ChimeraTK::runtime_error& ex) {
306 if(replacingDefaultConfig) {
308 throw ChimeraTK::logic_error(ex.what());
313 std::cout <<
"Could not load configuration " << fileName <<
", assuming no configuration wanted." << std::endl;
318 auto pathname = std::string(pairPathnameValue.first).substr(1);
319 auto value = pairPathnameValue.second;
323 using UserType = decltype(v);
324 auto moduleName = branch(pathname);
325 auto varName = leaf(pathname);
327 auto& theMap = boost::fusion::at_key<UserType>(
_variableMap.table);
336 auto pathname = std::string(pairPathnameValue.first).substr(1);
337 auto& value = pairPathnameValue.second;
341 using UserType =
typename std::remove_reference<decltype(v)>::type::value_type;
342 auto moduleName = branch(pathname);
343 auto arrayName = leaf(pathname);
344 auto* arrayOwner =
_moduleTree->lookup(moduleName);
345 auto& theMap = boost::fusion::at_key<UserType>(
_arrayMap.table);
359 auto fillVariableMap = [
this](
const Variable& var) {
360 bool processed{
false};
363 parsingError(
"Incorrect value '" + var.type +
"' for attribute 'type' of the 'variable' tag.");
369 bool processed{
false};
372 parsingError(
"Incorrect value '" + arr.type +
"' for attribute 'type' of the 'variable' tag.");
377 auto v = parser.getVariableList();
378 auto a = parser.getArrayList();
380 for(
const auto& var : *v) {
381 fillVariableMap(var);
383 for(
const auto& arr : *a) {
397 throw ChimeraTK::logic_error(
"ConfigReader: Error parsing the config file '" +
_fileName +
"': " + message);
422 template<
typename PAIR>
425 using T =
typename PAIR::first_type;
426 std::unordered_map<std::string, ConfigReader::Var<T>>& theMap =
430 for(
auto& pair : theMap) {
431 auto& var = pair.second;
432 var.accessor = var.value;
433 var.accessor.write();
446 template<
typename PAIR>
449 using T =
typename PAIR::first_type;
450 std::unordered_map<std::string, ConfigReader::Array<T>>& theMap =
454 for(
auto& pair : theMap) {
455 auto& var = pair.second;
456 var.accessor = var.value;
457 var.accessor.write();
475 if(flattened_module_name.empty()) {
479 return get(flattened_module_name);
486 for(
auto& [k, v] : _children) {
494 auto root_name = root(flattened_name);
495 auto remaining_branch_name = branchWithoutRoot(flattened_name);
499 auto r = _children.find(root_name);
500 if(r == _children.end() && !_sealed) {
501 addChildNode(root_name);
505 if(!remaining_branch_name.empty()) {
506 module = _children.at(root_name)->get(remaining_branch_name);
509 module = _children.at(root_name).get();
512 catch(std::out_of_range&) {
522 if(_variableList ==
nullptr) {
523 std::tie(_variableList, _arrayList) = parse();
525 return std::move(_variableList);
531 if(_arrayList !=
nullptr) {
532 std::tie(_variableList, _arrayList) = parse();
534 return std::move(_arrayList);
539 std::tuple<std::unique_ptr<VariableList>, std::unique_ptr<ArrayList>> ConfigParser::parse() {
540 auto*
const root = getRootNode(*_parser);
541 if(root->get_name() !=
"configuration") {
542 error(
"Expected 'configuration' tag instead of: " + root->get_name());
546 _variableList = std::make_unique<VariableList>();
547 _arrayList = std::make_unique<ArrayList>();
549 const auto* element =
dynamic_cast<const xmlpp::Element*
>(root);
550 std::string parent_module_name;
551 parseModule(element, parent_module_name);
553 return std::tuple<std::unique_ptr<VariableList>, std::unique_ptr<ArrayList>>{
554 std::move(_variableList), std::move(_arrayList)};
559 void ConfigParser::parseModule(
const xmlpp::Element* element, std::string parent_name) {
560 auto module_name = (element->get_name() ==
"configuration")
563 element->get_attribute(
"name")->get_value() +
"/";
565 parent_name += module_name;
567 for(
const auto& child : element->get_children()) {
568 element =
dynamic_cast<const xmlpp::Element*
>(child);
572 if(isVariable(element)) {
573 _variableList->emplace_back(prefix(parent_name, parseVariable(element)));
575 else if(isArray(element)) {
576 _arrayList->emplace_back(prefix(parent_name, parseArray(element)));
578 else if(isModule(element)) {
579 parseModule(element, parent_name);
582 error(
"Unknown tag: " + element->get_name());
589 Variable ConfigParser::parseVariable(
const xmlpp::Element* element) {
590 auto name = element->get_attribute(
"name")->get_value();
591 auto type = element->get_attribute(
"type")->get_value();
592 auto value = element->get_attribute(
"value")->get_value();
593 return Variable{name, type, value};
598 Array ConfigParser::parseArray(
const xmlpp::Element* element) {
599 auto name = element->get_attribute(
"name")->get_value();
600 auto type = element->get_attribute(
"type")->get_value();
601 std::map<size_t, std::string> values = gettArrayValues(element);
602 return Array{name, type, values};
607 xmlpp::Element* ConfigParser::getRootNode(xmlpp::DomParser& parser) {
608 auto*
root = parser.get_document()->get_root_node();
609 if(
root->get_name() !=
"configuration") {
610 error(
"Expected 'configuration' tag instead of: " +
root->get_name());
617 void ConfigParser::error(
const std::string& message) {
618 throw ChimeraTK::logic_error(
"ConfigReader: Error parsing the config file '" + _fileName +
"': " + message);
623 bool ConfigParser::isVariable(
const xmlpp::Element* element) {
624 if((element->get_name() ==
"variable") && element->get_attribute(
"value")) {
626 if(!element->get_attribute(
"name")) {
627 error(
"Missing attribute 'name' for the 'variable' tag.");
629 else if(!element->get_attribute(
"type")) {
630 error(
"Missing attribute 'type' for the 'variable' tag.");
639 bool ConfigParser::isArray(
const xmlpp::Element* element) {
640 if((element->get_name() ==
"variable") && !element->get_attribute(
"value")) {
642 if(!element->get_attribute(
"name")) {
643 error(
"Missing attribute 'name' for the 'variable' tag.");
645 else if(!element->get_attribute(
"type")) {
646 error(
"Missing attribute 'type' for the 'variable' tag.");
655 bool ConfigParser::isModule(
const xmlpp::Element* element) {
656 if(element->get_name() ==
"module") {
657 if(!element->get_attribute(
"name")) {
658 error(
"Missing attribute 'name' for the 'module' tag.");
667 std::map<size_t, std::string> ConfigParser::gettArrayValues(
const xmlpp::Element* element) {
668 bool valueFound =
false;
669 std::map<size_t, std::string> values;
671 for(
const auto& valueChild : element->get_children()) {
672 const auto* valueElement =
dynamic_cast<const xmlpp::Element*
>(valueChild);
676 validateValueNode(valueElement);
679 auto* index = valueElement->get_attribute(
"i");
680 auto* value = valueElement->get_attribute(
"v");
685 intIndex = std::stoi(index->get_value());
687 catch(std::exception& e) {
688 error(
"Cannot parse string '" + std::string(index->get_value()) +
"' as an index number: " + e.what());
690 values[intIndex] = value->get_value();
694 error(
"Each variable must have a value, either specified as an attribute or as child tags.");
701 void ConfigParser::validateValueNode(
const xmlpp::Element* valueElement) {
702 if(valueElement->get_name() !=
"value") {
703 error(
"Expected 'value' tag instead of: " + valueElement->get_name());
705 if(!valueElement->get_attribute(
"i")) {
706 error(
"Missing attribute 'index' for the 'value' tag.");
708 if(!valueElement->get_attribute(
"v")) {
709 error(
"Missing attribute 'value' for the 'value' tag.");
715 std::unique_ptr<xmlpp::DomParser> createDomParser(
const std::string& fileName) {
718 if(!std::filesystem::exists(fileName)) {
719 throw ChimeraTK::runtime_error(
"ConfigReader: " + fileName +
" does not exist");
723 return std::make_unique<xmlpp::DomParser>(fileName);
725 catch(xmlpp::exception& e) {
726 throw ChimeraTK::logic_error(
"ConfigReader: Error opening the config file '" + fileName +
"': " + e.what());
732 std::string
root(
const std::string& flattened_name) {
733 auto pos = flattened_name.find_first_of(
'/');
734 pos = (pos == std::string::npos) ? flattened_name.size() : pos;
735 return flattened_name.substr(0, pos);
740 std::string branchWithoutRoot(
const std::string& flattened_name) {
741 auto pos = flattened_name.find_first_of(
'/');
742 pos = (pos == std::string::npos) ? flattened_name.size() : pos + 1;
743 return flattened_name.substr(pos, flattened_name.size());
748 std::string branch(
const std::string& flattened_name) {
749 auto pos = flattened_name.find_last_of(
'/');
750 pos = (pos == std::string::npos) ? 0 : pos;
751 return flattened_name.substr(0, pos);
756 std::string leaf(
const std::string& flattened_name) {
757 auto pos = flattened_name.find_last_of(
'/');
758 return flattened_name.substr(pos + 1, flattened_name.size());