00001 from urdf_parser_py.xml_reflection.basics import *
00002 import sys
00003 import copy
00004
00005
00006
00007
00008
00009
00010
00011
00012
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
00019
00020
00021
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
00028
00029
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
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
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
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
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
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
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
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
00265 if value is not None:
00266 node.set(self.xml_var, self.value_type.to_string(value))
00267
00268
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
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
00382 unset_attributes = self.attribute_map.keys()
00383 unset_scalars = copy.copy(self.scalarNames)
00384
00385
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
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
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
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
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
00534 add_type('element_name', SimpleElementType('name', str))
00535 add_type('element_value', SimpleElementType('value', float))
00536
00537
00538 get_type('vector3')
00539 get_type('vector4')
00540 get_type('vector6')