@@ -19,12 +19,12 @@ use crate::{
1919 FastaIdentifier , IdentifiedPeptidoform , IdentifiedPeptidoformData , KnownFileFormat ,
2020 MaybePeptidoform , MetaData , SpectrumId , SpectrumIds ,
2121 } ,
22- ontology:: CustomDatabase ,
23- prelude:: CompoundPeptidoformIon ,
22+ ontology:: { CustomDatabase , Ontology } ,
23+ prelude:: { CompoundPeptidoformIon , MolecularFormula } ,
2424 quantities:: Tolerance ,
2525 sequence:: {
26- AminoAcid , PeptideModificationSearch , Peptidoform , ReturnModification , SemiAmbiguous ,
27- SimpleModification , SimpleModificationInner , SloppyParsingParameters ,
26+ AminoAcid , MUPSettings , PeptideModificationSearch , Peptidoform , ReturnModification ,
27+ SimpleLinear , SimpleModification , SimpleModificationInner , SloppyParsingParameters ,
2828 } ,
2929 system:: { Mass , MassOverCharge , Time , isize:: Charge } ,
3030} ;
@@ -33,7 +33,7 @@ use crate::{
3333#[ derive( Clone , Debug , Default , Deserialize , PartialEq , Serialize ) ]
3434pub struct MZTabData {
3535 /// The peptide's sequence corresponding to the PSM
36- pub peptide : Option < Peptidoform < SemiAmbiguous > > ,
36+ pub peptide : Option < Peptidoform < SimpleLinear > > ,
3737 /// A unique identifier for a PSM within the file. If a PSM can be matched to
3838 /// multiple proteins, the same PSM should be represented on multiple rows with
3939 /// different accessions and the same PSM_ID.
@@ -84,10 +84,8 @@ impl MZTabData {
8484 pub fn parse_file (
8585 path : impl AsRef < std:: path:: Path > ,
8686 custom_database : Option < & CustomDatabase > ,
87- ) -> Result <
88- Box < dyn Iterator < Item = Result < Self , BoxedError < ' static > > > + ' _ > ,
89- BoxedError < ' static > ,
90- > {
87+ ) -> Result < Box < dyn Iterator < Item = Result < Self , BoxedError < ' static > > > + ' _ > , BoxedError < ' static > >
88+ {
9189 let file = File :: open ( path. as_ref ( ) ) . map_err ( |e| {
9290 BoxedError :: error (
9391 "Could not open file" ,
@@ -285,37 +283,24 @@ impl MZTabData {
285283 . required_column ( "modifications" )
286284 . map_err ( BoxedError :: to_owned) ?;
287285 let mut mod_index = mod_range. start ;
288- let modifications: Vec < ( usize , SimpleModification ) > = mod_column
289- . split ( ',' )
290- . flat_map ( |definition| {
291- let pair = definition
292- . split_once ( '-' )
293- . map ( |( pos, _) | Ok ( (
294- pos. parse :: < usize > ( ) . map_err ( |err| BoxedError :: error (
295- "Invalid modification position" ,
296- format ! ( "The position {}" , explain_number_error( & err) ) ,
297- Context :: line_range ( Some ( line. line_index as u32 ) , line. line , mod_range. clone ( ) ) ,
298- ) ) ?,
299- SimpleModificationInner :: parse_pro_forma (
300- line. line ,
301- mod_index+1 +pos. len ( ) ..mod_index+definition. len ( ) ,
302- & mut Vec :: new ( ) ,
303- & mut Vec :: new ( ) ,
304- custom_database) ?. 0 . defined ( )
305- . ok_or_else (
306- || BoxedError :: error (
307- "Invalid modification" ,
308- "A modification should be a fully defined modification, no cross-link or ambiguous modification" ,
309- Context :: line_range ( Some ( line. line_index as u32 ) , line. line , mod_range. clone ( ) ) ) ) ?) ) )
310- . ok_or_else (
311- || BoxedError :: error (
312- "Invalid modification" ,
313- "A modification should be the position followed by a hyphen ('-') followed by the modification" ,
314- Context :: line_range ( Some ( line. line_index as u32 ) , line. line , mod_range. clone ( ) ) ) ) ;
315- mod_index += definition. len ( ) + 1 ;
316- pair
317- } )
318- . collect :: < Result < Vec < _ > , BoxedError > > ( ) ?;
286+ let modifications: Vec < MZTabReturnModification > =
287+ if mod_column. eq_ignore_ascii_case ( "null" ) || mod_column == "0" {
288+ Vec :: new ( )
289+ } else {
290+ mod_column
291+ . split ( ',' )
292+ . map ( |definition| {
293+ let res = parse_modification (
294+ line. line ,
295+ mod_index..mod_index + definition. len ( ) ,
296+ custom_database,
297+ line. line_index as u32 ,
298+ ) ;
299+ mod_index = mod_index + definition. len ( ) + 1 ;
300+ res
301+ } )
302+ . collect :: < Result < Vec < _ > , BoxedError > > ( ) ?
303+ } ;
319304
320305 let mut result = Self {
321306 peptide : {
@@ -327,24 +312,38 @@ impl MZTabData {
327312 if range. is_empty ( ) {
328313 None
329314 } else {
330- let mut peptide = Peptidoform :: sloppy_pro_forma (
315+ let mut peptide: Peptidoform < SimpleLinear > = Peptidoform :: sloppy_pro_forma (
331316 line. line ,
332317 range,
333318 custom_database,
334319 & SloppyParsingParameters {
335320 allow_unwrapped_modifications : true ,
336321 ..Default :: default ( )
337322 } ,
338- ) ?;
339- for ( location, modification) in modifications {
340- match location {
341- 0 => peptide. add_simple_n_term ( modification) ,
342- c if c == peptide. len ( ) + 1 => {
343- peptide. add_simple_c_term ( modification) ;
323+ ) ?
324+ . into ( ) ;
325+ for modification in modifications {
326+ match modification {
327+ MZTabReturnModification :: Defined ( location, modification) => {
328+ match location {
329+ 0 => peptide. add_simple_n_term ( modification) ,
330+ c if c == peptide. len ( ) + 1 => {
331+ peptide. add_simple_c_term ( modification) ;
332+ }
333+ i => {
334+ peptide. sequence_mut ( ) [ i - 1 ]
335+ . add_simple_modification ( modification) ;
336+ }
337+ }
344338 }
345- i => {
346- peptide. sequence_mut ( ) [ i - 1 ] . add_simple_modification ( modification) ;
339+ MZTabReturnModification :: GlobalAmbiguous ( modification) => {
340+ let _possible = peptide. add_unknown_position_modification (
341+ modification,
342+ 0 ..peptide. len ( ) ,
343+ & MUPSettings :: default ( ) ,
344+ ) ;
347345 }
346+ _ => todo ! ( ) ,
348347 }
349348 }
350349 Some (
@@ -695,6 +694,165 @@ impl MZTabData {
695694 }
696695}
697696
697+ enum MZTabReturnModification {
698+ GlobalAmbiguous ( SimpleModification ) ,
699+ Ambiguous ( Vec < ( usize , Option < f64 > ) > , SimpleModification ) ,
700+ Defined ( usize , SimpleModification ) ,
701+ }
702+
703+ fn parse_modification < ' a > (
704+ line : & ' a str ,
705+ range : Range < usize > ,
706+ custom_database : Option < & CustomDatabase > ,
707+ line_index : u32 ,
708+ ) -> Result < MZTabReturnModification , BoxedError < ' a > > {
709+ if let Some ( ( pos, modification) ) = line[ range. clone ( ) ] . split_once ( '-' ) {
710+ let position = if pos. eq_ignore_ascii_case ( "null" ) {
711+ Ok ( None )
712+ } else {
713+ // TODO: handle multiple and handle probabilities
714+ pos. parse :: < usize > ( )
715+ . map_err ( |err| {
716+ BoxedError :: error (
717+ "Invalid modification position" ,
718+ format ! ( "The position {}" , explain_number_error( & err) ) ,
719+ Context :: line_range (
720+ Some ( line_index) ,
721+ line,
722+ range. start ..range. start + pos. len ( ) ,
723+ ) ,
724+ )
725+ } )
726+ . map ( Some )
727+ } ?;
728+
729+ let modification = if let Some ( ( tag, value) ) = modification. split_once ( ':' ) {
730+ let value_range = range. start + pos. len ( ) + tag. len ( ) + 2 ..range. end ;
731+ let value_context = Context :: line_range ( Some ( line_index) , line, value_range. clone ( ) ) ;
732+ if tag. eq_ignore_ascii_case ( "unimod" ) {
733+ Ontology :: Unimod
734+ . find_id (
735+ value. parse :: < usize > ( ) . map_err ( |err| {
736+ BoxedError :: error (
737+ "Invalid unimod code" ,
738+ format ! ( "The unimod modification {}" , explain_number_error( & err) ) ,
739+ value_context. clone ( ) ,
740+ )
741+ } ) ?,
742+ None ,
743+ )
744+ . ok_or_else ( || {
745+ BoxedError :: error (
746+ "Invalid unimod code" ,
747+ "The given unimod modification does not exist" ,
748+ value_context. clone ( ) ,
749+ )
750+ } ) ?
751+ } else if tag. eq_ignore_ascii_case ( "mod" ) {
752+ Ontology :: Psimod
753+ . find_id (
754+ value. parse :: < usize > ( ) . map_err ( |err| {
755+ BoxedError :: error (
756+ "Invalid PSI-MOD code" ,
757+ format ! ( "The PSI-MOD modification {}" , explain_number_error( & err) ) ,
758+ value_context. clone ( ) ,
759+ )
760+ } ) ?,
761+ None ,
762+ )
763+ . ok_or_else ( || {
764+ BoxedError :: error (
765+ "Invalid PSI-MOD code" ,
766+ "The given PSI-MOD modification does not exist" ,
767+ value_context. clone ( ) ,
768+ )
769+ } ) ?
770+ } else if tag. eq_ignore_ascii_case ( "custom" ) {
771+ Ontology :: Custom
772+ . find_id (
773+ value. parse :: < usize > ( ) . map_err ( |err| {
774+ BoxedError :: error (
775+ "Invalid custom code" ,
776+ format ! ( "The custom modification {}" , explain_number_error( & err) ) ,
777+ value_context. clone ( ) ,
778+ )
779+ } ) ?,
780+ custom_database,
781+ )
782+ . ok_or_else ( || {
783+ BoxedError :: error (
784+ "Invalid custom code" ,
785+ "The given custom modification does not exist" ,
786+ value_context. clone ( ) ,
787+ )
788+ } ) ?
789+ } else if tag. eq_ignore_ascii_case ( "chemmod" ) {
790+ if let Ok ( mass) = value. parse :: < f64 > ( ) {
791+ SimpleModificationInner :: Mass ( Mass :: new :: < crate :: system:: dalton > ( mass) . into ( ) )
792+ } else {
793+ let factor = match line. as_bytes ( ) [ value_range. start ] {
794+ b'-' => -1 ,
795+ b'+' => 1 ,
796+ _ => {
797+ return Err ( BoxedError :: error (
798+ "Invalid mzTab modification" ,
799+ "A chemmod formula modification should be prepended by a sign" ,
800+ Context :: line_range (
801+ Some ( line_index) ,
802+ line,
803+ range. start + pos. len ( ) + 1
804+ ..range. start + pos. len ( ) + 1 + tag. len ( ) ,
805+ ) ,
806+ ) ) ;
807+ }
808+ } ;
809+ MolecularFormula :: from_pro_forma (
810+ line,
811+ value_range. start + 1 ..value_range. end ,
812+ false ,
813+ false ,
814+ true ,
815+ true ,
816+ )
817+ . map ( |f| SimpleModificationInner :: Formula ( f * factor) ) ?
818+ }
819+ . into ( )
820+ } else {
821+ return Err ( BoxedError :: error (
822+ "Invalid mzTab modification" ,
823+ "The modification should be prepended by a tag describing the kind of modification, the possible tags are: 'unimod', 'mod', and 'chemmod'" ,
824+ Context :: line_range (
825+ Some ( line_index) ,
826+ line,
827+ range. start + pos. len ( ) + 1 ..range. start + pos. len ( ) + 1 + tag. len ( ) ,
828+ ) ,
829+ ) ) ;
830+ }
831+ } else {
832+ return Err ( BoxedError :: error (
833+ "Invalid mzTab modification" ,
834+ "An mzTab modification should be in format 'tag:value' but the colon (':') is missing" ,
835+ Context :: line_range (
836+ Some ( line_index) ,
837+ line,
838+ range. start + pos. len ( ) + 1 ..range. end ,
839+ ) ,
840+ ) ) ;
841+ } ;
842+
843+ Ok ( match position {
844+ None => MZTabReturnModification :: GlobalAmbiguous ( modification) ,
845+ Some ( pos) => MZTabReturnModification :: Defined ( pos, modification) ,
846+ } )
847+ } else {
848+ Err ( BoxedError :: error (
849+ "Invalid modification" ,
850+ "A modification should be the position followed by a hyphen ('-') followed by the modification" ,
851+ Context :: line_range ( Some ( line_index) , line, range) ,
852+ ) )
853+ }
854+ }
855+
698856#[ derive( Clone , Copy , Debug ) ]
699857struct PSMLine < ' a > {
700858 line_index : usize ,
@@ -704,7 +862,7 @@ struct PSMLine<'a> {
704862}
705863
706864impl < ' a > PSMLine < ' a > {
707- /// Form a indexable line out of a set of fields
865+ /// Form an indexable line out of a set of fields
708866 /// # Errors
709867 /// When there is no header or the line has a different number of columns
710868 fn new (
@@ -760,7 +918,7 @@ impl<'a> PSMLine<'a> {
760918 }
761919}
762920
763- impl From < MZTabData > for IdentifiedPeptidoform < SemiAmbiguous , MaybePeptidoform > {
921+ impl From < MZTabData > for IdentifiedPeptidoform < SimpleLinear , MaybePeptidoform > {
764922 fn from ( value : MZTabData ) -> Self {
765923 Self {
766924 score : ( !value. search_engine . is_empty ( ) )
0 commit comments