1use 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#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
53pub struct Group {
54 #[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 #[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 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 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#[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#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
268#[serde(rename = "Group")]
269pub struct TableGroup {
270 #[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 #[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 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 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}