#![expect(
clippy::disallowed_types,
reason = "the BMM `Any` monomorphization (`X_VERSIONED_OBJECT<Any>` on \
`OPENEHR_CONTENT_ITEM.item`) renders as serde_json::Value in the generated RM \
model, so this runtime must implement the codec traits for it (#1694)"
)]
use quick_xml::Reader;
use quick_xml::Writer;
use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
pub const XSI_NS: &str = "http://www.w3.org/2001/XMLSchema-instance";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Namespace {
V1,
V2,
}
impl Namespace {
#[must_use]
pub const fn uri(self) -> &'static str {
match self {
Namespace::V1 => "http://schemas.openehr.org/v1",
Namespace::V2 => "http://schemas.openehr.org/v2",
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum XmlError {
#[error(transparent)]
Write(#[from] quick_xml::Error),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Utf8(#[from] std::string::FromUtf8Error),
#[error("xml parse error: {0}")]
Parse(String),
#[error(
"{location} is missing mandatory child <{child}> ({owner}.{child}){}",
missing_child_remedy(.location, .owner, .child)
)]
MissingChild {
location: Box<Location>,
owner: String,
child: String,
},
#[error("{location} is missing mandatory attribute {attribute} ({owner}.{attribute})")]
MissingAttribute {
location: Box<Location>,
owner: String,
attribute: String,
},
#[error("{location}: {detail}")]
Content {
location: Box<Location>,
detail: String,
},
#[error("<{child}> is not a child element {owner} declares")]
UndeclaredChild {
owner: String,
child: String,
},
#[error("<{element}> = {value:?} under {owner} is a tool extension no schema defines")]
UnsupportedExtension {
owner: String,
element: String,
value: String,
},
#[error("the root element is <{found}>, the schema publishes <{expected}>")]
UnexpectedRoot {
expected: String,
found: String,
},
#[error("xml parse error: {message}")]
ParseSource {
message: String,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
}
impl XmlError {
pub fn parse_source(
message: impl Into<String>,
source: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self::ParseSource {
message: message.into(),
source: Box::new(source),
}
}
}
pub struct XmlWriter {
w: Writer<Vec<u8>>,
pending_root_ns: Option<Namespace>,
}
impl std::fmt::Debug for XmlWriter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("XmlWriter")
.field("pending_root_ns", &self.pending_root_ns)
.finish_non_exhaustive()
}
}
impl XmlWriter {
#[must_use]
pub fn new() -> Self {
Self {
w: Writer::new(Vec::new()),
pending_root_ns: None,
}
}
pub fn set_root_namespace(&mut self, ns: Namespace) {
self.pending_root_ns = Some(ns);
}
pub fn write_start(&mut self, mut e: BytesStart<'_>) -> Result<(), XmlError> {
if let Some(ns) = self.pending_root_ns.take() {
e.push_attribute(("xmlns", ns.uri()));
e.push_attribute(("xmlns:xsi", XSI_NS));
}
self.w.write_event(Event::Start(e))?;
Ok(())
}
pub fn write_end(&mut self, tag: &str) -> Result<(), XmlError> {
self.w.write_event(Event::End(BytesEnd::new(tag)))?;
Ok(())
}
pub fn write_text_element(&mut self, tag: &str, text: &str) -> Result<(), XmlError> {
let mut start = BytesStart::new(tag);
if let Some(ns) = self.pending_root_ns.take() {
start.push_attribute(("xmlns", ns.uri()));
start.push_attribute(("xmlns:xsi", XSI_NS));
}
self.w.write_event(Event::Start(start))?;
self.w.write_event(Event::Text(BytesText::new(text)))?;
self.write_end(tag)
}
pub fn write_text(&mut self, text: &str) -> Result<(), XmlError> {
self.w.write_event(Event::Text(BytesText::new(text)))?;
Ok(())
}
pub fn write_kv_element(&mut self, tag: &str, id: &str, text: &str) -> Result<(), XmlError> {
let mut start = BytesStart::new(tag);
start.push_attribute(("id", id));
self.w.write_event(Event::Start(start))?;
self.w.write_event(Event::Text(BytesText::new(text)))?;
self.write_end(tag)
}
pub fn into_string(self) -> Result<String, XmlError> {
Ok(String::from_utf8(self.w.into_inner())?)
}
}
impl Default for XmlWriter {
fn default() -> Self {
Self::new()
}
}
pub fn to_xml<T: ToXml + ?Sized>(
value: &T,
root_tag: &str,
ns: Namespace,
) -> Result<String, XmlError> {
let mut w = XmlWriter::new();
w.set_root_namespace(ns);
value.write_xml(&mut w, root_tag, None)?;
w.into_string()
}
pub fn to_xml_declared<T: ToXml + ?Sized>(
value: &T,
root_tag: &str,
declared: &str,
ns: Namespace,
) -> Result<String, XmlError> {
let mut w = XmlWriter::new();
w.set_root_namespace(ns);
value.write_xml(&mut w, root_tag, Some(declared))?;
w.into_string()
}
pub trait ToXml {
fn xml_type_name(&self) -> &'static str {
""
}
fn write_xml(
&self,
w: &mut XmlWriter,
tag: &str,
declared: Option<&str>,
) -> Result<(), XmlError>;
}
impl ToXml for String {
fn write_xml(&self, w: &mut XmlWriter, tag: &str, _d: Option<&str>) -> Result<(), XmlError> {
w.write_text_element(tag, self)
}
}
impl ToXml for str {
fn write_xml(&self, w: &mut XmlWriter, tag: &str, _d: Option<&str>) -> Result<(), XmlError> {
w.write_text_element(tag, self)
}
}
macro_rules! impl_to_xml_display {
($($t:ty),*) => {$(
impl ToXml for $t {
fn write_xml(&self, w: &mut XmlWriter, tag: &str, _d: Option<&str>)
-> Result<(), XmlError>
{
w.write_text_element(tag, &self.to_string())
}
}
)*};
}
impl_to_xml_display!(bool, i32, i64, u8, char, f32);
fn xsd_double_lexical(v: f64) -> String {
if v.is_nan() {
return "NaN".to_owned();
}
if v.is_infinite() {
return if v.is_sign_negative() { "-INF" } else { "INF" }.to_owned();
}
if v.fract() == 0.0 {
format!("{v:.1}")
} else {
v.to_string()
}
}
impl ToXml for f64 {
fn write_xml(&self, w: &mut XmlWriter, tag: &str, _d: Option<&str>) -> Result<(), XmlError> {
w.write_text_element(tag, &xsd_double_lexical(*self))
}
}
impl<T: ToXml> ToXml for Box<T> {
fn xml_type_name(&self) -> &'static str {
(**self).xml_type_name()
}
fn write_xml(&self, w: &mut XmlWriter, tag: &str, d: Option<&str>) -> Result<(), XmlError> {
(**self).write_xml(w, tag, d)
}
}
impl ToXml for uuid::Uuid {
fn write_xml(&self, w: &mut XmlWriter, tag: &str, _d: Option<&str>) -> Result<(), XmlError> {
w.write_text_element(tag, &self.to_string())
}
}
impl ToXml for serde_json::Value {
fn write_xml(&self, w: &mut XmlWriter, tag: &str, _d: Option<&str>) -> Result<(), XmlError> {
w.write_text_element(tag, &self.to_string())
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct XmlAny {
attrs: Vec<(String, String)>,
content: Vec<XmlAnyNode>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum XmlAnyNode {
Text(String),
Element(String, XmlAny),
}
impl XmlAny {
#[must_use]
pub fn attrs(&self) -> &[(String, String)] {
&self.attrs
}
#[must_use]
pub fn attr(&self, key: &str) -> Option<&str> {
self.attrs
.iter()
.find(|(k, _)| k == key)
.map(|(_, v)| v.as_str())
}
#[must_use]
pub fn xsi_type(&self) -> Option<&str> {
self.attrs
.iter()
.find(|(k, _)| k == "xsi:type" || (k.ends_with(":type") && k.contains("xsi")))
.map(|(_, v)| v.rsplit(':').next().unwrap_or(v))
}
#[must_use]
pub fn content(&self) -> &[XmlAnyNode] {
&self.content
}
pub fn children(&self) -> impl Iterator<Item = (&str, &XmlAny)> {
self.content.iter().filter_map(|n| match n {
XmlAnyNode::Element(name, child) => Some((name.as_str(), child)),
XmlAnyNode::Text(_) => None,
})
}
pub fn children_named<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a XmlAny> {
self.children()
.filter_map(move |(n, c)| (n == name).then_some(c))
}
#[must_use]
pub fn child(&self, name: &str) -> Option<&XmlAny> {
self.children().find(|(n, _)| *n == name).map(|(_, c)| c)
}
#[must_use]
pub fn text(&self) -> String {
self.content
.iter()
.filter_map(|n| match n {
XmlAnyNode::Text(t) => Some(t.as_str()),
XmlAnyNode::Element(_, _) => None,
})
.collect()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.attrs.is_empty() && self.content.is_empty()
}
}
impl ToXml for openehr_base::serde_support::OpenSubtype {
fn write_xml(
&self,
w: &mut XmlWriter,
tag: &str,
declared: Option<&str>,
) -> Result<(), XmlError> {
if !self.members().is_empty() {
return Err(XmlError::Parse(format!(
"no canonical-XML mapping exists for the scheme-defined `{}` members",
self.type_name()
)));
}
let mut e = BytesStart::new(tag);
if declared != Some(self.type_name()) {
e.push_attribute(("xsi:type", self.type_name()));
}
w.write_start(e)?;
w.write_end(tag)
}
}
impl FromXml for openehr_base::serde_support::OpenSubtype {
fn from_xml(reader: &mut XmlReader, start: &StartTag) -> Result<Self, XmlError> {
let type_name = start
.attrs
.iter()
.find(|(k, _)| k == "xsi:type" || (k.ends_with(":type") && k.contains("xsi")))
.map_or("ACCESS_CONTROL_SETTINGS", |(_, v)| {
v.rsplit(':').next().unwrap_or(v)
})
.to_owned();
loop {
match reader.read()? {
XmlEvent::Start(_) => {
return Err(XmlError::Parse(format!(
"no canonical-XML mapping exists for the scheme-defined `{type_name}` members"
)));
}
XmlEvent::Text(t) if !t.trim().is_empty() => {
return Err(XmlError::Parse(format!(
"no canonical-XML mapping exists for scheme-defined `{type_name}` content"
)));
}
XmlEvent::Text(_) => {}
XmlEvent::End => break,
XmlEvent::Eof => {
return Err(XmlError::Parse(
"unexpected EOF in an open-subtype element".into(),
));
}
}
}
openehr_base::serde_support::OpenSubtype::new(type_name, serde_json::Map::new())
.map_err(|e| XmlError::parse_source("constructing the open-subtype value", e))
}
}
impl ToXml for XmlAny {
fn write_xml(&self, w: &mut XmlWriter, tag: &str, _d: Option<&str>) -> Result<(), XmlError> {
let mut e = BytesStart::new(tag);
for (k, v) in &self.attrs {
e.push_attribute((k.as_str(), v.as_str()));
}
w.write_start(e)?;
for node in &self.content {
match node {
XmlAnyNode::Text(t) => w.write_text(t)?,
XmlAnyNode::Element(name, child) => child.write_xml(w, name, None)?,
}
}
w.write_end(tag)
}
}
#[derive(Debug, Clone)]
pub struct StartTag {
pub name: String,
pub attrs: Vec<(String, String)>,
}
impl StartTag {
#[must_use]
pub fn attr(&self, key: &str) -> Option<&str> {
self.attrs
.iter()
.find(|(k, _)| k == key)
.map(|(_, v)| v.as_str())
}
#[must_use]
pub fn xsi_type(&self) -> Option<&str> {
self.attrs
.iter()
.find(|(k, _)| k == "xsi:type" || (k.ends_with(":type") && k.contains("xsi")))
.map(|(_, v)| v.rsplit(':').next().unwrap_or(v))
}
}
#[derive(Debug)]
pub enum XmlEvent {
Start(StartTag),
End,
Text(String),
Eof,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Location {
pub element: String,
pub xsi_type: Option<String>,
pub line: usize,
pub column: usize,
pub path: String,
}
impl std::fmt::Display for Location {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "element <{}", self.element)?;
if let Some(t) = &self.xsi_type {
write!(f, " xsi:type=\"{t}\"")?;
}
write!(
f,
"> at line {}, column {} ({})",
self.line, self.column, self.path
)
}
}
fn missing_child_remedy(location: &Location, owner: &str, child: &str) -> &'static str {
if location.element == "symbol" && owner == "DV_CODED_TEXT" && child == "value" {
". Add <value/> (empty is valid; the rubric is resolved from term_definitions; \
the ITS-XML profile schema requires the element on an ordinal symbol, see #3401)"
} else {
""
}
}
struct Frame {
element: String,
xsi_type: Option<String>,
offset: usize,
ordinal: u32,
children_seen: std::collections::BTreeMap<String, u32>,
namespaces: Vec<(String, String)>,
}
struct ClosedElement {
element: String,
xsi_type: Option<String>,
offset: usize,
ordinal: u32,
ancestors: usize,
}
pub struct XmlReader<'a> {
r: Reader<&'a [u8]>,
src: &'a str,
frames: Vec<Frame>,
last_closed: Option<ClosedElement>,
depth: u32,
}
pub const MAX_DEPTH: u32 = 256;
impl std::fmt::Debug for XmlReader<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("XmlReader").finish_non_exhaustive()
}
}
impl<'a> XmlReader<'a> {
#[must_use]
pub fn new(xml: &'a str) -> Self {
let mut r = Reader::from_str(xml);
r.config_mut().expand_empty_elements = true;
Self {
r,
src: xml,
frames: Vec::new(),
last_closed: None,
depth: 0,
}
}
fn open(&mut self, start: &StartTag, offset: usize) {
let ordinal = match self.frames.last_mut() {
Some(parent) => {
let seen = parent.children_seen.entry(start.name.clone()).or_insert(0);
*seen = seen.saturating_add(1);
*seen
}
None => 1,
};
self.frames.push(Frame {
element: start.name.clone(),
xsi_type: start.xsi_type().map(str::to_owned),
offset,
ordinal,
children_seen: std::collections::BTreeMap::new(),
namespaces: start
.attrs
.iter()
.filter_map(|(k, v)| Some((k.strip_prefix("xmlns:")?.to_owned(), v.clone())))
.collect(),
});
}
fn namespace_uri(&self, prefix: &str) -> Option<&str> {
self.frames
.iter()
.rev()
.flat_map(|frame| frame.namespaces.iter())
.find(|(p, _)| p == prefix)
.map(|(_, uri)| uri.as_str())
}
fn close(&mut self) {
if let Some(frame) = self.frames.pop() {
self.last_closed = Some(ClosedElement {
element: frame.element,
xsi_type: frame.xsi_type,
offset: frame.offset,
ordinal: frame.ordinal,
ancestors: self.frames.len(),
});
}
}
fn located(&self) -> Location {
let Some(closed) = &self.last_closed else {
return Location {
element: "document".to_owned(),
xsi_type: None,
line: 1,
column: 1,
path: "/".to_owned(),
};
};
let mut path = String::new();
for (i, frame) in self.frames.iter().take(closed.ancestors).enumerate() {
path.push('/');
path.push_str(&frame.element);
if i > 0 {
path.push('[');
path.push_str(&frame.ordinal.to_string());
path.push(']');
}
}
path.push('/');
path.push_str(&closed.element);
if closed.ancestors > 0 {
path.push('[');
path.push_str(&closed.ordinal.to_string());
path.push(']');
}
let (line, column) = line_col(self.src, closed.offset);
Location {
element: closed.element.clone(),
xsi_type: closed.xsi_type.clone(),
line,
column,
path,
}
}
#[must_use]
pub fn missing_child(&self, owner: &str, child: &str) -> XmlError {
XmlError::MissingChild {
location: Box::new(self.located()),
owner: owner.to_owned(),
child: child.to_owned(),
}
}
#[must_use]
pub fn missing_attribute(&self, owner: &str, attribute: &str) -> XmlError {
XmlError::MissingAttribute {
location: Box::new(self.located()),
owner: owner.to_owned(),
attribute: attribute.to_owned(),
}
}
#[must_use]
pub fn invalid_content(&self, detail: impl Into<String>) -> XmlError {
XmlError::Content {
location: Box::new(self.located()),
detail: detail.into(),
}
}
pub fn read(&mut self) -> Result<XmlEvent, XmlError> {
loop {
let at = usize::try_from(self.r.buffer_position()).unwrap_or(usize::MAX);
let ev = self
.r
.read_event()
.map_err(|e| XmlError::parse_source("reading the next XML event", e))?;
match ev {
Event::Start(e) => {
self.depth = self.depth.saturating_add(1);
if self.depth > MAX_DEPTH {
return Err(XmlError::Parse(format!(
"element nesting exceeds the {MAX_DEPTH}-level limit"
)));
}
let start = to_start_tag(&e)?;
self.open(&start, at);
return Ok(XmlEvent::Start(start));
}
Event::End(_) => {
self.depth = self.depth.saturating_sub(1);
self.close();
return Ok(XmlEvent::End);
}
Event::Text(t) => {
let s = quick_xml::escape::unescape(t.as_ref())
.map_err(|e| XmlError::parse_source("unescaping element text", e))?;
return Ok(XmlEvent::Text(s.into_owned()));
}
Event::Eof => return Ok(XmlEvent::Eof),
Event::GeneralRef(e) => {
if let Some(c) = e
.resolve_char_ref()
.map_err(|e| XmlError::parse_source("resolving a character reference", e))?
{
return Ok(XmlEvent::Text(c.to_string()));
}
let name: &str = e.as_ref();
let resolved = match name {
"amp" => "&",
"lt" => "<",
"gt" => ">",
"apos" => "'",
"quot" => "\"",
other => {
return Err(XmlError::Parse(format!("unknown entity &{other};")));
}
};
return Ok(XmlEvent::Text(resolved.to_string()));
}
Event::DocType(_) => {
return Err(XmlError::Parse(
"a DOCTYPE declaration is not accepted".into(),
));
}
_ => {}
}
}
}
pub fn skip_element(&mut self) -> Result<(), XmlError> {
let mut depth = 1i32;
while depth > 0 {
match self.read()? {
XmlEvent::Start(_) => depth += 1,
XmlEvent::End => depth -= 1,
XmlEvent::Eof => return Err(XmlError::Parse("unexpected EOF in element".into())),
XmlEvent::Text(_) => {}
}
}
Ok(())
}
}
fn to_start_tag(e: &BytesStart<'_>) -> Result<StartTag, XmlError> {
let qname = e.name();
let raw = qname.as_ref();
let name = raw.rsplit(':').next().unwrap_or(raw).to_string();
let mut attrs = Vec::new();
for a in e.attributes() {
let a = a.map_err(|e| XmlError::parse_source("reading a start-tag attribute", e))?;
let k = a.key.as_ref().to_owned();
let v = quick_xml::escape::unescape(a.value.as_ref())
.map_err(|e| XmlError::parse_source("unescaping an attribute value", e))?
.into_owned();
attrs.push((k, v));
}
Ok(StartTag { name, attrs })
}
fn line_col(src: &str, offset: usize) -> (usize, usize) {
let head = src.get(..offset.min(src.len())).unwrap_or(src);
let line = head.matches('\n').count() + 1;
let column = head.rsplit('\n').next().map_or(0, |l| l.chars().count()) + 1;
(line, column)
}
pub fn from_xml<T: FromXml>(xml: &str) -> Result<T, XmlError> {
let mut reader = XmlReader::new(xml);
loop {
match reader.read()? {
XmlEvent::Start(s) => return T::from_xml(&mut reader, &s),
XmlEvent::Eof => return Err(XmlError::Parse("no root element".into())),
_ => {}
}
}
}
pub fn from_xml_rooted<T: FromXml>(xml: &str, root: &str) -> Result<T, XmlError> {
let mut reader = XmlReader::new(xml);
loop {
match reader.read()? {
XmlEvent::Start(s) if s.name == root => return T::from_xml(&mut reader, &s),
XmlEvent::Start(s) => {
return Err(XmlError::UnexpectedRoot {
expected: root.to_owned(),
found: s.name,
});
}
XmlEvent::Eof => return Err(XmlError::Parse("no root element".into())),
_ => {}
}
}
}
pub fn write_value_wrapped(w: &mut XmlWriter, tag: &str, value: &str) -> Result<(), XmlError> {
w.write_start(BytesStart::new(tag))?;
w.write_text_element("value", value)?;
w.write_end(tag)
}
pub fn read_value_wrapped(reader: &mut XmlReader, start: &StartTag) -> Result<String, XmlError> {
let mut value = None;
loop {
match reader.read()? {
XmlEvent::Start(child) if child.name == "value" => {
value = Some(String::from_xml(reader, &child)?);
}
XmlEvent::Start(child) => {
return Err(XmlError::UndeclaredChild {
owner: format!("<{}>", start.name),
child: child.name,
});
}
XmlEvent::End => break,
XmlEvent::Text(_) => {}
XmlEvent::Eof => return Err(XmlError::Parse("EOF in an OBJECT_ID element".into())),
}
}
value.ok_or_else(|| reader.missing_child(&start.name, "value"))
}
pub fn read_false_extension(
reader: &mut XmlReader,
start: &StartTag,
owner: &str,
) -> Result<(), XmlError> {
let value = String::from_xml(reader, start)?;
if value.trim() == "false" {
return Ok(());
}
Err(XmlError::UnsupportedExtension {
owner: owner.to_owned(),
element: start.name.clone(),
value: value.trim().to_owned(),
})
}
pub trait FromXml: Sized {
fn from_xml(reader: &mut XmlReader, start: &StartTag) -> Result<Self, XmlError>;
}
impl FromXml for String {
fn from_xml(reader: &mut XmlReader, start: &StartTag) -> Result<Self, XmlError> {
let mut text = String::new();
loop {
match reader.read()? {
XmlEvent::Text(t) => text.push_str(&t),
XmlEvent::End => break,
XmlEvent::Start(child) => {
return Err(XmlError::UndeclaredChild {
owner: format!("<{}>", start.name),
child: child.name,
});
}
XmlEvent::Eof => return Err(XmlError::Parse("EOF in text element".into())),
}
}
Ok(text)
}
}
macro_rules! impl_from_xml_parse {
($($t:ty),*) => {$(
impl FromXml for $t {
fn from_xml(reader: &mut XmlReader, start: &StartTag) -> Result<Self, XmlError> {
let s = String::from_xml(reader, start)?;
s.trim().parse::<$t>().map_err(|e| {
XmlError::parse_source(
format!("{s:?} is not a valid {}", stringify!($t)),
e,
)
})
}
}
)*};
}
impl_from_xml_parse!(bool, i32, i64, u8, f32, f64, char);
impl FromXml for uuid::Uuid {
fn from_xml(reader: &mut XmlReader, start: &StartTag) -> Result<Self, XmlError> {
let s = String::from_xml(reader, start)?;
uuid::Uuid::parse_str(s.trim())
.map_err(|e| XmlError::parse_source(format!("{s:?} is not a valid UUID"), e))
}
}
impl<T: FromXml> FromXml for Box<T> {
fn from_xml(reader: &mut XmlReader, start: &StartTag) -> Result<Self, XmlError> {
Ok(Box::new(T::from_xml(reader, start)?))
}
}
impl FromXml for serde_json::Value {
fn from_xml(reader: &mut XmlReader, _start: &StartTag) -> Result<Self, XmlError> {
reader.skip_element()?;
Ok(serde_json::Value::Null)
}
}
impl FromXml for XmlAny {
fn from_xml(reader: &mut XmlReader, start: &StartTag) -> Result<Self, XmlError> {
let mut attrs = start.attrs.clone();
let type_prefix = attrs
.iter()
.find(|(k, _)| k == "xsi:type" || (k.ends_with(":type") && k.contains("xsi")))
.and_then(|(_, v)| v.split_once(':'))
.map(|(prefix, _)| format!("xmlns:{prefix}"));
if let Some(key) = type_prefix
&& start.attr(&key).is_none()
&& let Some(uri) = key
.strip_prefix("xmlns:")
.and_then(|prefix| reader.namespace_uri(prefix))
{
attrs.push((key.clone(), uri.to_owned()));
}
let mut any = XmlAny {
attrs,
content: Vec::new(),
};
loop {
match reader.read()? {
XmlEvent::Text(t) => match any.content.last_mut() {
Some(XmlAnyNode::Text(prev)) => prev.push_str(&t),
_ => any.content.push(XmlAnyNode::Text(t)),
},
XmlEvent::Start(child) => {
let sub = XmlAny::from_xml(reader, &child)?;
any.content.push(XmlAnyNode::Element(child.name, sub));
}
XmlEvent::End => break,
XmlEvent::Eof => {
return Err(XmlError::Parse(
"unexpected EOF in xs:anyType element".into(),
));
}
}
}
Ok(any)
}
}
#[cfg(test)]
mod real_lexical_tests {
use super::xsd_double_lexical;
#[test]
fn real_values_take_the_xsd_double_lexical_form() {
assert_eq!(xsd_double_lexical(120.0), "120.0");
assert_eq!(xsd_double_lexical(0.0), "0.0");
assert_eq!(xsd_double_lexical(-0.0), "-0.0");
assert_eq!(xsd_double_lexical(-7.0), "-7.0");
assert_eq!(xsd_double_lexical(5.66), "5.66");
assert_eq!(
xsd_double_lexical(32.299_869_242_485_19),
"32.29986924248519"
);
assert_eq!(xsd_double_lexical(f64::INFINITY), "INF");
assert_eq!(xsd_double_lexical(f64::NEG_INFINITY), "-INF");
assert_eq!(xsd_double_lexical(f64::NAN), "NaN");
assert_ne!(xsd_double_lexical(f64::INFINITY), f64::INFINITY.to_string());
}
#[test]
fn every_emitted_lexeme_parses_back() {
for v in [
120.0_f64,
-0.0,
5.66,
32.299_869_242_485_19,
1e21,
1e-7,
f64::MAX,
f64::MIN_POSITIVE,
f64::INFINITY,
f64::NEG_INFINITY,
] {
let text = xsd_double_lexical(v);
let back: f64 = text
.parse()
.unwrap_or_else(|e| panic!("{text:?} does not parse back: {e}"));
assert_eq!(back.to_bits(), v.to_bits(), "{text:?}");
}
assert!(
xsd_double_lexical(f64::NAN)
.parse::<f64>()
.is_ok_and(f64::is_nan)
);
}
}
#[cfg(test)]
mod xml_any_namespace_tests {
use super::{XmlAny, from_xml};
#[test]
fn an_xsi_type_prefix_bound_on_an_ancestor_travels_with_the_subtree() {
let xml = "<outer xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" \
xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">\
<item xsi:type=\"xsd:string\">archetype_id/value</item></outer>";
let outer: XmlAny = from_xml(xml).expect("well-formed");
let item = outer.child("item").expect("the item");
assert_eq!(item.attr("xsi:type"), Some("xsd:string"));
assert_eq!(
item.attr("xmlns:xsd"),
Some("http://www.w3.org/2001/XMLSchema")
);
}
}