GCC Code Coverage Report


source/XpertMassCore/src/
File: source/XpertMassCore/src/Isotope.cpp
Date: 2025-11-20 01:41:33
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 20 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 20 times.
✗ Branch 6 not taken.
20 Isotope::Isotope(QObject *parent): QObject(parent)
177 {
178 20 }
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 20083 Isotope::Isotope(const QString &name,
198 const QString &symbol,
199 double mass,
200 double probability,
201 20083 QObject *parent)
202 : QObject(parent),
203 40166 m_name(name),
204
1/2
✓ Branch 0 taken 20083 times.
✗ Branch 1 not taken.
20083 m_symbol(symbol),
205 20083 m_mass(mass),
206
1/2
✓ Branch 1 taken 20083 times.
✗ Branch 2 not taken.
20083 m_probability(probability)
207 {
208 20083 ErrorList error_list;
209
1/2
✓ Branch 1 taken 20083 times.
✗ Branch 2 not taken.
20083 m_isValid = validate(&error_list);
210
211
2/2
✓ Branch 0 taken 70 times.
✓ Branch 1 taken 20013 times.
20083 if(!m_isValid)
212 {
213
2/4
✓ Branch 1 taken 70 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 70 times.
✗ Branch 5 not taken.
70 QString errors = Utils::joinErrorList(error_list, "\n");
214
215
1/2
✓ Branch 1 taken 70 times.
✗ Branch 2 not taken.
140 qWarning().noquote()
216
1/2
✓ Branch 1 taken 70 times.
✗ Branch 2 not taken.
70 << "The constructed isotope is not valid, with errors:\n"
217
1/2
✓ Branch 1 taken 70 times.
✗ Branch 2 not taken.
70 << errors;
218 70 }
219 20083 }
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 681 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 681 times.
✗ Branch 6 not taken.
681 Isotope::Isotope(const Isotope &other, QObject *parent): QObject(parent)
227 {
228 // qDebug() << "Constructing using reference.";
229
230 681 m_name = other.m_name;
231 681 m_symbol = other.m_symbol;
232 681 m_mass = other.m_mass;
233 681 m_probability = other.m_probability;
234
235 681 QVector<QString> error_list;
236
1/2
✓ Branch 1 taken 681 times.
✗ Branch 2 not taken.
681 m_isValid = validate(&error_list);
237
238
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 679 times.
681 if(!m_isValid)
239 {
240
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 QString errors = Utils::joinErrorList(error_list, "\n");
241
242
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
4 qWarning().noquote()
243
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 << "The copy-constructed isotope is not valid, with errors:\n"
244
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 << errors;
245 2 }
246 681 }
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 21318 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 21318 times.
✗ Branch 6 not taken.
21318 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 21318 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 21317 times.
21318 if(!initialize(text))
296
3/6
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
1 qWarning() << "The initialization failed using text:" << text;
297 21318 }
298
299 /*!
300 \brief Destructs this Isotope.
301 */
302 45036 Isotope::~Isotope()
303 {
304 45036 }
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 42664 Isotope::clear()
348 {
349 42664 m_name = "";
350 42664 m_symbol = "";
351 42664 m_mass = -1.0;
352 42664 m_probability = -1.0;
353
354 42664 m_isValid = false;
355 42664 }
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 8 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 8 clear();
378
379 8 QString local_text;
380
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 QRegularExpression regexp;
381
382 //////// The element's name
383
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 local_text = name.simplified();
384
2/4
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
8 regexp.setPattern("^[a-z]+$");
385
386
4/6
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✓ Branch 8 taken 6 times.
8 if(!regexp.match(local_text).hasMatch())
387 {
388
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 qWarning() << "Failed to initialize the element's name.";
389 }
390 else
391 6 m_name = local_text;
392
393 //////// The element's symbol
394
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 local_text = symbol.simplified();
395
2/4
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
8 regexp.setPattern("^[A-Z][a-z]*$");
396
397
4/6
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✓ Branch 8 taken 6 times.
8 if(!regexp.match(local_text).hasMatch())
398
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 qWarning() << "Failed to initialize the element's symbol.";
399 else
400 6 m_symbol = local_text;
401
402 //////// The isotope's mass
403
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
8 if(mass < 0)
404
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 qWarning() << "Failed to initialize the isotope's mass.";
405 else
406 7 m_mass = mass;
407
408 //////// The isotope's probability
409
4/4
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 6 times.
8 if(probability < 0 || probability > 1)
410
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 qWarning() << "Failed to initialize the isotope's probability.";
411 else
412 6 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 8 QVector<QString> error_list;
418
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 m_isValid = validate(&error_list);
419
420
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1 times.
8 if(!m_isValid)
421 {
422 7 QString errors;
423
424
4/6
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 7 times.
✓ Branch 7 taken 7 times.
14 for(const QString &error : error_list)
425
3/6
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 7 times.
✗ Branch 8 not taken.
14 errors.append(QString("%1\n").arg(error));
426
427
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
14 qWarning().noquote()
428
2/4
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
7 << "The initialized isotope is not valid, with errors:" << errors;
429 7 }
430
431 8 return m_isValid;
432 8 }
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 21329 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 21329 clear();
458
459
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 21328 times.
21329 if(text.isEmpty())
460 {
461 2 qCritical()
462
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 << "The text used to initialize the Isotope instance is empty.";
463 1 m_isValid = false;
464 1 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 21328 QString local_text = text.simplified();
472 // qDebug() << "After simplification, the text becomes:" << local_text;
473
474
1/2
✓ Branch 1 taken 21328 times.
✗ Branch 2 not taken.
21328 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 2 times.
✓ Branch 1 taken 21326 times.
21328 if(string_list.size() == 10)
484 {
485 // We may be reading from an older version file.
486
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 return initializeVersion1(text);
487 }
488
2/2
✓ Branch 0 taken 21325 times.
✓ Branch 1 taken 1 times.
21326 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 21325 times.
✗ Branch 2 not taken.
21325 return initializeVersion2(text);
492 }
493 else
494 {
495 return false;
496 }
497
498 return false;
499 21328 }
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 2 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 2 clear();
545
546
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 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 2 double temp_value_double;
555 2 QString temp_value_string;
556
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 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 2 times.
✗ Branch 2 not taken.
2 QString local_text = text.simplified();
563 // qDebug() << "After simplification, the text becomes:" << local_text;
564
565
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 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 2 times.
2 if(string_list.size() != 10)
571 {
572 qDebug() << "The text does not match an Isotope definition.";
573 return false;
574 }
575
576 2 bool ok = false;
577
578 // The element name
579
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 temp_value_string =
580
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 string_list[static_cast<int>(Version1IsotopeFields::ELEMENT)].simplified();
581 // The element name can be checked using a regexp
582
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 regexp.setPattern("^[a-z]+$");
583
3/6
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
2 if(!regexp.match(temp_value_string).hasMatch())
584 {
585 qDebug() << "Failed to extract the element name.";
586 return false;
587 }
588 2 m_name = temp_value_string;
589
590
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 temp_value_string =
591
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 string_list[static_cast<int>(Version1IsotopeFields::SYMBOL)].simplified();
592 // The symbol can be checked using a regexp
593
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 regexp.setPattern("^[A-Z][a-z]*$");
594
3/6
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
2 if(!regexp.match(temp_value_string).hasMatch())
595 {
596 qDebug() << "Failed to extract the element symbol.";
597 return false;
598 }
599 2 m_symbol = temp_value_string;
600
601
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 temp_value_double = string_list[static_cast<int>(Version1IsotopeFields::MASS)]
602 .simplified()
603
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 .toDouble(&ok);
604
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if(!ok)
605 {
606 qDebug() << "Failed to extract the isotope mass.";
607 return false;
608 }
609 2 m_mass = temp_value_double;
610
611
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 temp_value_double =
612
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 string_list[static_cast<int>(Version1IsotopeFields::PROBABILITY)]
613 .simplified()
614
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 .toDouble(&ok);
615
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if(!ok)
616 {
617 qDebug() << "Failed to extract the isotope probability.";
618 return false;
619 }
620 2 m_probability = temp_value_double;
621
622 // qDebug() << toString();
623
624 2 return true;
625 2 }
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 21325 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 21325 clear();
649
650
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 21325 times.
21325 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 21325 double temp_value_double;
659 21325 QString temp_value_string;
660
1/2
✓ Branch 1 taken 21325 times.
✗ Branch 2 not taken.
21325 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 21325 times.
✗ Branch 2 not taken.
21325 QString local_text = text.simplified();
667 // qDebug() << "After simplification, the text becomes:" << local_text;
668
669
1/2
✓ Branch 1 taken 21325 times.
✗ Branch 2 not taken.
21325 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 21325 times.
21325 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 21325 bool ok = false;
686
687
1/2
✓ Branch 1 taken 21325 times.
✗ Branch 2 not taken.
21325 temp_value_string =
688
1/2
✓ Branch 1 taken 21325 times.
✗ Branch 2 not taken.
21325 string_list[static_cast<int>(IsotopeFields::NAME)].simplified();
689
2/4
✓ Branch 1 taken 21325 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 21325 times.
✗ Branch 5 not taken.
21325 regexp.setPattern("^[a-z]+$");
690
4/6
✓ Branch 1 taken 21325 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 21325 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✓ Branch 8 taken 21323 times.
21325 if(!regexp.match(temp_value_string).hasMatch())
691 {
692
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 qWarning() << "Failed to extract the element name.";
693 2 m_isValid = false;
694 2 return m_isValid;
695 }
696 else
697 21323 m_name = temp_value_string;
698
699
1/2
✓ Branch 1 taken 21323 times.
✗ Branch 2 not taken.
21323 temp_value_string =
700
1/2
✓ Branch 1 taken 21323 times.
✗ Branch 2 not taken.
21323 string_list[static_cast<int>(IsotopeFields::SYMBOL)].simplified();
701
2/4
✓ Branch 1 taken 21323 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 21323 times.
✗ Branch 5 not taken.
21323 regexp.setPattern("^[A-Z][a-z]*$");
702
4/6
✓ Branch 1 taken 21323 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 21323 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✓ Branch 8 taken 21321 times.
21323 if(!regexp.match(temp_value_string).hasMatch())
703 {
704
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 qWarning() << "Failed to extract the element symbol.";
705 2 m_isValid = false;
706 2 return m_isValid;
707 }
708 else
709 21321 m_symbol = temp_value_string;
710
711
1/2
✓ Branch 1 taken 21321 times.
✗ Branch 2 not taken.
21321 temp_value_double =
712
2/4
✓ Branch 1 taken 21321 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 21321 times.
✗ Branch 5 not taken.
21321 string_list[static_cast<int>(IsotopeFields::MASS)].simplified().toDouble(
713 &ok);
714
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 21320 times.
21321 if(!ok)
715 {
716
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
1 qWarning() << "Failed to extract the isotope mass.";
717 1 m_isValid = false;
718 1 return m_isValid;
719 }
720 else
721 21320 m_mass = temp_value_double;
722
723
724
2/4
✓ Branch 1 taken 21320 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 21320 times.
✗ Branch 5 not taken.
21320 temp_value_double = string_list[static_cast<int>(IsotopeFields::PROBABILITY)]
725 .simplified()
726
1/2
✓ Branch 1 taken 21320 times.
✗ Branch 2 not taken.
21320 .toDouble(&ok);
727
4/4
✓ Branch 0 taken 21319 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 21317 times.
21320 if(!ok || temp_value_double > 1)
728 {
729
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 probability.";
730 3 m_isValid = false;
731 3 return m_isValid;
732 }
733 else
734 21317 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 21317 QVector<QString> error_list;
740
1/2
✓ Branch 1 taken 21317 times.
✗ Branch 2 not taken.
21317 m_isValid = validate(&error_list);
741
742
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 21315 times.
21317 if(!m_isValid)
743 {
744 2 QString errors;
745
746
4/6
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 2 times.
4 for(const QString &error : error_list)
747
3/6
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
4 errors.append(QString("%1\n").arg(error));
748
749
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
4 qWarning().noquote()
750
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 << "The initialized isotope is not valid, with errors:" << errors;
751 2 }
752
753 21317 return m_isValid;
754 42642 }
755
756 /*!
757 \brief Sets the name of the isotope to \a name.
758
759 \sa getName()
760 */
761 void
762 7 Isotope::setName(const QString &name)
763 {
764
1/2
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
7 QRegularExpression regexp("^[a-z]+$");
765
4/6
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1 times.
✓ Branch 8 taken 6 times.
7 if(!regexp.match(name).hasMatch())
766 {
767
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 qWarning() << "Failed to set the element name. The name should be all "
768
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 "lower-case. Isotope left unchanged.";
769 1 m_isValid = false;
770 }
771
772 7 m_name = name;
773 7 }
774
775 /*!
776 \brief Returns the name of the isotope.
777
778 \sa setName()
779 */
780 QString
781 21 Isotope::getName() const
782 {
783
1/2
✓ Branch 0 taken 21 times.
✗ Branch 1 not taken.
21 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 10 Isotope::setSymbol(const QString &symbol)
793 {
794
1/2
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
10 QRegularExpression regexp("^[A-Z][a-z]*$");
795
4/6
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 5 times.
✓ Branch 8 taken 5 times.
10 if(!regexp.match(symbol).hasMatch())
796 {
797
2/4
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
5 qWarning() << "Failed to set the element symbol. Isotope left unchanged.";
798 5 m_isValid = false;
799 }
800
801 10 m_symbol = symbol;
802 10 }
803
804 /*!
805 \brief Returns the symbol of the isotope.
806
807 \sa setSymbol()
808 */
809 QString
810 9417731 Isotope::getSymbol() const
811 {
812
1/2
✓ Branch 0 taken 9417731 times.
✗ Branch 1 not taken.
9417731 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 6 Isotope::setMass(double mass)
822 {
823
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 if(mass < 0)
824 {
825
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 qWarning() << "Failed to set the mass. Isotope left unchanged.";
826 3 m_isValid = false;
827 }
828
829 6 m_mass = mass;
830 6 }
831
832 /*!
833 \brief Returns the mass of the isotope.
834
835 \sa setMass()
836 */
837 double
838 66423 Isotope::getMass() const
839 {
840 66423 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 5 Isotope::setProbability(double probability)
850 {
851
4/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 2 times.
5 if(probability < 0 || probability > 1)
852 {
853
1/2
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 qWarning() << "Failed to set the probability. Isotope left unchanged.";
854 3 m_isValid = false;
855 }
856
857 5 m_probability = probability;
858 5 }
859
860 /*!
861 \brief Returns the probability (the abundance) of the isotope.
862
863 \sa setProbability()
864 */
865 double
866 148017 Isotope::getProbability() const
867 {
868 148017 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 82851 Isotope::validate(ErrorList *error_list_p)
898 {
899
1/2
✓ Branch 1 taken 82851 times.
✗ Branch 2 not taken.
82851 qsizetype previous_error_count = error_list_p->size();
900
901 82851 QString error_msg;
902 82851 QString local_text;
903
1/2
✓ Branch 1 taken 82851 times.
✗ Branch 2 not taken.
82851 QRegularExpression regexp;
904
905 //////// The element name
906
1/2
✓ Branch 1 taken 82851 times.
✗ Branch 2 not taken.
82851 local_text = m_name.simplified();
907
2/4
✓ Branch 1 taken 82851 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 82851 times.
✗ Branch 5 not taken.
82851 regexp.setPattern("^[a-z]+$");
908
909
4/6
✓ Branch 1 taken 82851 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 82851 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 75 times.
✓ Branch 8 taken 82776 times.
82851 if(!regexp.match(local_text).hasMatch())
910 {
911
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
75 error_msg = "Failed to validate the element's name.";
912
1/2
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
75 error_list_p->push_back(error_msg);
913
2/4
✓ Branch 1 taken 75 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 75 times.
✗ Branch 5 not taken.
75 qWarning() << error_msg;
914 }
915 82851 m_name = local_text;
916
917 //////// The element symbol
918
1/2
✓ Branch 1 taken 82851 times.
✗ Branch 2 not taken.
82851 local_text = m_symbol.simplified();
919
2/4
✓ Branch 1 taken 82851 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 82851 times.
✗ Branch 5 not taken.
82851 regexp.setPattern("^[A-Z][a-z]*$");
920
921
4/6
✓ Branch 1 taken 82851 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 82851 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 18 times.
✓ Branch 8 taken 82833 times.
82851 if(!regexp.match(local_text).hasMatch())
922 {
923
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 error_msg = "Failed to validate the element's symbol.";
924
1/2
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
18 error_list_p->push_back(error_msg);
925
2/4
✓ Branch 1 taken 18 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
18 qWarning() << error_msg;
926 }
927 82851 m_symbol = local_text;
928
929 //////// The element mass
930
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 82834 times.
82851 if(m_mass < 0)
931 {
932
1/2
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
17 error_msg = "Failed to validate the isotope's mass.";
933
1/2
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
17 error_list_p->push_back(error_msg);
934
2/4
✓ Branch 1 taken 17 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 17 times.
✗ Branch 5 not taken.
17 qWarning() << error_msg;
935 }
936
937 //////// The element probability
938
4/4
✓ Branch 0 taken 82837 times.
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 82832 times.
82851 if(m_probability < 0 || m_probability > 1)
939 {
940 // qDebug() << "The probability:" << m_probability;
941
942
1/2
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
19 error_msg = "Failed to validate the isotope's probability.";
943
1/2
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
19 error_list_p->push_back(error_msg);
944
2/4
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 19 times.
✗ Branch 5 not taken.
19 qWarning() << error_msg;
945 }
946
947
2/2
✓ Branch 0 taken 94 times.
✓ Branch 1 taken 82757 times.
82851 if(error_list_p->size() > previous_error_count)
948 94 m_isValid = false;
949 else
950 82757 m_isValid = true;
951
952 165702 return m_isValid;
953 82851 }
954
955 /*!
956 \brief Return the member datum \l{m_isValid}.
957 */
958 bool
959 123 Isotope::isValid() const
960 {
961 123 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 600 Isotope::operator==(const Isotope &other) const
977 {
978
2/2
✓ Branch 0 taken 599 times.
✓ Branch 1 taken 1 times.
600 if(&other == this)
979 return true;
980
981
4/4
✓ Branch 0 taken 598 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 596 times.
✓ Branch 3 taken 2 times.
599 return (m_name == other.m_name && m_symbol == other.m_symbol &&
982
4/6
✓ Branch 0 taken 598 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 596 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 596 times.
✗ Branch 5 not taken.
1195 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 585 Isotope::operator!=(const Isotope &other) const
994 {
995
2/2
✓ Branch 0 taken 580 times.
✓ Branch 1 taken 5 times.
585 if(&other == this)
996 return false;
997
998 580 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 589 Isotope::toString() const
1018 {
1019 // We need to use CSV because there might be spaces in the
1020 // text in the IsoSpec tables.
1021 589 return QString("%1,%2,%3,%4")
1022
1/2
✓ Branch 1 taken 589 times.
✗ Branch 2 not taken.
1178 .arg(m_name)
1023
1/2
✓ Branch 1 taken 589 times.
✗ Branch 2 not taken.
1178 .arg(m_symbol)
1024
1/2
✓ Branch 1 taken 589 times.
✗ Branch 2 not taken.
1178 .arg(m_mass, 0, 'f', 60)
1025
1/2
✓ Branch 1 taken 589 times.
✗ Branch 2 not taken.
1178 .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