use std::{collections::HashMap, io::Write, str};
use log::warn;
use paste::paste;
use quick_xml::{events::Event, Reader, Writer};
use serde_json::Value;
use crate::{
error::VOTableError,
utils::{discard_comment, discard_event, is_empty},
HasSubElements, HasSubElems, QuickXmlReadWrite, VOTableElement,
};
use super::{globals::Globals, model::Model, report::Report, templates::Templates, VodmlVisitor};
#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct Vodml {
pub xmlns: Option<String>,
#[serde(flatten, skip_serializing_if = "HashMap::is_empty")]
pub extra: HashMap<String, Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub report: Option<Report>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub models: Vec<Model>,
#[serde(skip_serializing_if = "Option::is_none")]
pub globals: Option<Globals>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub templates: Vec<Templates>,
}
impl Vodml {
pub fn new() -> Self {
Self {
xmlns: None,
report: None,
models: vec![],
globals: None,
templates: vec![],
extra: HashMap::default(),
}
}
impl_builder_opt_string_attr!(xmlns);
impl_builder_insert_extra!();
impl_builder_opt_subelem!(report, Report);
impl_builder_push!(Model);
impl_builder_opt_subelem!(globals, Globals);
impl_builder_push_no_s!(Templates);
pub fn visit<V: VodmlVisitor>(&mut self, visitor: &mut V) -> Result<(), V::E> {
visitor.visit_vodml_start(self)?;
if let Some(report) = self.report.as_mut() {
report.visit(visitor)?;
}
for model in self.models.iter_mut() {
model.visit(visitor)?;
}
if let Some(globals) = self.globals.as_mut() {
globals.visit(visitor)?;
}
for template in self.templates.iter_mut() {
template.visit(visitor)?;
}
visitor.visit_vodml_ended(self)
}
}
impl VOTableElement for Vodml {
const TAG: &'static str = "VODML";
type MarkerType = HasSubElems;
fn from_attrs<K, V, I>(attrs: I) -> Result<Self, VOTableError>
where
K: AsRef<str> + Into<String>,
V: AsRef<str> + Into<String>,
I: Iterator<Item = (K, V)>,
{
Self::new().set_attrs(attrs)
}
fn set_attrs_by_ref<K, V, I>(&mut self, attrs: I) -> Result<(), VOTableError>
where
K: AsRef<str> + Into<String>,
V: AsRef<str> + Into<String>,
I: Iterator<Item = (K, V)>,
{
for (key, val) in attrs {
let key = key.as_ref();
match key {
"xmlns" => self.set_xmlns_by_ref(val),
_ => self.insert_extra_str_by_ref(key, val),
}
}
Ok(())
}
fn for_each_attribute<F>(&self, mut f: F)
where
F: FnMut(&str, &str),
{
if let Some(xmlns) = &self.xmlns {
f("xmlns", xmlns.to_string().as_str());
}
for_each_extra_attribute!(self, f);
}
}
impl HasSubElements for Vodml {
type Context = ();
fn has_no_sub_elements(&self) -> bool {
self.report.is_none()
&& self.models.is_empty()
&& self.globals.is_none()
&& self.extra.is_empty()
}
fn read_sub_elements_by_ref<R: std::io::BufRead>(
&mut self,
mut reader: &mut Reader<R>,
mut reader_buff: &mut Vec<u8>,
_context: &Self::Context,
) -> Result<(), VOTableError> {
loop {
let mut event = reader.read_event(reader_buff).map_err(VOTableError::Read)?;
match &mut event {
Event::Start(e) => match e.local_name() {
Report::TAG_BYTES => set_from_event_start!(self, Report, reader, reader_buff, e),
Model::TAG_BYTES => push_from_event_start!(self, Model, reader, reader_buff, e),
Globals::TAG_BYTES => set_from_event_start!(self, Globals, reader, reader_buff, e),
Templates::TAG_BYTES => push_from_event_start!(self, Templates, reader, reader_buff, e),
_ => {
return Err(VOTableError::UnexpectedStartTag(
e.local_name().to_vec(),
Self::TAG,
))
}
},
Event::Empty(e) => match e.local_name() {
Report::TAG_BYTES => set_from_event_empty!(self, Report, e),
Model::TAG_BYTES => push_from_event_empty!(self, Model, e),
Globals::TAG_BYTES => set_from_event_empty!(self, Globals, e),
Templates::TAG_BYTES => push_from_event_empty!(self, Templates, e),
_ => {
return Err(VOTableError::UnexpectedEmptyTag(
e.local_name().to_vec(),
Self::TAG,
))
}
},
Event::Text(e) if is_empty(e) => {}
Event::End(e) if e.local_name() == Self::TAG_BYTES => {
if !self.models.is_empty() {
return Ok(());
} else {
return Err(VOTableError::Custom(
"Expected a <MODEL> tag, none was found".to_owned(),
));
}
}
Event::Eof => return Err(VOTableError::PrematureEOF(Self::TAG)),
Event::Comment(e) => discard_comment(e, reader, Self::TAG),
_ => discard_event(event, Self::TAG),
}
}
}
fn write_sub_elements_by_ref<W: Write>(
&mut self,
writer: &mut Writer<W>,
context: &Self::Context,
) -> Result<(), VOTableError> {
write_elem!(self, report, writer, context);
write_elem_vec!(self, models, writer, context);
write_elem!(self, globals, writer, context);
write_elem_vec!(self, templates, writer, context);
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::{
mivot::test::test_error,
mivot::{test::get_xml, vodml::Vodml},
tests::test_read,
};
#[test]
fn test_vodml_read() {
let xml = get_xml("./resources/mivot/1/test_1_ok_1.1.xml");
println!("testing 1.1");
test_read::<Vodml>(&xml);
let xml = get_xml("./resources/mivot/1/test_1_ok_1.2.xml");
println!("testing 1.2");
test_read::<Vodml>(&xml);
let xml = get_xml("./resources/mivot/1/test_1_ok_1.3.xml");
println!("testing 1.3");
test_read::<Vodml>(&xml);
let xml = get_xml("./resources/mivot/1/test_1_ok_1.4.xml");
println!("testing 1.4");
test_read::<Vodml>(&xml);
let xml = get_xml("./resources/mivot/1/test_1_ok_1.8.xml");
println!("testing 1.8");
test_read::<Vodml>(&xml);
let xml = get_xml("./resources/mivot/1/test_1_ok_1.9.xml");
println!("testing 1.9");
test_read::<Vodml>(&xml);
let xml = get_xml("./resources/mivot/1/test_1_ko_1.5.xml");
println!("testing 1.5"); test_error::<Vodml>(&xml, false);
let xml = get_xml("./resources/mivot/1/test_1_ko_1.6.xml");
println!("testing 1.6"); test_read::<Vodml>(&xml); let xml = get_xml("./resources/mivot/1/test_1_ko_1.7.xml");
println!("testing 1.7"); test_read::<Vodml>(&xml); let xml = get_xml("./resources/mivot/1/test_1_ko_1.10.xml");
println!("testing 1.10"); test_read::<Vodml>(&xml); let xml = get_xml("./resources/mivot/1/test_1_ko_1.11.xml");
println!("testing 1.11"); test_error::<Vodml>(&xml, false);
}
}