imzml/mzml/
cvparam.rs

1use std::borrow::{Borrow, Cow};
2use std::collections::VecDeque;
3
4use quick_xml::events::{BytesStart, Event};
5use quick_xml::Decoder;
6use std::io::{BufRead, Write};
7
8use crate::error::Breadcrumbs;
9use crate::obo::{OBOTerm, Ontology, Term, ValueType};
10use crate::{FatalParseError, ParseError, Tag};
11
12use super::referenceableparamgroup::ReferenceableParamGroupRef;
13use super::writer::Writer;
14use super::{MzMLReader, MzMLTag};
15
16/// Searches cvParams of the tag, as well as referenceablrParamGroups for any cvParam with the specified accession
17pub fn search_all_params<'a, T: HasCVParams + HasParamGroupRefs>(
18    tag: &'a T,
19    accession: &str,
20) -> Option<&'a CVParam> {
21    match tag.search_param_groups(accession) {
22        Some(cv_param) => Some(cv_param),
23        None => match tag.search_cv_params(accession) {
24            Some(cv_param) => Some(cv_param),
25            None => None,
26        },
27    }
28}
29
30/// Searches cvParams of the tag, as well as referenceablrParamGroups for any cvParam with the specified accession
31pub fn children_of<'a, T: HasCVParams + HasParamGroupRefs>(
32    ontology: &'a Ontology,
33    tag: &'a T,
34    accession: &str,
35) -> Vec<&'a CVParam> {
36    let mut children = tag.children_of(ontology, accession);
37
38    for ref_group in tag.param_group_refs() {
39        children.extend(ref_group.as_ref().children_of(ontology, accession).iter());
40    }
41
42    children
43}
44
45/// Trait for tags which include <cvParam> tags. This allows adding, removing and writing of cvParam and userParam tags.
46pub trait HasCVParams {
47    // type ParamIterator: Iterator<Item = CVParam<'a>>;
48
49    // fn param_iter(&self) -> Self::ParamIterator {
50    //     self.cv_params().iter()
51    //     self.cv_params().iter()
52    // }
53
54    // fn cv_param_iter(&self) -> CVParamIterator;
55
56    /// Add a cvParam to the tag.
57    fn add_cv_param(&mut self, param: CVParam);
58    /// Return access to the list of cvParams currently associated with the tag.
59    fn cv_params(&self) -> &Vec<CVParam>;
60    /// Return mutable access to the list of cvParams currently associated with the tag.
61    fn cv_params_mut(&mut self) -> &mut Vec<CVParam>;
62
63    /// Clone the current parameters into a `Vec<CVParam<'a>>`.
64    fn clone_params(&self) -> Vec<CVParam> {
65        let mut params = Vec::new();
66
67        for param in self.cv_params() {
68            params.push(param.clone())
69        }
70
71        params
72    }
73
74    /// Remove any cvParam which has a term that is the child of the of the supplied term (parent_id).
75    fn remove_children_of(&mut self, ontology: &Ontology, parent_id: &str) {
76        let cv_params = self.cv_params_mut();
77
78        cv_params.retain(|cv_param| {
79            if let Some(term) = cv_param.term() {
80                !term.is_child_of(ontology, parent_id)
81            } else {
82                true
83            }
84        })
85    }
86
87    /// Return all cvParams which have a term that is the child of the of the supplied term (parent_id).
88    fn children_of(&self, ontology: &Ontology, parent_id: &str) -> Vec<&CVParam> {
89        let mut children = Vec::new();
90
91        for cv_param in self.cv_params() {
92            if let Some(term) = cv_param.term() {
93                if term.is_child_of(ontology, parent_id) {
94                    children.push(cv_param);
95                }
96            }
97        }
98
99        children
100    }
101
102    /// Add a userParam to the tag.
103    fn add_user_param(&mut self, param: UserParam);
104    /// Return access to all userParams.
105    fn user_params(&self) -> &Vec<UserParam>;
106
107    /// Return cvParam with supplied term (accession) if present, None otherwise.
108    fn search_cv_params(&self, accession: &str) -> Option<&CVParam> {
109        self.cv_params()
110            .iter()
111            .find(|&param| param.term.id() == accession)
112    }
113
114    /// Write the params (cvParam and userParam) as XML to the supplied writer.
115    fn write_params_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<(), quick_xml::Error> {
116        for param in self.cv_params() {
117            param.write_xml(writer)?;
118        }
119
120        for param in self.user_params() {
121            param.write_xml(writer)?;
122        }
123
124        Ok(())
125    }
126}
127
128/// Tag can include referenceableParamGroupRef entries
129pub trait HasParamGroupRefs: HasCVParams {
130    /// Returns a Vec<CVParam> containing cloned versions of all cvParams
131    fn clone_params_with_groups(&self) -> Vec<CVParam> {
132        let mut params = Vec::new();
133
134        for group_ref in self.param_group_refs() {
135            match group_ref {
136                ReferenceableParamGroupRef::Id(_) => todo!(),
137                ReferenceableParamGroupRef::Ref(group_ref) => {
138                    for param in group_ref.cv_params() {
139                        params.push(param.clone());
140                    }
141                }
142            }
143        }
144
145        for param in self.cv_params() {
146            params.push(param.clone())
147        }
148
149        params
150    }
151
152    /// Add a referenceableParamGroupRef to the tag.
153    fn add_param_group_ref(&mut self, param_group_ref: ReferenceableParamGroupRef);
154
155    /// Returns a reference to list of referenceableParamGroupRef
156    fn param_group_refs(&self) -> &Vec<ReferenceableParamGroupRef>;
157
158    /// Search param groups for
159    fn search_param_groups(&self, accession: &str) -> Option<&CVParam> {
160        for ref_group in self.param_group_refs() {
161            if let ReferenceableParamGroupRef::Ref(ref_group) = ref_group {
162                if let Some(cv_param) = ref_group.search_cv_params(accession) {
163                    return Some(cv_param);
164                }
165            }
166        }
167        None
168    }
169
170    /// Write the references (referenceableParamGroupRef) as XML to the supplied writer.
171    fn write_ref_param_groups_xml<W: Write>(
172        &self,
173        writer: &mut Writer<W>,
174    ) -> Result<(), quick_xml::Error> {
175        for ref_group in self.param_group_refs() {
176            let mut elem = BytesStart::new("referenceableParamGroupRef");
177
178            match ref_group {
179                ReferenceableParamGroupRef::Id(id) => {
180                    elem.push_attribute(("ref", id.as_str()));
181                }
182                ReferenceableParamGroupRef::Ref(group_ref) => {
183                    elem.push_attribute(("ref", group_ref.id()));
184                }
185            }
186
187            writer.write_event(Event::Empty(elem))?;
188        }
189
190        Ok(())
191    }
192}
193
194/// Possible types for the values associated with a cvParam
195#[derive(Debug)]
196pub enum CVParamValue {
197    /// No value
198    Empty,
199    /// Boolean
200    Boolean(bool),
201    /// String
202    String(String),
203    //Float(f32),
204    /// Floating point
205    Float(f64),
206    /// Integer
207    Integer(i64),
208    /// Non-negative integer
209    NonNegativeInteger(u64),
210    /// Datetime (currently represented as a String)
211    // TODO: Use a more appropriate type
212    DateTime(String),
213}
214
215impl Clone for CVParamValue {
216    fn clone(&self) -> Self {
217        match self {
218            Self::Empty => Self::Empty,
219            Self::Boolean(arg0) => Self::Boolean(*arg0),
220            Self::String(arg0) => Self::String(arg0.clone()),
221            Self::Float(arg0) => Self::Float(*arg0),
222            Self::Integer(arg0) => Self::Integer(*arg0),
223            Self::NonNegativeInteger(arg0) => Self::NonNegativeInteger(*arg0),
224            Self::DateTime(arg0) => Self::DateTime(arg0.clone()),
225        }
226    }
227}
228
229impl CVParamValue {
230    /// Returns true if the value associated with the cvParam is an integer
231    pub fn is_integer(&self) -> bool {
232        matches!(self, CVParamValue::Integer(_))
233    }
234
235    /// Returns the value as a u32 if it is possible to convert, None otherwise.
236    pub fn as_u32(&self) -> Option<u32> {
237        match self {
238            CVParamValue::Integer(value) => Some(*value as u32),
239            CVParamValue::NonNegativeInteger(value) => Some(*value as u32),
240            _ => None,
241        }
242    }
243
244    /// Returns the value as a u64 if it is possible to convert, None otherwise.
245    pub fn as_u64(&self) -> Option<u64> {
246        match self {
247            CVParamValue::Integer(value) => Some(*value as u64),
248            CVParamValue::NonNegativeInteger(value) => Some(*value),
249            _ => None,
250        }
251    }
252
253    /// Returns the value as a f64 if it is possible to convert, None otherwise.
254    pub fn as_f64(&self) -> Option<f64> {
255        match self {
256            CVParamValue::Float(value) => Some(*value),
257            _ => None,
258        }
259    }
260}
261
262/// RawTerm describes a cvParam tag using borrowed data from the XML parser.
263pub struct RawTerm<'a> {
264    //encoding: &'static Encoding,
265    decoder: Decoder,
266    ontology: Ontology,
267    //breadcrumbs: &'a VecDeque<Tag>,
268    //accession_raw: Cow<'b, [u8]>,
269    accession: Cow<'a, [u8]>,
270    name: Cow<'a, [u8]>,
271    value: Option<Cow<'a, [u8]>>,
272    unit_accession: Option<Cow<'a, [u8]>>,
273    unit_name: Option<Cow<'a, [u8]>>,
274}
275
276impl<'a> RawTerm<'a> {
277    pub(crate) fn raw_accession(&self) -> &[u8] {
278        &self.accession
279    }
280
281    #[inline]
282    pub(crate) fn accession(&self) -> Cow<str> {
283        self.decoder.decode(&self.accession).unwrap()
284        /*match &self.accession {
285            Accession::Raw(_) => todo!(),
286            Accession::String((_, accession)) => {
287                accession
288            },
289        }*/
290    }
291
292    #[inline]
293    pub(crate) fn name(&self) -> Cow<str> {
294        self.decoder.decode(&self.name).unwrap()
295    }
296
297    #[inline]
298    pub(crate) fn value_as_f64(&self) -> f64 {
299        self.decoder
300            .decode(self.value.as_ref().unwrap().borrow())
301            .unwrap()
302            .parse()
303            .unwrap()
304    }
305
306    #[inline]
307    pub(crate) fn value_as_u64(&self) -> u64 {
308        self.decoder
309            .decode(self.value.as_ref().unwrap().borrow())
310            .unwrap()
311            .parse()
312            .unwrap()
313    }
314
315    #[inline]
316    pub(crate) fn value_as_u32(&self) -> u32 {
317        self.decoder
318            .decode(self.value.as_ref().unwrap().borrow())
319            .unwrap()
320            .parse()
321            .unwrap()
322    }
323
324    pub(crate) fn parse_start_tag<'b, B: BufRead>(
325        parser: &'b mut MzMLReader<B>,
326        start_event: &'a BytesStart,
327    ) -> Result<Self, FatalParseError>
328    where
329        Self: std::marker::Sized,
330    {
331        let mut accession: Option<Cow<[u8]>> = None;
332        let mut name: Option<Cow<[u8]>> = None;
333        let mut value: Option<Cow<[u8]>> = None;
334
335        let mut unit_accession: Option<Cow<[u8]>> = None;
336        let mut unit_name: Option<Cow<[u8]>> = None;
337
338        for att in start_event
339            .attributes()
340            .with_checks(parser.with_attribute_checks)
341        {
342            match att {
343                Ok(att) => {
344                    match att.key.as_ref() {
345                        // Ignore - this can be gained from the accession
346                        b"cvRef" => {}
347                        b"accession" => {
348                            accession = Some(att.value);
349                        }
350                        b"name" => {
351                            name = Some(att.value);
352                        }
353                        b"value" => {
354                            value = Some(att.value);
355                        }
356                        // Ignore - this can be gained from the unitAccession
357                        b"unitCvRef" => {}
358                        b"unitAccession" => {
359                            unit_accession = Some(att.value);
360                        }
361                        b"unitName" => {
362                            unit_name = Some(att.value);
363                        }
364                        _ => {
365                            parser.errors.push_back(ParseError::UnexpectedAttribute((
366                                Tag::CVParam,
367                                std::str::from_utf8(att.key.as_ref())?.to_string(),
368                            )));
369                        }
370                    }
371                }
372                Err(error) => {
373                    parser
374                        .errors
375                        .push_back(ParseError::XMLError((Tag::CVParam, error.into())));
376                }
377            };
378        }
379
380        match (accession, name) {
381            (Some(accession), Some(name)) => Ok(RawTerm {
382                ontology: parser.ontology.clone(),
383                decoder: parser.decoder,
384                accession,
385                name,
386                value,
387                unit_accession,
388                unit_name,
389            }),
390            (accession, name) => Err(FatalParseError::InvalidTag((
391                Tag::CVParam,
392                format!(
393                    "Missing asscession ({:?}) or name ({:?}) when parsing a RawTerm: {}",
394                    accession,
395                    name,
396                    parser
397                        .decoder
398                        .decode(start_event.name().local_name().as_ref())
399                        .unwrap()
400                ),
401            ))),
402        }
403    }
404
405    /// Convert the RawTerm to a `UserParam`
406    pub fn to_user_param(self) -> UserParam {
407        let name = self.decoder.decode(&self.name).unwrap();
408        let value = match &self.value {
409            Some(value) => Some(self.decoder.decode(value).unwrap().into_owned()),
410            None => None,
411        };
412
413        let unit_term = match (&self.unit_accession, &self.unit_name) {
414            (Some(unit_term), Some(unit_name)) => Some(Accession::String {
415                accession: self.decoder.decode(unit_term).unwrap().into_owned(),
416                name: self.decoder.decode(unit_name).unwrap().into_owned(),
417            }),
418            (Some(unit_term), None) => Some(Accession::String {
419                accession: self.decoder.decode(unit_term).unwrap().into_owned(),
420                name: "".to_owned(),
421            }),
422            (None, Some(unit_name)) => Some(Accession::String {
423                accession: "".to_owned(),
424                name: self.decoder.decode(unit_name).unwrap().into_owned(),
425            }),
426            (None, None) => None,
427        };
428
429        UserParam {
430            name: name.into_owned(),
431            data_type: None,
432            value,
433            unit_term,
434        }
435    }
436
437    /// Convert raw term to a CVParam if the term is present in the ontology.
438    pub fn to_cv_param(
439        &self,
440        breadcrumbs: &VecDeque<(Tag, Option<String>)>,
441        errors: &mut VecDeque<ParseError>,
442    ) -> CVParam {
443        let term = match self.ontology.get(&self.accession()) {
444            Some(term) => Accession::Term(term.clone()),
445            None => {
446                errors.push_back(ParseError::UnknownAccession((
447                    Breadcrumbs(breadcrumbs.to_owned()),
448                    self.accession().to_string(),
449                )));
450
451                Accession::String {
452                    accession: self.accession().to_string(),
453                    name: self.name().to_string(),
454                }
455                //panic!("Unknown term: {}", self.accession())
456            }
457        };
458
459        let mut cv_param;
460
461        match &self.value {
462            Some(value) => {
463                let value = self.decoder.decode(value).unwrap();
464
465                if value.is_empty() {
466                    if let Some(value_type) = term.value_type() {
467                        errors.push_back(ParseError::MissingValue((
468                            Breadcrumbs(breadcrumbs.to_owned()),
469                            format!("[{}] {}", term.id(), term.name()),
470                            value_type,
471                        )));
472                    }
473
474                    cv_param = CVParam::new(term, CVParamValue::Empty);
475                } else {
476                    let param_value = match term.value_type() {
477                        Some(ValueType::String) => CVParamValue::String(value.to_string()),
478                        Some(ValueType::Boolean) => match value.as_ref() {
479                            "true" => CVParamValue::Boolean(true),
480                            "false" => CVParamValue::Boolean(false),
481                            _ => {
482                                errors.push_back(ParseError::InvalidValue((
483                                    Breadcrumbs(breadcrumbs.to_owned()),
484                                    format!("[{}] {}", term.id(), term.name()),
485                                    format!(
486                                        "Expected a boolean (true or false), but have {}",
487                                        value
488                                    ),
489                                )));
490
491                                CVParamValue::String(value.to_string())
492                            }
493                        },
494                        Some(ValueType::Int) => CVParamValue::Integer(value.parse().unwrap()),
495                        Some(ValueType::NonNegativeInteger) => match value.parse() {
496                            Ok(value) => CVParamValue::NonNegativeInteger(value),
497                            Err(error) => {
498                                // A bug in certain software causes integer overflow
499
500                                errors.push_back(ParseError::InvalidValue((
501                                    Breadcrumbs(breadcrumbs.to_owned()), //self.breadcrumbs_as_vec(),
502                                    format!("[{}] {}", term.id(), term.name()),
503                                    format!(
504                                        "Expected a non-negative integer, but have {}: {:?}",
505                                        value, error
506                                    ),
507                                )));
508
509                                CVParamValue::String(value.to_string())
510                            }
511                        },
512                        Some(ValueType::Float) => match value.parse() {
513                            Ok(value) => CVParamValue::Float(value),
514                            Err(error) => {
515                                errors.push_back(ParseError::InvalidValue((
516                                    Breadcrumbs(breadcrumbs.to_owned()), //self.breadcrumbs_as_vec(),
517                                    format!("[{}] {}", term.id(), term.name()),
518                                    format!("Expected a float, but have {}: {:?}", value, error),
519                                )));
520
521                                CVParamValue::String(value.to_string())
522                            }
523                        },
524                        Some(ValueType::NonNegativeFloat) => {
525                            let float = value.parse().unwrap();
526
527                            if float < 0.0 {
528                                errors.push_back(ParseError::InvalidValue((
529                                    Breadcrumbs(breadcrumbs.to_owned()), //self.breadcrumbs_as_vec(),
530                                    format!("[{}] {}", term.id(), term.name()),
531                                    format!("Expected a non-negative float, but have {}", value),
532                                )));
533                            }
534
535                            CVParamValue::Float(float)
536                        }
537                        Some(ValueType::DateTime) => CVParamValue::DateTime(value.to_string()),
538                        Some(value_type) => {
539                            println!("Unknown type {:?} for term {}", value_type, term.id());
540                            CVParamValue::Empty
541                        }
542                        None => {
543                            errors.push_back(ParseError::UnexpectedValue((
544                                Breadcrumbs(breadcrumbs.to_owned()), //     self.breadcrumbs_as_vec(),
545                                format!("[{}] {}", term.id(), term.name()),
546                                value.to_string(),
547                            )));
548
549                            CVParamValue::String(value.to_string())
550                        }
551                    };
552
553                    cv_param = CVParam::new(term, param_value);
554                }
555            }
556            None => {
557                cv_param = CVParam::new(term, CVParamValue::Empty);
558            }
559        }
560
561        if let Some(unit_accession) = &self.unit_accession {
562            let unit_accession = self.decoder.decode(unit_accession).unwrap();
563
564            // Get the term from the dictionary - this shouldn't fail as we have just added any missing term
565            if let Some(unit_term) = self.ontology.get(&unit_accession) {
566                cv_param.set_unit(unit_term.clone());
567            } else {
568                panic!("Unknown ontology term: {:?}", self.accession());
569            }
570        }
571
572        cv_param
573    }
574}
575
576/// CVParam represents a cvParam tag in (i)mzML
577#[derive(Debug)]
578pub struct CVParam {
579    term: Accession, //&'a Term,
580    value: CVParamValue,
581
582    unit_term: Option<OBOTerm>,
583}
584
585impl Clone for CVParam {
586    fn clone(&self) -> Self {
587        Self {
588            term: self.term.clone(),
589            value: self.value.clone(),
590            unit_term: self.unit_term.clone(),
591        }
592    }
593}
594
595impl CVParam {
596    /// Create a new CVParam based on supplied accession and value.
597    pub fn new(term: Accession, value: CVParamValue) -> Self {
598        //&'a Term
599        //accession: String, name: String
600        //let mut splitter = accession.splitn(2, ':');
601        //let cv_ref = splitter.next().unwrap();
602
603        CVParam {
604            //cv_ref: String::from(cv_ref),
605            //accession,
606            //name,
607            term,
608            value,
609            unit_term: None,
610        }
611    }
612
613    /// Returns a reference to the ontology term associated with this cvParam. If the supplied ontology
614    /// did not contain the term when generating the CVParam (i.e. the `Accession` is `Accession::String`)
615    /// then this will return None.
616    #[inline]
617    pub fn term(&self) -> Option<&Term> {
618        match &self.term {
619            Accession::Term(term) => Some(term),
620            Accession::String {
621                accession: _,
622                name: _,
623            } => None,
624        }
625    }
626
627    /// Returns the accession associated with this cvParam
628    #[inline]
629    pub fn accession(&self) -> &str {
630        match &self.term {
631            Accession::Term(term) => term.id(),
632            Accession::String { accession, name: _ } => accession,
633        }
634    }
635
636    /// Returns the identifier for the controlled vocabulary to which the accession belongs
637    #[inline]
638    pub fn cv_ref(&self) -> &str {
639        let mut splitter = self.term.id().splitn(2, ':');
640        splitter.next().unwrap()
641    }
642
643    /// REturns the name of the ontology term
644    #[inline]
645    pub fn name(&self) -> &str {
646        match &self.term {
647            Accession::Term(term) => term.name().unwrap(),
648            Accession::String { accession: _, name } => name,
649        }
650    }
651
652    /// Returns the value associated with the cvParam
653    #[inline]
654    pub fn value(&self) -> &CVParamValue {
655        &self.value
656    }
657
658    /// Returns the value as u32, if possible to convert, or None otherwise.
659    #[inline]
660    pub fn value_as_u32(&self) -> Option<u32> {
661        self.value.as_u32()
662    }
663
664    /// Returns the value as u64, if possible to convert, or None otherwise.
665    #[inline]
666    pub fn value_as_u64(&self) -> Option<u64> {
667        self.value.as_u64()
668    }
669
670    /// Returns the value as f64, if possible to convert, or None otherwise.
671    #[inline]
672    pub fn value_as_f64(&self) -> Option<f64> {
673        self.value.as_f64()
674    }
675
676    /// Sets the unit for the cvParam (specifically for the value associated with this cvParam) to the
677    /// specified ontology term
678    #[inline]
679    pub fn set_unit(&mut self, unit_term: OBOTerm) {
680        self.unit_term = Some(unit_term);
681    }
682
683    /// Returns true if a unit has been associated with this cvParam
684    #[inline]
685    pub fn has_unit(&self) -> bool {
686        self.unit_term.is_some()
687    }
688
689    /// Returns the identifier for the controlled vocabulary to which the unit accession belongs
690    #[inline]
691    pub fn unit_cv_ref(&self) -> Option<&str> {
692        if let Some(unit_term) = &self.unit_term {
693            let mut splitter = unit_term.id().splitn(2, ':');
694            Some(splitter.next().unwrap())
695        } else {
696            None
697        }
698    }
699
700    /// Returns the accession associated with the unit term
701    #[inline]
702    pub fn unit_accession(&self) -> Option<&str> {
703        if let Some(unit_term) = &self.unit_term {
704            Some(unit_term.id())
705        } else {
706            None
707        }
708    }
709
710    /// Returns the name associated with the unit accession (if a unit is specified)
711    #[inline]
712    pub fn unit_name(&self) -> Option<&str> {
713        if let Some(unit_term) = &self.unit_term {
714            Some(unit_term.name().unwrap())
715        } else {
716            None
717        }
718    }
719}
720
721impl MzMLTag for CVParam {
722    fn parse_start_tag<B: BufRead>(
723        parser: &mut MzMLReader<B>,
724        start_event: &BytesStart,
725    ) -> Result<Option<Self>, FatalParseError>
726    where
727        Self: std::marker::Sized,
728    {
729        if start_event.name().local_name().as_ref() != b"cvParam" {
730            Err(FatalParseError::UnexpectedTag(format!(
731                "Unexpected event {:?} when processing CVParam",
732                start_event,
733            )))
734        } else {
735            let mut accession: Option<Cow<[u8]>> = None;
736            let mut name: Option<Cow<[u8]>> = None;
737            let mut value: Option<Cow<[u8]>> = None;
738
739            let mut unit_accession: Option<Cow<[u8]>> = None;
740            let mut unit_name: Option<Cow<[u8]>> = None;
741
742            for att in start_event
743                .attributes()
744                .with_checks(parser.with_attribute_checks)
745            {
746                match att {
747                    Ok(att) => {
748                        match att.key.as_ref() {
749                            // Ignore - this can be gained from the accession
750                            b"cvRef" => {}
751                            b"accession" => {
752                                accession = Some(att.value);
753                            }
754                            b"name" => {
755                                name = Some(att.value);
756                            }
757                            b"value" => {
758                                value = Some(att.value);
759                            }
760                            // Ignore - this can be gained from the unitAccession
761                            b"unitCvRef" => {}
762                            b"unitAccession" => {
763                                unit_accession = Some(att.value);
764                            }
765                            b"unitName" => {
766                                unit_name = Some(att.value);
767                            }
768                            _ => {
769                                parser.errors.push_back(ParseError::UnexpectedAttribute((
770                                    Tag::CVParam,
771                                    std::str::from_utf8(att.key.as_ref())?.to_string(),
772                                )));
773                            }
774                        }
775                    }
776                    Err(error) => {
777                        parser
778                            .errors
779                            .push_back(ParseError::XMLError((Tag::CVParam, error.into())));
780                    }
781                }
782            }
783
784            match (accession, name) {
785                (Some(accession), Some(name)) => {
786                    if accession.is_empty() {
787                        parser.errors.push_back(ParseError::MissingAttribute((
788                            Tag::CVParam,
789                            "No accession found for CVParam".to_string(),
790                        )));
791
792                        Ok(None)
793                    } else {
794                        let raw_term = RawTerm {
795                            ontology: parser.ontology.clone(),
796                            decoder: parser.reader.decoder(),
797                            accession,
798                            name,
799                            value,
800                            unit_accession,
801                            unit_name,
802                            //breadcrumbs: &self.breadcrumb,
803                        };
804
805                        Ok(Some(
806                            raw_term.to_cv_param(&parser.breadcrumbs, &mut parser.errors),
807                        ))
808                    }
809                }
810                _ => {
811                    parser.errors.push_back(ParseError::MissingAttribute((
812                        Tag::CVParam,
813                        "No accession found for CVParam".to_string(),
814                    )));
815
816                    Ok(None)
817                }
818            }
819        }
820    }
821
822    fn tag() -> Tag {
823        Tag::CVParam
824    }
825
826    fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<(), quick_xml::Error> {
827        let mut elem = BytesStart::new("cvParam");
828        elem.push_attribute(("cvRef", self.cv_ref()));
829        elem.push_attribute(("accession", self.accession()));
830        elem.push_attribute(("name", self.name()));
831
832        match self.value() {
833            CVParamValue::Empty => {}
834            CVParamValue::Boolean(value) => {
835                if *value {
836                    elem.push_attribute(("value", "true"))
837                } else {
838                    elem.push_attribute(("value", "false"))
839                }
840            }
841            CVParamValue::String(value) => elem.push_attribute(("value", value.as_str())),
842            CVParamValue::Float(value) => {
843                elem.push_attribute(("value", value.to_string().as_str()))
844            }
845            CVParamValue::Integer(value) => {
846                elem.push_attribute(("value", value.to_string().as_str()))
847            }
848            CVParamValue::NonNegativeInteger(value) => {
849                elem.push_attribute(("value", value.to_string().as_str()))
850            }
851            CVParamValue::DateTime(value) => elem.push_attribute(("value", value.as_str())),
852        }
853
854        if self.has_unit() {
855            elem.push_attribute(("unitCvRef", self.unit_cv_ref().unwrap()));
856            elem.push_attribute(("unitAccession", self.unit_accession().unwrap()));
857            elem.push_attribute(("unitName", self.unit_name().unwrap()));
858        }
859
860        writer.write_event(Event::Empty(elem))
861    }
862
863    fn parse_xml<B: BufRead>(
864        &mut self,
865        _parser: &mut MzMLReader<B>,
866        _buffer: &mut Vec<u8>,
867    ) -> Result<(), FatalParseError> {
868        Ok(())
869    }
870}
871
872/// Accession can either be (ideally) defined by a `Term` in an `Ontology` or a String if this is not possible.
873#[derive(Debug, Clone)]
874pub enum Accession {
875    /// Term from an ontology
876    Term(OBOTerm),
877    /// Accession not present in ontology, so handled as a string
878    String {
879        /// Accession (unique identifier)
880        accession: String,
881        /// Name of the term (typically a short description)
882        name: String,
883    },
884}
885
886impl From<OBOTerm> for Accession {
887    fn from(term: OBOTerm) -> Self {
888        Accession::Term(term)
889    }
890}
891
892impl From<&OBOTerm> for Accession {
893    fn from(term: &OBOTerm) -> Self {
894        Accession::Term(term.clone())
895    }
896}
897
898impl Accession {
899    /// Returns the unique identifier for the controlled vocabulary term (accession)
900    pub fn id(&self) -> &str {
901        match self {
902            Accession::Term(term) => term.id(),
903            Accession::String { accession, name: _ } => accession,
904        }
905    }
906
907    /// Returns a descriptive name for the controlled vocabulary term
908    pub fn name(&self) -> &str {
909        match self {
910            Accession::Term(term) => term.name().unwrap(),
911            Accession::String { accession: _, name } => name,
912        }
913    }
914
915    /// Returns the expected value type (if known)
916    pub fn value_type(&self) -> Option<ValueType> {
917        match self {
918            Accession::Term(term) => term.value_type(),
919            Accession::String {
920                accession: _,
921                name: _,
922            } => None,
923        }
924    }
925}
926
927/// UserParam represents a userParam tag in (i)mzML
928#[derive(Debug)]
929pub struct UserParam {
930    name: String,
931    data_type: Option<String>,
932    value: Option<String>,
933    unit_term: Option<Accession>,
934}
935
936impl Clone for UserParam {
937    fn clone(&self) -> Self {
938        Self {
939            name: self.name.clone(),
940            data_type: self.data_type.clone(),
941            value: self.value.clone(),
942            unit_term: self.unit_term.clone(),
943        }
944    }
945}
946
947impl UserParam {
948    /// Create a new userParam from the specified name
949    pub fn new(name: &str) -> Self {
950        Self {
951            name: name.to_string(),
952            data_type: None,
953            value: None,
954            unit_term: None,
955        }
956    }
957
958    /// Returns the descriptive name associated with the UserParam
959    pub fn name(&self) -> &str {
960        &self.name
961    }
962}
963
964impl MzMLTag for UserParam {
965    fn parse_start_tag<B: BufRead>(
966        parser: &mut MzMLReader<B>,
967        start_event: &BytesStart,
968    ) -> Result<Option<Self>, FatalParseError>
969    where
970        Self: std::marker::Sized,
971    {
972        if start_event.name().local_name().as_ref() != b"userParam" {
973            Err(FatalParseError::UnexpectedTag(format!(
974                "Unexpected event {:?} when processing UserParam",
975                start_event,
976            )))
977        } else {
978            let mut name: Option<Cow<[u8]>> = None;
979            let mut data_type: Option<Cow<[u8]>> = None;
980            let mut value: Option<Cow<[u8]>> = None;
981
982            let mut unit_accession: Option<Cow<[u8]>> = None;
983            let mut unit_name: Option<Cow<[u8]>> = None;
984
985            for att in start_event
986                .attributes()
987                .with_checks(parser.with_attribute_checks)
988            {
989                match att {
990                    Ok(att) => {
991                        match att.key.as_ref() {
992                            b"name" => {
993                                name = Some(att.value);
994                            }
995                            b"value" => {
996                                value = Some(att.value);
997                            }
998                            b"type" => {
999                                data_type = Some(att.value);
1000                            }
1001                            // Ignore - this can be gained from the unitAccession
1002                            b"unitCvRef" => {}
1003                            b"unitAccession" => {
1004                                unit_accession = Some(att.value);
1005                            }
1006                            b"unitName" => {
1007                                unit_name = Some(att.value);
1008                            }
1009                            _ => {
1010                                parser.errors.push_back(ParseError::UnexpectedAttribute((
1011                                    Tag::UserParam,
1012                                    std::str::from_utf8(att.key.as_ref())?.to_string(),
1013                                )));
1014                            }
1015                        }
1016                    }
1017                    Err(error) => {
1018                        parser
1019                            .errors
1020                            .push_back(ParseError::XMLError((Tag::UserParam, error.into())));
1021                    }
1022                };
1023            }
1024
1025            let mut user_param = match name {
1026                Some(name) => {
1027                    UserParam::new(parser.parse_string(Tag::UserParam, &name).unwrap_or(""))
1028                }
1029                None => {
1030                    return Err(FatalParseError::InvalidTag((
1031                        Tag::UserParam,
1032                        "No name attribute supplied for the <userParam> tag".to_string(),
1033                    )));
1034                }
1035            };
1036
1037            if let Some(value) = value {
1038                user_param.value = parser
1039                    .parse_string(Tag::UserParam, &value)
1040                    .map(|value| value.to_string());
1041            }
1042
1043            if let Some(data_type) = data_type {
1044                user_param.data_type = parser
1045                    .parse_string(Tag::UserParam, &data_type)
1046                    .map(|data_type| data_type.to_string());
1047            }
1048
1049            if let (Some(unit_accession), Some(unit_name)) = (unit_accession, unit_name) {
1050                user_param.unit_term = Some(Accession::String {
1051                    accession: parser
1052                        .parse_string(Tag::UserParam, &unit_accession)
1053                        .map(|accession| accession.to_string())
1054                        .unwrap_or_else(|| "".to_string()),
1055                    name: parser
1056                        .parse_string(Tag::UserParam, &unit_name)
1057                        .map(|unit_name| unit_name.to_string())
1058                        .unwrap_or_else(|| "".to_string()),
1059                });
1060            }
1061
1062            Ok(Some(user_param))
1063        }
1064    }
1065
1066    fn parse_xml<B: BufRead>(
1067        &mut self,
1068        _parser: &mut MzMLReader<B>,
1069        _buffer: &mut Vec<u8>,
1070    ) -> Result<(), FatalParseError> {
1071        Ok(())
1072    }
1073
1074    fn tag() -> Tag {
1075        Tag::UserParam
1076    }
1077
1078    fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<(), quick_xml::Error> {
1079        let mut elem = BytesStart::new("userParam");
1080        elem.push_attribute(("name", self.name()));
1081
1082        if let Some(data_type) = &self.data_type {
1083            elem.push_attribute(("type", data_type.as_str()));
1084        }
1085
1086        if let Some(value) = &self.value {
1087            elem.push_attribute(("value", value.as_str()));
1088        }
1089
1090        if let Some(unit_term) = &self.unit_term {
1091            let mut splitter = unit_term.id().splitn(2, ':');
1092            let cv_ref = splitter.next().unwrap();
1093
1094            elem.push_attribute(("unitCvRef", cv_ref));
1095            elem.push_attribute(("unitAccession", unit_term.id()));
1096            elem.push_attribute(("unitName", unit_term.name()));
1097        }
1098
1099        writer.write_event(Event::Empty(elem))
1100    }
1101}