use ifc_schema::SchemaVersion;
use super::super::layout::Layout;
use super::finding::MeasureRole;
use crate::template::PropertyTemplateKind;
const SIMPLE: [(&str, &[&str]); 12] = [
("P_SINGLEVALUE", &["IFCPROPERTYSINGLEVALUE"]),
("P_ENUMERATEDVALUE", &["IFCPROPERTYENUMERATEDVALUE"]),
("P_BOUNDEDVALUE", &["IFCPROPERTYBOUNDEDVALUE"]),
("P_LISTVALUE", &["IFCPROPERTYLISTVALUE"]),
("P_TABLEVALUE", &["IFCPROPERTYTABLEVALUE"]),
("P_REFERENCEVALUE", &["IFCPROPERTYREFERENCEVALUE"]),
("Q_LENGTH", &["IFCQUANTITYLENGTH"]),
("Q_AREA", &["IFCQUANTITYAREA"]),
("Q_VOLUME", &["IFCQUANTITYVOLUME"]),
("Q_COUNT", &["IFCQUANTITYCOUNT"]),
("Q_WEIGHT", &["IFCQUANTITYWEIGHT"]),
("Q_TIME", &["IFCQUANTITYTIME"]),
];
const Q_TIME_IFC4X3: &[&str] = &["IFCQUANTITYTIME", "IFCQUANTITYNUMBER"];
const COMPLEX: [(&str, &[&str]); 2] = [
("P_COMPLEX", &["IFCCOMPLEXPROPERTY"]),
("Q_COMPLEX", &["IFCPHYSICALCOMPLEXQUANTITY"]),
];
const ANY_SIMPLE: &[&str] = &["IFCSIMPLEPROPERTY", "IFCPHYSICALSIMPLEQUANTITY"];
const ANY_COMPLEX: &[&str] = &["IFCCOMPLEXPROPERTY", "IFCPHYSICALCOMPLEXQUANTITY"];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Prescribed {
Entities(&'static [&'static str]),
Unknown,
Undocumented,
}
pub(super) fn prescribed(
layout: Layout,
kind: PropertyTemplateKind,
template_type: Option<&str>,
) -> Prescribed {
let (entity, table, any): (_, &[(&str, &'static [&'static str])], _) = match kind {
PropertyTemplateKind::Simple => ("IFCSIMPLEPROPERTYTEMPLATE", &SIMPLE, ANY_SIMPLE),
PropertyTemplateKind::Complex => ("IFCCOMPLEXPROPERTYTEMPLATE", &COMPLEX, ANY_COMPLEX),
};
let Some(token) = template_type else {
return Prescribed::Entities(any);
};
if !layout.enum_accepts(entity, "TemplateType", token) {
return Prescribed::Unknown;
}
if layout.version() == SchemaVersion::Ifc4x3 && kind == PropertyTemplateKind::Simple {
if token.eq_ignore_ascii_case("Q_NUMBER") {
return Prescribed::Undocumented;
}
if token.eq_ignore_ascii_case("Q_TIME") {
return Prescribed::Entities(Q_TIME_IFC4X3);
}
}
table
.iter()
.find(|(constant, _)| constant.eq_ignore_ascii_case(token))
.map_or(Prescribed::Undocumented, |(_, entities)| {
Prescribed::Entities(entities)
})
}
pub(super) fn measured(template_type: &str, measure: MeasureRole) -> &'static [&'static str] {
let upper = template_type.to_ascii_uppercase();
match (upper.as_str(), measure) {
("P_SINGLEVALUE", MeasureRole::Primary) => &["NominalValue"],
("P_ENUMERATEDVALUE", MeasureRole::Primary) => &["EnumerationValues"],
("P_BOUNDEDVALUE", MeasureRole::Primary) => &["LowerBoundValue"],
("P_BOUNDEDVALUE", MeasureRole::Secondary) => &["UpperBoundValue"],
("P_LISTVALUE", MeasureRole::Primary) => &["ListValues"],
("P_TABLEVALUE", MeasureRole::Primary) => &["DefiningValues"],
("P_TABLEVALUE", MeasureRole::Secondary) => &["DefinedValues"],
("P_REFERENCEVALUE", MeasureRole::Primary) => &["PropertyReference"],
_ => &[],
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Attachment {
Any,
Types,
Objects,
TypesOrObjects,
PerformanceHistory,
Nowhere,
}
impl Attachment {
pub(super) fn admits(self, layout: Layout, object: &str) -> bool {
let is_type = || layout.is_a(object, "IFCTYPEOBJECT");
let is_object = || layout.is_a(object, "IFCOBJECT");
match self {
Self::Any => true,
Self::Types => is_type(),
Self::Objects => is_object(),
Self::TypesOrObjects => is_type() || is_object(),
Self::PerformanceHistory => layout.is_a(object, "IFCPERFORMANCEHISTORY"),
Self::Nowhere => false,
}
}
}
pub(super) fn attachment(layout: Layout, token: &str) -> Option<Attachment> {
if !layout.enum_accepts("IFCPROPERTYSETTEMPLATE", "TemplateType", token) {
return None;
}
match token.to_ascii_uppercase().as_str() {
"PSET_TYPEDRIVENONLY" | "QTO_TYPEDRIVENONLY" => Some(Attachment::Types),
"PSET_TYPEDRIVENOVERRIDE" | "QTO_TYPEDRIVENOVERRIDE" => Some(Attachment::TypesOrObjects),
"PSET_OCCURRENCEDRIVEN" | "QTO_OCCURRENCEDRIVEN" => Some(Attachment::Objects),
"PSET_PERFORMANCEDRIVEN" => Some(Attachment::PerformanceHistory),
"PSET_MATERIALDRIVEN" | "PSET_PROFILEDRIVEN" => Some(Attachment::Nowhere),
"NOTDEFINED" => Some(Attachment::Any),
_ => None,
}
}
pub(super) fn set_entity(token: &str) -> Option<&'static str> {
let upper = token.to_ascii_uppercase();
match upper.as_str() {
"PSET_MATERIALDRIVEN" => Some("IFCMATERIALPROPERTIES"),
"PSET_PROFILEDRIVEN" => Some("IFCPROFILEPROPERTIES"),
_ if upper.starts_with("PSET_") => Some("IFCPROPERTYSET"),
_ if upper.starts_with("QTO_") => Some("IFCELEMENTQUANTITY"),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use ifc_model::Model;
fn layout(token: &str) -> Layout {
let mut model = Model::new();
model.header_mut().schema = vec![token.to_owned()];
Layout::permissive(&model)
}
#[test]
fn every_property_template_type_prescribes_declared_entities() {
for token in ["IFC4", "IFC4X3_ADD2"] {
let layout = layout(token);
for (kind, entity, enumeration) in [
(
PropertyTemplateKind::Simple,
"IFCSIMPLEPROPERTYTEMPLATE",
"IfcSimplePropertyTemplateTypeEnum",
),
(
PropertyTemplateKind::Complex,
"IFCCOMPLEXPROPERTYTEMPLATE",
"IfcComplexPropertyTemplateTypeEnum",
),
] {
let members = layout.enum_members(enumeration);
assert!(!members.is_empty(), "{token} declares {enumeration}");
for member in members {
assert!(layout.enum_accepts(entity, "TemplateType", member));
match prescribed(layout, kind, Some(member)) {
Prescribed::Entities(entities) => {
for name in entities {
assert!(
layout.schema().entity(name).is_some(),
"{token}: {member} prescribes undeclared {name}"
);
}
}
Prescribed::Undocumented => assert!(
token == "IFC4X3_ADD2" && member == "Q_NUMBER",
"{token}: {member} has no documented entity"
),
Prescribed::Unknown => panic!("{token}: {member} is a member"),
}
}
}
for name in ANY_SIMPLE.iter().chain(ANY_COMPLEX) {
assert!(layout.schema().entity(name).is_some(), "{token}: {name}");
}
}
assert_eq!(
prescribed(
layout("IFC4"),
PropertyTemplateKind::Simple,
Some("Q_NUMBER")
),
Prescribed::Unknown
);
}
#[test]
fn every_measured_attribute_is_declared_by_the_prescribed_entity() {
for token in ["IFC4", "IFC4X3_ADD2"] {
let layout = layout(token);
for (constant, entities) in SIMPLE {
for measure in [MeasureRole::Primary, MeasureRole::Secondary] {
for attribute in measured(constant, measure) {
let declared = layout.schema().attribute_names(entities[0]);
assert!(
declared.iter().any(|name| name == attribute),
"{token}: {} has no {attribute}",
entities[0]
);
}
}
}
}
}
#[test]
fn every_set_template_type_has_a_rule() {
for token in ["IFC4", "IFC4X3_ADD2"] {
let layout = layout(token);
let members = layout.enum_members("IfcPropertySetTemplateTypeEnum");
assert!(!members.is_empty());
for member in members {
assert!(attachment(layout, member).is_some(), "{token}: {member}");
if member != "NOTDEFINED" {
let entity = set_entity(member).expect("a governed set entity");
assert!(
layout.schema().entity(entity).is_some(),
"{token}: {entity}"
);
}
}
for name in ["IFCTYPEOBJECT", "IFCOBJECT", "IFCPERFORMANCEHISTORY"] {
assert!(layout.schema().entity(name).is_some());
}
}
assert_eq!(attachment(layout("IFC4"), "PSET_MATERIALDRIVEN"), None);
}
}