Newer
Older
Martin Killenberg
committed
#include "VariableMapper.h"
#include <libxml++/libxml++.h>
Martin Killenberg
committed
#include "splitStringAtFirstSlash.h"
#include <mtca4u/RegisterPath.h>
#include <iostream>
Martin Killenberg
committed
#include <locale>
#include <algorithm>
Martin Killenberg
committed
namespace ChimeraTK{
Martin Killenberg
committed
VariableMapper & VariableMapper::getInstance(){
static VariableMapper instance;
return instance;
}
Martin Killenberg
committed
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
Martin Killenberg
committed
void VariableMapper::processLocationNode(xmlpp::Node const * locationNode){
Martin Killenberg
committed
const xmlpp::Element* location = dynamic_cast<const xmlpp::Element*>(locationNode);
std::string locationName = location->get_attribute("name")->get_value();
Martin Killenberg
committed
for (auto const & node : location->get_children()){
if (nodeIsWhitespace(node)) continue;
Martin Killenberg
committed
if (dynamic_cast<xmlpp::CommentNode const *>(node)) continue;
Martin Killenberg
committed
if (node->get_name() == "property"){
Martin Killenberg
committed
processPropertyNode(node, locationName);
Martin Killenberg
committed
}else if (node->get_name() == "import"){
Martin Killenberg
committed
processImportNode(node, locationName);
Martin Killenberg
committed
}else if (node->get_name() == "has_history"){
Martin Killenberg
committed
auto & locationInfo = _locationDefaults[locationName];
locationInfo.useHasHistoryDefault = true;
locationInfo.hasHistory = evaluateBool(getContentString(node));
Martin Killenberg
committed
}else if (node->get_name() == "is_writeable"){
auto & locationInfo = _locationDefaults[locationName];
locationInfo.useIsWriteableDefault = true;
locationInfo.isWriteable = evaluateBool(getContentString(node));
}else if (node->get_name() == "D_spectrum"){
//FIXME: Ugly hack: running it as a normal node makes the tests pass because the factory still creates specta by default
processPropertyNode(node, locationName);
std::cout << "FIXME: Implement processSpectrumNode" << std::endl;
Martin Killenberg
committed
}else{
throw std::invalid_argument(std::string("Error parsing xml file in location ") + locationName + ": Unknown node '"+node->get_name()+"'");
Martin Killenberg
committed
}
Martin Killenberg
committed
}
Martin Killenberg
committed
}
Martin Killenberg
committed
void VariableMapper::processPropertyNode(xmlpp::Node const * propertyNode, std::string locationName){
Martin Killenberg
committed
const xmlpp::Element* property = dynamic_cast<const xmlpp::Element*>(propertyNode);
std::string source = property->get_attribute("source")->get_value();
std::string name;
const xmlpp::Attribute* nameAttribute = property->get_attribute("name");
if (nameAttribute){
name = nameAttribute->get_value();
}else{
if (source[0] == '/'){
name = source.substr(1);
}else{
name = source;
}
// replace / with . in name
name = std::regex_replace(name, std::regex("/"), ".");
Martin Killenberg
committed
}
std::string absoluteSource;
if (source[0] == '/'){
absoluteSource=source;
}else{
absoluteSource=std::string("/")+locationName+"/"+source;
}
Martin Killenberg
committed
// prepare the property description
auto autoPropertyDescription = std::make_shared<AutoPropertyDescription>(absoluteSource, locationName, name);
Martin Killenberg
committed
Martin Killenberg
committed
auto hasHistoryNodes = property->get_children("has_history");
if (!hasHistoryNodes.empty()){
autoPropertyDescription->hasHistory= evaluateBool(getContentString(hasHistoryNodes.front()));
Martin Killenberg
committed
}else{
autoPropertyDescription->hasHistory = getHasHistoryDefault(locationName);
Martin Killenberg
committed
}
auto isWriteableNodes = property->get_children("is_writeable");
if (!isWriteableNodes.empty()){
autoPropertyDescription->isWriteable = evaluateBool(getContentString(isWriteableNodes.front()));
autoPropertyDescription->isWriteable = getIsWriteableDefault(locationName);
Martin Killenberg
committed
}
Martin Killenberg
committed
Martin Killenberg
committed
auto existingCandidate = _inputSortedDescriptions.find(absoluteSource);
if (existingCandidate != _inputSortedDescriptions.end()){
auto existingPropertyDescription = existingCandidate->second;
throw std::logic_error(std::string("Invalid XML content for ") + absoluteSource + " -> "+ locationName+"/" +name +". Process variable already defined to point to " + existingPropertyDescription->location + "/" + existingPropertyDescription->name);
Martin Killenberg
committed
}else{
_inputSortedDescriptions[absoluteSource] = std::dynamic_pointer_cast<PropertyDescription>(autoPropertyDescription);
Martin Killenberg
committed
}
Martin Killenberg
committed
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();
}
}
Martin Killenberg
committed
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);
Martin Killenberg
committed
}
Martin Killenberg
committed
}
Martin Killenberg
committed
void VariableMapper::import(std::string importSource, std::string importLocationName,
std::string directory){
Martin Killenberg
committed
// a slash will be added after the source, so we make the source empty for an import of everything
if (importSource == "/"){
importSource = "";
}
Martin Killenberg
committed
Martin Killenberg
committed
// loop source tree, cut beginning, replace / with _ and add a property
for (auto const & processVariable : _inputVariables){
if (_inputSortedDescriptions.find(processVariable) != _inputSortedDescriptions.end()){
continue;
}
Martin Killenberg
committed
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 .
mtca4u::RegisterPath propertyName;
Martin Killenberg
committed
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.");
Martin Killenberg
committed
}
// 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.");
Martin Killenberg
committed
}else{
// import into a location, we know the location name.
// add the directory first, then add the name source property
propertyName /= directory;
propertyName /= nameSource;
Martin Killenberg
committed
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
Martin Killenberg
committed
// we are importing, so all properties get the intended defaults (not individual settings)
autoPropertyDescription->hasHistory = getHasHistoryDefault(locationName);
autoPropertyDescription->isWriteable = getIsWriteableDefault(locationName);
Martin Killenberg
committed
_inputSortedDescriptions[processVariable] = std::dynamic_pointer_cast<PropertyDescription>(autoPropertyDescription);
Martin Killenberg
committed
}
}
}
Martin Killenberg
committed
void VariableMapper::prepareOutput(std::string xmlFile, std::set< std::string > inputVariables){
Martin Killenberg
committed
clear();
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()){
Martin Killenberg
committed
if (nodeIsWhitespace(mainNode)) continue;
if (mainNode->get_name() == "location"){
Martin Killenberg
committed
processLocationNode(mainNode);
Martin Killenberg
committed
}else if (mainNode->get_name() == "import"){
Martin Killenberg
committed
processImportNode(mainNode);
Martin Killenberg
committed
}else if (mainNode->get_name() == "has_history"){
_globalDefaults.hasHistory = evaluateBool(getContentString(mainNode));
}else if (mainNode->get_name() == "is_writeable"){
_globalDefaults.isWriteable = evaluateBool(getContentString(mainNode));
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::map< std::string, std::shared_ptr<VariableMapper::PropertyDescription> > const & VariableMapper::getAllProperties() const{
Martin Killenberg
committed
return _inputSortedDescriptions;
}
std::map< std::string, std::shared_ptr<VariableMapper::PropertyDescription> > VariableMapper::getPropertiesInLocation(std::string location) const{
std::map< std::string, std::shared_ptr<PropertyDescription> > output;
Martin Killenberg
committed
for (auto const & variable : _inputSortedDescriptions){
if (variable.second->location == location){
Martin Killenberg
committed
// no need to check return value. There cannot be duplicate entries because the values are
// coming from another map.
(void) output.insert( variable );
}
}
return output;
}
void VariableMapper::directImport(std::set< std::string > inputVariables){
Martin Killenberg
committed
clear();
Martin Killenberg
committed
import("/",""); // import from /, create location names from first level of the tree
}
void VariableMapper::clear(){
_inputVariables.clear();
_locationDefaults.clear();
_globalDefaults = PropertyAttributes();
_inputSortedDescriptions.clear();
}
Martin Killenberg
committed
Martin Killenberg
committed
/// printing the map is useful for debugging
void VariableMapper::print(std::ostream & os) const {
for (auto & variableNameAndPropertyDescription : _inputSortedDescriptions ){
auto & variableName = variableNameAndPropertyDescription.first;
auto & propertyDescription = variableNameAndPropertyDescription.second;
os << variableName << " -> " << propertyDescription->location << " / " << propertyDescription->name
// << " hasHistory:" << propertyDescription.hasHistory
// << " isWriteable:" << propertyDescription.isWriteable
Martin Killenberg
committed
<< std::endl;
Martin Killenberg
committed
}
}
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);
}
}
Martin Killenberg
committed
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();
}
}
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;
}
}
bool VariableMapper::getIsWriteableDefault(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.useIsWriteableDefault){
return locationInfo.isWriteable;
}else{
return _globalDefaults.isWriteable;
}
}
Martin Killenberg
committed
bool VariableMapper::compareDoocsTypeDescriptions(boost::any const &description1, boost::any const &description2){
if (description1.empty()){
return description2.empty();
}
// this is not symmetric. If type 2 is unknown the comparison will just return false, not throw.
if (description1.type() == typeid(SpectrumDescription) ){
return castAndCompare<SpectrumDescription>(description1, description2);
}
throw std::invalid_argument("unknown doocs type description");
}
Martin Killenberg
committed
} // namespace ChimeraTK