use std::{borrow::Cow, collections::HashMap};
use quick_xml::{
NsReader, XmlVersion,
escape::unescape,
events::{BytesStart, Event},
name::{QName, ResolveResult},
};
use crate::{
MultipleEMLErrors,
error::{EMLError, EMLErrorKind, EMLResultExt},
io::QualifiedName,
utils::{StringValue, StringValueData},
};
pub trait EMLRead {
fn parse_eml(input: &str, parsing_mode: EMLParsingMode) -> EMLReadResult<Self>
where
Self: Sized;
}
#[must_use]
pub enum EMLReadResult<T> {
Ok(T, Vec<EMLError>),
Err(EMLError),
}
impl<T> EMLReadResult<T> {
pub fn errors(&self) -> &[EMLError] {
match self {
EMLReadResult::Ok(_, errors) => errors,
EMLReadResult::Err(EMLError::Multiple(MultipleEMLErrors { errors })) => errors,
EMLReadResult::Err(err) => std::slice::from_ref(err),
}
}
pub fn ok(self) -> Result<T, EMLError> {
self.into()
}
pub fn ok_with_errors(self) -> Result<(T, Vec<EMLError>), EMLError> {
self.into()
}
pub fn unwrap(self) -> T {
self.ok().unwrap()
}
pub fn expect(self, msg: &str) -> T {
self.ok().expect(msg)
}
}
impl<T> From<EMLReadResult<T>> for Result<T, EMLError> {
fn from(value: EMLReadResult<T>) -> Self {
match value {
EMLReadResult::Ok(doc, _) => Ok(doc),
EMLReadResult::Err(e) => Err(e),
}
}
}
impl<T> From<EMLReadResult<T>> for Result<(T, Vec<EMLError>), EMLError> {
fn from(value: EMLReadResult<T>) -> Self {
match value {
EMLReadResult::Ok(doc, errors) => Ok((doc, errors)),
EMLReadResult::Err(e) => Err(e),
}
}
}
impl<T> EMLRead for T
where
T: EMLReadElement + 'static,
{
fn parse_eml(input: &str, parsing_mode: EMLParsingMode) -> EMLReadResult<Self>
where
Self: Sized + 'static,
{
let mut reader = EMLReader::init_from_str(input, parsing_mode);
let res = reader.with_next_element(|r| T::read_eml_element(r));
let e = match res {
Ok(doc) => return EMLReadResult::Ok(doc, reader.errors),
Err(e) => e,
};
if reader.errors.is_empty() {
EMLReadResult::Err(e)
} else {
EMLReadResult::Err(EMLError::from_vec_with_additional(reader.errors, e))
}
}
}
pub(crate) trait EMLReadElement {
fn read_eml_element<'a, 'b>(elem: &mut EMLElementReader<'a, 'b>) -> Result<Self, EMLError>
where
Self: Sized + 'static;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Span {
pub start: u64,
pub end: u64,
}
impl Span {
pub fn new(start: u64, end: u64) -> Span {
Span { start, end }
}
}
impl std::fmt::Display for Span {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} until {}", self.start, self.end)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EMLParsingMode {
Strict,
StrictFallback,
Loose,
}
impl EMLParsingMode {
pub fn is_strict(&self) -> bool {
matches!(self, EMLParsingMode::Strict)
}
}
pub(crate) struct EMLReader<'a> {
inner: NsReader<&'a [u8]>,
xml_version: XmlVersion,
parsing_mode: EMLParsingMode,
errors: Vec<EMLError>,
}
impl<'a> EMLReader<'a> {
pub fn init_from_str(data: &'a str, parsing_mode: EMLParsingMode) -> EMLReader<'a> {
Self::from_reader(NsReader::from_str(data), parsing_mode)
}
pub fn from_reader(reader: NsReader<&'a [u8]>, parsing_mode: EMLParsingMode) -> EMLReader<'a> {
EMLReader {
inner: reader,
xml_version: XmlVersion::default(),
parsing_mode,
errors: Vec::new(),
}
}
fn next(&mut self) -> Result<(Event<'a>, Span), EMLError> {
let span_start = self.inner.buffer_position();
let event = self.inner.read_event();
let event = match event {
Ok(evt) => evt,
Err(xml_err) => {
let error_pos = self.inner.error_position();
if error_pos == 0 {
return Err(xml_err)
.with_span(Span::new(span_start, self.inner.buffer_position()));
} else {
return Err(xml_err).with_span(Span::new(error_pos, error_pos));
}
}
};
let span = Span::new(span_start, self.inner.buffer_position());
if let Event::Decl(d) = &event {
self.xml_version = d.xml_version().with_span(span)?;
}
Ok((event, span))
}
pub fn next_element<'tmp>(&'tmp mut self) -> Result<EMLElementReader<'tmp, 'a>, EMLError> {
loop {
match self.next()? {
(Event::Start(start), span) => {
return Ok(EMLElementReader::from_start(self, start, false, span));
}
(Event::Empty(start), span) => {
return Ok(EMLElementReader::from_start(self, start, true, span));
}
(Event::Eof, span) => {
return Err(EMLErrorKind::UnexpectedEof).with_span(span);
}
_other => {
}
}
}
}
pub fn with_next_element<R>(
&mut self,
f: impl for<'d, 'e> FnOnce(&mut EMLElementReader<'d, 'e>) -> Result<R, EMLError>,
) -> Result<R, EMLError> {
let mut root = self.next_element()?;
f(&mut root)
}
}
pub(crate) struct EMLElementReader<'r, 'input> {
reader: &'r mut EMLReader<'input>,
start: BytesStart<'input>,
depth: usize,
found_matching_end: bool,
is_empty: bool,
span: Span,
last_span: Span,
}
impl<'r, 'input> EMLElementReader<'r, 'input> {
pub fn from_start(
reader: &'r mut EMLReader<'input>,
start: BytesStart<'input>,
is_empty: bool,
span: Span,
) -> EMLElementReader<'r, 'input> {
EMLElementReader {
reader,
start,
depth: 1,
found_matching_end: is_empty,
is_empty,
span,
last_span: span,
}
}
pub fn name(&self) -> Result<QualifiedName<'_, '_>, EMLError> {
self.get_resolved_name(self.start.name(), self.span, false)
}
pub fn has_name<'a, 'b>(
&self,
name: impl Into<QualifiedName<'a, 'b>>,
) -> Result<bool, EMLError> {
self.is_resolved_name(self.start.name(), self.span, name, false)
}
pub fn next_child(&mut self) -> Result<Option<EMLElementReader<'_, 'input>>, EMLError> {
loop {
match self.next()? {
Some((Event::Start(start), span)) => {
self.depth -= 1; return Ok(Some(EMLElementReader::from_start(
self.reader,
start,
false,
span,
)));
}
Some((Event::Empty(start), span)) => {
return Ok(Some(EMLElementReader::from_start(
self.reader,
start,
true,
span,
)));
}
None => return Ok(None),
_other => {
}
}
}
}
pub fn attribute_value_req<'a, 'b>(
&self,
name: impl Into<QualifiedName<'a, 'b>>,
) -> Result<Cow<'_, str>, EMLError> {
let name = name.into();
self.attribute_value(name.clone())?
.ok_or_else(|| EMLErrorKind::MissingAttribute(name.as_owned()))
.with_span(self.span)
}
pub fn attribute_value<'a, 'b>(
&self,
name: impl Into<QualifiedName<'a, 'b>>,
) -> Result<Option<Cow<'_, str>>, EMLError> {
let name = name.into();
for attr in self.start.attributes() {
let attr = attr.with_span(self.span)?;
if self.is_resolved_name(attr.key, self.span, name.clone(), true)? {
return Ok(Some(
attr.decoded_and_normalized_value(
self.reader.xml_version,
self.reader.inner.decoder(),
)
.with_span(self.span)?,
));
}
}
Ok(None)
}
#[expect(unused)]
pub fn attributes(&self) -> Result<HashMap<QualifiedName<'_, '_>, Cow<'_, str>>, EMLError> {
let mut attributes = HashMap::new();
for attr in self.start.attributes() {
let attr = attr.with_span(self.span)?;
let name = self.get_resolved_name(attr.key, self.span, true)?;
let value = attr
.decoded_and_normalized_value(self.reader.xml_version, self.reader.inner.decoder())
.with_span(self.span)?;
attributes.insert(name, value);
}
Ok(attributes)
}
pub fn text_without_children_opt(&mut self) -> Result<Option<Box<str>>, EMLError> {
if self.is_empty {
Ok(None)
} else {
let text = self.text_without_children()?;
if text.is_empty() {
Ok(None)
} else {
Ok(Some(text))
}
}
}
pub fn text_without_children(&mut self) -> Result<Box<str>, EMLError> {
let mut text = String::new();
loop {
match self.next()? {
Some((Event::Text(t), span)) => {
let decoded = t.xml_content(self.reader.xml_version).with_span(span)?;
text.push_str(decoded.as_ref());
}
Some((Event::CData(t), span)) => {
let decoded = t.xml_content(self.reader.xml_version).with_span(span)?;
text.push_str(decoded.as_ref());
}
Some((Event::GeneralRef(r), span)) => {
let ref_name = r.decode().with_span(span)?;
let formatted_entity = format!("&{};", ref_name);
text.push_str(unescape(&formatted_entity).with_span(span)?.as_ref());
}
Some((Event::Comment(_), _)) => {
}
None => break,
Some((_other, span)) => {
return Err(EMLErrorKind::UnexpectedEvent).with_span(span);
}
}
}
Ok(text.into())
}
pub fn skip(&mut self) -> Result<(), EMLError> {
while let Some(_evt) = self.next()? {
}
Ok(())
}
pub fn span(&self) -> Span {
self.span
}
pub fn last_span(&self) -> Span {
self.last_span
}
pub fn full_span(&self) -> Span {
Span::new(self.span.start, self.last_span.end)
}
pub fn inner_span(&self) -> Span {
Span::new(self.span.end, self.last_span.start)
}
pub fn parsing_mode(&self) -> EMLParsingMode {
self.reader.parsing_mode
}
pub fn push_err(&mut self, err: EMLError) {
if let EMLError::Multiple(MultipleEMLErrors { errors }) = err {
self.reader.errors.extend(errors);
} else {
self.reader.errors.push(err);
}
}
fn map_value_error<'a, 'b, T: StringValueData>(
&self,
parsing_error: Result<StringValue<T>, T::Error>,
name: QualifiedName<'a, 'b>,
span: Span,
) -> Result<StringValue<T>, EMLError> {
parsing_error.map_err(|e| EMLError::invalid_value(name.as_owned(), e, Some(span)))
}
pub(crate) fn string_value_from_text<'a, 'b, T: StringValueData>(
&mut self,
text: Box<str>,
name: Option<QualifiedName<'a, 'b>>,
span: Span,
) -> Result<StringValue<T>, EMLError> {
let name = name.map_or_else(|| self.name(), Ok)?;
match self.parsing_mode() {
EMLParsingMode::Strict => {
self.map_value_error(StringValue::<T>::from_raw_parsed(&text), name, span)
}
EMLParsingMode::StrictFallback => {
match self.map_value_error(StringValue::<T>::from_raw_parsed(&text), name, span) {
Ok(v) => Ok(v),
Err(e) => {
self.push_err(e);
Ok(StringValue::<T>::Raw(text))
}
}
}
EMLParsingMode::Loose => Ok(StringValue::Raw(text)),
}
}
pub fn string_value<T: StringValueData>(&mut self) -> Result<StringValue<T>, EMLError> {
let text = self.text_without_children()?;
self.string_value_from_text(text, None, self.inner_span())
}
pub fn string_value_opt<T: StringValueData>(
&mut self,
) -> Result<Option<StringValue<T>>, EMLError> {
self.text_without_children_opt()?
.map(|v| self.string_value_from_text(v, None, self.inner_span()))
.transpose()
}
pub fn string_value_attr_opt<'a, 'b, T: StringValueData>(
&mut self,
attr_name: impl Into<QualifiedName<'a, 'b>>,
) -> Result<Option<StringValue<T>>, EMLError> {
let attr_name = attr_name.into();
match self.attribute_value(attr_name.clone())? {
Some(value) => Ok(Some(self.string_value_from_text(
value.into(),
Some(attr_name),
self.span(),
)?)),
None => Ok(None),
}
}
pub fn string_value_attr<'a, 'b, T: StringValueData>(
&mut self,
attr_name: impl Into<QualifiedName<'a, 'b>>,
default_value: Option<&str>,
) -> Result<StringValue<T>, EMLError> {
let attr_name = attr_name.into();
let value = self
.attribute_value(attr_name.clone())?
.or_else(|| default_value.map(Cow::Borrowed));
match value {
Some(value) => self.string_value_from_text(value.into(), Some(attr_name), self.span()),
None => {
Err(EMLErrorKind::MissingAttribute(attr_name.as_owned())).with_span(self.span())
}
}
}
fn namespace_name<'a>(
&self,
resolve_result: ResolveResult<'a>,
span: Span,
) -> Result<Option<Cow<'a, str>>, EMLError> {
match resolve_result {
ResolveResult::Bound(namespace) => Ok(Some(
self.reader
.inner
.decoder()
.decode(namespace.into_inner())
.with_span(span)?,
)),
ResolveResult::Unbound => Ok(None),
ResolveResult::Unknown(scope) => Err(EMLErrorKind::UnknownNamespace(
self.reader
.inner
.decoder()
.decode(&scope)
.with_span(span)?
.into_owned(),
))
.with_span(span),
}
}
fn is_resolved_name<'a, 'b, 'c>(
&self,
name: QName<'a>,
span: Span,
expected_name: impl Into<QualifiedName<'b, 'c>>,
is_attribute: bool,
) -> Result<bool, EMLError> {
let expected_name = expected_name.into();
let resolved_name = self.get_resolved_name(name, span, is_attribute)?;
let matches_local = resolved_name.local_name.as_ref() == expected_name.local_name.as_ref();
let matches_namespace = match (
expected_name.namespace.as_deref(),
resolved_name.namespace.as_deref(),
) {
(Some(expected), Some(found)) => expected == found,
(None, None) => true,
_ => false,
};
Ok(matches_local && matches_namespace)
}
fn get_resolved_name<'a>(
&'a self,
name: QName<'a>,
span: Span,
is_attribute: bool,
) -> Result<QualifiedName<'a, 'a>, EMLError> {
let (resolved, local_name) = if is_attribute {
self.reader.inner.resolver().resolve_attribute(name)
} else {
self.reader.inner.resolver().resolve_element(name)
};
let namespace = self.namespace_name(resolved, span)?;
let local_name = self
.reader
.inner
.decoder()
.decode(local_name.into_inner())
.with_span(span)?;
Ok(QualifiedName::new(local_name, namespace))
}
fn next(&mut self) -> Result<Option<(Event<'input>, Span)>, EMLError> {
if self.found_matching_end {
return Ok(None);
}
let (evt, span) = self.reader.next()?;
self.last_span = span;
if matches!(evt, Event::Start(_)) {
self.depth += 1;
}
if matches!(evt, Event::End(_)) {
self.depth -= 1;
}
if matches!(evt, Event::Eof) {
return Err(EMLErrorKind::UnexpectedEof).with_span(span);
}
if self.depth == 0
&& let Event::End(e) = &evt
{
if e.name() == self.start.name() {
self.found_matching_end = true;
return Ok(None);
} else {
return Err(EMLErrorKind::UnexpectedEndElement).with_span(span);
}
}
Ok(Some((evt, span)))
}
pub fn is_empty(&self) -> bool {
self.is_empty
}
}
impl Drop for EMLElementReader<'_, '_> {
fn drop(&mut self) {
let _ = self.skip();
}
}
macro_rules! collect_struct {
( $root:expr, $ty:ident { $($rest:tt)* }) => {
collect_struct!(@expand [$root] [$ty] [] $($rest)* )
};
( @expand [$root:expr] [$ty:ident] [$($items:tt ; )*]
$field:ident: $namespaced_name:expr => |$var:ident| $map:expr ,
$($tail:tt)*
) => {
collect_struct!(@expand [$root] [$ty] [
$($items ; )*
(@field [$field] [$namespaced_name] [$var] [$map]) ;
] $($tail)*)
};
( @expand [$root:expr] [$ty:ident] [$($items:tt ; )*]
$field:ident as None: $namespaced_name:expr => |$var:ident| $map:expr ,
$($tail:tt)*
) => {
collect_struct!(@expand [$root] [$ty] [
$($items ; )*
(@parse_only [$field] [$namespaced_name] [$var] [$map]) ;
] $($tail)*)
};
( @expand [$root:expr] [$ty:ident] [$($items:tt ; )*]
$field:ident as Option: $namespaced_name:expr => |$var:ident| $map:expr ,
$($tail:tt)*
) => {
collect_struct!(@expand [$root] [$ty] [
$($items ; )*
(@optional [$field] [$namespaced_name] [$var] [$map]) ;
] $($tail)*)
};
( @expand [$root:expr] [$ty:ident] [$($items:tt ; )*]
$field:ident as BTreeMap: $namespaced_name:expr => |$var:ident| $map:expr,
$($tail:tt)*
) => {
collect_struct!(@expand [$root] [$ty] [
$($items ; )*
(@btreemap [$field] [$namespaced_name] [$var] [$map]) ;
] $($tail)*)
};
( @expand [$root:expr] [$ty:ident] [$($items:tt ; )*]
$field:ident as Vec: $namespaced_name:expr => |$var:ident| $map:expr ,
$($tail:tt)*
) => {
collect_struct!(@expand [$root] [$ty] [
$($items ; )*
(@vec [$field] [$namespaced_name] [$var] [$map]) ;
] $($tail)*)
};
( @expand [$root:expr] [$ty:ident] [$($items:tt ; )*]
$field:ident: $value:expr ,
$($tail:tt)*
) => {
collect_struct!(@expand [$root] [$ty] [
$($items ; )*
(@direct [$field] [$value]) ;
] $($tail)*)
};
( @expand [$root:expr] [$ty:ident] [$($items:tt ; )*] ) => {
collect_struct!(@emit [$root] [$ty] [$($items ; )*])
};
( @emit [$root:expr] [$ty:ident] [$($items:tt ; )*] ) => {{
$( collect_struct!(@decl $items); )*
let elem_name = $root.name()?.as_owned();
while let Some(mut next_child) = $root.next_child()? {
let name = next_child.name()?.as_owned().into_inner();
#[allow(unused_mut)]
let mut handled = false;
$( collect_struct!(@matcher next_child, name, handled, $items); )*
if !handled {
next_child.push_err($crate::error::EMLError::Positioned {
kind: $crate::error::EMLErrorKind::UnexpectedElement(name.as_owned(), elem_name.clone()),
span: next_child.span(),
});
next_child.skip()?;
}
}
$( collect_struct!(@process $root, $items); )*
collect_struct!(@assign $root, $ty, [], $($items ; )*)
}};
(@decl (@parse_only [$field:ident] [$namespaced_name:expr] [$var:ident] [$map:expr])) => {
let mut $field: Option<_> = None;
};
(@decl (@direct [$field:ident] [$value:expr])) => {};
(@decl (@optional [$field:ident] [$namespaced_name:expr] [$var:ident] [$map:expr])) => {
let mut $field: Option<_> = None;
};
(@decl (@btreemap [$field:ident] [$namespaced_name:expr] [$var:ident] [$map:expr])) => {
let mut $field: std::collections::BTreeMap<_, _> = std::collections::BTreeMap::new();
};
(@decl (@field [$field:ident] [$namespaced_name:expr] [$var:ident] [$map:expr])) => {
let mut $field: Option<_> = None;
};
(@decl (@vec [$field:ident] [$namespaced_name:expr] [$var:ident] [$map:expr])) => {
let mut $field: Vec<_> = Vec::new();
};
(@matcher $next_child:ident, $name:ident, $handled:ident, (@direct [$field:ident] [$value:expr])) => {};
(@matcher $next_child:ident, $name:ident, $handled:ident, (@optional [$field:ident] [$namespaced_name:expr] [$var:ident] [$map:expr])) => {
collect_struct!(@matcher $next_child, $name, $handled, (@field [$field] [$namespaced_name] [$var] [$map]));
};
(@matcher $next_child:ident, $name:ident, $handled:ident, (@parse_only [$field:ident] [$namespaced_name:expr] [$var:ident] [$map:expr])) => {
collect_struct!(@matcher $next_child, $name, $handled, (@field [$field] [$namespaced_name] [$var] [$map]));
};
(@matcher $next_child:ident, $name:ident, $handled:ident, (@field [$field:ident] [$namespaced_name:expr] [$var:ident] [$map:expr])) => {
if !$handled &&
&$name == $crate::io::IntoQualifiedNameCow::into_qname_cow($namespaced_name).as_ref()
{
let $var = &mut $next_child;
$field = Some($map);
$var.skip()?;
$handled = true;
}
};
(@matcher $next_child:ident, $name:ident, $handled:ident, (@vec [$field:ident] [$namespaced_name:expr] [$var:ident] [$map:expr])) => {
if !$handled &&
&$name == $crate::io::IntoQualifiedNameCow::into_qname_cow($namespaced_name).as_ref()
{
let $var = &mut $next_child;
$field.push($map);
$var.skip()?;
$handled = true;
}
};
(@matcher $next_child:ident, $name:ident, $handled:ident, (@btreemap [$field:ident] [$namespaced_name:expr] [$var:ident] [$map:expr])) => {
if !$handled &&
&$name == $crate::io::IntoQualifiedNameCow::into_qname_cow($namespaced_name).as_ref()
{
let $var = &mut $next_child;
let (k, v) = $map;
$field.insert(k, v);
$var.skip()?;
$handled = true;
}
};
(@process $root:expr, (@parse_only [$field:ident] [$namespaced_name:expr] [$var:ident] [$map:expr])) => {
$crate::error::EMLResultExt::with_span(
$field.ok_or_else(|| $crate::error::EMLErrorKind::MissingElement(
$crate::io::QualifiedName::from($namespaced_name).as_owned()
)),
$root.last_span()
)?;
};
(@process $root:expr, (@direct [$field:ident] [$value:expr])) => {};
(@process $root:expr, (@optional [$field:ident] [$namespaced_name:expr] [$var:ident] [$map:expr])) => {};
(@process $root:expr, (@btreemap [$field:ident] [$namespaced_name:expr] [$var:ident] [$map:expr])) => {};
(@process $root:expr, (@field [$field:ident] [$namespaced_name:expr] [$var:ident] [$map:expr])) => {};
(@process $root:expr, (@vec [$field:ident] [$namespaced_name:expr] [$var:ident] [$map:expr])) => {};
(@build_struct $root:expr, $ty:ident, $($items:tt ; )* ) => {
$ty {
collect_struct!(@assign $root, $($items ; )*)
}
};
(@assign $root:expr, $ty:ident, [$($out:tt)*], (@direct [$field:ident] [$value:expr]) ; $($tail:tt)*) => {
collect_struct!(@assign $root, $ty, [
$($out)*
$field: $value,
], $($tail)*)
};
(@assign $root:expr, $ty:ident, [$($out:tt)*], (@parse_only [$field:ident] [$namespaced_name:expr] [$var:ident] [$map:expr]) ; $($tail:tt)*) => {
collect_struct!(@assign $root, $ty, [
$($out)*
], $($tail)*)
};
(@assign $root:expr, $ty:ident, [$($out:tt)*], (@optional [$field:ident] [$namespaced_name:expr] [$var:ident] [$map:expr]) ; $($tail:tt)*) => {
collect_struct!(@assign $root, $ty, [
$($out)*
$field: $field,
], $($tail)*)
};
(@assign $root:expr, $ty:ident, [$($out:tt)*], (@vec [$field:ident] [$namespaced_name:expr] [$var:ident] [$map:expr]) ; $($tail:tt)*) => {
collect_struct!(@assign $root, $ty, [
$($out)*
$field: $field,
], $($tail)*)
};
(@assign $root:expr, $ty:ident, [$($out:tt)*], (@btreemap [$field:ident] [$namespaced_name:expr] [$var:ident] [$map:expr]) ; $($tail:tt)*) => {
collect_struct!(@assign $root, $ty, [
$($out)*
$field: $field,
], $($tail)*)
};
(@assign $root:expr, $ty:ident, [$($out:tt)*], (@field [$field:ident] [$namespaced_name:expr] [$var:ident] [$map:expr]) ; $($tail:tt)*) => {
collect_struct!(@assign $root, $ty, [
$($out)*
$field: $crate::error::EMLResultExt::with_span(
$field.ok_or_else(|| $crate::error::EMLErrorKind::MissingElement(
$crate::io::QualifiedName::from($namespaced_name).as_owned()
)),
$root.last_span()
)?,
], $($tail)*)
};
(@assign $root:expr, $ty:ident, [$($out:tt)*], ) => {
$ty {
$($out)*
}
};
}
pub(crate) use collect_struct;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unknown_namespace() {
let document = r#"<eml:UnknownElement />"#;
let mut reader = EMLReader::init_from_str(document, EMLParsingMode::Strict);
let root = reader.next_element().unwrap();
let error = root.name().unwrap_err();
assert!(matches!(
error.kind(),
EMLErrorKind::UnknownNamespace(ns) if ns == "eml"
));
}
#[test]
fn test_input_without_element_returns_unexpected_eof() {
for document in [
"",
"5738 d520 a7f2 89b8 8875 ebfe cfc8 6012 d7b6 f65f 3271 d0e1 180b ccdd 8134 cd5d\n",
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
] {
let mut reader = EMLReader::init_from_str(document, EMLParsingMode::Strict);
let Err(error) = reader.next_element() else {
panic!("expected an error for input {document:?}");
};
assert!(matches!(error.kind(), EMLErrorKind::UnexpectedEof));
}
}
}