Skip to main content

fml_xml/
lib.rs

1//! XML for FHIR logical models.
2//!
3//! FHIR maps run on JSON. Logical models whose instances are XML — CDA is the best known — say in
4//! their StructureDefinitions how the XML looks: namespaces and names, which elements are
5//! attributes or text, where `xsi:type` gives a type, which elements group others that appear
6//! directly in the parent. This crate reads such XML into the JSON of the model, so a map can
7//! run over it, and writes JSON of the model back as XML. It knows nothing about any particular
8//! model; everything comes from the definitions.
9//!
10//! ```text
11//! <value xsi:type="PQ" value="120" unit="mm[Hg]"/>   ⇄   "value": { "@type": "PQ", "value": 120, "unit": "mm[Hg]" }
12//! ```
13//!
14//! The JSON form:
15//! - property names are the model's element names (`sdtcCategory`, not `sdtc:category`);
16//! - attributes are primitives: `boolean` and numbers as JSON booleans and numbers, a repeating
17//!   attribute (`use="H WP"`) as an array;
18//! - text content is the `xmlText` element, markup (narrative) a string of XML;
19//! - a value whose type `xsi:type` gives, or whose type differs from the element's, names it in
20//!   `"@type"` (the model's type name, `IVL_TS`);
21//! - a choice group is an array of one-property objects, in document order: an address is
22//!   `"item": [{ "streetAddressLine": … }, { "city": … }]`;
23//! - dates and times declared with a date format are read into the FHIR lexical form at the
24//!   precision written (`20200115093000-0500` → `2020-01-15T09:30:00-05:00`) and written back the
25//!   same.
26//!
27//! Reading also records where each element was (line and column) by its path in the JSON, the
28//! path the engine's debug trace uses, and reports what it could not read into the model instead
29//! of dropping it silently. [`compare`] tells whether two documents say the same.
30
31mod compare;
32mod datetime;
33mod model;
34mod read;
35mod write;
36
37pub use compare::{Difference, compare, compare_fragments};
38pub use datetime::{from_compact, to_compact};
39pub use model::{Element, Model, Representation, Root, TypeDef};
40pub use read::{Document, ReadOptions, read, read_with};
41pub use write::{Written, write};
42
43/// The property that names the type of a value in the JSON form.
44pub const TYPE_KEY: &str = "@type";
45
46/// The XML Schema instance namespace (`xsi:type`).
47pub const XSI_NS: &str = "http://www.w3.org/2001/XMLSchema-instance";
48
49/// The `xml:` namespace.
50pub const XML_NS: &str = "http://www.w3.org/XML/1998/namespace";
51
52/// A line and column in the XML, both from 1.
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub struct Position {
55    pub line: u32,
56    pub column: u32,
57}
58
59impl std::fmt::Display for Position {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        write!(f, "{}:{}", self.line, self.column)
62    }
63}
64
65/// XML that cannot be read or written at all.
66#[derive(Debug, Clone, PartialEq, Eq)]
67pub struct Error {
68    pub message: String,
69    pub position: Option<Position>,
70}
71
72impl std::fmt::Display for Error {
73    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74        match self.position {
75            Some(position) => write!(f, "{position}: {}", self.message),
76            None => f.write_str(&self.message),
77        }
78    }
79}
80
81impl std::error::Error for Error {}
82
83/// What kind of content could not be carried over.
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
85pub enum IssueKind {
86    /// An element the model does not have.
87    UnknownElement,
88    /// An attribute the model does not have.
89    UnknownAttribute,
90    /// A type (`xsi:type`, `"@type"`) the model does not have, or one that does not fit.
91    UnknownType,
92    /// A polymorphic element without a type.
93    MissingType,
94    /// A value that does not fit its type, kept as text.
95    InvalidValue,
96    /// Text where the model has none.
97    UnexpectedText,
98    /// More values than the element allows.
99    TooMany,
100}
101
102/// Content left out or changed while reading or writing.
103#[derive(Debug, Clone, PartialEq, Eq)]
104pub struct Issue {
105    pub kind: IssueKind,
106    /// The JSON path of the element it concerns.
107    pub path: String,
108    /// Where in the XML, when reading.
109    pub position: Option<Position>,
110    pub message: String,
111}
112
113impl std::fmt::Display for Issue {
114    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        match self.position {
116            Some(position) => write!(f, "{position} {}: {}", self.path, self.message),
117            None => write!(f, "{}: {}", self.path, self.message),
118        }
119    }
120}