#[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>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Leaf {
Integer,
Real,
Number,
Text {
fixed: 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 { fixed: None } => "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(),
}
}
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 Lexical {
Native,
Xsd,
}
#[derive(Debug, Clone)]
pub(crate) struct SlotLayout {
pub(crate) name: Arc<str>,
pub(crate) shape: Shape,
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) {
slots.push(SlotLayout {
name: attribute.name.as_str().into(),
shape: attribute_shape(schema, attribute)?,
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),
})
.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, 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 fixed = match (lower.trim().parse::<usize>(), upper.trim().parse::<usize>()) {
(Ok(lower), Ok(upper)) if lower == upper => Some(lower),
_ => None,
};
(fixed, bounds[close + 1..].trim_start())
} else {
(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 });
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 fixed = upper
.contains("FIXED")
.then(|| {
let open = upper.find('(')?;
let close = upper.find(')')?;
upper.get(open + 1..close)?.trim().parse().ok()
})
.flatten();
Leaf::Text { fixed }
}
_ => 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(),
}
}