GCC Code Coverage Report


source/XpertMassCore/src/
File: source/XpertMassCore/src/Isotope.cpp
Date: 2025-11-19 15:26:11
Lines:
253/298
84.9%
Functions:
23/27
85.2%
Branches:
243/454
53.5%

Line Branch Exec Source
1 /* BEGIN software license
2 *
3 * MsXpertSuite - mass spectrometry software suite
4 * -----------------------------------------------
5 * Copyright (C) 2009--2024 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 /////////////////////// Stdlib includes
35 #include <vector>
36
37 /////////////////////// Qt includes
38 #include <QString>
39 #include <QStringList>
40 #include <QDebug>
41 #include <QRegularExpression>
42
43
44 /////////////////////// Local includes
45 #include "MsXpS/libXpertMassCore/globals.hpp"
46 #include "MsXpS/libXpertMassCore/Isotope.hpp"
47 #include "MsXpS/libXpertMassCore/Utils.hpp"
48
49
50 int isotopeMetaTypeId = qRegisterMetaType<MsXpS::libXpertMassCore::Isotope>(
51 "MsXpS::libXpertMassCore::Isotope");
52
53 int isotopePtrMetaTypeId =
54 qRegisterMetaType<MsXpS::libXpertMassCore::Isotope *>(
55 "MsXpS::libXpertMassCore::IsotopePtr");
56
57 int isotopeSPtrMetaTypeId =
58 qRegisterMetaType<QSharedPointer<MsXpS::libXpertMassCore::Isotope>>(
59 "QSharedPointer<MsXpS::libXpertMassCore::Isotope>");
60
61 int isotopeCstSPtrMetaTypeId =
62 qRegisterMetaType<QSharedPointer<MsXpS::libXpertMassCore::Isotope>>(
63 "QSharedPointer<const MsXpS::libXpertMassCore::Isotope>");
64
65 namespace MsXpS
66 {
67 namespace libXpertMassCore
68 {
69
70 // #include <libisospec++/isoSpec++.h>
71 //
72
73 // extern const char* elem_table_element[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
74
75 // extern const char* elem_table_symbol[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
76
77 // extern const double elem_table_mass[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
78
79 // extern const double
80 // elem_table_probability[ISOSPEC_NUMBER_OF_ISOTOPIC_ENTRIES];
81
82
83 // This is the order of the columns in the gui TableView
84 // ELEMENT,
85 // SYMBOL,
86 // MASS,
87 // PROBABILITY
88
89 /*!
90 \enum MsXpS::libXpertMassCore::IsotopeFields
91
92 \brief This enum type documents the various member data in \l{Isotope}.
93
94 The values assigned to the various enum members are used to specify the
95 columsn in the GUI table view. They are also used to access substrings in
96 the proper order in the overloaded initialize() function.
97
98 \value NAME Indicates Isotope::m_name.
99 \value SYMBOL Indicates the Isotope::m_symbol.
100 \value MASS Indicates the Isotope::m_mass.
101 \value PROBABILITY Indicates the Isotope::m_probability.
102 \omitvalue LAST
103 */
104
105 /*!
106 \class MsXpS::libXpertMassCore::Isotope
107 \inmodule libXpertMassCore
108 \ingroup PolChemDefBuildingdBlocks
109 \inheaderfile Isotope.hpp
110
111 \brief The Isotope class models an isotope.
112
113 The Isotope class models an Isotope by featuring all the methods and
114 member data required to fully characterize an isotope. The member data in this
115 class have been inspired by the element tables from the IsoSpec library.
116 Please, see \l{https://github.com/MatteoLacki/IsoSpec/}.
117 */
118
119 /*!
120 \variable MsXpS::libXpertMassCore::Isotope::m_name
121
122 \brief The element name, like "carbon" or "nitrogen" (lowercase).
123 */
124
125 /*!
126 \variable MsXpS::libXpertMassCore::Isotope::m_symbol
127 \brief The element symbol, like "C" or "N".
128 */
129
130 /*!
131 \variable MsXpS::libXpertMassCore::Isotope::m_mass
132 \brief The mass of this isotope.
133
134 Cannot be negative.
135 */
136
137 /*!
138 \variable MsXpS::libXpertMassCore::Isotope::m_probability
139 \brief The probability of this isotope, that is, its abundance.
140
141 Cannot be negative nor superior to 1.
142 */
143
144 /*!
145 \variable MsXpS::libXpertMassCore::Isotope::m_isValid
146 \brief The validity status of this isotope.
147
148 \sa validate()
149 */
150
151 /*!
152 \typedef MsXpS::libXpertMassCore::IsotopeQSPtr
153 \relates Isotope
154
155 Synonym for std::shared_ptr<Isotope>.
156 */
157
158 /*!
159 \typedef MsXpS::libXpertMassCore::IsotopeCstQSPtr
160 \relates Isotope
161
162 Synonym for std::shared_ptr<const Isotope>.
163 */
164
165 /*!
166 \brief Constructs an absolutely empty \l{Isotope}.
167
168 The Isotope instance is invalid. It can be later intialized
169 with the setter functions or the initialization functions.
170
171 \sa initialize(const QString &), initialize(const QString &name,
172 const QString &symbol,
173 double mass,
174 double probability)
175 */
176
2/4
✓ Branch 2 taken 60 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 60 times.
✗ Branch 6 not taken.
60 Isotope::Isotope(QObject *parent): QObject(parent)
177 {
178 60 }
179
180 /*!
181 \overload
182 \brief Constructs the \l{Isotope} with all the required arguments.
183
184 The isotope is created as a fully documented instance if all the following
185 parameters are correctly set:
186
187 \list
188 \li \a name (\l{m_name}): element's name
189 \li \a symbol (\l{m_symbol}): element's symbol
190 \li \a mass (\l{m_mass}): isotope's mass
191 \li \a probability (\l{m_probability}): isotope's probability, that is abundance
192 \endlist
193
194 \sa initialize(const QString &name, const QString &symbol, double mass, double
195 probability)
196 */
197 60249 Isotope::Isotope(const QString &name,
198 const QString &symbol,
199 double mass,
200 double probability,
201 60249 QObject *parent)
202 : QObject(parent),
203 120498 m_name(name),
204
1/2
✓ Branch 0 taken 60249 times.
✗ Branch 1 not taken.
60249 m_symbol(symbol),
205 60249 m_mass(mass),
206
1/2
✓ Branch 1 taken 60249 times.
✗ Branch 2 not taken.
60249 m_probability(probability)
207 {
208 60249 ErrorList error_list;
209
1/2
✓ Branch 1 taken 60249 times.
✗ Branch 2 not taken.
60249 m_isValid = validate(&error_list);
210
211
2/2
✓ Branch 0 taken 210 times.
✓ Branch 1 taken 60039 times.
60249 if(!m_isValid)
212 {
213
2/4
✓ Branch 1 taken 210 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 210 times.
✗ Branch 5 not taken.
210 QString errors = Utils::joinErrorList(error_list, "\n");
214
215
1/2
✓ Branch 1 taken 210 times.
✗ Branch 2 not taken.
420 qWarning().noquote()
216
1/2
✓ Branch 1 taken 210 times.
✗ Branch 2 not taken.
210 << "The constructed isotope is not valid, with errors:\n"
217
1/2
✓ Branch 1 taken 210 times.
✗ Branch 2 not taken.
210 << errors;
218 210 }
219 60249 }
220
221 /*!
222 \overload
223 \brief Constructs the \l{Isotope} as a copy of \a other setting parent to \a
224 parent.
225 */
226
2/4
✓ Branch 2 taken 2043 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 2043 times.
✗ Branch 6 not taken.
2043 Isotope::Isotope(const Isotope &other, QObject *parent): QObject(parent)
227 {
228 // qDebug() << "Constructing using reference.";
229
230 2043 m_name = other.m_name;
231 2043 m_symbol = other.m_symbol;
232 2043 m_mass = other.m_mass;
233 2043 m_probability = other.m_probability;
234
235 2043 QVector<QString> error_list;
236
1/2
✓ Branch 1 taken 2043 times.
✗ Branch 2 not taken.
2043 m_isValid = validate(&error_list);
237
238
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 2037 times.
2043 if(!m_isValid)
239 {
240
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 QString errors = Utils::joinErrorList(error_list, "\n");
241
242
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
12 qWarning().noquote()
243
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 << "The copy-constructed isotope is not valid, with errors:\n"
244
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 << errors;
245 6 }
246 2043 }
247
248 /*!
249 \overload
250 \brief Constructs the \l{Isotope} as a copy of \a other_p, setting parent to \a
251 parent.
252 */
253 Isotope::Isotope(const Isotope *other_p, QObject *parent): QObject(parent)
254 {
255 Q_ASSERT(other_p != nullptr);
256 // qDebug() << "Constructing using pointer.";
257
258 m_name = other_p->m_name;
259 m_symbol = other_p->m_symbol;
260 m_mass = other_p->m_mass;
261 m_probability = other_p->m_probability;
262
263 QVector<QString> error_list;
264 m_isValid = validate(&error_list);
265
266 if(!m_isValid)
267 {
268 QString errors = Utils::joinErrorList(error_list, "\n");
269
270 qWarning().noquote()
271 << "The copy-constructed isotope is not valid, with errors:\n"
272 << errors;
273 }
274 }
275
276 /*!
277 \overload
278 \brief Isotope::Isotope(const QString &text, QObject *parent)
279
280 Constructs this \l{Isotope} using all the data in the \a text string,
281 setting parent to \a parent.
282
283 The string contains all the Isotope data, separated by a comma ',' exactly
284 with the same format as that implemented by Isotope::toString().
285
286 \sa initialize(const QString &), initialize(const QString &name,
287 const QString &symbol,
288 double mass,
289 double probability), toString()
290 */
291
2/4
✓ Branch 2 taken 63954 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 63954 times.
✗ Branch 6 not taken.
63954 Isotope::Isotope(const QString &text, QObject *parent): QObject(parent)
292 {
293 // This is somehow the reverse of toString().
294
295
3/4
✓ Branch 1 taken 63954 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3 times.
✓ Branch 4 taken 63951 times.
63954 if(!initialize(text))
296
3/6
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
3 qWarning() << "The initialization failed using text:" << text;
297 63954 }
298
299 /*!
300 \brief Destructs this Isotope.
301 */
302 135108 Isotope::~Isotope()
303 {
304 135108 }
305
306 /*!
307 \brief Returns a heap-allocated Isotope as a copy of \c this Isotope but
308 reparented with \a parent.
309
310 Each member datum in \c this Isotope is copied the returned Isotope,
311 effectivaly making a copy of \c this Isotope and returning it after
312 reparenting to \a parent.
313 */
314 Isotope *
315 Isotope::clone(QObject *parent) const
316 {
317 Isotope *copy_p = new Isotope(parent);
318 copy_p->initialize(m_name, m_symbol, m_mass, m_probability);
319
320 return copy_p;
321 }
322
323 /*!
324 \brief Returns a heap-allocated Isotope as a copy of \a other but reparented
325 with \a parent.
326
327 Each member datum in \a other is copied the returned Isotope,
328 effectivaly making a copy of \a other and returning it after
329 reparenting to \a parent.
330 */
331 Isotope *
332 Isotope::clone(const Isotope &other, QObject *parent)
333 {
334 Isotope *copy_p = new Isotope(parent);
335 copy_p->initialize(
336 other.m_name, other.m_symbol, other.m_mass, other.m_probability);
337
338 return copy_p;
339 }
340
341 /*!
342 \brief Resets all the member data to default invalid values.
343
344 The m_isValid member datum is set to false.
345 */
346 void
347 127992 Isotope::clear()
348 {
349 127992 m_name = "";
350 127992 m_symbol = "";
351 127992 m_mass = -1.0;
352 127992 m_probability = -1.0;
353
354 127992 m_isValid = false;
355 127992 }
356
357 /*!
358 \overload MsXpS::libXpertMassCore::initialize(const QString &name,
359 const QString &symbol,
360 double mass,
361 double probability)
362
363 \brief Initializes this Isotope instance using \a name, \a symbol, \a mass and
364 \a probability.
365
366 Returns true if initilization was without error, false otherwise.
367
368 \sa initialize(const QString &text)
369 */
370 bool
371 24 Isotope::initialize(const QString &name,
372 const QString &symbol,
373 double mass,
374 double probability)
375 {
376 // Start by resetting this Isotope instance (sets m_isValid to false)
377 24 clear();
378
379 24 QString local_text;
380
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 QRegularExpression regexp;
381
382 //////// The element's name
383
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 local_text = name.simplified();
384
2/4
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
24 regexp.setPattern("^[a-z]+$");
385
386
4/6
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 6 times.
✓ Branch 8 taken 18 times.
24 if(!regexp.match(local_text).hasMatch())
387 {
388
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 qWarning() << "Failed to initialize the element's name.";
389 }
390 else
391 18 m_name = local_text;
392
393 //////// The element's symbol
394
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 local_text = symbol.simplified();
395
2/4
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
24 regexp.setPattern("^[A-Z][a-z]*$");
396
397
4/6
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 24 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 6 times.
✓ Branch 8 taken 18 times.
24 if(!regexp.match(local_text).hasMatch())
398
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 qWarning() << "Failed to initialize the element's symbol.";
399 else
400 18 m_symbol = local_text;
401
402 //////// The isotope's mass
403
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 21 times.
24 if(mass < 0)
404
2/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
3 qWarning() << "Failed to initialize the isotope's mass.";
405 else
406 21 m_mass = mass;
407
408 //////// The isotope's probability
409
4/4
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 18 times.
24 if(probability < 0 || probability > 1)
410
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 qWarning() << "Failed to initialize the isotope's probability.";
411 else
412 18 m_probability = probability;
413
414 // At this point, it looks like the Isotope is syntactically valid,
415 // but is it from a chemical standpoint?
416
417 24 QVector<QString> error_list;
418
1/2
✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
24 m_isValid = validate(&error_list);
419
420
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 3 times.
24 if(!m_isValid)
421 {
422 21 QString errors;
423
424
4/6
✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 21 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 21 times.
✓ Branch 7 taken 21 times.
42 for(const QString &error : error_list)
425
3/6
✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 21 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 21 times.
✗ Branch 8 not taken.
42 errors.append(QString("%1\n").arg(error));
426
427
1/2
✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
42 qWarning().noquote()
428
2/4
✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 21 times.
✗ Branch 5 not taken.
21 << "The initialized isotope is not valid, with errors:" << errors;
429 21 }
430
431 24 return m_isValid;
432 24 }
433
434 /*!
435 \brief Initializes the \l{Isotope} using all the data in the \a text string.
436
437 The string passed as argument is QString::simplified() and
438 QString::split() with ',' as the delimiter.
439
440 The obtained strings are converted to the corresponding numerical or textual
441 values to initalize all the member data.
442
443 Returns true if the string contained valid substrings that successfully
444 initialized the \l{Isotope}, false otherwise.
445
446 \sa Isotope(const QString &text, QObject *parent), initialize(const QString &name,
447 const QString &symbol,
448 double mass,
449 double probability)
450 */
451 bool
452 63987 Isotope::initialize(const QString &text)
453 {
454 // qDebug() << "Initializing Isotope with text:" << text;
455
456 // Start by resetting this Isotope instance (sets m_isValid to false)
457 63987 clear();
458
459
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 63984 times.
63987 if(text.isEmpty())
460 {
461 6 qCritical()
462
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 << "The text used to initialize the Isotope instance is empty.";
463 3 m_isValid = false;
464 3 return m_isValid;
465 }
466
467 // At this point deconstruct the line. Make sure we remove all spaces from
468 // beginning and end AND reduce all multiple-space substrings in the text to
469 // only single-space substrings.
470
471 63984 QString local_text = text.simplified();
472 // qDebug() << "After simplification, the text becomes:" << local_text;
473
474
1/2
✓ Branch 1 taken 63984 times.
✗ Branch 2 not taken.
63984 QStringList string_list = local_text.split(',');
475
476 // qDebug() << "After splitting text at commas, count of the subtext strings:"
477 // << string_list.size();
478
479 // We may be reading data from a line that comes from an old version of
480 // isotopic data files that contain much more information than from newer
481 // version of such files. Check this.
482
483
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 63978 times.
63984 if(string_list.size() == 10)
484 {
485 // We may be reading from an older version file.
486
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 return initializeVersion1(text);
487 }
488
2/2
✓ Branch 0 taken 63975 times.
✓ Branch 1 taken 3 times.
63978 else if(string_list.size() == static_cast<int>(IsotopeFields::LAST))
489 {
490 // We may be reading from a more recent version file.
491
1/2
✓ Branch 1 taken 63975 times.
✗ Branch 2 not taken.
63975 return initializeVersion2(text);
492 }
493 else
494 {
495 return false;
496 }
497
498 return false;
499 63984 }
500
501 /*!
502 \brief Initializes the \l{Isotope} using all the data in the \a text string.
503
504 The string passed as argument is QString::simplified() and
505 QString::split() with ',' as the delimiter.
506
507 The index of the substring in the string list obtained by splitting the initial
508 text at ',' delimiter defines what data element it corresponds to.
509
510 The obtained strings are converted to the corresponding numerical or textual
511 values to initalize all the member data.
512
513 Returns true if the string contained valid substrings that successfully
514 initialized the \l{Isotope}, false otherwise.
515
516 \sa Isotope(const QString &text, QObject *parent), initialize(const QString &name,
517 const QString &symbol,
518 double mass,
519 double probability)
520 */
521 bool
522 6 Isotope::initializeVersion1(const QString &text)
523 {
524 // qDebug() << "Initializing Isotope with text:" << text;
525
526 // This version has a set of data matching this structure:
527
528 // enum class IsotopeFields
529 // {
530 // ID = 0,
531 // ELEMENT = 1,
532 // SYMBOL = 2,
533 // ATOMIC_NUMBER = 3,
534 // MASS = 4,
535 // MASS_NUMBER = 5,
536 // EXTRA_NEUTRONS = 6,
537 // PROBABILITY = 7,
538 // LN_PROBABILITY = 8,
539 // RADIOACTIVE = 9,
540 // LAST = 10,
541 // };
542
543 // Start by resetting this Isotope instance (sets m_isValid to false)
544 6 clear();
545
546
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if(text.isEmpty())
547 {
548 qCritical()
549 << "The text used to initialize the Isotope instance is empty.";
550 m_isValid = false;
551 return m_isValid;
552 }
553
554 6 double temp_value_double;
555 6 QString temp_value_string;
556
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 QRegularExpression regexp;
557
558 // At this point deconstruct the line. Make sure we remove all spaces from
559 // beginning and end AND reduce all multiple-space substrings in the text to
560 // only single-space substrings.
561
562
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 QString local_text = text.simplified();
563 // qDebug() << "After simplification, the text becomes:" << local_text;
564
565
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 QStringList string_list = local_text.split(',');
566
567 // qDebug() << "After splitting text at commas, count of the subtext strings:"
568 // << string_list.size();
569
570
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if(string_list.size() != 10)
571 {
572 qDebug() << "The text does not match an Isotope definition.";
573 return false;
574 }
575
576 6 bool ok = false;
577
578 // The element name
579
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 temp_value_string =
580
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 string_list[static_cast<int>(Version1IsotopeFields::ELEMENT)].simplified();
581 // The element name can be checked using a regexp
582
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 regexp.setPattern("^[a-z]+$");
583
3/6
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 6 times.
6 if(!regexp.match(temp_value_string).hasMatch())
584 {
585 qDebug() << "Failed to extract the element name.";
586 return false;
587 }
588 6 m_name = temp_value_string;
589
590
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 temp_value_string =
591
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 string_list[static_cast<int>(Version1IsotopeFields::SYMBOL)].simplified();
592 // The symbol can be checked using a regexp
593
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 regexp.setPattern("^[A-Z][a-z]*$");
594
3/6
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 6 times.
6 if(!regexp.match(temp_value_string).hasMatch())
595 {
596 qDebug() << "Failed to extract the element symbol.";
597 return false;
598 }
599 6 m_symbol = temp_value_string;
600
601
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 temp_value_double = string_list[static_cast<int>(Version1IsotopeFields::MASS)]
602 .simplified()
603
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 .toDouble(&ok);
604
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if(!ok)
605 {
606 qDebug() << "Failed to extract the isotope mass.";
607 return false;
608 }
609 6 m_mass = temp_value_double;
610
611
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 temp_value_double =
612
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 string_list[static_cast<int>(Version1IsotopeFields::PROBABILITY)]
613 .simplified()
614
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 .toDouble(&ok);
615
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
6 if(!ok)
616 {
617 qDebug() << "Failed to extract the isotope probability.";
618 return false;
619 }
620 6 m_probability = temp_value_double;
621
622 // qDebug() << toString();
623
624 6 return true;
625 6 }
626
627 /*!
628 \brief Initializes the \l{Isotope} using all the data in the \a text string.
629
630 The string passed as argument is QString::simplified() and
631 QString::split() with ',' as the delimiter.
632
633 The index of the substring in the string list obtained by splitting the initial
634 text at ',' delimiter defines what data element it corresponds to.
635
636 Returns true if the string contained valid substrings that successfully
637 initialized the \l{Isotope}, false otherwise.
638
639 \sa Isotope(const QString &text, QObject *parent), initialize(const QString &name, const QString
640 &symbol, double mass, double probability)
641 */
642 bool
643 63975 Isotope::initializeVersion2(const QString &text)
644 {
645 // qDebug() << "Initializing Isotope with text:" << text;
646
647 // Start by resetting this Isotope instance (sets m_isValid to false)
648 63975 clear();
649
650
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 63975 times.
63975 if(text.isEmpty())
651 {
652 qCritical()
653 << "The text used to initialize the Isotope instance is empty.";
654 m_isValid = false;
655 return m_isValid;
656 }
657
658 63975 double temp_value_double;
659 63975 QString temp_value_string;
660
1/2
✓ Branch 1 taken 63975 times.
✗ Branch 2 not taken.
63975 QRegularExpression regexp;
661
662 // At this point deconstruct the line. Make sure we remove all spaces from
663 // beginning and end AND reduce all multiple-space substrings in the text to
664 // only single-space substrings.
665
666
1/2
✓ Branch 1 taken 63975 times.
✗ Branch 2 not taken.
63975 QString local_text = text.simplified();
667 // qDebug() << "After simplification, the text becomes:" << local_text;
668
669
1/2
✓ Branch 1 taken 63975 times.
✗ Branch 2 not taken.
63975 QStringList string_list = local_text.split(',');
670
671 // qDebug() << "After splitting text at commas, count of the subtext strings:"
672 // << string_list.size();
673
674 // qDebug() << "Expecting " << static_cast<int>(IsotopeFields::LAST)
675 // << "comma-separated fields for isotope-describing text line."
676 // << "QStringList is:" << string_list;
677
678
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 63975 times.
63975 if(string_list.size() != static_cast<int>(IsotopeFields::LAST))
679 {
680 qWarning() << "The text does not match an Isotope definition.";
681 m_isValid = false;
682 return m_isValid;
683 }
684
685 63975 bool ok = false;
686
687
1/2
✓ Branch 1 taken 63975 times.
✗ Branch 2 not taken.
63975 temp_value_string =
688
1/2
✓ Branch 1 taken 63975 times.
✗ Branch 2 not taken.
63975 string_list[static_cast<int>(IsotopeFields::NAME)].simplified();
689
2/4
✓ Branch 1 taken 63975 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 63975 times.
✗ Branch 5 not taken.
63975 regexp.setPattern("^[a-z]+$");
690
4/6
✓ Branch 1 taken 63975 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 63975 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 6 times.
✓ Branch 8 taken 63969 times.
63975 if(!regexp.match(temp_value_string).hasMatch())
691 {
692
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 qWarning() << "Failed to extract the element name.";
693 6 m_isValid = false;
694 6 return m_isValid;
695 }
696 else
697 63969 m_name = temp_value_string;
698
699
1/2
✓ Branch 1 taken 63969 times.
✗ Branch 2 not taken.
63969 temp_value_string =
700
1/2
✓ Branch 1 taken 63969 times.
✗ Branch 2 not taken.
63969 string_list[static_cast<int>(IsotopeFields::SYMBOL)].simplified();
701
2/4
✓ Branch 1 taken 63969 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 63969 times.
✗ Branch 5 not taken.
63969 regexp.setPattern("^[A-Z][a-z]*$");
702
4/6
✓ Branch 1 taken 63969 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 63969 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 6 times.
✓ Branch 8 taken 63963 times.
63969 if(!regexp.match(temp_value_string).hasMatch())
703 {
704
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 qWarning() << "Failed to extract the element symbol.";
705 6 m_isValid = false;
706 6 return m_isValid;
707 }
708 else
709 63963 m_symbol = temp_value_string;
710
711
1/2
✓ Branch 1 taken 63963 times.
✗ Branch 2 not taken.
63963 temp_value_double =
712
2/4
✓ Branch 1 taken 63963 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 63963 times.
✗ Branch 5 not taken.
63963 string_list[static_cast<int>(IsotopeFields::MASS)].simplified().toDouble(
713 &ok);
714
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 63960 times.
63963 if(!ok)
715 {
716
2/4
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
3 qWarning() << "Failed to extract the isotope mass.";
717 3 m_isValid = false;
718 3 return m_isValid;
719 }
720 else
721 63960 m_mass = temp_value_double;
722
723
724
2/4
✓ Branch 1 taken 63960 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 63960 times.
✗ Branch 5 not taken.
63960 temp_value_double = string_list[static_cast<int>(IsotopeFields::PROBABILITY)]
725 .simplified()
726
1/2
✓ Branch 1 taken 63960 times.
✗ Branch 2 not taken.
63960 .toDouble(&ok);
727
4/4
✓ Branch 0 taken 63957 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 63951 times.
63960 if(!ok || temp_value_double > 1)
728 {
729
2/4
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 9 times.
✗ Branch 5 not taken.
9 qWarning() << "Failed to extract the isotope probability.";
730 9 m_isValid = false;
731 9 return m_isValid;
732 }
733 else
734 63951 m_probability = temp_value_double;
735
736 // At this point, it looks like the Isotope is syntactically valid,
737 // but is it from a chemical standpoint?
738
739 63951 QVector<QString> error_list;
740
1/2
✓ Branch 1 taken 63951 times.
✗ Branch 2 not taken.
63951 m_isValid = validate(&error_list);
741
742
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 63945 times.
63951 if(!m_isValid)
743 {
744 6 QString errors;
745
746
4/6
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 6 times.
✓ Branch 7 taken 6 times.
12 for(const QString &error : error_list)
747
3/6
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 6 times.
✗ Branch 8 not taken.
12 errors.append(QString("%1\n").arg(error));
748
749
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
12 qWarning().noquote()
750
2/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6 times.
✗ Branch 5 not taken.
6 << "The initialized isotope is not valid, with errors:" << errors;
751 6 }
752
753 63951 return m_isValid;
754 127926 }
755
756 /*!
757 \brief Sets the name of the isotope to \a name.
758
759 \sa getName()
760 */
761 void
762 21 Isotope::setName(const QString &name)
763 {
764
1/2
✓ Branch 2 taken 21 times.
✗ Branch 3 not taken.
21 QRegularExpression regexp("^[a-z]+$");
765
4/6
✓ Branch 1 taken 21 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 21 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 3 times.
✓ Branch 8 taken 18 times.
21 if(!regexp.match(name).hasMatch())
766 {
767
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
6 qWarning() << "Failed to set the element name. The name should be all "
768
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 "lower-case. Isotope left unchanged.";
769 3 m_isValid = false;
770 }
771
772 21 m_name = name;
773 21 }
774
775 /*!
776 \brief Returns the name of the isotope.
777
778 \sa setName()
779 */
780 QString
781 63 Isotope::getName() const
782 {
783
1/2
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
63 return m_name;
784 }
785
786 /*!
787 \brief Sets the symbol of the isotope to \a symbol.
788
789 \sa getSymbol()
790 */
791 void
792 30 Isotope::setSymbol(const QString &symbol)
793 {
794
1/2
✓ Branch 2 taken 30 times.
✗ Branch 3 not taken.
30 QRegularExpression regexp("^[A-Z][a-z]*$");
795
4/6
✓ Branch 1 taken 30 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 30 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 15 times.
✓ Branch 8 taken 15 times.
30 if(!regexp.match(symbol).hasMatch())
796 {
797
2/4
✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
15 qWarning() << "Failed to set the element symbol. Isotope left unchanged.";
798 15 m_isValid = false;
799 }
800
801 30 m_symbol = symbol;
802 30 }
803
804 /*!
805 \brief Returns the symbol of the isotope.
806
807 \sa setSymbol()
808 */
809 QString
810 28253193 Isotope::getSymbol() const
811 {
812
1/2
✓ Branch 0 taken 28253193 times.
✗ Branch 1 not taken.
28253193 return m_symbol;
813 }
814
815 /*!
816 \brief Sets the the mass of the isotope to \a mass.
817
818 \sa getMass()
819 */
820 void
821 18 Isotope::setMass(double mass)
822 {
823
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 9 times.
18 if(mass < 0)
824 {
825
1/2
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
9 qWarning() << "Failed to set the mass. Isotope left unchanged.";
826 9 m_isValid = false;
827 }
828
829 18 m_mass = mass;
830 18 }
831
832 /*!
833 \brief Returns the mass of the isotope.
834
835 \sa setMass()
836 */
837 double
838 199269 Isotope::getMass() const
839 {
840 199269 return m_mass;
841 }
842
843 /*!
844 \brief Sets the probability (the abundance) of the isotope to \a probability.
845
846 \sa getProbability()
847 */
848 void
849 15 Isotope::setProbability(double probability)
850 {
851
4/4
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 6 times.
15 if(probability < 0 || probability > 1)
852 {
853
1/2
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
9 qWarning() << "Failed to set the probability. Isotope left unchanged.";
854 9 m_isValid = false;
855 }
856
857 15 m_probability = probability;
858 15 }
859
860 /*!
861 \brief Returns the probability (the abundance) of the isotope.
862
863 \sa setProbability()
864 */
865 double
866 444051 Isotope::getProbability() const
867 {
868 444051 return m_probability;
869 }
870
871 /*!
872 \brief Validates the isotope.
873
874 The element name, symbol, mass and probability member data are scrutinized and
875 if errors are detected descriptive error messages are added to \a error_list_p.
876
877 For example, for symbol:
878
879 \code
880 if(m_symbol.isEmpty())
881 {
882 local_text = m_symbol.simplified();
883 regexp.setPattern("^[A-Z][a-z]*$");
884
885 if(!regexp.match(local_text).hasMatch())
886 {
887 error_msg = "Failed to validate the element's symbol.";
888 error_list_p->push_back(error_msg);
889 qWarning() << error_msg;
890 }
891 }
892 \endcode
893
894 Returns the error count. If no error occurred, the returned value is 0.
895 */
896 bool
897 248553 Isotope::validate(ErrorList *error_list_p)
898 {
899
1/2
✓ Branch 1 taken 248553 times.
✗ Branch 2 not taken.
248553 qsizetype previous_error_count = error_list_p->size();
900
901 248553 QString error_msg;
902 248553 QString local_text;
903
1/2
✓ Branch 1 taken 248553 times.
✗ Branch 2 not taken.
248553 QRegularExpression regexp;
904
905 //////// The element name
906
1/2
✓ Branch 1 taken 248553 times.
✗ Branch 2 not taken.
248553 local_text = m_name.simplified();
907
2/4
✓ Branch 1 taken 248553 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 248553 times.
✗ Branch 5 not taken.
248553 regexp.setPattern("^[a-z]+$");
908
909
4/6
✓ Branch 1 taken 248553 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 248553 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 225 times.
✓ Branch 8 taken 248328 times.
248553 if(!regexp.match(local_text).hasMatch())
910 {
911
1/2
✓ Branch 1 taken 225 times.
✗ Branch 2 not taken.
225 error_msg = "Failed to validate the element's name.";
912
1/2
✓ Branch 1 taken 225 times.
✗ Branch 2 not taken.
225 error_list_p->push_back(error_msg);
913
2/4
✓ Branch 1 taken 225 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 225 times.
✗ Branch 5 not taken.
225 qWarning() << error_msg;
914 }
915 248553 m_name = local_text;
916
917 //////// The element symbol
918
1/2
✓ Branch 1 taken 248553 times.
✗ Branch 2 not taken.
248553 local_text = m_symbol.simplified();
919
2/4
✓ Branch 1 taken 248553 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 248553 times.
✗ Branch 5 not taken.
248553 regexp.setPattern("^[A-Z][a-z]*$");
920
921
4/6
✓ Branch 1 taken 248553 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 248553 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 54 times.
✓ Branch 8 taken 248499 times.
248553 if(!regexp.match(local_text).hasMatch())
922 {
923
1/2
✓ Branch 1 taken 54 times.
✗ Branch 2 not taken.
54 error_msg = "Failed to validate the element's symbol.";
924
1/2
✓ Branch 1 taken 54 times.
✗ Branch 2 not taken.
54 error_list_p->push_back(error_msg);
925
2/4
✓ Branch 1 taken 54 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 54 times.
✗ Branch 5 not taken.
54 qWarning() << error_msg;
926 }
927 248553 m_symbol = local_text;
928
929 //////// The element mass
930
2/2
✓ Branch 0 taken 51 times.
✓ Branch 1 taken 248502 times.
248553 if(m_mass < 0)
931 {
932
1/2
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
51 error_msg = "Failed to validate the isotope's mass.";
933
1/2
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
51 error_list_p->push_back(error_msg);
934
2/4
✓ Branch 1 taken 51 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 51 times.
✗ Branch 5 not taken.
51 qWarning() << error_msg;
935 }
936
937 //////// The element probability
938
4/4
✓ Branch 0 taken 248511 times.
✓ Branch 1 taken 42 times.
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 248496 times.
248553 if(m_probability < 0 || m_probability > 1)
939 {
940 // qDebug() << "The probability:" << m_probability;
941
942
1/2
✓ Branch 1 taken 57 times.
✗ Branch 2 not taken.
57 error_msg = "Failed to validate the isotope's probability.";
943
1/2
✓ Branch 1 taken 57 times.
✗ Branch 2 not taken.
57 error_list_p->push_back(error_msg);
944
2/4
✓ Branch 1 taken 57 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 57 times.
✗ Branch 5 not taken.
57 qWarning() << error_msg;
945 }
946
947
2/2
✓ Branch 0 taken 282 times.
✓ Branch 1 taken 248271 times.
248553 if(error_list_p->size() > previous_error_count)
948 282 m_isValid = false;
949 else
950 248271 m_isValid = true;
951
952 497106 return m_isValid;
953 248553 }
954
955 /*!
956 \brief Return the member datum \l{m_isValid}.
957 */
958 bool
959 369 Isotope::isValid() const
960 {
961 369 return m_isValid;
962 }
963
964 /*!
965 \brief Tests the equality between this isotope and \a other.
966
967 Each member datum in \a other is compared to this isotope's member datum. If a
968 difference is detected, a counter is incremented.
969
970 Returns true if no difference has been encountered (the counter is 0) and
971 false otherwise.
972
973 \sa operator!=()
974 */
975 bool
976 1800 Isotope::operator==(const Isotope &other) const
977 {
978
2/2
✓ Branch 0 taken 1797 times.
✓ Branch 1 taken 3 times.
1800 if(&other == this)
979 return true;
980
981
4/4
✓ Branch 0 taken 1794 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1788 times.
✓ Branch 3 taken 6 times.
1797 return (m_name == other.m_name && m_symbol == other.m_symbol &&
982
4/6
✓ Branch 0 taken 1794 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1788 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1788 times.
✗ Branch 5 not taken.
3585 m_mass == other.m_mass && m_probability == other.m_probability);
983 }
984
985 /*!
986 \brief Tests the inequality between this isotope and \a other.
987
988 Returns the negated result of operator==().
989
990 \sa operator==()
991 */
992 bool
993 1755 Isotope::operator!=(const Isotope &other) const
994 {
995
2/2
✓ Branch 0 taken 1740 times.
✓ Branch 1 taken 15 times.
1755 if(&other == this)
996 return false;
997
998 1740 return !operator==(other);
999 }
1000
1001 /*!
1002 \brief Returns a string containing a comma-separated textual representation
1003 of all the member data values.
1004
1005 All the member data values are separated using commas ','. Only the values
1006 are stored in the string, without naming the variables:
1007
1008 \code
1009 return QString("%1,%2,%3,%4")
1010 .arg(m_name)
1011 .arg(m_symbol)
1012 .arg(m_mass, 0, 'f', 60)
1013 .arg(m_probability, 0, 'f', 60);
1014 \endcode
1015 */
1016 QString
1017 1767 Isotope::toString() const
1018 {
1019 // We need to use CSV because there might be spaces in the
1020 // text in the IsoSpec tables.
1021 1767 return QString("%1,%2,%3,%4")
1022
1/2
✓ Branch 1 taken 1767 times.
✗ Branch 2 not taken.
3534 .arg(m_name)
1023
1/2
✓ Branch 1 taken 1767 times.
✗ Branch 2 not taken.
3534 .arg(m_symbol)
1024
1/2
✓ Branch 1 taken 1767 times.
✗ Branch 2 not taken.
3534 .arg(m_mass, 0, 'f', 60)
1025
1/2
✓ Branch 1 taken 1767 times.
✗ Branch 2 not taken.
3534 .arg(m_probability, 0, 'f', 60);
1026 }
1027
1028 void
1029 Isotope::registerJsConstructor(QJSEngine *engine)
1030
1031 {
1032 if(!engine)
1033 {
1034 qWarning() << "Cannot register Isotope class: engine is null";
1035 return;
1036 }
1037
1038 // Register the meta object as a constructor
1039
1040 QJSValue jsMetaObject = engine->newQMetaObject(&Isotope::staticMetaObject);
1041 engine->globalObject().setProperty("Isotope", jsMetaObject);
1042 }
1043
1044
1045 } // namespace libXpertMassCore
1046
1047 } // namespace MsXpS
1048