1use std::collections::HashMap;
4
5use roxmltree::{Document as XmlDocument, Node, ParsingOptions};
6use serde_json::{Map, Number, Value};
7
8use crate::datetime;
9use crate::model::{Element, Model, Representation, Root, TypeDef};
10use crate::{Error, Issue, IssueKind, Position, TYPE_KEY, XML_NS, XSI_NS};
11
12#[derive(Debug, Clone)]
14pub struct Document {
15 pub json: Value,
18 pub root_type: String,
20 pub positions: HashMap<String, Position>,
23 pub issues: Vec<Issue>,
26}
27
28impl Document {
29 pub fn position(&self, path: &str) -> Option<Position> {
31 self.positions.get(path).copied()
32 }
33}
34
35#[derive(Debug, Clone, Default)]
37pub struct ReadOptions<'a> {
38 pub root: Option<&'a str>,
42 pub namespaces: Vec<(&'a str, &'a str)>,
45}
46
47pub fn read(xml: &str, model: &Model, root_type: Option<&str>) -> Result<Document, Error> {
50 read_with(
51 xml,
52 model,
53 &ReadOptions {
54 root: root_type,
55 namespaces: Vec::new(),
56 },
57 )
58}
59
60pub fn read_with(xml: &str, model: &Model, options: &ReadOptions<'_>) -> Result<Document, Error> {
62 let parsing = ParsingOptions {
63 allow_dtd: false,
64 ..ParsingOptions::default()
65 };
66 let (text, shift) = if options.namespaces.is_empty() {
69 (std::borrow::Cow::Borrowed(xml), 0)
70 } else {
71 let (wrapped, shift) = wrap_fragment(xml, &options.namespaces);
72 (std::borrow::Cow::Owned(wrapped), shift)
73 };
74 let doc = XmlDocument::parse_with_options(&text, parsing).map_err(|e| {
75 let pos = e.pos();
76 Error {
77 message: format!("not well-formed XML: {e}"),
78 position: Some(Position {
79 line: pos.row,
80 column: if pos.row == 1 {
81 pos.col.saturating_sub(shift)
82 } else {
83 pos.col
84 },
85 }),
86 }
87 })?;
88 let root = if shift > 0 {
89 doc.root_element()
90 .children()
91 .find(|n| n.is_element())
92 .ok_or_else(|| Error {
93 message: "the fragment has no element".to_owned(),
94 position: None,
95 })?
96 } else {
97 doc.root_element()
98 };
99 let resolved = root_of(model, root, options.root)?;
100 let expected_name = match resolved {
101 Root::Element(_, element) => Some(element.xml_name.as_str()),
102 Root::Type(ty) => ty.xml_name.as_deref(),
103 };
104 if let Some(expected) = expected_name
105 && expected != root.tag_name().name()
106 {
107 return Err(Error {
108 message: format!(
109 "the root element is `{}`, not `{}`",
110 root.tag_name().name(),
111 expected
112 ),
113 position: None,
114 });
115 }
116 let mut reader = Reader {
117 model,
118 doc: &doc,
119 shift,
120 positions: HashMap::new(),
121 issues: Vec::new(),
122 };
123 let path = resolved.path();
124 reader.at(root, &path);
125 let json = match resolved {
126 Root::Type(ty) => reader.complex(root, &Shape::of(ty), &path),
127 Root::Element(ty, element) => {
128 let context = Shape::of(ty);
129 reader
130 .element_value(root, element, &context, &path)
131 .unwrap_or(Value::Null)
132 }
133 };
134 Ok(Document {
135 json,
136 root_type: resolved.spec(),
137 positions: reader.positions,
138 issues: reader.issues,
139 })
140}
141
142pub(crate) fn wrap_fragment(xml: &str, namespaces: &[(&str, &str)]) -> (String, u32) {
146 let mut open = String::from("<fragment");
147 for (prefix, uri) in namespaces {
148 if prefix.is_empty() {
149 open.push_str(" xmlns=\"");
150 } else {
151 open.push_str(&format!(" xmlns:{prefix}=\""));
152 }
153 crate::write::escape_attribute(uri, &mut open);
154 open.push('"');
155 }
156 open.push('>');
157 let body = match xml.find("<?xml") {
158 Some(start) if xml[..start].trim().is_empty() => {
159 let end = xml[start..].find("?>").map_or(start, |e| start + e + 2);
160 let mut blanked = String::with_capacity(xml.len());
161 blanked.push_str(&xml[..start]);
162 blanked.extend(
163 xml[start..end]
164 .chars()
165 .map(|c| if c == '\n' { '\n' } else { ' ' }),
166 );
167 blanked.push_str(&xml[end..]);
168 blanked
169 }
170 _ => xml.to_owned(),
171 };
172 let shift = open.chars().count() as u32;
173 (format!("{open}{body}</fragment>"), shift)
174}
175
176fn root_of<'m>(
177 model: &'m Model,
178 root: Node<'_, '_>,
179 stated: Option<&str>,
180) -> Result<Root<'m>, Error> {
181 if let Some(stated) = stated {
182 if let Some(ty) = model.by_type_name(root.tag_name().namespace(), stated) {
183 return Ok(Root::Type(ty));
184 }
185 return model.root(stated).ok_or_else(|| Error {
186 message: format!("the model has no type or element `{stated}`"),
187 position: None,
188 });
189 }
190 let name = root.tag_name().name();
191 let candidates = model.roots(root.tag_name().namespace(), name);
192 match candidates.as_slice() {
193 [single] => Ok(Root::Type(single)),
194 [] => Err(Error {
195 message: format!(
196 "no type of the model has the root element `{name}`{}; say which type or element it is",
197 root.tag_name()
198 .namespace()
199 .map(|ns| format!(" in namespace `{ns}`"))
200 .unwrap_or_default()
201 ),
202 position: None,
203 }),
204 several => Err(Error {
205 message: format!(
206 "the root element `{name}` can be any of {}; say which type it is",
207 several
208 .iter()
209 .map(|t| t.url.as_str())
210 .collect::<Vec<_>>()
211 .join(", ")
212 ),
213 position: None,
214 }),
215 }
216}
217
218struct Shape<'m> {
220 elements: &'m [Element],
221 owner: String,
223 namespace: Option<String>,
224}
225
226impl<'m> Shape<'m> {
227 fn of(ty: &'m TypeDef) -> Self {
228 Shape {
229 elements: &ty.elements,
230 owner: ty.url.clone(),
231 namespace: ty.namespace.clone(),
232 }
233 }
234
235 fn text_element(&self) -> Option<&'m Element> {
236 self.elements
237 .iter()
238 .find(|e| e.representation == Representation::Text)
239 }
240
241 fn choice_group(&self) -> Option<&'m Element> {
242 self.elements.iter().find(|e| e.choice_group)
243 }
244}
245
246enum Slot<'m> {
248 Direct(&'m Element),
249 Grouped(&'m Element, &'m Element),
251}
252
253struct Reader<'m, 'd> {
254 model: &'m Model,
255 doc: &'d XmlDocument<'d>,
256 shift: u32,
258 positions: HashMap<String, Position>,
259 issues: Vec<Issue>,
260}
261
262impl<'m> Reader<'m, '_> {
263 fn position(&self, node: Node<'_, '_>) -> Position {
264 let pos = self.doc.text_pos_at(node.range().start);
265 Position {
266 line: pos.row,
267 column: if pos.row == 1 {
268 pos.col.saturating_sub(self.shift)
269 } else {
270 pos.col
271 },
272 }
273 }
274
275 fn at(&mut self, node: Node<'_, '_>, path: &str) {
276 let position = self.position(node);
277 self.positions.insert(path.to_owned(), position);
278 }
279
280 fn issue(&mut self, kind: IssueKind, path: &str, node: Node<'_, '_>, message: String) {
281 let position = Some(self.position(node));
282 self.issues.push(Issue {
283 kind,
284 path: path.to_owned(),
285 position,
286 message,
287 });
288 }
289
290 fn complex(&mut self, node: Node<'_, '_>, shape: &Shape<'m>, path: &str) -> Value {
292 let mut object = Map::new();
293 self.attributes(node, shape, path, &mut object);
294 let text_element = shape.text_element();
295 let group = shape.choice_group();
296 let has_children = node.children().any(|c| c.is_element());
297 let mut text = String::new();
298 let mut text_seen = false;
299 for child in node.children() {
300 if child.is_text() {
301 let content = child.text().unwrap_or_default();
302 if let Some(group) = group
303 && group.child("xmlText").is_some()
304 {
305 if !content.trim().is_empty() {
306 let index = push_many(&mut object, &group.name, {
307 let mut item = Map::new();
308 item.insert("xmlText".to_owned(), Value::String(content.to_owned()));
309 Value::Object(item)
310 });
311 let _ = index;
312 }
313 continue;
314 }
315 if text_element.is_some() {
316 text.push_str(content);
317 text_seen = true;
318 } else if !content.trim().is_empty() {
319 self.issue(
320 IssueKind::UnexpectedText,
321 path,
322 child,
323 format!("text `{}` where the model has no text", content.trim()),
324 );
325 }
326 continue;
327 }
328 if !child.is_element() {
329 continue;
330 }
331 let expected_ns = node.tag_name().namespace();
332 let Some(slot) = self.slot(shape, child, expected_ns) else {
333 let name = qualified(child);
334 self.issue(
335 IssueKind::UnknownElement,
336 path,
337 child,
338 format!("element `{name}` is not in the model; left out"),
339 );
340 continue;
341 };
342 match slot {
343 Slot::Direct(element) => {
344 let index = count(&object, &element.name);
345 let child_path = if element.many {
346 format!("{path}.{}[{index}]", element.name)
347 } else {
348 format!("{path}.{}", element.name)
349 };
350 if !element.many && object.contains_key(&element.name) {
351 self.issue(
352 IssueKind::TooMany,
353 &child_path,
354 child,
355 format!("`{}` repeats but the model allows one", element.xml_name),
356 );
357 continue;
358 }
359 self.at(child, &child_path);
360 let Some(value) = self.element_value(child, element, shape, &child_path) else {
361 continue;
362 };
363 if element.many {
364 push_many(&mut object, &element.name, value);
365 } else {
366 object.insert(element.name.clone(), value);
367 }
368 }
369 Slot::Grouped(group, member) => {
370 let index = count(&object, &group.name);
371 let child_path = format!("{path}.{}[{index}].{}", group.name, member.name);
372 self.at(child, &child_path);
373 let inner = Shape {
374 elements: &group.children,
375 owner: shape.owner.clone(),
376 namespace: shape.namespace.clone(),
377 };
378 let Some(value) = self.element_value(child, member, &inner, &child_path) else {
379 continue;
380 };
381 let mut item = Map::new();
382 item.insert(member.name.clone(), value);
383 push_many(&mut object, &group.name, Value::Object(item));
384 }
385 }
386 }
387 if let Some(text_element) = text_element
388 && text_seen
389 && (!has_children || !text.trim().is_empty())
390 {
391 object.insert(text_element.name.clone(), Value::String(text));
392 }
393 Value::Object(object)
394 }
395
396 fn attributes(
397 &mut self,
398 node: Node<'_, '_>,
399 shape: &Shape<'m>,
400 path: &str,
401 object: &mut Map<String, Value>,
402 ) {
403 for attribute in node.attributes() {
404 let namespace = attribute.namespace();
405 if namespace == Some(XSI_NS) {
406 continue;
408 }
409 let element = shape.elements.iter().find(|e| {
410 e.representation == Representation::Attribute
411 && e.xml_name == attribute.name()
412 && namespace
413 == if e.explicit_namespace {
414 e.namespace.as_deref()
415 } else {
416 None
417 }
418 });
419 let Some(element) = element else {
420 let name = match namespace {
421 Some(ns) if ns == XML_NS => format!("xml:{}", attribute.name()),
422 Some(ns) => format!("{{{ns}}}{}", attribute.name()),
423 None => attribute.name().to_owned(),
424 };
425 self.issue(
426 IssueKind::UnknownAttribute,
427 path,
428 node,
429 format!("attribute `{name}` is not in the model; left out"),
430 );
431 continue;
432 };
433 let attr_path = format!("{path}.{}", element.name);
434 let value = if element.many {
435 Value::Array(
436 attribute
437 .value()
438 .split_whitespace()
439 .map(|token| self.primitive(token, element, &attr_path, node))
440 .collect(),
441 )
442 } else {
443 self.primitive(attribute.value(), element, &attr_path, node)
444 };
445 object.insert(element.name.clone(), value);
446 }
447 }
448
449 fn primitive(
451 &mut self,
452 text: &str,
453 element: &Element,
454 path: &str,
455 node: Node<'_, '_>,
456 ) -> Value {
457 if element.date_format {
458 return match datetime::from_compact(text.trim()) {
459 Some(converted) => Value::String(converted),
460 None => {
461 self.issue(
462 IssueKind::InvalidValue,
463 path,
464 node,
465 format!("`{text}` is not a timestamp (YYYYMMDDHHMMSS.UUUU[+|-ZZzz]); kept as it is"),
466 );
467 Value::String(text.to_owned())
468 }
469 };
470 }
471 let code = element.single_type().unwrap_or_default();
472 let invalid = |reader: &mut Self, what: &str| {
473 reader.issue(
474 IssueKind::InvalidValue,
475 path,
476 node,
477 format!("`{text}` is not a valid {what}; kept as text"),
478 );
479 Value::String(text.to_owned())
480 };
481 match code {
482 "boolean" => match text.trim() {
483 "true" => Value::Bool(true),
484 "false" => Value::Bool(false),
485 _ => invalid(self, "boolean"),
486 },
487 "integer" | "positiveInt" | "unsignedInt" | "integer64" => {
488 match text.trim().parse::<i64>() {
489 Ok(n) => Value::Number(n.into()),
490 Err(_) => invalid(self, "integer"),
491 }
492 }
493 "decimal" => match decimal(text.trim()) {
494 Some(n) => Value::Number(n),
495 None => invalid(self, "decimal"),
496 },
497 _ => Value::String(text.to_owned()),
498 }
499 }
500
501 fn slot(
503 &self,
504 shape: &Shape<'m>,
505 child: Node<'_, '_>,
506 parent_ns: Option<&str>,
507 ) -> Option<Slot<'m>> {
508 let name = child.tag_name().name();
509 let namespace = child.tag_name().namespace();
510 let fits = |element: &Element| {
511 element.representation != Representation::Attribute
512 && element.representation != Representation::Text
513 && !element.choice_group
514 && element.xml_name == name
515 && namespace == element.namespace.as_deref().or(parent_ns)
516 };
517 for element in shape.elements {
518 if element.choice_group {
519 if let Some(member) = element.children.iter().find(|m| fits(m)) {
520 return Some(Slot::Grouped(element, member));
521 }
522 } else if fits(element) {
523 return Some(Slot::Direct(element));
524 }
525 }
526 None
527 }
528
529 fn element_value(
531 &mut self,
532 node: Node<'_, '_>,
533 element: &'m Element,
534 context: &Shape<'m>,
535 path: &str,
536 ) -> Option<Value> {
537 if element.representation == Representation::Markup {
538 let mut out = String::new();
539 write_markup(node, node.tag_name().namespace(), &mut Vec::new(), &mut out);
540 return Some(Value::String(out));
541 }
542 let model: &'m Model = self.model;
543 let declared: Option<&'m TypeDef> = element
544 .single_type()
545 .and_then(|code| model.type_of(code, &context.owner))
546 .map(|t| &**t);
547 let stated = node
548 .attributes()
549 .find(|a| a.namespace() == Some(XSI_NS) && a.name() == "type")
550 .map(|a| a.value().to_owned());
551 let mut actual: Option<&'m TypeDef> = match &stated {
552 Some(qname) => {
553 let (prefix, local) = match qname.split_once(':') {
554 Some((prefix, local)) => (Some(prefix), local),
555 None => (None, qname.as_str()),
556 };
557 let namespace = node.lookup_namespace_uri(prefix);
558 match model.by_type_name(namespace, local) {
559 Some(found) => Some(&**found),
560 None => {
561 self.issue(
562 IssueKind::UnknownType,
563 path,
564 node,
565 format!("xsi:type `{qname}` is not a type of the model"),
566 );
567 None
568 }
569 }
570 }
571 None => None,
572 };
573 if actual.is_none()
574 && let Some(default) = &element.default_type
575 {
576 actual = model.type_of(default, &context.owner).map(|t| &**t);
577 }
578 if actual.is_none() && !element.children.is_empty() {
580 let shape = Shape {
581 elements: &element.children,
582 owner: context.owner.clone(),
583 namespace: element.namespace.clone(),
584 };
585 return Some(self.complex(node, &shape, path));
586 }
587 if actual.is_none() {
588 actual = declared;
589 }
590 let Some(actual) = actual else {
591 let candidates: Vec<&str> = element
592 .types
593 .iter()
594 .map(|t| t.rsplit('/').next().unwrap_or(t))
595 .collect();
596 self.issue(
597 IssueKind::MissingType,
598 path,
599 node,
600 format!(
601 "`{}` can be {}; without xsi:type its content cannot be read",
602 element.xml_name,
603 candidates.join(", ")
604 ),
605 );
606 return None;
607 };
608 if let Some(stated_type) = declared
609 && !model.derives_from(actual, &stated_type.url)
610 {
611 self.issue(
612 IssueKind::UnknownType,
613 path,
614 node,
615 format!("xsi:type `{}` is not a `{}`", actual.name, stated_type.name),
616 );
617 }
618 let shape = Shape::of(actual);
619 let mut value = self.complex(node, &shape, path);
620 let polymorphic = element.types.len() > 1;
623 let differs = declared.is_none_or(|d| d.url != actual.url);
624 if (stated.is_some() || polymorphic || differs)
625 && let Value::Object(object) = &mut value
626 {
627 let mut typed = Map::with_capacity(object.len() + 1);
628 typed.insert(TYPE_KEY.to_owned(), Value::String(actual.name.clone()));
629 typed.extend(std::mem::take(object));
630 *object = typed;
631 }
632 Some(value)
633 }
634}
635
636fn decimal(text: &str) -> Option<Number> {
639 if !is_decimal_text(text) {
640 return None;
641 }
642 if !text.contains('.')
643 && let Ok(n) = text.parse::<i64>()
644 {
645 return Some(n.into());
646 }
647 text.parse::<f64>()
648 .ok()
649 .filter(|n| n.is_finite())
650 .and_then(Number::from_f64)
651}
652
653fn is_decimal_text(text: &str) -> bool {
655 let digits = text.strip_prefix(['+', '-']).unwrap_or(text);
656 !digits.is_empty()
657 && digits.bytes().all(|b| b.is_ascii_digit() || b == b'.')
658 && digits.bytes().filter(|&b| b == b'.').count() <= 1
659 && digits.bytes().any(|b| b.is_ascii_digit())
660}
661
662fn count(object: &Map<String, Value>, name: &str) -> usize {
663 match object.get(name) {
664 Some(Value::Array(items)) => items.len(),
665 Some(_) => 1,
666 None => 0,
667 }
668}
669
670fn push_many(object: &mut Map<String, Value>, name: &str, value: Value) -> usize {
671 let entry = object
672 .entry(name.to_owned())
673 .or_insert_with(|| Value::Array(Vec::new()));
674 match entry {
675 Value::Array(items) => {
676 items.push(value);
677 items.len() - 1
678 }
679 other => {
680 let previous = std::mem::take(other);
681 *other = Value::Array(vec![previous, value]);
682 1
683 }
684 }
685}
686
687fn qualified(node: Node<'_, '_>) -> String {
688 match node.tag_name().namespace() {
689 Some(ns) => format!("{{{ns}}}{}", node.tag_name().name()),
690 None => node.tag_name().name().to_owned(),
691 }
692}
693
694fn write_markup(
697 node: Node<'_, '_>,
698 default_ns: Option<&str>,
699 declared: &mut Vec<(String, String)>,
700 out: &mut String,
701) {
702 let mark = declared.len();
703 let mut declarations = String::new();
704 let tag = name_in(
705 node,
706 node.tag_name().namespace(),
707 node.tag_name().name(),
708 default_ns,
709 declared,
710 &mut declarations,
711 false,
712 );
713 out.push('<');
714 out.push_str(&tag);
715 let mut attributes = String::new();
716 for attribute in node.attributes() {
717 let name = name_in(
718 node,
719 attribute.namespace(),
720 attribute.name(),
721 default_ns,
722 declared,
723 &mut declarations,
724 true,
725 );
726 attributes.push(' ');
727 attributes.push_str(&name);
728 attributes.push_str("=\"");
729 crate::write::escape_attribute(attribute.value(), &mut attributes);
730 attributes.push('"');
731 }
732 out.push_str(&declarations);
733 out.push_str(&attributes);
734 let mut children = node
735 .children()
736 .filter(|c| c.is_element() || c.is_text())
737 .peekable();
738 if children.peek().is_none() {
739 out.push_str("/>");
740 } else {
741 out.push('>');
742 for child in children {
743 if child.is_text() {
744 crate::write::escape_text(child.text().unwrap_or_default(), out);
745 } else {
746 write_markup(child, default_ns, declared, out);
747 }
748 }
749 out.push_str("</");
750 out.push_str(&tag);
751 out.push('>');
752 }
753 declared.truncate(mark);
754}
755
756fn name_in(
759 node: Node<'_, '_>,
760 namespace: Option<&str>,
761 local: &str,
762 default_ns: Option<&str>,
763 declared: &mut Vec<(String, String)>,
764 declarations: &mut String,
765 attribute: bool,
766) -> String {
767 let Some(namespace) = namespace else {
768 if !attribute && default_ns.is_some() {
769 if !declarations.contains("xmlns=\"\"") {
771 declarations.push_str(" xmlns=\"\"");
772 }
773 }
774 return local.to_owned();
775 };
776 if namespace == XML_NS {
777 return format!("xml:{local}");
778 }
779 if !attribute && Some(namespace) == default_ns {
780 return local.to_owned();
781 }
782 if let Some((prefix, _)) = declared.iter().rev().find(|(_, ns)| ns == namespace) {
783 return format!("{prefix}:{local}");
784 }
785 let prefix = node
786 .lookup_prefix(namespace)
787 .filter(|p| !p.is_empty())
788 .map(str::to_owned)
789 .unwrap_or_else(|| format!("ns{}", declared.len() + 1));
790 declarations.push_str(&format!(" xmlns:{prefix}=\""));
791 crate::write::escape_attribute(namespace, declarations);
792 declarations.push('"');
793 declared.push((prefix.clone(), namespace.to_owned()));
794 format!("{prefix}:{local}")
795}