GCC Code Coverage Report


./
File: src/XpertMass/Polymer.cpp
Date: 2024-08-24 11:26:06
Lines:
224/694
32.3%
Functions:
25/49
51.0%
Branches:
242/1359
17.8%

Line Branch Exec Source
1 /* BEGIN software license
2 *
3 * MsXpertSuite - mass spectrometry software suite
4 * -----------------------------------------------
5 * Copyright(C) 2009,...,2018 Filippo Rusconi
6 *
7 * http://www.msxpertsuite.org
8 *
9 * This file is part of the MsXpertSuite project.
10 *
11 * The MsXpertSuite project is the successor of the massXpert project. This
12 * project now includes various independent modules:
13 *
14 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
15 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
16 *
17 * This program is free software: you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation, either version 3 of the License, or
20 * (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with this program. If not, see <http://www.gnu.org/licenses/>.
29 *
30 * END software license
31 */
32
33
34 /////////////////////// Qt includes
35 #include <QDateTime>
36 #include <QFile>
37 #include <QIODevice>
38
39 /////////////////////// Local includes
40 #include "Polymer.hpp"
41 #include "Modif.hpp"
42
43 #include "PolChemDef.hpp"
44 #include "CrossLink.hpp"
45
46
47 int polymerMetaTypeId = qRegisterMetaType<MsXpS::libXpertMass::Polymer>(
48 "MsXpS::libXpertMass::Polymer");
49
50 int polymerSPtrMetaTypeId = qRegisterMetaType<MsXpS::libXpertMass::PolymerSPtr>(
51 "MsXpS::libXpertMass::PolymerSPtr");
52
53 int polymerCstSPtrMetaTypeId =
54 qRegisterMetaType<MsXpS::libXpertMass::PolymerCstSPtr>(
55 "MsXpS::libXpertMass::PolymerCstSPtr");
56
57
58 namespace MsXpS
59 {
60
61 namespace libXpertMass
62 {
63
64
65 /*!
66 \class MsXpS::libXpertMass::Polymer
67 \inmodule libXpertMass
68 \ingroup PolChemDefBuildingdBlocks
69 \inheaderfile Polymer.hpp
70
71 \brief The Polymer class provides abstractions to work with
72 a polymer molecule (protein or saccharide , for example).
73
74 The Polymer class provides a polymer sequence. In the
75 protein world, a polymer sequence is a protein. From a computing standpoint,
76 that sequence is first created by chaining amino acid residues (the residue
77 chain). In a second step, the entity is set to a finished polymer state by
78 adding the N-terminal proton and the C-terminal hydroxyl residue. It might
79 happen also that the ends of a polymer be chemically modified (acetylation of
80 the N-terminal residue, for example).
81
82 The Polymer class allows modelling a polymer sequence in its finest details.
83 */
84
85 /*!
86 \variable int MsXpS::libXpertMass::POL_SEQ_FILE_FORMAT_VERSION
87
88 \brief Version number of the polymer sequence file format.
89 */
90
91 /*!
92 \variable int MsXpS::libXpertMass::Polymer::m_name
93
94 \brief The name of the polymer, for example, "Apomyoglobin".
95 */
96
97 /*!
98 \variable int MsXpS::libXpertMass::Polymer::m_code
99
100 \brief The code of the polymer, for example, the accession number in
101 SwissProt.
102 */
103
104 /*!
105 \variable int MsXpS::libXpertMass::Polymer::m_author
106
107 \brief The author or creator of the file.
108 */
109
110 /*!
111 \variable int MsXpS::libXpertMass::Polymer::m_dateTime
112
113 \brief The date and time of last modification.
114 */
115
116 /*!
117 \variable int MsXpS::libXpertMass::Polymer::m_leftEndModif
118
119 \brief The left end modification.
120 */
121
122 /*!
123 \variable int MsXpS::libXpertMass::Polymer::m_rightEndModif
124
125 \brief The right end modification.
126 */
127
128 /*!
129 \variable int MsXpS::libXpertMass::Polymer::mpa_crossLinkList
130
131 \brief The list of CrossLink events in the polymer sequence.
132 */
133
134 /*!
135 \brief Constructs a polymer with \a name, \a code, \a author and the \a
136 pol_chem_def_csp polymer chemistry definition.
137 */
138 116 Polymer::Polymer(PolChemDefCstSPtr pol_chem_def_csp,
139 const QString &name,
140 const QString &code,
141 116 const QString &author)
142 : Ionizable(pol_chem_def_csp, name),
143 116 m_code(code),
144 116 m_author(author),
145
2/4
✓ Branch 2 taken 116 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 116 times.
✗ Branch 7 not taken.
116 m_leftEndModif(pol_chem_def_csp, "NOT_SET"),
146
6/12
✓ Branch 3 taken 116 times.
✗ Branch 4 not taken.
✓ Branch 7 taken 116 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 116 times.
✗ Branch 11 not taken.
✓ Branch 14 taken 116 times.
✗ Branch 15 not taken.
✓ Branch 24 taken 116 times.
✗ Branch 25 not taken.
✓ Branch 28 taken 116 times.
✗ Branch 29 not taken.
348 m_rightEndModif(pol_chem_def_csp, "NOT_SET")
147 {
148
2/6
✓ Branch 1 taken 116 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 116 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
116 mpa_crossLinkList = new CrossLinkList();
149 116 }
150
151
152 /*!
153 \brief Destructs the polymer.
154 */
155 232 Polymer::~Polymer()
156 {
157
1/2
✓ Branch 0 taken 116 times.
✗ Branch 1 not taken.
232 delete mpa_crossLinkList;
158 232 }
159
160
161 /*!
162 \brief Sets the \a code.
163 */
164 void
165 4 Polymer::setCode(const QString &code)
166 {
167 4 m_code = code;
168 4 }
169
170 /*!
171 \brief Returns the code.
172 */
173 QString
174 108 Polymer::code() const
175 {
176 108 return m_code;
177 }
178 /*!
179 \brief Sets the \a author.
180 */
181 void
182 4 Polymer::setAuthor(const QString &author)
183 {
184 4 m_author = author;
185 4 }
186 /*!
187 \brief Returns the author.
188 */
189 QString
190 108 Polymer::author() const
191 {
192 108 return m_author;
193 }
194
195 /*!
196 \brief Sets the file path to \a file_path.
197 */
198 void
199 212 Polymer::setFilePath(const QString &file_path)
200 {
201 212 m_filePath = file_path;
202 212 }
203
204 /*!
205 \brief Returns the file path.
206 */
207 QString
208 104 Polymer::filePath() const
209 {
210 104 return m_filePath;
211 }
212
213 /*!
214 \brief Set the date and time to \a date_time.
215 */
216 void
217 Polymer::setDateTime(const QString &date_time)
218 {
219 m_dateTime = QDateTime::fromString((QString)date_time, "yyyy-MM-dd:mm:ss");
220 }
221
222 /*!
223 \brief Returns the date and time.
224 */
225 QString
226 Polymer::dateTime() const
227 {
228 return m_dateTime.toString("yyyy-MM-dd:mm:ss");
229 }
230
231 /*!
232 \brief Returns true if a least one monomer in the sequence between residues
233 \a left_index and \a right_index is modified.
234 */
235 bool
236 8 Polymer::hasModifiedMonomer(int left_index, int right_index) const
237 {
238
3/6
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 8 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 8 times.
8 if(left_index >= size() || right_index >= size())
239 qFatal("Fatal error. Program aborted.");
240
241
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if(left_index > right_index)
242 qFatal("Fatal error. Program aborted.");
243
244 8 int startIndex = left_index;
245 8 int endIndex = right_index;
246
247
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if(left_index == -1)
248 startIndex = 0;
249
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 8 times.
8 if(right_index == -1)
250 endIndex = size() - 1;
251
252
2/2
✓ Branch 0 taken 648 times.
✓ Branch 1 taken 4 times.
652 for(int iter = startIndex; iter <= endIndex; ++iter)
253 {
254
2/2
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 644 times.
648 if(at(iter)->isModified())
255 4 return true;
256 }
257
258 4 return false;
259 }
260
261 /*!
262 \brief Sets this polymer's left end modification to \a name.
263
264 Returns true if a modification by \a name was found in the member polymer
265 chemistry definition's list of modifications and if that modification could be
266 set and its masses could be calculated successfully, otherwise returns false.
267 */
268 bool
269 48 Polymer::setLeftEndModif(const QString &name)
270 {
271
2/4
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 48 times.
✗ Branch 6 not taken.
48 const QList<Modif *> &refList = getPolChemDefCstSPtr()->modifList();
272
273
3/6
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 48 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 48 times.
48 if(name.isNull() || name.isEmpty())
274 {
275 // Reset the modif to nothing.
276 m_leftEndModif.reset();
277 }
278
279
1/2
✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
48 if(Modif::isNameInList(name, refList, &m_leftEndModif) != -1)
280 {
281
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 48 times.
48 if(!m_leftEndModif.calculateMasses())
282 return false;
283 else
284 48 return true;
285 }
286 else
287 return false;
288 }
289
290 /*!
291 \brief Sets this polymer's left end modification to \a modif.
292
293 Returns true if the modification could be set and its masses could be
294 calculated successfully, otherwise returns false.
295 */
296 bool
297 8 Polymer::setLeftEndModif(const Modif &modif)
298 {
299 8 m_leftEndModif = modif;
300
301 // qDebug() << "The left end modif set:" << m_leftEndModif.toString();
302
303
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
8 if(!m_leftEndModif.calculateMasses())
304 return false;
305 else
306 8 return true;
307 }
308
309 /*!
310 \brief Returns this polymer's left end modif.
311 */
312 const Modif &
313 76 Polymer::leftEndModif() const
314 {
315 76 return m_leftEndModif;
316 }
317
318 /*!
319 \brief Returns true if this polymer is modified at its left end.
320 */
321 bool
322 60 Polymer::isLeftEndModified() const
323 {
324 60 return m_leftEndModif.checkSyntax();
325 }
326
327 /*!
328 \brief Sets this polymer's right end modification to \a name.
329
330 Returns true if a modification by \a name was found in the member polymer
331 chemistry definition's list of modifications and if that modification could be
332 set and its masses could be calculated successfully, otherwise returns false.
333 */
334 bool
335 44 Polymer::setRightEndModif(const QString &name)
336 {
337
2/4
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 44 times.
✗ Branch 6 not taken.
44 const QList<Modif *> &refList = getPolChemDefCstSPtr()->modifList();
338
339
3/6
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 44 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 44 times.
44 if(name.isNull() || name.isEmpty())
340 {
341 // Reset the modif to nothing.
342 m_rightEndModif.reset();
343 }
344
345
1/2
✓ Branch 1 taken 44 times.
✗ Branch 2 not taken.
44 if(Modif::isNameInList(name, refList, &m_rightEndModif) != -1)
346 {
347
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 44 times.
44 if(!m_rightEndModif.calculateMasses())
348 return false;
349 else
350 44 return true;
351 }
352 else
353 return false;
354 }
355
356 /*!
357 \brief Sets this polymer's right end modification to \a modif.
358
359 Returns true if the modification could be set and its masses could be
360 calculated successfully, otherwise returns false.
361 */
362 bool
363 4 Polymer::setRightEndModif(const Modif &modif)
364 {
365 4 m_rightEndModif = modif;
366
367
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
4 if(!m_rightEndModif.calculateMasses())
368 return false;
369 else
370 4 return true;
371 }
372
373 /*!
374 \brief Returns this polymer's right end modif.
375 */
376 const Modif &
377 72 Polymer::rightEndModif() const
378 {
379 72 return m_rightEndModif;
380 }
381
382 /*!
383 \brief Returns true if this polymer is modified at its right end.
384 */
385 bool
386 60 Polymer::isRightEndModified() const
387 {
388 60 return m_rightEndModif.checkSyntax();
389 }
390
391 /*!
392 \brief Returns a reference to the list of CrossLink entities associated to
393 this polymer.
394 */
395 const CrossLinkList &
396 Polymer::crossLinkList() const
397 {
398 return *mpa_crossLinkList;
399 }
400
401 /*!
402 \brief Returns the list of CrossLink entities associated to this polymer.
403 */
404 CrossLinkList *
405 Polymer::crossLinkListPtr()
406 {
407 return mpa_crossLinkList;
408 }
409
410 /*!
411 \brief Fills-in the \a index_list_p with indices of monomers involved in
412 cross-links.
413
414 This function iterates in this polymer's list of cross-links and
415 checks for each cross-link the monomers that are involved in that cross-link
416 have indices:
417 \list
418 \li Fully contained in the indices range [\a start_index, \a end_index]
419 \li Only partially contained in the range
420 \li Not contained at all in the range.
421 \endlist
422
423 The \a index_list_p is filled only with indices of monomers involved in
424 cross-links of which the monomers are fully contained in range [\a start_index,
425 \a end_index].
426
427 If \a partials is not nullptr, then, it is set to the count of cross-links
428 involving monomers only partially contained in range [\a start_index,
429 \a end_index].
430
431 Returns true if at least one cross-link was found to be fully encompassed by
432 the range, false otherwise.
433 */
434 bool
435 Polymer::crossLinkedMonomerIndexList(int start_index,
436 int end_index,
437 QList<int> *index_list_p,
438 int *partials)
439 {
440 if(index_list_p == nullptr)
441 qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);
442
443 // We are asked to return a list of all the indices of the
444 // monomers involved in cross-links that link any monomer in the
445 // region delimited by startIndex and endIndex.
446
447 bool oneFound = false;
448
449 // Iterate in the list of cross-links set to *this polymer. For
450 // each iterated cross-link, check if it is fully encompasse by
451 // the region delimited by [startIndex--endIndex]. If so, get the
452 // indices of the monomers involved in that cross-link and append
453 // these (no duplicates) to the indexList passed as param to the
454 // function.
455 for(int jter = 0; jter < mpa_crossLinkList->size(); ++jter)
456 {
457 CrossLink *crossLink = mpa_crossLinkList->at(jter);
458
459 // qDebug() << __FILE__ << __LINE__
460 // << "Cross-link:" << crossLink;
461
462 int ret = crossLink->encompassedBy(start_index, end_index);
463
464 if(ret == CROSS_LINK_ENCOMPASSED_FULL)
465 {
466 // qDebug() << __FILE__ << __LINE__
467 // << "Cross-link:" << crossLink
468 // << "is encompassed by region:"
469 // << startIndex << "-" << endIndex;
470
471 QList<int> localIndexList;
472
473 crossLink->monomerIndexList(&localIndexList);
474
475 // qDebug() << __FILE__ << __LINE__
476 // << "Index list:" << localIndexList;
477
478 // Avoid duplicating indices in the target index
479 // list. This will have the excellent side effect of
480 // condensating into one single region a number of
481 // contained cross-linked regions. For example, from the
482 // Kunitz inhibitor, there are the following cross-links:
483
484 // 90 -- 187
485 // 230 -- 280
486 // 239 -- 263
487 // 255 -- 276
488 // 286 -- 336
489 // 295 -- 319
490 // 311 -- 332
491
492 // Thanks to our strategy below, the cross-links
493 // 230 -- 280
494 // 239 -- 263
495 // 255 -- 276
496 // become contained in one single region:
497 // 230--276.
498
499 // Same for the cross-links
500 // 286 -- 336
501 // 295 -- 319
502 // 311 -- 332
503 // Which become contained in 286--336
504
505 for(int iter = 0, size = localIndexList.size(); iter < size; ++iter)
506 {
507 int index = localIndexList.at(iter);
508
509 if(!index_list_p->contains(index))
510 index_list_p->append(index);
511 }
512
513 oneFound = true;
514 }
515 else if(ret == CROSS_LINK_ENCOMPASSED_PARTIAL)
516 {
517 if(partials)
518 ++(*partials);
519 }
520 }
521
522 return oneFound;
523 }
524
525
526 /*!
527 \brief Fills-in the \a crosslink_list_p with cross-links.
528
529 This function iterates in this polymer's list of cross-links and checks for
530 each cross-link the monomers that are involved in that cross-link have indices:
531 \list
532 \li Fully contained in the indices range [\a start_index, \a end_index]
533 \li Only partially contained in the range
534 \li Not contained at all in the range.
535 \endlist
536
537 The \a crosslink_list_p is filled only with cross-links found to be
538 fully encompassed by the range.
539
540 If \a partials is not nullptr, then, it is set to the count of cross-links
541 involving monomers only partially contained in range [\a start_index,
542 \a end_index].
543
544 Returns true if at least one cross-link was found to be fully encompassed by
545 the range, false otherwise.
546 */
547 bool
548 Polymer::crossLinkList(int start_index,
549 int end_index,
550 QList<CrossLink *> *crosslink_list_p,
551 int *partials)
552 {
553 if(crosslink_list_p == nullptr)
554 qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);
555
556 // We are asked to return a list of all the cross-links that are
557 // fully encompassed by the [startIndex--endIndex] region.
558
559 bool oneFound = false;
560
561 // Iterate in the list of cross-links set to *this polymer. For
562 // each iterated cross-link, check if it is fully encompassed by
563 // the [startIndex--endIndex] region. If so simply add it to the
564 // list of cross-links.
565 for(int jter = 0; jter < mpa_crossLinkList->size(); ++jter)
566 {
567 CrossLink *crossLink = mpa_crossLinkList->at(jter);
568
569 // qDebug() << __FILE__ << __LINE__
570 // << "Cross-link:" << crossLink;
571
572 int ret = crossLink->encompassedBy(start_index, end_index);
573
574 if(ret == CROSS_LINK_ENCOMPASSED_FULL)
575 {
576 // qDebug() << __FILE__ << __LINE__
577 // << "Cross-link:" << crossLink
578 // << "is encompassed by region:"
579 // << startIndex << "-" << endIndex;
580 crosslink_list_p->append(crossLink);
581
582 oneFound = true;
583 }
584 else if(ret == CROSS_LINK_ENCOMPASSED_PARTIAL)
585 {
586 if(partials)
587 ++(*partials);
588 }
589 }
590
591 return oneFound;
592 }
593
594 /*!
595 \brief Prepares for the removal of \a monomer from this polymer's sequence.
596
597 The challenge is that monomers might be involved in cross-links. In that
598 case, removing a monomer that was involved in a cross-link need preparation:
599 it needs to first be uncross-linked.
600
601 Returns true;
602
603 \sa uncrossLink()
604 */
605 bool
606 Polymer::prepareMonomerRemoval(const Monomer *monomer_p)
607 {
608 Q_ASSERT(monomer_p);
609
610 // We are asked to destroy **all** the crossLinks that involve the
611 // 'monomer'.
612
613 // Iterate in the list of crossLinks, and for each crossLink check
614 // if it involves 'monomer_p'.
615
616 if(!mpa_crossLinkList->size())
617 return true;
618
619 int iter = mpa_crossLinkList->size();
620
621 while(iter)
622 {
623 CrossLink *crossLink = mpa_crossLinkList->at(iter - 1);
624
625 if(crossLink->involvesMonomer(monomer_p) != -1)
626 {
627 // The current crossLink involves the monomer_p for which all
628 // the crossLinks should be destroyed.
629
630 uncrossLink(crossLink);
631 // The uncrossLinking takes care of removing the crossLink
632 // from the list.
633 }
634 --iter;
635 }
636
637 return true;
638 }
639
640 /*!
641 \brief Removes the monomer at \a index.
642
643 The monomer is first uncross-linked (if it was).
644
645 Returns false if the uncross-linking of the monomer failed, true otherwise.
646 */
647 bool
648 Polymer::removeMonomerAt(int index)
649 {
650 Q_ASSERT(index > -1);
651 Q_ASSERT(index < size());
652
653 Monomer *monomer = const_cast<Monomer *>(at(index));
654
655 if(!prepareMonomerRemoval(monomer))
656 return false;
657
658 m_monomerList.removeAt(index);
659
660 delete monomer;
661
662 return true;
663 }
664
665 /*!
666 \brief Computes a MD5SUM has with the data described in \a hash_data_specifs.
667
668 If hash_data_specifs & HASH_ACCOUNT_SEQUENCE, the sequence is included in the
669 hash computation.
670 If hash_data_specifs & HASH_ACCOUNT_MONOMER_MODIF, the monomer modifications
671 are included in the hash computation.
672 If hash_data_specifs & HASH_ACCOUNT_POLYMER_MODIF, the polymer modifications
673 are include in the hash computation.
674
675 Returns the hash.
676 */
677 QByteArray
678 Polymer::md5Sum(int hash_data_specifs) const
679 {
680 // We first need to craft a complete string that encapsulates the
681 // maximum number of information from the polymer sequence (sequence,
682 // modifications in the monomers...) depending on the parameter passed
683 // to the function.
684
685 QString *dataString = new QString;
686
687 if(hash_data_specifs & HASH_ACCOUNT_SEQUENCE)
688 {
689 for(int iter = 0; iter < size(); ++iter)
690 {
691 const Monomer *monomer = at(iter);
692
693 dataString->append(monomer->code());
694
695 if(hash_data_specifs & HASH_ACCOUNT_MONOMER_MODIF)
696 {
697 if(monomer->isModified())
698 {
699 QList<Modif *> *modifList = monomer->modifList();
700
701 for(int jter = 0; jter < modifList->size(); ++jter)
702 {
703 Modif *modif = modifList->at(jter);
704
705 dataString->append(modif->name());
706 }
707 }
708 }
709 }
710 }
711
712 if(hash_data_specifs & HASH_ACCOUNT_POLYMER_MODIF)
713 {
714 if(isLeftEndModified())
715 {
716 dataString->append(leftEndModif().formula());
717 }
718 if(isRightEndModified())
719 {
720 dataString->append(rightEndModif().formula());
721 }
722 }
723
724 // Now that we have the data string, we can craft the hash itself:
725
726 QByteArray hash =
727 QCryptographicHash::hash(dataString->toUtf8(), QCryptographicHash::Md5);
728
729 delete dataString;
730
731 return hash;
732 }
733
734 // MASS CALCULATION FUNCTIONS
735
736 /*!
737 \brief Accounts the masses of this polymer.
738
739 Accounting masses means calculating masses and adding the results to
740 objects. Here the masses of this polymer are calculated and added to those of
741 this polymer.
742
743 The calculation of the masses (monoisotopic and average) is configured in \a
744 calc_options.
745
746 Returns true upon success, false otherwise.
747
748 \sa CalcOptions
749 */
750 bool
751 Polymer::accountMasses(const CalcOptions &calc_options)
752 {
753 // We do not want to reset masses prior to calculating the masses
754 // because we are accounting them in the polymer itself.
755 return calculateMasses(calc_options, false);
756 }
757
758 /*!
759 \brief Accounts the masses of this polymer.
760
761 Accounting masses means calculating masses and adding the results to
762 objects. Here the masses of this polymer are calculated and set to \a mono and
763 \a avg.
764
765 The calculation of the masses (monoisotopic and average) is configured in \a
766 calc_options.
767
768 \sa CalcOptions
769 */
770 bool
771 Polymer::accountMasses(Polymer *polymer_p,
772 const CalcOptions &calc_options,
773 double *mono,
774 double *avg)
775 {
776 // We do not want to reset masses prior to calculating the masses
777 // because we are accounting them in the polymer itself.
778 return calculateMasses(polymer_p, calc_options, mono, avg, false);
779 }
780
781 /*!
782 \brief Calculates the masses of this polymer.
783
784 The calculated masses are set to this polymer. If \a reset is true, the
785 masses of this polymer are first reset, otherwise this polymer's masses are
786 incremented with those obtained from the calculation.
787
788 The calculation of the masses (monoisotopic and average) is configured in \a
789 calc_options.
790
791 Returns true.
792
793 \sa CalcOptions
794 */
795 bool
796 72 Polymer::calculateMasses(const CalcOptions &calc_options, bool reset)
797 {
798 72 return calculateMasses(this, calc_options, &m_mono, &m_avg, reset);
799 }
800
801 /*!
802 \brief Calculates the masses of \a polymer_p.
803
804 The calculated masses are set to \a mono and \a avg (that cannot be nullptr).
805
806 If \a reset is true, \a mono and \a avg are reset, otherwise they are
807 incremented with the masses obtained from the calculation.
808
809 The calculation of the masses (monoisotopic and average) is configured in \a
810 calc_options.
811
812 Returns true.
813
814 \sa CalcOptions
815 */
816 bool
817 72 Polymer::calculateMasses(Polymer *polymer_p,
818 const CalcOptions &calc_options,
819 double *mono,
820 double *avg,
821 bool reset)
822 {
823
824
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 72 times.
72 Q_ASSERT(polymer_p != nullptr);
825
2/4
✓ Branch 0 taken 72 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 72 times.
✗ Branch 3 not taken.
72 Q_ASSERT(mono != nullptr && avg != nullptr);
826
827 72 int ret = 0;
828
829
1/2
✓ Branch 0 taken 72 times.
✗ Branch 1 not taken.
72 if(reset)
830 {
831 // Reset the masses to 0.
832 72 *mono = 0;
833 72 *avg = 0;
834 }
835
836 // The calc_options parameter holds a CoordinateList instance
837 // listing all the coordinates of the different(if any) region
838 // selections of the polymer sequence. This CoordinateList is
839 // never empty, as it should at least contain the pseudo-selection
840 // of the sequence, that is [start of sequence, cursor index] or
841 // the [-1, -1] values for whole sequence mass
842 // calculation. Iterate in this CoordinateList and for each item
843 // call this function.
844
845
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 72 times.
72 Q_ASSERT(calc_options.coordinateList().size());
846
847 // For each Coordinates item in the calc_options.coordinateList()
848 // list of such items, perform the mass calculation.
849
850
851
2/2
✓ Branch 2 taken 72 times.
✓ Branch 3 taken 72 times.
144 for(int iter = 0; iter < calc_options.coordinateList().size(); ++iter)
852 {
853 // New coordinates instance we are iterating into.
854
2/4
✓ Branch 1 taken 72 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 72 times.
✗ Branch 6 not taken.
72 Coordinates coordinates(*(calc_options.coordinateList().at(iter)));
855
856 // qDebug() << "Iterating in Coordinates:" << coordinates.indicesAsText();
857
858 // If the start value is less than 0(typically it is set to
859 // -1) then set it to 0(the first monomer of the sequence.
860
861
2/4
✓ Branch 1 taken 72 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 72 times.
72 if(coordinates.start() < 0)
862 coordinates.setStart(0);
863
864 // If the end value is less than 0(typically it is set to -1)
865 // then set it to be the size of the polymer so that all the
866 // protein is taken into account in the calculation.
867
868
2/4
✓ Branch 1 taken 72 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 72 times.
72 if(coordinates.end() < 0)
869 coordinates.setEnd(polymer_p->size() - 1);
870
871 // If the end value is greater than the polymer size, set it
872 // to the polymer size.
873
3/6
✓ Branch 1 taken 72 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 72 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 72 times.
72 if(coordinates.end() >= polymer_p->size())
874 coordinates.setEnd(polymer_p->size() - 1);
875
876 // First account for the residual chain masses.
877
878 // qDebug() << "calculateMasses: accounting for residual chain indices"
879 // << "[" << coordinates.start() << "--" << coordinates.end()
880 // << "]";
881
882
4/6
✓ Branch 1 taken 72 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 5616 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 5544 times.
✓ Branch 7 taken 72 times.
5616 for(int jter = coordinates.start(); jter <= coordinates.end(); ++jter)
883 {
884 // qDebug() << "Going to call at() with value" << jter;
885
886
1/2
✓ Branch 1 taken 5544 times.
✗ Branch 2 not taken.
5544 Monomer *monomer = const_cast<Monomer *>(polymer_p->at(jter));
887
888
3/4
✓ Branch 1 taken 5544 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 680 times.
✓ Branch 4 taken 4864 times.
5544 if(calc_options.isDeepCalculation())
889
2/4
✓ Branch 1 taken 680 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 680 times.
✗ Branch 5 not taken.
680 monomer->calculateMasses(calc_options.monomerEntities());
890
891
1/2
✓ Branch 1 taken 5544 times.
✗ Branch 2 not taken.
5544 monomer->accountMasses(mono, avg);
892 }
893 72 }
894
895 // Even if we are not in the residual chain loop, we have to account
896 // for the crossLinks, if so requires it. The crossLinks are a
897 // monomer chemical entity, but because it is unpractical to
898 // calculate their ponderable contribution in the loop above, we
899 // deal with them here. This is difficult stuff. In fact, the
900 // crossLinks, which in reality belong to at least two monomers
901 //(monomers can be engaged in more than a crossLink), are not
902 // stored as properties in the monomers(contrary to monomer
903 // modifications, for example). The crossLinks are stored in a list
904 // of such instances in the polymer(m_crossLinkList of CrossLink
905 // pointers). Now, the point is: if one of the monomers of a
906 // crossLink is selected but not the other partners, then what
907 // should be do about that crossLink accounting ?
908
909
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 72 times.
72 if(calc_options.monomerEntities() & MONOMER_CHEMENT_CROSS_LINK)
910 {
911 // qDebug() << "Cross-links are to be accounted for.";
912
913 // We have to take into account the crossLinks. Hmmm... hard
914 // task. The calculation is to be performed for the sequence
915 // stretch from localStart to localEnd. We can iterate in the
916 // crossLink list and for each crossLink check if it involves
917 // monomers that *all* are contained in the sequence stretch
918 //(or sequence stretches, that is a number of Coordinates
919 // items in the calc_options.coordinateList()) we're
920 // calculating the mass of. If at least one monomer of any
921 // crossLink is not contained in the [localStart--localEnd]
922 // sequence stretch, than increment a count variable and do
923 // not account the mass.
924
925 const CrossLinkList &crossLinkList = polymer_p->crossLinkList();
926
927 int crossLinkPartial = 0;
928
929 for(int jter = 0; jter < crossLinkList.size(); ++jter)
930 {
931 CrossLink *crossLink = crossLinkList.at(jter);
932
933 ret = crossLink->encompassedBy(calc_options.coordinateList());
934
935 if(ret == CROSS_LINK_ENCOMPASSED_FULL)
936 {
937 // qDebug() << "CrossLink at iter:" << jter
938 //<< "is fully encompassed: accounting its masses.";
939
940 // The crossLink is fully encompassed by our monomer
941 // stretch, so we should take it into account.
942
943 ret = crossLink->accountMasses(mono, avg);
944
945 Q_ASSERT(ret);
946 }
947 else if(ret == CROSS_LINK_ENCOMPASSED_PARTIAL)
948 {
949 // qDebug()
950 //<< "CrossLink at iter:" << jter
951 //<< "is only partially encompassed: not accounting its"
952 // "masses.";
953
954 ++crossLinkPartial;
955 }
956 else
957 {
958 // qDebug()
959 //<< "CrossLink at iter:" << jter
960 //<< "is not encompassed at all: not accounting its masses.";
961 }
962 }
963
964 emit(polymer_p->crossLinksPartiallyEncompassedSignal(crossLinkPartial));
965 }
966
967 // We now have to account for the left/right cappings. However,
968 // when there are multiple region selections(that is multiple
969 // Coordinate elements in the calc_options.coordinateList()) it is
970 // necessary to know if the user wants each of these Coordinates
971 // to be considered real oligomers(each one with its left/right
972 // caps) or as residual chains. Thus there are two cases:
973
974 // 1. Each Coordinates item should be considered an oligomer
975 //(SelectionType is SELECTION_TYPE_OLIGOMERS), thus for each item
976 // the left and right caps should be accounted for. This is
977 // typically the case when the user selects multiple regions to
978 // compute the mass of cross-linked oligomers.
979
980 // 2. Each Coordinates item should be considered a residual chain
981 //(SelectionType is SELECTION_TYPE_RESIDUAL_CHAINS), thus only
982 // one item should see its left and right caps accounted for. This
983 // is typically the case when the user selects multiple regions
984 // like it would select repeated sequence elements in a polymer
985 // sequence: all the regions selected are treated as a single
986 // oligomer.
987
988 // Holds the number of times the chemical entities are to be
989 // accounted for.
990 72 int times = 0;
991
992
2/2
✓ Branch 1 taken 40 times.
✓ Branch 2 taken 32 times.
72 if(calc_options.selectionType() == SELECTION_TYPE_RESIDUAL_CHAINS)
993 {
994 // qDebug() << __FILE__ << __LINE__
995 // << "SELECTION_TYPE_RESIDUAL_CHAINS";
996
997 40 times = 1;
998 }
999 else
1000 {
1001 // qDebug() << __FILE__ << __LINE__
1002 // << "SELECTION_TYPE_OLIGOMERS";
1003
1004 32 times = calc_options.coordinateList().size();
1005 }
1006
1007 // Account for the left and right cap masses, if so required.
1008
2/2
✓ Branch 1 taken 56 times.
✓ Branch 2 taken 16 times.
72 if(calc_options.capping() & CAP_LEFT)
1009 {
1010 56 ret =
1011 56 Polymer::accountCappingMasses(polymer_p, CAP_LEFT, mono, avg, times);
1012
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 56 times.
56 Q_ASSERT(ret);
1013 }
1014
1015
2/2
✓ Branch 1 taken 56 times.
✓ Branch 2 taken 16 times.
72 if(calc_options.capping() & CAP_RIGHT)
1016 {
1017 56 ret =
1018 56 Polymer::accountCappingMasses(polymer_p, CAP_RIGHT, mono, avg, times);
1019
1020
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 56 times.
56 Q_ASSERT(ret);
1021 }
1022
1023 // Account for the left and right modification masses, if so
1024 // required and the region(s) require(s) it: we have to make it
1025 // clear if the selection encompasses indices 0(left end) and/or
1026 // polymerSize-1(right end).
1027
1028 // Note that if we are force to take into account either or both
1029 // the left/right end modif, then even if the selected region does
1030 // not encompass the end(s), their modif(s) must be taken into
1031 // account.
1032
1033
2/2
✓ Branch 1 taken 28 times.
✓ Branch 2 taken 44 times.
72 if(calc_options.polymerEntities() & POLYMER_CHEMENT_LEFT_END_MODIF)
1034 {
1035
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
28 if(calc_options.polymerEntities() & POLYMER_CHEMENT_FORCE_LEFT_END_MODIF)
1036 {
1037 ret = Polymer::accountEndModifMasses(
1038 polymer_p, POLYMER_CHEMENT_LEFT_END_MODIF, mono, avg);
1039
1040 Q_ASSERT(ret);
1041 }
1042 else
1043 {
1044
1/2
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
28 if(calc_options.coordinateList().encompassIndex(0))
1045 {
1046 28 ret = Polymer::accountEndModifMasses(
1047 polymer_p, POLYMER_CHEMENT_LEFT_END_MODIF, mono, avg);
1048
1049
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
28 Q_ASSERT(ret);
1050 }
1051 }
1052 }
1053
1054
2/2
✓ Branch 1 taken 32 times.
✓ Branch 2 taken 40 times.
72 if(calc_options.polymerEntities() & POLYMER_CHEMENT_RIGHT_END_MODIF)
1055 {
1056
2/2
✓ Branch 1 taken 12 times.
✓ Branch 2 taken 20 times.
32 if(calc_options.polymerEntities() & POLYMER_CHEMENT_FORCE_RIGHT_END_MODIF)
1057 {
1058 12 ret = Polymer::accountEndModifMasses(
1059 polymer_p, POLYMER_CHEMENT_RIGHT_END_MODIF, mono, avg);
1060
1061
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 Q_ASSERT(ret);
1062 }
1063 else
1064 {
1065
2/2
✓ Branch 3 taken 12 times.
✓ Branch 4 taken 8 times.
20 if(calc_options.coordinateList().encompassIndex(polymer_p->size() -
1066 1))
1067 {
1068 12 ret = Polymer::accountEndModifMasses(
1069 polymer_p, POLYMER_CHEMENT_RIGHT_END_MODIF, mono, avg);
1070
1071
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 Q_ASSERT(ret);
1072 }
1073 }
1074 }
1075
1076 // qDebug() <<__FILE__ << __LINE__
1077 // << "CalculateMasses Mono:"
1078 // << polymer->mono()
1079 // << "Avg:"
1080 // << polymer->avg();
1081
1082 72 return true;
1083 }
1084
1085 /*!
1086 \brief Accounts for the mass of the end caps.
1087
1088 The polymer sequence is actually a chain of monomers (that is, residues). In
1089 order to compute the mass of the polymer in its finished state, it is necessary
1090 to add the masses of its end caps (typically, a proton and a hydroxyl group in
1091 protein chemistry, respectively capping the N-terminus and the C-terminus).
1092
1093 The mass of the the left end is added to the monoisotopic and average masses
1094 of this polymer if (\a how & CAP_LEFT). The mass of the the right end is added
1095 to the monoisotopic and average masses of this polymer if (\a how & CAP_RIGHT).
1096
1097 The masses of the caps are multiplied by \a times before accounting them to
1098 this polymer's masses.
1099
1100 Returns true.
1101 */
1102 bool
1103 Polymer::accountCappingMasses(int how, int times)
1104 {
1105 return accountCappingMasses(this, how, &m_mono, &m_avg, times);
1106 }
1107
1108
1109 /*!
1110 \brief Accounts for the \a{polymer}'s masses of its end caps to \a mono and
1111 \a avg.
1112
1113 The polymer sequence is actually a chain of monomers (that is, residues). In
1114 order to compute the mass of the polymer in its finished state, it is necessary
1115 to add the masses of its end caps (typically, a proton and a hydroxyl group in
1116 protein chemistry, respectively capping the N-terminus and the C-terminus).
1117
1118 The mass of the the left end is added to the \a mono and \a avg
1119 masses if (\a how & CAP_LEFT). The mass of the the right end is added
1120 to the \a mono and \a avg masses if (\a how & CAP_RIGHT).
1121
1122 The masses of the caps are multiplied by \a times before accounting them to
1123 \a mono and \a avg
1124
1125 \a mono and \a avg cannot be nullptr. \a polymer cannot be nullptr.
1126 Returns true.
1127 */
1128 bool
1129 112 Polymer::accountCappingMasses(
1130 Polymer *polymer, int how, double *mono, double *avg, int times)
1131 {
1132
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 112 times.
112 Q_ASSERT(polymer);
1133
2/4
✓ Branch 0 taken 112 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 112 times.
✗ Branch 3 not taken.
112 Q_ASSERT(mono && avg);
1134
1135
1/2
✓ Branch 1 taken 112 times.
✗ Branch 2 not taken.
112 PolChemDefCstSPtr polChemDef = polymer->getPolChemDefCstSPtr();
1136
1137
1/2
✓ Branch 2 taken 112 times.
✗ Branch 3 not taken.
112 IsotopicDataCstSPtr isotopic_data_csp = polChemDef->getIsotopicDataCstSPtr();
1138
1139
1/2
✓ Branch 2 taken 112 times.
✗ Branch 3 not taken.
112 Formula formula;
1140
1141
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 56 times.
112 if(how & CAP_LEFT)
1142 {
1143
2/4
✓ Branch 2 taken 56 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 56 times.
✗ Branch 6 not taken.
56 formula = polChemDef->leftCap();
1144 }
1145
1/2
✓ Branch 0 taken 56 times.
✗ Branch 1 not taken.
56 else if(how & CAP_RIGHT)
1146 {
1147
2/4
✓ Branch 2 taken 56 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 56 times.
✗ Branch 6 not taken.
56 formula = polChemDef->rightCap();
1148 }
1149 else if(how & CAP_NONE)
1150 return true;
1151 else
1152 Q_ASSERT(0);
1153
1154
2/4
✓ Branch 2 taken 112 times.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 112 times.
112 if(!formula.accountMasses(isotopic_data_csp, mono, avg, times))
1155 return false;
1156
1157 112 return true;
1158 112 }
1159
1160 /*!
1161 \brief Accounts for this polymer's end modifications masses as defined by \a
1162 how.
1163
1164 The left end's modification masses are accounted for in this polymer if (how &
1165 POLYMER_CHEMENT_LEFT_END_MODIF), and the right end's are if (how &
1166 POLYMER_CHEMENT_RIGHT_END_MODIF).
1167
1168 Returns true upon success, false otherwise.
1169 */
1170 bool
1171 Polymer::accountEndModifMasses(int how)
1172 {
1173 return accountEndModifMasses(this, how, &m_mono, &m_avg);
1174 }
1175
1176 /*!
1177 \brief Accounts for the \a{polymer}'s end modifications masses as defined by
1178 \a how.
1179
1180 The masses are accounted for into \a ponderable without resetting its masses.
1181 If (how & POLYMER_CHEMENT_LEFT_END_MODIF), the left end modification masses
1182 are accounted for and the right end's are if (how
1183 & POLYMER_CHEMENT_RIGHT_END_MODIF).
1184 */
1185 bool
1186 Polymer::accountEndModifMasses(Polymer *polymer,
1187 int how,
1188 Ponderable *ponderable)
1189 {
1190 Q_ASSERT(polymer);
1191 Q_ASSERT(ponderable);
1192
1193 return accountEndModifMasses(
1194 polymer, how, &ponderable->rmono(), &ponderable->ravg());
1195 }
1196
1197 /*!
1198 \brief Accounts for the \a{polymer}'s end modifications masses as defined by
1199 \a how.
1200
1201 The masses are accounted for into \a mono and \a avg without
1202 resetting these masses. If \c{(how & POLYMER_CHEMENT_LEFT_END_MODIF)}, the left
1203 end
1204 modification masses are accounted for and the right end's are if \c{(how&
1205 POLYMER_CHEMENT_RIGHT_END_MODIF)}.
1206
1207 Returns true upon success and false otherwise.
1208 */
1209 bool
1210 52 Polymer::accountEndModifMasses(Polymer *polymer,
1211 int how,
1212 double *mono,
1213 double *avg)
1214 {
1215
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 52 times.
52 Q_ASSERT(polymer);
1216
2/4
✓ Branch 0 taken 52 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 52 times.
✗ Branch 3 not taken.
52 Q_ASSERT(mono && avg);
1217
1218 // Make a safe copy of the polymer's left/right end modif and use it
1219 // for doing the calculation INTO the 'mono' and 'avg' variables.
1220
1221
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 24 times.
52 if(how & POLYMER_CHEMENT_LEFT_END_MODIF)
1222 {
1223
2/5
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 28 times.
✗ Branch 5 not taken.
28 Modif modif(polymer->leftEndModif());
1224
1225
2/4
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 28 times.
28 if(!modif.accountMasses(mono, avg))
1226 return false;
1227
1/2
✓ Branch 1 taken 28 times.
✗ Branch 2 not taken.
28 }
1228
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 28 times.
52 if(how & POLYMER_CHEMENT_RIGHT_END_MODIF)
1229 {
1230
2/5
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
24 Modif modif(polymer->rightEndModif());
1231
1232
2/4
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 24 times.
24 if(!modif.accountMasses(mono, avg))
1233 return false;
1234
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 }
1235
1236 52 return true;
1237 }
1238
1239 /*!
1240 \brief Performs the actual cross-linking as described in \a cross_link_p.
1241
1242 The chemical representation of the cross-link must have been performed in \a
1243 cross_link_p.
1244
1245 Returns true upon success or false if the CrossLink does not validate
1246 successfully.
1247 */
1248 bool
1249 Polymer::crossLink(CrossLink *cross_link_p)
1250 {
1251 Q_ASSERT(cross_link_p);
1252
1253 // This function must be called once all the members taking part
1254 // into the crossLink have been set.
1255
1256 if(!cross_link_p->validate())
1257 return false;
1258
1259 // OK, from the perspective of the chemical modification of the
1260 // monomers involved in the crosslink, everything is fine.
1261
1262 // Now is the moment that we actually perform the crossLink : this
1263 // is done simply by adding *this crossLink to the list of
1264 // crossLinks that belongs to the polymer.
1265
1266 mpa_crossLinkList->append(cross_link_p);
1267
1268 // If the crossLink dialog is open, inform it that it can refresh
1269 // the data.
1270 emit(crossLinkChangedSignal(this));
1271
1272 return true;
1273 }
1274
1275
1276 /*!
1277 \brief Undoes the cross-link \a cross_link_p.
1278
1279 Returns true upon success or false if the CrossLink does not validate
1280 successfully.
1281 */
1282 bool
1283 Polymer::uncrossLink(CrossLink *cross_link_p)
1284 {
1285 Q_ASSERT(cross_link_p);
1286
1287 if(!cross_link_p->validate())
1288 return false;
1289
1290 mpa_crossLinkList->removeAt(mpa_crossLinkList->indexOf(cross_link_p));
1291
1292 delete cross_link_p;
1293 cross_link_p = 0;
1294
1295 // If the crossLink dialog is open, inform it that it can refresh
1296 // the data.
1297 emit(crossLinkChangedSignal(this));
1298
1299 return true;
1300 }
1301
1302 /*!
1303 \brief Determines the element composition of this polymer.
1304
1305 The elemental composition is performed by looking into the core chemical
1306 entities of the polymer, like the monomers, the modifications, but also by
1307 accounting for the IonizeRule \a ionize_rule, and the CalcOptions \a
1308 calc_options.
1309
1310 The polymer sequence is accounted for by looking at the \a coordinate_list
1311 list of Coordinates.
1312
1313 Returns the elemental composition.
1314
1315 \sa Coordinates, CoordinateList, IonizeRule
1316 */
1317 QString
1318 Polymer::elementalComposition(const IonizeRule &ionize_rule,
1319 const CoordinateList &coordinate_list,
1320 const CalcOptions &calc_options)
1321 {
1322 Formula formula;
1323
1324 IsotopicDataCstSPtr isotopic_data_csp =
1325 mcsp_polChemDef->getIsotopicDataCstSPtr();
1326
1327 // Iterate in all the oligomers that are encompassed in the
1328 // selection.
1329
1330 for(int iter = 0; iter < coordinate_list.size(); ++iter)
1331 {
1332 // New coordinates instance we are iterating into.
1333 Coordinates *coordinates = coordinate_list.at(iter);
1334
1335 for(int jter = coordinates->start(); jter < coordinates->end() + 1;
1336 ++jter)
1337 {
1338 const Monomer *iterMonomer = at(jter);
1339 Q_ASSERT(iterMonomer);
1340
1341 // Set the formula of the new monomer in the same formula
1342 // instance.
1343 formula.setFormula(iterMonomer->formula());
1344
1345 // Incrementally account for the new formula in the same
1346 // atomcount list in the formula.
1347 if(formula.accountSymbolCounts(isotopic_data_csp, 1) == false)
1348 qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);
1349
1350 if(calc_options.monomerEntities() & MONOMER_CHEMENT_MODIF &&
1351 iterMonomer->isModified())
1352 {
1353 for(int jter = 0; jter < iterMonomer->modifList()->size(); ++jter)
1354 {
1355 Modif *modif = iterMonomer->modifList()->at(jter);
1356
1357 formula.setFormula(modif->formula());
1358
1359 // Incrementally account for the new formula in the same
1360 // atomcount list in the formula.
1361 if(formula.accountSymbolCounts(isotopic_data_csp, 1) == false)
1362 qFatal(
1363 "Fatal error at %s@%d. "
1364 "Aborting.",
1365 __FILE__,
1366 __LINE__);
1367 }
1368 }
1369 }
1370 // End of for (int jter = m_startIndex ; jter < m_endIndex + 1;
1371 // ++jter)
1372 }
1373
1374 // qDebug() << __FILE__ << __LINE__
1375 // << "Formula after accounting for all the residual chains:"
1376 // << formula.elementalComposition();
1377
1378 // We now have to account for the left/right cappings. However,
1379 // when there are multiple region selections(that is multiple
1380 // Coordinate elements in the calc_options.coordinateList()) it is
1381 // necessary to know if the user wants each of these Coordinates
1382 // to be considered real oligomers(each one with its left/right
1383 // caps) or as residual chains. Thus there are two cases:
1384
1385 // 1. Each Coordinates item should be considered an oligomer
1386 //(SelectionType is SELECTION_TYPE_OLIGOMERS), thus for each item
1387 // the left and right caps should be accounted for. This is
1388 // typically the case when the user selects multiple regions to
1389 // compute the mass of cross-linked oligomers.
1390
1391 // 2. Each Coordinates item should be considered a residual chain
1392 //(SelectionType is SELECTION_TYPE_RESIDUAL_CHAINS), thus only
1393 // one item should see its left and right caps accounted for. This
1394 // is typically the case when the user selects multiple regions
1395 // like it would select repeated sequence elements in a polymer
1396 // sequence: all the regions selected are treated as a single
1397 // oligomer.
1398
1399 // Holds the number of times the chemical entities are to be
1400 // accounted for.
1401 int times = 0;
1402
1403 if(calc_options.selectionType() == SELECTION_TYPE_RESIDUAL_CHAINS)
1404 {
1405 times = 1;
1406 // qDebug() << __FILE__ << __LINE__
1407 // << "SELECTION_TYPE_RESIDUAL_CHAINS ; times:" << times;
1408 }
1409 else
1410 {
1411 times = calc_options.coordinateList().size();
1412
1413 // qDebug() << __FILE__ << __LINE__
1414 // << "SELECTION_TYPE_OLIGOMERS ; times:" << times;
1415 }
1416
1417 // Account for the left and right cap masses, if so required.
1418
1419 if(calc_options.capping() & CAP_LEFT)
1420 {
1421 formula.setFormula(mcsp_polChemDef->leftCap());
1422
1423 // Incrementally account for the new formula in the same
1424 // atomcount list in the formula.
1425 if(formula.accountSymbolCounts(isotopic_data_csp, times) == false)
1426 qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);
1427
1428 // qDebug() << __FILE__ << __LINE__
1429 // << "Formula after accounting left cap:"
1430 // << formula.elementalComposition();
1431 }
1432
1433 if(calc_options.capping() & CAP_RIGHT)
1434 {
1435 formula.setFormula(mcsp_polChemDef->rightCap());
1436
1437 // Incrementally account for the new formula in the same
1438 // atomcount list in the formula.
1439 if(formula.accountSymbolCounts(isotopic_data_csp, times) == false)
1440 qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);
1441
1442 // qDebug() << __FILE__ << __LINE__
1443 // << "Formula after accounting right cap:"
1444 // << formula.elementalComposition();
1445 }
1446
1447 // Account for the left and right modification masses, if so
1448 // required and the region(s) require(s) it: we have to make it
1449 // clear if the selection encompasses indices 0(left end) and/or
1450 // polymerSize-1(right end).
1451
1452 if(calc_options.polymerEntities() & POLYMER_CHEMENT_LEFT_END_MODIF)
1453 {
1454 if(coordinate_list.encompassIndex(0))
1455 {
1456 Modif modif = leftEndModif();
1457
1458 formula.setFormula(modif.formula());
1459
1460 // qDebug() << __FILE__ << __LINE__
1461 // << "Accounting for left end modif:"
1462 // << modif.name();
1463
1464 // Incrementally account for the new formula in the same
1465 // atomcount list in the formula.
1466 if(formula.accountSymbolCounts(isotopic_data_csp, 1) == false)
1467 qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);
1468
1469 // qDebug() << __FILE__ << __LINE__
1470 // << "Formula after accounting left end modif:"
1471 // << formula.elementalComposition();
1472 }
1473 }
1474
1475 if(calc_options.polymerEntities() & POLYMER_CHEMENT_RIGHT_END_MODIF)
1476 {
1477 if(coordinate_list.encompassIndex(size() - 1))
1478 {
1479 Modif modif = rightEndModif();
1480
1481 formula.setFormula(modif.formula());
1482
1483 // qDebug() << __FILE__ << __LINE__
1484 // << "Accounting for right end modif:"
1485 // << modif.name();
1486
1487 // Incrementally account for the new formula in the same
1488 // atomcount list in the formula.
1489 if(formula.accountSymbolCounts(isotopic_data_csp, 1) == false)
1490 qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);
1491
1492 // qDebug() << __FILE__ << __LINE__
1493 // << "Formula after accounting right end modif:"
1494 // << formula.elementalComposition();
1495 }
1496 }
1497
1498 // At this point we should not forget if the user asks to take into
1499 // account the cross-links... However, BE CAREFUL that cross-links
1500 // can only be taken into account if all the partners of a given
1501 // cross-link are actually encompassed into the selection.
1502
1503 if(calc_options.monomerEntities() & MONOMER_CHEMENT_CROSS_LINK)
1504 {
1505
1506 for(int iter = 0; iter < crossLinkList().size(); ++iter)
1507 {
1508 CrossLink *crossLink = crossLinkList().at(iter);
1509
1510 if(crossLink->encompassedBy(coordinate_list) ==
1511 CROSS_LINK_ENCOMPASSED_FULL)
1512 {
1513 // The crossLink is fully encompassed by our monomer
1514 // stretch, so we should take it into account.
1515
1516 // qDebug() << __FILE__ << __LINE__
1517 // << "Accounting for fully encompassed cross-link:"
1518 // << crossLink->name();
1519
1520 if(!crossLink->formula().isEmpty())
1521 {
1522 formula.setFormula(crossLink->formula());
1523
1524 // qDebug() << __FILE__ << __LINE__
1525 // << "Cross-link formula:" <<
1526 // crossLink->formula();
1527
1528 // Incrementally account for the new formula in the same
1529 // atomcount list in the formula.
1530 if(formula.accountSymbolCounts(isotopic_data_csp, 1) == false)
1531 qFatal(
1532 "Fatal error at %s@%d. "
1533 "Aborting.",
1534 __FILE__,
1535 __LINE__);
1536 }
1537
1538 // And now each modification that belongs to the
1539 // crosslinker.
1540
1541 for(int jter = 0; jter < crossLink->modifList().size(); ++jter)
1542 {
1543 QString iterFormulaString =
1544 crossLink->modifList().at(jter)->formula();
1545
1546 // qDebug() << __FILE__ << __LINE__
1547 // << "Cross-link's modif formula:"
1548 // << iterFormulaString;
1549
1550 formula.setFormula(iterFormulaString);
1551
1552 // Incrementally account for the new formula in the same
1553 // atomcount list in the formula.
1554 if(formula.accountSymbolCounts(isotopic_data_csp, 1) == false)
1555 qFatal(
1556 "Fatal error at %s@%d. "
1557 "Aborting.",
1558 __FILE__,
1559 __LINE__);
1560 }
1561 }
1562 // End of
1563 // if (ret == CROSS_LINK_ENCOMPASSED_FULL)
1564 }
1565 // End of
1566 // for (int iter = 0; iter < crossLinkList->size(); ++iter)
1567 }
1568 // End of
1569 // if (calc_options.monomerEntities() & MONOMER_CHEMENT_CROSS_LINK)
1570
1571
1572 // The ionization rule. Do not forget to take into account the
1573 // level!
1574 formula.setFormula(ionize_rule.formula());
1575
1576 // Incrementally account for the new formula in the same
1577 // atomcount list in the formula.
1578 if(formula.accountSymbolCounts(isotopic_data_csp, ionize_rule.level()) ==
1579 false)
1580 qFatal("Fatal error at %s@%d. Aborting.", __FILE__, __LINE__);
1581
1582 // qDebug() << __FILE__ << __LINE__
1583 // << "Formula after accounting ionization: "
1584 // << formula.elementalComposition();
1585
1586 return formula.elementalComposition();
1587 }
1588
1589 /*!
1590 \brief Parses the XML \a element representing a sequence of monomer codes.
1591
1592 We are getting this: \c{<codes>MEFEEGTEEDWYGTEEDWYGTEEDWYGTEEDWYGT</codes>}
1593 about which we need to create \l{Monomer}s and add them to this polymer's
1594 \l{Sequence}.
1595
1596 Returns true if parsing and conversion of the text to a monomer list
1597 were successful, false otherwise.
1598
1599 \sa Sequence::makeMonomerList()
1600 */
1601 bool
1602 104 Polymer::renderXmlCodesElement(const QDomElement &element)
1603 {
1604 104 QString sequence;
1605
1606 // We are getting this:
1607 // <codes>MEFEEDWYGEEDWYGTEEDWYGTEEDWYGTEEDWYGTEEDWYGTEEDWYGT</codes>
1608 // We have to make monomers and add them to the list of monomers.
1609
1610
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 104 times.
104 if(element.tagName() != "codes")
1611 {
1612 qDebug() << "Expected codes element not found.";
1613 return false;
1614 }
1615
1616
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 m_monomerText = element.text();
1617
1618 // qDebug() << "Now rendering the <codes> element, that is the sequence:"
1619 //<< m_monomerText;
1620
1621
2/4
✓ Branch 2 taken 104 times.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 104 times.
104 if(makeMonomerList(mcsp_polChemDef, false) == -1)
1622 {
1623 qDebug() << "Failed to make the monomer list.";
1624 return false;
1625 }
1626 else
1627 104 return true;
1628 104 }
1629
1630 /*!
1631 \brief Extracts the name of the polymer chemistry definition from the \a
1632 file_path polymer sequence file.
1633
1634 Returns the polymer chemistry definition name.
1635 */
1636 QString
1637 Polymer::xmlPolymerFileGetPolChemDefName(const QString &file_path)
1638 {
1639 QDomDocument doc("polSeqData");
1640 QDomElement element;
1641 QDomElement child;
1642 QDomElement indentedChild;
1643
1644 QFile file(file_path);
1645
1646 /*
1647 <polseqdata>
1648 <polchemdef_name>protein</polchemdef_name>
1649 ...
1650 */
1651
1652 if(!file.open(QIODevice::ReadOnly))
1653 return QString("");
1654
1655 if(!doc.setContent(&file))
1656 {
1657 file.close();
1658 return QString("");
1659 }
1660
1661 file.close();
1662
1663 element = doc.documentElement();
1664
1665 if(element.tagName() != "polseqdata")
1666 {
1667 qDebug() << "Polymer sequence file is erroneous\n";
1668 return QString("");
1669 }
1670
1671 // <polchemdef_name>
1672 child = element.firstChildElement();
1673 if(child.tagName() != "polchemdef_name")
1674 return QString("");
1675
1676 return child.text();
1677 }
1678
1679
1680 /*!
1681 \brief Extracts from \a element, using the proper function (\a version), the
1682 polymer end modification.
1683
1684 The \a element tag is found in the polymer sequence XML file.
1685
1686 If the \a element tag name is \c{le_modif}, the modification name is set to
1687 the left end modification of this polymer sequence; if the tag name is
1688 \c{re_modif}, the right end of this polymer is modifified. The modifications
1689 are then rendered in place.
1690
1691 Returns true if no error was encountered, false otherwise.
1692
1693 \sa Modif::renderXmlMdfElement()
1694 */
1695 bool
1696 208 Polymer::renderXmlPolymerModifElement(const QDomElement &element, int version)
1697 {
1698
9/18
✓ Branch 1 taken 208 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 104 times.
✓ Branch 5 taken 104 times.
✓ Branch 7 taken 104 times.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 104 times.
✓ Branch 12 taken 104 times.
✓ Branch 13 taken 104 times.
✓ Branch 15 taken 208 times.
✗ Branch 16 not taken.
✗ Branch 18 not taken.
✓ Branch 19 taken 208 times.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
208 if(element.tagName() != "le_modif" && element.tagName() != "re_modif")
1699 return false;
1700
1701
1/2
✓ Branch 0 taken 208 times.
✗ Branch 1 not taken.
208 if(version == 1)
1702 {
1703 // no-op
1704 208 version = 1;
1705 }
1706
1707
1/2
✓ Branch 1 taken 208 times.
✗ Branch 2 not taken.
208 QDomElement child;
1708
1709
3/4
✓ Branch 1 taken 208 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 104 times.
✓ Branch 6 taken 104 times.
208 if(element.tagName() == "le_modif")
1710 {
1711 // Go down to the <mdf> element.
1712
2/4
✓ Branch 3 taken 104 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 104 times.
✗ Branch 7 not taken.
104 child = element.firstChildElement();
1713
1714
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 104 times.
✗ Branch 4 not taken.
104 if(child.isNull())
1715 104 return true;
1716
1717 if(!m_leftEndModif.renderXmlMdfElement(child, version))
1718 return false;
1719 else
1720 return true;
1721 }
1722
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 104 times.
✗ Branch 6 not taken.
104 else if(element.tagName() == "re_modif")
1723 {
1724 // Go down to the <mdf> element.
1725
2/4
✓ Branch 3 taken 104 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 104 times.
✗ Branch 7 not taken.
104 child = element.firstChildElement();
1726
1727
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 104 times.
✗ Branch 4 not taken.
104 if(child.isNull())
1728 104 return true;
1729
1730 if(!m_rightEndModif.renderXmlMdfElement(child, version))
1731 return false;
1732 else
1733 return true;
1734 }
1735
1736 return false;
1737 208 }
1738
1739
1740 /*!
1741 \brief Extracts from \a element, using the proper function (\a version),
1742 all the \l{CrossLink}s contained in it.
1743
1744 Each cross-link is rendered apart and applied to this polymer.
1745
1746 Returns true if no error was encountered, false otherwise.
1747
1748 \sa crossLink()
1749 */
1750 bool
1751 104 Polymer::renderXmlCrossLinksElement(const QDomElement &element, int version)
1752 {
1753
1/2
✓ Branch 0 taken 104 times.
✗ Branch 1 not taken.
104 if(version == 1)
1754 {
1755 // no-op
1756 104 version = 1;
1757 }
1758
1759
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 QDomElement child;
1760
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 QDomElement indentedChild;
1761
1762 // element is <crosslinks>
1763
1764 // <crosslinks>
1765 // <crosslink>
1766 // <name>DisulfideBond</name>
1767 // <targets>;2;6;</targets>
1768 // </crosslink>
1769 // </crosslinks>
1770
1771
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 104 times.
104 if(element.tagName() != "crosslinks")
1772 return false;
1773
1774
2/4
✓ Branch 3 taken 104 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 104 times.
✗ Branch 7 not taken.
104 child = element.firstChildElement();
1775
1776 // There can be any number of <crosslink> elements.
1777
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 104 times.
104 while(!child.isNull())
1778 {
1779 if(child.tagName() != "crosslink")
1780 return false;
1781
1782 indentedChild = child.firstChildElement();
1783
1784 if(indentedChild.tagName() != "name")
1785 return false;
1786
1787 // We actually do have a <crosslink> element, so we can allocate
1788 // one now.
1789
1790 // qDebug() << "Rendering a polymer sequence CrossLink by name:"
1791 //<< indentedChild.text();
1792
1793 CrossLink *aCrossLink = new CrossLink(
1794 mcsp_polChemDef, this, indentedChild.text(), "NOT_SET", "NOT_SET");
1795
1796 // And now find in the polymer chemistry definition the right
1797 // crossLinker and copy it into our newly allocated one.
1798
1799 if(!mcsp_polChemDef->referenceCrossLinkerByName(
1800 indentedChild.text(), static_cast<CrossLinker *>(aCrossLink)))
1801 {
1802 delete aCrossLink;
1803 return false;
1804 }
1805
1806 // At this point the crossLinker superclass of crossLink is
1807 // updated with the ref one.
1808
1809 indentedChild = indentedChild.nextSiblingElement();
1810
1811 if(indentedChild.tagName() != "targets")
1812 {
1813 delete aCrossLink;
1814 return false;
1815 }
1816
1817 if(aCrossLink->populateMonomerList(indentedChild.text()) == -1)
1818 {
1819 delete aCrossLink;
1820 return false;
1821 }
1822
1823 indentedChild = indentedChild.nextSiblingElement();
1824
1825 if(!indentedChild.isNull())
1826 {
1827 if(indentedChild.tagName() != "comment")
1828 {
1829 delete aCrossLink;
1830 return false;
1831 }
1832 }
1833
1834 // At this point the crossLink element is finished rendering,
1835 // all we have to do is perform the crossLink proper.
1836
1837 if(!crossLink(aCrossLink))
1838 {
1839 delete aCrossLink;
1840 return false;
1841 }
1842
1843 child = child.nextSiblingElement();
1844 }
1845
1846 104 return true;
1847 104 }
1848
1849 /*!
1850 \brief Parses the \a file_path polymer sequence file.
1851
1852 During parsing, the encountered data are set to this polymer. This parsing is
1853 called "rendering".
1854
1855 Returns true if parsing succeeded, false otherwise.
1856 */
1857 bool
1858 104 Polymer::renderXmlPolymerFile(QString file_path)
1859 {
1860 104 QString localFilePath;
1861
1862
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 104 times.
✗ Branch 5 not taken.
104 QDomDocument doc("polSeqData");
1863
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 QDomElement element;
1864
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 QDomElement child;
1865
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 QDomElement indentedChild;
1866
1867 104 Monomer *monomer = nullptr;
1868
1869 /*
1870 <polseqdata>
1871 <polchemdef_name>protein</polchemdef_name>
1872 <name>Sample</name>
1873 <code>SP2003</code>
1874 <author>rusconi</author>
1875 <datetime>1967-09-224:09:23</datetime>
1876 */
1877
1878
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 if(file_path.isEmpty())
1879 104 localFilePath = m_filePath;
1880 else
1881 localFilePath = file_path;
1882
1883
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 QFile file(localFilePath);
1884
1885
2/4
✓ Branch 2 taken 104 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 104 times.
104 if(!file.open(QIODevice::ReadOnly))
1886 {
1887 qDebug() << "Could not open file.";
1888 return false;
1889 }
1890
1891
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 104 times.
104 if(!doc.setContent(&file))
1892 {
1893 qDebug() << "Failed to set file contents to doc object.";
1894 file.close();
1895 return false;
1896 }
1897
1898
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 file.close();
1899
1900
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 104 times.
✗ Branch 5 not taken.
104 element = doc.documentElement();
1901
1902
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 104 times.
104 if(element.tagName() != "polseqdata")
1903 {
1904 qDebug() << "Polymer sequence file is erroneous\n";
1905 return false;
1906 }
1907
1908 ///////////////////////////////////////////////
1909 // Check the version of the document.
1910
1911 104 QString text;
1912
1913
3/6
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 104 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 104 times.
104 if(!element.hasAttribute("version"))
1914 text = "1";
1915 else
1916
2/4
✓ Branch 2 taken 104 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 104 times.
✗ Branch 6 not taken.
104 text = element.attribute("version");
1917
1918 104 bool ok = false;
1919
1920
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 int version = text.toInt(&ok, 10);
1921
1922
2/4
✓ Branch 0 taken 104 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 104 times.
104 if(version < 1 || !ok)
1923 {
1924 qDebug() << "Polymer sequence file has bad version number: " << version;
1925
1926 return false;
1927 }
1928
1929 // <polchemdef_name>
1930
2/4
✓ Branch 3 taken 104 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 104 times.
✗ Branch 7 not taken.
104 child = element.firstChildElement();
1931
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 104 times.
104 if(child.tagName() != "polchemdef_name")
1932 return false;
1933 // mcsp_polChemDef->setName(child.text());
1934
1935 // <name>
1936
2/4
✓ Branch 3 taken 104 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 104 times.
✗ Branch 7 not taken.
104 child = child.nextSiblingElement();
1937
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 104 times.
104 if(child.tagName() != "name")
1938 return false;
1939
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 m_name = child.text();
1940
1941 // <code>
1942
2/4
✓ Branch 3 taken 104 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 104 times.
✗ Branch 7 not taken.
104 child = child.nextSiblingElement();
1943
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 104 times.
104 if(child.tagName() != "code")
1944 return false;
1945
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 m_code = child.text();
1946
1947 // <author>
1948
2/4
✓ Branch 3 taken 104 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 104 times.
✗ Branch 7 not taken.
104 child = child.nextSiblingElement();
1949
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 104 times.
104 if(child.tagName() != "author")
1950 return false;
1951
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 m_author = child.text();
1952
1953 // <datetime>
1954
2/4
✓ Branch 3 taken 104 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 104 times.
✗ Branch 7 not taken.
104 child = child.nextSiblingElement();
1955
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 104 times.
104 if(child.tagName() != "datetime")
1956 return false;
1957
4/8
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 104 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 104 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 104 times.
✗ Branch 11 not taken.
104 m_dateTime = QDateTime::fromString(child.text(), "yyyy-MM-dd:mm:ss");
1958
1959 // <polseq>
1960
2/4
✓ Branch 3 taken 104 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 104 times.
✗ Branch 7 not taken.
104 child = child.nextSiblingElement();
1961
1962
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 104 times.
104 if(child.tagName() != "polseq")
1963 return false;
1964
1965 /*
1966 <polseq>
1967 <codes>MEFEEDF</codes>
1968 <monomer>
1969 <code>S</code>
1970 <prop>
1971 <name>MODIF</name>
1972 <data>Phosphorylation</data>
1973 </prop>
1974 </monomer>
1975 <codes>GRKDKNFLKMGRK</codes>
1976 </polseq>
1977 <le_modif>
1978 <mdf>
1979 <name>Acetylation</name>
1980 <formula>-H+C2H3O</formula>
1981 <targets>*</targets>
1982 <maxcount>1</maxcount>
1983 </mdf>
1984 </le_modif>
1985 <re_modif>
1986 <mdf>
1987 <name>Phosphorylation</name>
1988 <formula>-H+H2PO3</formula>
1989 <targets>*</targets>
1990 <maxcount>1</maxcount>
1991 </mdf>
1992 </re_modif>
1993 */
1994
1995 // There can be any number of <codes> and <monomer> elements, in
1996 // whatever order.
1997
1998
2/4
✓ Branch 3 taken 104 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 104 times.
✗ Branch 7 not taken.
104 indentedChild = child.firstChildElement();
1999
2000
3/4
✓ Branch 1 taken 208 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 104 times.
✓ Branch 4 taken 104 times.
208 while(!indentedChild.isNull())
2001 {
2002
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 104 times.
✗ Branch 6 not taken.
104 if(indentedChild.tagName() == "codes")
2003 {
2004
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 104 times.
104 if(!renderXmlCodesElement(indentedChild))
2005 {
2006 qDebug() << "Failed to render the XML codes element.";
2007 return false;
2008 }
2009 }
2010 else if(indentedChild.tagName() == "monomer")
2011 {
2012 monomer = new Monomer(mcsp_polChemDef, "NOT_SET");
2013
2014 if(!monomer->renderXmlMonomerElement(indentedChild, version))
2015 {
2016 qDebug() << "Failed to render the XML monomer element.";
2017 delete monomer;
2018
2019 return false;
2020 }
2021 m_monomerList.append(monomer);
2022 }
2023 else
2024 return false;
2025
2026
2/4
✓ Branch 3 taken 104 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 104 times.
✗ Branch 7 not taken.
104 indentedChild = indentedChild.nextSiblingElement();
2027 }
2028
2029 // Go on to the next element(has to be <le_modif>.
2030
2031 104 QString error;
2032
2033
2/4
✓ Branch 3 taken 104 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 104 times.
✗ Branch 7 not taken.
104 child = child.nextSiblingElement();
2034
2035
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 104 times.
104 if(child.tagName() != "le_modif")
2036 error = "Expected le_modif element not found.";
2037
2038
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 104 times.
104 if(!renderXmlPolymerModifElement(child, version))
2039 error = "Failed to render the left end modif element.";
2040
2041 // Go on to the next element(has to be <re_modif>.
2042
2043
2/4
✓ Branch 3 taken 104 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 104 times.
✗ Branch 7 not taken.
104 child = child.nextSiblingElement();
2044
2045
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 104 times.
104 if(child.tagName() != "re_modif")
2046 error = "Expected re_modif element not found.";
2047
2048
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 104 times.
104 if(!renderXmlPolymerModifElement(child, version))
2049 error = "Failed to render the right end modif element.";
2050
2051 // Go on to the next element(has to be <crosslinks>.
2052
2053
2/4
✓ Branch 3 taken 104 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 104 times.
✗ Branch 7 not taken.
104 child = child.nextSiblingElement();
2054
2055
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 104 times.
104 if(child.tagName() != "crosslinks")
2056 error = "Expected crosslinks element not found.";
2057
2058
2/4
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 104 times.
104 if(!renderXmlCrossLinksElement(child, version))
2059 error = "Failed to render the crosslinks element.";
2060
2061
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 104 times.
104 if(!error.isEmpty())
2062 {
2063 qDebug() << "Rendering of XML file failed with error:" << error;
2064 return false;
2065 }
2066
2067
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 setFilePath(localFilePath);
2068
2069 // qDebug() << "Finished rendering the XML file, returning true.";
2070
2071 104 return true;
2072 104 }
2073
2074
2075 /*!
2076 \brief Creates the XML DTD for a polymer sequence file.
2077
2078 Returns The DTD in a dynamically allocated string.
2079 */
2080 QString *
2081 Polymer::formatXmlDtd()
2082 {
2083 QString *string = new QString(
2084 "<?xml version=\"1.0\"?>\n"
2085 "<!-- DTD for polymer sequences, used by the\n"
2086 "'massXpert' mass spectrometry application.\n"
2087 "Copyright 2006,2007,2008 Filippo Rusconi - Licensed under "
2088 "the GNU GPL -->\n"
2089 "<!DOCTYPE polseqdata [\n"
2090 "<!ELEMENT polseqdata "
2091 "(polchemdef_name,name,code,author,datetime,polseq,le_modif,"
2092 "re_modif,crosslinks,prop*)>\n"
2093 "<!ATTLIST polseqdata version NMTOKEN #REQUIRED>\n"
2094 "<!ELEMENT polchemdef_name (#PCDATA)>\n"
2095 "<!ELEMENT mdf (name,formula,targets)>\n"
2096 "<!ELEMENT name (#PCDATA)>\n"
2097 "<!ELEMENT formula (#PCDATA)>\n"
2098 "<!ELEMENT targets (#PCDATA)>\n"
2099 "<!ELEMENT code (#PCDATA)>\n"
2100 "<!ELEMENT author (#PCDATA)>\n"
2101 "<!ELEMENT datetime (#PCDATA)>\n"
2102 "<!ELEMENT polseq (codes|monomer)*>\n"
2103 "<!ELEMENT le_modif (mdf?)>\n"
2104 "<!ELEMENT re_modif (mdf?)>\n"
2105 "<!ELEMENT codes (#PCDATA)>\n"
2106 "<!ELEMENT crosslink (name,targets)>\n"
2107 "<!ELEMENT crosslinks (crosslink*)>\n"
2108 "<!ELEMENT monomer (code, mdf*)>\n"
2109 "<!ELEMENT prop (name, data+)>\n"
2110 "<!ATTLIST data type (str | int | dbl) \"str\">\n"
2111 "<!ELEMENT data (#PCDATA)>\n"
2112 "]>\n");
2113
2114 return string;
2115 }
2116
2117
2118 /*!
2119 \brief Writes this polymer to file.
2120
2121 Returns true if successful, false otherwise.
2122 */
2123 bool
2124 Polymer::writeXmlFile()
2125 {
2126 QString *string = 0;
2127 QString indent(" ");
2128
2129
2130 // We are asked to send an xml description of the polymer sequence.
2131
2132 QFile file(m_filePath);
2133
2134 if(!file.open(QIODevice::WriteOnly))
2135 {
2136 qDebug() << "Failed to open file" << m_filePath << "for writing.";
2137
2138 return false;
2139 }
2140
2141 QTextStream stream(&file);
2142 stream.setEncoding(QStringConverter::Utf8);
2143
2144 // The DTD
2145 string = formatXmlDtd();
2146 stream << *string;
2147 delete string;
2148
2149 // Open the <polseqdata> element.
2150 //"<!ELEMENT polseqdata(polchemdef_name,name,code,
2151 // author,datetime,polseq,prop*)>\n"
2152
2153 stream << QString("<polseqdata version=\"%1\">\n")
2154 .arg(POL_SEQ_FILE_FORMAT_VERSION);
2155
2156 Q_ASSERT(!mcsp_polChemDef->name().isEmpty());
2157 stream << QString("%1<polchemdef_name>%2</polchemdef_name>\n")
2158 .arg(indent)
2159 .arg(mcsp_polChemDef->name());
2160
2161 stream << QString("%1<name>%2</name>\n")
2162 .arg(indent)
2163 .arg(m_name.isEmpty() ? "Not Set" : m_name);
2164
2165 stream << QString("%1<code>%2</code>\n")
2166 .arg(indent)
2167 .arg(m_code.isEmpty() ? "Not Set" : m_code);
2168
2169 Q_ASSERT(!m_author.isEmpty());
2170 stream << QString("%1<author>%2</author>\n").arg(indent).arg(m_author);
2171
2172 m_dateTime = QDateTime::currentDateTime();
2173 stream << QString("%1<datetime>%2</datetime>\n").arg(indent).arg(dateTime());
2174
2175 string = formatXmlPolSeqElement(POL_SEQ_FILE_FORMAT_VERSION);
2176
2177 if(string == 0)
2178 {
2179 qDebug() << "Failed to produce the <polseq> element string.";
2180
2181 return false;
2182 }
2183
2184 stream << *string;
2185 delete string;
2186
2187
2188 // Now deal with the polymer modifications. These are represented as
2189 // <mdf> elements.
2190
2191 // Left end modif
2192 Q_ASSERT(!m_leftEndModif.name().isEmpty());
2193
2194 stream << QString("%1<le_modif>\n").arg(indent);
2195
2196 if(m_leftEndModif.name() != "NOT_SET")
2197 {
2198 string = m_leftEndModif.formatXmlMdfElement(POL_SEQ_FILE_FORMAT_VERSION);
2199
2200 stream << *string;
2201 delete string;
2202 }
2203
2204 stream << QString("%1</le_modif>\n").arg(indent);
2205
2206
2207 // Right end modif
2208 Q_ASSERT(!m_rightEndModif.name().isEmpty());
2209
2210 stream << QString("%1<re_modif>\n").arg(indent);
2211
2212 if(m_rightEndModif.name() != "NOT_SET")
2213 {
2214 string = m_rightEndModif.formatXmlMdfElement(POL_SEQ_FILE_FORMAT_VERSION);
2215
2216 stream << *string;
2217 delete string;
2218 }
2219
2220 stream << QString("%1</re_modif>\n").arg(indent);
2221
2222 string = 0;
2223 string = formatXmlCrossLinksElement(POL_SEQ_FILE_FORMAT_VERSION);
2224
2225 if(!string)
2226 {
2227 qDebug() << "Failed to produce the <crosslinks> element string.";
2228
2229 return false;
2230 }
2231
2232 stream << *string;
2233 delete string;
2234
2235 // Note that at some point, there might be any number of polymer
2236 // <prop> elements at this place...
2237
2238
2239 // Finally close the polseqdata.
2240
2241 stream << QString("</polseqdata>\n");
2242
2243 return true;
2244 }
2245
2246
2247 /*!
2248 \brief Formats this polymer's sequence as a string suitable to use as an XML
2249 element.
2250
2251 This function generates a string holding all the elements pertaining to this
2252 polymer' \e sequence (the list of
2253 monomers, potentially modified, \e not all the other data). The typical
2254 element that is generated in this function looks like this:
2255
2256 \code
2257 <polseq>
2258 <codes>MEFEEDWYGEEDWYGTEEDWYGTEEDWYGTEEDWYGTEEDWYGTEEDWYGT</codes>
2259 <monomer>
2260 <code>S</code>
2261 <mdf>
2262 <name>Phosphorylation</name>
2263 <formula></formula>
2264 <targets>*</targets>
2265 </mdf>
2266 </monomer>
2267 </polseq>
2268 \endcode
2269
2270 \a offset times the \a indent string must be used as a lead in the
2271 formatting of elements.
2272
2273 Returns a dynamically allocated string that needs to be freed after
2274 use.
2275
2276 \sa writeXmlFile()
2277 */
2278 QString *
2279 Polymer::formatXmlPolSeqElement(int offset, const QString &indent)
2280 {
2281 int newOffset;
2282 int iter = 0;
2283
2284 QString lead("");
2285 QString codesString("");
2286 QString *monomerString = 0;
2287 QString *string = new QString();
2288
2289 const Monomer *monomer = 0;
2290
2291
2292 // Prepare the lead.
2293 newOffset = offset;
2294 while(iter < newOffset)
2295 {
2296 lead += indent;
2297 ++iter;
2298 }
2299
2300
2301 // At this point, we have to iterate in the sequence. If the
2302 // monomers are not modified, then put their codes in a raw, like
2303 // "ETGSH", in a <codes> element. As soon as a monomer is modified,
2304 // whatever the modification --that is, it has a prop object in its
2305 // --m_propList, it and its contents should be listed in a detailed
2306 // <monomer> element.
2307
2308 *string += QString("%1<polseq>\n").arg(lead);
2309
2310 // Prepare the lead.
2311 ++newOffset;
2312 lead.clear();
2313 iter = 0;
2314 while(iter < newOffset)
2315 {
2316 lead += indent;
2317 ++iter;
2318 }
2319
2320 // Iterate in the polymer sequence.
2321
2322 for(int iter = 0; iter < m_monomerList.size(); ++iter)
2323 {
2324 monomer = m_monomerList.at(iter);
2325 Q_ASSERT(monomer);
2326
2327 // Check if the monomer is modified. If not, we just append its
2328 // code to the elongating codesString, else we use a more
2329 // thorough monomer element-parsing function.
2330
2331 if(!monomer->isModified())
2332 {
2333 codesString += monomer->code();
2334 continue;
2335 }
2336 else
2337 {
2338 // If something was baking in codesString, then we have to
2339 // create the element right now, fill the data in it and
2340 // close it before opening one <monomer> element below.
2341
2342 if(!codesString.isEmpty())
2343 {
2344 *string += QString("%1<codes>%2%3")
2345 .arg(lead)
2346 .arg(codesString)
2347 .arg("</codes>\n");
2348
2349 codesString.clear();
2350 }
2351
2352 monomerString = monomer->formatXmlMonomerElement(newOffset);
2353
2354 if(!monomerString)
2355 {
2356 delete string;
2357 return 0;
2358 }
2359 else
2360 {
2361 *string += *monomerString;
2362 }
2363 }
2364 }
2365
2366 // If something was baking in codesString, then we have to
2367 // create the element right now, fill the data in it and
2368 // close it before opening one <monomer> element below.
2369
2370 if(!codesString.isEmpty())
2371 {
2372 *string +=
2373 QString("%1<codes>%2%3").arg(lead).arg(codesString).arg("</codes>\n");
2374
2375 codesString.clear();
2376 }
2377
2378
2379 // Prepare the lead for the closing element.
2380 --newOffset;
2381 lead.clear();
2382 iter = 0;
2383 while(iter < newOffset)
2384 {
2385 lead += indent;
2386 ++iter;
2387 }
2388
2389 *string += QString("%1</polseq>\n").arg(lead);
2390
2391 return string;
2392 }
2393
2394 /*!
2395 \brief Formats an XML element suitable to describe the \c <crosslinks>
2396 element.
2397
2398 Iterates in the cross-link list of this polymer and crafts XML elements
2399 describing them.
2400
2401 The XML element looks like this:
2402
2403 \code
2404 <crosslinks>
2405 <crosslink>
2406 <name>DisulfideBond</name>
2407 <targets>;2;6;</targets>
2408 </crosslink>
2409 </crosslinks>
2410 \endcode
2411
2412 \a offset times the \a indent string must be used as a lead in the
2413 formatting of elements.
2414
2415 Returns the XML element as a dynamically allocated string.
2416 */
2417 QString *
2418 Polymer::formatXmlCrossLinksElement(int offset, const QString &indent)
2419 {
2420 int newOffset;
2421 int iter = 0;
2422
2423 QString lead("");
2424 QString *string = new QString();
2425
2426 // Prepare the lead.
2427 newOffset = offset;
2428 while(iter < newOffset)
2429 {
2430 lead += indent;
2431 ++iter;
2432 }
2433
2434 // This is the kind of string we have to generate.
2435
2436 // <crosslinks>
2437 // <crosslink>
2438 // <name>DisulfideBond</name>
2439 // <targets>;2;6;</targets>
2440 // </crosslink>
2441 // </crosslinks>
2442
2443
2444 // At this point, we have to iterate in the list of crosslinks and
2445 // for each crosslink determine what's the crosslinker and which
2446 // monomer are actually crosslinked together.
2447
2448 *string += QString("%1<crosslinks>\n").arg(lead);
2449
2450 // Prepare the lead.
2451 ++newOffset;
2452 lead.clear();
2453 iter = 0;
2454 while(iter < newOffset)
2455 {
2456 lead += indent;
2457 ++iter;
2458 }
2459
2460 for(int jter = 0; jter < mpa_crossLinkList->size(); ++jter)
2461 {
2462 CrossLink *crossLink = mpa_crossLinkList->at(jter);
2463 Q_ASSERT(crossLink);
2464
2465 *string += QString("%1<crosslink>\n").arg(lead);
2466
2467 // Prepare the lead.
2468 ++newOffset;
2469 lead.clear();
2470 iter = 0;
2471 while(iter < newOffset)
2472 {
2473 lead += indent;
2474 ++iter;
2475 }
2476
2477 *string +=
2478 QString("%1<name>%2</name>\n").arg(lead).arg(crossLink->name());
2479
2480 // Create the string with all the monomer indices(which are the
2481 // targets of the crossLink).
2482
2483 *string += QString("%1<targets>%2</targets>\n")
2484 .arg(lead)
2485 .arg(crossLink->monomerIndexText());
2486
2487 *string += QString("%1<comment>%2</comment>\n")
2488 .arg(lead)
2489 .arg(crossLink->comment());
2490
2491 // Prepare the lead.
2492 --newOffset;
2493 lead.clear();
2494 iter = 0;
2495 while(iter < newOffset)
2496 {
2497 lead += indent;
2498 ++iter;
2499 }
2500
2501 *string += QString("%1</crosslink>\n").arg(lead);
2502 }
2503
2504 // Prepare the lead.
2505 --newOffset;
2506 lead.clear();
2507 iter = 0;
2508 while(iter < newOffset)
2509 {
2510 lead += indent;
2511 ++iter;
2512 }
2513
2514 *string += QString("%1</crosslinks>\n").arg(lead);
2515
2516 return string;
2517 }
2518
2519 /*!
2520 \brief Validates the Sequence of this polymer.
2521
2522 Returns true if validation was successful, false, otherwise.
2523
2524 \sa Sequence::validate()
2525 */
2526 bool
2527 Polymer::validate()
2528 {
2529 if(!Sequence::validate(mcsp_polChemDef))
2530 return false;
2531 if(!Ionizable::validate())
2532 return false;
2533
2534 return true;
2535 }
2536
2537
2538 /*! Outputs a string describing the polymer using qDebug().
2539
2540 Used for debugging purposes.
2541 */
2542 void
2543 Polymer::debugPutStdErr()
2544 {
2545 qDebug() << m_name << m_code << mcsp_polChemDef->name() << m_author
2546 << m_filePath << m_leftEndModif.name() << m_rightEndModif.name();
2547
2548
2549 for(int iter = 0; iter < m_monomerList.size(); ++iter)
2550 {
2551 qDebug() << m_monomerList.at(iter)->code();
2552 }
2553 }
2554
2555 } // namespace libXpertMass
2556 } // namespace MsXpS
2557