From 0ff9de3cd991b0f574f0fa73e0d805b5bd76ce57 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Fri, 8 Apr 2016 23:23:29 +0200 Subject: [PATCH] Update EXPRESS code generator to work on ancient IFC schemas --- src/ifcexpressparser/bootstrap.py | 2 ++ src/ifcexpressparser/documentation.py | 6 +++-- src/ifcexpressparser/header.py | 21 ++++++++++----- src/ifcexpressparser/implementation.py | 11 +++++++- .../latebound_implementation.py | 12 ++++----- src/ifcexpressparser/mapping.py | 9 ++++--- src/ifcexpressparser/nodes.py | 23 +++++++++++++--- src/ifcexpressparser/schema.py | 26 +++++++++++++++++-- 8 files changed, 86 insertions(+), 24 deletions(-) diff --git a/src/ifcexpressparser/bootstrap.py b/src/ifcexpressparser/bootstrap.py index d59fe55048..00936c2f4b 100644 --- a/src/ifcexpressparser/bootstrap.py +++ b/src/ifcexpressparser/bootstrap.py @@ -122,6 +122,8 @@ actions = { 'inverse_attr' : "lambda t: InverseAttribute(t)", 'bound_spec' : "lambda t: BoundSpecification(t)", 'explicit_attr' : "lambda t: ExplicitAttribute(t)", + 'width_spec' : "lambda t: WidthSpec(t)", + 'string_type' : "lambda t: StringType(t)", } to_emit = set(id for id, expr in express) diff --git a/src/ifcexpressparser/documentation.py b/src/ifcexpressparser/documentation.py index ef8bde5a69..c099864a61 100644 --- a/src/ifcexpressparser/documentation.py +++ b/src/ifcexpressparser/documentation.py @@ -31,12 +31,14 @@ import re import os import csv +from schema import OrderedCaseInsensitiveDict + try: from html.entities import entitydefs except: from htmlentitydefs import entitydefs make_absolute = lambda fn: os.path.join(os.path.dirname(os.path.realpath(__file__)), fn) -name_to_oid = {} +name_to_oid = OrderedCaseInsensitiveDict() oid_to_desc = {} oid_to_name = {} oid_to_pid = {} @@ -59,7 +61,7 @@ with open(make_absolute('DocAttribute.csv')) as f: for oid, name, desc in csv.reader(f, delimiter=';', quotechar='"'): pid = oid_to_pid[oid] pname = oid_to_name[pid] - name_to_oid[(pname, name)] = oid + name_to_oid[".".join((pname, name))] = oid oid_to_desc[oid] = desc def description(item): diff --git a/src/ifcexpressparser/header.py b/src/ifcexpressparser/header.py index 449506bc0f..6d416cf37c 100644 --- a/src/ifcexpressparser/header.py +++ b/src/ifcexpressparser/header.py @@ -41,15 +41,18 @@ class Header(codegen.Base): emitted_simpletypes = set() while len(emitted_simpletypes) < len(mapping.schema.simpletypes): for name, type in mapping.schema.simpletypes.items(): - if name in emitted_simpletypes: continue + if name.lower() in emitted_simpletypes: continue type_str = mapping.make_type_string(mapping.flatten_type_string(type)) attr_type = mapping.make_argument_type(type) superclass = mapping.simple_type_parent(name) if superclass is None: superclass = "IfcUtil::IfcBaseType" - elif superclass not in emitted_simpletypes: + elif superclass.lower() not in emitted_simpletypes: continue - emitted_simpletypes.add(name) + else: + # Case normalize + superclass = [k for k in mapping.schema.simpletypes.keys() if k.lower() == superclass.lower()][0] + emitted_simpletypes.add(name.lower()) write(templates.simpletype, name=name, type=type_str, attr_type=attr_type, superclass=superclass) class_definitions = [] @@ -60,14 +63,14 @@ class Header(codegen.Base): emitted_entities = set() while len(emitted_entities) < len(mapping.schema.entities): for name, type in mapping.schema.entities.items(): - if name in emitted_entities: continue - if len(type.supertypes) == 0 or set(type.supertypes) < emitted_entities: + if name.lower() in emitted_entities: continue + if len(type.supertypes) == 0 or set(map(str.lower, type.supertypes)) <= emitted_entities: attr_lines = [] def write_method(attr): if attr.optional: attr_lines.append(templates.optional_attribute_description % (attr.name, name)) attr_lines.append("bool has%s() const;"%(attr.name)) - attr_lines.extend(["/// %s"%d for d in documentation.description((name, attr.name))]) + attr_lines.extend(["/// %s"%d for d in documentation.description(".".join((name, attr.name)))]) type_str = mapping.get_parameter_type(attr, allow_optional=False, allow_entities=False) if mapping.make_argument_type(attr) != "IfcUtil::Argument_UNKNOWN": attr_lines.append("%s %s() const;"%(type_str, attr.name)) @@ -88,7 +91,11 @@ class Header(codegen.Base): inverse = "\n".join(["%s%s"%(' '*4, a) for a in inv_lines]) if len(inverse): inverse += '\n' - supertypes = type.supertypes if len(type.supertypes) else ['IfcUtil::IfcBaseEntity'] + def case_norm(n): + n = n.lower() + return [k for k in mapping.schema.entities.keys() if k.lower() == n][0] + + supertypes = map(case_norm, type.supertypes) if len(type.supertypes) else ['IfcUtil::IfcBaseEntity'] superclass = ": %s "%(", ".join(["public %s"%c for c in supertypes])) argument_count = mapping.argument_count(type) diff --git a/src/ifcexpressparser/implementation.py b/src/ifcexpressparser/implementation.py index f26581cf0e..cd320274c7 100644 --- a/src/ifcexpressparser/implementation.py +++ b/src/ifcexpressparser/implementation.py @@ -20,6 +20,8 @@ import codegen import templates +from schema import OrderedCaseInsensitiveDict + class Implementation(codegen.Base): def __init__(self, mapping): enumeration_functions = [] @@ -132,7 +134,7 @@ class Implementation(codegen.Base): def get_attribute_index(entity, attr_name): related_entity = mapping.schema.entities[entity] - return [a['name'] for a in mapping.get_assignable_arguments(related_entity, include_derived=True)].index(attr_name) + return [a['name'].lower() for a in mapping.get_assignable_arguments(related_entity, include_derived=True)].index(attr_name.lower()) inverse = [templates.const_function % { 'class_name' : name, @@ -167,6 +169,13 @@ class Implementation(codegen.Base): 'name' : name, 'padding' : ' ' * (max_len - len(name)) } for name in enumerable_types] + + enumeration_index_by_str = OrderedCaseInsensitiveDict((j,i) for i,j in enumerate(enumerable_types)) + def get_parent_id(s): + e = mapping.schema.entities.get(s) + if e and e.supertypes: + return enumeration_index_by_str[e.supertypes[0]] + else: return -1 parent_type_statements = [templates.parent_type_stmt % { 'name' : name, diff --git a/src/ifcexpressparser/latebound_implementation.py b/src/ifcexpressparser/latebound_implementation.py index 1303d000ff..7b114460ae 100644 --- a/src/ifcexpressparser/latebound_implementation.py +++ b/src/ifcexpressparser/latebound_implementation.py @@ -41,11 +41,10 @@ class LateBoundImplementation(codegen.Base): }) emitted_entities = set() - entities_to_emit = mapping.schema.entities.keys() while len(emitted_entities) < len(mapping.schema.entities): for name, type in mapping.schema.entities.items(): - if name in emitted_entities: continue - if len(type.supertypes) == 0 or set(type.supertypes) < emitted_entities: + if name.lower() in emitted_entities: continue + if len(type.supertypes) == 0 or set(map(str.lower, type.supertypes)) <= emitted_entities: constructor_arguments = mapping.get_assignable_arguments(type, include_derived = True) entity_descriptor_attributes = [] for arg in constructor_arguments: @@ -61,8 +60,9 @@ class LateBoundImplementation(codegen.Base): }) emitted_entities.add(name) + parent_statement = '0' if len(type.supertypes) != 1 else templates.entity_descriptor_parent % { - 'type' : type.supertypes[0] + 'type' : [k for k in mapping.schema.entities.keys() if k.lower() == type.supertypes[0].lower()][0] } entity_descriptors.append(templates.entity_descriptor % { 'type' : name, @@ -92,13 +92,13 @@ class LateBoundImplementation(codegen.Base): if type.inverse: for attr in type.inverse.elements: related_entity = mapping.schema.entities[attr.entity] - related_attrs = [a['name'] for a in mapping.get_assignable_arguments(related_entity, include_derived=True)] + related_attrs = [a['name'].lower() for a in mapping.get_assignable_arguments(related_entity, include_derived=True)] inverse_implementations.append(templates.inverse_implementation % { 'type' : name, 'name' : attr.name, 'related_type' : attr.entity, - 'index' : related_attrs.index(attr.attribute) + 'index' : related_attrs.index(attr.attribute.lower()) }) self.str = templates.lb_implementation % { diff --git a/src/ifcexpressparser/mapping.py b/src/ifcexpressparser/mapping.py index 5b9721e191..285bd00a53 100644 --- a/src/ifcexpressparser/mapping.py +++ b/src/ifcexpressparser/mapping.py @@ -57,7 +57,7 @@ class Mapping: return None if str(parent) in self.express_to_cpp_typemapping else parent def make_type_string(self, type): - if isinstance(type, (str, nodes.BinaryType)): + if isinstance(type, (str, nodes.BinaryType, nodes.StringType)): return self.express_to_cpp_typemapping.get(str(type), type) else: is_list = self.schema.is_entity(type.type) @@ -89,6 +89,8 @@ class Mapping: return "ENTITY_INSTANCE" elif isinstance(type, nodes.BinaryType): return "BINARY" + elif isinstance(type, nodes.StringType): + return "STRING" elif isinstance(type, nodes.EnumerationType): return "ENUMERATION" elif isinstance(type, nodes.AggregationType): @@ -128,10 +130,11 @@ class Mapping: type_str = templates.untyped_list elif self.schema.is_simpletype(ty) or str(ty) in self.express_to_cpp_typemapping.values(): tmpl = templates.nested_array_type if is_nested_list else templates.array_type + bounds = (attr_type.bounds.lower, attr_type.bounds.upper) if attr_type.bounds else (-1, -1) type_str = tmpl % { 'instance_type' : ty, - 'lower' : attr_type.bounds.lower, - 'upper' : attr_type.bounds.upper + 'lower' : bounds[0], + 'upper' : bounds[1] } else: tmpl = templates.list_list_type if is_nested_list else templates.list_type diff --git a/src/ifcexpressparser/nodes.py b/src/ifcexpressparser/nodes.py index d21a29df67..4a16a536da 100644 --- a/src/ifcexpressparser/nodes.py +++ b/src/ifcexpressparser/nodes.py @@ -21,8 +21,8 @@ import string import collections class Node: - def __init__(self, tokens): - self.tokens = tokens + def __init__(self, tokens = None): + self.tokens = tokens or [] self.init() def tokens_of_type(self, cls): return [t for t in self.tokens if isinstance(t, cls)] @@ -128,7 +128,7 @@ class AttributeList(Node): class InverseAttribute(Node): name = property(lambda self: self.tokens[0]) type = property(lambda self: self.tokens[2]) - bounds = property(lambda self: None if len(self.tokens) == 6 else self.tokens[3]) + bounds = property(lambda self: None if len(self.tokens) != 9 else self.tokens[3]) entity = property(lambda self: self.tokens[-4]) attribute = property(lambda self: self.tokens[-2]) def init(self): @@ -170,6 +170,23 @@ class ExplicitAttribute(Node): def init(self): # NB: This assumes a single name per attribute # definition, which is not necessarily the case. + if self.tokens[0] == "self": + i = list(self.tokens).index(":") + self.tokens = self.tokens[i-1:] assert self.tokens[1] == ':' def __repr__(self): return "%s : %s%s" % (self.name, self.type, " ?" if self.optional else "") + + +class WidthSpec(Node): + def init(self): + if self.tokens[-1] == "fixed": + self.tokens[-1:] = [] + assert (self.tokens[0], self.tokens[-1]) == ("(", ")") + self.width = int("".join(self.tokens[1:-1])) + +class StringType(Node): + def init(self): + pass + def __repr__(self): + return "string" diff --git a/src/ifcexpressparser/schema.py b/src/ifcexpressparser/schema.py index 18335f3cdf..4bd87a7c8b 100644 --- a/src/ifcexpressparser/schema.py +++ b/src/ifcexpressparser/schema.py @@ -24,6 +24,28 @@ import collections if tuple(map(int, platform.python_version_tuple())) < (2, 7): import ordereddict collections.OrderedDict = ordereddict.OrderedDict + +# According to ISO 10303-11 7.1.2: Letters: "... The case of +# letters is significant only within explicit string literals." +class OrderedCaseInsensitiveDict(collections.OrderedDict): + class KeyObject(str): + def __eq__(self, other): + return self.lower() == other.lower() + def __hash__(self): + return hash(self.lower()) + + def __init__(self, *args, **kwargs): + collections.OrderedDict.__init__(self) + for key, value in collections.OrderedDict(*args, **kwargs).items(): + self[OrderedCaseInsensitiveDict.KeyObject(key)] = value + def __setitem__(self, key, value): + return collections.OrderedDict.__setitem__(self, OrderedCaseInsensitiveDict.KeyObject(key), value) + def __getitem__(self, key): + return collections.OrderedDict.__getitem__(self, OrderedCaseInsensitiveDict.KeyObject(key)) + def get(self, key, *args, **kwargs): + return collections.OrderedDict.get(self, OrderedCaseInsensitiveDict.KeyObject(key), *args, **kwargs) + def __contains__(self, key): + return collections.OrderedDict.__contains__(self, OrderedCaseInsensitiveDict.KeyObject(key)) class Schema: def is_enumeration(self, v): @@ -39,7 +61,7 @@ class Schema: def __init__(self, parsetree): self.name = parsetree[1] - sort = lambda d: collections.OrderedDict(sorted(d)) + sort = lambda d: OrderedCaseInsensitiveDict(sorted(d)) self.types = sort([(t.name,t) for t in parsetree if isinstance(t, nodes.TypeDeclaration)]) self.entities = sort([(t.name,t) for t in parsetree if isinstance(t, nodes.EntityDeclaration)]) @@ -48,4 +70,4 @@ class Schema: self.enumerations = of_type(nodes.EnumerationType) self.selects = of_type(nodes.SelectType) - self.simpletypes = of_type(str, nodes.AggregationType, nodes.BinaryType) + self.simpletypes = of_type(str, nodes.AggregationType, nodes.BinaryType, nodes.StringType)