ChimeraTK-DeviceAccess 03.25.00
Loading...
Searching...
No Matches
LogicalNameMapParser.cc
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
5
6#include "DeviceBackend.h"
7#include "Exception.h"
9
10#include <libxml++/libxml++.h>
11
12#include <stdexcept>
13
14namespace ChimeraTK {
15
16 template<>
17 std::string LogicalNameMapParser::getValueFromXmlSubnode<std::string>(const xmlpp::Node* node,
18 const std::string& subnodeName, BackendRegisterCatalogue<LNMBackendRegisterInfo> const& catalogue,
19 bool hasDefault, std::string defaultValue) {
20 auto list = node->find(subnodeName);
21 if(list.empty() && hasDefault) return defaultValue;
22 if(list.size() != 1) {
23 parsingError(node,
24 "Expected exactly one subnode of the type '" + subnodeName + "' below node '" + node->get_name() + "'.");
25 }
26 auto childList = list[0]->get_children();
27
28 std::string value;
29 for(auto& child : childList) {
30 if(!child) {
31 parsingError(child, "Got nullptr from parser library.");
32 }
33
34 // Check for CDATA node
35 const auto* cdataNode = dynamic_cast<const xmlpp::CdataNode*>(child);
36 if(cdataNode) {
37 value += cdataNode->get_content();
38 continue;
39 }
40
41 // check for plain text
42 const auto* textNode = dynamic_cast<const xmlpp::TextNode*>(child);
43 if(textNode) {
44 // put to stream buffer
45 value += textNode->get_content();
46 continue;
47 }
48
49 // check for reference
50 if(child->get_name() == "ref") {
51 auto childChildList = child->get_children();
52 const auto* refNameNode = dynamic_cast<const xmlpp::TextNode*>(childChildList.front());
53 if(refNameNode && childChildList.size() == 1) {
54 std::string regName = refNameNode->get_content();
55 if(!catalogue.hasRegister(regName)) {
56 parsingError(child, "Reference to constant '" + regName + "' could not be resolved.");
57 }
58 auto reg = catalogue.getBackendRegister(regName);
59 // auto reg_casted = boost::dynamic_pointer_cast<LNMBackendRegisterInfo>(reg);
60 // assert(reg_casted != nullptr); // this is our own catalogue
61 // fetch the value of the target constant
63 if(!reg.plugins.empty()) {
64 parsingError(childList.front(), "'" + regName + "' uses plugins which is not supported for <ref>");
65 }
66 // put to stream buffer
67 auto& lnmVariable = _variables[reg.name];
68 callForType(reg.valueType, [&](auto arg) {
69 std::stringstream buf;
70 buf << boost::fusion::at_key<decltype(arg)>(lnmVariable.valueTable.table).latestValue[0];
71 value = buf.str();
72 });
73 continue;
74 }
75 parsingError(child, "Reference to '" + regName + "' does not refer to a constant.");
76 }
77 else {
78 parsingError(child, "The <ref> node must contain only text.");
79 }
80 }
81
82 // check for parameter
83 if(child->get_name() == "par") {
84 auto childChildList = child->get_children();
85 const auto* parNameNode = dynamic_cast<const xmlpp::TextNode*>(childChildList.front());
86 if(parNameNode && childChildList.size() == 1) {
87 std::string parName = parNameNode->get_content();
88 if(_parameters.find(parName) == _parameters.end()) {
89 parsingError(child, "Parameter '" + parName + "' could not be resolved.");
90 }
91 // put to stream buffer
92 value += _parameters[parName];
93 continue;
94 }
95 parsingError(child, "The <par> node must contain only text.");
96 }
97
98 // neither found: throw error
99 parsingError(node,
100 "Node '" + subnodeName +
101 "' should contain only text, CDATA sections, references or parameters. Instead child '" +
102 child->get_name() + "' was found.");
103 }
104 return value;
105 } // namespace ChimeraTK
106
107 /********************************************************************************************************************/
108
109 template<typename T>
110 static T stringToValue(const std::string& valAsString) {
111 if constexpr(std::is_integral_v<T>) {
112 // special case of integers: handle prefix for hex or octal notation, like 0xff, 077
113 T value;
114 try {
115 // base=0 means auto detect base, depending on prefix
116 int64_t longVal = std::stoll(valAsString, nullptr, 0);
117 if(std::is_unsigned_v<T>) {
118 if(longVal < 0) {
119 throw std::out_of_range(std::string("negative!"));
120 }
121 }
122 else {
123 if(longVal < (int64_t)std::numeric_limits<T>::min()) {
124 throw std::out_of_range(std::string("too small!"));
125 }
126 }
127 // we need to exclude upper range check for uint64 since max val does not fit into longVal
128 if(!std::is_same_v<T, uint64_t> && longVal > (int64_t)std::numeric_limits<T>::max()) {
129 throw std::out_of_range(std::string("too large!"));
130 }
131 value = longVal;
132 }
133 catch(std::exception& e) {
134 std::string msg =
135 "LogicalNameMapParser: string '" + valAsString + "' cannot be converted to integer (" + e.what() + ")";
136 throw ChimeraTK::logic_error(msg);
137 }
138 return value;
139 }
140 else {
141 // generic case: put into stream
142 std::stringstream stream;
143 stream << valAsString;
144 // interpret stream as value of type T and return it
145 T value;
146 stream >> value;
147 return value;
148 }
149 }
150
151 template<typename T>
152 T LogicalNameMapParser::getValueFromXmlSubnode(const xmlpp::Node* node, const std::string& subnodeName,
153 BackendRegisterCatalogue<LNMBackendRegisterInfo> const& catalogue, bool hasDefault, T defaultValue) {
154 // obtain result as string
155 auto valAsString =
156 getValueFromXmlSubnode<std::string>(node, subnodeName, catalogue, hasDefault, std::to_string(defaultValue));
157
158 return stringToValue<T>(valAsString);
159 }
160
161 /********************************************************************************************************************/
162
163 template<typename T>
164 std::vector<T> LogicalNameMapParser::getValueVectorFromXmlSubnode(const xmlpp::Node* node,
165 const std::string& subnodeName, BackendRegisterCatalogue<LNMBackendRegisterInfo> const& catalogue) {
166 auto list = node->find(subnodeName);
167 if(list.empty()) {
168 parsingError(node,
169 "Expected at least one subnode of the type '" + subnodeName + "' below node '" + node->get_name() + "'.");
170 }
171
172 std::vector<T> valueVector;
173
174 for(auto& child : list) {
175 const auto* childElement = dynamic_cast<const xmlpp::Element*>(child);
176 if(!childElement) continue; // ignore comments etc.
177
178 // obtain index and resize valueVector if necessary
179 auto* indexAttr = childElement->get_attribute("index");
180 size_t index = 0;
181 if(indexAttr) {
182 index = std::stoi(indexAttr->get_value());
183 }
184 if(index >= valueVector.size()) valueVector.resize(index + 1);
185
186 // get content
187 auto childList = childElement->get_children();
188 if(childList.size() != 1) {
189 parsingError(childElement,
190 "Node '" + subnodeName +
191 "' should contain only text or exactly one reference, "
192 "instead multiple childs "
193 "were found.");
194 }
195
196 // check for plain text
197 const auto* textNode = dynamic_cast<const xmlpp::TextNode*>(childList.front());
198 if(textNode) {
199 std::string valAsString = textNode->get_content();
200 valueVector[index] = stringToValue<T>(valAsString);
201 continue;
202 }
203
204 // check for reference
205 if(childList.front()->get_name() == "ref") {
206 auto childChildList = childList.front()->get_children();
207 const auto* refNameNode = dynamic_cast<const xmlpp::TextNode*>(childChildList.front());
208 if(refNameNode && childChildList.size() == 1) {
209 std::string regName = refNameNode->get_content();
210 if(!catalogue.hasRegister(regName)) {
211 parsingError(childList.front(), "Reference to constant '" + regName + "' could not be resolved.");
212 }
213 auto reg = catalogue.getBackendRegister(regName);
214 // fetch the value of the target constant
216 if(!reg.plugins.empty()) {
217 parsingError(childList.front(), "'" + regName + "' uses plugins which is not supported for <ref>");
218 }
219 // convert via string
220 std::stringstream buf;
221 auto& lnmVariable = _variables[reg.name];
222 callForType(reg.valueType, [&](auto arg) {
223 buf << boost::fusion::at_key<decltype(arg)>(lnmVariable.valueTable.table).latestValue[0];
224 });
225 std::string strVal;
226 buf >> strVal;
227 valueVector[index] = stringToValue<T>(strVal);
228 continue;
229 }
230 parsingError(childList.front(), "Reference to '" + regName + "' does not refer to a constant.");
231 }
232 else {
233 parsingError(childList.front(), "The <ref> node must contain only text.");
234 }
235 }
236
237 if(childList.front()->get_name() == "par") {
238 auto childChildList = childList.front()->get_children();
239 const auto* parNameNode = dynamic_cast<const xmlpp::TextNode*>(childChildList.front());
240 if(parNameNode && childChildList.size() == 1) {
241 std::string parName = parNameNode->get_content();
242 if(_parameters.find(parName) == _parameters.end()) {
243 parsingError(childList.front(), "Parameter '" + parName + "' could not be resolved.");
244 }
245 valueVector[index] = stringToValue<T>(_parameters[parName]);
246 continue;
247 }
248 parsingError(childList.front(), "The <par> node must contain only text.");
249 }
250
251 // neither found: throw error
252 parsingError(node,
253 "Node '" + subnodeName + "' should contain only text or exactly one reference, instead child '" +
254 childList.front()->get_name() + "' was found.");
255 }
256
257 return valueVector;
258 }
259
260 /********************************************************************************************************************/
261
263 _fileName = fileName;
264
266
267 // parse the file into a DOM structure
268 xmlpp::DomParser parser;
269 try {
270 parser.parse_file(fileName);
271 }
272 catch(xmlpp::exception& e) {
273 throw ChimeraTK::logic_error("Error opening the xlmap file '" + fileName + "': " + e.what());
274 }
275
276 // get root element
277 auto* const root = parser.get_document()->get_root_node();
278 if(root->get_name() != "logicalNameMap") {
279 parsingError(root, "Expected 'logicalNameMap' tag instead of: " + root->get_name());
280 }
281
282 // parsing loop
283 for(const auto& child : root->get_children()) {
284 // cast into element, ignore if not an element (e.g. comment)
285 const auto* element = dynamic_cast<const xmlpp::Element*>(child);
286 if(!element) continue;
287
288 // parse the element
289 parseElement(RegisterPath(), element, catalogue);
290 }
291
292 return catalogue;
293 }
294
295 void LogicalNameMapParser::parseElement(const RegisterPath& currentPath, const xmlpp::Element* element,
297 // module tag found: look for registers and sub-modules in module
298 if(element->get_name() == "module") {
299 // obtain name of the module
300 auto* nameAttr = element->get_attribute("name");
301 if(!nameAttr) {
302 parsingError(element, "Missing name attribute of 'module' tag.");
303 }
304 std::string moduleName = nameAttr->get_value();
305
306 // iterate over children in module
307 for(const auto& child : element->get_children()) {
308 // cast into element, ignore if not an element (e.g. comment)
309 const auto* childElement = dynamic_cast<const xmlpp::Element*>(child);
310 if(!childElement) continue;
311
312 // parse the element
313 parseElement(currentPath / moduleName, childElement, catalogue);
314 }
315 }
316
317 // register tag found: create new entry in map
318 else {
319 // obtain the type
320 std::string type = element->get_name();
321
322 // obtain name of logical register
323 auto* nameAttr = element->get_attribute("name");
324 if(!nameAttr) {
325 parsingError(element, "Missing name attribute of '" + type + "' tag.");
326 }
327 RegisterPath registerName = currentPath / std::string(nameAttr->get_value());
328
329 // create new RegisterInfo object
331 info.name = registerName;
332 if(type == "redirectedRegister") {
334 info.deviceName = getValueFromXmlSubnode<std::string>(element, "targetDevice", catalogue);
335 info.registerName = getValueFromXmlSubnode<std::string>(element, "targetRegister", catalogue);
336 info.firstIndex = getValueFromXmlSubnode<unsigned int>(element, "targetStartIndex", catalogue, true, 0);
337 info.length = getValueFromXmlSubnode<unsigned int>(element, "numberOfElements", catalogue, true, 0);
338 info.nChannels = 0;
339 }
340 else if(type == "redirectedChannel") {
342 info.deviceName = getValueFromXmlSubnode<std::string>(element, "targetDevice", catalogue);
343 info.registerName = getValueFromXmlSubnode<std::string>(element, "targetRegister", catalogue);
344 info.channel = getValueFromXmlSubnode<unsigned int>(element, "targetChannel", catalogue);
345 info.firstIndex = getValueFromXmlSubnode<unsigned int>(element, "targetStartIndex", catalogue, true, 0);
346 info.length = getValueFromXmlSubnode<unsigned int>(element, "numberOfElements", catalogue, true, 0);
347 info.nChannels = 1;
348 }
349 else if(type == "redirectedBit") {
351 info.deviceName = getValueFromXmlSubnode<std::string>(element, "targetDevice", catalogue);
352 info.registerName = getValueFromXmlSubnode<std::string>(element, "targetRegister", catalogue);
353 info.bit = getValueFromXmlSubnode<unsigned int>(element, "targetBit", catalogue);
354 info.firstIndex = 0;
355 info.length = 0;
356 info.nChannels = 1;
357 }
358 else if(type == "constant") {
359 std::string constantType = getValueFromXmlSubnode<std::string>(element, "type", catalogue);
360 if(constantType == "integer") constantType = "int32";
362 info.valueType = DataType(constantType);
363 auto& lnmVariable = _variables[info.name];
364 callForType(info.valueType, [&](auto arg) {
365 boost::fusion::at_key<decltype(arg)>(lnmVariable.valueTable.table).latestValue =
366 this->getValueVectorFromXmlSubnode<decltype(arg)>(element, "value", catalogue);
367 });
368 lnmVariable.isConstant = true;
369 lnmVariable.valueType = info.valueType;
370 info.firstIndex = 0;
371 info.length = getValueFromXmlSubnode<unsigned int>(element, "numberOfElements", catalogue, true, 1);
372 info.nChannels = 1;
373 info.writeable = false;
374 info.readable = true;
376 }
377 else if(type == "variable") {
378 std::string constantType = getValueFromXmlSubnode<std::string>(element, "type", catalogue);
379 if(constantType == "integer") constantType = "int32";
381 info.valueType = DataType(constantType);
382 auto& lnmVariable = _variables[info.name];
383 callForType(info.valueType, [&](auto arg) {
384 boost::fusion::at_key<decltype(arg)>(lnmVariable.valueTable.table).latestValue =
385 this->getValueVectorFromXmlSubnode<decltype(arg)>(element, "value", catalogue);
386 });
387 lnmVariable.isConstant = false;
388 lnmVariable.valueType = info.valueType;
389 info.firstIndex = 0;
390 info.length = getValueFromXmlSubnode<unsigned int>(element, "numberOfElements", catalogue, true, 1);
391 info.nChannels = 1;
392 info.writeable = true;
393 info.readable = true;
396 }
397 else {
398 parsingError(element, "Wrong logical register type: " + type);
399 }
400
401 // iterate over children of the register to find plugins
402 for(const auto& child : element->get_children()) {
403 // cast into element, ignore if not an element (e.g. comment)
404 const auto* childElement = dynamic_cast<const xmlpp::Element*>(child);
405 if(!childElement) continue;
406 if(childElement->get_name() != "plugin") continue; // look only for plugins
407
408 // get name of plugin
409 auto* pluginNameAttr = childElement->get_attribute("name");
410 if(!pluginNameAttr) {
411 parsingError(childElement, "Missing name attribute of 'plugin' tag.");
412 }
413 std::string pluginName = pluginNameAttr->get_value();
414
415 // collect parameters
416 std::map<std::string, std::string> parameters;
417 for(const auto& paramchild : childElement->get_children()) {
418 // cast into element, ignore if not an element (e.g. comment)
419 const auto* paramElement = dynamic_cast<const xmlpp::Element*>(paramchild);
420 if(!paramElement) continue;
421 if(paramElement->get_name() != "parameter") {
422 parsingError(paramElement, "Unexpected element '" + paramElement->get_name() + "' inside plugin tag.");
423 }
424
425 // get name of parameter
426 auto* parameterNameAttr = paramElement->get_attribute("name");
427 if(!pluginNameAttr) {
428 parsingError(paramElement, "Missing name attribute of 'parameter' tag.");
429 }
430 std::string parameterName = parameterNameAttr->get_value();
431
432 // get value of parameter and store in map
433 parameters[parameterName] =
434 getValueFromXmlSubnode<std::string>(childElement, "parameter[@name='" + parameterName + "']", catalogue);
435 }
436
437 // create instance of plugin and add to the list in the register info
438 info.plugins.push_back(LNMBackend::makePlugin(info, info.plugins.size(), pluginName, parameters));
439 }
440
441 // add register to catalogue
442 catalogue.addRegister(info);
443 }
444 }
445
446 /********************************************************************************************************************/
447
448 void LogicalNameMapParser::parsingError(const xmlpp::Node* node, const std::string& message) {
450 "Error parsing the xlmap file '" + _fileName + "'(" + std::to_string(node->get_line()) + ") : " + message);
451 }
452
453} // namespace ChimeraTK
Interface for backends to the register catalogue.
void addRegister(const BackendRegisterInfo &registerInfo)
Add register information to the catalogue.
virtual BackendRegisterInfo getBackendRegister(const RegisterPath &registerPathName) const
Note: Override this function if backend has "hidden" registers which are not added to the map and hen...
bool hasRegister(const RegisterPath &registerPathName) const override
Check if register with the given path name exists.
Class describing the actual payload data format of a register in an abstract manner.
A class to describe which of the supported data types is used.
RegisterInfo structure for the LogicalNameMappingBackend.
DataType valueType
Data type of CONSTANT or VARIABLE type.
unsigned int firstIndex
The first index in the range.
std::string registerName
The target register name.
AccessModeFlags supportedFlags
Supported AccessMode flags.
bool readable
Flag if the register is readable.
unsigned int nChannels
The number of channels of the logical register.
bool writeable
Flag if the register is writeable.
unsigned int length
The length of the range (i.e.
std::string deviceName
The target device alias.
TargetType targetType
Type of the target.
RegisterPath name
Name of the register.
unsigned int bit
The bit of the target register (if TargetType::BIT)
std::vector< boost::shared_ptr< LNMBackend::AccessorPluginBase > > plugins
List of accessor plugins enabled for this register.
unsigned int channel
The channel of the target 2D register (if TargetType::CHANNEL)
std::map< std::string, LNMVariable > & _variables
Reference to the variables map inside the LNM backend.
BackendRegisterCatalogue< LNMBackendRegisterInfo > parseFile(const std::string &fileName)
parse the given XML file
std::vector< ValueType > getValueVectorFromXmlSubnode(const xmlpp::Node *node, const std::string &subnodeName, BackendRegisterCatalogue< LNMBackendRegisterInfo > const &catalogue)
std::string _fileName
file name of the logical map
ValueType getValueFromXmlSubnode(const xmlpp::Node *node, const std::string &subnodeName, BackendRegisterCatalogue< LNMBackendRegisterInfo > const &catalogue, bool hasDefault=false, ValueType defaultValue=ValueType())
Build a Value object for a given subnode.
void parseElement(const RegisterPath &currentPath, const xmlpp::Element *element, BackendRegisterCatalogue< LNMBackendRegisterInfo > &catalogue)
called inside parseFile() to parse an XML element and its sub-elements recursively
std::map< std::string, std::string > _parameters
actual register info map (register name to target information)
void parsingError(const xmlpp::Node *node, const std::string &message)
throw a parsing error with more information
Class to store a register path name.
Exception thrown when a logic error has occured.
Definition Exception.h:51
boost::shared_ptr< AccessorPluginBase > makePlugin(LNMBackendRegisterInfo info, size_t pluginIndex, const std::string &name, const std::map< std::string, std::string > &parameters)
Factory function for accessor plugins.
std::string LogicalNameMapParser::getValueFromXmlSubnode< std::string >(const xmlpp::Node *node, const std::string &subnodeName, BackendRegisterCatalogue< LNMBackendRegisterInfo > const &catalogue, bool hasDefault, std::string defaultValue)
void callForType(const std::type_info &type, LAMBDATYPE lambda)
Helper function for running code which uses some compile-time type that is specified at runtime as a ...
@ wait_for_new_data
Make any read blocking until new data has arrived since the last read.
std::string to_string(const std::string &v)