RcsPySim
A robot control and simulation library
PropertySourceDict.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2  Copyright (c) 2020, Fabio Muratore, Honda Research Institute Europe GmbH, and
3  Technical University of Darmstadt.
4  All rights reserved.
5 
6  Redistribution and use in source and binary forms, with or without
7  modification, are permitted provided that the following conditions are met:
8  1. Redistributions of source code must retain the above copyright
9  notice, this list of conditions and the following disclaimer.
10  2. Redistributions in binary form must reproduce the above copyright
11  notice, this list of conditions and the following disclaimer in the
12  documentation and/or other materials provided with the distribution.
13  3. Neither the name of Fabio Muratore, Honda Research Institute Europe GmbH,
14  or Technical University of Darmstadt, nor the names of its contributors may
15  be used to endorse or promote products derived from this software without
16  specific prior written permission.
17 
18  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  DISCLAIMED. IN NO EVENT SHALL FABIO MURATORE, HONDA RESEARCH INSTITUTE EUROPE GMBH,
22  OR TECHNICAL UNIVERSITY OF DARMSTADT BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
26  IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  POSSIBILITY OF SUCH DAMAGE.
29 *******************************************************************************/
30 
31 #include "PropertySourceDict.h"
32 #include "../util/pybind_dict_utils.h"
33 #include "../util/type_casters.h"
34 
35 #include <libxml/tree.h>
36 
37 #include <algorithm>
38 
39 namespace Rcs
40 {
41 
43  pybind11::dict dict,
44  PropertySourceDict* parent, const char* prefix, bool exists) :
45  dict(dict), parent(parent), prefix(prefix), _exists(exists)
46 {
47  // internal ctor, sets all parts
48 }
49 
51  dict(dict), parent(NULL), prefix(NULL), _exists(true)
52 {
53  // nothing special to do
54 }
55 
57 {
58  // delete children where needed
59  for (auto& it: children) {
60  if (it.second != empty()) {
61  delete it.second;
62  }
63  }
64  for (auto& it: listChildren) {
65  for (auto le : it.second) {
66  delete le;
67  }
68  }
69 }
70 
72 {
73  return _exists;
74 }
75 
76 bool PropertySourceDict::getProperty(std::string& out, const char* property)
77 {
78  if (!_exists) {
79  return false;
80  }
81  // try to retrieve
82  pybind11::handle elem;
83  if (!try_get(dict, property, elem) || elem.is_none()) {
84  return false;
85  }
86 
87  // cast to string
88  pybind11::str str(elem);
89  out = str;
90  return true;
91 }
92 
93 bool PropertySourceDict::getProperty(std::vector<std::string> &out, const char *property) {
94  if (!_exists) {
95  return false;
96  }
97  // try to retrieve
98  pybind11::handle elem;
99  if (!try_get(dict, property, elem) || elem.is_none()) {
100  return false;
101  }
102 
103  if (py::isinstance<py::str>(elem)) {
104  py::str str(elem);
105  out.push_back(str);
106  return true;
107  } else if (py::isinstance<py::iterable>(elem)) {
108  for (auto part : elem) {
109  py::str str(part);
110  out.push_back(str);
111  }
112  return true;
113  } else {
114  std::ostringstream os;
115  os << "Unsupported value for property entry at ";
116  appendPrefix(os);
117  os << property << ": Expected string or iterable of string, but got ";
118  auto childType = py::str(elem.get_type());
119  os << childType.cast<std::string>();
120  throw std::invalid_argument(os.str());
121  }
122 }
123 
124 bool PropertySourceDict::getProperty(double& out, const char* property)
125 {
126  if (!_exists) {
127  return false;
128  }
129  // try to retrieve
130  pybind11::handle elem;
131  if (!try_get(dict, property, elem) || elem.is_none()) {
132  return false;
133  }
134 
135  // cast
136  out = elem.cast<double>();
137  return true;
138 }
139 
140 bool PropertySourceDict::getProperty(int& out, const char* property)
141 {
142  if (!_exists) {
143  return false;
144  }
145  // try to retrieve
146  pybind11::handle elem;
147  if (!try_get(dict, property, elem) || elem.is_none()) {
148  return false;
149  }
150 
151  // cast
152  out = elem.cast<int>();
153  return true;
154 }
155 
156 bool PropertySourceDict::getProperty(MatNd*& out, const char* property)
157 {
158  if (!_exists) {
159  return false;
160  }
161  // try to retrieve
162  pybind11::handle elem;
163  if (!try_get(dict, property, elem) || elem.is_none()) {
164  return false;
165  }
166 
167  // cast. must use internals since handle.cast() doesn't allow pointers
168  py::detail::make_caster<MatNd> caster;
169  py::detail::load_type(caster, elem);
170  out = MatNd_clone(py::detail::cast_op<MatNd*>(caster));
171  return true;
172 }
173 
174 bool PropertySourceDict::getPropertyBool(const char* property, bool def)
175 {
176  if (!_exists) {
177  return def;
178  }
179  return get_cast<bool>(dict, property, def);
180 }
181 
183 {
184  std::string prefixStr = prefix;
185  // check if it exists already
186  auto iter = children.find(prefixStr);
187  if (iter != children.end()) {
188  return iter->second;
189  }
190  // try to get child dict
191  pybind11::object child;
192  bool exists = this->_exists;
193  if (exists) {
194  exists = try_get(dict, prefix, child) && !child.is_none();
195  }
196  if (!exists) {
197  // not found, use a fresh dict
198  child = pybind11::dict();
199  }
200  else if (!pybind11::isinstance<pybind11::dict>(child)) {
201  // exists but not a dict
202  std::ostringstream os;
203  os << "Unsupported value for child property entry at ";
204  appendPrefix(os);
205  os << prefix << ": Expected dict, but got ";
206  auto childType = pybind11::str(child.get_type());
207  os << childType.cast<std::string>();
208  throw std::invalid_argument(os.str());
209  }
210  auto* result = new PropertySourceDict(child, this, prefix, exists);
211  children[prefixStr] = result;
212  return result;
213 }
214 
215 void PropertySourceDict::appendPrefix(std::ostream& os)
216 {
217  if (parent != NULL) {
218  parent->appendPrefix(os);
219  os << prefix << ".";
220  }
221 }
222 
223 // most of these are easy
225  const char* property,
226  const std::string& value)
227 {
228  dict[property] = value;
229 
230  onWrite();
231 }
232 
233 void PropertySourceDict::setProperty(const char* property, bool value)
234 {
235  dict[property] = value;
236 
237  onWrite();
238 }
239 
240 void PropertySourceDict::setProperty(const char* property, int value)
241 {
242  dict[property] = value;
243 
244  onWrite();
245 }
246 
247 void PropertySourceDict::setProperty(const char* property, double value)
248 {
249  dict[property] = value;
250 
251  onWrite();
252 }
253 
254 void PropertySourceDict::setProperty(const char* property, MatNd* value)
255 {
256  // make sure we use a copy
257  dict[property] = pybind11::cast(value, pybind11::return_value_policy::copy);
258 
259  onWrite();
260 }
261 
263 {
264  if (parent != NULL) {
265  // notify parent
266  parent->onWrite();
267 
268 
269  // put to parent if new
270  if (!_exists) {
271  parent->dict[prefix] = dict;
272  }
273  }
274 }
275 
276 const std::vector<PropertySource*>& PropertySourceDict::getChildList(const char* prefix)
277 {
278  std::string prefixStr = prefix;
279  // Check if it exists already
280  auto iter = listChildren.find(prefixStr);
281  if (iter != listChildren.end()) {
282  return iter->second;
283  }
284  // Create new entry
285  auto& list = listChildren[prefixStr];
286  // Retrieve entry from dict
287  pybind11::object child;
288  bool exists = this->_exists;
289  if (exists) {
290  exists = try_get(dict, prefix, child) && !child.is_none();
291  }
292  if (exists) {
293  // Parse entry sequence
294  if (pybind11::isinstance<pybind11::dict>(child)) {
295  // Single element
296  list.push_back(new PropertySourceDict(child, this, prefix, true));
297  }
298  else if (pybind11::isinstance<pybind11::iterable>(child)) {
299  // Element sequence
300  unsigned int i = 0;
301  for (auto elem : child) {
302  if (pybind11::isinstance<pybind11::dict>(elem)) {
303  std::ostringstream itemPrefix;
304  itemPrefix << prefix << "[" << i << "]";
305  // Add element
306  list.push_back(new PropertySourceDict(pybind11::reinterpret_borrow<pybind11::dict>(elem), this,
307  itemPrefix.str().c_str(), true));
308  }
309  else {
310  // Exists but not a dict
311  std::ostringstream os;
312  os << "Unsupported element for child property entry at ";
313  appendPrefix(os);
314  os << prefix << "[" << i << "]" << ": Expected dict, but got ";
315  auto childType = pybind11::str(child.get_type());
316  os << childType.cast<std::string>();
317  throw std::invalid_argument(os.str());
318  }
319  i++;
320  }
321  }
322  else {
323  // Exists but not a dict
324  std::ostringstream os;
325  os << "Unsupported value for child list property entry at ";
326  appendPrefix(os);
327  os << prefix << ": Expected list of dict or dict, but got ";
328  auto childType = pybind11::str(child.get_type());
329  os << childType.cast<std::string>();
330  throw std::invalid_argument(os.str());
331  }
332  }
333 
334  return list;
335 }
336 
338 {
339  // use python deepcopy to copy the dict.
340  py::object copymod = py::module::import("copy");
341  py::dict cpdict = copymod.attr("deepcopy")(dict);
342 
343  return new PropertySourceDict(cpdict);
344 }
345 
346 static std::string spaceJoinedString(py::handle iterable)
347 {
348  std::ostringstream os;
349 
350  for (auto ele : iterable) {
351  // Append stringified element
352  os << py::cast<std::string>(py::str(ele)) << ' ';
353  }
354  std::string result = os.str();
355  // Remove trailing ' '
356  if (!result.empty()) {
357  result.erase(result.end() - 1);
358  }
359  return result;
360 }
361 
362 static xmlNodePtr dict2xml(const py::dict& data, xmlDocPtr doc, const char* nodeName)
363 {
364  xmlNodePtr node = xmlNewDocNode(doc, NULL, BAD_CAST nodeName, NULL);
365  // add children
366  for (auto entry : data) {
367  auto key = py::cast<std::string>(entry.first);
368  auto value = entry.second;
369 
370  std::string valueStr;
371  // check value type
372  if (py::isinstance<py::dict>(value)) {
373  // as sub node
374  xmlNodePtr subNode = dict2xml(py::reinterpret_borrow<py::dict>(value), doc, key.c_str());
375  xmlAddChild(node, subNode);
376  continue;
377  }
378  else if (py::isinstance<py::str>(value)) {
379  // handle strings before catching them as iterable
380  valueStr = py::cast<std::string>(value);
381  }
382  else if (py::isinstance<py::array>(value)) {
383  // handle arrays first before catching them as iterable
384  // we don't really have a proper 2d format, just flatten if needed
385 
386  valueStr = spaceJoinedString(value.attr("flatten")().attr("tolist")());
387  }
388  else if (py::isinstance<py::iterable>(value)) {
389  // an iterable
390 
391  auto it = value.begin();
392  // check if empty
393  if (it == value.end()) continue;
394 
395  // check if child list
396  if (std::all_of(it, value.end(), [](py::handle ele) {
397  return py::isinstance<py::dict>(ele);
398  })) {
399  // child list, add one element per child
400  for (auto ele : value) {
401  xmlNodePtr subNode = dict2xml(py::reinterpret_borrow<py::dict>(ele), doc, key.c_str());
402  xmlAddChild(node, subNode);
403  }
404  continue;
405  }
406  // treat as array-like; a space-joined string
407  valueStr = spaceJoinedString(value);
408  }
409  else {
410  // use default str() format for other cases
411  valueStr = py::cast<std::string>(py::str(value));
412  }
413 
414  xmlSetProp(node, BAD_CAST key.c_str(), BAD_CAST valueStr.c_str());
415  }
416 
417  return node;
418 }
419 
420 void PropertySourceDict::saveXML(const char* fileName, const char* rootNodeName)
421 {
422  // create xml doc
423  std::unique_ptr<xmlDoc, void (*)(xmlDocPtr)> doc(xmlNewDoc(NULL), xmlFreeDoc);
424 
425  // convert root node
426  xmlNodePtr rootNode = dict2xml(dict, doc.get(), rootNodeName);
427  xmlDocSetRootElement(doc.get(), rootNode);
428 
429  // perform save
430  xmlIndentTreeOutput = 1;
431  xmlSaveFormatFile(fileName, doc.get(), 1);
432 }
433 
434 } /* namespace Rcs */
435 
virtual void saveXML(const char *fileName, const char *rootNodeName)
virtual PropertySink * getChild(const char *prefix)
static std::string spaceJoinedString(py::handle iterable)
static xmlNodePtr dict2xml(const py::dict &data, xmlDocPtr doc, const char *nodeName)
virtual PropertySink * clone() const
PropertySourceDict * parent
T get_cast(const py::dict &dict, const char *key, T default_, bool noneIsEmpty=true)
void appendPrefix(std::ostream &)
virtual const std::vector< PropertySource * > & getChildList(const char *prefix)
static PropertySource * empty()
PropertySourceDict(pybind11::dict dict, PropertySourceDict *parent, const char *prefix, bool exists)
virtual bool getProperty(std::string &out, const char *property)
std::map< std::string, PropertySourceDict * > children
bool try_get(const py::dict &dict, const char *key, T &var)
std::map< std::string, std::vector< PropertySource * > > listChildren
virtual bool getPropertyBool(const char *property, bool def=false)
virtual void setProperty(const char *property, const std::string &value)