ConfigReader.cc 21.64 KiB
#include <libxml++/libxml++.h>
#include "ConfigReader.h"
#include<iostream>
namespace ChimeraTK {
template <typename Element> static Element prefix(std::string s, Element e) {
e.name = s + e.name;
return e;
}
static std::unique_ptr<xmlpp::DomParser> createDomParser(const std::string &fileName);
static std::string branch(std::string flattened_name);
static std::string leaf(std::string flattened_name);
struct Variable {
std::string name;
std::string type;
std::string value;
};
struct Array {
std::string name;
std::string type;
std::map<size_t, std::string> values;
};
using VariableList = std::vector<Variable>;
using ArrayList = std::vector<Array>;
class ConfigParser {
std::string fileName_{};
std::unique_ptr<xmlpp::DomParser> parser_{};
std::unique_ptr<VariableList> variableList_{};
std::unique_ptr<ArrayList> arrayList_{};
public:
ConfigParser(const std::string &fileName)
: fileName_(fileName), parser_(createDomParser(fileName)) {}
std::unique_ptr<VariableList> getVariableList();
std::unique_ptr<ArrayList> getArrayList();
private:
std::tuple<std::unique_ptr<VariableList>, std::unique_ptr<ArrayList>> parse();
xmlpp::Element *getRootNode(xmlpp::DomParser &parser);
void error(const std::string &message);
bool isVariable(const xmlpp::Element *element);
bool isArray(const xmlpp::Element *element);
bool isModule(const xmlpp::Element *element);
Variable parseVariable(const xmlpp::Element *element);
Array parseArray(const xmlpp::Element *element);
void parseModule(const xmlpp::Element *element, std::string parent_name);
void validateValueNode(const xmlpp::Element *valueElement);
std::map<size_t, std::string> gettArrayValues(const xmlpp::Element * element);
};
class ModuleList {
std::unordered_map<std::string, ChimeraTK::VariableGroup> map_;
ChimeraTK::Module* owner_;
public:
ModuleList(ChimeraTK::Module* o):owner_(o){}
ChimeraTK::Module *lookup(std::string flattened_module_name);
ChimeraTK::Module *get(std::string flattened_name);
};
/*********************************************************************************************************************/
/** Functor to fill variableMap */
struct FunctorFill {
FunctorFill(ConfigReader *owner, const std::string &type,
const std::string &name, const std::string &value,
bool &processed)
: _owner(owner), _type(type), _name(name), _value(value), _processed(processed) {
_processed = false;
}
template <typename PAIR> void operator()(PAIR &) const {
// extract the user type from the pair
typedef typename PAIR::first_type T;
// skip this type, if not matching the type string in the config file
if (_type != boost::fusion::at_key<T>(_owner->typeMap))
return;
_owner->createVar<T>(_name, _value);
_processed = true;
}
ConfigReader *_owner;
const std::string &_type, &_name, &_value;
bool &_processed; // must be a non-const reference, since we want to return
// this to the caller
};
/*********************************************************************************************************************/
/** Functor to fill variableMap for arrays */
struct ArrayFunctorFill {
ArrayFunctorFill(ConfigReader *owner, const std::string &type,
const std::string &name,
const std::map<size_t, std::string> &values,
bool &processed)
: _owner(owner), _type(type), _name(name), _values(values),_processed(processed) {
_processed = false;
}
template <typename PAIR> void operator()(PAIR &) const {
// extract the user type from the pair
typedef typename PAIR::first_type T;
// skip this type, if not matching the type string in the config file
if (_type != boost::fusion::at_key<T>(_owner->typeMap))
return;
_owner->createArray<T>(_name, _values);
_processed = true;
}
ConfigReader *_owner;
const std::string &_type, &_name;
const std::map<size_t, std::string> &_values;
bool &_processed; // must be a non-const reference, since we want to return
// this to the caller
};
/*********************************************************************************************************************/
template<typename T>
void ConfigReader::createVar(const std::string& name, const std::string& value) {
// convert value into user type
/// @todo error handling!
std::stringstream stream(value);
T convertedValue;
if(typeid(T) == typeid(int8_t) || typeid(T) == typeid(uint8_t)) { // prevent interpreting int8-types as characters
int16_t intermediate;
stream >> intermediate;
convertedValue = intermediate;
}
else { // note: string is done in template specialisation
stream >> convertedValue;
}
auto moduleName = branch(name);
auto varName = leaf(name);
auto varOwner = _moduleList->lookup(moduleName);
// place the variable onto the vector
std::unordered_map<std::string, ConfigReader::Var<T>>& theMap = boost::fusion::at_key<T>(variableMap.table);
theMap.emplace(std::make_pair(name, ConfigReader::Var<T>(varOwner, varName, convertedValue)));
}
/*********************************************************************************************************************/
template<>
void ConfigReader::createVar<std::string>(const std::string& name, const std::string& value) {
auto moduleName = branch(name);
auto varName = leaf(name);
auto varOwner = _moduleList->lookup(moduleName);
// place the variable onto the vector
std::unordered_map<std::string, ConfigReader::Var<std::string>>& theMap = boost::fusion::at_key<std::string>(variableMap.table);
theMap.emplace(std::make_pair(name, ConfigReader::Var<std::string>(varOwner, varName, value)));
}
/*********************************************************************************************************************/
/*********************************************************************************************************************/
template<typename T>
void ConfigReader::createArray(const std::string& name, const std::map<size_t, std::string>& values) {
std::vector<T> Tvalues;
size_t expectedIndex = 0;
for(auto it = values.begin(); it != values.end(); ++it) {
// check index (std::map should be ordered by the index)
if(it->first != expectedIndex) {
parsingError("Array index " + std::to_string(expectedIndex) + " not found, but " + std::to_string(it->first) +
" was. "
"Sparse arrays are not supported!");
}
++expectedIndex;
// convert value into user type
std::stringstream stream(it->second);
T convertedValue;
if(typeid(T) == typeid(int8_t) || typeid(T) == typeid(uint8_t)) { // prevent interpreting int8-types as characters
int16_t intermediate;
stream >> intermediate;
convertedValue = intermediate;
}
else { // note: string is done in template specialisation
stream >> convertedValue;
}
// store value in vector
Tvalues.push_back(convertedValue);
}
auto moduleName = branch(name);
auto arrayName = leaf(name);
auto arrayOwner = _moduleList->lookup(moduleName);
// place the variable onto the vector
std::unordered_map<std::string, ConfigReader::Array<T>>& theMap = boost::fusion::at_key<T>(arrayMap.table);
theMap.emplace(std::make_pair( name, ConfigReader::Array<T>(arrayOwner, arrayName, Tvalues)));
}
/*********************************************************************************************************************/
template<>
void ConfigReader::createArray<std::string>(const std::string& name, const std::map<size_t, std::string>& values) {
std::vector<std::string> Tvalues;
size_t expectedIndex = 0;
for(auto it = values.begin(); it != values.end(); ++it) {
// check index (std::map should be ordered by the index)
if(it->first != expectedIndex) {
parsingError("Array index " + std::to_string(expectedIndex) + " not found, but " + std::to_string(it->first) +
" was. "
"Sparse arrays are not supported!");
}
++expectedIndex;
// store value in vector
Tvalues.push_back(it->second);
}
auto moduleName = branch(name);
auto arrayName = leaf(name);
auto arrayOwner = _moduleList->lookup(moduleName);
// place the variable onto the vector
std::unordered_map<std::string, ConfigReader::Array<std::string>>& theMap = boost::fusion::at_key<std::string>(arrayMap.table);
theMap.emplace(std::make_pair( name, ConfigReader::Array<std::string>(arrayOwner, arrayName, Tvalues)));
}
/*********************************************************************************************************************/
ConfigReader::ConfigReader(EntityOwner* owner, const std::string& name, const std::string& fileName, const std::unordered_set<std::string>& tags)
: ApplicationModule(owner, name, "Configuration read from file '" + fileName + "'", false, tags),
_fileName(fileName), _moduleList(std::make_unique<ModuleList>(this)) {
auto fillVariableMap = [this](const Variable &var) {
bool processed{false};
boost::fusion::for_each(variableMap.table,
FunctorFill(this, var.type,
var.name,
var.value,
processed));
if (!processed)
parsingError("Incorrect value '" + var.type + "' for attribute 'type' of the 'variable' tag.");
};
auto fillArrayMap = [this](const ChimeraTK::Array &arr) {
// create accessor and store array value in map using functor
bool processed{false};
boost::fusion::for_each(arrayMap.table,
ArrayFunctorFill(this, arr.type,
arr.name,
arr.values,
processed));
if (!processed)
parsingError("Incorrect value '" + arr.type + "' for attribute 'type' of the 'variable' tag.");
};
auto parser = ConfigParser(fileName);
auto v = parser.getVariableList();
auto a = parser.getArrayList();
for(const auto &var: *v){
fillVariableMap(var);
}
for(const auto &arr: *a){
fillArrayMap(arr) ;
}
}
// workaround for std::unique_ptr static assert.
ConfigReader::~ConfigReader() = default;
/********************************************************************************************************************/
void ConfigReader::parsingError(const std::string& message) {
throw ChimeraTK::logic_error("ConfigReader: Error parsing the config file '" + _fileName + "': " + message);
}
/*********************************************************************************************************************/
/** Functor to set values to the scalar accessors */
struct FunctorSetValues {
FunctorSetValues(ConfigReader* owner) : _owner(owner) {}
template<typename PAIR>
void operator()(PAIR&) const {
// get user type and vector
typedef typename PAIR::first_type T;
std::unordered_map<std::string, ConfigReader::Var<T>>& theMap = boost::fusion::at_key<T>(_owner->variableMap.table);
// iterate vector and set values
for(auto& pair : theMap) {
auto& var = pair.second;
var._accessor = var._value;
var._accessor.write();
}
}
ConfigReader* _owner;
};
/*********************************************************************************************************************/
/** Functor to set values to the array accessors */
struct FunctorSetValuesArray {
FunctorSetValuesArray(ConfigReader* owner) : _owner(owner) {}
template<typename PAIR>
void operator()(PAIR&) const {
// get user type and vector
typedef typename PAIR::first_type T;
std::unordered_map<std::string, ConfigReader::Array<T>>& theMap = boost::fusion::at_key<T>(_owner->arrayMap.table);
// iterate vector and set values
for(auto& pair : theMap) {
auto& var = pair.second;
var._accessor = var._value;
var._accessor.write();
}
}
ConfigReader* _owner;
};
/*********************************************************************************************************************/
void ConfigReader::prepare() {
boost::fusion::for_each(variableMap.table, FunctorSetValues(this));
boost::fusion::for_each(arrayMap.table, FunctorSetValuesArray(this));
}
/*********************************************************************************************************************/
ChimeraTK::Module *ModuleList::lookup(std::string flattened_module_name) {
return get(flattened_module_name);
}
/*********************************************************************************************************************/
ChimeraTK::Module *ModuleList::get(std::string flattened_name) {
if (flattened_name == "") {
return owner_;
}
auto e = map_.find(flattened_name);
if (e == map_.end()) {
auto module_name = leaf(flattened_name);
auto owner = get(branch(flattened_name));
map_[flattened_name] = ChimeraTK::VariableGroup(owner, module_name, "");
return &map_[flattened_name];
}
return &e->second;
}
/*********************************************************************************************************************/
std::unique_ptr<VariableList> ConfigParser::getVariableList() {
if (variableList_ == nullptr) {
std::tie(variableList_, arrayList_) = parse();
}
return std::move(variableList_);
}
/*********************************************************************************************************************/
std::unique_ptr<ArrayList> ConfigParser::getArrayList() {
if (arrayList_ != nullptr) {
std::tie(variableList_, arrayList_) = parse();
}
return std::move(arrayList_);
}
/*********************************************************************************************************************/
std::tuple<std::unique_ptr<VariableList>, std::unique_ptr<ArrayList>>
ConfigParser::parse() {
const auto root = getRootNode(*parser_);
if (root->get_name() != "configuration") {
error("Expected 'configuration' tag instead of: " + root->get_name());
}
//start with clean lists: parseModule accumulates elements into these.
variableList_ = std::make_unique<VariableList>();
arrayList_ = std::make_unique<ArrayList>();
const xmlpp::Element *element = dynamic_cast<const xmlpp::Element *>(root);
std::string parent_module_name = "";
parseModule(element, parent_module_name);
return std::tuple<std::unique_ptr<VariableList>, std::unique_ptr<ArrayList>>{std::move(variableList_), std::move(arrayList_)};
}
/*********************************************************************************************************************/
void ConfigParser::parseModule(const xmlpp::Element * element, std::string parent_name) {
auto module_name = (element->get_name() ==
"configuration") // root node gets special treatment
? ""
: element->get_attribute("name")->get_value() + "/";
parent_name+=module_name;
for (const auto &child : element->get_children()) {
element = dynamic_cast<const xmlpp::Element *>(child);
if (!element) {
continue; // ignore if not an element (e.g. comment)
}
else if (isVariable(element)) {
variableList_->emplace_back(prefix(parent_name, parseVariable(element)));
}
else if (isArray(element)) {
arrayList_->emplace_back(prefix(parent_name, parseArray(element)));
}
else if (isModule(element)) {
parseModule(element, parent_name);
}
else {
error("Unknown tag: " + element->get_name());
}
}
}
/*********************************************************************************************************************/
Variable ConfigParser::parseVariable(const xmlpp::Element *element) {
auto name = element->get_attribute("name")->get_value();
auto type = element->get_attribute("type")->get_value();
auto value = element->get_attribute("value")->get_value();
return Variable{name, type, value};
}
/*********************************************************************************************************************/
Array ConfigParser::parseArray(const xmlpp::Element *element) {
auto name = element->get_attribute("name")->get_value();
auto type = element->get_attribute("type")->get_value();
std::map<size_t, std::string> values = gettArrayValues(element);
return Array{name, type, values};
}
/*********************************************************************************************************************/
xmlpp::Element* ConfigParser::getRootNode(xmlpp::DomParser& parser){
auto root = parser.get_document()->get_root_node();
if(root->get_name() != "configuration") {
error("Expected 'configuration' tag instead of: " + root->get_name());
}
return root;
}
/*********************************************************************************************************************/
void ConfigParser::error(const std::string &message) {
throw ChimeraTK::logic_error("ConfigReader: Error parsing the config file '" + fileName_ + "': " + message);
}
/*********************************************************************************************************************/
bool ConfigParser::isVariable(const xmlpp::Element *element) {
if ((element->get_name() == "variable") && element->get_attribute("value")) {
// validate variable node
if (!element->get_attribute("name")) {
error("Missing attribute 'name' for the 'variable' tag.");
} else if (!element->get_attribute("type")) {
error("Missing attribute 'type' for the 'variable' tag.");
}
return true;
} else {
return false;
}
}
/*********************************************************************************************************************/
bool ConfigParser::isArray(const xmlpp::Element *element) {
if ((element->get_name() == "variable") && !element->get_attribute("value")) {
// validate array node
if (!element->get_attribute("name")) {
error("Missing attribute 'name' for the 'variable' tag.");
} else if (!element->get_attribute("type")) {
error("Missing attribute 'type' for the 'variable' tag.");
}
return true;
} else {
return false;
}
}
/*********************************************************************************************************************/
bool ConfigParser::isModule(const xmlpp::Element *element) {
if (element->get_name() == "module") {
if (!element->get_attribute("name")) {
error("Missing attribute 'name' for the 'module' tag.");
}
return true;
} else {
return false;
}
}
/*********************************************************************************************************************/
std::map<size_t, std::string> ConfigParser::gettArrayValues(const xmlpp::Element * element) {
bool valueFound = false;
std::map<size_t, std::string> values;
for (const auto &valueChild : element->get_children()) {
const xmlpp::Element *valueElement = dynamic_cast<const xmlpp::Element *>(valueChild);
if (!valueElement)
continue; // ignore comments etc.
validateValueNode(valueElement);
valueFound = true;
auto index = valueElement->get_attribute("i");
auto value = valueElement->get_attribute("v");
// get index as number and store value as a string
size_t intIndex;
try {
intIndex = std::stoi(index->get_value());
} catch (std::exception &e) {
error("Cannot parse string '" + std::string(index->get_value()) + "' as an index number: " + e.what());
}
values[intIndex] = value->get_value();
}
// make sure there is at least one value
if (!valueFound) {
error("Each variable must have a value, either specified as an attribute or as child tags.");
}
return values;
}
/*********************************************************************************************************************/
void ConfigParser::validateValueNode(const xmlpp::Element* valueElement){
if (valueElement->get_name() != "value") {
error("Expected 'value' tag instead of: " + valueElement->get_name());
}
if(!valueElement->get_attribute("i")){
error("Missing attribute 'index' for the 'value' tag.");
}
if(!valueElement->get_attribute("v")){
error("Missing attribute 'value' for the 'value' tag.");
}
}
/*********************************************************************************************************************/
std::unique_ptr<xmlpp::DomParser> createDomParser(const std::string &fileName){
try {
return std::make_unique<xmlpp::DomParser>(fileName);
} catch (xmlpp::exception &e) { /// @todo change exception!
throw ChimeraTK::logic_error( "ConfigReader: Error opening the config file '" + fileName + "': " + e.what());
}
}
/*********************************************************************************************************************/
std::string branch(std::string flattened_name) {
auto pos = flattened_name.find_last_of('/');
pos = (pos == std::string::npos) ? 0 : pos;
return flattened_name.substr(0, pos);
}
/*********************************************************************************************************************/
std::string leaf(std::string flattened_name) {
auto pos = flattened_name.find_last_of('/');
return flattened_name.substr(pos + 1, flattened_name.size());
}
/*********************************************************************************************************************/
} // namespace ChimeraTK