use ifc_schema::{Schema, TypeKind};
const MAX_ALIAS_HOPS: usize = 16;
#[must_use]
pub fn aliased_aggregate(schema: &Schema, type_name: &str) -> Option<String> {
let mut current = type_name.to_string();
for _ in 0..MAX_ALIAS_HOPS {
let TypeKind::Defined(target) = &schema.type_def(¤t)?.kind else {
return None;
};
let target = target.trim();
if is_aggregate_expression(target) {
return Some(target.to_string());
}
current = target.to_string();
}
None
}
#[must_use]
pub fn element_type(schema: &Schema, declared: &str) -> String {
aliased_aggregate(schema, declared)
.and_then(|expression| element_of(&expression))
.unwrap_or_else(|| declared.to_string())
}
fn is_aggregate_expression(text: &str) -> bool {
let head = text
.split(|c: char| !c.is_ascii_alphabetic())
.next()
.unwrap_or_default()
.to_ascii_uppercase();
matches!(head.as_str(), "LIST" | "SET" | "ARRAY" | "BAG")
}
fn element_of(expression: &str) -> Option<String> {
let mut tokens = expression.split_whitespace();
tokens
.by_ref()
.find(|token| token.eq_ignore_ascii_case("OF"))?;
let token = tokens.find(|token| {
!token.eq_ignore_ascii_case("UNIQUE") && !token.eq_ignore_ascii_case("OPTIONAL")
})?;
let token = token.trim_matches(|c: char| matches!(c, '(' | ')' | ';'));
(!token.is_empty()).then(|| token.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_aliased_aggregate_yields_its_element_type() {
let schema = ifc_schema::ifc4();
assert_eq!(element_type(schema, "IfcComplexNumber"), "REAL");
assert_eq!(element_type(schema, "IfcLineIndex"), "IfcPositiveInteger");
assert_eq!(
element_type(schema, "IfcPropertySetDefinitionSet"),
"IfcPropertySetDefinition"
);
}
#[test]
fn a_non_aggregate_type_is_its_own_element_type() {
let schema = ifc_schema::ifc4();
assert_eq!(aliased_aggregate(schema, "IfcLengthMeasure"), None);
assert_eq!(element_type(schema, "IfcLengthMeasure"), "IfcLengthMeasure");
assert_eq!(element_type(schema, "IfcProduct"), "IfcProduct");
}
#[test]
fn element_parsing_skips_qualifiers() {
assert_eq!(
element_of("LIST [3:?] OF UNIQUE IfcCartesianPoint").as_deref(),
Some("IfcCartesianPoint")
);
assert_eq!(
element_of("LIST [1:?] OF LIST [3:3] OF REAL").as_deref(),
Some("LIST")
);
assert_eq!(element_of("STRING(255)"), None);
}
}