core.py
Go to the documentation of this file.
00001 from urdf_parser_py.xml_reflection.basics import *
00002 import sys
00003 import copy
00004 
00005 # @todo Get rid of "import *"
00006 # @todo Make this work with decorators
00007 
00008 # Is this reflection or serialization? I think it's serialization...
00009 # Rename?
00010 
00011 # Do parent operations after, to allow child to 'override' parameters?
00012 # Need to make sure that duplicate entires do not get into the 'unset*' lists
00013 
00014 
00015 def reflect(cls, *args, **kwargs):
00016     """
00017     Simple wrapper to add XML reflection to an xml_reflection.Object class
00018     """
00019     cls.XML_REFL = Reflection(*args, **kwargs)
00020 
00021 # Rename 'write_xml' to 'write_xml' to have paired 'load/dump', and make
00022 # 'pre_dump' and 'post_load'?
00023 # When dumping to yaml, include tag name?
00024 
00025 # How to incorporate line number and all that jazz?
00026 
00027 
00028 def on_error(message):
00029     """ What to do on an error. This can be changed to raise an exception. """
00030     sys.stderr.write(message + '\n')
00031 
00032 
00033 skip_default = False
00034 # defaultIfMatching = True # Not implemeneted yet
00035 
00036 # Registering Types
00037 value_types = {}
00038 value_type_prefix = ''
00039 
00040 
00041 def start_namespace(namespace):
00042     """
00043     Basic mechanism to prevent conflicts for string types for URDF and SDF
00044     @note Does not handle nesting!
00045     """
00046     global value_type_prefix
00047     value_type_prefix = namespace + '.'
00048 
00049 
00050 def end_namespace():
00051     global value_type_prefix
00052     value_type_prefix = ''
00053 
00054 
00055 def add_type(key, value):
00056     if isinstance(key, str):
00057         key = value_type_prefix + key
00058     assert key not in value_types
00059     value_types[key] = value
00060 
00061 
00062 def get_type(cur_type):
00063     """ Can wrap value types if needed """
00064     if value_type_prefix and isinstance(cur_type, str):
00065         # See if it exists in current 'namespace'
00066         curKey = value_type_prefix + cur_type
00067         value_type = value_types.get(curKey)
00068     else:
00069         value_type = None
00070     if value_type is None:
00071         # Try again, in 'global' scope
00072         value_type = value_types.get(cur_type)
00073     if value_type is None:
00074         value_type = make_type(cur_type)
00075         add_type(cur_type, value_type)
00076     return value_type
00077 
00078 
00079 def make_type(cur_type):
00080     if isinstance(cur_type, ValueType):
00081         return cur_type
00082     elif isinstance(cur_type, str):
00083         if cur_type.startswith('vector'):
00084             extra = cur_type[6:]
00085             if extra:
00086                 count = float(extra)
00087             else:
00088                 count = None
00089             return VectorType(count)
00090         else:
00091             raise Exception("Invalid value type: {}".format(cur_type))
00092     elif cur_type == list:
00093         return ListType()
00094     elif issubclass(cur_type, Object):
00095         return ObjectType(cur_type)
00096     elif cur_type in [str, float]:
00097         return BasicType(cur_type)
00098     else:
00099         raise Exception("Invalid type: {}".format(cur_type))
00100 
00101 
00102 class ValueType(object):
00103     """ Primitive value type """
00104 
00105     def from_xml(self, node):
00106         return self.from_string(node.text)
00107 
00108     def write_xml(self, node, value):
00109         """
00110         If type has 'write_xml', this function should expect to have it's own
00111         XML already created i.e., In Axis.to_sdf(self, node), 'node' would be
00112         the 'axis' element.
00113         @todo Add function that makes an XML node completely independently?
00114         """
00115         node.text = self.to_string(value)
00116 
00117     def equals(self, a, b):
00118         return a == b
00119 
00120 
00121 class BasicType(ValueType):
00122     def __init__(self, cur_type):
00123         self.type = cur_type
00124 
00125     def to_string(self, value):
00126         return str(value)
00127 
00128     def from_string(self, value):
00129         return self.type(value)
00130 
00131 
00132 class ListType(ValueType):
00133     def to_string(self, values):
00134         return ' '.join(values)
00135 
00136     def from_string(self, text):
00137         return text.split()
00138 
00139     def equals(self, aValues, bValues):
00140         return len(aValues) == len(bValues) and all(a == b for (a, b) in zip(aValues, bValues))  # noqa
00141 
00142 
00143 class VectorType(ListType):
00144     def __init__(self, count=None):
00145         self.count = count
00146 
00147     def check(self, values):
00148         if self.count is not None:
00149             assert len(values) == self.count, "Invalid vector length"
00150 
00151     def to_string(self, values):
00152         self.check(values)
00153         raw = list(map(str, values))
00154         return ListType.to_string(self, raw)
00155 
00156     def from_string(self, text):
00157         raw = ListType.from_string(self, text)
00158         self.check(raw)
00159         return list(map(float, raw))
00160 
00161 
00162 class RawType(ValueType):
00163     """
00164     Simple, raw XML value. Need to bugfix putting this back into a document
00165     """
00166 
00167     def from_xml(self, node):
00168         return node
00169 
00170     def write_xml(self, node, value):
00171         # @todo rying to insert an element at root level seems to screw up
00172         # pretty printing
00173         children = xml_children(value)
00174         list(map(node.append, children))
00175         # Copy attributes
00176         for (attrib_key, attrib_value) in value.attrib.iteritems():
00177             node.set(attrib_key, attrib_value)
00178 
00179 
00180 class SimpleElementType(ValueType):
00181     """
00182     Extractor that retrieves data from an element, given a
00183     specified attribute, casted to value_type.
00184     """
00185 
00186     def __init__(self, attribute, value_type):
00187         self.attribute = attribute
00188         self.value_type = get_type(value_type)
00189 
00190     def from_xml(self, node):
00191         text = node.get(self.attribute)
00192         return self.value_type.from_string(text)
00193 
00194     def write_xml(self, node, value):
00195         text = self.value_type.to_string(value)
00196         node.set(self.attribute, text)
00197 
00198 
00199 class ObjectType(ValueType):
00200     def __init__(self, cur_type):
00201         self.type = cur_type
00202 
00203     def from_xml(self, node):
00204         obj = self.type()
00205         obj.read_xml(node)
00206         return obj
00207 
00208     def write_xml(self, node, obj):
00209         obj.write_xml(node)
00210 
00211 
00212 class FactoryType(ValueType):
00213     def __init__(self, name, typeMap):
00214         self.name = name
00215         self.typeMap = typeMap
00216         self.nameMap = {}
00217         for (key, value) in typeMap.items():
00218             # Reverse lookup
00219             self.nameMap[value] = key
00220 
00221     def from_xml(self, node):
00222         cur_type = self.typeMap.get(node.tag)
00223         if cur_type is None:
00224             raise Exception("Invalid {} tag: {}".format(self.name, node.tag))
00225         value_type = get_type(cur_type)
00226         return value_type.from_xml(node)
00227 
00228     def get_name(self, obj):
00229         cur_type = type(obj)
00230         name = self.nameMap.get(cur_type)
00231         if name is None:
00232             raise Exception("Invalid {} type: {}".format(self.name, cur_type))
00233         return name
00234 
00235     def write_xml(self, node, obj):
00236         obj.write_xml(node)
00237 
00238 
00239 class DuckTypedFactory(ValueType):
00240     def __init__(self, name, typeOrder):
00241         self.name = name
00242         assert len(typeOrder) > 0
00243         self.type_order = typeOrder
00244 
00245     def from_xml(self, node):
00246         error_set = []
00247         for value_type in self.type_order:
00248             try:
00249                 return value_type.from_xml(node)
00250             except Exception as e:
00251                 error_set.append((value_type, e))
00252         # Should have returned, we encountered errors
00253         out = "Could not perform duck-typed parsing."
00254         for (value_type, e) in error_set:
00255             out += "\nValue Type: {}\nException: {}\n".format(value_type, e)
00256         raise Exception(out)
00257 
00258     def write_xml(self, node, obj):
00259         obj.write_xml(node)
00260 
00261 
00262 class Param(object):
00263     """ Mirroring Gazebo's SDF api
00264 
00265     @param xml_var: Xml name
00266             @todo If the value_type is an object with a tag defined in it's
00267                   reflection, allow it to act as the default tag name?
00268     @param var: Python class variable name. By default it's the same as the
00269                 XML name
00270     """
00271 
00272     def __init__(self, xml_var, value_type, required=True, default=None,
00273                  var=None):
00274         self.xml_var = xml_var
00275         if var is None:
00276             self.var = xml_var
00277         else:
00278             self.var = var
00279         self.type = None
00280         self.value_type = get_type(value_type)
00281         self.default = default
00282         if required:
00283             assert default is None, "Default does not make sense for a required field"  # noqa
00284         self.required = required
00285         self.is_aggregate = False
00286 
00287     def set_default(self, obj):
00288         if self.required:
00289             raise Exception("Required {} not set in XML: {}".format(self.type, self.xml_var))  # noqa
00290         elif not skip_default:
00291             setattr(obj, self.var, self.default)
00292 
00293 
00294 class Attribute(Param):
00295     def __init__(self, xml_var, value_type, required=True, default=None,
00296                  var=None):
00297         Param.__init__(self, xml_var, value_type, required, default, var)
00298         self.type = 'attribute'
00299 
00300     def set_from_string(self, obj, value):
00301         """ Node is the parent node in this case """
00302         # Duplicate attributes cannot occur at this point
00303         setattr(obj, self.var, self.value_type.from_string(value))
00304 
00305     def add_to_xml(self, obj, node):
00306         value = getattr(obj, self.var)
00307         # Do not set with default value if value is None
00308         if value is None:
00309             if self.required:
00310                 raise Exception("Required attribute not set in object: {}".format(self.var))  # noqa
00311             elif not skip_default:
00312                 value = self.default
00313         # Allow value type to handle None?
00314         if value is not None:
00315             node.set(self.xml_var, self.value_type.to_string(value))
00316 
00317 # Add option if this requires a header?
00318 # Like <joints> <joint/> .... </joints> ???
00319 # Not really... This would be a specific list type, not really aggregate
00320 
00321 
00322 class Element(Param):
00323     def __init__(self, xml_var, value_type, required=True, default=None,
00324                  var=None, is_raw=False):
00325         Param.__init__(self, xml_var, value_type, required, default, var)
00326         self.type = 'element'
00327         self.is_raw = is_raw
00328 
00329     def set_from_xml(self, obj, node):
00330         value = self.value_type.from_xml(node)
00331         setattr(obj, self.var, value)
00332 
00333     def add_to_xml(self, obj, parent):
00334         value = getattr(obj, self.xml_var)
00335         if value is None:
00336             if self.required:
00337                 raise Exception("Required element not defined in object: {}".format(self.var))  # noqa
00338             elif not skip_default:
00339                 value = self.default
00340         if value is not None:
00341             self.add_scalar_to_xml(parent, value)
00342 
00343     def add_scalar_to_xml(self, parent, value):
00344         if self.is_raw:
00345             node = parent
00346         else:
00347             node = node_add(parent, self.xml_var)
00348         self.value_type.write_xml(node, value)
00349 
00350 
00351 class AggregateElement(Element):
00352     def __init__(self, xml_var, value_type, var=None, is_raw=False):
00353         if var is None:
00354             var = xml_var + 's'
00355         Element.__init__(self, xml_var, value_type, required=False, var=var,
00356                          is_raw=is_raw)
00357         self.is_aggregate = True
00358 
00359     def add_from_xml(self, obj, node):
00360         value = self.value_type.from_xml(node)
00361         obj.add_aggregate(self.xml_var, value)
00362 
00363     def set_default(self, obj):
00364         pass
00365 
00366 
00367 class Info:
00368     """ Small container for keeping track of what's been consumed """
00369 
00370     def __init__(self, node):
00371         self.attributes = list(node.attrib.keys())
00372         self.children = xml_children(node)
00373 
00374 
00375 class Reflection(object):
00376     def __init__(self, params=[], parent_cls=None, tag=None):
00377         """ Construct a XML reflection thing
00378         @param parent_cls: Parent class, to use it's reflection as well.
00379         @param tag: Only necessary if you intend to use Object.write_xml_doc()
00380                 This does not override the name supplied in the reflection
00381                 definition thing.
00382         """
00383         if parent_cls is not None:
00384             self.parent = parent_cls.XML_REFL
00385         else:
00386             self.parent = None
00387         self.tag = tag
00388 
00389         # Laziness for now
00390         attributes = []
00391         elements = []
00392         for param in params:
00393             if isinstance(param, Element):
00394                 elements.append(param)
00395             else:
00396                 attributes.append(param)
00397 
00398         self.vars = []
00399         self.paramMap = {}
00400 
00401         self.attributes = attributes
00402         self.attribute_map = {}
00403         self.required_attribute_names = []
00404         for attribute in attributes:
00405             self.attribute_map[attribute.xml_var] = attribute
00406             self.paramMap[attribute.xml_var] = attribute
00407             self.vars.append(attribute.var)
00408             if attribute.required:
00409                 self.required_attribute_names.append(attribute.xml_var)
00410 
00411         self.elements = []
00412         self.element_map = {}
00413         self.required_element_names = []
00414         self.aggregates = []
00415         self.scalars = []
00416         self.scalarNames = []
00417         for element in elements:
00418             self.element_map[element.xml_var] = element
00419             self.paramMap[element.xml_var] = element
00420             self.vars.append(element.var)
00421             if element.required:
00422                 self.required_element_names.append(element.xml_var)
00423             if element.is_aggregate:
00424                 self.aggregates.append(element)
00425             else:
00426                 self.scalars.append(element)
00427                 self.scalarNames.append(element.xml_var)
00428 
00429     def set_from_xml(self, obj, node, info=None):
00430         is_final = False
00431         if info is None:
00432             is_final = True
00433             info = Info(node)
00434 
00435         if self.parent:
00436             self.parent.set_from_xml(obj, node, info)
00437 
00438         # Make this a map instead? Faster access? {name: isSet} ?
00439         unset_attributes = list(self.attribute_map.keys())
00440         unset_scalars = copy.copy(self.scalarNames)
00441         # Better method? Queues?
00442         for xml_var in copy.copy(info.attributes):
00443             attribute = self.attribute_map.get(xml_var)
00444             if attribute is not None:
00445                 value = node.attrib[xml_var]
00446                 attribute.set_from_string(obj, value)
00447                 unset_attributes.remove(xml_var)
00448                 info.attributes.remove(xml_var)
00449 
00450         # Parse unconsumed nodes
00451         for child in copy.copy(info.children):
00452             tag = child.tag
00453             element = self.element_map.get(tag)
00454             if element is not None:
00455                 if element.is_aggregate:
00456                     element.add_from_xml(obj, child)
00457                 else:
00458                     if tag in unset_scalars:
00459                         element.set_from_xml(obj, child)
00460                         unset_scalars.remove(tag)
00461                     else:
00462                         on_error("Scalar element defined multiple times: {}".format(tag))  # noqa
00463                 info.children.remove(child)
00464 
00465         for attribute in map(self.attribute_map.get, unset_attributes):
00466             attribute.set_default(obj)
00467 
00468         for element in map(self.element_map.get, unset_scalars):
00469             element.set_default(obj)
00470 
00471         if is_final:
00472             for xml_var in info.attributes:
00473                 on_error('Unknown attribute: {}'.format(xml_var))
00474             for node in info.children:
00475                 on_error('Unknown tag: {}'.format(node.tag))
00476 
00477     def add_to_xml(self, obj, node):
00478         if self.parent:
00479             self.parent.add_to_xml(obj, node)
00480         for attribute in self.attributes:
00481             attribute.add_to_xml(obj, node)
00482         for element in self.scalars:
00483             element.add_to_xml(obj, node)
00484         # Now add in aggregates
00485         if self.aggregates:
00486             obj.add_aggregates_to_xml(node)
00487 
00488 
00489 class Object(YamlReflection):
00490     """ Raw python object for yaml / xml representation """
00491     XML_REFL = None
00492 
00493     def get_refl_vars(self):
00494         return self.XML_REFL.vars
00495 
00496     def check_valid(self):
00497         pass
00498 
00499     def pre_write_xml(self):
00500         """ If anything needs to be converted prior to dumping to xml
00501         i.e., getting the names of objects and such """
00502         pass
00503 
00504     def write_xml(self, node):
00505         """ Adds contents directly to XML node """
00506         self.check_valid()
00507         self.pre_write_xml()
00508         self.XML_REFL.add_to_xml(self, node)
00509 
00510     def to_xml(self):
00511         """ Creates an overarching tag and adds its contents to the node """
00512         tag = self.XML_REFL.tag
00513         assert tag is not None, "Must define 'tag' in reflection to use this function"  # noqa
00514         doc = etree.Element(tag)
00515         self.write_xml(doc)
00516         return doc
00517 
00518     def to_xml_string(self, addHeader=True):
00519         return xml_string(self.to_xml(), addHeader)
00520 
00521     def post_read_xml(self):
00522         pass
00523 
00524     def read_xml(self, node):
00525         self.XML_REFL.set_from_xml(self, node)
00526         self.post_read_xml()
00527         self.check_valid()
00528 
00529     @classmethod
00530     def from_xml(cls, node):
00531         cur_type = get_type(cls)
00532         return cur_type.from_xml(node)
00533 
00534     @classmethod
00535     def from_xml_string(cls, xml_string):
00536         node = etree.fromstring(xml_string)
00537         return cls.from_xml(node)
00538 
00539     @classmethod
00540     def from_xml_file(cls, file_path):
00541         xml_string = open(file_path, 'r').read()
00542         return cls.from_xml_string(xml_string)
00543 
00544     # Confusing distinction between loading code in object and reflection
00545     # registry thing...
00546 
00547     def get_aggregate_list(self, xml_var):
00548         var = self.XML_REFL.paramMap[xml_var].var
00549         values = getattr(self, var)
00550         assert isinstance(values, list)
00551         return values
00552 
00553     def aggregate_init(self):
00554         """ Must be called in constructor! """
00555         self.aggregate_order = []
00556         # Store this info in the loaded object??? Nah
00557         self.aggregate_type = {}
00558 
00559     def add_aggregate(self, xml_var, obj):
00560         """ NOTE: One must keep careful track of aggregate types for this system.
00561         Can use 'lump_aggregates()' before writing if you don't care. """
00562         self.get_aggregate_list(xml_var).append(obj)
00563         self.aggregate_order.append(obj)
00564         self.aggregate_type[obj] = xml_var
00565 
00566     def add_aggregates_to_xml(self, node):
00567         for value in self.aggregate_order:
00568             typeName = self.aggregate_type[value]
00569             element = self.XML_REFL.element_map[typeName]
00570             element.add_scalar_to_xml(node, value)
00571 
00572     def remove_aggregate(self, obj):
00573         self.aggregate_order.remove(obj)
00574         xml_var = self.aggregate_type[obj]
00575         del self.aggregate_type[obj]
00576         self.get_aggregate_list(xml_var).remove(obj)
00577 
00578     def lump_aggregates(self):
00579         """ Put all aggregate types together, just because """
00580         self.aggregate_init()
00581         for param in self.XML_REFL.aggregates:
00582             for obj in self.get_aggregate_list(param.xml_var):
00583                 self.add_aggregate(param.var, obj)
00584 
00585     """ Compatibility """
00586 
00587     def parse(self, xml_string):
00588         node = etree.fromstring(xml_string)
00589         self.read_xml(node)
00590         return self
00591 
00592 
00593 # Really common types
00594 # Better name: element_with_name? Attributed element?
00595 add_type('element_name', SimpleElementType('name', str))
00596 add_type('element_value', SimpleElementType('value', float))
00597 
00598 # Add in common vector types so they aren't absorbed into the namespaces
00599 get_type('vector3')
00600 get_type('vector4')
00601 get_type('vector6')


urdfdom_py
Author(s): Thomas Moulard, David Lu, Kelsey Hawkins, Antonio El Khoury, Eric Cousineau, Ioan Sucan , Jackie Kay
autogenerated on Thu Jun 6 2019 20:07:42