use std::sync::Arc;
use super::cvparam::{CVParam, HasCVParams, HasParamGroupRefs};
use super::writer::Writer;
use super::{MzMLReader, MzMLTag};
use quick_xml::events::{BytesStart, Event};
use std::io::{BufRead, Write};
use crate::mzml::attributes::{AttributeValue, LIST_ATTRIBUTES, SOFTWARE_ATTRIBUTES};
use crate::mzml::cvparam::UserParam;
use crate::{FatalParseError, ParseError, Tag};
use super::referenceableparamgroup::ReferenceableParamGroupRef;
pub struct SoftwareList {
pub(crate) list: Vec<Arc<Software>>,
}
impl SoftwareList {
fn new(count: usize) -> Self {
SoftwareList {
list: Vec::with_capacity(count),
}
}
}
impl MzMLTag for SoftwareList {
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"softwareList" {
return Err(FatalParseError::UnexpectedTag(format!(
"Unexpected event {:?} when processing SoftwareList",
start_event,
)));
}
let attributes = parser.process_attributes(Self::tag(), &LIST_ATTRIBUTES, start_event)?;
let count = match attributes.get("count") {
Some(&AttributeValue::Integer(count)) => count as usize,
_ => 0,
};
parser.breadcrumbs.push_back((Tag::SoftwareList, None));
Ok(Some(SoftwareList::new(count)))
}
fn parse_xml<B: BufRead>(
&mut self,
parser: &mut MzMLReader<B>,
buffer: &mut Vec<u8>,
) -> Result<(), FatalParseError> {
loop {
buffer.clear();
let next_event = parser.next(buffer)?;
let is_empty = matches!(next_event, Event::Empty(_));
match next_event {
Event::Start(start_event) | Event::Empty(start_event) => {
match start_event.name().as_ref() {
b"software" => {
if let Some(mut software) =
Software::parse_start_tag(parser, &start_event)?
{
if !is_empty {
software.parse_xml(parser, buffer)?;
}
self.list.push(Arc::new(software));
}
}
_ => 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"softwareList" = end_event.name().as_ref() {
parser.breadcrumbs.pop_back();
break;
}
}
Event::Eof => {
return Err(FatalParseError::MissingClosingTag(
"softwareList".to_string(),
));
}
_ => {}
}
}
Ok(())
}
fn tag() -> Tag {
Tag::SoftwareList
}
fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<(), quick_xml::Error> {
writer.write_arc_list("softwareList", &self.list)
}
}
#[derive(Debug)]
pub enum SoftwareRef {
Id(String),
Ref(Arc<Software>),
}
impl Clone for SoftwareRef {
fn clone(&self) -> Self {
match self {
Self::Id(arg0) => Self::Id(arg0.clone()),
Self::Ref(arg0) => Self::Ref(arg0.clone()),
}
}
}
impl MzMLTag for SoftwareRef {
fn tag() -> Tag {
Tag::SoftwareRef
}
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"softwareRef" {
panic!(
"Unexpected event {:?} when processing SoftwareRef",
start_event
);
}
let mut value: Option<String> = None;
for attribute in start_event
.attributes()
.with_checks(parser.with_attribute_checks)
{
match attribute {
Ok(attribute) => match attribute.key.as_ref() {
b"ref" => match std::str::from_utf8(&attribute.value) {
Ok(v) => {
value = Some(v.to_owned());
}
Err(error) => {
parser
.errors
.push_back(ParseError::Utf8Error((Tag::RefParamGroupRef, error)));
}
},
_ => parser.errors.push_back(ParseError::UnexpectedAttribute((
Tag::RefParamGroupRef,
std::str::from_utf8(attribute.key.as_ref())?.to_string(),
))),
},
Err(error) => {
parser
.errors
.push_back(ParseError::XMLError((Tag::RefParamGroupRef, error.into())));
}
}
}
match value {
Some(value) => match parser.software_ref(value.as_bytes()) {
Some(reference) => Ok(Some(reference)),
None => Ok(Some(SoftwareRef::Id(value))),
},
None => todo!(),
}
}
fn parse_xml<B: BufRead>(
&mut self,
_parser: &mut MzMLReader<B>,
_buffer: &mut Vec<u8>,
) -> Result<(), FatalParseError> {
Ok(())
}
fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<(), quick_xml::Error> {
writer.empty_tag_with_attr("softwareRef", "ref", self.id())
}
}
impl SoftwareRef {
pub fn id(&self) -> &str {
match self {
SoftwareRef::Id(id) => id,
SoftwareRef::Ref(software) => software.id(),
}
}
}
#[derive(Debug)]
pub struct Software {
id: Arc<str>,
version: String,
param_group_refs: Vec<ReferenceableParamGroupRef>,
cv_params: Vec<CVParam>,
user_params: Vec<UserParam>,
}
impl Clone for Software {
fn clone(&self) -> Self {
Self {
id: self.id.clone(),
version: self.version.clone(),
param_group_refs: self.param_group_refs.clone(),
cv_params: self.cv_params.clone(),
user_params: self.user_params.clone(),
}
}
}
impl Software {
pub fn new(id: &str, version: &str) -> Self {
Software {
id: id.into(),
version: version.into(),
param_group_refs: Vec::new(),
cv_params: Vec::new(),
user_params: Vec::new(),
}
}
pub fn id(&self) -> &str {
&self.id
}
}
impl MzMLTag for Software {
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"software" {
return Err(FatalParseError::UnexpectedTag(format!(
"Unexpected event {:?} when processing Software",
start_event,
)));
}
let attributes =
parser.process_attributes(Tag::Sample, &SOFTWARE_ATTRIBUTES, start_event)?;
let software = match (attributes.get("id"), attributes.get("version")) {
(Some(AttributeValue::String(id)), Some(AttributeValue::String(version))) => {
Software::new(
parser.parse_string(Tag::Software, id).unwrap_or(""),
parser.parse_string(Tag::Software, version).unwrap_or(""),
)
}
(Some(AttributeValue::String(id)), None) => {
Software::new(parser.parse_string(Tag::Software, id).unwrap_or(""), "")
}
(None, Some(AttributeValue::String(version))) => Software::new(
"",
parser.parse_string(Tag::Software, version).unwrap_or(""),
),
_ => Software::new("", ""),
};
parser
.breadcrumbs
.push_back((Tag::Software, Some(software.id().to_owned())));
Ok(Some(software))
}
fn parse_xml<B: BufRead>(
&mut self,
parser: &mut MzMLReader<B>,
buffer: &mut Vec<u8>,
) -> Result<(), FatalParseError> {
loop {
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"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);
}
}
_ => 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"software" = end_event.name().as_ref() {
parser.breadcrumbs.pop_back();
break;
}
}
Event::Eof => {
return Err(FatalParseError::MissingClosingTag("software".to_string()));
}
_ => {}
}
}
Ok(())
}
fn tag() -> Tag {
Tag::Software
}
fn write_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<(), quick_xml::Error> {
let mut elem = BytesStart::new("software");
elem.push_attribute(("id", self.id.as_ref()));
elem.push_attribute(("version", self.version.as_ref()));
writer.write_event(Event::Start(elem))?;
self.write_ref_param_groups_xml(writer)?;
self.write_params_xml(writer)?;
writer.end_tag("software")
}
}
impl HasCVParams for Software {
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
}
}
impl HasParamGroupRefs for Software {
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
}
}