Skip to main content

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}