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
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
00022
00023
00024
00025
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
00035
00036
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
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
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))
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
00172
00173 children = xml_children(value)
00174 list(map(node.append, children))
00175
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
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
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"
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))
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
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
00308 if value is None:
00309 if self.required:
00310 raise Exception("Required attribute not set in object: {}".format(self.var))
00311 elif not skip_default:
00312 value = self.default
00313
00314 if value is not None:
00315 node.set(self.xml_var, self.value_type.to_string(value))
00316
00317
00318
00319
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))
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
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
00439 unset_attributes = list(self.attribute_map.keys())
00440 unset_scalars = copy.copy(self.scalarNames)
00441
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
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))
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
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"
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
00545
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
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
00594
00595 add_type('element_name', SimpleElementType('name', str))
00596 add_type('element_value', SimpleElementType('value', float))
00597
00598
00599 get_type('vector3')
00600 get_type('vector4')
00601 get_type('vector6')