You need to sign in or sign up before continuing.
Newer
Older
Martin Killenberg
committed
#include "VariableMapper.h"
Martin Killenberg
committed
#include "splitStringAtFirstSlash.h"
#include <ChimeraTK/RegisterPath.h>
Martin Killenberg
committed
#include <locale>
/********************************************************************************************************************/
VariableMapper& VariableMapper::getInstance() {
static VariableMapper instance;
return instance;
}
/********************************************************************************************************************/
bool VariableMapper::nodeIsWhitespace(const xmlpp::Node* node) {
const xmlpp::TextNode* nodeAsText = dynamic_cast<const xmlpp::TextNode*>(node);
if(nodeAsText) {
return nodeAsText->is_white_space();
}
return false;
Martin Killenberg
committed
/********************************************************************************************************************/
void VariableMapper::processLocationNode(xmlpp::Node const* locationNode) {
const xmlpp::Element* location = dynamic_cast<const xmlpp::Element*>(locationNode);
if(!location) {
throw std::invalid_argument("Error parsing xml file in location node.");
Martin Killenberg
committed
}
std::string locationName = getAttributeValue(location, "name");
Martin Killenberg
committed
for(auto const& node : location->get_children()) {
if(nodeIsWhitespace(node)) continue;
if(dynamic_cast<xmlpp::CommentNode const*>(node)) continue;
Martin Christoph Hierholzer
committed
processNode(node, locationName);
}
else if(node->get_name() == "import") {
processImportNode(node, locationName);
}
else if(node->get_name() == "has_history") {
auto& locationInfo = _locationDefaults[locationName];
locationInfo.useHasHistoryDefault = true;
locationInfo.hasHistory = evaluateBool(getContentString(node));
}
else if(node->get_name() == "is_writeable") {
auto& locationInfo = _locationDefaults[locationName];
locationInfo.useIsWriteableDefault = true;
locationInfo.isWriteable = evaluateBool(getContentString(node));
}
else if(node->get_name() == "macro_pulse_number_source") {
auto& locationInfo = _locationDefaults[locationName];
locationInfo.useMacroPulseNumberSourceDefault = true;
locationInfo.macroPulseNumberSource = getContentString(node);
}
else if(node->get_name() == "D_spectrum") {
processSpectrumNode(node, locationName);
}
else if(node->get_name() == "D_array") {
processNode(node, locationName);
else if(node->get_name() == "D_xy") {
processXyNode(node, locationName);
}
else {
throw std::invalid_argument(std::string("Error parsing xml file in location ") + locationName +
": Unknown node '" + node->get_name() + "'");
}
}
/********************************************************************************************************************/
std::string determineName(const xmlpp::Element* property, std::string source) {
const xmlpp::Attribute* nameAttribute = property->get_attribute("name");
// if a name is given in xml take it
if(nameAttribute) {
return nameAttribute->get_value();
}
else { // auto-determine the name from the source
///@todo FIXME: use register path to do the slash and replace fiddeling
std::string name;
if(source[0] == '/') {
name = source.substr(1);
}
else {
name = source;
}
// replace / with . in name
return std::regex_replace(name, std::regex("/"), ".");
}
/********************************************************************************************************************/
std::string getAbsoluteSource(std::string source, std::string locationName) {
if(source[0] == '/') {
return source;
}
else {
return std::string("/") + locationName + "/" + source;
}
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/********************************************************************************************************************/
template<class PROPERTY_DESCRIPTION_TYPE>
void VariableMapper::processHistoryAndWritableAttributes(PROPERTY_DESCRIPTION_TYPE propertyDescription,
const xmlpp::Element* propertyXmlElement, std::string locationName) {
auto hasHistoryNodes = propertyXmlElement->get_children("has_history");
if(!hasHistoryNodes.empty()) {
propertyDescription->hasHistory = evaluateBool(getContentString(hasHistoryNodes.front()));
}
else {
propertyDescription->hasHistory = getHasHistoryDefault(locationName);
}
auto isWriteableNodes = propertyXmlElement->get_children("is_writeable");
if(!isWriteableNodes.empty()) {
propertyDescription->isWriteable = evaluateBool(getContentString(isWriteableNodes.front()));
}
else {
propertyDescription->isWriteable = getIsWriteableDefault(locationName);
}
auto publishZeroMQ = propertyXmlElement->get_children("publish_ZMQ");
if(!publishZeroMQ.empty()) {
propertyDescription->publishZMQ = evaluateBool(getContentString(publishZeroMQ.front()));
}
else {
propertyDescription->publishZMQ = false;
}
auto macroPulseNumberSource = propertyXmlElement->get_children("macro_pulse_number_source");
if(!macroPulseNumberSource.empty()) {
propertyDescription->macroPulseNumberSource = getContentString(macroPulseNumberSource.front());
}
else {
propertyDescription->macroPulseNumberSource = getMacroPusleNumberSourceDefault(locationName);
}
/********************************************************************************************************************/
Martin Christoph Hierholzer
committed
void VariableMapper::addDescription(
std::shared_ptr<PropertyDescription> const& propertyDescription, std::list<std::string> const& absoluteSources) {
_descriptions.push_back(propertyDescription);
for(auto& source : absoluteSources) {
_usedInputVariables.insert(source);
}
/********************************************************************************************************************/
xmlpp::Element const* asXmlElement(xmlpp::Node const* node) {
const xmlpp::Element* element = dynamic_cast<const xmlpp::Element*>(node);
if(!element) {
throw std::invalid_argument("Error parsing xml file: Node is not an element node: " + node->get_name());
}
return element;
/********************************************************************************************************************/
void VariableMapper::processNode(xmlpp::Node const* propertyNode, std::string locationName) {
auto property = asXmlElement(propertyNode);
Martin Killenberg
committed
std::string source = getAttributeValue(property, "source");
std::string name = determineName(property, source);
const xmlpp::Attribute* typeAttribute = property->get_attribute("type");
std::map<std::string, AutoPropertyDescription::DataType> dataTypeMap(
{{"auto", AutoPropertyDescription::DataType::Auto}, {"byte", AutoPropertyDescription::DataType::Byte},
{"short", AutoPropertyDescription::DataType::Short}, {"int", AutoPropertyDescription::DataType::Int},
{"long", AutoPropertyDescription::DataType::Long}, {"float", AutoPropertyDescription::DataType::Float},
{"double", AutoPropertyDescription::DataType::Double}});
auto type = AutoPropertyDescription::DataType::Auto;
try {
type = dataTypeMap.at(typeAttribute->get_value());
}
catch(std::out_of_range&) {
throw ChimeraTK::logic_error("Unknown type attribute in line " + std::to_string(property->get_line()) + ": " +
typeAttribute->get_value());
}
std::string absoluteSource = getAbsoluteSource(source, locationName);
Martin Killenberg
committed
auto autoPropertyDescription = std::make_shared<AutoPropertyDescription>(absoluteSource, locationName, name, type);
Martin Killenberg
committed
processHistoryAndWritableAttributes(autoPropertyDescription, property, locationName);
Martin Killenberg
committed
addDescription(autoPropertyDescription, {absoluteSource});
}
/********************************************************************************************************************/
Martin Killenberg
committed
void VariableMapper::processSpectrumNode(xmlpp::Node const* node, std::string locationName) {
auto spectrumXml = asXmlElement(node);
// the "main source" of a spectum
std::string source = getAttributeValue(spectrumXml, "source");
std::string name = determineName(spectrumXml, source);
std::string absoluteSource = getAbsoluteSource(source, locationName);
Martin Killenberg
committed
// prepare the property description
auto spectrumDescription = std::make_shared<SpectrumDescription>(absoluteSource, locationName, name);
Martin Killenberg
committed
processHistoryAndWritableAttributes(spectrumDescription, spectrumXml, locationName);
auto startNodes = spectrumXml->get_children("start");
if(!startNodes.empty()) {
spectrumDescription->start = std::stof(getContentString(startNodes.front()));
}
std::list<std::string> usedVariables({absoluteSource});
auto incrementNodes = spectrumXml->get_children("increment");
if(!incrementNodes.empty()) {
spectrumDescription->increment = std::stof(getContentString(incrementNodes.front()));
}
auto startSourceNodes = spectrumXml->get_children("startSource");
if(!startSourceNodes.empty()) {
auto startSource = getContentString(startSourceNodes.front());
spectrumDescription->startSource = startSource;
usedVariables.push_back(startSource);
}
auto incrementSourceNodes = spectrumXml->get_children("incrementSource");
if(!incrementSourceNodes.empty()) {
auto incrementSource = getContentString(incrementSourceNodes.front());
spectrumDescription->incrementSource = incrementSource;
usedVariables.push_back(incrementSource);
}
Martin Christoph Hierholzer
committed
auto numberOfBuffersNodes = spectrumXml->get_children("numberOfBuffers");
if(!numberOfBuffersNodes.empty()) {
auto numberOfBuffers = getContentString(numberOfBuffersNodes.front());
spectrumDescription->numberOfBuffers = std::stoi(numberOfBuffers);
usedVariables.push_back(numberOfBuffers);
}
addDescription(spectrumDescription, usedVariables);
Martin Killenberg
committed
/********************************************************************************************************************/
Martin Killenberg
committed
void VariableMapper::processXyNode(xmlpp::Node const* node, std::string& locationName) {
auto xyXml = asXmlElement(node);
auto xSource = getAttributeValue(xyXml, "x_source");
auto ySource = getAttributeValue(xyXml, "y_source");
auto name = determineName(xyXml, xSource + "_" + ySource);
auto xAbsoluteSource = getAbsoluteSource(xSource, locationName);
auto yAbsoluteSource = getAbsoluteSource(ySource, locationName);
auto xyDescription = std::make_shared<XyDescription>(xAbsoluteSource, yAbsoluteSource, locationName, name, false);
processHistoryAndWritableAttributes(xyDescription, xyXml, locationName);
auto descriptionNode = xyXml->get_first_child("description");
xyDescription->description = getContentString(descriptionNode);
addDescription(xyDescription, {xAbsoluteSource, yAbsoluteSource});
}
/********************************************************************************************************************/
void VariableMapper::processImportNode(xmlpp::Node const* importNode, std::string importLocationName) {
const xmlpp::Element* importElement = dynamic_cast<const xmlpp::Element*>(importNode);
std::string directory;
if(importElement) {
// look for a directory attribute
const xmlpp::Attribute* directoryAttribute = importElement->get_attribute("directory");
if(directoryAttribute) {
directory = directoryAttribute->get_value();
}
}
for(auto const& node : importNode->get_children()) {
const xmlpp::TextNode* nodeAsText = dynamic_cast<const xmlpp::TextNode*>(node);
std::string importSource = nodeAsText->get_content();
import(importSource, importLocationName, directory);
}
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
/********************************************************************************************************************/
void VariableMapper::import(std::string importSource, std::string importLocationName, std::string directory) {
// a slash will be added after the source, so we make the source empty for an
// import of everything
if(importSource == "/") {
importSource = "";
}
// loop source tree, cut beginning, replace / with _ and add a property
for(auto const& processVariable : _inputVariables) {
if(_usedInputVariables.find(processVariable) != _usedInputVariables.end()) {
continue;
}
if(processVariable.find(importSource + "/") == 0) {
// processVariable starts with wanted source
auto nameSource = processVariable.substr(importSource.size() + 1); // add the slash to be removed
// we use the register path because it removes duplicate separators and
// allows to use . as separater to replace all / with .
ChimeraTK::RegisterPath propertyName;
std::string locationName;
if(importLocationName.empty()) {
// a global import, try to get the location name from the source
auto locationAndPropertyName = splitStringAtFirstSlash(nameSource);
locationName = locationAndPropertyName.first;
propertyName = locationAndPropertyName.second;
if(locationName.empty()) {
throw std::logic_error(std::string("Invalid XML content in global import of ") +
(importSource.empty() ? "/" : importSource) + ": Cannot create location name from '" + nameSource +
"', one hirarchy level is missing.");
}
// convenience for the user: You get an error message is you try a
// global import with directory (in case you did not validate your xml
// against the schema).
if(!directory.empty()) {
throw std::logic_error(std::string("Invalid XML content in global import of ") +
(importSource.empty() ? "/" : importSource) + ": You cannot have a directory in a global import.");
}
else {
// import into a location, we know the location name.
// add the directory first, then add the name source property
propertyName /= directory;
propertyName /= nameSource;
locationName = importLocationName;
Martin Killenberg
committed
}
Martin Killenberg
committed
// get the property name with . instead of /
propertyName.setAltSeparator(".");
auto autoPropertyDescription = std::make_shared<AutoPropertyDescription>(
processVariable, locationName, propertyName.getWithAltSeparator());
Martin Killenberg
committed
// we are importing, so all properties get the intended defaults (not
// individual settings)
autoPropertyDescription->hasHistory = getHasHistoryDefault(locationName);
autoPropertyDescription->isWriteable = getIsWriteableDefault(locationName);
autoPropertyDescription->macroPulseNumberSource = getMacroPusleNumberSourceDefault(locationName);
addDescription(autoPropertyDescription, {processVariable});
}
Martin Killenberg
committed
}
}
Martin Killenberg
committed
/********************************************************************************************************************/
void VariableMapper::prepareOutput(std::string xmlFile, std::set<std::string> inputVariables) {
clear();
_inputVariables = inputVariables;
xmlpp::DomParser parser;
// parser.set_validate();
parser.set_substitute_entities(); // We just want the text to be
// resolved/unescaped automatically.
parser.parse_file(xmlFile);
if(parser) {
// Walk the tree:
const xmlpp::Node* rootNode = parser.get_document()->get_root_node(); // deleted by DomParser.
for(auto const& mainNode : rootNode->get_children()) {
if(nodeIsWhitespace(mainNode)) continue;
if(dynamic_cast<xmlpp::CommentNode const*>(mainNode)) continue;
if(mainNode->get_name() == "location") {
processLocationNode(mainNode);
}
else if(mainNode->get_name() == "import") {
processImportNode(mainNode);
}
else if(mainNode->get_name() == "has_history") {
_globalDefaults.hasHistory = evaluateBool(getContentString(mainNode));
}
else if(mainNode->get_name() == "is_writeable") {
_globalDefaults.isWriteable = evaluateBool(getContentString(mainNode));
}
else if(mainNode->get_name() == "macro_pulse_number_source") {
_globalDefaults.macroPulseNumberSource = getContentString(mainNode);
}
else {
throw std::invalid_argument(
std::string("Error parsing xml file ") + xmlFile + ": Unknown node '" + mainNode->get_name() + "'");
}
else {
throw std::invalid_argument(std::string("Error parsing xml file ") + xmlFile + ". No document produced.");
}
Martin Killenberg
committed
/********************************************************************************************************************/
std::list<std::shared_ptr<PropertyDescription>> const& VariableMapper::getAllProperties() const {
return _descriptions;
}
Martin Killenberg
committed
/********************************************************************************************************************/
std::list<std::shared_ptr<PropertyDescription>> VariableMapper::getPropertiesInLocation(std::string location) const {
std::list<std::shared_ptr<PropertyDescription>> output;
Martin Killenberg
committed
for(auto const& variable : _descriptions) {
if(variable->location == location) {
(void)output.push_back(variable);
}
Martin Killenberg
committed
}
/********************************************************************************************************************/
std::unordered_set<std::string> VariableMapper::getAllLocations() const {
std::unordered_set<std::string> result;
for(auto const& variable : _descriptions) {
result.insert(variable->location);
}
return result;
}
/********************************************************************************************************************/
void VariableMapper::directImport(std::set<std::string> inputVariables) {
clear();
_inputVariables = inputVariables;
import("/",
""); // import from /, create location names from first level of the tree
Martin Killenberg
committed
}
/********************************************************************************************************************/
void VariableMapper::clear() {
_inputVariables.clear();
_usedInputVariables.clear();
_locationDefaults.clear();
_globalDefaults = PropertyAttributes();
_descriptions.clear();
Martin Killenberg
committed
}
/********************************************************************************************************************/
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
/// printing the map is useful for debugging
void VariableMapper::print(std::ostream& os) const {
os << "====== VariableMapper =====" << std::endl;
for(auto& description : _descriptions) {
description->print(os);
}
os << "======= Mapping End =======" << std::endl;
}
/********************************************************************************************************************/
bool VariableMapper::evaluateBool(std::string txt) {
transform(txt.begin(), txt.end(), txt.begin(), ::tolower);
if(txt == "false" or txt == "0") {
return false;
}
else if(txt == "true" or txt == "1") {
return true;
}
else {
throw std::invalid_argument(std::string("Error parsing xml file: could not convert to bool: ") + txt);
}
}
/********************************************************************************************************************/
std::string VariableMapper::getContentString(xmlpp::Node const* node) {
for(auto const& subNode : node->get_children()) {
const xmlpp::TextNode* nodeAsText = dynamic_cast<const xmlpp::TextNode*>(subNode);
if(nodeAsText) {
if(nodeAsText->is_white_space()) {
continue;
}
return nodeAsText->get_content();
Martin Killenberg
committed
}
}
throw std::invalid_argument(
std::string("Error parsing xml file: node ") + node->get_name() + " does not have text nodes as children.");
Martin Killenberg
committed
}
/********************************************************************************************************************/
bool VariableMapper::getHasHistoryDefault(std::string const& locationName) {
// if there is no default setting for the location, we will get the "default
// default" which has useHasHistoryDefault disabled which is auto-generated by
// the [] operator
auto locationInfo = _locationDefaults[locationName];
if(locationInfo.useHasHistoryDefault) {
return locationInfo.hasHistory;
}
else {
return _globalDefaults.hasHistory;
}
Martin Killenberg
committed
}
/********************************************************************************************************************/
bool VariableMapper::getIsWriteableDefault(std::string const& locationName) {
// if there is no default setting for the location, we will get the "default
// default" which has useIsWriteableDefault disabled which is auto-generated
// by the [] operator
auto locationInfo = _locationDefaults[locationName];
if(locationInfo.useIsWriteableDefault) {
return locationInfo.isWriteable;
}
else {
return _globalDefaults.isWriteable;
}
/********************************************************************************************************************/
std::string VariableMapper::getMacroPusleNumberSourceDefault(std::string const& locationName) {
// if there is no default setting for the location, we will get the "default
// default" which has useMacroPulseNumberSourceDefault disabled which is
// auto-generated by the [] operator
auto locationInfo = _locationDefaults[locationName];
if(locationInfo.useMacroPulseNumberSourceDefault) {
return locationInfo.macroPulseNumberSource;
}
else {
return _globalDefaults.macroPulseNumberSource;
}
Martin Christoph Hierholzer
committed
}
/********************************************************************************************************************/
std::string VariableMapper::getAttributeValue(const xmlpp::Element* node, std::string const& attributeName) {
auto attribute = node->get_attribute(attributeName);
if(!attribute) {
throw std::invalid_argument(
"Error parsing xml file. Attribute '" + attributeName + "' not found in node '" + node->get_name() + "'.");
}
return attribute->get_value();
Martin Killenberg
committed
}
Martin Killenberg
committed