""" parse simple structures from an xml tree We only support a subset of features but should be enough for custom structures """ import os import importlib from lxml import objectify from opcua.ua.ua_binary import Primitives def get_default_value(uatype): if uatype == "String": return "None" elif uatype == "Guid": return "uuid.uuid4()" elif uatype in ("ByteString", "CharArray", "Char"): return None elif uatype == "Boolean": return "True" elif uatype == "DateTime": return "datetime.utcnow()" elif uatype in ("Int8", "Int16", "Int32", "Int64", "UInt8", "UInt16", "UInt32", "UInt64", "Double", "Float", "Byte", "SByte"): return 0 else: return "ua." + uatype + "()" class Struct(object): def __init__(self, name): self.name = name self.fields = [] self.code = "" def get_code(self): if not self.fields: return """ class {}(object): pass """.format(self.name) self._make_constructor() self._make_from_binary() self._make_to_binary() return self.code def _make_constructor(self): self.code = """ class {0}(object): ''' {0} structure autogenerated from xml ''' def __init__(self, data=None): if data is not None: self._binary_init(data) return """.format(self.name) for field in self.fields: self.code += " self.{} = {}\n".format(field.name, field.value) def _make_from_binary(self): self.code += ''' @staticmethod def from_binary(data): return {}(data=data) def _binary_init(self, data): '''.format(self.name) for field in self.fields: if hasattr(Primitives, field.uatype): if field.array: self.code += ' self.{} = ua.ua_binary.Primitives.{}.unpack_array(data)\n'.format(field.name, field.uatype) else: self.code += ' self.{} = ua.ua_binary.Primitives.{}.unpack(data)\n'.format(field.name, field.uatype) else: if field.array: self.code += ''' length = ua.ua_binary.Primitives.Int32.unpack(data) if length == -1: self.{0} = None else: self.{0} = [ua.{1}.from_binary(data) for _ in range(length)] '''.format(field.name, field.uatype) else: self.code += " self.{} = ua.{}.from_binary(data)\n".format(field.name, field.uatype) def _make_to_binary(self): self.code += ''' def to_binary(self): packet = [] ''' for field in self.fields: if hasattr(Primitives, field.uatype): if field.array: self.code += ' packet.append(ua.ua_binary.Primitives.{}.pack_array(self.{}))\n'.format(field.uatype, field.name) else: self.code += ' packet.append(ua.ua_binary.Primitives.{}.pack(self.{}))\n'.format(field.uatype, field.name) else: if field.array: self.code += ''' if self.{0} is None: packet.append(ua.ua_binary.Primitives.Int32.pack(-1)) else: packet.append(ua.ua_binary.Primitives.Int32.pack(len(self.{0}))) for element in self.{0}: packet.append(element.to_binary()) '''.format(field.name) else: self.code += " packet.append(self.{}.to_binary())\n".format(field.name) self.code += ' return b"".join(packet)' class Field(object): def __init__(self, name): self.name = name self.uatype = None self.value = None self.array = False class StructGenerator(object): def __init__(self): self.model = [] def make_model_from_string(self, xml): obj = objectify.fromstring(xml) self._make_model(obj) def make_model_from_file(self, path): obj = objectify.parse(path) root = obj.getroot() self._make_model(root) def _make_model(self, root): for child in root.iter("{*}StructuredType"): struct = Struct(child.get("Name")) array = False for xmlfield in child.iter("{*}Field"): name = xmlfield.get("Name") if name.startswith("NoOf"): array = True continue field = Field(name) field.uatype = xmlfield.get("TypeName") if ":" in field.uatype: field.uatype = field.uatype.split(":")[1] field.value = get_default_value(field.uatype) if array: field.array = True field.value = [] array = False struct.fields.append(field) self.model.append(struct) def save_to_file(self, path): _file = open(path, "wt") self._make_header(_file) for struct in self.model: _file.write(struct.get_code()) _file.close() def save_and_import(self, path, append_to=None): """ save the new structures to a python file which be used later import the result and return resulting classes in a dict if append_to is a dict, the classes are added to the dict """ self.save_to_file(path) name = os.path.basename(path) name = os.path.splitext(name)[0] mymodule = importlib.import_module(name) if append_to is None: result = {} else: result = append_to for struct in self.model: result[struct.name] = getattr(mymodule, struct.name) return result def get_structures(self): ld = {} for struct in self.model: exec(struct.get_code(), ld) return ld def _make_header(self, _file): _file.write(""" ''' THIS FILE IS AUTOGENERATED, DO NOT EDIT!!! ''' from datetime import datetime import uuid from opcua import ua """) if __name__ == "__main__": import sys from IPython import embed sys.path.insert(0, ".") # necessary for import in current dir #xmlpath = "schemas/Opc.Ua.Types.bsd" xmlpath = "schemas/example.bsd" c = StructGenerator(xmlpath, "structures.py") c.run() import structures as s #sts = c.get_structures() embed()