Skip to main content

ifc_schema/
attribute.rs

1//! One positional attribute slot on an IFC entity, and aggregate shapes.
2//!
3//! This crate owns the types rather than re-exporting the EXPRESS
4//! extractor's: the bundled tables are decoded straight into them, so a
5//! release of the parser never changes this crate's public API.
6
7/// The four EXPRESS aggregation types (ISO 10303-11 ยง8.2).
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9#[non_exhaustive]
10pub enum AggregateKind {
11    /// `LIST`: ordered, duplicates allowed unless `UNIQUE`.
12    List,
13    /// `SET`: unordered, no duplicates.
14    Set,
15    /// `BAG`: unordered, duplicates allowed.
16    Bag,
17    /// `ARRAY`: fixed-size, indexed by its bounds.
18    Array,
19}
20
21/// One bound of an aggregation, as declared.
22#[derive(Debug, Clone, PartialEq, Eq, Hash)]
23#[non_exhaustive]
24pub enum Bound {
25    /// An integer literal.
26    Integer(u64),
27    /// `?`: no upper limit.
28    Unbounded,
29    /// Any other bound, a reference to another attribute such as
30    /// `SELF\IfcBSplineCurve.UpperIndexOnControlPoints`, kept as written
31    /// with whitespace normalised. It is not evaluated.
32    Expression(String),
33}
34
35impl Bound {
36    /// The bound as an integer, when it is a literal.
37    #[must_use]
38    pub const fn as_integer(&self) -> Option<u64> {
39        match self {
40            Self::Integer(value) => Some(*value),
41            _ => None,
42        }
43    }
44}
45
46/// One aggregation level of a declared type, e.g. `LIST [1:3] OF ...`.
47#[derive(Debug, Clone, PartialEq, Eq, Hash)]
48#[non_exhaustive]
49pub struct Aggregation {
50    /// Which aggregation type.
51    pub kind: AggregateKind,
52    /// Lower bound. `0` when a `LIST`, `SET` or `BAG` omits its bounds,
53    /// which ISO 10303-11 defines as `[0:?]`.
54    pub lower: Bound,
55    /// Upper bound; [`Bound::Unbounded`] for `?` or omitted bounds.
56    pub upper: Bound,
57    /// Whether the elements are declared `UNIQUE` (`OF UNIQUE ...`). A `SET`
58    /// is unique by definition whether or not this is set.
59    pub unique: bool,
60    /// Whether an `ARRAY`'s elements are declared `OPTIONAL`.
61    pub optional_elements: bool,
62}
63
64impl Aggregation {
65    /// Creates an aggregation level with explicit bounds.
66    #[must_use]
67    pub const fn new(kind: AggregateKind, lower: Bound, upper: Bound) -> Self {
68        Self {
69            kind,
70            lower,
71            upper,
72            unique: false,
73            optional_elements: false,
74        }
75    }
76
77    /// Marks the elements `UNIQUE`.
78    #[must_use]
79    pub const fn unique(mut self) -> Self {
80        self.unique = true;
81        self
82    }
83
84    /// Marks an `ARRAY`'s elements `OPTIONAL`.
85    #[must_use]
86    pub const fn optional_elements(mut self) -> Self {
87        self.optional_elements = true;
88        self
89    }
90
91    /// Whether duplicate elements are forbidden: `UNIQUE`, or a `SET`.
92    #[must_use]
93    pub const fn forbids_duplicates(&self) -> bool {
94        self.unique || matches!(self.kind, AggregateKind::Set)
95    }
96}
97
98/// One explicit positional attribute declared by an entity.
99///
100/// `#[non_exhaustive]`: build one with [`Attribute::new`] and the builder
101/// methods. Further facts about a declaration are added as new fields
102/// without breaking existing readers.
103#[derive(Debug, Clone, PartialEq, Eq)]
104#[non_exhaustive]
105pub struct Attribute {
106    /// Declared attribute name.
107    pub name: String,
108    /// Declared scalar type, or the innermost element type of an aggregate:
109    /// `IfcLengthMeasure` for `LIST [1:?] OF LIST [3:3] OF IfcLengthMeasure`.
110    pub type_name: String,
111    /// Whether `OPTIONAL` was present.
112    pub optional: bool,
113    /// Whether a `LIST`, `SET`, `ARRAY`, or `BAG` wrapper was present.
114    pub aggregate: bool,
115    /// Aggregation levels with their bounds, outermost first; empty for a
116    /// scalar. A table written before bounds were recorded (artifact format
117    /// 1 or 2) has `aggregate` set and this empty.
118    pub aggregation: Vec<Aggregation>,
119}
120
121impl Attribute {
122    /// Creates a required scalar attribute.
123    #[must_use]
124    pub fn new(name: impl Into<String>, type_name: impl Into<String>) -> Self {
125        Self {
126            name: name.into(),
127            type_name: type_name.into(),
128            optional: false,
129            aggregate: false,
130            aggregation: Vec::new(),
131        }
132    }
133
134    /// Marks the attribute as optional.
135    #[must_use]
136    pub const fn optional(mut self) -> Self {
137        self.optional = true;
138        self
139    }
140
141    /// Marks the attribute as an aggregate without recording its shape.
142    ///
143    /// Prefer [`Self::with_aggregation`], which records kind and bounds.
144    #[must_use]
145    pub const fn aggregate(mut self) -> Self {
146        self.aggregate = true;
147        self
148    }
149
150    /// Wraps the type in one more aggregation level, inside any already
151    /// added: call it outermost first. Also sets [`Self::aggregate`].
152    #[must_use]
153    pub fn with_aggregation(mut self, aggregation: Aggregation) -> Self {
154        self.aggregation.push(aggregation);
155        self.aggregate = true;
156        self
157    }
158}