use ifc_model::{Entity, EntityId, Model, Value};
use ifc_schema::{for_version, Schema, SchemaVersion};
use crate::{ClassificationError, ClassificationResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Release {
Bound(SchemaVersion),
Multiple(usize),
}
impl Release {
pub(crate) const LEGACY: Self = Self::Bound(SchemaVersion::Ifc4);
pub(crate) fn of(model: &Model) -> Self {
let is_2x3 =
|token: &String| SchemaVersion::from_header_token(token) == Some(SchemaVersion::Ifc2x3);
match model.header().schema.as_slice() {
[token] if is_2x3(token) => Self::Bound(SchemaVersion::Ifc2x3),
tokens if tokens.len() > 1 && tokens.iter().any(is_2x3) => Self::Multiple(tokens.len()),
_ => Self::LEGACY,
}
}
pub(crate) fn bound(self) -> ClassificationResult<(SchemaVersion, &'static Schema)> {
match self {
Self::Bound(version) => Ok((
version,
for_version(version).expect("the IFC2X3 and IFC4 tables are bundled"),
)),
Self::Multiple(schemas) => Err(ClassificationError::MultipleSchemas { schemas }),
}
}
pub(crate) fn slot(
self,
entity: &'static str,
id: EntityId,
attribute: &'static str,
) -> ClassificationResult<usize> {
let (version, schema) = self.bound()?;
let name = release_name(version, entity, attribute);
schema
.attributes(entity)
.iter()
.position(|declared| declared.name.eq_ignore_ascii_case(name))
.ok_or(ClassificationError::NotInSchema {
entity,
id,
attribute,
schema: version,
})
}
pub(crate) fn text_slot(
self,
entity: &'static str,
id: EntityId,
record: &Entity,
attribute: &'static str,
) -> ClassificationResult<usize> {
let slot = self.slot(entity, id, attribute)?;
if let Some(Value::Ref(target)) = record.attribute(slot) {
let declared = self.declared_type(entity, id, attribute)?;
let (_, schema) = self.bound()?;
if schema.entity(declared).is_some() {
return Err(ClassificationError::StructuredValue {
entity,
id,
attribute,
target: *target,
});
}
}
Ok(slot)
}
pub(crate) fn declared_type(
self,
entity: &'static str,
id: EntityId,
attribute: &'static str,
) -> ClassificationResult<&'static str> {
let slot = self.slot(entity, id, attribute)?;
let (_, schema) = self.bound()?;
Ok(schema.attributes(entity)[slot].type_name.as_str())
}
pub(crate) fn accepts(
self,
entity: &'static str,
id: EntityId,
attribute: &'static str,
candidate: &str,
) -> ClassificationResult<bool> {
let declared = self.declared_type(entity, id, attribute)?;
let (_, schema) = self.bound()?;
Ok(schema.accepts_type(declared, candidate))
}
}
fn release_name(release: SchemaVersion, entity: &str, attribute: &'static str) -> &'static str {
match (release, entity, attribute) {
(SchemaVersion::Ifc2x3, "IFCDOCUMENTINFORMATION", "Identification") => "DocumentId",
(
SchemaVersion::Ifc2x3,
"IFCCLASSIFICATIONREFERENCE" | "IFCDOCUMENTREFERENCE" | "IFCLIBRARYREFERENCE",
"Identification",
) => "ItemReference",
_ => attribute,
}
}
pub fn classification_schema(model: &Model) -> ClassificationResult<SchemaVersion> {
Release::of(model).bound().map(|(version, _)| version)
}