1use crate::error::XmlError;
11use crate::scalar::{decode_element, format_ref, infer, parse_ref};
12use crate::{slots, XmlCodec};
13use ifc_model::{Entity, EntityId, Model, Value};
14use quick_xml::escape::unescape;
15use quick_xml::events::attributes::Attribute;
16use quick_xml::events::{BytesRef, BytesStart, Event};
17use quick_xml::name::ResolveResult;
18use quick_xml::reader::NsReader;
19
20const XSI_NAMESPACE: &str = "http://www.w3.org/2001/XMLSchema-instance";
21
22pub fn looks_like_xml(bytes: &[u8]) -> bool {
24 let head = &bytes[..bytes.len().min(512)];
25 let text = String::from_utf8_lossy(head);
26 let trimmed = text.trim_start_matches(['\u{feff}', ' ', '\n', '\r', '\t']);
27 trimmed.starts_with("<?xml") || trimmed.starts_with("<ifcXML")
28}
29
30pub fn read(codec: &XmlCodec, bytes: &[u8]) -> Result<Model, XmlError> {
32 let mut reader = NsReader::from_reader(bytes);
37
38 let mut model = Model::new();
39 let mut buf = Vec::new();
40 let mut seen_root = false;
41 let mut root_closed = false;
42 let mut element_depth = 0usize;
43
44 let mut in_header = false;
46 let mut header_tag: Option<String> = None;
47
48 let mut current: Option<PendingEntity> = None;
50 let mut stack: Vec<PendingValue> = Vec::new();
52 let mut text_buf = String::new();
53
54 loop {
55 match reader.read_resolved_event_into(&mut buf) {
56 Err(error) => {
57 return Err(
58 XmlError::Malformed(error.to_string()).at(current_path(¤t, &stack, None))
59 );
60 }
61 Ok((_, Event::Eof)) => break,
62
63 Ok((namespace, Event::Start(e))) => {
64 let name = local_name(&e);
65 validate_element(codec, namespace, &name)?;
66 validate_root(codec, &e, &name, element_depth, root_closed, &mut seen_root)?;
67 element_depth += 1;
68 match name.as_str() {
69 "ifcXML" => {
70 if let Some(schema) = attr_value(&e, "schema") {
71 model.header_mut().schema = vec![schema];
72 }
73 }
74 "header" => in_header = true,
75 _ if in_header => {
76 header_tag = Some(name);
77 text_buf.clear();
78 }
79 _ if current.is_none() => match start_entity(&e, &name) {
80 Ok(Some(started)) => current = Some(started),
81 Ok(None) => {}
82 Err(error) => {
83 return Err(error.at(raw_entity_path(&name, attr_value(&e, "id"))));
84 }
85 },
86 _ => {
87 let item_index = stack.last().map_or(0, |parent| parent.items.len());
88 stack.push(PendingValue::from_start(&e, name, item_index));
89 text_buf.clear();
90 }
91 }
92 }
93
94 Ok((namespace, Event::Empty(e))) => {
95 let name = local_name(&e);
96 validate_element(codec, namespace, &name)?;
97 validate_root(codec, &e, &name, element_depth, root_closed, &mut seen_root)?;
98 if codec.profile().is_some() && element_depth == 0 {
99 root_closed = true;
100 }
101 if current.is_none() && !in_header {
102 match start_entity(&e, &name) {
103 Ok(Some(entity)) => finish_entity(codec, &mut model, entity)?,
104 Ok(None) => {}
105 Err(error) => {
106 return Err(error.at(raw_entity_path(&name, attr_value(&e, "id"))));
107 }
108 }
109 } else if current.is_some() {
110 let value = if has_true_xsi_nil(&reader, &e) {
111 Value::Null
112 } else if attr_value(&e, "derived").as_deref() == Some("true") {
113 Value::Derived
114 } else {
115 let item_index = stack.last().map_or(0, |parent| parent.items.len());
116 let pending = PendingValue::from_start(&e, name.clone(), item_index);
117 let path =
118 current_path(¤t, &stack, Some(pending.path_segment.as_str()));
119 pending.finish("").map_err(|error| error.at(path))?
120 };
121 push_value(&mut stack, &mut current, name, value);
122 }
123 }
124
125 Ok((_, Event::Text(t))) => text_buf.push_str(&t),
129
130 Ok((_, Event::GeneralRef(reference))) => {
131 let resolved = resolve_reference(&reference).map_err(|error| {
132 XmlError::Malformed(error.to_string()).at(current_path(¤t, &stack, None))
133 })?;
134 text_buf.push_str(&resolved);
135 }
136
137 Ok((namespace, Event::End(e))) => {
138 let name = e.local_name().as_ref().to_owned();
139 validate_element(codec, namespace, &name)?;
140 element_depth = element_depth.saturating_sub(1);
141 if codec.profile().is_some() && element_depth == 0 {
142 root_closed = true;
143 }
144 match name.as_str() {
145 "header" => in_header = false,
146 "ifcXML" => {}
147 _ if in_header => {
148 if let Some(tag) = header_tag.take() {
149 apply_header_field(&mut model, &tag, &text_buf);
150 }
151 text_buf.clear();
152 }
153 _ => {
154 if let Some(pending) = stack.pop() {
155 let path =
156 current_path(¤t, &stack, Some(pending.path_segment.as_str()));
157 let value =
158 pending.finish(&text_buf).map_err(|error| error.at(path))?;
159 push_value(&mut stack, &mut current, pending.name.clone(), value);
160 text_buf.clear();
161 } else if let Some(entity) = current.take() {
162 finish_entity(codec, &mut model, entity)?;
163 }
164 text_buf.clear();
165 }
166 }
167 }
168 Ok(_) => {}
169 }
170 buf.clear();
171 }
172
173 if codec.profile().is_some() && (!seen_root || !root_closed) {
174 return Err(XmlError::Root { found: None });
175 }
176
177 Ok(model)
178}
179
180fn validate_element(
181 codec: &XmlCodec,
182 namespace: ResolveResult<'_>,
183 element: &str,
184) -> Result<(), XmlError> {
185 let Some(profile) = codec.profile() else {
186 return Ok(());
187 };
188 let found = match namespace {
189 ResolveResult::Unbound => None,
190 ResolveResult::Bound(namespace) => Some(namespace.as_ref().to_owned()),
191 ResolveResult::Unknown(prefix) => Some(format!("unresolved prefix `{prefix}`")),
192 };
193 if found.as_deref() != Some(profile.namespace()) {
194 return Err(XmlError::Namespace {
195 element: element.into(),
196 expected: profile.namespace(),
197 found,
198 });
199 }
200 Ok(())
201}
202
203fn validate_root(
204 codec: &XmlCodec,
205 element: &BytesStart<'_>,
206 name: &str,
207 depth: usize,
208 root_closed: bool,
209 seen_root: &mut bool,
210) -> Result<(), XmlError> {
211 let Some(profile) = codec.profile() else {
212 return Ok(());
213 };
214 if *seen_root {
215 if depth == 0 || root_closed || name == "ifcXML" {
216 return Err(XmlError::Root {
217 found: Some(name.into()),
218 });
219 }
220 return Ok(());
221 }
222 if depth != 0 || name != "ifcXML" {
223 return Err(XmlError::Root {
224 found: Some(name.into()),
225 });
226 }
227 *seen_root = true;
228 let found = attr_value(element, "schema");
229 if found.as_deref() != Some(profile.schema_token()) {
230 return Err(XmlError::Profile {
231 expected: profile.schema_token(),
232 found,
233 });
234 }
235 Ok(())
236}
237
238struct PendingEntity {
243 id: EntityId,
244 type_name: String,
245 attrs: Vec<(String, Value)>,
246}
247
248struct PendingValue {
250 name: String,
251 path_segment: String,
252 kind: String,
253 type_name: Option<String>,
254 items: Vec<Value>,
255}
256
257impl PendingValue {
258 fn from_start(e: &BytesStart<'_>, name: String, item_index: usize) -> Self {
259 let path_segment = if name == "item" {
260 format!("item[{item_index}]")
261 } else {
262 name.clone()
263 };
264 Self {
265 name,
266 path_segment,
267 kind: attr_value(e, "kind").unwrap_or_default(),
268 type_name: attr_value(e, "type"),
269 items: Vec::new(),
270 }
271 }
272
273 fn finish(&self, text: &str) -> Result<Value, XmlError> {
274 if let Some(value) = decode_element(&self.kind, text)? {
275 return Ok(value);
276 }
277 let value = if self.kind == "list" {
278 Value::List(self.items.clone())
279 } else {
280 let inner = self.items.first().cloned().unwrap_or(Value::Null);
281 Value::Typed {
282 type_name: self.type_name.clone().unwrap_or_default().into(),
283 value: Box::new(inner),
284 }
285 };
286 Ok(value)
287 }
288}
289
290fn push_value(
292 stack: &mut [PendingValue],
293 current: &mut Option<PendingEntity>,
294 name: String,
295 value: Value,
296) {
297 if let Some(parent) = stack.last_mut() {
298 parent.items.push(value);
299 return;
300 }
301 if let Some(entity) = current.as_mut() {
302 entity.attrs.push((name, value));
303 }
304}
305
306fn start_entity(e: &BytesStart<'_>, name: &str) -> Result<Option<PendingEntity>, XmlError> {
308 let Some(id_text) = attr_value(e, "id") else {
309 return Ok(None);
310 };
311 let id = parse_ref(&id_text).ok_or_else(|| XmlError::BadId(id_text.clone()))?;
312 let mut attrs = Vec::new();
313 for attr in e.attributes().flatten() {
314 let key = attr.key.local_name().as_ref().to_owned();
315 if key == "id" {
316 continue;
317 }
318 let value = unescape_attribute(&attr)
319 .map(|value| value.into_owned())
320 .map_err(|error| XmlError::Malformed(error.to_string()))?;
321 attrs.push((key, infer(&value)));
322 }
323 Ok(Some(PendingEntity {
324 id,
325 type_name: name.to_string(),
326 attrs,
327 }))
328}
329
330fn finish_entity(
332 codec: &XmlCodec,
333 model: &mut Model,
334 entity: PendingEntity,
335) -> Result<(), XmlError> {
336 let values = slots::order(codec, &entity.type_name, entity.attrs).map_err(|error| {
337 error.at(raw_entity_path(
338 &entity.type_name,
339 Some(format_ref(entity.id)),
340 ))
341 })?;
342 model.insert(entity.id, Entity::new(entity.type_name, values));
343 Ok(())
344}
345
346fn raw_entity_path(type_name: &str, id: Option<String>) -> String {
347 match id {
348 Some(id) => format!("/ifcXML/{type_name}[@id='{id}']"),
349 None => format!("/ifcXML/{type_name}"),
350 }
351}
352
353fn current_path(
354 current: &Option<PendingEntity>,
355 stack: &[PendingValue],
356 leaf: Option<&str>,
357) -> String {
358 let mut path = String::from("/ifcXML");
359 if let Some(entity) = current {
360 path.push('/');
361 path.push_str(&entity.type_name);
362 path.push_str(&format!("[@id='i{}']", entity.id.0));
363 }
364 for value in stack {
365 path.push('/');
366 path.push_str(&value.path_segment);
367 }
368 if let Some(leaf) = leaf {
369 path.push('/');
370 path.push_str(leaf);
371 }
372 path
373}
374
375fn local_name(e: &BytesStart<'_>) -> String {
376 e.local_name().as_ref().to_owned()
377}
378
379fn unescape_attribute<'a>(
386 attribute: &'a Attribute<'_>,
387) -> Result<std::borrow::Cow<'a, str>, quick_xml::escape::EscapeError> {
388 unescape(&attribute.value)
389}
390
391fn resolve_reference(reference: &BytesRef<'_>) -> Result<String, quick_xml::escape::EscapeError> {
395 unescape(&format!("&{};", &**reference)).map(std::borrow::Cow::into_owned)
396}
397
398fn has_true_xsi_nil(reader: &NsReader<&[u8]>, element: &BytesStart<'_>) -> bool {
399 element.attributes().flatten().any(|attribute| {
400 if attribute.key.local_name().as_ref() != "nil" {
401 return false;
402 }
403 let (namespace, _) = reader.resolver().resolve_attribute(attribute.key);
404 matches!(
405 namespace,
406 ResolveResult::Bound(namespace) if namespace.as_ref() == XSI_NAMESPACE
407 ) && unescape_attribute(&attribute).is_ok_and(|value| value.as_ref() == "true")
408 })
409}
410
411fn attr_value(e: &BytesStart<'_>, key: &str) -> Option<String> {
412 e.attributes().flatten().find_map(|a| {
413 (a.key.local_name().as_ref() == key)
414 .then(|| unescape_attribute(&a).map(|v| v.into_owned()).ok())
415 .flatten()
416 })
417}
418
419fn apply_header_field(model: &mut Model, tag: &str, text: &str) {
420 let h = model.header_mut();
421 match tag {
422 "name" => h.name = text.to_string(),
423 "time_stamp" => h.time_stamp = text.to_string(),
424 "preprocessor_version" => h.preprocessor_version = text.to_string(),
425 "originating_system" => h.originating_system = text.to_string(),
426 "authorization" => h.authorization = text.to_string(),
427 "author" => h.author.push(text.to_string()),
428 "organization" => h.organization.push(text.to_string()),
429 "description" => h.description.push(text.to_string()),
430 _ => {}
431 }
432}