imzml 0.1.3

A library for reading the mass spectrometry (imaging) formats mzML and imzML.
Documentation
use std::{
    io::{BufRead, Write},
    sync::Arc,
};

use quick_xml::events::{BytesStart, Event};

use crate::{
    mzml::{
        attributes::{AttributeValue, RUN_ATTRIBUTES},
        cvparam::{CVParam, HasCVParams, HasParamGroupRefs, UserParam},
        filedescription::SourceFileRef,
        instrument::InstrumentConfigurationRef,
        referenceableparamgroup::ReferenceableParamGroupRef,
        sample::SampleRef,
    },
    ChromatogramList, FatalParseError, ParseError, Spectrum, SpectrumList, Tag,
};

use super::{writer::Writer, MzMLReader, MzMLTag};

/// Represents the <run> tag.
//#[wasm_bindgen]
pub struct Run {
    param_group_refs: Vec<ReferenceableParamGroupRef>,
    cv_params: Vec<CVParam>,
    user_params: Vec<UserParam>,

    default_instrument_configuration_ref: InstrumentConfigurationRef,
    id: Arc<str>,

    default_source_file_ref: Option<SourceFileRef>,
    sample_ref: Option<SampleRef>,
    start_time_stamp: Option<String>,

    pub(crate) spectrum_list: Option<SpectrumList>,
    pub(crate) chromatogram_list: Option<ChromatogramList>,
}

impl Clone for Run {
    fn clone(&self) -> Self {
        Self {
            param_group_refs: self.param_group_refs.clone(),
            cv_params: self.cv_params.clone(),
            user_params: self.user_params.clone(),
            default_instrument_configuration_ref: self.default_instrument_configuration_ref.clone(),
            id: self.id.clone(),
            default_source_file_ref: self.default_source_file_ref.clone(),
            sample_ref: self.sample_ref.clone(),
            start_time_stamp: self.start_time_stamp.clone(),
            spectrum_list: self.spectrum_list.clone(),
            chromatogram_list: self.chromatogram_list.clone(),
        }
    }
}

impl Run {
    /// Create a new `Run` with the specified unique identifier and default instrument configuration.
    pub fn new(id: &str, default_instrument_configuration_ref: InstrumentConfigurationRef) -> Self {
        Run {
            param_group_refs: Vec::new(),
            cv_params: Vec::new(),
            user_params: Vec::new(),

            id: id.into(),
            default_instrument_configuration_ref,

            default_source_file_ref: None,
            sample_ref: None,
            start_time_stamp: None,

            spectrum_list: None,
            chromatogram_list: None,
        }
    }

    /// Set the (optional) reference to the default source file (origin of the data)
    pub fn set_default_source_file_ref(&mut self, source_file_ref: SourceFileRef) {
        self.default_source_file_ref = Some(source_file_ref);
    }

    /// Set the (optional) reference to the sample description
    pub fn set_sample_ref(&mut self, sample_ref: SampleRef) {
        self.sample_ref = Some(sample_ref);
    }

    /// Set the (optional) attribute, when the run was initiated
    pub fn set_start_time_stamp(&mut self, start_time_stamp: &str) {
        self.start_time_stamp = Some(start_time_stamp.into());
    }

    /// Returns a reference to the spectrum at the specified index in the list, if one exists
    pub fn spectrum(&self, index: usize) -> Option<&Arc<Spectrum>> {
        self.spectrum_list.as_ref().unwrap().spectrum(index)
    }

    /// Returns the number of spectra in the run
    pub fn num_spectra(&self) -> usize {
        self.spectrum_list.as_ref().unwrap().len()
    }

    /// Sets the data associated with this run (`SpectrumList`)
    pub fn set_spectrum_list(&mut self, spectrum_list: SpectrumList) {
        self.spectrum_list = Some(spectrum_list)
    }

    /// Returns the `SpectrumList` associated with this run, if one was specified.
    pub fn spectrum_list(&self) -> Option<&SpectrumList> {
        self.spectrum_list.as_ref()
    }

    /// Returns mutable access to the `SpectrumList` associated with this run, if one was specified.
    pub fn spectrum_list_mut(&mut self) -> Option<&mut SpectrumList> {
        self.spectrum_list.as_mut()
    }

    /// Returns the `ChromatogramList` associated with this run, if one was specified.
    pub fn chromatogram_list(&self) -> Option<&ChromatogramList> {
        self.chromatogram_list.as_ref()
    }
}

impl MzMLTag for Run {
    fn parse_start_tag<B: BufRead>(
        parser: &mut MzMLReader<B>,
        start_event: &BytesStart,
    ) -> Result<Option<Self>, FatalParseError>
    where
        Self: std::marker::Sized,
    {
        if start_event.name().as_ref() != b"run" {
            return Err(FatalParseError::UnexpectedTag(format!(
                "Unexpected event {:?} when processing Run",
                start_event,
            )));
        }

        let attributes = parser.process_attributes(Tag::Run, &RUN_ATTRIBUTES, start_event)?;

        let id = match attributes.get("id") {
            Some(AttributeValue::String(id)) => parser.parse_string(Tag::Run, id).unwrap(),
            _ => "",
        };

        let default_instrument_configuration_ref =
            match attributes.get("defaultInstrumentConfigurationRef") {
                Some(AttributeValue::String(instrument_ref_id)) => {
                    let instrument_ref_id = parser
                        .parse_string(Tag::Run, instrument_ref_id)
                        .unwrap_or("");

                    let configuration_ref =
                        parser.instrument_configuration_ref(instrument_ref_id.as_bytes());

                    configuration_ref.unwrap_or_else(|| {
                        InstrumentConfigurationRef::Id(instrument_ref_id.to_string())
                    })
                }
                _ => InstrumentConfigurationRef::Id("".to_string()),
            };

        let mut run = Run::new(id, default_instrument_configuration_ref);

        if let Some(attribute) = attributes.get("defaultSourceFileRef") {
            let source_file_ref = match attribute {
                AttributeValue::String(source_file_ref_id) => {
                    let source_file_ref_id = parser
                        .parse_string(Tag::Run, source_file_ref_id)
                        .unwrap_or("");

                    let source_file_ref = parser.source_file_ref(source_file_ref_id.as_bytes());

                    source_file_ref
                        .unwrap_or_else(|| SourceFileRef::Id(source_file_ref_id.to_string()))
                }
                _ => SourceFileRef::Id("".to_string()),
            };

            run.set_default_source_file_ref(source_file_ref);
        }

        // start_time_stamp
        if let Some(AttributeValue::String(value)) = attributes.get("startTimeStamp") {
            run.start_time_stamp = parser
                .parse_string(Tag::UserParam, value)
                .map(|value| value.to_string());
        }

        // TODO: sample_ref

        // We don't really need the id of the Run as the should only be one anyway.
        parser.breadcrumbs.push_back((Tag::Run, None)); //Some(id.to_string())));
        Ok(Some(run))
    }

    fn parse_xml<B: BufRead>(
        &mut self,
        parser: &mut MzMLReader<B>,
        buffer: &mut Vec<u8>,
    ) -> Result<(), FatalParseError> {
        // Check what comes next
        loop {
            // Clear the buffer ready for the next tag
            buffer.clear();

            let next_event = parser.next(buffer)?;

            match next_event {
                Event::Start(start_event) | Event::Empty(start_event) => {
                    // TODO: refParamGroupRef, cvParam, userParam, spectrumList, chromatogramList
                    match start_event.name().as_ref() {
                        b"cvParam" => {
                            if let Some(cv_param) = CVParam::parse_start_tag(parser, &start_event)?
                            {
                                self.cv_params.push(cv_param);
                            }
                        }
                        b"referenceableParamGroupRef" => {
                            let param_group_ref =
                                ReferenceableParamGroupRef::parse_start_tag(parser, &start_event)?;
                            self.param_group_refs.push(param_group_ref);
                        }
                        b"userParam" => {
                            if let Some(user_param) =
                                UserParam::parse_start_tag(parser, &start_event)?
                            {
                                self.user_params.push(user_param);
                            }
                        }
                        b"spectrumList" => {
                            if let Some(mut spectrum_list) =
                                SpectrumList::parse_start_tag(parser, &start_event)?
                            {
                                spectrum_list.parse_xml(parser, buffer)?;

                                self.spectrum_list = Some(spectrum_list);
                            }
                        }
                        b"chromatogramList" => {
                            if let Some(mut chromatogram_list) =
                                ChromatogramList::parse_start_tag(parser, &start_event)?
                            {
                                chromatogram_list.parse_xml(parser, buffer)?;

                                self.chromatogram_list = Some(chromatogram_list);
                            }
                        }
                        _ => parser.errors.push_back(ParseError::UnexpectedTag(format!(
                            "{:?} unexpected when processing {:?}",
                            std::str::from_utf8(start_event.name().as_ref()),
                            Self::tag()
                        ))),
                    }
                }
                Event::End(end_event) => {
                    if let b"run" = end_event.name().as_ref() {
                        parser.breadcrumbs.pop_back();

                        break;
                    }
                }
                Event::Eof => {
                    return Err(FatalParseError::MissingClosingTag("run".to_string()));
                }
                _ => {}
            }
        }

        Ok(())
    }

    fn tag() -> crate::Tag {
        Tag::Run
    }

    fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<(), quick_xml::Error> {
        let mut elem = BytesStart::new("run");
        elem.push_attribute(("id", self.id.as_ref()));

        match self.default_instrument_configuration_ref {
            InstrumentConfigurationRef::Id(ref id) => {
                elem.push_attribute(("defaultInstrumentConfigurationRef", id.as_str()))
            }
            InstrumentConfigurationRef::Ref(ref instrument_configuration) => elem.push_attribute((
                "defaultInstrumentConfigurationRef",
                instrument_configuration.id(),
            )),
        }
        if let Some(ref default_source_file_ref) = self.default_source_file_ref {
            match default_source_file_ref {
                SourceFileRef::Id(ref id) => {
                    elem.push_attribute(("defaultSourceFileRef", id.as_str()))
                }
                SourceFileRef::Ref(ref source_file) => {
                    elem.push_attribute(("defaultSourceFileRef", source_file.id()))
                }
            }
        }

        if let Some(ref sample_ref) = self.sample_ref {
            match sample_ref {
                SampleRef::Id(ref id) => elem.push_attribute(("sampleRef", id.as_str())),
                SampleRef::Ref(ref sample) => elem.push_attribute(("sampleRef", sample.id())),
            }
        }

        if let Some(ref start_time_stamp) = self.start_time_stamp {
            elem.push_attribute(("startTimeStamp", start_time_stamp.as_str()));
        }

        writer.write_event(Event::Start(elem))?;

        // TODO: params

        // spectrumList
        if let Some(ref spectrum_list) = self.spectrum_list {
            spectrum_list.write_xml(writer)?;
        }

        writer.end_tag("run")
    }
}

impl HasCVParams for Run {
    fn add_cv_param(&mut self, param: CVParam) {
        self.cv_params.push(param);
    }

    fn cv_params(&self) -> &Vec<CVParam> {
        &self.cv_params
    }

    fn cv_params_mut(&mut self) -> &mut Vec<CVParam> {
        self.cv_params.as_mut()
    }

    fn add_user_param(&mut self, param: UserParam) {
        self.user_params.push(param);
    }

    fn user_params(&self) -> &Vec<UserParam> {
        &self.user_params
    }

    // fn cv_param_iter(&self) -> CVParamIterator {
    //     CVParamIterator::from_has_param_group(self)
    // }
}

impl HasParamGroupRefs for Run {
    fn add_param_group_ref(&mut self, param_group_ref: ReferenceableParamGroupRef) {
        self.param_group_refs.push(param_group_ref);
    }

    fn param_group_refs(&self) -> &Vec<ReferenceableParamGroupRef> {
        &self.param_group_refs
    }
}