use ifc_model::{Entity, EntityId, Model, Value};
use ifc_schema::{for_version, ifc4, Schema, SchemaVersion, TypeKind};
use crate::error::{PropertyAnomaly, TemplateError};
use crate::exact_schema;
#[derive(Clone, Copy)]
pub(crate) struct Layout {
version: SchemaVersion,
schema: &'static Schema,
}
impl Layout {
pub(crate) fn permissive(model: &Model) -> Self {
let declared = match model.header().schema.as_slice() {
[token] => SchemaVersion::from_header_token(token)
.and_then(|version| for_version(version).map(|schema| (version, schema))),
_ => None,
};
let (version, schema) = declared.unwrap_or((SchemaVersion::Ifc4, ifc4()));
Self { version, schema }
}
pub(crate) fn declared(model: &Model) -> Result<Self, TemplateError> {
let version = exact_schema(model).map_err(TemplateError::Release)?;
let schema = for_version(version).ok_or(TemplateError::NoTemplates { schema: version })?;
if schema.entity("IFCPROPERTYSETTEMPLATE").is_none() {
return Err(TemplateError::NoTemplates { schema: version });
}
Ok(Self { version, schema })
}
pub(crate) fn version(self) -> SchemaVersion {
self.version
}
pub(crate) fn schema(self) -> &'static Schema {
self.schema
}
pub(crate) fn is_a(self, name: &str, ancestor: &str) -> bool {
self.schema.is_a(name, ancestor)
}
fn declared_attribute(self, entity: &str, attribute: &str) -> Option<(usize, &'static str)> {
self.schema
.attributes(entity)
.into_iter()
.enumerate()
.find(|(_, declared)| declared.name.eq_ignore_ascii_case(attribute))
.map(|(slot, declared)| (slot, declared.type_name.as_str()))
}
pub(crate) fn get<'e>(self, entity: &'e Entity, attribute: &str) -> Option<&'e Value> {
let (slot, _) = self.declared_attribute(&entity.type_name, attribute)?;
entity.attributes.get(slot)
}
pub(crate) fn check_arity(self, id: EntityId, entity: &Entity, out: &mut Vec<PropertyAnomaly>) {
let expected = self.schema.attributes(&entity.type_name).len();
if expected != entity.attributes.len() {
out.push(PropertyAnomaly::SlotCountMismatch {
entity: id,
type_name: entity.type_name.to_string(),
expected,
actual: entity.attributes.len(),
});
}
}
pub(crate) fn enum_members(self, name: &str) -> &'static [String] {
match self
.schema
.type_def(name)
.map(|definition| &definition.kind)
{
Some(TypeKind::Enumeration(members)) => members,
_ => &[],
}
}
pub(crate) fn enum_accepts(self, entity: &str, attribute: &str, token: &str) -> bool {
self.declared_attribute(entity, attribute)
.is_some_and(|(_, declared)| {
self.enum_members(declared)
.iter()
.any(|member| member.eq_ignore_ascii_case(token))
})
}
pub(crate) fn text(
self,
id: EntityId,
entity: &Entity,
attribute: &'static str,
out: &mut Vec<PropertyAnomaly>,
) -> Option<std::sync::Arc<str>> {
match self.get(entity, attribute)?.unwrap_typed() {
Value::Text(text) => Some(text.clone()),
Value::Null => None,
other => {
out.push(malformed(id, attribute, other));
None
}
}
}
pub(crate) fn enumeration(
self,
id: EntityId,
entity: &Entity,
attribute: &'static str,
out: &mut Vec<PropertyAnomaly>,
) -> Option<std::sync::Arc<str>> {
match self.get(entity, attribute)?.unwrap_typed() {
Value::Enum(token) => {
if !self.enum_accepts(&entity.type_name, attribute, token) {
out.push(malformed(id, attribute, &Value::Enum(token.clone())));
}
Some(token.clone())
}
Value::Null => None,
other => {
out.push(malformed(id, attribute, other));
None
}
}
}
pub(crate) fn reference(
self,
model: &Model,
id: EntityId,
entity: &Entity,
attribute: &'static str,
out: &mut Vec<PropertyAnomaly>,
) -> Option<EntityId> {
let (_, declared) = self.declared_attribute(&entity.type_name, attribute)?;
match self.get(entity, attribute)?.unwrap_typed() {
Value::Ref(target) => {
let admitted = model
.get(*target)
.is_some_and(|found| self.schema.accepts_type(declared, &found.type_name));
if !admitted {
out.push(malformed(id, attribute, &Value::Ref(*target)));
}
Some(*target)
}
Value::Null => None,
other => {
out.push(malformed(id, attribute, other));
None
}
}
}
}
fn malformed(entity: EntityId, attribute: &'static str, found: &Value) -> PropertyAnomaly {
PropertyAnomaly::MalformedAttribute {
entity,
attribute,
found: format!("{found:?}"),
}
}