ifc_properties/exact/enumerate.rs
1//! Exact enumeration of an object's properties (#78).
2//!
3//! A buildingSMART IDS property facet may name its set and property by
4//! pattern (`Pset_.*Common`), and then every matching property must satisfy
5//! the requirement. Deciding that needs every match, and an empty result
6//! must be as much a proof as [`ExactResolution::Absent`]. So enumeration
7//! runs the traversal of [`exact_property`] with two selectors in place of
8//! its two names, and for one set name and one property name it answers
9//! exactly what [`exact_property`] answers:
10//!
11//! - Model and assignment validation are the same.
12//! - A set whose name the set selector rejects is skipped unread, as
13//! [`exact_property`] skips a set of another name. Every member of a
14//! selected set is validated before anything is matched.
15//! - A selected predefined-set attribute that cannot be read exactly (an
16//! aggregate), and an unnamed predefined set outside the set selection
17//! one of whose own attributes is selected, are refused. A complex
18//! property or quantity resolves as `ExactValue::Complex` (#208). An
19//! unselected member must be well formed but need not have a supported
20//! value form.
21//!
22//! [`ExactResolution::Absent`]: super::ExactResolution::Absent
23//! [`exact_property`]: super::exact_property
24
25use std::{collections::BTreeMap, sync::Arc};
26
27use ifc_model::{EntityId, Model};
28
29use super::assignment::assigned_sets;
30use super::release::{validate_model, Release};
31use super::set::load_source_set;
32use super::{ExactProperty, ExactPropertyError, ExactSource};
33
34/// One property of an enumeration, with the name it was selected by.
35#[derive(Debug, Clone, PartialEq)]
36pub struct ExactPropertyEntry {
37 /// `IfcProperty.Name` or `IfcPhysicalQuantity.Name`.
38 pub name: Arc<str>,
39 /// The resolved property with its provenance, as [`exact_property`]
40 /// reports it.
41 ///
42 /// [`exact_property`]: super::exact_property
43 pub property: ExactProperty,
44}
45
46/// Every property, simple quantity and predefined-set attribute of
47/// `object`, resolved exactly.
48///
49/// Equivalent to [`exact_properties_where`] selecting every set and every
50/// property, so any predefined set with an aggregate attribute is refused.
51/// Every property and quantity kind resolves, complex ones included, as for
52/// [`exact_property`](super::exact_property). Callers that need
53/// only some properties (an IDS pattern facet) select them with
54/// [`exact_properties_where`], so an unsupported member they do not ask
55/// about cannot refuse their answer.
56///
57/// # Errors
58///
59/// Any [`ExactPropertyError`], as for [`exact_property`](super::exact_property).
60pub fn exact_properties(
61 model: &Model,
62 object: EntityId,
63) -> Result<Vec<ExactPropertyEntry>, ExactPropertyError> {
64 exact_properties_where(model, object, |_| true, |_| true)
65}
66
67/// The properties and simple quantities of `object` in the sets
68/// `select_set` picks, whose names `select_property` picks, resolved
69/// exactly.
70///
71/// Both selectors see names only: `select_set` the `Name` of each assigned
72/// property set and quantity set, `select_property` the `Name` of each
73/// member of a selected set. With `|s| s == set` and `|p| p == name` the
74/// result is `[x]` exactly when [`exact_property`] with that set name
75/// answers `Present(x)`, empty exactly when it answers `Absent`, and the
76/// same error otherwise.
77///
78/// A predefined property set (`IfcDoorLiningProperties` and the like) holds
79/// its values in attributes of its own entity (#149). Its members are the
80/// attributes it declares below `IfcPropertySetDefinition`, by schema name
81/// (`LiningDepth`), and `select_set` sees its `Name`, or its entity name
82/// (`IfcDoorLiningProperties`) when it states none. Because a set without a
83/// `Name` cannot be ruled out by a name, such a set that `select_set` rejects
84/// is still refused with [`ExactPropertyError::UnsupportedDefinition`] when
85/// `select_property` picks one of its attributes (#66).
86///
87/// The result is in assignment order: occurrence sets first, then the sets
88/// inherited from the object's `IfcTypeObject`, each set's members in file
89/// order. An inherited property is left out when an occurrence set of the
90/// same name has a selected property of the same name: the occurrence
91/// value overrides it at property level. An empty result is a proven
92/// absence of every selected property. For a queried `IfcTypeObject` the
93/// result is its own `HasPropertySets`, each entry with
94/// [`ExactSource::Type`] of `object` (#193), as for
95/// [`exact_property`].
96///
97/// # Errors
98///
99/// Any [`ExactPropertyError`], as for [`exact_property`]. Ambiguity is
100/// refused per source: two selected sets of the same name, a property set
101/// and a quantity set included
102/// ([`ExactPropertyError::DuplicateMatchingSets`]), and two selected members
103/// of one set with the same name
104/// ([`ExactPropertyError::DuplicateMatchingProperties`]).
105///
106/// [`exact_property`]: super::exact_property
107pub fn exact_properties_where<S, P>(
108 model: &Model,
109 object: EntityId,
110 mut select_set: S,
111 mut select_property: P,
112) -> Result<Vec<ExactPropertyEntry>, ExactPropertyError>
113where
114 S: FnMut(&str) -> bool,
115 P: FnMut(&str) -> bool,
116{
117 let release = validate_model(model)?;
118 let assigned = assigned_sets(model, release, object)?;
119 let mut selector = Selector {
120 set: &mut select_set,
121 property: &mut select_property,
122 };
123 let mut entries = collect(
124 model,
125 release,
126 &assigned.occurrence_sets,
127 ExactSource::Occurrence,
128 &mut selector,
129 )?;
130 if let Some((type_id, sets)) = &assigned.type_sets {
131 let inherited = collect(
132 model,
133 release,
134 sets,
135 ExactSource::Type(*type_id),
136 &mut selector,
137 )?;
138 let overridden: Vec<(Arc<str>, Arc<str>)> = entries
139 .iter()
140 .map(|entry| (entry.property.property_set.clone(), entry.name.clone()))
141 .collect();
142 entries.extend(inherited.into_iter().filter(|entry| {
143 !overridden
144 .iter()
145 .any(|(set, name)| *set == entry.property.property_set && *name == entry.name)
146 }));
147 }
148 Ok(entries)
149}
150
151/// The caller's two selectors.
152pub(super) struct Selector<'a> {
153 pub(super) set: &'a mut dyn FnMut(&str) -> bool,
154 pub(super) property: &'a mut dyn FnMut(&str) -> bool,
155}
156
157/// The selected properties of the selected sets of one source.
158pub(super) fn collect(
159 model: &Model,
160 release: Release,
161 sets: &[EntityId],
162 source: ExactSource,
163 select: &mut Selector<'_>,
164) -> Result<Vec<ExactPropertyEntry>, ExactPropertyError> {
165 let mut entries = Vec::new();
166 let mut selected_sets: BTreeMap<&str, EntityId> = BTreeMap::new();
167 for &set_id in sets {
168 let set = load_source_set(model, release, source, set_id)?;
169 if !(select.set)(set.name) {
170 set.refuse_unselected(release, &mut *select.property)?;
171 continue;
172 }
173 if let Some(first) = set.shares_name(&mut selected_sets) {
174 return Err(ExactPropertyError::DuplicateMatchingSets {
175 source,
176 first,
177 second: set_id,
178 });
179 }
180 let mut chosen: BTreeMap<&str, EntityId> = BTreeMap::new();
181 let mut in_order = Vec::new();
182 for (member, name) in set.members(model, release)? {
183 if !(select.property)(name) {
184 continue;
185 }
186 if let Some(first) = chosen.insert(name, set.member_id(member)) {
187 return Err(ExactPropertyError::DuplicateMatchingProperties {
188 set: set_id,
189 first,
190 second: set.member_id(member),
191 });
192 }
193 in_order.push((member, name));
194 }
195 for (member, name) in in_order {
196 let resolved = set.value(model, release, member)?;
197 // A predefined set without a `Name` shares its entity name with
198 // any other such set: a member both hold is ambiguous, as
199 // `exact_property` finds it.
200 let earlier = entries.iter().find(|entry: &&ExactPropertyEntry| {
201 entry.property.property_set.as_ref() == set.name && entry.name.as_ref() == name
202 });
203 if let Some(earlier) = earlier {
204 return Err(ExactPropertyError::DuplicateMatchingSets {
205 source,
206 first: earlier.property.set_id,
207 second: set_id,
208 });
209 }
210 entries.push(ExactPropertyEntry {
211 name: Arc::from(name),
212 property: set.exact(source, member, resolved),
213 });
214 }
215 }
216 Ok(entries)
217}
218
219/// One assigned property set or quantity set that a set selector picked.
220#[derive(Debug, Clone, PartialEq)]
221#[non_exhaustive]
222pub struct ExactPropertySetEntry {
223 /// The name the selector saw: `Name`, or for a predefined set that states
224 /// none, its entity name (`IfcDoorLiningProperties`).
225 pub name: Arc<str>,
226 /// The set entity.
227 pub set_id: EntityId,
228 /// Whether the object carries the set itself or inherits it from its
229 /// type; a queried type object's own sets are `Type` of that object.
230 pub source: ExactSource,
231 /// How many members the set holds, each validated; `0` for an empty set.
232 pub members: usize,
233}
234
235/// The property sets and quantity sets of `object` whose names `select_set`
236/// picks, empty ones included (#186).
237///
238/// [`exact_properties_where`] lists properties, so a selected set with no
239/// members leaves no trace in its answer. This lists the sets themselves:
240/// an IDS property facet must fail on a matching set that is empty, and it
241/// can tell that case from "no such set" only here. An empty result is a
242/// proven absence of every selected set.
243///
244/// The traversal, model and assignment validation and refusals are those of
245/// [`exact_properties_where`]; every member of a selected set is validated,
246/// though none is resolved. One difference: a set whose `HasProperties` or
247/// `Quantities` is `()` or `$` is listed with `members == 0`. The schema
248/// declares both `SET [1:?]`, so [`exact_properties_where`] refuses such a
249/// set as [`ExactPropertyError::MalformedAggregate`]; here the question is
250/// only whether the set exists, and it does. Occurrence sets come first, then the sets
251/// inherited from the object's `IfcTypeObject`, each in assignment order. A
252/// type set is listed even when an occurrence set has the same name:
253/// overriding works per property, so both sets exist for the object. A
254/// queried `IfcTypeObject` lists its own `HasPropertySets` with
255/// [`ExactSource::Type`] of `object` (#193).
256///
257/// # Errors
258///
259/// Any [`ExactPropertyError`], as for [`exact_properties_where`]; in
260/// particular two selected sets of one name on one source are
261/// [`ExactPropertyError::DuplicateMatchingSets`].
262pub fn exact_property_sets_where<S>(
263 model: &Model,
264 object: EntityId,
265 mut select_set: S,
266) -> Result<Vec<ExactPropertySetEntry>, ExactPropertyError>
267where
268 S: FnMut(&str) -> bool,
269{
270 let release = validate_model(model)?;
271 let assigned = assigned_sets(model, release, object)?;
272 let mut entries = list_sets(
273 model,
274 release,
275 &assigned.occurrence_sets,
276 ExactSource::Occurrence,
277 &mut select_set,
278 )?;
279 if let Some((type_id, sets)) = &assigned.type_sets {
280 entries.extend(list_sets(
281 model,
282 release,
283 sets,
284 ExactSource::Type(*type_id),
285 &mut select_set,
286 )?);
287 }
288 Ok(entries)
289}
290
291/// The selected sets of one source.
292pub(super) fn list_sets(
293 model: &Model,
294 release: Release,
295 sets: &[EntityId],
296 source: ExactSource,
297 select_set: &mut dyn FnMut(&str) -> bool,
298) -> Result<Vec<ExactPropertySetEntry>, ExactPropertyError> {
299 let mut entries = Vec::new();
300 let mut selected: BTreeMap<&str, EntityId> = BTreeMap::new();
301 for &set_id in sets {
302 let set = load_source_set(model, release, source, set_id)?;
303 if !select_set(set.name) {
304 continue;
305 }
306 if let Some(first) = set.shares_name(&mut selected) {
307 return Err(ExactPropertyError::DuplicateMatchingSets {
308 source,
309 first,
310 second: set_id,
311 });
312 }
313 // An empty or unset member list violates `SET [1:?]`, but the set
314 // exists and holds nothing, which is what an IDS facet must see.
315 let members = if set.member_list_is_empty(release) {
316 0
317 } else {
318 set.members(model, release)?.len()
319 };
320 entries.push(ExactPropertySetEntry {
321 name: Arc::from(set.name),
322 set_id,
323 source,
324 members,
325 });
326 }
327 Ok(entries)
328}