use crate::error::XmlError;
use crate::scalar::infer;
use crate::XmlCodec;
use ifc_model::Value;
pub(crate) enum Raw {
Text(String),
Value(Value),
}
impl Raw {
fn inferred(self) -> Value {
match self {
Self::Text(text) => infer(&text),
Self::Value(value) => value,
}
}
}
pub(crate) fn attribute_names(codec: &XmlCodec, type_name: &str, count: usize) -> Vec<String> {
let names = schema_names(codec, type_name);
if names.len() >= count {
return names.into_iter().take(count).collect();
}
(0..count).map(|slot| format!("a{slot}")).collect()
}
pub(crate) fn order(
codec: &XmlCodec,
type_name: &str,
named: Vec<(String, Raw)>,
) -> Result<Vec<Value>, XmlError> {
let names = schema_names(codec, type_name);
let mut positioned: Vec<(usize, String, Value)> = Vec::new();
let mut unknown = Vec::new();
for (name, raw) in named {
let value = raw.inferred();
let slot = names
.iter()
.position(|candidate| *candidate == name)
.or_else(|| positional_index(&name));
match slot {
Some(slot) => positioned.push((slot, name, value)),
None => unknown.push(value),
}
}
positioned.sort_by_key(|(slot, _, _)| *slot);
let mut values = Vec::with_capacity(positioned.len() + unknown.len());
for (slot, name, value) in positioned {
if slot < values.len() {
return Err(XmlError::DuplicateSlot { name, slot });
}
if slot > values.len() && slot > names.len() {
return Err(XmlError::MissingSlot {
name,
slot,
missing: values.len(),
});
}
values.resize(slot, Value::Null);
values.push(value);
}
values.extend(unknown);
Ok(values)
}
#[cfg(feature = "schema")]
fn schema_names(codec: &XmlCodec, type_name: &str) -> Vec<String> {
let Some(schema) = codec.schema() else {
return Vec::new();
};
let names = schema.attribute_names(type_name);
let mut seen = std::collections::HashSet::new();
let usable = names
.iter()
.all(|name| *name != "id" && positional_index(name).is_none() && seen.insert(*name));
if usable {
names.into_iter().map(str::to_string).collect()
} else {
Vec::new()
}
}
#[cfg(not(feature = "schema"))]
fn schema_names(codec: &XmlCodec, type_name: &str) -> Vec<String> {
let _ = (codec, type_name);
Vec::new()
}
fn positional_index(name: &str) -> Option<usize> {
name.strip_prefix('a')?.parse().ok()
}
#[cfg(feature = "schema")]
pub(crate) fn order_strict(
layouts: &mut crate::typing::Layouts<'_>,
type_name: &str,
path: &str,
named: Vec<(String, Raw)>,
) -> Result<Vec<Value>, XmlError> {
use crate::typing::{conform, scalar, Lexical};
let layout = layouts
.entity(type_name, false)?
.ok_or_else(|| XmlError::UnknownEntity {
name: type_name.into(),
})?;
if layout.abstract_ {
return Err(XmlError::AbstractEntity {
name: layout.name.to_string(),
});
}
let mut slots: Vec<Option<Value>> = vec![None; layout.slots.len()];
for (name, raw) in named {
let slot = layout
.slot(&name)
.ok_or_else(|| XmlError::UnknownAttribute {
entity: layout.name.to_string(),
element: type_name.into(),
attribute: name.clone(),
})?;
if slots[slot].is_some() {
return Err(XmlError::DuplicateSlot { name, slot });
}
let shape = &layout.slots[slot].shape;
let typed = match raw {
Raw::Text(text) if shape.levels.is_empty() => {
scalar(&shape.leaf, &text, Lexical::Native)
}
Raw::Text(_) => Err(XmlError::WrongForm {
attribute: name.clone(),
expected: "a list child element",
found: "an XML attribute",
}),
Raw::Value(value) => Ok(value),
};
let value = typed
.and_then(|value| conform(layouts.schema(), shape, &value).map(|()| value))
.map_err(|error| error.at(format!("{path}/{name}")))?;
slots[slot] = Some(value);
}
Ok(slots
.into_iter()
.map(|value| value.unwrap_or(Value::Null))
.collect())
}