def sort_key(tup): return (EXPRESS_ORDER.index(tup.type), express.ifc_name(tup.name))
def __iter__(self): """ A generator that yields tuples of <a, b> with a: an element in EXPRESS_ORDER b: an type/entity/function definition string """ for c in (self.xmi.by_tag_and_type["element"]["uml:Class"] + self.xmi.by_tag_and_type["element"]["uml:Interface"] + self.xmi.by_tag_and_type["element"]["uml:Enumeration"] + self.xmi.by_tag_and_type["element"]["uml:DataType"]): if self.skip_by_package(c): continue stereotype = (c / "properties")[0].stereotype if ( c / "properties") else None if stereotype is not None: stereotype = stereotype.lower() if stereotype in all_excluded_stereotypes: continue status = (c / "project")[0].status if (c / "project") else None if status is not None: status = status.lower() if status not in included_statuses: continue if stereotype in {"express function", "express rule"}: yield xmi_item( stereotype.split(" ")[1].upper(), unescape((c | "behaviour").value).split(" ")[1], fix_schema_name(unescape((c | "behaviour").value)) + ";", c) elif stereotype == 'predefinedtype': # A predefined type enumeration value, handled as part of 'ptcontainer' continue elif stereotype == "propertyset" or stereotype == "quantityset" or ( stereotype is not None and stereotype.startswith("pset_")): yield xmi_item("PSET", c.name, "", c, [(x.name, x) for x in c / ("attribute")]) elif c.xmi_type == "uml:DataType": dd = self.xmi.by_id[c.idref] try: super = [ t for t in c / "tag" if t.name == "ExpressDefinition" ][0].value super_verbatim = True except IndexError as e: try: super = self.xmi.by_id[(dd | "generalization").general].name super_verbatim = False except ValueError as ee: # if the type does not have a generalization, let's assume it's one of EXPRESS' native types logging.warning( "No generalization found on %s, omitting", c) continue cs = sorted(c / "constraint", key=lambda cc: float_international(cc.weight)) constraints = map( lambda cc: "\t%s : %s;" % tuple( map( unescape, map(functools.partial(getattr, cc), ("name", "description")))), cs) yield xmi_item( "TYPE", c.name, express.format_simple_type(c.name, super, constraints, super_verbatim=super_verbatim), c) elif c.xmi_type == "uml:Enumeration": get_attribute = lambda n: [ x for x in self.xmi.by_idref[n.xmi_id] if x.xml.tagName == "attribute" ][0] values = [(x.name, get_attribute(x)) for x in self.xmi.by_id[c.xmi_idref] / "ownedLiteral" ] yield xmi_item( "PENUM" if stereotype == "penumtype" or c.name.lower().startswith("penum_") else "ENUM", # <------- nb c.name, express.format_type(c.name, "ENUMERATION OF", [a for a, b in values]), c, values) elif stereotype == "express select" or stereotype == "select" or c.xmi_type == "uml:Interface": values = sorted( filter( lambda s: s != c.name, map(lambda s: self.xmi.by_id[s.start].name, c / "Substitution"))) yield xmi_item("SELECT", c.name, express.format_type(c.name, "SELECT", values), c) elif stereotype == "enumeration": if "penum_" in c.name.lower(): continue values = map( operator.itemgetter(1), sorted( map(lambda a: (try_get_order(a), (a.name, a)), c / ("attribute")))) yield xmi_item( "ENUM", c.name, express.format_type(c.name, "ENUMERATION OF", [a for a, b in values]), c, values) elif stereotype == 'templatecontainer': nodes = [[ x for x in self.xmi.by_idref[d.supplier] if x.xml.tagName == "element" ][0] for d in self.xmi.by_tag_and_type["packagedElement"] ["uml:Dependency"] if d.client == c.idref] values = [(n.name, n) for n in nodes] # @todo ExpressOrdering on <element> yield xmi_item( "ENUM", c.name, express.format_type(c.name, "ENUMERATION OF", [a for a, b in values]), c, values) elif stereotype == 'ptcontainer': children = [] ids = [ d.end for d in (c / "Dependency") if d.start == c.xmi_idref ] if not ids: print("Warning, no dependency relationship for %s" % c.name) nodes = [ x for x in self.xmi.by_tag_and_type["element"] ["uml:Class"] if x.name.startswith(c.name + ".") ] children = list( map( operator.itemgetter(1), sorted( map( lambda a: (try_get_order(a), (a.name.split('.')[1], a)), nodes)))) values = [a for a, b in children] else: def get_element(id): return [ n for n in self.xmi.by_tag_and_type['element'] ['uml:Class'] if n.idref == id ][0] elems = list(map(get_element, ids)) def get_stereotype(elem): return (elem / "properties")[0].stereotype if ( elem / "properties") else None stereotypes = list(map(get_stereotype, elems)) # pelems = [self.xmi.by_id[n.idref] for n in elems] # [self.skip_by_package(x) for x in pelems] # [(x|"project").status for x in elems] filtered_elems = [ e for e, s in zip(elems, stereotypes) if s.lower() != 'virtualentity' ] children = [(e.name.split('.')[-1], e) for e in filtered_elems] # make unique by name children = [ x for i, x in enumerate(children) if x[0] not in [y[0] for y in children[0:i]] ] values = [a for a, b in children] if "USERDEFINED" not in values: values.append("USERDEFINED") if "NOTDEFINED" not in values: values.append("NOTDEFINED") yield xmi_item( "ENUM", c.name, express.format_type(c.name, "ENUMERATION OF", values), c, children) elif stereotype is not None and (stereotype.startswith("pset") or stereotype == "$"): # Don't serialize psets to EXPRESS # @todo Some psets (and only psets) have their stereotype set to $, why? continue elif stereotype == "virtualentity": continue else: if not c.name.startswith("Ifc"): # @tfk entities need to start with IFC or otherwise 'conceptual'? continue is_abstract = (c / "properties")[0].isAbstract == "true" attribute_names = set() attributes_optional = {} for la in c / ("attribute"): iref = la.idref a = self.xmi.by_id[iref] n = a.name attribute_names.add(n) attributes, inverses, derived, subtypes, supertypes, children = [], [], [], [], [], [] for g in c / ("Generalization"): start, end = g.start, g.end issub = start == c.idref other = [start, end][issub] other_name = self.xmi.by_id[other].name [supertypes, subtypes][issub].append(other_name) for end_node, end_node_type, other_end, assoc_node in self.assocations[ c.name]: try: nm = end_node.name assert nm except: continue try: attr_name = other_end.name except: attr_name = None try: is_inverse, inverse_order, express_aggr, is_optional = self.connectors[ assoc_node.xmi_id][c.name] except: is_inverse, is_optional, express_aggr = False, False, None is_inverse |= assoc_node.xmi_id not in self.order if not is_inverse and end_node.isOrdered is not None: #### express_aggr = "LIST" if end_node.isOrdered == "true" else "SET" if end_node / "lowerValue" and not express_aggr: is_optional |= (end_node | "lowerValue").value == '0' bound = "[0:1]" if is_inverse else "[0:?]" try: lv = int((end_node | "lowerValue").value) uv_s = (end_node | "upperValue").value if uv_s == "*": uv = "?" else: uv = int(uv_s) if uv == -1: uv = "?" bound = "[%s:%s]" % (lv, uv) except: pass attr_entity = end_node_type.name # bound != "[0:?]" and # inverse attributes always aggregates? if is_inverse and end_node.isOrdered is None and bound != "[1:1]": express_aggr = "SET" is_optional_string = "OPTIONAL " if is_optional else "" if express_aggr: attr_entity = "%s %s OF %s" % (express_aggr, bound, attr_entity) if nm not in attribute_names: if (is_inverse and attr_name is not None ): # or assoc_node.xmi_id not in order: inverses.append( (inverse_order, "\t%s : %s FOR %s;" % (nm, attr_entity, attr_name))) else: attribute_order = self.order.get( assoc_node.xmi_id, None) if attribute_order is None: logging.warning("No attribute order on %s.%s" % (c.name, nm)) attribute_order = 1000 attributes.append( (attribute_order, "\t%s : %s%s;" % (nm, is_optional_string, attr_entity))) children.append((nm, assoc_node)) else: # mark as optional for when emitted as an UML attribute below attributes_optional[nm] = is_optional # @tfk workaround for associations with the same name attribute_names.add(nm) for la in c / ("attribute"): iref = la.idref a = self.xmi.by_id[iref] is_optional = "ExpressOptional" in map( operator.attrgetter('name'), la / ("tag")) bnds = la / "bounds" express_aggr = la.tags().get("ExpressAggregation") or ( bnds and bnds[0].upper and bnds[0].upper != '1') if not express_aggr: if bnds: is_optional |= bnds[0].lower == '0' is_optional |= attributes_optional.get(a.name, False) is_derived = a.isDerived == "true" if express_aggr is True and (la / "coords"): express_aggr = "LIST" if ( la / "coords")[0].ordered == "1" else "SET" if express_aggr: bound = "" try: lv = int((a | "lowerValue").value) uv_s = (a | "upperValue").value if uv_s == "*": uv = "?" else: uv = int(uv_s) if uv == -1: uv = "?" bound = "[%s:%s]" % (lv, uv) except: continue # or use order[iref]? try: ordering = int(la.tags()["ExpressOrdering"]) except KeyError as e: ordering = 0 is_optional_string = "OPTIONAL " if is_optional else "" n = a.name try: t = (a | "type").idref except: is_derived = len(la / "Constraint") == 1 if not is_derived: logging.warning("Unable to find type of %s on %s", a, c) continue if is_derived: derived.append("\t%s : %s;" % (n, unescape( (la | "Constraint").notes))) children.append((n, la)) continue attribute_names.add(n) tv = express.ifc_name(self.xmi.by_id[t].name) if express_aggr: tv = "%s %s OF %s" % (express_aggr, bound, tv) override = la.tags().get("ExpressDefinition") if override: tv = override is_optional_string = "" attributes.append( (ordering, "\t%s : %s%s;" % (n, is_optional_string, tv))) children.append((n, la)) cs = sorted(c / ("constraint"), key=lambda cc: float_international(cc.weight)) constraints = map( lambda cc: (cc.type, "\t%s : %s;" % tuple( map( unescape, map(functools.partial(getattr, cc), ("name", "description"))))), cs) constraints_by_type = defaultdict(list) for ct, cc in constraints: constraints_by_type[ct].append(fix_schema_name(cc)) attributes = map(operator.itemgetter(1), sorted(attributes)) inverses = map(operator.itemgetter(1), sorted(inverses)) yield xmi_item( "ENTITY", c.name, express.format_entity( c.name, attributes, derived, inverses, constraints_by_type["EXPRESS_WHERE"], constraints_by_type["EXPRESS_UNIQUE"], subtypes, supertypes, is_abstract), c, children)
def process(node): node_name = express.ifc_name(node.name) docs = (node / "properties")[0].documentation mdfn = os.path.join(output, node_name[3], node_name + ".md") if not os.path.exists(os.path.dirname(mdfn)): os.makedirs(os.path.dirname(mdfn)) stereotype = (node / "properties")[0].stereotype if stereotype is not None: stereotype = stereotype.lower() if stereotype in { 'predefinedtype', 'ptcontainer', '$', 'enumeration', 'propertyset', 'complexproperty' }: # @todo handle enumerations and 'ptcontainers' return if stereotype is not None and (stereotype.startswith('pset') or stereotype.startswith('qto_')): return with open(mdfn, 'w', encoding='utf-8') as f: print(node_name, file=f) print('=' * len(node_name), file=f) if docs is not None: print(format(docs), file=f) if node.xmi_type == "uml:Class": attrs = [(la.name, format((la | "documentation").value or '', cell=True)) for la in node / ("attribute")] if len(attrs): print("Attribute definitions", file=f) print("---------------------", file=f) print(tabulate.tabulate(attrs, headers=['Attribute', 'Description'], tablefmt="github"), file=f) print(file=f) cs = sorted(node / "constraint", key=lambda cc: float_international(cc.weight)) cs_names = map(operator.attrgetter('name'), cs) cs_names_cells = [[n, ''] for n in cs_names] if len(cs_names_cells): print("Formal Propositions", file=f) print("-------------------", file=f) print(tabulate.tabulate(cs_names_cells, headers=['Rule', 'Description'], tablefmt="github"), file=f) print(file=f) assoc_names = [tup.own_end.name for tup in assocations[c.name]] assoc_names_cells = [[n, ''] for n in assoc_names] if len(assoc_names_cells): print("Associations", file=f) print("------------", file=f) print(tabulate.tabulate(assoc_names_cells, headers=['Attribute', 'Description'], tablefmt="github"), file=f) print(file=f)
def generate_definitions(): """ A generator that yields tuples of <a, b> with a: an element in EXPRESS_ORDER b: an type/entity/function definition string """ for d in xmi.by_tag_and_type["element"]["uml:DataType"]: dd = xmi.by_id[d.idref] try: super = [t for t in d / "tag" if t.name == "ExpressDefinition"][0].value except IndexError as e: try: super = xmi.by_id[(dd | "generalization").general].name except ValueError as ee: # if the type does not have a generalization, let's assume it's one of EXPRESS' native types logging.warning("No generalization found on %s, omitting", d) continue cs = sorted(d / "constraint", key=lambda cc: float(cc.weight)) constraints = map( lambda cc: "\t%s : %s;" % tuple( map( html.unescape, map(functools.partial(getattr, cc), ("name", "description")))), cs) yield "TYPE", d.name, express.format_simple_type( d.name, super, constraints) for c in xmi.by_tag_and_type["element"]["uml:Class"]: stereotype = (c / "properties")[0].stereotype if stereotype is not None: stereotype = stereotype.lower() if stereotype in {"express function", "express rule"}: yield stereotype.split(" ")[1].upper(), html.unescape( (c | "behaviour").value).split(" ")[1], html.unescape( (c | "behaviour").value) + ";" elif stereotype == "express select": values = filter( lambda s: s != c.name, map(lambda s: xmi.by_id[s.start].name, c / "Substitution")) yield "SELECT", c.name, express.format_type( c.name, "SELECT", values) elif stereotype == "enumeration": def try_get_order(a): try: return int([ t for t in a / ("tag") if t.name == "ExpressOrdering" ][0].value) except IndexError as e: return 0 values = map( operator.itemgetter(1), sorted( map(lambda a: (try_get_order(a), a.name), c / ("attribute")))) yield "ENUM", c.name, express.format_type(c.name, "ENUMERATION OF", values) elif stereotype is not None and (stereotype.startswith("pset") or stereotype == "$"): # Don't serialize psets to EXPRESS # @todo Some psets (and only psets) have their stereotype set to $, why? continue else: is_abstract = (c / "properties")[0].isAbstract == "true" attribute_names = set() for la in c / ("attribute"): iref = la.idref a = xmi.by_id[iref] n = a.name attribute_names.add(n) attributes, inverses, derived, subtypes, supertypes = [], [], [], [], [] for g in c / ("Generalization"): start, end = g.start, g.end issub = start == c.idref other = [start, end][issub] other_name = xmi.by_id[other].name [supertypes, subtypes][issub].append(other_name) for end_node, end_node_type, other_end, assoc_node in assocations[ c.name]: try: nm = end_node.name assert nm except: continue try: attr_name = other_end.name except: attr_name = None bound = "" try: lv = int((end_node | "lowerValue").value) uv = int((end_node | "upperValue").value) if uv == -1: uv = "?" bound = "[%s:%s]" % (lv, uv) except: pass attr_entity = end_node_type.name is_inverse, inverse_order, express_aggr, is_optional = connectors[ assoc_node.xmi_id][c.name] is_optional_string = "OPTIONAL " if is_optional else "" if express_aggr: attr_entity = "%s %s OF %s" % (express_aggr, bound, attr_entity) if nm not in attribute_names: if (is_inverse and attr_name is not None) or assoc_node.xmi_id not in order: inverses.append((inverse_order, "\t%s : %s FOR %s;" % (nm, attr_entity, attr_name))) else: attribute_order = order[assoc_node.xmi_id] attributes.append( (attribute_order, "\t%s : %s%s;" % (nm, is_optional_string, attr_entity))) for la in c / ("attribute"): iref = la.idref a = xmi.by_id[iref] is_optional = "ExpressOptional" in map( operator.attrgetter('name'), la / ("tag")) is_derived = a.isDerived == "true" express_aggr = la.tags().get("ExpressAggregation") if express_aggr: bound = "" try: lv = int((a | "lowerValue").value) uv = int((a | "upperValue").value) if uv == -1: uv = "?" bound = "[%s:%s]" % (lv, uv) except: continue # or use order[iref]? try: ordering = int(la.tags()["ExpressOrdering"]) except KeyError as e: ordering = 0 is_optional_string = "OPTIONAL " if is_optional else "" n = a.name try: t = (a | "type").idref except: is_derived = len(la / "Constraint") == 1 if not is_derived: logging.warning("Unable to find type of %s on %s", a, c) continue if is_derived: derived.append("\t%s : %s;" % (n, html.unescape( (la | "Constraint").notes))) continue attribute_names.add(n) tv = express.ifc_name(xmi.by_id[t].name) if express_aggr: tv = "%s %s OF %s" % (express_aggr, bound, tv) attributes.append( (ordering, "\t%s : %s%s;" % (n, is_optional_string, tv))) cs = sorted(c / ("constraint"), key=lambda cc: float(cc.weight)) constraints = map( lambda cc: (cc.type, "\t%s : %s;" % tuple( map( html.unescape, map(functools.partial(getattr, cc), ("name", "description"))))), cs) constraints_by_type = defaultdict(list) for ct, cc in constraints: constraints_by_type[ct].append(cc) attributes = map(operator.itemgetter(1), sorted(attributes)) inverses = map(operator.itemgetter(1), sorted(inverses)) yield "ENTITY", c.name, express.format_entity( c.name, attributes, derived, inverses, constraints_by_type["EXPRESS_WHERE"], constraints_by_type["EXPRESS_UNIQUE"], subtypes, supertypes, is_abstract)
def __iter__(self): """ A generator that yields tuples of <a, b> with a: an element in EXPRESS_ORDER b: an type/entity/function definition string """ for c in (self.xmi.by_tag_and_type["element"]["uml:Class"] + self.xmi.by_tag_and_type["element"]["uml:Interface"] + self.xmi.by_tag_and_type["element"]["uml:Enumeration"] + self.xmi.by_tag_and_type["element"]["uml:DataType"]): if self.skip_by_package(c): continue stereotype = (c/"properties")[0].stereotype if (c/"properties") else None if stereotype is not None: stereotype = stereotype.lower() if stereotype in all_excluded_stereotypes: continue status = (c/"project")[0].status if (c/"project") else None if status is not None: status = status.lower() if status not in included_statuses: continue if stereotype in {"express function", "express rule"}: yield xmi_item( stereotype.split(" ")[1].upper(), unescape((c|"behaviour").value).split(" ")[1], fix_schema_name(unescape((c|"behaviour").value)) + ";", c, document=self ) elif stereotype == 'predefinedtype': # A predefined type enumeration value, handled as part of 'ptcontainer' continue # NB: can have multiple dependency relationships... # yield xmi_item("PT", c.name, "", c, [], container=((c|"links")|"Dependency").start, document=self) elif stereotype is not None and (stereotype.startswith("pset_") or stereotype.startswith("qto_") or stereotype == "complexproperty"): if stereotype.startswith("qto_"): set_stereotype = "QSET" elif stereotype.startswith("pset_"): set_stereotype = "PSET" else: set_stereotype = "CPROP" refs = None if set_stereotype == "PSET" or set_stereotype == "QSET": try: concept_to_use = ["PropertySetsforObjects", "PropertySetsforContexts", "QuantitySets", "PropertySetsforMaterials", "PropertySetsforProfiles", "PropertySetsforPerformance"] refs = sum((self.concepts[ctu].get(c.id or c.idref, []) for ctu in concept_to_use), []) except ValueError as e: print("WARNING:", c.name, "has no associated class", file=sys.stderr) continue # There are several kinds of pset and qset association mechanisms: # # - occurence driven: only applicable to IfcObject subtypes # - type driven: only applicable to IfcTypeObject subtypes # - type driven override: applicable to both IfcObject and IfcTypeObject # where the former can override the latter # - material driven: applicable to IfcMaterialDefinition, uses IfcMaterialProperties # - profile driven: applicable to IfcProfileDef, uses IfcProfileProperties # # In UML we associate only to the IfcObject subtype and have the # pset mechanism encoded in the property set stereotype name. # # In this step we make sure that the serialized data corresponds # to the association mechanism of the pset. is_type_driven_only = "typedrivenonly" in stereotype is_type_driven_override = "typedrivenoverride" in stereotype if self.should_translate_pset_types and (is_type_driven_override or is_type_driven_only): get_name = lambda id_: self.xmi.by_id[id_].name ref_names = list(map(get_name, refs)) def substitute_predefined_type_with_containing_entity(ref_name): """ PQsets can also be associated to a predefined type which is modelled in UML as a class with a DOT in its name, signalling the enum container and predefined type value. We should trace the containment of such a predefined type ot the relevant entity by means of the formal UML association, but we take a shortcut here by looking at the name. """ if "." in ref_name: type_name, type_value = ref_name.split(".") entity_name = re.sub("(Type)?Enum$", "", type_name) entity = [c for c in self.xmi.by_tag_and_type["packagedElement"]["uml:Class"] if c.name == entity_name][0] return entity.xmi_id, type_value ref_substituted_entities = list(map(substitute_predefined_type_with_containing_entity, ref_names)) refs_combined = [b if b else (a,) for a, b in zip(refs, ref_substituted_entities)] # Lookup corresponding object types for the applicable entities object_typing_reversed = {v[0]: k for k, v in self.concepts['ObjectTyping'].items() if len(v) == 1} corresponding_types = list(filter(None, map(lambda id_pt: object_typing_reversed.get(id_pt[0], None), refs_combined))) if len(corresponding_types) != len(refs) or set(corresponding_types) & set(refs): ref_names = map(get_name, refs) ref_type_names = map(get_name, corresponding_types) print(f"WARNING: For PQset {c.name} applicability [{', '.join(ref_names)}] results in type associations [{', '.join(ref_type_names)}]") # augment with predefined types again: corresponding_types = [(a,) + b[1:] if b[1:] else a for a, b in zip(corresponding_types, refs_combined)] if is_type_driven_only: refs = corresponding_types else: refs = refs + corresponding_types # TEMPORARY SKIP SOME OLD PSET DEFINITIONS if (c|"project").author == 'IQSOFT': continue set_definition = [] for attr in self.xmi.by_id[c.idref]/"ownedAttribute": nm = attr.name ty = self.xmi.by_id[(attr|"type").idref] if set_stereotype in ("PSET", "CPROP"): for b in ty/"templateBinding": if b/"parameterSubstitution": prop_type = self.xmi.by_id[b.signature].xml.parentNode.getAttribute('name') prop_args = {} for sub in b/"parameterSubstitution": formal = (self.xmi.by_id[sub.formal] | "ownedParameteredElement").name actual = self.xmi.by_id[sub.actual].name prop_args[formal] = actual set_definition.append((nm, (prop_type, prop_args))) else: set_definition.append((nm, ty.name)) yield xmi_item( "CPROP" if set_stereotype == "CPROP" else "PSET", c.name, set_definition, c, [(x.name, x) for x in c/("attribute")], document=self, refs=refs, stereotype=set_stereotype ) elif c.xmi_type == "uml:DataType": dd = self.xmi.by_id[c.idref] try: super = [t for t in c/"tag" if t.name == "ExpressDefinition"][0].value super_verbatim = True except IndexError as e: try: super = self.xmi.by_id[(dd|"generalization").general].name super_verbatim = False except ValueError as ee: # if the type does not have a generalization, let's assume it's one of EXPRESS' native types logging.warning("No generalization found on %s, omitting", c) continue cs = sorted(c/"constraint", key=lambda cc: float_international(cc.weight)) constraints = map(lambda cc: "\t%s : %s;" % tuple(map(unescape, map(functools.partial(getattr, cc), ("name", "description")))), cs) yield xmi_item("TYPE", c.name, express.simple_type(c.name, super, constraints, super_verbatim=super_verbatim), c, document=self) elif self.xmi.by_id[c.xmi_idref].xmi_type == "uml:Enumeration": values = [(x.name.split(".")[1], x) for x in self.xmi.by_tag_and_type["packagedElement"]["uml:Class"] if x.name.startswith(c.name+".")] get_attribute = lambda n: [x for x in self.xmi.by_idref[n.xmi_id] if x.xml.tagName == "attribute"][0] if not values: values = [(x.name, get_attribute(x)) for x in self.xmi.by_id[c.xmi_idref]/"ownedLiteral"] is_property_enum = c.name.lower().startswith("penum_") # alphabetical sort, but the items below always come last undefined = ("OTHER", "NOTKNOWN", "UNSET", "USERDEFINED", "NOTDEFINED") penalize_undefined = lambda tup: f"z{undefined.index(tup[0]):02d}" if tup[0] in undefined else tup[0] values = sorted(values, key=penalize_undefined) express_definition = "" if not is_property_enum: # not that it would fail, but they're just not intended to be written to EXP express_definition = express.enumeration(c.name, [a for a, b in values]) yield xmi_item( "PENUM" if is_property_enum else "ENUM", c.name, express_definition, c, values, document=self) elif stereotype == "express select" or stereotype == "select" or c.xmi_type == "uml:Interface": values = sorted(filter(lambda s: s != c.name, map(lambda s: self.xmi.by_id[s.start].name, c/"Substitution"))) yield xmi_item("SELECT", c.name, express.select(c.name, values), c, document=self) elif stereotype == "virtualentity": continue else: if not c.name.startswith("Ifc") or "." in c.name: # @tfk entities need to start with IFC or otherwise 'conceptual'? # ^ probably no longer relevant, all UML data is now relevant # @todo: # some enumerations elements for the concepts need a stereotype probably, # or we can filter them out looking at the uml:Dependency. For now # checking the dot in the name is quickest. continue is_abstract = (c/"properties")[0].isAbstract == "true" attribute_names = set() attributes_optional = {} for la in c / ("attribute"): iref = la.idref a = self.xmi.by_id[iref] n = a.name attribute_names.add(n) attributes, inverses, derived, subtypes, supertypes, children = [], [], [], [], [], [] for g in c/("Generalization"): start, end = g.start, g.end issub = start == c.idref other = [start, end][issub] other_name = self.xmi.by_id[other].name [supertypes, subtypes][issub].append(other_name) # In case of asymmetric inverse relationships we need to find the # proper target element. assocs_by_name = self.assocations[c.name].copy() # count duplicate role names counter = Counter() counter.update(ass[0].name for ass in assocs_by_name) # flag duplicates duplicates = [ass[0].name is not None and counter[ass[0].name] > 1 for ass in assocs_by_name] # look up suppression tag suppressed = [self.xmi.tags['ExpressSuppressRel'].get(ass[2].id) == "YES" for ass in self.assocations[c.name]] # apply filter assocs_by_name = [a for a,d,s in zip(assocs_by_name, duplicates, suppressed) if not (d and s)] for end_node, end_node_type, other_end, assoc_node in assocs_by_name: try: nm = end_node.name assert nm except: continue try: attr_name = other_end.name except: attr_name = None is_inverse = bool(self.xmi.tags['ExpressInverse'].get(end_node.id)) if is_inverse: inverse_order = int(self.xmi.tags['ExpressOrderingInverse'].get(end_node.id, 1e6)) express_aggr = self.xmi.tags['ExpressAggregation'].get(end_node.id, "") # It appears there is some inconsistency (IfcBSplineSurface.ControlPointList) # where the definition is located on the association and not on the member ends express_definition = self.xmi.tags['ExpressDefinition'].get(end_node.id) or \ self.xmi.tags['ExpressDefinition'].get(assoc_node.id) is_optional = bool(self.xmi.tags['ExpressOptional'].get(end_node.id, False)) is_unique = bool(self.xmi.tags['ExpressUnique'].get(end_node.id, False)) # @tfk this was misguided # is_inverse |= assoc_node.xmi_id not in self.order # @tfk this is no longer working, rely fully on `express_aggr` # if not is_inverse and end_node.isOrdered is not None: # express_aggr = "LIST" if end_node.isOrdered == "true" else "SET" if end_node/"lowerValue" and not express_aggr: is_optional |= (end_node|"lowerValue").value == '0' bound = "[0:1]" if is_inverse else "[0:?]" try: lv = int((end_node|"lowerValue").value) uv_s = (end_node|"upperValue").value if uv_s == "*": uv = "?" else: uv = int(uv_s) if uv == -1: uv = "?" bound = "[%s:%s]" % (lv, uv) except: pass attr_entity = end_node_type.name # bound != "[0:?]" and # inverse attributes always aggregates? if is_inverse and end_node.isOrdered is None and bound != "[1:1]": express_aggr = "SET" is_optional_string = "OPTIONAL " if is_optional else "" express_aggr_unique = "UNIQUE " if is_unique else "" if express_aggr: attr_entity = "%s %s OF %s%s" % (express_aggr, bound, express_aggr_unique, attr_entity) if express_definition: attr_entity = express_definition if nm not in attribute_names: if is_inverse: # or assoc_node.xmi_id not in order: if attr_name is not None: inverses.append((inverse_order, "\t%s : %s FOR %s;" % (nm, attr_entity, attr_name))) else: logging.warning("No role name in connector target for %s.%s" % (c.name, nm)) else: attribute_order = self.order.get(assoc_node.xmi_id, None) if attribute_order is None: attribute_order = self.order.get(end_node.xmi_id, None) if attribute_order is None: logging.warning("No attribute order on %s.%s" % (c.name, nm)) attribute_order = 1000 attributes.append((attribute_order, (nm, is_optional_string + attr_entity))) children.append((nm, assoc_node)) else: # mark as optional for when emitted as an UML attribute below attributes_optional[nm] = is_optional # @tfk workaround for associations with the same name attribute_names.add(nm) for la in c/("attribute"): iref = la.idref a = self.xmi.by_id[iref] is_optional = "ExpressOptional" in map(operator.attrgetter('name'), la/("tag")) is_unique = bool(self.xmi.tags['ExpressUnique'].get(iref, False)) bnds = la/"bounds" express_aggr = la.tags().get("ExpressAggregation") or (bnds and bnds[0].upper and bnds[0].upper != '1') if not express_aggr: if bnds: is_optional |= bnds[0].lower == '0' is_optional |= attributes_optional.get(a.name, False) is_derived = a.isDerived == "true" if express_aggr is True and (la/"coords"): express_aggr = "LIST" if (la/"coords")[0].ordered == "1" else "SET" if express_aggr: bound = "" try: lv = int((a|"lowerValue").value) uv_s = (a|"upperValue").value if uv_s == "*": uv = "?" else: uv = int(uv_s) if uv == -1: uv = "?" bound = "[%s:%s]" % (lv, uv) except: continue # or use order[iref]? try: ordering = int(la.tags()["ExpressOrdering"]) except KeyError as e: ordering = 0 is_optional_string = "OPTIONAL " if is_optional else "" n = a.name try: t = (a|"type").idref except: is_derived = len(la/"Constraint") == 1 if not is_derived: logging.warning("Unable to find type of %s on %s", a, c) continue if is_derived: derive_def = unescape((la|"Constraint").notes) if derive_def.startswith("%s : " % n): logging.warning("Derived attribute definition for %s contains attribute name: %s", n, derive_def) derive_def = derive_def[len(n) + 3:] derived.append((ordering, "\t%s : %s;" % (n, derive_def))) children.append((n, la)) continue attribute_names.add(n) tv = express.ifc_name(self.xmi.by_id[t].name) if express_aggr: tv = "%s %s OF %s%s" % (express_aggr, bound, "UNIQUE " if is_unique else "", tv) override = la.tags().get("ExpressDefinition") if override: tv = override is_optional_string = "" attributes.append((ordering, (n, is_optional_string + tv))) children.append((n, la)) def trailing_semi_fix(s): if s.endswith(';'): logging.warning("Definition '%s' ends in semicolon", s) return s[:-1] else: return s cs = c/("constraint") constraints_type_name_def = map(lambda cc: ((cc.type,) + tuple(map(trailing_semi_fix, map(unescape, map(functools.partial(getattr, cc), ("name", "description")))))), cs) constraints_by_type = defaultdict(list) for ct, cname, cdef in constraints_type_name_def: if cdef.startswith("%s : " % cname): logging.warning("Where clause for %s contains attribute name: %s", cname, cdef) cdef = cdef[len(cname) + 3:] cc = (cname.strip(), fix_schema_name(cdef)) constraints_by_type[ct].append(cc) attributes = list(map(operator.itemgetter(1), sorted(attributes))) inverses = map(operator.itemgetter(1), sorted(inverses)) derived = map(operator.itemgetter(1), sorted(derived)) attribute_dict = dict(attributes) itm_supertype_names = set(self.supertypes(c.idref)) is_occurence=itm_supertype_names & {"IfcElement", "IfcSystem", "IfcSpatialStructureElement"} is_type = "IfcElementType" in itm_supertype_names if is_type or is_occurence: if "PredefinedType" in attribute_dict: type_attr = attribute_dict["PredefinedType"] type_name = type_attr.split(" ")[-1] type_optional = "OPTIONAL" in type_attr attr = "IfcElementType.ElementType" if is_type else "IfcObject.ObjectType" clause_1 = "NOT(EXISTS(PredefinedType)) OR\n " if type_optional else '' rule = clause_1 + "(PredefinedType <> %(type_name)s.USERDEFINED) OR\n ((PredefinedType = %(type_name)s.USERDEFINED) AND EXISTS (SELF\\%(attr)s))" % locals() constraints_by_type["EXPRESS_WHERE"].append(("CorrectPredefinedType", rule)) if is_occurence: if c.name + "Type" in [x.name for x in self.xmi.by_tag_and_type["packagedElement"]["uml:Class"]]: ty_def = [x for x in self.xmi.by_tag_and_type["packagedElement"]["uml:Class"] if x.name == c.name + "Type"][0] ty_attr_names = [attr.name for attr in ty_def/"ownedAttribute"] if "PredefinedType" in ty_attr_names: constraints_by_type["EXPRESS_WHERE"].append( ("CorrectTypeAssigned", "(SIZEOF(IsTypedBy) = 0) OR\n ('%(schema_name)s.%(entity_name_upper)sTYPE' IN TYPEOF(SELF\\IfcObject.IsTypedBy[1].RelatingType))" % {'schema_name': SCHEMA_NAME, 'entity_name_upper': c.name.upper()}) ) express_entity = express.entity(c.name, attributes, derived, inverses, sorted(constraints_by_type["EXPRESS_WHERE"]), sorted(constraints_by_type["EXPRESS_UNIQUE"]), subtypes, supertypes, is_abstract ) yield xmi_item( "ENTITY", c.name, express_entity, c, children, document=self, supertypes=subtypes )