use super::{invalid, type_shape, unsupported, Leaf, Lexical, Shape, MAX_DEPTH};
use crate::error::XmlError;
use ifc_model::Value;
use ifc_schema::{Schema, TypeKind};
use std::sync::Arc;
pub(crate) fn scalar(leaf: &Leaf, text: &str, lexical: Lexical) -> Result<Value, XmlError> {
let token = text.trim();
let value = match leaf {
Leaf::Integer => Value::Integer(token.parse().map_err(|_| invalid(leaf, text))?),
Leaf::Real => Value::Real(finite(leaf, token, text)?),
Leaf::Number => match lexical {
Lexical::Xsd => Value::Real(finite(leaf, token, text)?),
Lexical::Native => match token.parse::<i64>() {
Ok(integer) => Value::Integer(integer),
Err(_) => Value::Real(finite(leaf, token, text)?),
},
},
Leaf::Text { fixed } => {
if let Some(width) = fixed {
if text.chars().count() != *width {
return Err(invalid(leaf, text));
}
}
Value::Text(text.into())
}
Leaf::Boolean => match (token, lexical) {
("true", _) | ("1", Lexical::Xsd) => Value::Bool(true),
("false", _) | ("0", Lexical::Xsd) => Value::Bool(false),
_ => return Err(invalid(leaf, text)),
},
Leaf::Logical => match token {
"true" => Value::Bool(true),
"false" => Value::Bool(false),
"unknown" => Value::LogicalUnknown,
_ => return Err(invalid(leaf, text)),
},
Leaf::Binary => match lexical {
Lexical::Xsd => Value::Binary(hex_binary(token).ok_or_else(|| invalid(leaf, text))?),
Lexical::Native => {
if !is_step_binary(text) {
return Err(invalid(leaf, text));
}
Value::Binary(text.into())
}
},
Leaf::Enumeration { members, .. } => {
let member = members
.iter()
.find(|member| member.eq_ignore_ascii_case(token))
.ok_or_else(|| invalid(leaf, text))?;
Value::Enum(member.to_ascii_uppercase().into())
}
Leaf::Entity(_) | Leaf::Select(_) => match lexical {
Lexical::Native => Value::Ref(crate::scalar::parse_ref(text).ok_or_else(|| {
XmlError::TypeMismatch {
declared: leaf.describe(),
found: format!("text {text:?}"),
}
})?),
Lexical::Xsd => {
return Err(XmlError::TypeMismatch {
declared: leaf.describe(),
found: format!("text {text:?}"),
})
}
},
};
Ok(value)
}
fn finite(leaf: &Leaf, token: &str, text: &str) -> Result<f64, XmlError> {
match token.parse::<f64>() {
Ok(real) if real.is_finite() => Ok(real),
_ => Err(invalid(leaf, text)),
}
}
pub(crate) fn hex_binary(token: &str) -> Option<Arc<str>> {
if token.len() % 2 != 0 || !token.bytes().all(|byte| byte.is_ascii_hexdigit()) {
return None;
}
Some(format!("0{}", token.to_ascii_uppercase()).into())
}
fn is_step_binary(text: &str) -> bool {
let mut bytes = text.bytes();
matches!(bytes.next(), Some(b'0'..=b'3')) && bytes.all(|byte| byte.is_ascii_hexdigit())
}
pub(crate) fn list_text(shape: &Shape, text: &str, lexical: Lexical) -> Result<Value, XmlError> {
let items = text
.split_ascii_whitespace()
.map(|token| scalar(&shape.leaf, token, lexical))
.collect::<Result<Vec<_>, _>>()?;
nest(shape, items, None)
}
pub(crate) fn nest(
shape: &Shape,
items: Vec<Value>,
array_size: Option<&[usize]>,
) -> Result<Value, XmlError> {
let depth = shape.levels.len();
if depth == 0 {
return Err(unsupported(
"a list where the declared type is not an aggregate".into(),
));
}
let count = items.len();
let mismatch = || XmlError::TypeMismatch {
declared: shape.describe(),
found: format!("{count} flattened items"),
};
let sizes: Vec<usize> = match array_size {
Some(sizes) => {
if sizes.len() != depth || sizes.iter().product::<usize>() != count {
return Err(XmlError::TypeMismatch {
declared: shape.describe(),
found: format!("arraySize {sizes:?} for {count} flattened items"),
});
}
sizes[1..].to_vec()
}
None => shape.levels[1..]
.iter()
.map(|level| {
level.fixed.ok_or_else(|| {
unsupported(format!(
"a flattened {} without arraySize: its inner sizes are not fixed",
shape.describe()
))
})
})
.collect::<Result<_, _>>()?,
};
chunk(items, &sizes).ok_or_else(mismatch)
}
fn chunk(items: Vec<Value>, inner: &[usize]) -> Option<Value> {
let Some((&size, rest)) = inner.split_first() else {
return Some(Value::List(items));
};
let stride: usize = inner.iter().product();
if size == 0 || stride == 0 || items.len() % stride != 0 {
return None;
}
let mut out = Vec::with_capacity(items.len() / stride);
let mut items = items.into_iter();
loop {
let group: Vec<Value> = items.by_ref().take(stride).collect();
if group.is_empty() {
break;
}
out.push(chunk(group, rest)?);
}
Some(Value::List(out))
}
pub(crate) fn conform(schema: &Schema, shape: &Shape, value: &Value) -> Result<(), XmlError> {
conform_at(schema, shape, value, 0)
}
fn conform_at(schema: &Schema, shape: &Shape, value: &Value, depth: usize) -> Result<(), XmlError> {
if depth > MAX_DEPTH {
return Err(unsupported("a value nested too deeply".into()));
}
let mismatch = |found: String| XmlError::TypeMismatch {
declared: shape.describe(),
found,
};
match value {
Value::Null | Value::Derived => return Ok(()),
Value::List(items) => {
if shape.levels.is_empty() {
return Err(mismatch("a list".into()));
}
let inner = shape.inner();
for item in items {
conform_at(schema, &inner, item, depth + 1)?;
}
return Ok(());
}
_ if !shape.levels.is_empty() => return Err(mismatch(kind_of(value))),
_ => {}
}
let fits = match (&shape.leaf, value) {
(Leaf::Entity(_), Value::Ref(_)) => true,
(Leaf::Select(select), Value::Ref(_)) => select_admits_entities(schema, select),
(Leaf::Select(select), Value::Typed { type_name, value }) => {
let Some(definition) = schema.type_def(type_name) else {
return Err(mismatch(format!("a value typed `{type_name}`")));
};
if !schema.accepts_type(select, &definition.name) {
return Err(XmlError::TypeMismatch {
declared: select.to_string(),
found: format!("a value typed `{type_name}`"),
});
}
let inner = type_shape(schema, &definition.name)?;
return conform_at(schema, &inner, value, depth + 1);
}
(Leaf::Integer, Value::Integer(_))
| (Leaf::Real | Leaf::Number, Value::Real(_) | Value::Integer(_))
| (Leaf::Boolean, Value::Bool(_))
| (Leaf::Logical, Value::Bool(_) | Value::LogicalUnknown)
| (Leaf::Binary, Value::Binary(_)) => true,
(Leaf::Text { fixed }, Value::Text(text)) => {
if let Some(width) = fixed {
if text.chars().count() != *width {
return Err(invalid(&shape.leaf, text));
}
}
true
}
(Leaf::Enumeration { members, .. }, Value::Enum(member)) => {
if !members
.iter()
.any(|declared| declared.eq_ignore_ascii_case(member))
{
return Err(invalid(&shape.leaf, member));
}
true
}
_ => false,
};
if fits {
Ok(())
} else {
Err(mismatch(kind_of(value)))
}
}
fn select_admits_entities(schema: &Schema, select: &str) -> bool {
let mut pending = vec![select.to_string()];
let mut seen = std::collections::HashSet::new();
while let Some(name) = pending.pop() {
if !seen.insert(name.to_ascii_uppercase()) || seen.len() > 256 {
continue;
}
if schema.entity(&name).is_some() {
return true;
}
if let Some(definition) = schema.type_def(&name) {
if let TypeKind::Select(members) = &definition.kind {
pending.extend(members.iter().cloned());
}
}
}
false
}
fn kind_of(value: &Value) -> String {
match value {
Value::Null => "an unset value".into(),
Value::Derived => "a derived value".into(),
Value::Bool(_) | Value::LogicalUnknown => "a logical".into(),
Value::Integer(_) => "an integer".into(),
Value::Real(_) => "a real".into(),
Value::Text(_) => "a string".into(),
Value::Binary(_) => "a binary".into(),
Value::Enum(member) => format!("the enumeration value `{member}`"),
Value::Ref(_) => "an entity reference".into(),
Value::List(_) => "a list".into(),
Value::Typed { type_name, .. } => format!("a value typed `{type_name}`"),
}
}
pub(crate) fn references(
schema: &Schema,
shape: &Shape,
value: &Value,
out: &mut Vec<(ifc_model::EntityId, Arc<str>)>,
) -> Result<(), XmlError> {
match value {
Value::Ref(id) => match &shape.leaf {
Leaf::Entity(name) | Leaf::Select(name) if shape.levels.is_empty() => {
out.push((*id, name.clone()));
}
_ => {
return Err(XmlError::TypeMismatch {
declared: shape.describe(),
found: "an entity reference".into(),
})
}
},
Value::List(items) if !shape.levels.is_empty() => {
let inner = shape.inner();
for item in items {
references(schema, &inner, item, out)?;
}
}
Value::Typed { type_name, value } => {
if let Some(definition) = schema.type_def(type_name) {
let inner = type_shape(schema, &definition.name)?;
references(schema, &inner, value, out)?;
}
}
_ => {}
}
Ok(())
}