core.py
Go to the documentation of this file.
2 import sys
3 import copy
4 
5 # @todo Get rid of "import *"
6 # @todo Make this work with decorators
7 
8 # Is this reflection or serialization? I think it's serialization...
9 # Rename?
10 
11 # Do parent operations after, to allow child to 'override' parameters?
12 # Need to make sure that duplicate entires do not get into the 'unset*' lists
13 
14 
15 def reflect(cls, *args, **kwargs):
16  """
17  Simple wrapper to add XML reflection to an xml_reflection.Object class
18  """
19  cls.XML_REFL = Reflection(*args, **kwargs)
20 
21 # Rename 'write_xml' to 'write_xml' to have paired 'load/dump', and make
22 # 'pre_dump' and 'post_load'?
23 # When dumping to yaml, include tag name?
24 
25 # How to incorporate line number and all that jazz?
26 
27 
28 def on_error(message):
29  """ What to do on an error. This can be changed to raise an exception. """
30  sys.stderr.write(message + '\n')
31 
32 
33 skip_default = False
34 # defaultIfMatching = True # Not implemeneted yet
35 
36 # Registering Types
37 value_types = {}
38 value_type_prefix = ''
39 
40 
41 def start_namespace(namespace):
42  """
43  Basic mechanism to prevent conflicts for string types for URDF and SDF
44  @note Does not handle nesting!
45  """
46  global value_type_prefix
47  value_type_prefix = namespace + '.'
48 
49 
51  global value_type_prefix
52  value_type_prefix = ''
53 
54 
55 def add_type(key, value):
56  if isinstance(key, str):
57  key = value_type_prefix + key
58  assert key not in value_types
59  value_types[key] = value
60 
61 
62 def get_type(cur_type):
63  """ Can wrap value types if needed """
64  if value_type_prefix and isinstance(cur_type, str):
65  # See if it exists in current 'namespace'
66  curKey = value_type_prefix + cur_type
67  value_type = value_types.get(curKey)
68  else:
69  value_type = None
70  if value_type is None:
71  # Try again, in 'global' scope
72  value_type = value_types.get(cur_type)
73  if value_type is None:
74  value_type = make_type(cur_type)
75  add_type(cur_type, value_type)
76  return value_type
77 
78 
79 def make_type(cur_type):
80  if isinstance(cur_type, ValueType):
81  return cur_type
82  elif isinstance(cur_type, str):
83  if cur_type.startswith('vector'):
84  extra = cur_type[6:]
85  if extra:
86  count = float(extra)
87  else:
88  count = None
89  return VectorType(count)
90  else:
91  raise Exception("Invalid value type: {}".format(cur_type))
92  elif cur_type == list:
93  return ListType()
94  elif issubclass(cur_type, Object):
95  return ObjectType(cur_type)
96  elif cur_type in [str, float]:
97  return BasicType(cur_type)
98  else:
99  raise Exception("Invalid type: {}".format(cur_type))
100 
101 
102 class ValueType(object):
103  """ Primitive value type """
104 
105  def from_xml(self, node):
106  return self.from_string(node.text)
107 
108  def write_xml(self, node, value):
109  """
110  If type has 'write_xml', this function should expect to have it's own
111  XML already created i.e., In Axis.to_sdf(self, node), 'node' would be
112  the 'axis' element.
113  @todo Add function that makes an XML node completely independently?
114  """
115  node.text = self.to_string(value)
116 
117  def equals(self, a, b):
118  return a == b
119 
120 
122  def __init__(self, cur_type):
123  self.type = cur_type
124 
125  def to_string(self, value):
126  return str(value)
127 
128  def from_string(self, value):
129  return self.type(value)
130 
131 
133  def to_string(self, values):
134  return ' '.join(values)
135 
136  def from_string(self, text):
137  return text.split()
138 
139  def equals(self, aValues, bValues):
140  return len(aValues) == len(bValues) and all(a == b for (a, b) in zip(aValues, bValues)) # noqa
141 
142 
144  def __init__(self, count=None):
145  self.count = count
146 
147  def check(self, values):
148  if self.count is not None:
149  assert len(values) == self.count, "Invalid vector length"
150 
151  def to_string(self, values):
152  self.check(values)
153  raw = list(map(str, values))
154  return ListType.to_string(self, raw)
155 
156  def from_string(self, text):
157  raw = ListType.from_string(self, text)
158  self.check(raw)
159  return list(map(float, raw))
160 
161 
163  """
164  Simple, raw XML value. Need to bugfix putting this back into a document
165  """
166 
167  def from_xml(self, node):
168  return node
169 
170  def write_xml(self, node, value):
171  # @todo rying to insert an element at root level seems to screw up
172  # pretty printing
173  children = xml_children(value)
174  list(map(node.append, children))
175  # Copy attributes
176  for (attrib_key, attrib_value) in value.attrib.iteritems():
177  node.set(attrib_key, attrib_value)
178 
179 
181  """
182  Extractor that retrieves data from an element, given a
183  specified attribute, casted to value_type.
184  """
185 
186  def __init__(self, attribute, value_type):
187  self.attribute = attribute
188  self.value_type = get_type(value_type)
189 
190  def from_xml(self, node):
191  text = node.get(self.attribute)
192  return self.value_type.from_string(text)
193 
194  def write_xml(self, node, value):
195  text = self.value_type.to_string(value)
196  node.set(self.attribute, text)
197 
198 
200  def __init__(self, cur_type):
201  self.type = cur_type
202 
203  def from_xml(self, node):
204  obj = self.type()
205  obj.read_xml(node)
206  return obj
207 
208  def write_xml(self, node, obj):
209  obj.write_xml(node)
210 
211 
213  def __init__(self, name, typeMap):
214  self.name = name
215  self.typeMap = typeMap
216  self.nameMap = {}
217  for (key, value) in typeMap.items():
218  # Reverse lookup
219  self.nameMap[value] = key
220 
221  def from_xml(self, node):
222  cur_type = self.typeMap.get(node.tag)
223  if cur_type is None:
224  raise Exception("Invalid {} tag: {}".format(self.name, node.tag))
225  value_type = get_type(cur_type)
226  return value_type.from_xml(node)
227 
228  def get_name(self, obj):
229  cur_type = type(obj)
230  name = self.nameMap.get(cur_type)
231  if name is None:
232  raise Exception("Invalid {} type: {}".format(self.name, cur_type))
233  return name
234 
235  def write_xml(self, node, obj):
236  obj.write_xml(node)
237 
238 
240  def __init__(self, name, typeOrder):
241  self.name = name
242  assert len(typeOrder) > 0
243  self.type_order = typeOrder
244 
245  def from_xml(self, node):
246  error_set = []
247  for value_type in self.type_order:
248  try:
249  return value_type.from_xml(node)
250  except Exception as e:
251  error_set.append((value_type, e))
252  # Should have returned, we encountered errors
253  out = "Could not perform duck-typed parsing."
254  for (value_type, e) in error_set:
255  out += "\nValue Type: {}\nException: {}\n".format(value_type, e)
256  raise Exception(out)
257 
258  def write_xml(self, node, obj):
259  obj.write_xml(node)
260 
261 
262 class Param(object):
263  """ Mirroring Gazebo's SDF api
264 
265  @param xml_var: Xml name
266  @todo If the value_type is an object with a tag defined in it's
267  reflection, allow it to act as the default tag name?
268  @param var: Python class variable name. By default it's the same as the
269  XML name
270  """
271 
272  def __init__(self, xml_var, value_type, required=True, default=None,
273  var=None):
274  self.xml_var = xml_var
275  if var is None:
276  self.var = xml_var
277  else:
278  self.var = var
279  self.type = None
280  self.value_type = get_type(value_type)
281  self.default = default
282  if required:
283  assert default is None, "Default does not make sense for a required field" # noqa
284  self.required = required
285  self.is_aggregate = False
286 
287  def set_default(self, obj):
288  if self.required:
289  raise Exception("Required {} not set in XML: {}".format(self.type, self.xml_var)) # noqa
290  elif not skip_default:
291  setattr(obj, self.var, self.default)
292 
293 
295  def __init__(self, xml_var, value_type, required=True, default=None,
296  var=None):
297  Param.__init__(self, xml_var, value_type, required, default, var)
298  self.type = 'attribute'
299 
300  def set_from_string(self, obj, value):
301  """ Node is the parent node in this case """
302  # Duplicate attributes cannot occur at this point
303  setattr(obj, self.var, self.value_type.from_string(value))
304 
305  def add_to_xml(self, obj, node):
306  value = getattr(obj, self.var)
307  # Do not set with default value if value is None
308  if value is None:
309  if self.required:
310  raise Exception("Required attribute not set in object: {}".format(self.var)) # noqa
311  elif not skip_default:
312  value = self.default
313  # Allow value type to handle None?
314  if value is not None:
315  node.set(self.xml_var, self.value_type.to_string(value))
316 
317 # Add option if this requires a header?
318 # Like <joints> <joint/> .... </joints> ???
319 # Not really... This would be a specific list type, not really aggregate
320 
321 
322 class Element(Param):
323  def __init__(self, xml_var, value_type, required=True, default=None,
324  var=None, is_raw=False):
325  Param.__init__(self, xml_var, value_type, required, default, var)
326  self.type = 'element'
327  self.is_raw = is_raw
328 
329  def set_from_xml(self, obj, node):
330  value = self.value_type.from_xml(node)
331  setattr(obj, self.var, value)
332 
333  def add_to_xml(self, obj, parent):
334  value = getattr(obj, self.xml_var)
335  if value is None:
336  if self.required:
337  raise Exception("Required element not defined in object: {}".format(self.var)) # noqa
338  elif not skip_default:
339  value = self.default
340  if value is not None:
341  self.add_scalar_to_xml(parent, value)
342 
343  def add_scalar_to_xml(self, parent, value):
344  if self.is_raw:
345  node = parent
346  else:
347  node = node_add(parent, self.xml_var)
348  self.value_type.write_xml(node, value)
349 
350 
352  def __init__(self, xml_var, value_type, var=None, is_raw=False):
353  if var is None:
354  var = xml_var + 's'
355  Element.__init__(self, xml_var, value_type, required=False, var=var,
356  is_raw=is_raw)
357  self.is_aggregate = True
358 
359  def add_from_xml(self, obj, node):
360  value = self.value_type.from_xml(node)
361  obj.add_aggregate(self.xml_var, value)
362 
363  def set_default(self, obj):
364  pass
365 
366 
367 class Info:
368  """ Small container for keeping track of what's been consumed """
369 
370  def __init__(self, node):
371  self.attributes = list(node.attrib.keys())
372  self.children = xml_children(node)
373 
374 
375 class Reflection(object):
376  def __init__(self, params=[], parent_cls=None, tag=None):
377  """ Construct a XML reflection thing
378  @param parent_cls: Parent class, to use it's reflection as well.
379  @param tag: Only necessary if you intend to use Object.write_xml_doc()
380  This does not override the name supplied in the reflection
381  definition thing.
382  """
383  if parent_cls is not None:
384  self.parent = parent_cls.XML_REFL
385  else:
386  self.parent = None
387  self.tag = tag
388 
389  # Laziness for now
390  attributes = []
391  elements = []
392  for param in params:
393  if isinstance(param, Element):
394  elements.append(param)
395  else:
396  attributes.append(param)
397 
398  self.vars = []
399  self.paramMap = {}
400 
401  self.attributes = attributes
402  self.attribute_map = {}
404  for attribute in attributes:
405  self.attribute_map[attribute.xml_var] = attribute
406  self.paramMap[attribute.xml_var] = attribute
407  self.vars.append(attribute.var)
408  if attribute.required:
409  self.required_attribute_names.append(attribute.xml_var)
410 
411  self.elements = []
412  self.element_map = {}
414  self.aggregates = []
415  self.scalars = []
416  self.scalarNames = []
417  for element in elements:
418  self.element_map[element.xml_var] = element
419  self.paramMap[element.xml_var] = element
420  self.vars.append(element.var)
421  if element.required:
422  self.required_element_names.append(element.xml_var)
423  if element.is_aggregate:
424  self.aggregates.append(element)
425  else:
426  self.scalars.append(element)
427  self.scalarNames.append(element.xml_var)
428 
429  def set_from_xml(self, obj, node, info=None):
430  is_final = False
431  if info is None:
432  is_final = True
433  info = Info(node)
434 
435  if self.parent:
436  self.parent.set_from_xml(obj, node, info)
437 
438  # Make this a map instead? Faster access? {name: isSet} ?
439  unset_attributes = list(self.attribute_map.keys())
440  unset_scalars = copy.copy(self.scalarNames)
441  # Better method? Queues?
442  for xml_var in copy.copy(info.attributes):
443  attribute = self.attribute_map.get(xml_var)
444  if attribute is not None:
445  value = node.attrib[xml_var]
446  attribute.set_from_string(obj, value)
447  unset_attributes.remove(xml_var)
448  info.attributes.remove(xml_var)
449 
450  # Parse unconsumed nodes
451  for child in copy.copy(info.children):
452  tag = child.tag
453  element = self.element_map.get(tag)
454  if element is not None:
455  if element.is_aggregate:
456  element.add_from_xml(obj, child)
457  else:
458  if tag in unset_scalars:
459  element.set_from_xml(obj, child)
460  unset_scalars.remove(tag)
461  else:
462  on_error("Scalar element defined multiple times: {}".format(tag)) # noqa
463  info.children.remove(child)
464 
465  for attribute in map(self.attribute_map.get, unset_attributes):
466  attribute.set_default(obj)
467 
468  for element in map(self.element_map.get, unset_scalars):
469  element.set_default(obj)
470 
471  if is_final:
472  for xml_var in info.attributes:
473  on_error('Unknown attribute: {}'.format(xml_var))
474  for node in info.children:
475  on_error('Unknown tag: {}'.format(node.tag))
476 
477  def add_to_xml(self, obj, node):
478  if self.parent:
479  self.parent.add_to_xml(obj, node)
480  for attribute in self.attributes:
481  attribute.add_to_xml(obj, node)
482  for element in self.scalars:
483  element.add_to_xml(obj, node)
484  # Now add in aggregates
485  if self.aggregates:
486  obj.add_aggregates_to_xml(node)
487 
488 
490  """ Raw python object for yaml / xml representation """
491  XML_REFL = None
492 
493  def get_refl_vars(self):
494  return self.XML_REFL.vars
495 
496  def check_valid(self):
497  pass
498 
499  def pre_write_xml(self):
500  """ If anything needs to be converted prior to dumping to xml
501  i.e., getting the names of objects and such """
502  pass
503 
504  def write_xml(self, node):
505  """ Adds contents directly to XML node """
506  self.check_valid()
507  self.pre_write_xml()
508  self.XML_REFL.add_to_xml(self, node)
509 
510  def to_xml(self):
511  """ Creates an overarching tag and adds its contents to the node """
512  tag = self.XML_REFL.tag
513  assert tag is not None, "Must define 'tag' in reflection to use this function" # noqa
514  doc = etree.Element(tag)
515  self.write_xml(doc)
516  return doc
517 
518  def to_xml_string(self, addHeader=True):
519  return xml_string(self.to_xml(), addHeader)
520 
521  def post_read_xml(self):
522  pass
523 
524  def read_xml(self, node):
525  self.XML_REFL.set_from_xml(self, node)
526  self.post_read_xml()
527  self.check_valid()
528 
529  @classmethod
530  def from_xml(cls, node):
531  cur_type = get_type(cls)
532  return cur_type.from_xml(node)
533 
534  @classmethod
535  def from_xml_string(cls, xml_string):
536  node = etree.fromstring(xml_string)
537  return cls.from_xml(node)
538 
539  @classmethod
540  def from_xml_file(cls, file_path):
541  xml_string = open(file_path, 'r').read()
542  return cls.from_xml_string(xml_string)
543 
544  # Confusing distinction between loading code in object and reflection
545  # registry thing...
546 
547  def get_aggregate_list(self, xml_var):
548  var = self.XML_REFL.paramMap[xml_var].var
549  values = getattr(self, var)
550  assert isinstance(values, list)
551  return values
552 
553  def aggregate_init(self):
554  """ Must be called in constructor! """
555  self.aggregate_order = []
556  # Store this info in the loaded object??? Nah
557  self.aggregate_type = {}
558 
559  def add_aggregate(self, xml_var, obj):
560  """ NOTE: One must keep careful track of aggregate types for this system.
561  Can use 'lump_aggregates()' before writing if you don't care. """
562  self.get_aggregate_list(xml_var).append(obj)
563  self.aggregate_order.append(obj)
564  self.aggregate_type[obj] = xml_var
565 
566  def add_aggregates_to_xml(self, node):
567  for value in self.aggregate_order:
568  typeName = self.aggregate_type[value]
569  element = self.XML_REFL.element_map[typeName]
570  element.add_scalar_to_xml(node, value)
571 
572  def remove_aggregate(self, obj):
573  self.aggregate_order.remove(obj)
574  xml_var = self.aggregate_type[obj]
575  del self.aggregate_type[obj]
576  self.get_aggregate_list(xml_var).remove(obj)
577 
578  def lump_aggregates(self):
579  """ Put all aggregate types together, just because """
580  self.aggregate_init()
581  for param in self.XML_REFL.aggregates:
582  for obj in self.get_aggregate_list(param.xml_var):
583  self.add_aggregate(param.var, obj)
584 
585  """ Compatibility """
586 
587  def parse(self, xml_string):
588  node = etree.fromstring(xml_string)
589  self.read_xml(node)
590  return self
591 
592 
593 # Really common types
594 # Better name: element_with_name? Attributed element?
595 add_type('element_name', SimpleElementType('name', str))
596 add_type('element_value', SimpleElementType('value', float))
597 
598 # Add in common vector types so they aren't absorbed into the namespaces
599 get_type('vector3')
600 get_type('vector4')
601 get_type('vector6')
def set_from_string(self, obj, value)
Definition: core.py:300
def start_namespace(namespace)
Definition: core.py:41
def from_xml_file(cls, file_path)
Definition: core.py:540
def get_aggregate_list(self, xml_var)
Definition: core.py:547
def __init__(self, xml_var, value_type, required=True, default=None, var=None)
Definition: core.py:296
def add_to_xml(self, obj, parent)
Definition: core.py:333
def __init__(self, params=[], parent_cls=None, tag=None)
Definition: core.py:376
def set_from_xml(self, obj, node, info=None)
Definition: core.py:429
def __init__(self, xml_var, value_type, var=None, is_raw=False)
Definition: core.py:352
def __init__(self, xml_var, value_type, required=True, default=None, var=None)
Definition: core.py:273
def add_aggregate(self, xml_var, obj)
Definition: core.py:559
def add_type(key, value)
Definition: core.py:55
def write_xml(self, node, value)
Definition: core.py:108
def equals(self, aValues, bValues)
Definition: core.py:139
def reflect(cls, args, kwargs)
Definition: core.py:15
def add_scalar_to_xml(self, parent, value)
Definition: core.py:343
def set_from_xml(self, obj, node)
Definition: core.py:329
def from_xml_string(cls, xml_string)
Definition: core.py:535
def xml_string(rootXml, addHeader=True)
Definition: basics.py:11
def __init__(self, attribute, value_type)
Definition: core.py:186
def __init__(self, xml_var, value_type, required=True, default=None, var=None, is_raw=False)
Definition: core.py:324
def __init__(self, name, typeMap)
Definition: core.py:213
def to_xml_string(self, addHeader=True)
Definition: core.py:518
def parse(self, xml_string)
Definition: core.py:587
def write_xml(self, node, value)
Definition: core.py:170


urdfdom_py
Author(s): Thomas Moulard, David Lu, Kelsey Hawkins, Antonio El Khoury, Eric Cousineau, Ioan Sucan , Jackie Kay
autogenerated on Mon Jun 10 2019 15:36:23