8 from xml.etree
import ElementTree
as ET
21 Simple wrapper to add XML reflection to an xml_reflection.Object class
31 """ What to do on an error. This can be changed to raise an exception. """
32 sys.stderr.write(message +
'\n')
33 on_error = on_error_stderr
41 value_type_prefix =
''
46 Basic mechanism to prevent conflicts for string types for URDF and SDF
47 @note Does not handle nesting!
49 global value_type_prefix
50 value_type_prefix = namespace +
'.'
54 global value_type_prefix
55 value_type_prefix =
''
59 if isinstance(key, str):
60 key = value_type_prefix + key
61 assert key
not in value_types
62 value_types[key] = value
66 """ Can wrap value types if needed """
67 if value_type_prefix
and isinstance(cur_type, str):
69 curKey = value_type_prefix + cur_type
70 value_type = value_types.get(curKey)
73 if value_type
is None:
75 value_type = value_types.get(cur_type)
76 if value_type
is None:
83 if isinstance(cur_type, ValueType):
85 elif isinstance(cur_type, str):
86 if cur_type.startswith(
'vector'):
94 raise Exception(
"Invalid value type: {}".format(cur_type))
95 elif cur_type == list:
97 elif issubclass(cur_type, Object):
99 elif cur_type
in [str, float]:
102 raise Exception(
"Invalid type: {}".format(cur_type))
106 def __init__(self, tag, parent=None, suffix="", tree=None):
113 if self.
parent is not None:
116 if self.
tag is not None and len(self.
tag) > 0:
117 return "/{}{}".format(self.
tag, self.
suffix)
125 message =
"ParseError in {}:\n{}".format(self.
path, self.
e)
126 super(ParseError, self).
__init__(message)
130 """ Primitive value type """
133 return self.from_string(node.text)
137 If type has 'write_xml', this function should expect to have it's own
138 XML already created i.e., In Axis.to_sdf(self, node), 'node' would be
140 @todo Add function that makes an XML node completely independently?
142 node.text = self.to_string(value)
156 return self.
type(value)
161 return ' '.join(values)
167 return len(aValues) == len(bValues)
and all(a == b
for (a, b)
in zip(aValues, bValues))
175 if self.
count is not None:
176 assert len(values) == self.
count,
"Invalid vector length"
180 raw = list(map(str, values))
181 return ListType.to_string(self, raw)
184 raw = ListType.from_string(self, text)
186 return list(map(float, raw))
191 Simple, raw XML value. Need to bugfix putting this back into a document
201 list(map(node.append, children))
203 for (attrib_key, attrib_value)
in value.attrib.items():
204 node.set(attrib_key, attrib_value)
209 Extractor that retrieves data from an element, given a
210 specified attribute, casted to value_type.
232 obj.read_xml(node, path)
244 for (key, value)
in typeMap.items():
249 cur_type = self.
typeMap.get(node.tag)
251 raise Exception(
"Invalid {} tag: {}".format(self.
name, node.tag))
253 return value_type.from_xml(node, path)
257 name = self.
nameMap.get(cur_type)
259 raise Exception(
"Invalid {} type: {}".format(self.
name, cur_type))
269 assert len(typeOrder) > 0
276 return value_type.from_xml(node, path)
277 except Exception
as e:
278 error_set.append((value_type, e))
280 out =
"Could not perform duck-typed parsing."
281 for (value_type, e)
in error_set:
282 out +=
"\nValue Type: {}\nException: {}\n".format(value_type, e)
290 """ Mirroring Gazebo's SDF api
292 @param xml_var: Xml name
293 @todo If the value_type is an object with a tag defined in it's
294 reflection, allow it to act as the default tag name?
295 @param var: Python class variable name. By default it's the same as the
299 def __init__(self, xml_var, value_type, required=True, default=None,
310 assert default
is None,
"Default does not make sense for a required field"
316 raise Exception(
"Required {} not set in XML: {}".format(self.
type, self.
xml_var))
317 elif not skip_default:
322 def __init__(self, xml_var, value_type, required=True, default=None,
324 Param.__init__(self, xml_var, value_type, required, default, var)
325 self.
type =
'attribute'
328 """ Node is the parent node in this case """
333 return getattr(obj, self.
var)
336 value = getattr(obj, self.
var)
340 raise Exception(
"Required attribute not set in object: {}".format(self.
var))
341 elif not skip_default:
344 if value
is not None:
353 def __init__(self, xml_var, value_type, required=True, default=None,
354 var=None, is_raw=False):
355 Param.__init__(self, xml_var, value_type, required, default, var)
361 setattr(obj, self.
var, value)
364 value = getattr(obj, self.
xml_var)
367 raise Exception(
"Required element not defined in object: {}".format(self.
var))
368 elif not skip_default:
370 if value
is not None:
382 def __init__(self, xml_var, value_type, var=None, is_raw=False):
385 Element.__init__(self, xml_var, value_type, required=
False, var=var,
391 obj.add_aggregate(self.
xml_var, value)
398 """ Small container for keeping track of what's been consumed """
406 def __init__(self, params=[], parent_cls=None, tag=None):
407 """ Construct a XML reflection thing
408 @param parent_cls: Parent class, to use it's reflection as well.
409 @param tag: Only necessary if you intend to use Object.write_xml_doc()
410 This does not override the name supplied in the reflection
413 if parent_cls
is not None:
423 if isinstance(param, Element):
424 elements.append(param)
426 attributes.append(param)
434 for attribute
in attributes:
436 self.
paramMap[attribute.xml_var] = attribute
437 self.
vars.append(attribute.var)
438 if attribute.required:
447 for element
in elements:
449 self.
paramMap[element.xml_var] = element
450 self.
vars.append(element.var)
453 if element.is_aggregate:
472 def get_attr_path(attribute):
473 attr_path = copy.copy(path)
474 attr_path.suffix +=
'[@{}]'.format(attribute.xml_var)
477 def get_element_path(element):
478 element_path =
Path(element.xml_var, parent = path)
480 if element.is_aggregate:
481 values = obj.get_aggregate_list(element.xml_var)
482 index = 1 + len(values)
483 element_path.suffix =
"[{}]".format(index)
488 for xml_var
in copy.copy(info.attributes):
490 if attribute
is not None:
491 value = node.attrib[xml_var]
492 attr_path = get_attr_path(attribute)
494 attribute.set_from_string(obj, value)
495 if attribute.xml_var == id_var:
497 path.suffix =
"[@{}='{}']".format(id_var, attribute.get_value(obj))
500 except Exception
as e:
502 unset_attributes.remove(xml_var)
503 info.attributes.remove(xml_var)
506 for child
in copy.copy(info.children):
509 if element
is not None:
511 element_path = get_element_path(element)
512 if element.is_aggregate:
513 element.add_from_xml(obj, child, element_path)
515 if tag
in unset_scalars:
516 element.set_from_xml(obj, child, element_path)
517 unset_scalars.remove(tag)
519 on_error(
"Scalar element defined multiple times: {}".format(tag))
520 info.children.remove(child)
526 for attribute
in map(self.
attribute_map.get, unset_attributes):
528 attribute.set_default(obj)
531 except Exception
as e:
534 for element
in map(self.
element_map.get, unset_scalars):
536 element.set_default(obj)
539 except Exception
as e:
543 for xml_var
in info.attributes:
544 on_error(
'Unknown attribute "{}" in {}'.format(xml_var, path))
545 for node
in info.children:
546 on_error(
'Unknown tag "{}" in {}'.format(node.tag, path))
554 attribute.add_to_xml(obj, node)
556 element.add_to_xml(obj, node)
559 obj.add_aggregates_to_xml(node)
563 """ Raw python object for yaml / xml representation """
573 """ If anything needs to be converted prior to dumping to xml
574 i.e., getting the names of objects and such """
578 """ Adds contents directly to XML node """
581 self.XML_REFL.add_to_xml(self, node)
584 """ Creates an overarching tag and adds its contents to the node """
586 assert tag
is not None,
"Must define 'tag' in reflection to use this function"
587 doc = ET.Element(tag)
598 self.XML_REFL.set_from_xml(self, node, path)
604 except Exception
as e:
610 return cur_type.from_xml(node, path)
614 node = ET.fromstring(xml_string)
615 path =
Path(cls.
XML_REFL.tag, tree=ET.ElementTree(node))
620 xml_string = open(file_path,
'r').read()
627 var = self.
XML_REFL.paramMap[xml_var].var
628 values = getattr(self, var)
629 assert isinstance(values, list)
633 """ Must be called in constructor! """
639 """ NOTE: One must keep careful track of aggregate types for this system.
640 Can use 'lump_aggregates()' before writing if you don't care. """
648 element = self.
XML_REFL.element_map[typeName]
649 element.add_scalar_to_xml(node, value)
658 """ Put all aggregate types together, just because """
660 for param
in self.
XML_REFL.aggregates:
664 """ Compatibility """
667 node = ET.fromstring(xml_string)
668 path =
Path(self.
XML_REFL.tag, tree=ET.ElementTree(node))