imzml 0.1.3

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

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

use crate::{
    mzml::attributes::{AttributeValue, LIST_ATTRIBUTES},
    FatalParseError, ParseError, Tag,
};

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

pub struct CVList {
    // TODO: Currently need this to avoid breaking code with the old cv_list: Vec<CV> parameter on MzML struct
    pub(crate) cvs: Vec<CV>,
}

impl CVList {
    fn new(count: usize) -> Self {
        CVList {
            cvs: Vec::with_capacity(count),
        }
    }
}

impl MzMLTag for CVList {
    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().local_name().as_ref() != b"cvList" {
            Err(FatalParseError::UnexpectedTag(format!(
                "Unexpected event {:?} when processing CVList",
                start_event,
            )))
        } else {
            let attributes =
                parser.process_attributes(Tag::CVList, &LIST_ATTRIBUTES, start_event)?;

            let count = match attributes.get("count") {
                Some(&AttributeValue::Integer(count)) => count as usize,
                _ => 0,
            };

            parser.breadcrumbs.push_back((Tag::CVList, None));

            Ok(Some(CVList::new(count)))
        }
    }

    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) => {
                    match start_event.name().as_ref() {
                        b"cv" => {
                            if let Some(cv) = CV::parse_start_tag(parser, &start_event)? {
                                self.cvs.push(cv);
                            }
                        }
                        _ => parser.errors.push_back(ParseError::UnexpectedTag(format!(
                            "{:?} unexpected when processing {:?}",
                            std::str::from_utf8(start_event.name().as_ref()),
                            Tag::CVList
                        ))),
                    }
                }
                Event::End(end_event) => {
                    if let b"cvList" = end_event.name().as_ref() {
                        parser.breadcrumbs.pop_back();

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

        Ok(())
    }

    fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<(), quick_xml::Error> {
        writer.write_list("cvList", &self.cvs)
        // let mut elem = BytesStart::owned(b"cvList".to_vec(), "cvList".len());
        // // encodedLength is required
        // elem.push_attribute(("count", format!("{}", self.cvs.len()).as_str()));

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

        // for item in &self.cvs {
        //     item.write_xml(writer)?;
        // }

        // writer.write_event(Event::End(BytesEnd::owned(b"cvList".to_vec())))
    }

    #[inline]
    fn tag() -> Tag {
        Tag::CVList
    }
}

pub struct CV {
    uri: String,
    full_name: String,
    id: String,
    version: Option<String>,
}

impl Clone for CV {
    fn clone(&self) -> Self {
        Self {
            uri: self.uri.clone(),
            full_name: self.full_name.clone(),
            id: self.id.clone(),
            version: self.version.clone(),
        }
    }
}

impl CV {
    pub fn new(id: &str, uri: &str, full_name: &str) -> CV {
        CV {
            id: id.into(),
            uri: uri.into(),
            full_name: full_name.into(),
            version: None,
        }
    }

    #[inline]
    pub fn set_version(&mut self, version: &str) {
        self.version = Some(version.into());
    }
}

impl MzMLTag for CV {
    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().local_name().as_ref() != b"cv" {
            Err(FatalParseError::UnexpectedTag(format!(
                "Unexpected event {:?} when processing CV",
                start_event,
            )))
        } else {
            let mut id: Option<Cow<[u8]>> = None;
            let mut uri: Option<Cow<[u8]>> = None;
            let mut full_name: Option<Cow<[u8]>> = None;
            let mut version: Option<Cow<[u8]>> = None;

            for attribute in start_event
                .attributes()
                .with_checks(parser.with_attribute_checks)
            {
                match attribute {
                    Ok(attribute) => match attribute.key.as_ref() {
                        b"id" => {
                            id = Some(attribute.value);
                        }
                        b"URI" | b"uri" => {
                            uri = Some(attribute.value);
                        }
                        b"fullName" => {
                            full_name = Some(attribute.value);
                        }
                        b"version" => {
                            version = Some(attribute.value);
                        }
                        _ => {
                            parser.errors.push_back(ParseError::UnexpectedAttribute((
                                Tag::CV,
                                std::str::from_utf8(attribute.key.as_ref())?.to_string(),
                            )));
                        }
                    },
                    Err(error) => {
                        parser
                            .errors
                            .push_back(ParseError::XMLError((Tag::CV, error.into())));
                    }
                };
            }

            let id = match &id {
                Some(id) => parser.parse_string(Tag::CV, id).unwrap_or(""),
                None => {
                    parser
                        .errors
                        .push_back(ParseError::MissingAttribute((Tag::CV, "id".to_string())));

                    ""
                }
            };

            let uri = match &uri {
                Some(uri) => parser.parse_string(Tag::CV, uri).unwrap_or(""),
                None => {
                    parser
                        .errors
                        .push_back(ParseError::MissingAttribute((Tag::CV, "uri".to_string())));

                    ""
                }
            };

            let full_name = match &full_name {
                Some(full_name) => parser.parse_string(Tag::CV, full_name).unwrap_or(""),
                None => {
                    parser.errors.push_back(ParseError::MissingAttribute((
                        Tag::CV,
                        "full_name".to_string(),
                    )));

                    ""
                }
            };

            let mut cv = CV::new(id, uri, full_name);

            if let Some(version) = version {
                cv.set_version(parser.parse_string(Tag::CV, &version).unwrap_or(""));
            }

            Ok(Some(cv))
        }
    }

    fn parse_xml<B: BufRead>(
        &mut self,
        _parser: &mut MzMLReader<B>,
        _buffer: &mut Vec<u8>,
    ) -> Result<(), FatalParseError> {
        Ok(())
    }

    fn tag() -> Tag {
        Tag::CV
    }

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

        if let Some(ref version) = self.version {
            elem.push_attribute(("version", version.as_ref()));
        }

        writer.write_event(Event::Empty(elem))
    }
}