#[cfg(test)]
mod conformance;
mod finish;
mod item;
mod open;
mod state;
use crate::error::XmlError;
use crate::typing::{self, EntityLayout, InverseLayout, Layouts, Leaf, Lexical, Shape};
use crate::XmlProfile;
use ifc_model::{Entity, EntityId, Model, Value};
use ifc_schema::{AggregateKind, Schema};
use quick_xml::escape::unescape;
use quick_xml::events::{BytesStart, Event};
use quick_xml::name::{QName, ResolveResult};
use quick_xml::reader::NsReader;
use quick_xml::XmlVersion;
use std::collections::HashMap;
use std::sync::Arc;
const XSI: &str = "http://www.w3.org/2001/XMLSchema-instance";
const PLACEHOLDER: u64 = 1 << 62;
pub(crate) fn read(schema: &Schema, profile: XmlProfile, bytes: &[u8]) -> Result<Model, XmlError> {
if schema.version() != Some(profile.version()) {
return Err(XmlError::SchemaMismatch {
profile: profile.schema_token(),
schema: schema.name().into(),
});
}
let mut state = State::new(schema, profile);
let mut reader = NsReader::from_reader(bytes);
let mut buf = Vec::new();
loop {
let event = reader
.read_resolved_event_into(&mut buf)
.map_err(|error| XmlError::Malformed(error.to_string()).at(state.path()))?;
match event {
(_, Event::Eof) => break,
(namespace, Event::Start(element)) => {
let namespace = owned_namespace(namespace);
state.open(&reader, namespace, &element)?;
}
(namespace, Event::Empty(element)) => {
let namespace = owned_namespace(namespace);
state.open(&reader, namespace, &element)?;
state.close()?;
}
(_, Event::End(_)) => state.close()?,
(_, Event::Text(text)) => {
let text = text.replace("\r\n", "\n").replace('\r', "\n");
state.text(&text)?;
}
(_, Event::CData(data)) => {
let text: &str = data.as_ref();
state.text(text)?;
}
(_, Event::GeneralRef(reference)) => {
let reference = format!("&{};", &*reference);
let resolved = unescape(&reference)
.map_err(|error| XmlError::Malformed(error.to_string()).at(state.path()))?;
state.text(&resolved)?;
}
(_, Event::DocType(_)) => {
return Err(unsupported("a document type declaration"));
}
_ => {}
}
buf.clear();
}
state.finish()
}
fn unsupported(construct: &str) -> XmlError {
XmlError::Unsupported {
construct: construct.into(),
}
}
struct Pending {
layout: Arc<EntityLayout>,
slots: Vec<Option<Value>>,
parent: u64,
relative: Box<str>,
}
struct Backfill {
relationship: u64,
parent: u64,
inverse: InverseLayout,
}
struct Claim {
target: u64,
claimed: Arc<str>,
referrer: u64,
}
enum Target {
Slot { entity: u64, slot: usize },
Item,
Inverse { parent: u64, inverse: InverseLayout },
}
struct ValueFrame {
target: Target,
shape: Shape,
text_content: bool,
single: bool,
wrap: Option<Arc<str>>,
items: Vec<Value>,
nested_items: Option<bool>,
text: String,
array_size: Option<Vec<usize>>,
attribute: Arc<str>,
}
enum Frame {
Root,
Header,
HeaderField {
name: String,
text: String,
},
Entity {
number: u64,
},
Value(Box<ValueFrame>),
Empty,
}
struct Segment {
frame: Frame,
segment: String,
}
#[derive(Default)]
struct Attributes {
plain: Vec<(String, String)>,
xsi_type: Option<String>,
xsi_nil: Option<bool>,
xsi_schema_location: bool,
}
impl Attributes {
fn take(&mut self, name: &str) -> Option<String> {
let index = self.plain.iter().position(|(key, _)| key == name)?;
Some(self.plain.remove(index).1)
}
}
struct State<'s> {
schema: &'s Schema,
profile: XmlProfile,
layouts: Layouts<'s>,
model: Model,
entities: Vec<Pending>,
ids: HashMap<String, u64>,
forward: HashMap<String, u64>,
forward_names: Vec<String>,
backfills: Vec<Backfill>,
claims: Vec<Claim>,
stack: Vec<Segment>,
seen_root: bool,
}
fn owned_namespace(namespace: ResolveResult<'_>) -> Option<String> {
match namespace {
ResolveResult::Bound(namespace) => Some(namespace.as_ref().to_owned()),
ResolveResult::Unbound => None,
ResolveResult::Unknown(prefix) => Some(format!("unresolved prefix `{prefix}`")),
}
}
fn local_href(href: &str) -> Result<String, XmlError> {
let href = href.trim();
let local = href.strip_prefix('#').unwrap_or(href);
if local.is_empty() || local.contains(['#', '/', ':', '?']) {
return Err(XmlError::Unsupported {
construct: format!("the external reference href={href:?}"),
});
}
Ok(local.into())
}
const fn index(number: u64) -> usize {
(number - 1) as usize
}
fn rewrite(value: &mut Value, resolve: &impl Fn(u64) -> u64) {
match value {
Value::Ref(EntityId(number)) => *number = resolve(*number),
Value::List(items) => items.iter_mut().for_each(|item| rewrite(item, resolve)),
Value::Typed { value, .. } => rewrite(value, resolve),
_ => {}
}
}