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


pr2_calibration_launch
Author(s): Vijay Pradeep
autogenerated on Fri Aug 28 2015 12:06:40