#[cfg(test)]
mod tests;
mod value;
pub(crate) use value::{conform, hex_binary, list_text, nest, references, scalar};
use crate::error::XmlError;
use ifc_schema::{AggregateKind, Attribute, Bound, Schema, TypeKind};
use std::collections::HashMap;
use std::sync::Arc;
const MAX_DEPTH: usize = 32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Level {
pub(crate) kind: AggregateKind,
pub(crate) fixed: Option<usize>,
pub(crate) count: (usize, Option<usize>),
}
fn count(kind: AggregateKind, lower: Option<u64>, upper: Option<u64>) -> (usize, Option<usize>) {
let size = |bound: Option<u64>| bound.and_then(|bound| usize::try_from(bound).ok());
match (kind, size(lower), size(upper)) {
(AggregateKind::Array, Some(lower), Some(upper)) if upper >= lower => {
let items = upper - lower + 1;
(items, Some(items))
}
(AggregateKind::Array, _, _) => (0, None),
(_, lower, upper) => (lower.unwrap_or(0), upper),
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Leaf {
Integer,
Real,
Number,
Text {
fixed: Option<usize>,
width: Option<usize>,
},
Boolean,
Logical,
Binary,
Enumeration {
name: Arc<str>,
members: Arc<[String]>,
},
Entity(Arc<str>),
Select(Arc<str>),
}
impl Leaf {
pub(crate) const fn is_simple(&self) -> bool {
!matches!(self, Self::Entity(_) | Self::Select(_))
}
fn describe(&self) -> String {
match self {
Self::Integer => "INTEGER".into(),
Self::Real => "REAL".into(),
Self::Number => "NUMBER".into(),
Self::Text {
fixed: Some(width), ..
} => format!("STRING({width}) FIXED"),
Self::Text { .. } => "STRING".into(),
Self::Boolean => "BOOLEAN".into(),
Self::Logical => "LOGICAL".into(),
Self::Binary => "BINARY".into(),
Self::Enumeration { name, .. } | Self::Entity(name) | Self::Select(name) => {
name.to_string()
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Shape {
pub(crate) levels: Vec<Level>,
pub(crate) leaf: Leaf,
pub(crate) named: Arc<str>,
}
impl Shape {
pub(crate) fn inner(&self) -> Self {
Self {
levels: self.levels.get(1..).unwrap_or_default().to_vec(),
leaf: self.leaf.clone(),
named: self.named.clone(),
}
}
pub(crate) fn inner_leaf(&self) -> Self {
Self {
levels: Vec::new(),
leaf: self.leaf.clone(),
named: self.named.clone(),
}
}
fn default_xsd_form(&self) -> XsdForm {
let Some((_, inner)) = self.levels.split_first() else {
return match self.leaf {
Leaf::Entity(_) => XsdForm::Entity,
Leaf::Select(_) => XsdForm::Select,
Leaf::Binary => XsdForm::Text,
_ => XsdForm::Attribute,
};
};
if self.leaf.is_simple() && inner.iter().all(|level| level.fixed.is_some()) {
XsdForm::Attribute
} else {
XsdForm::Container(Items::Flat)
}
}
pub(crate) fn describe(&self) -> String {
let mut text = String::new();
for level in &self.levels {
text.push_str(match level.kind {
AggregateKind::List => "LIST OF ",
AggregateKind::Set => "SET OF ",
AggregateKind::Bag => "BAG OF ",
AggregateKind::Array => "ARRAY OF ",
_ => "AGGREGATE OF ",
});
}
text.push_str(&self.named);
if self.leaf.describe() != *self.named {
text.push_str(" (");
text.push_str(&self.leaf.describe());
text.push(')');
}
text
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum XsdForm {
Attribute,
Text,
Entity,
Select,
Container(Items),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Items {
Flat,
Seq,
}
const FORM_OVERRIDES: &[(&str, &str, XsdForm)] = &[
(
"IfcClassification",
"ReferenceTokens",
XsdForm::Container(Items::Flat),
),
(
"IfcPostalAddress",
"AddressLines",
XsdForm::Container(Items::Flat),
),
(
"IfcTextStyleFontModel",
"FontFamily",
XsdForm::Container(Items::Flat),
),
(
"IfcIndexedPolygonalFaceWithVoids",
"InnerCoordIndices",
XsdForm::Container(Items::Seq),
),
(
"IfcTextureCoordinateIndicesWithVoids",
"InnerTexCoordIndices",
XsdForm::Attribute,
),
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Lexical {
Native,
Xsd,
}
#[derive(Debug, Clone)]
pub(crate) struct SlotLayout {
pub(crate) name: Arc<str>,
pub(crate) shape: Shape,
pub(crate) optional: bool,
pub(crate) form: XsdForm,
pub(crate) derived: bool,
}
#[derive(Debug, Clone)]
pub(crate) struct InverseLayout {
pub(crate) name: Arc<str>,
pub(crate) entity: Arc<str>,
pub(crate) for_attribute: Arc<str>,
}
#[derive(Debug, Clone)]
pub(crate) struct EntityLayout {
pub(crate) name: Arc<str>,
pub(crate) upper: Arc<str>,
pub(crate) abstract_: bool,
pub(crate) slots: Vec<SlotLayout>,
pub(crate) inverses: Vec<InverseLayout>,
}
impl EntityLayout {
pub(crate) fn slot(&self, name: &str) -> Option<usize> {
self.slots.iter().position(|slot| &*slot.name == name)
}
pub(crate) fn inverse(&self, name: &str) -> Option<&InverseLayout> {
self.inverses.iter().find(|inverse| &*inverse.name == name)
}
}
pub(crate) struct Layouts<'s> {
schema: &'s Schema,
cache: HashMap<String, Arc<EntityLayout>>,
}
impl<'s> Layouts<'s> {
pub(crate) fn new(schema: &'s Schema) -> Self {
Self {
schema,
cache: HashMap::new(),
}
}
pub(crate) const fn schema(&self) -> &'s Schema {
self.schema
}
pub(crate) fn entity(
&mut self,
name: &str,
exact: bool,
) -> Result<Option<Arc<EntityLayout>>, XmlError> {
let Some(definition) = self.schema.entity(name) else {
return Ok(None);
};
if exact && definition.name != name {
return Ok(None);
}
let key = definition.name.to_ascii_uppercase();
if let Some(layout) = self.cache.get(&key) {
return Ok(Some(layout.clone()));
}
let layout = Arc::new(build_layout(self.schema, &definition.name)?);
self.cache.insert(key, layout.clone());
Ok(Some(layout))
}
}
fn build_layout(schema: &Schema, name: &str) -> Result<EntityLayout, XmlError> {
let definition = schema
.entity(name)
.ok_or_else(|| XmlError::UnknownEntity { name: name.into() })?;
let chain: Vec<&str> = std::iter::once(definition.name.as_str())
.chain(schema.supertypes(name))
.collect();
let derived = |slot: &str| {
chain
.iter()
.filter_map(|entity| schema.entity(entity))
.any(|entity| entity.is_derived(slot))
};
let mut slots = Vec::new();
for attribute in schema.attributes(name) {
let shape = attribute_shape(schema, attribute)?;
let form = FORM_OVERRIDES
.iter()
.find(|(declarer, overridden, _)| {
*overridden == attribute.name && schema.is_a(name, declarer)
})
.map_or_else(|| shape.default_xsd_form(), |(_, _, form)| *form);
slots.push(SlotLayout {
name: attribute.name.as_str().into(),
shape,
optional: attribute.optional,
form,
derived: derived(&attribute.name),
});
}
let mut inverses: Vec<InverseLayout> = Vec::new();
for entity in chain.iter().filter_map(|entity| schema.entity(entity)) {
for inverse in &entity.inverses {
if inverses.iter().any(|seen| *seen.name == *inverse.name) {
continue;
}
inverses.push(InverseLayout {
name: inverse.name.as_str().into(),
entity: inverse.entity.as_str().into(),
for_attribute: inverse.for_attribute.as_str().into(),
});
}
}
Ok(EntityLayout {
name: definition.name.as_str().into(),
upper: definition.name.to_ascii_uppercase().into(),
abstract_: definition.abstract_,
slots,
inverses,
})
}
pub(crate) fn attribute_shape(schema: &Schema, attribute: &Attribute) -> Result<Shape, XmlError> {
if attribute.aggregate && attribute.aggregation.is_empty() {
return Err(unsupported(format!(
"attribute `{}` declares an aggregate without recorded levels",
attribute.name
)));
}
let mut levels: Vec<Level> = attribute
.aggregation
.iter()
.map(|aggregation| Level {
kind: aggregation.kind,
fixed: fixed_size(&aggregation.lower, &aggregation.upper),
count: count(
aggregation.kind,
aggregation.lower.as_integer(),
aggregation.upper.as_integer(),
),
})
.collect();
let leaf = resolve(schema, &attribute.type_name, &mut levels, 0)?;
Ok(Shape {
levels,
leaf,
named: attribute.type_name.as_str().into(),
})
}
pub(crate) fn type_shape(schema: &Schema, name: &str) -> Result<Shape, XmlError> {
let mut levels = Vec::new();
let leaf = resolve(schema, name, &mut levels, 0)?;
Ok(Shape {
levels,
leaf,
named: name.into(),
})
}
fn fixed_size(lower: &Bound, upper: &Bound) -> Option<usize> {
match (lower.as_integer(), upper.as_integer()) {
(Some(lower), Some(upper)) if lower == upper => usize::try_from(lower).ok(),
_ => None,
}
}
fn resolve(
schema: &Schema,
expression: &str,
levels: &mut Vec<Level>,
depth: usize,
) -> Result<Leaf, XmlError> {
if depth > MAX_DEPTH {
return Err(unsupported(format!(
"type `{expression}` nests or aliases too deeply"
)));
}
let expression = expression.trim();
if let Some(rest) = aggregate_prefix(expression, levels)? {
return resolve(schema, rest, levels, depth + 1);
}
if let Some(leaf) = primitive(expression) {
return Ok(leaf);
}
if let Some(entity) = schema.entity(expression) {
return Ok(Leaf::Entity(entity.name.as_str().into()));
}
let Some(definition) = schema.type_def(expression) else {
return Err(unsupported(format!(
"type `{expression}` is not declared by the schema"
)));
};
match &definition.kind {
TypeKind::Enumeration(members) => Ok(Leaf::Enumeration {
name: definition.name.as_str().into(),
members: members.clone().into(),
}),
TypeKind::Select(_) => Ok(Leaf::Select(definition.name.as_str().into())),
TypeKind::Defined(target) => resolve(schema, target, levels, depth + 1),
_ => Err(unsupported(format!(
"type `{expression}` has a declaration form this reader does not know"
))),
}
}
fn aggregate_prefix<'e>(
expression: &'e str,
levels: &mut Vec<Level>,
) -> Result<Option<&'e str>, XmlError> {
let upper = expression.to_ascii_uppercase();
let kind = [
("LIST", AggregateKind::List),
("SET", AggregateKind::Set),
("BAG", AggregateKind::Bag),
("ARRAY", AggregateKind::Array),
]
.into_iter()
.find(|(keyword, _)| {
upper.starts_with(keyword)
&& upper[keyword.len()..]
.chars()
.next()
.is_some_and(|next| next == ' ' || next == '[')
});
let Some((keyword, kind)) = kind else {
return Ok(None);
};
let malformed = || unsupported(format!("aggregate type `{expression}` is malformed"));
let rest = expression[keyword.len()..].trim_start();
let (fixed, items, rest) = if let Some(bounds) = rest.strip_prefix('[') {
let close = bounds.find(']').ok_or_else(malformed)?;
let (lower, upper) = bounds[..close].split_once(':').ok_or_else(malformed)?;
let (lower, upper) = (
lower.trim().parse::<u64>().ok(),
upper.trim().parse::<u64>().ok(),
);
let fixed = match (lower, upper) {
(Some(lower), Some(upper)) if lower == upper => usize::try_from(lower).ok(),
_ => None,
};
(
fixed,
count(kind, lower, upper),
bounds[close + 1..].trim_start(),
)
} else {
(None, (0, None), rest)
};
let rest = rest
.strip_prefix("OF")
.or_else(|| rest.strip_prefix("of"))
.ok_or_else(malformed)?
.trim_start();
let mut rest = rest;
for qualifier in ["UNIQUE", "OPTIONAL"] {
if rest.to_ascii_uppercase().starts_with(qualifier)
&& rest[qualifier.len()..].starts_with(' ')
{
rest = rest[qualifier.len()..].trim_start();
}
}
levels.push(Level {
kind,
fixed,
count: items,
});
Ok(Some(rest))
}
fn primitive(expression: &str) -> Option<Leaf> {
let upper = expression.to_ascii_uppercase();
let word = upper
.split(|c: char| !c.is_ascii_alphanumeric() && c != '_')
.next()
.unwrap_or_default();
let leaf = match word {
"INTEGER" => Leaf::Integer,
"REAL" => Leaf::Real,
"NUMBER" => Leaf::Number,
"BOOLEAN" => Leaf::Boolean,
"LOGICAL" => Leaf::Logical,
"BINARY" => Leaf::Binary,
"STRING" => {
let width: Option<usize> = (|| {
let open = upper.find('(')?;
let close = upper.find(')')?;
upper.get(open + 1..close)?.trim().parse().ok()
})();
let fixed = width.filter(|_| upper.contains("FIXED"));
Leaf::Text { fixed, width }
}
_ => return None,
};
Some(leaf)
}
fn unsupported(construct: String) -> XmlError {
XmlError::Unsupported { construct }
}
fn invalid(leaf: &Leaf, text: &str) -> XmlError {
XmlError::InvalidScalar {
kind: leaf.describe(),
value: text.into(),
}
}