votable/
param.rs

1//! Struct dedicated to the `PARAM` 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};
11use serde_json::Value;
12
13use super::{
14  datatype::Datatype,
15  desc::Description,
16  error::VOTableError,
17  field::{ArraySize, Field, Precision},
18  link::Link,
19  utils::{discard_comment, discard_event},
20  values::Values,
21  HasSubElements, HasSubElems, QuickXmlReadWrite, TableDataContent, VOTableElement, VOTableVisitor,
22};
23
24/// Struct corresponding to the `PARAM` XML tag.
25#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
26pub struct Param {
27  #[serde(flatten)]
28  pub field: Field,
29  pub value: String,
30}
31
32impl Param {
33  pub fn new<N: Into<String>, V: Into<String>>(name: N, datatype: Datatype, value: V) -> Self {
34    Param {
35      field: Field::new(name, datatype),
36      value: value.into(),
37    }
38  }
39
40  // attributes
41  impl_builder_opt_string_attr_delegated!(id, field);
42  impl_builder_mandatory_string_attr_delegated!(name, field);
43  impl_builder_mandatory_attr_delegated!(datatype, Datatype, field);
44  impl_builder_opt_string_attr_delegated!(unit, field);
45  impl_builder_opt_attr_delegated!(precision, Precision, field);
46  impl_builder_opt_attr_delegated!(width, u16, field);
47  impl_builder_opt_string_attr_delegated!(xtype, field);
48  impl_builder_opt_string_attr_delegated!(ref_, ref, field);
49  impl_builder_opt_string_attr_delegated!(ucd, field);
50  impl_builder_opt_string_attr_delegated!(utype, field);
51  impl_builder_opt_attr_delegated!(arraysize, ArraySize, field);
52  impl_builder_mandatory_string_attr!(value);
53  // extra attributes
54  impl_builder_insert_extra_delegated!(field);
55  // sub-elements
56  impl_builder_opt_subelem_delegated!(description, Description, field);
57  impl_builder_opt_subelem_delegated!(values, Values, field);
58  impl_builder_push_delegated!(Link, field);
59
60  pub fn visit<C, V>(&mut self, visitor: &mut V) -> Result<(), V::E>
61  where
62    C: TableDataContent,
63    V: VOTableVisitor<C>,
64  {
65    visitor.visit_param_start(self)?;
66    if let Some(description) = &mut self.field.description {
67      visitor.visit_description(description)?;
68    }
69    if let Some(values) = &mut self.field.values {
70      values.visit(visitor)?;
71    }
72    for l in &mut self.field.links {
73      visitor.visit_link(l)?;
74    }
75    visitor.visit_param_ended(self)
76  }
77}
78
79impl VOTableElement for Param {
80  const TAG: &'static str = "PARAM";
81
82  type MarkerType = HasSubElems;
83
84  fn from_attrs<K, V, I>(attrs: I) -> Result<Self, VOTableError>
85  where
86    K: AsRef<str> + Into<String>,
87    V: AsRef<str> + Into<String>,
88    I: Iterator<Item = (K, V)>,
89  {
90    const DEFAULT_VALUE: &str = "@TBD";
91    const DEFAULT_DT: Datatype = Datatype::Logical;
92    let mut name_found = false;
93    let mut dt_found = false;
94    let mut val_found = false;
95    Self::new(DEFAULT_VALUE, DEFAULT_DT, DEFAULT_VALUE)
96      .set_attrs(attrs.map(|(k, v)| {
97        match k.as_ref() {
98          "name" => name_found = true,
99          "datatype" => dt_found = true,
100          "value" => val_found = true,
101          _ => {}
102        };
103        (k, v)
104      }))
105      .and_then(|param| {
106        if name_found && dt_found && val_found {
107          Ok(param)
108        } else {
109          Err(VOTableError::Custom(format!(
110            "Attributes 'name', 'datatype' and 'value' are mandatory in tag '{}'",
111            Self::TAG
112          )))
113        }
114      })
115  }
116
117  fn set_attrs_by_ref<K, V, I>(&mut self, attrs: I) -> Result<(), VOTableError>
118  where
119    K: AsRef<str> + Into<String>,
120    V: AsRef<str> + Into<String>,
121    I: Iterator<Item = (K, V)>,
122  {
123    for (key, val) in attrs {
124      let key = key.as_ref();
125      match key {
126        "ID" => self.set_id_by_ref(val),
127        "name" => self.set_name_by_ref(val),
128        "datatype" => {
129          self.set_datatype_by_ref(val.as_ref().parse().map_err(VOTableError::ParseDatatype)?)
130        }
131        "unit" => self.set_unit_by_ref(val),
132        "precision" => {
133          if val.as_ref().is_empty() {
134            warn!(
135              "Emtpy 'precision' attribute in tag {}: attribute ignored",
136              Self::TAG
137            )
138          } else {
139            self.set_precision_by_ref(val.as_ref().parse().map_err(VOTableError::ParseInt)?)
140          }
141        }
142        "width" => self.set_width_by_ref(val.as_ref().parse().map_err(VOTableError::ParseInt)?),
143        "xtype" => self.set_xtype_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        "arraysize" => {
148          self.set_arraysize_by_ref(val.as_ref().parse().map_err(VOTableError::ParseInt)?)
149        }
150        "value" => self.set_value_by_ref(val),
151        _ => self.insert_extra_str_by_ref(key, val),
152      }
153    }
154    Ok(())
155  }
156
157  fn for_each_attribute<F>(&self, mut f: F)
158  where
159    F: FnMut(&str, &str),
160  {
161    if let Some(id) = &self.field.id {
162      f("ID", id.as_str());
163    }
164    f("name", self.field.name.as_str());
165    f("datatype", self.field.datatype.to_string().as_str());
166    f("value", self.value.as_str());
167    if let Some(arraysize) = &self.field.arraysize {
168      f("arraysize", arraysize.to_string().as_str());
169    }
170    if let Some(width) = &self.field.width {
171      f("width", width.to_string().as_str());
172    }
173    if let Some(precision) = &self.field.precision {
174      f("precision", precision.to_string().as_str());
175    }
176    if let Some(unit) = &self.field.unit {
177      f("unit", unit.as_str());
178    }
179    if let Some(ucd) = &self.field.ucd {
180      f("ucd", ucd.as_str());
181    }
182    if let Some(utype) = &self.field.utype {
183      f("utype", utype.as_str());
184    }
185    if let Some(xtype) = &self.field.xtype {
186      f("xtype", xtype.as_str());
187    }
188    if let Some(ref_) = &self.field.ref_ {
189      f("ref", ref_.as_str());
190    }
191    for_each_extra_attribute_delegated!(self, field, f);
192  }
193}
194
195impl HasSubElements for Param {
196  type Context = ();
197
198  fn has_no_sub_elements(&self) -> bool {
199    self.field.has_no_sub_elements()
200  }
201
202  fn read_sub_elements_by_ref<R: BufRead>(
203    &mut self,
204    mut reader: &mut Reader<R>,
205    mut reader_buff: &mut Vec<u8>,
206    _context: &Self::Context,
207  ) -> Result<(), VOTableError> {
208    loop {
209      let mut event = reader.read_event(reader_buff).map_err(VOTableError::Read)?;
210      match &mut event {
211        Event::Start(ref e) => match e.local_name() {
212          Description::TAG_BYTES => {
213            set_from_event_start!(self, Description, reader, reader_buff, e)
214          }
215          Values::TAG_BYTES => set_from_event_start!(self, Values, reader, reader_buff, e),
216          Link::TAG_BYTES => push_from_event_start!(self, Link, reader, reader_buff, e),
217          _ => {
218            return Err(VOTableError::UnexpectedStartTag(
219              e.local_name().to_vec(),
220              Self::TAG,
221            ))
222          }
223        },
224        Event::Empty(ref e) => match e.local_name() {
225          Values::TAG_BYTES => set_from_event_empty!(self, Values, e),
226          Link::TAG_BYTES => push_from_event_empty!(self, Link, e),
227          _ => {
228            return Err(VOTableError::UnexpectedEmptyTag(
229              e.local_name().to_vec(),
230              Self::TAG,
231            ))
232          }
233        },
234        Event::End(e) if e.local_name() == Self::TAG_BYTES => return Ok(()),
235        Event::Eof => return Err(VOTableError::PrematureEOF(Self::TAG)),
236        Event::Comment(e) => discard_comment(e, reader, Self::TAG),
237        _ => discard_event(event, Self::TAG),
238      }
239    }
240  }
241
242  fn write_sub_elements_by_ref<W: Write>(
243    &mut self,
244    writer: &mut Writer<W>,
245    context: &Self::Context,
246  ) -> Result<(), VOTableError> {
247    self.field.write_sub_elements_by_ref(writer, context)
248  }
249}
250
251#[cfg(test)]
252mod tests {
253  use crate::{
254    param::Param,
255    tests::{test_read, test_writer},
256  };
257
258  #[test]
259  fn test_params_read_write() {
260    let xml = r#"<PARAM name="Freq" datatype="float" value="352" ucd="em.freq" utype="MHz"/>"#; // Test read
261    let param = test_read::<Param>(xml);
262    //Other parameters like name datatype etc... depend on Field reading, see Field read_write_test
263    assert_eq!(param.value.as_str(), "352");
264    // Test write
265    test_writer(param, xml)
266  }
267}