votable/
lib.rs

1extern crate core;
2
3/// Metadata, in addition to the `schema` information, possibly used in the header part of
4/// a file.
5/// The `schema` information are mandatory to be able to read/write data in the various file format.
6/// The `metadata` here contains additional optional information (such as units, UCDs, descriptions,
7/// ...).
8/// The `metadata` may contains redundancies with `schema`. If it is the case, the metadata
9/// *MUST* be overwritten by the `schema` (the schema contains minimal, yet very important, information).
10///
11/// `schema` + `metadata` => any format meta
12///
13/// `metadata` my be complex, we would like to build it from a simpler (e.x. TOML) files.
14///
15/// We used first the `VOTable` format to defined a `metadata` set, add other elements,
16/// plus a mechanism to allow for custom metadata.
17use std::{
18  error::Error,
19  io::{BufRead, Write},
20  str::from_utf8,
21};
22
23use quick_xml::{
24  events::{attributes::Attributes, BytesStart, BytesText, Event},
25  Reader, Writer,
26};
27
28#[macro_use]
29mod macros;
30mod utils;
31
32pub mod coosys;
33pub mod data;
34pub mod datatype;
35pub mod definitions;
36pub mod desc;
37pub mod error;
38pub mod field;
39pub mod fieldref;
40pub mod group;
41pub mod impls;
42pub mod info;
43pub mod iter;
44pub mod link;
45pub mod param;
46pub mod paramref;
47pub mod resource;
48pub mod table;
49pub mod timesys;
50pub mod values;
51pub mod votable;
52
53#[cfg(feature = "mivot")]
54pub mod mivot;
55
56#[cfg(feature = "mivot")]
57pub use self::mivot::VodmlVisitor;
58use self::utils::{discard_comment, discard_event};
59pub use self::{
60  coosys::CooSys,
61  data::{
62    binary::Binary, binary2::Binary2, fits::Fits, stream::Stream, tabledata::TableData, Data,
63  },
64  definitions::Definitions,
65  desc::Description,
66  error::VOTableError,
67  field::Field,
68  fieldref::FieldRef,
69  group::{Group, TableGroup},
70  impls::mem::VoidTableDataContent,
71  info::Info,
72  link::Link,
73  param::Param,
74  paramref::ParamRef,
75  resource::Resource,
76  table::Table,
77  table::TableElem,
78  timesys::TimeSys,
79  values::{Max, Min, Opt, Values},
80  votable::VOTable,
81};
82
83pub trait VOTableElement: Sized {
84  /// XML Tag og the VOTable element.
85  const TAG: &'static str;
86  const TAG_BYTES: &'static [u8] = Self::TAG.as_bytes();
87
88  type MarkerType: VOTableElementType;
89
90  /// Returns the XML tag of this VOTable Element.
91  fn tag(&self) -> &'static str {
92    Self::TAG
93  }
94
95  fn tag_bytes(&self) -> &'static [u8] {
96    Self::TAG_BYTES
97  }
98
99  /// Create a new object from a set of attributes.
100  ///
101  /// Usually, `from_attributes` simply call the object default constructor
102  /// and delegate the setting of attributes to `set_attributes`.
103  /// It may be more complicated when the object contains mandatory attributes.
104  /// # Warning
105  /// The returned object should not be considered in its final state.
106  /// Indeed, it may not be valid yet (e.g. if it **must** contains mandatory sub-elements,
107  /// those sub-elements have to be set afterwards).
108  fn from_attrs<K, V, I>(attrs: I) -> Result<Self, VOTableError>
109  where
110    K: AsRef<str> + Into<String>,
111    V: AsRef<str> + Into<String>,
112    I: Iterator<Item = (K, V)>;
113
114  /// Set (or re-set) the objet attributes.
115  fn set_attrs<K, V, I>(mut self, attrs: I) -> Result<Self, VOTableError>
116  where
117    K: AsRef<str> + Into<String>,
118    V: AsRef<str> + Into<String>,
119    I: Iterator<Item = (K, V)>,
120  {
121    self.set_attrs_by_ref(attrs).map(|_| self)
122  }
123
124  /// Set (or re-set) the objet attributes.
125  fn set_attrs_by_ref<K, V, I>(&mut self, attrs: I) -> Result<(), VOTableError>
126  where
127    K: AsRef<str> + Into<String>,
128    V: AsRef<str> + Into<String>,
129    I: Iterator<Item = (K, V)>;
130
131  /// Calls the given function `f` on each `key=value` attribute pairs.
132  fn for_each_attribute<F>(&self, f: F)
133  where
134    F: FnMut(&str, &str);
135
136  fn get_attrs(&self) -> Vec<(String, String)> {
137    let mut attrs = Vec::new();
138    self.for_each_attribute(|k, v| attrs.push((k.to_string(), v.to_string())));
139    attrs
140  }
141}
142
143/// This trait is a marker trait used to provide specific `QuickXmlReadWrite` implementations
144/// for VOTable element families.
145/// We resort to it because negative traits is not yet fully implemented in Rust, e.g. we cannot write
146/// ```ignore
147/// impl<T: HasContent> !IsEmpty for T {}
148/// impl<T: IsEmpty> !HasContent for T {}
149/// ```
150/// For more details, see e.g. [this blog post](https://geo-ant.github.io/blog/2021/mutually-exclusive-traits-rust/)
151pub trait VOTableElementType {}
152/// Marker struct telling that the VOTable element has neither a content nor sub-elements
153pub struct EmptyElem;
154impl VOTableElementType for EmptyElem {}
155/// Marker struct telling that the VOTable element has a content, but no sub-elements
156pub struct HasContentElem;
157impl VOTableElementType for HasContentElem {}
158/// Marker struct telling that the VOTable element has sub-elements (but no content)
159pub struct HasSubElems;
160impl VOTableElementType for HasSubElems {}
161/// Marker struct telling that the VOTable element has specific reader/writers.
162pub struct SpecialElem;
163impl VOTableElementType for SpecialElem {}
164
165/// Marker trait telling that the tag is always empty: i.e. has no content and no sub-elements.
166pub trait IsEmpty: VOTableElement<MarkerType = EmptyElem> {}
167/// Implements 'IsEmpty' for all `VOTableElements` having the `EmptyElem` marker.
168impl<T> IsEmpty for T where T: VOTableElement<MarkerType = EmptyElem> {}
169
170impl<T: IsEmpty> QuickXmlReadWrite<EmptyElem> for T {
171  type Context = ();
172
173  fn read_content_by_ref<R: BufRead>(
174    &mut self,
175    _reader: &mut Reader<R>,
176    _reader_buff: &mut Vec<u8>,
177    _context: &Self::Context,
178  ) -> Result<(), VOTableError> {
179    Ok(())
180  }
181
182  fn write<W: Write>(
183    &mut self,
184    writer: &mut Writer<W>,
185    _context: &Self::Context,
186  ) -> Result<(), VOTableError> {
187    let mut elem_writer = writer.create_element(Self::TAG_BYTES);
188    elem_writer = elem_writer.with_attributes(
189      self
190        .get_attrs()
191        .iter()
192        .map(|(k, v)| (k.as_str(), v.as_str())),
193    );
194    elem_writer
195      .write_empty()
196      .map_err(VOTableError::Write)
197      .map(|_| ())
198  }
199}
200
201/// Tels that the element may contains text between its opening and closing tags.
202/// In the VOTable standard, tags possibly having a content do not have sub-elements
203pub trait HasContent: VOTableElement<MarkerType = HasContentElem> {
204  /// Returns the string content of the tag.
205  fn get_content(&self) -> Option<&str>;
206  /// Set the optional content of this tag, taking the ownership and returning itself.
207  fn set_content<S: Into<String>>(self, content: S) -> Self;
208  /// Set the optional content of this tag, by mutable ref.
209  fn set_content_by_ref<S: Into<String>>(&mut self, content: S);
210}
211impl<T: HasContent> QuickXmlReadWrite<HasContentElem> for T {
212  type Context = ();
213
214  fn read_content_by_ref<R: BufRead>(
215    &mut self,
216    reader: &mut Reader<R>,
217    reader_buff: &mut Vec<u8>,
218    _context: &Self::Context,
219  ) -> Result<(), VOTableError> {
220    let mut content = String::new();
221    loop {
222      let mut event = reader.read_event(reader_buff).map_err(VOTableError::Read)?;
223      match &mut event {
224        Event::Text(e) => content.push_str(
225          e.unescape_and_decode(reader)
226            .map_err(VOTableError::Read)?
227            .as_str(),
228        ),
229        Event::CData(e) => {
230          content.push_str(from_utf8(e.clone().into_inner().as_ref()).map_err(VOTableError::Utf8)?)
231        }
232        Event::End(e) if e.local_name() == Self::TAG_BYTES => {
233          self.set_content_by_ref(content);
234          reader_buff.clear();
235          return Ok(());
236        }
237        Event::Eof => return Err(VOTableError::PrematureEOF(Self::TAG)),
238        Event::Comment(e) => discard_comment(e, reader, Self::TAG),
239        _ => discard_event(event, Self::TAG),
240      }
241    }
242  }
243
244  fn write<W: Write>(
245    &mut self,
246    writer: &mut Writer<W>,
247    _context: &Self::Context,
248  ) -> Result<(), VOTableError> {
249    let mut elem_writer = writer.create_element(Self::TAG_BYTES);
250    elem_writer = elem_writer.with_attributes(
251      self
252        .get_attrs()
253        .iter()
254        .map(|(k, v)| (k.as_str(), v.as_str())),
255    );
256    if let Some(content) = self.get_content() {
257      elem_writer.write_text_content(BytesText::from_plain_str(content))
258    } else {
259      elem_writer.write_empty()
260    }
261    .map_err(VOTableError::Write)
262    .map(|_| ())
263  }
264}
265
266/// Marker trait telling that the tag mau contents sub-elements (but no content).
267pub trait HasSubElements: VOTableElement<MarkerType = HasSubElems> {
268  type Context;
269
270  /// Returns `false` if this object contains sub-elements.
271  fn has_no_sub_elements(&self) -> bool;
272
273  /// Same as `read_sub_elements`, cleaning the `reader_buf` before returning.
274  fn read_sub_elements_and_clean<R: BufRead>(
275    &mut self,
276    reader: Reader<R>,
277    reader_buff: &mut Vec<u8>,
278    context: &Self::Context,
279  ) -> Result<Reader<R>, VOTableError> {
280    let res = self.read_sub_elements(reader, reader_buff, context);
281    reader_buff.clear();
282    res
283  }
284
285  /// We assume that the previous event was `Start`, and that the method returns
286  /// when finding the `End` event matching the last `Start` event before entering the method.
287  fn read_sub_elements<R: BufRead>(
288    &mut self,
289    mut reader: Reader<R>,
290    reader_buff: &mut Vec<u8>,
291    context: &Self::Context,
292  ) -> Result<Reader<R>, VOTableError> {
293    self
294      .read_sub_elements_by_ref(&mut reader, reader_buff, context)
295      .map(|()| reader)
296  }
297
298  /// Same as `read_sub_elements`, cleaning the `reader_buf` before returning.
299  fn read_sub_elements_and_clean_by_ref<R: BufRead>(
300    &mut self,
301    reader: &mut Reader<R>,
302    reader_buff: &mut Vec<u8>,
303    context: &Self::Context,
304  ) -> Result<(), VOTableError> {
305    let res = self.read_sub_elements_by_ref(reader, reader_buff, context);
306    reader_buff.clear();
307    res
308  }
309
310  /// We assume that the previous event was `Start`, and that the method returns
311  /// when finding the `End` event matching the last `Start` event before entering the method.
312  fn read_sub_elements_by_ref<R: BufRead>(
313    &mut self,
314    reader: &mut Reader<R>,
315    reader_buff: &mut Vec<u8>,
316    context: &Self::Context,
317  ) -> Result<(), VOTableError>;
318
319  /// Write sub_elements
320  fn write_sub_elements_by_ref<W: Write>(
321    &mut self,
322    writer: &mut Writer<W>,
323    context: &Self::Context,
324  ) -> Result<(), VOTableError>;
325}
326
327impl<T: HasSubElements> QuickXmlReadWrite<HasSubElems> for T {
328  type Context = <Self as HasSubElements>::Context;
329
330  fn read_content_by_ref<R: BufRead>(
331    &mut self,
332    reader: &mut Reader<R>,
333    reader_buff: &mut Vec<u8>,
334    context: &Self::Context,
335  ) -> Result<(), VOTableError> {
336    self.read_sub_elements_by_ref(reader, reader_buff, context)
337  }
338
339  /// `&mut self` in case internals are modified while writing (e.g. if we iterate on rows
340  /// and discard them as we iterate).
341  /// We could add a context, e.g. to modify the parent (adding infos for example).
342  fn write<W: Write>(
343    &mut self,
344    writer: &mut Writer<W>,
345    context: &Self::Context,
346  ) -> Result<(), VOTableError> {
347    if self.has_no_sub_elements() {
348      let mut elem_writer = writer.create_element(Self::TAG_BYTES);
349      elem_writer = elem_writer.with_attributes(
350        self
351          .get_attrs()
352          .iter()
353          .map(|(k, v)| (k.as_str(), v.as_str())),
354      );
355      elem_writer
356        .write_empty()
357        .map_err(VOTableError::Write)
358        .map(|_| ())
359    } else {
360      let mut tag = BytesStart::borrowed_name(Self::TAG_BYTES);
361      // Write tag + attributes
362      self.for_each_attribute(|k, v| tag.push_attribute((k, v)));
363      writer
364        .write_event(Event::Start(tag.to_borrowed()))
365        .map_err(VOTableError::Write)?;
366      // Write_sub-elems
367      self.write_sub_elements_by_ref(writer, context)?;
368      // Close tag
369      writer
370        .write_event(Event::End(tag.to_end()))
371        .map_err(VOTableError::Write)
372    }
373  }
374}
375
376pub trait TableDataContent: Default + PartialEq + serde::Serialize {
377  fn new() -> Self {
378    Self::default()
379  }
380
381  /// When deserializing from JSON, TOML or YAML, we should implement a 'DeserializeSeed'
382  /// based on the table Schema. But:
383  /// * we have to implement `Deserialize` by hand on `Table`, `Data`, `DataElem`,
384  ///   `TableData`, `Binary`, `Binary2` and `Stream`, which is daunting task, even using
385  ///   `cargo expand`...
386  /// * even so, the metadata may be parsed after the data
387  ///   (e.g. in JSON the key order is not guaranteed to be preserved)
388  ///
389  /// So, the result of the deserialization without knowing the table schema may result in
390  /// no-homogeneous datatype in a same column.
391  /// E.g `short` and `int`, or `char` and `string` may be mixed.
392  ///
393  /// So, we use this method to replace incorrect types by the porper ones as a post-parsing process.
394  /// This is not ideal on a performance point-of-view, but Serde usage to convert from JSON, TOML
395  /// and YAML **should be** limited to small tables (less than a few hundreds of megabytes).
396  fn ensures_consistency(&mut self, context: &[TableElem]) -> Result<(), String>;
397
398  /// Called when Event::Start("DATATABLE") as been detected and **MUST**
399  /// return after event Event::End("DATATABLE")
400  fn read_datatable_content<R: BufRead>(
401    &mut self,
402    reader: &mut Reader<R>,
403    reader_buff: &mut Vec<u8>,
404    context: &[TableElem],
405  ) -> Result<(), VOTableError>;
406
407  /// Called when Event::Start("STREAM") as been detected (in BINARY) and **MUST**
408  /// return after event Event::End("STREAM")
409  fn read_binary_content<R: BufRead>(
410    &mut self,
411    reader: &mut Reader<R>,
412    reader_buff: &mut Vec<u8>,
413    context: &[TableElem],
414  ) -> Result<(), VOTableError>;
415
416  /// Called when Event::Start("STREAM") as been detected (in BINARY2) and **MUST**
417  /// return after event Event::End("STREAM")
418  fn read_binary2_content<R: BufRead>(
419    &mut self,
420    reader: &mut Reader<R>,
421    reader_buff: &mut Vec<u8>,
422    context: &[TableElem],
423  ) -> Result<(), VOTableError>;
424
425  fn write_in_datatable<W: Write>(
426    &mut self,
427    writer: &mut Writer<W>,
428    context: &[TableElem],
429  ) -> Result<(), VOTableError>;
430
431  fn write_in_binary<W: Write>(
432    &mut self,
433    writer: &mut Writer<W>,
434    context: &[TableElem],
435  ) -> Result<(), VOTableError>;
436
437  fn write_in_binary2<W: Write>(
438    &mut self,
439    writer: &mut Writer<W>,
440    context: &[TableElem],
441  ) -> Result<(), VOTableError>;
442}
443
444/// The `VOTableElementType` generic parameter is here only to be able to provide
445/// various default implementations, emulating mutually exclusive traits
446/// extended by `QuickXmlReadWrite`.
447pub trait QuickXmlReadWrite<VOTableElementType>: VOTableElement {
448  type Context;
449
450  fn from_event_empty(e: &BytesStart) -> Result<Self, VOTableError> {
451    Self::from_event_start(e)
452  }
453
454  fn from_event_start(e: &BytesStart) -> Result<Self, VOTableError> {
455    Self::from_attributes(e.attributes())
456  }
457
458  /// We assume that the previous event was either `Start` or `Empty`.
459  fn from_attributes(attrs: Attributes) -> Result<Self, VOTableError> {
460    Self::quick_xml_attrs_to_vec(attrs).and_then(|attrs| Self::from_attrs(attrs.into_iter()))
461  }
462
463  /*
464  // Does not work because BytesStart has a lifetime depending on reader_buff...
465  fn from_event_start<R: BufRead>(
466    e: &BytesStart,
467    mut reader: &mut Reader<R>,
468    reader_buff: &mut Vec<u8>,
469    context: &Self::Context,
470  ) -> Result<Self, VOTableError> {
471    Self::from_attributes(e.attributes()).and_then(|mut elem| {
472      elem
473        .read_sub_elements_and_clean_by_ref(&mut reader, reader_buff, context)
474        .map(|()| elem)
475    })
476  }*/
477
478  fn quick_xml_attrs_to_vec(attrs: Attributes) -> Result<Vec<(String, String)>, VOTableError> {
479    attrs
480      //.filter_map(|r| {
481      .map(|r| {
482        r.map_err(VOTableError::Attr).and_then(|attr| {
483          from_utf8(attr.key)
484            .map_err(VOTableError::Utf8)
485            .and_then(|key| {
486              attr
487                .unescaped_value()
488                .map_err(VOTableError::Read)
489                .and_then(|val| {
490                  from_utf8(val.as_ref())
491                    .map(|val| val.to_string())
492                    .map_err(VOTableError::Utf8)
493                })
494                .map(
495                  |val| (key.to_string(), val),
496                  /*{
497                    if val.is_empty() {
498                      None
499                    } else {
500                      Some((key.to_string(), val))
501                    }
502                  }*/
503                )
504            })
505        })
506        //.transpose()
507      })
508      .collect::<Result<Vec<(String, String)>, _>>()
509  }
510
511  fn read_content<R: BufRead>(
512    mut self,
513    reader: &mut Reader<R>,
514    reader_buff: &mut Vec<u8>,
515    context: &Self::Context,
516  ) -> Result<Self, VOTableError> {
517    self
518      .read_content_by_ref(reader, reader_buff, context)
519      .map(|()| self)
520  }
521
522  fn read_content_by_ref<R: BufRead>(
523    &mut self,
524    reader: &mut Reader<R>,
525    reader_buff: &mut Vec<u8>,
526    context: &Self::Context,
527  ) -> Result<(), VOTableError>;
528
529  /// `&mut self` in case internals are modified while writing (e.g. if we iterate on rows
530  /// and discard them as we iterate).
531  /// We could add a context, e.g. to modify the parent (adding infos for example).
532  fn write<W: Write>(
533    &mut self,
534    writer: &mut Writer<W>,
535    context: &Self::Context,
536  ) -> Result<(), VOTableError>;
537}
538
539// We visit all sub elements, bu we retrieve attributes from objects
540// We kind of added a part of the context by prefixing some visit methods with the name of the
541// TAG it is called from.
542pub trait VOTableVisitor<C: TableDataContent> {
543  type E: Error;
544
545  #[cfg(feature = "mivot")]
546  type M: VodmlVisitor<E = Self::E>;
547
548  fn visit_votable_start(&mut self, votable: &mut VOTable<C>) -> Result<(), Self::E>;
549  fn visit_votable_ended(&mut self, votable: &mut VOTable<C>) -> Result<(), Self::E>;
550
551  fn visit_description(&mut self, description: &mut Description) -> Result<(), Self::E>; // No start/end
552  fn visit_coosys_start(&mut self, coosys: &mut CooSys) -> Result<(), Self::E>;
553  fn visit_coosys_ended(&mut self, coosys: &mut CooSys) -> Result<(), Self::E>;
554  fn visit_timesys(&mut self, timesys: &mut TimeSys) -> Result<(), Self::E>; // No start/end
555  fn visit_group_start(&mut self, group: &mut Group) -> Result<(), Self::E>;
556  fn visit_group_ended(&mut self, group: &mut Group) -> Result<(), Self::E>;
557
558  #[cfg(feature = "mivot")]
559  fn get_mivot_visitor(&mut self) -> Self::M;
560
561  fn visit_table_group_start(&mut self, group: &mut TableGroup) -> Result<(), Self::E>;
562  fn visit_table_group_ended(&mut self, group: &mut TableGroup) -> Result<(), Self::E>;
563
564  fn visit_paramref(&mut self, paramref: &mut ParamRef) -> Result<(), Self::E>; // No start/end
565  fn visit_fieldref(&mut self, fieldref: &mut FieldRef) -> Result<(), Self::E>; // No start/end
566
567  fn visit_param_start(&mut self, param: &mut Param) -> Result<(), Self::E>;
568  fn visit_param_ended(&mut self, param: &mut Param) -> Result<(), Self::E>;
569
570  fn visit_field_start(&mut self, field: &mut Field) -> Result<(), Self::E>;
571  fn visit_field_ended(&mut self, field: &mut Field) -> Result<(), Self::E>;
572
573  fn visit_info(&mut self, info: &mut Info) -> Result<(), Self::E>; // No start/end
574  fn visit_definitions_start(&mut self, coosys: &mut Definitions) -> Result<(), Self::E>;
575  fn visit_definitions_ended(&mut self, coosys: &mut Definitions) -> Result<(), Self::E>;
576
577  fn visit_resource_start(&mut self, resource: &mut Resource<C>) -> Result<(), Self::E>;
578  fn visit_resource_ended(&mut self, resource: &mut Resource<C>) -> Result<(), Self::E>;
579
580  fn visit_post_info(&mut self, info: &mut Info) -> Result<(), Self::E>;
581
582  /// Resource sub-elems are purely virtual elements.
583  fn visit_resource_sub_elem_start(&mut self) -> Result<(), Self::E>;
584  fn visit_resource_sub_elem_ended(&mut self) -> Result<(), Self::E>;
585
586  fn visit_link(&mut self, link: &mut Link) -> Result<(), Self::E>; // No start/end
587
588  fn visit_table_start(&mut self, table: &mut Table<C>) -> Result<(), Self::E>;
589  fn visit_table_ended(&mut self, table: &mut Table<C>) -> Result<(), Self::E>;
590
591  fn visit_data_start(&mut self, data: &mut Data<C>) -> Result<(), Self::E>;
592  fn visit_data_ended(&mut self, data: &mut Data<C>) -> Result<(), Self::E>;
593
594  fn visit_tabledata(&mut self, table: &mut TableData<C>) -> Result<(), Self::E>;
595  fn visit_binary_stream(&mut self, stream: &mut Stream<C>) -> Result<(), Self::E>;
596  fn visit_binary2_stream(&mut self, stream: &mut Stream<C>) -> Result<(), Self::E>;
597  fn visit_fits_start(&mut self, fits: &mut Fits) -> Result<(), Self::E>;
598  fn visit_fits_stream(&mut self, stream: &mut Stream<VoidTableDataContent>)
599    -> Result<(), Self::E>;
600  fn visit_fits_ended(&mut self, fits: &mut Fits) -> Result<(), Self::E>;
601
602  fn visit_values_start(&mut self, values: &mut Values) -> Result<(), Self::E>;
603  fn visit_values_min(&mut self, min: &mut Min) -> Result<(), Self::E>; // No start/end
604  fn visit_values_max(&mut self, max: &mut Max) -> Result<(), Self::E>; // No start/end
605  fn visit_values_opt_start(&mut self, opt: &mut Opt) -> Result<(), Self::E>;
606  fn visit_values_opt_ended(&mut self, opt: &mut Opt) -> Result<(), Self::E>;
607  fn visit_values_ended(&mut self, values: &mut Values) -> Result<(), Self::E>;
608}
609
610#[cfg(test)]
611mod tests {
612  use std::{i64, io::Cursor, str::from_utf8};
613
614  use quick_xml::{events::Event, Reader, Writer};
615  use serde_json::{Number, Value};
616
617  use super::{
618    coosys::{CooSys, System},
619    data::Data,
620    datatype::Datatype,
621    field::{ArraySize, Field, Precision},
622    impls::{mem::InMemTableDataRows, VOTableValue},
623    info::Info,
624    link::Link,
625    resource::{Resource, ResourceSubElem},
626    table::Table,
627    values::Values,
628    votable::{VOTable, Version},
629    HasContent, QuickXmlReadWrite, VOTableElement,
630  };
631
632  #[test]
633  fn test_create_in_mem_1() {
634    let rows = vec![
635      vec![
636        VOTableValue::Null, //VOTableValue::Double(f64::NAN),
637        VOTableValue::CharASCII('*'),
638        VOTableValue::Long(i64::max_value()),
639      ],
640      vec![
641        VOTableValue::Double(0.4581e+38),
642        VOTableValue::Null,
643        VOTableValue::Long(i64::min_value()),
644      ],
645      vec![
646        VOTableValue::Null,
647        VOTableValue::CharASCII('*'),
648        VOTableValue::Long(0),
649      ],
650    ];
651    let data_content = InMemTableDataRows::new(rows);
652
653    let table = Table::new()
654              .set_id("V_147_sdss12")
655              .set_name("V/147/sdss12")
656              .set_description("* output of the SDSS photometric catalog".into())
657              .push_field(
658                  Field::new("RA_ICRS", Datatype::Double)
659                    .set_unit("deg")
660                    .set_ucd("pos.eq.ra;meta.main")
661                    .set_ref("H")
662                    .set_width(10)
663                    .set_precision(Precision::new_dec(6))
664                    .set_description("Right Ascension of the object (ICRS) (ra)".into())
665                    .insert_extra("toto", Number::from_f64(0.5).map(Value::Number).unwrap_or(Value::Null))
666              ).push_field(
667                Field::new("m_SDSS12", Datatype::CharASCII)
668                  .set_ucd("meta.code.multip")
669                  .set_width(1)
670                  .set_description("[*] The asterisk indicates that 2 different SDSS objects share the same SDSS12 name".into())
671                  .push_link(Link::new().set_href("http://vizier.u-strasbg.fr/viz-bin/VizieR-4?-info=XML&amp;-out.add=.&amp;-source=V/147&amp;SDSS12=${SDSS12}"))
672            ).push_field(
673                Field::new("umag", Datatype::LongInt)
674                  .set_unit("mag")
675                  .set_ucd("phot.mag;em.opt.U")
676                  .set_description("[4/38]? Model magnitude in u filter, AB scale (u) (5)".into())
677                  .set_values(Values::new().set_null("NaN"))
678            ).set_data(Data::new_empty().set_tabledata(data_content));
679
680    let resource = Resource::default()
681      .set_id("yCat_17011219")
682      .set_name("J/ApJ/701/1219")
683      .set_description(
684        r#"Photometric and spectroscopic catalog of objects in the field around HE0226-4110"#
685          .into(),
686      )
687      .push_coosys(CooSys::new("J2000", System::new_default_eq_fk5()))
688      .push_coosys(CooSys::new("J2015.5", System::new_icrs().set_epoch(2015.5)))
689      .insert_extra(
690        "toto",
691        Number::from_f64(0.5)
692          .map(Value::Number)
693          .unwrap_or(Value::Null),
694      )
695      .push_sub_elem(
696        ResourceSubElem::from_table(table)
697          .push_info(Info::new("matches", "50").set_content("matching records"))
698          .push_info(Info::new("Warning", "No center provided++++"))
699          .push_info(Info::new("Warning", "truncated result (maxtup=50)"))
700          .push_info(
701            Info::new("QUERY_STATUS", "OVERFLOW").set_content("truncated result (maxtup=50)"),
702          ),
703      );
704
705    let mut votable = VOTable::new(Version::V1_4, resource)
706              .set_id("my_votable")
707              .set_description(r#"
708    VizieR Astronomical Server vizier.u-strasbg.fr
709    Date: 2022-04-13T06:55:08 [V1.99+ (14-Oct-2013)]
710    Explanations and Statistics of UCDs:			See LINK below
711    In case of problem, please report to:	cds-question@unistra.fr
712    In this version, NULL integer columns are written as an empty string
713    &lt;TD&gt;&lt;/TD&gt;, explicitely possible from VOTable-1.3
714    "#.into()
715              )
716              .push_info(Info::new("votable-version", "1.99+ (14-Oct-2013)").set_id("VERSION"))
717              .push_info(Info::new("queryParameters", "25")
718                .set_content(r#"
719    -oc.form=dec
720    -out.max=50
721    -out.all=2
722    -nav=cat:J/ApJ/701/1219&amp;tab:{J/ApJ/701/1219/table4}&amp;key:source=J/ApJ/701/1219&amp;HTTPPRM:&amp;
723    -c.eq=J2000
724    -c.r=  2
725    -c.u=arcmin
726    -c.geom=r
727    -source=J/ApJ/701/1219/table4
728    -order=I
729    -out=ID
730    -out=RAJ2000
731    -out=DEJ2000
732    -out=Sep
733    -out=Dist
734    -out=Bmag
735    -out=e_Bmag
736    -out=Rmag
737    -out=e_Rmag
738    -out=Imag
739    -out=e_Imag
740    -out=z
741    -out=Type
742    -out=RMag
743    -out.all=2
744        "#));
745
746    println!("\n\n#### JSON ####\n");
747
748    match serde_json::to_string_pretty(&votable) {
749      Ok(content) => {
750        // println!("{}", &content);
751        let mut votable2 =
752          serde_json::de::from_str::<VOTable<InMemTableDataRows>>(content.as_str()).unwrap();
753        votable2.ensures_consistency().unwrap();
754        let content2 = serde_json::to_string_pretty(&votable2).unwrap();
755
756        // println!("Content1: {}", content);
757        // println!("Content2: {}", content2);
758
759        assert_eq!(content, content2);
760        // To solve this, we have to implement either:
761        // * a Deserialiser with Seed on Table::data from the table schema
762        // * a post processing replacing the FieldValue by the righ objects (given the table schema)
763        assert_eq!(votable, votable2);
764      }
765      Err(error) => {
766        println!("{:?}", &error);
767        assert!(false);
768      }
769    }
770
771    println!("\n\n#### YAML ####\n");
772
773    match serde_yaml::to_string(&votable) {
774      Ok(content) => {
775        // println!("{}", &content);
776        let votable2 =
777          serde_yaml::from_str::<VOTable<InMemTableDataRows>>(content.as_str()).unwrap();
778        let content2 = serde_yaml::to_string(&votable2).unwrap();
779        assert_eq!(content, content2);
780      }
781      Err(error) => {
782        println!("{:?}", &error);
783        assert!(false);
784      }
785    }
786
787    println!("\n\n#### VOTABLE ####\n");
788
789    let mut content = Vec::new();
790    let mut write = Writer::new_with_indent(/*stdout()*/ &mut content, b' ', 4);
791    match votable.write(&mut write, &()) {
792      Ok(_) => {
793        // println!("{}", from_utf8(content.as_slice()).unwrap());
794
795        let mut votable2 = VOTable::<InMemTableDataRows>::from_reader(content.as_slice()).unwrap();
796        let mut content2 = Vec::new();
797        let mut write2 = Writer::new_with_indent(&mut content2, b' ', 4);
798        votable2.write(&mut write2, &()).unwrap();
799
800        eprintln!("CONTENT1:\n{}", from_utf8(content.as_slice()).unwrap());
801        eprintln!("CONTENT2:\n{}", from_utf8(content2.as_slice()).unwrap());
802
803        assert_eq!(content, content2);
804      }
805      Err(error) => {
806        println!("Error: {:?}", &error);
807        assert!(false);
808      }
809    }
810
811    println!("\n\n#### TOML ####\n");
812
813    match toml::ser::to_string_pretty(&votable) {
814      Ok(content) => {
815        // println!("{}", &content);
816        let votable2 = toml::de::from_str::<VOTable<InMemTableDataRows>>(content.as_str()).unwrap();
817        let content2 = toml::ser::to_string_pretty(&votable2).unwrap();
818        assert_eq!(content, content2);
819      }
820      Err(error) => {
821        println!("{:?}", &error);
822        assert!(false);
823      }
824    }
825
826    /*println!("\n\n#### XML ####\n");
827
828    match quick_xml::se::to_string(&votable) {
829      Ok(content) => println!("{}", &content),
830      Err(error) => println!("{:?}", &error),
831    }*/
832
833    // AVRO ?
834  }
835
836  // not a test, used for the README.md
837  #[test]
838  fn test_create_in_mem_simple() {
839    let rows = vec![
840      vec![
841        VOTableValue::Double(f64::NAN),
842        VOTableValue::CharASCII('*'),
843        VOTableValue::Float(14.52),
844      ],
845      vec![
846        VOTableValue::Double(1.25),
847        VOTableValue::Null,
848        VOTableValue::Float(-1.2),
849      ],
850    ];
851    let data_content = InMemTableDataRows::new(rows);
852    let table = Table::new()
853        .set_id("V_147_sdss12")
854        .set_name("V/147/sdss12")
855        .set_description("SDSS photometric catalog".into())
856        .push_field(
857            Field::new("RA_ICRS", Datatype::Double)
858              .set_unit("deg")
859              .set_ucd("pos.eq.ra;meta.main")
860              .set_width(10)
861              .set_precision(Precision::new_dec(6))
862              .set_description("Right Ascension of the object (ICRS) (ra)".into())
863        ).push_field(
864          Field::new("m_SDSS12", Datatype::CharASCII)
865            .set_ucd("meta.code.multip")
866            .set_arraysize(ArraySize::new_fixed_1d(1))
867            .set_width(10)
868            .set_precision(Precision::new_dec(6))
869            .set_description("[*] Multiple SDSS12 name".into())
870            .push_link(Link::new().set_href("http://vizier.u-strasbg.fr/viz-bin/VizieR-4?-info=XML&-out.add=.&-source=V/147&SDSS12=${SDSS12}"))
871      ).push_field(
872          Field::new("umag", Datatype::Float)
873            .set_unit("mag")
874            .set_ucd("phot.mag;em.opt.U")
875            .set_width(2)
876            .set_precision(Precision::new_dec(3))
877            .set_description("[4/38]? Model magnitude in u filter, AB scale (u) (5)".into())
878            .set_values(Values::new().set_null("NaN"))
879      ).set_data(Data::new_empty().set_tabledata(data_content));
880
881    let resource = Resource::default()
882      .set_id("yCat_17011219")
883      .set_name("J/ApJ/701/1219")
884      .set_description(
885        r#"Photometric and spectroscopic catalog of objects in the field around HE0226-4110"#
886          .into(),
887      )
888      .push_coosys(CooSys::new("J2000", System::new_default_eq_fk5()))
889      .push_coosys(CooSys::new("J2015.5", System::new_icrs().set_epoch(2015.5)))
890      .push_sub_elem(ResourceSubElem::from_table(table).push_info(
891        Info::new("QUERY_STATUS", "OVERFLOW").set_content("truncated result (maxtup=2)"),
892      ));
893
894    let votable = VOTable::new(Version::V1_4, resource)
895      .set_id("my_votable")
896      .set_description(r#"VizieR Astronomical Server vizier.u-strasbg.fr"#.into())
897      .push_info(Info::new("votable-version", "1.99+ (14-Oct-2013)").set_id("VERSION"))
898      .wrap();
899
900    println!("\n\n#### JSON ####\n");
901
902    match serde_json::to_string_pretty(&votable) {
903      Ok(content) => {
904        println!("{}", &content);
905      }
906      Err(error) => println!("{:?}", &error),
907    }
908
909    println!("\n\n#### YAML ####\n");
910
911    match serde_yaml::to_string(&votable) {
912      Ok(content) => {
913        println!("{}", &content);
914      }
915      Err(error) => println!("{:?}", &error),
916    }
917
918    println!("\n\n#### TOML ####\n");
919
920    match toml::ser::to_string_pretty(&votable) {
921      Ok(content) => {
922        println!("{}", &content);
923      }
924      Err(error) => println!("{:?}", &error),
925    }
926
927    println!("\n\n#### VOTABLE ####\n");
928    let mut write = Writer::new_with_indent(std::io::stdout(), b' ', 4);
929    match votable.unwrap().write(&mut write, &()) {
930      Ok(_) => println!("\nOK"),
931      Err(error) => println!("Error: {:?}", &error),
932    }
933
934    /*println!("\n\n#### XML ####\n");
935    match quick_xml::se::to_string(&votable) {
936      Ok(content) => println!("{}", &content),
937      Err(error) => println!("{:?}", &error),
938    }*/
939    // AVRO ?
940  }
941
942  pub(crate) fn test_read<X>(xml: &str) -> X
943  where
944    X: VOTableElement + QuickXmlReadWrite<<X as VOTableElement>::MarkerType, Context = ()>,
945  {
946    let mut reader = Reader::from_reader(Cursor::new(xml.as_bytes()));
947    let mut buff: Vec<u8> = Vec::with_capacity(xml.len());
948    loop {
949      let mut event = reader.read_event(&mut buff).unwrap();
950      match &mut event {
951        Event::Start(e) if e.local_name() == X::TAG_BYTES => {
952          match X::from_event_start(e)
953            .and_then(|info| info.read_content(&mut reader, &mut buff, &()))
954          {
955            Err(e) => {
956              eprintln!("Error: {}", e.to_string());
957              assert!(false);
958            }
959            Ok(info) => return info,
960          }
961        }
962        Event::Empty(e) if e.local_name() == X::TAG_BYTES => {
963          let info = X::from_event_start(e).unwrap();
964          return info;
965        }
966        Event::Text(e) if e.escaped().is_empty() => (), // First even read
967        Event::Comment(_) => (),
968        Event::DocType(_) => (),
969        Event::Decl(_) => (),
970        _ => {
971          println!("{:?}", event);
972          assert!(false);
973        }
974      }
975    }
976  }
977
978  pub(crate) fn test_writer<X>(mut writable: X, xml: &str)
979  where
980    X: VOTableElement + QuickXmlReadWrite<<X as VOTableElement>::MarkerType, Context = ()>,
981  {
982    // Test write
983    let mut writer = Writer::new(Cursor::new(Vec::new()));
984    writable.write(&mut writer, &()).unwrap();
985    let output = writer.into_inner().into_inner();
986    let output_str = unsafe { std::str::from_utf8_unchecked(output.as_slice()) };
987    assert_eq!(output_str, xml);
988  }
989}