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}