votable/
group.rs

1//! Struct dedicated to the `GROUP` tag.
2
3use std::{
4  io::{BufRead, Write},
5  str,
6};
7
8use log::warn;
9use paste::paste;
10use quick_xml::{events::Event, Reader, Writer};
11
12use super::{
13  desc::Description,
14  error::VOTableError,
15  fieldref::FieldRef,
16  param::Param,
17  paramref::ParamRef,
18  utils::{discard_comment, discard_event, unexpected_attr_warn},
19  HasSubElements, HasSubElems, QuickXmlReadWrite, TableDataContent, VOTableElement, VOTableVisitor,
20};
21
22#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
23#[serde(tag = "elem_type")]
24pub enum GroupElem {
25  ParamRef(Box<ParamRef>),
26  Param(Box<Param>),
27  Group(Box<Group>),
28}
29
30impl GroupElem {
31  fn write<W: Write>(&mut self, writer: &mut Writer<W>) -> Result<(), VOTableError> {
32    match self {
33      GroupElem::ParamRef(elem) => elem.write(writer, &()),
34      GroupElem::Param(elem) => elem.write(writer, &()),
35      GroupElem::Group(elem) => elem.write(writer, &()),
36    }
37  }
38  pub fn visit<C, V>(&mut self, visitor: &mut V) -> Result<(), V::E>
39  where
40    C: TableDataContent,
41    V: VOTableVisitor<C>,
42  {
43    match self {
44      GroupElem::ParamRef(e) => visitor.visit_paramref(e),
45      GroupElem::Param(e) => e.visit(visitor),
46      GroupElem::Group(e) => e.visit(visitor),
47    }
48  }
49}
50
51/// Struct corresponding to the `GROUP` XML tag when it is in a `VOTABLE` or a `RESOURCE`.
52#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
53pub struct Group {
54  // attributes
55  #[serde(rename = "ID", skip_serializing_if = "Option::is_none")]
56  pub id: Option<String>,
57  #[serde(skip_serializing_if = "Option::is_none")]
58  pub name: Option<String>,
59  #[serde(rename = "ref", skip_serializing_if = "Option::is_none")]
60  pub ref_: Option<String>,
61  #[serde(skip_serializing_if = "Option::is_none")]
62  pub ucd: Option<String>,
63  #[serde(skip_serializing_if = "Option::is_none")]
64  pub utype: Option<String>,
65  // Sub-elems
66  #[serde(skip_serializing_if = "Option::is_none")]
67  pub description: Option<Description>,
68  #[serde(default, skip_serializing_if = "Vec::is_empty")]
69  pub elems: Vec<GroupElem>,
70}
71
72impl Default for Group {
73  fn default() -> Self {
74    Group::new()
75  }
76}
77
78impl Group {
79  pub fn new() -> Self {
80    Group {
81      id: None,
82      name: None,
83      ref_: None,
84      ucd: None,
85      utype: None,
86      description: None,
87      elems: vec![],
88    }
89  }
90
91  // attributes
92  impl_builder_opt_string_attr!(id);
93  impl_builder_opt_string_attr!(name);
94  impl_builder_opt_string_attr!(ref_, ref);
95  impl_builder_opt_string_attr!(ucd);
96  impl_builder_opt_string_attr!(utype);
97  // sub-elements
98  impl_builder_opt_subelem!(description, Description);
99  impl_builder_push_boxed_elem!(ParamRef, GroupElem);
100  impl_builder_push_boxed_elem!(Param, GroupElem);
101  impl_builder_push_boxed_elem!(Group, GroupElem);
102
103  pub fn visit<C, V>(&mut self, visitor: &mut V) -> Result<(), V::E>
104  where
105    C: TableDataContent,
106    V: VOTableVisitor<C>,
107  {
108    visitor.visit_group_start(self)?;
109    if let Some(descrition) = &mut self.description {
110      visitor.visit_description(descrition)?;
111    }
112    for elem in &mut self.elems {
113      elem.visit(visitor)?;
114    }
115    visitor.visit_group_ended(self)
116  }
117}
118
119impl VOTableElement for Group {
120  const TAG: &'static str = "GROUP";
121
122  type MarkerType = HasSubElems;
123
124  fn from_attrs<K, V, I>(attrs: I) -> Result<Self, VOTableError>
125  where
126    K: AsRef<str> + Into<String>,
127    V: AsRef<str> + Into<String>,
128    I: Iterator<Item = (K, V)>,
129  {
130    Self::new().set_attrs(attrs)
131  }
132
133  fn set_attrs_by_ref<K, V, I>(&mut self, attrs: I) -> Result<(), VOTableError>
134  where
135    K: AsRef<str> + Into<String>,
136    V: AsRef<str> + Into<String>,
137    I: Iterator<Item = (K, V)>,
138  {
139    for (key, val) in attrs {
140      let key = key.as_ref();
141      match key {
142        "ID" => self.set_id_by_ref(val),
143        "name" => self.set_name_by_ref(val),
144        "ref" => self.set_ref_by_ref(val),
145        "ucd" => self.set_ucd_by_ref(val),
146        "utype" => self.set_utype_by_ref(val),
147        _ => unexpected_attr_warn(key, Self::TAG),
148      }
149    }
150    Ok(())
151  }
152
153  fn for_each_attribute<F>(&self, mut f: F)
154  where
155    F: FnMut(&str, &str),
156  {
157    if let Some(id) = &self.id {
158      f("ID", id.as_str());
159    }
160    if let Some(name) = &self.name {
161      f("name", name.as_str());
162    }
163    if let Some(r) = &self.ref_ {
164      f("ref", r.as_str());
165    }
166    if let Some(ucd) = &self.ucd {
167      f("ucd", ucd.as_str());
168    }
169    if let Some(utype) = &self.utype {
170      f("utype", utype.as_str());
171    }
172  }
173}
174
175impl HasSubElements for Group {
176  type Context = ();
177
178  fn has_no_sub_elements(&self) -> bool {
179    self.description.is_none() && self.elems.is_empty()
180  }
181
182  fn read_sub_elements_by_ref<R: BufRead>(
183    &mut self,
184    mut reader: &mut Reader<R>,
185    mut reader_buff: &mut Vec<u8>,
186    _context: &Self::Context,
187  ) -> Result<(), VOTableError> {
188    loop {
189      let mut event = reader.read_event(reader_buff).map_err(VOTableError::Read)?;
190      match &mut event {
191        Event::Start(e) => match e.local_name() {
192          Description::TAG_BYTES => set_desc_from_event_start!(self, reader, reader_buff, e),
193          ParamRef::TAG_BYTES => push_from_event_start!(self, ParamRef, reader, reader_buff, e),
194          Param::TAG_BYTES => push_from_event_start!(self, Param, reader, reader_buff, e),
195          Group::TAG_BYTES => push_from_event_start!(self, Group, reader, reader_buff, e),
196          _ => {
197            return Err(VOTableError::UnexpectedStartTag(
198              e.local_name().to_vec(),
199              Self::TAG,
200            ))
201          }
202        },
203        Event::Empty(e) => match e.local_name() {
204          ParamRef::TAG_BYTES => push_from_event_empty!(self, ParamRef, e),
205          Param::TAG_BYTES => push_from_event_empty!(self, Param, e),
206          _ => {
207            return Err(VOTableError::UnexpectedEmptyTag(
208              e.local_name().to_vec(),
209              Self::TAG,
210            ))
211          }
212        },
213        Event::End(e) if e.local_name() == Self::TAG_BYTES => return Ok(()),
214        Event::Eof => return Err(VOTableError::PrematureEOF(Self::TAG)),
215        Event::Comment(e) => discard_comment(e, reader, Self::TAG),
216        _ => discard_event(event, Self::TAG),
217      }
218    }
219  }
220
221  fn write_sub_elements_by_ref<W: Write>(
222    &mut self,
223    writer: &mut Writer<W>,
224    context: &Self::Context,
225  ) -> Result<(), VOTableError> {
226    write_elem!(self, description, writer, context);
227    write_elem_vec_no_context!(self, elems, writer);
228    Ok(())
229  }
230}
231
232/// The only difference with the Group than can be in a VOTable or in a Resource
233/// is that the TableGroup can contain FieldRef!
234#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
235#[serde(tag = "elem_type")]
236pub enum TableGroupElem {
237  FieldRef(Box<FieldRef>),
238  ParamRef(Box<ParamRef>),
239  Param(Box<Param>),
240  TableGroup(Box<TableGroup>),
241}
242
243impl TableGroupElem {
244  fn write<W: Write>(&mut self, writer: &mut Writer<W>) -> Result<(), VOTableError> {
245    match self {
246      TableGroupElem::FieldRef(elem) => elem.write(writer, &()),
247      TableGroupElem::ParamRef(elem) => elem.write(writer, &()),
248      TableGroupElem::Param(elem) => elem.write(writer, &()),
249      TableGroupElem::TableGroup(elem) => elem.write(writer, &()),
250    }
251  }
252  pub fn visit<C, V>(&mut self, visitor: &mut V) -> Result<(), V::E>
253  where
254    C: TableDataContent,
255    V: VOTableVisitor<C>,
256  {
257    match self {
258      TableGroupElem::FieldRef(e) => visitor.visit_fieldref(e),
259      TableGroupElem::ParamRef(e) => visitor.visit_paramref(e),
260      TableGroupElem::Param(e) => e.visit(visitor),
261      TableGroupElem::TableGroup(e) => e.visit(visitor),
262    }
263  }
264}
265
266/// Struct corresponding to the `GROUP` XML tag when it is in a `TABLE`.
267#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
268#[serde(rename = "Group")]
269pub struct TableGroup {
270  // attributes
271  #[serde(skip_serializing_if = "Option::is_none")]
272  pub id: Option<String>,
273  #[serde(skip_serializing_if = "Option::is_none")]
274  pub name: Option<String>,
275  #[serde(rename = "ref", skip_serializing_if = "Option::is_none")]
276  pub ref_: Option<String>,
277  #[serde(skip_serializing_if = "Option::is_none")]
278  pub ucd: Option<String>,
279  #[serde(skip_serializing_if = "Option::is_none")]
280  pub utype: Option<String>,
281  // Sub-elems
282  #[serde(skip_serializing_if = "Option::is_none")]
283  pub description: Option<Description>,
284  #[serde(skip_serializing_if = "Vec::is_empty")]
285  pub elems: Vec<TableGroupElem>,
286}
287
288impl Default for TableGroup {
289  fn default() -> Self {
290    TableGroup::new()
291  }
292}
293
294impl TableGroup {
295  pub fn new() -> Self {
296    TableGroup {
297      id: None,
298      name: None,
299      ref_: None,
300      ucd: None,
301      utype: None,
302      description: None,
303      elems: vec![],
304    }
305  }
306
307  // attributes
308  impl_builder_opt_string_attr!(id);
309  impl_builder_opt_string_attr!(name);
310  impl_builder_opt_string_attr!(ref_, ref);
311  impl_builder_opt_string_attr!(ucd);
312  impl_builder_opt_string_attr!(utype);
313  // sub-elements
314  impl_builder_opt_subelem!(description, Description);
315  impl_builder_push_boxed_elem!(FieldRef, TableGroupElem);
316  impl_builder_push_boxed_elem!(ParamRef, TableGroupElem);
317  impl_builder_push_boxed_elem!(Param, TableGroupElem);
318  impl_builder_push_boxed_elem!(TableGroup, TableGroupElem);
319
320  pub fn visit<C, V>(&mut self, visitor: &mut V) -> Result<(), V::E>
321  where
322    C: TableDataContent,
323    V: VOTableVisitor<C>,
324  {
325    visitor.visit_table_group_start(self)?;
326    if let Some(desc) = &mut self.description {
327      visitor.visit_description(desc)?;
328    }
329    for e in &mut self.elems {
330      e.visit(visitor)?;
331    }
332    visitor.visit_table_group_ended(self)
333  }
334}
335
336impl VOTableElement for TableGroup {
337  const TAG: &'static str = "GROUP";
338
339  type MarkerType = HasSubElems;
340
341  fn from_attrs<K, V, I>(attrs: I) -> Result<Self, VOTableError>
342  where
343    K: AsRef<str> + Into<String>,
344    V: AsRef<str> + Into<String>,
345    I: Iterator<Item = (K, V)>,
346  {
347    Self::new().set_attrs(attrs)
348  }
349
350  fn set_attrs_by_ref<K, V, I>(&mut self, attrs: I) -> Result<(), VOTableError>
351  where
352    K: AsRef<str> + Into<String>,
353    V: AsRef<str> + Into<String>,
354    I: Iterator<Item = (K, V)>,
355  {
356    for (key, val) in attrs {
357      let key = key.as_ref();
358      match key {
359        "ID" => self.set_id_by_ref(val),
360        "name" => self.set_name_by_ref(val),
361        "ref" => self.set_ref_by_ref(val),
362        "ucd" => self.set_ucd_by_ref(val),
363        "utype" => self.set_utype_by_ref(val),
364        _ => unexpected_attr_warn(key, Self::TAG),
365      }
366    }
367    Ok(())
368  }
369
370  fn for_each_attribute<F>(&self, mut f: F)
371  where
372    F: FnMut(&str, &str),
373  {
374    if let Some(id) = &self.id {
375      f("ID", id.as_str());
376    }
377    if let Some(name) = &self.name {
378      f("name", name.as_str());
379    }
380    if let Some(r) = &self.ref_ {
381      f("ref", r.as_str());
382    }
383    if let Some(ucd) = &self.ucd {
384      f("ucd", ucd.as_str());
385    }
386    if let Some(utype) = &self.utype {
387      f("utype", utype.as_str());
388    }
389  }
390}
391
392impl HasSubElements for TableGroup {
393  type Context = ();
394
395  fn has_no_sub_elements(&self) -> bool {
396    self.description.is_none() && self.elems.is_empty()
397  }
398
399  fn read_sub_elements_by_ref<R: BufRead>(
400    &mut self,
401    mut reader: &mut Reader<R>,
402    mut reader_buff: &mut Vec<u8>,
403    _context: &Self::Context,
404  ) -> Result<(), VOTableError> {
405    loop {
406      let mut event = reader.read_event(reader_buff).map_err(VOTableError::Read)?;
407      match &mut event {
408        Event::Start(e) => match e.local_name() {
409          Description::TAG_BYTES => set_desc_from_event_start!(self, reader, reader_buff, e),
410          FieldRef::TAG_BYTES => push_from_event_start!(self, FieldRef, reader, reader_buff, e),
411          ParamRef::TAG_BYTES => push_from_event_start!(self, ParamRef, reader, reader_buff, e),
412          Param::TAG_BYTES => push_from_event_start!(self, Param, reader, reader_buff, e),
413          TableGroup::TAG_BYTES => push_from_event_start!(self, TableGroup, reader, reader_buff, e),
414          _ => {
415            return Err(VOTableError::UnexpectedStartTag(
416              e.local_name().to_vec(),
417              Self::TAG,
418            ))
419          }
420        },
421        Event::Empty(e) => match e.local_name() {
422          FieldRef::TAG_BYTES => push_from_event_empty!(self, FieldRef, e),
423          ParamRef::TAG_BYTES => push_from_event_empty!(self, ParamRef, e),
424          Param::TAG_BYTES => push_from_event_empty!(self, Param, e),
425          _ => {
426            return Err(VOTableError::UnexpectedEmptyTag(
427              e.local_name().to_vec(),
428              Self::TAG,
429            ))
430          }
431        },
432        Event::End(e) if e.local_name() == Self::TAG_BYTES => return Ok(()),
433        Event::Eof => return Err(VOTableError::PrematureEOF(Self::TAG)),
434        Event::Comment(e) => discard_comment(e, reader, Self::TAG),
435        _ => discard_event(event, Self::TAG),
436      }
437    }
438  }
439
440  fn write_sub_elements_by_ref<W: Write>(
441    &mut self,
442    writer: &mut Writer<W>,
443    context: &Self::Context,
444  ) -> Result<(), VOTableError> {
445    write_elem!(self, description, writer, context);
446    write_elem_vec_no_context!(self, elems, writer);
447    Ok(())
448  }
449}
450
451#[cfg(test)]
452mod tests {
453  use crate::{
454    group::Group,
455    tests::{test_read, test_writer},
456  };
457
458  #[test]
459  fn test_group_read_write() {
460    let xml = r#"<GROUP ID="flux" name="Flux" ucd="phot.flux;em.radio.200-400MHz"><DESCRIPTION>Flux measured at 352MHz</DESCRIPTION><PARAM name="Freq" datatype="float" value="352" ucd="em.freq" utype="MHz"/><PARAMref ref="col4"/><PARAMref ref="col5"/></GROUP>"#;
461    let group = test_read::<Group>(xml);
462    assert_eq!(group.id, Some("flux".to_string()));
463    assert_eq!(group.name, Some("Flux".to_string()));
464    assert_eq!(group.ucd, Some("phot.flux;em.radio.200-400MHz".to_string()));
465    assert_eq!(
466      group.description.as_ref().unwrap().get_content_unwrapped(),
467      "Flux measured at 352MHz"
468    );
469    assert_eq!(group.elems.len(), 3);
470    test_writer(group, xml);
471  }
472}