#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SchemaVersion {
Ifc2x3,
Ifc4,
Ifc4x1,
Ifc4x2,
Ifc4x3,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SchemaEntityInfo {
pub name: &'static str,
pub parent: Option<&'static str>,
pub is_abstract: bool,
}
impl SchemaVersion {
pub fn from_file_schema(label: &str) -> Option<Self> {
let upper = label.trim().to_ascii_uppercase();
if is_schema_family(&upper, "IFC4X3") {
Some(Self::Ifc4x3)
} else if is_schema_family(&upper, "IFC4X2") {
Some(Self::Ifc4x2)
} else if is_schema_family(&upper, "IFC4X1") {
Some(Self::Ifc4x1)
} else if is_schema_family(&upper, "IFC4") {
Some(Self::Ifc4)
} else if is_schema_family(&upper, "IFC2X3") {
Some(Self::Ifc2x3)
} else {
None
}
}
pub fn attribute_names(self, entity_name: &str) -> Option<&'static [&'static str]> {
match self {
Self::Ifc2x3 => attribute_names_ifc2x3(entity_name),
Self::Ifc4 => attribute_names_ifc4(entity_name),
Self::Ifc4x1 => attribute_names_ifc4x1(entity_name),
Self::Ifc4x2 => attribute_names_ifc4x2(entity_name),
Self::Ifc4x3 => attribute_names_ifc4x3(entity_name),
}
}
pub fn entity_info(self, entity_name: &str) -> Option<SchemaEntityInfo> {
match self {
Self::Ifc2x3 => entity_info_ifc2x3(entity_name),
Self::Ifc4 => entity_info_ifc4(entity_name),
Self::Ifc4x1 => entity_info_ifc4x1(entity_name),
Self::Ifc4x2 => entity_info_ifc4x2(entity_name),
Self::Ifc4x3 => entity_info_ifc4x3(entity_name),
}
}
pub fn is_subtype_of(self, child: &str, parent: &str) -> bool {
let mut current = self.entity_info(child);
while let Some(info) = current {
if info.name.eq_ignore_ascii_case(parent) {
return true;
}
current = info.parent.and_then(|name| self.entity_info(name));
}
false
}
}
fn is_schema_family(label: &str, family: &str) -> bool {
let Some(suffix) = label.strip_prefix(family) else {
return false;
};
if suffix.is_empty() {
return true;
}
let Some(revision) = suffix.strip_prefix('_') else {
return false;
};
revision.split('_').all(|component| {
["ADD", "TC", "RC"].iter().any(|prefix| {
component.strip_prefix(prefix).is_some_and(|number| {
!number.is_empty() && number.bytes().all(|b| b.is_ascii_digit())
})
})
})
}
fn attribute_names_ifc2x3(name: &str) -> Option<&'static [&'static str]> {
let ty = super::ifc2x3::IfcType::from_str(name);
if matches!(ty, super::ifc2x3::IfcType::Unknown(_)) {
None
} else {
Some(ty.attribute_names())
}
}
fn entity_info_ifc2x3(name: &str) -> Option<SchemaEntityInfo> {
let ty = super::ifc2x3::IfcType::from_str(name);
if matches!(ty, super::ifc2x3::IfcType::Unknown(_)) {
None
} else {
Some(SchemaEntityInfo {
name: ty.known_as_str()?,
parent: ty.parent().and_then(|parent| parent.known_as_str()),
is_abstract: ty.is_abstract(),
})
}
}
fn attribute_names_ifc4(name: &str) -> Option<&'static [&'static str]> {
let ty = super::ifc4::IfcType::from_str(name);
if matches!(ty, super::ifc4::IfcType::Unknown(_)) {
None
} else {
Some(ty.attribute_names())
}
}
fn entity_info_ifc4(name: &str) -> Option<SchemaEntityInfo> {
let ty = super::ifc4::IfcType::from_str(name);
if matches!(ty, super::ifc4::IfcType::Unknown(_)) {
None
} else {
Some(SchemaEntityInfo {
name: ty.known_as_str()?,
parent: ty.parent().and_then(|parent| parent.known_as_str()),
is_abstract: ty.is_abstract(),
})
}
}
fn attribute_names_ifc4x1(name: &str) -> Option<&'static [&'static str]> {
let ty = super::ifc4x1::IfcType::from_str(name);
if matches!(ty, super::ifc4x1::IfcType::Unknown(_)) {
None
} else {
Some(ty.attribute_names())
}
}
fn entity_info_ifc4x1(name: &str) -> Option<SchemaEntityInfo> {
let ty = super::ifc4x1::IfcType::from_str(name);
if matches!(ty, super::ifc4x1::IfcType::Unknown(_)) {
None
} else {
Some(SchemaEntityInfo {
name: ty.known_as_str()?,
parent: ty.parent().and_then(|parent| parent.known_as_str()),
is_abstract: ty.is_abstract(),
})
}
}
fn attribute_names_ifc4x2(name: &str) -> Option<&'static [&'static str]> {
let ty = super::ifc4x2::IfcType::from_str(name);
if matches!(ty, super::ifc4x2::IfcType::Unknown(_)) {
None
} else {
Some(ty.attribute_names())
}
}
fn entity_info_ifc4x2(name: &str) -> Option<SchemaEntityInfo> {
let ty = super::ifc4x2::IfcType::from_str(name);
if matches!(ty, super::ifc4x2::IfcType::Unknown(_)) {
None
} else {
Some(SchemaEntityInfo {
name: ty.known_as_str()?,
parent: ty.parent().and_then(|parent| parent.known_as_str()),
is_abstract: ty.is_abstract(),
})
}
}
fn attribute_names_ifc4x3(name: &str) -> Option<&'static [&'static str]> {
let ty = super::schema::IfcType::from_str(name);
if ty.declared_by_canonical_schema() {
Some(ty.attribute_names())
} else {
None
}
}
fn entity_info_ifc4x3(name: &str) -> Option<SchemaEntityInfo> {
let ty = super::schema::IfcType::from_str(name);
if !ty.declared_by_canonical_schema() {
return None;
}
Some(SchemaEntityInfo {
name: ty.known_as_str()?,
parent: ty.parent().and_then(|parent| parent.known_as_str()),
is_abstract: ty.is_abstract(),
})
}
pub fn attribute_names_for_schema(
file_schema: &str,
entity_name: &str,
) -> Option<&'static [&'static str]> {
SchemaVersion::from_file_schema(file_schema)?.attribute_names(entity_name)
}
pub fn entity_info_for_schema(file_schema: &str, entity_name: &str) -> Option<SchemaEntityInfo> {
SchemaVersion::from_file_schema(file_schema)?.entity_info(entity_name)
}
pub fn is_subtype_of_for_schema(file_schema: &str, child: &str, parent: &str) -> bool {
SchemaVersion::from_file_schema(file_schema).is_some_and(|schema| schema.is_subtype_of(child, parent))
}