Exemplo n.º 1
0
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)
Exemplo n.º 3
0
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)
Exemplo n.º 4
0
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)
Exemplo n.º 5
0
    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
                )