GCC Code Coverage Report


source/XpertMassCore/src/
File: source/XpertMassCore/src/IsotopicDataUserConfigHandler.cpp
Date: 2025-11-20 01:41:33
Lines:
59/71
83.1%
Functions:
5/6
83.3%
Branches:
55/104
52.9%

Line Branch Exec Source
1 /* BEGIN software license
2 *
3 * MsXpertSuite - mass spectrometry software suite
4 * -----------------------------------------------
5 * Copyright (C) 2009--2020 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 <QDebug>
36 #include <QFile>
37
38
39 /////////////////////// IsoSpec
40 #include <IsoSpec++/isoSpec++.h>
41 #include <IsoSpec++/element_tables.h>
42
43
44 /////////////////////// Local includes
45 #include "MsXpS/libXpertMassCore/globals.hpp"
46 #include "MsXpS/libXpertMassCore/IsotopicDataUserConfigHandler.hpp"
47
48 namespace MsXpS
49 {
50
51 namespace libXpertMassCore
52 {
53
54
55 /*!
56 \class MsXpS::libXpertMassCore::IsotopicDataUserConfigHandler
57 \inmodule libXpertMassCore
58 \ingroup PolChemDefBuildingdBlocks
59 \inheaderfile IsotopicDataUserConfigHandler.hpp
60
61 \brief The IsotopicDataUserConfigHandler class handles user-defined
62 \l{IsotopicData}.
63
64 The data can be loaded and written to file. The format of the data is based on
65 Isotope::toString(), that outputs each \l{Isotope} data in a separate line.
66
67 Comments are allowed and are on lines that start with the '#' character.
68 These lines are ignored when loading data.
69
70 \sa IsotopicDataLibraryHandler, IsotopicDataManualConfigHandler
71 \sa Isotope::toString
72 */
73
74 /*!
75 \brief Constructs the \l{IsotopicDataUserConfigHandler}.
76
77 m_fileName is set to \a file_name
78 */
79 12 IsotopicDataUserConfigHandler::IsotopicDataUserConfigHandler(
80 12 const QString &file_name)
81 12 : IsotopicDataBaseHandler(file_name)
82 {
83 12 }
84
85 /*!
86 \brief Constructs the IsotopicDataUserConfigHandler.
87
88 The instance will have its isotopic data member pointing to \a
89 isotopic_data_sp.
90
91 m_fileName is set to \a file_name
92 */
93 68 IsotopicDataUserConfigHandler::IsotopicDataUserConfigHandler(
94 68 IsotopicDataSPtr isotopic_data_sp, const QString &file_name)
95
1/2
✓ Branch 2 taken 68 times.
✗ Branch 3 not taken.
68 : IsotopicDataBaseHandler(isotopic_data_sp, file_name)
96 {
97 68 }
98
99 /*!
100 \brief Destructs the IsotopicDataUserConfigHandler.
101
102 Nothing is explicitely deleted in the destructor.
103 */
104 162 IsotopicDataUserConfigHandler::~IsotopicDataUserConfigHandler()
105 {
106 // qDebug();
107 162 }
108
109 /*!
110 \brief Loads isotopic data from \a file_name if set.
111
112 If \a file_name is empty, then the member m_fileName is used.
113
114 The format of the file consists in a single line of data per \l{Isotope} as
115 created using the Isotope::toString() function. Each line is used to create an
116 Isotope with the text-based constructor.
117
118 Returns the count of \l{Isotope}s created or 0 if the file does not exist or
119 is not readable.
120
121 \sa Isotope::Isotope and overloads
122 */
123 qsizetype
124 76 IsotopicDataUserConfigHandler::loadData(const QString &file_name)
125 {
126
2/2
✓ Branch 0 taken 72 times.
✓ Branch 1 taken 4 times.
76 QString local_file_name = file_name;
127
128
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 72 times.
76 if(local_file_name.isEmpty())
129 4 local_file_name = m_fileName;
130
131 // See the Isotope::toString() function that is used to write the isotopic
132 // data to file. We thus expect exactly that format from the file.
133
134
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 76 times.
76 if(local_file_name.isEmpty())
135 {
136 qCritical("File name is emtpy. Failed to open file for reading.");
137 return 0;
138 }
139
140
1/2
✓ Branch 1 taken 76 times.
✗ Branch 2 not taken.
76 QFile file(local_file_name);
141
142
3/4
✓ Branch 1 taken 76 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 74 times.
76 if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
143 {
144
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 qCritical("Failed to open file for reading.");
145 2 return 0;
146 }
147
148 // qDebug() << "Loading isotopic data from file:" << local_file_name;
149
150 // We may have written comments to the file in the form of '# <text>' lines
151
2/4
✓ Branch 1 taken 74 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 74 times.
✗ Branch 5 not taken.
74 QRegularExpression commentRegexp("^\\s*#.*$");
152
153
1/2
✓ Branch 1 taken 74 times.
✗ Branch 2 not taken.
74 msp_isotopicData->clear();
154
155 74 qsizetype isotope_count = 0;
156
157
1/2
✓ Branch 1 taken 74 times.
✗ Branch 2 not taken.
74 QTextStream in_stream(&file);
158
159
3/4
✓ Branch 1 taken 21608 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 21534 times.
✓ Branch 4 taken 74 times.
21608 while(!in_stream.atEnd())
160 {
161
1/2
✓ Branch 1 taken 21534 times.
✗ Branch 2 not taken.
43068 QString line = in_stream.readLine().simplified();
162
163 // qDebug() << "simplified line:" << line;
164
165 // Ignore empty or comment lines
166
6/10
✓ Branch 0 taken 21460 times.
✓ Branch 1 taken 74 times.
✓ Branch 3 taken 21460 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 21460 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 222 times.
✓ Branch 10 taken 21312 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
21534 if(line.length() < 1 || commentRegexp.match(line).hasMatch())
167 222 continue;
168
169
1/2
✓ Branch 1 taken 21312 times.
✗ Branch 2 not taken.
21312 IsotopeQSPtr isotope_qsp = QSharedPointer<Isotope>::create(line);
170
171 // We do not want to update the mono/avg maps each time we load an
172 // isotope. We'll call the relevant function later.
173
2/4
✓ Branch 0 taken 21312 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 21312 times.
✗ Branch 4 not taken.
42624 msp_isotopicData->appendNewIsotope(isotope_qsp, /* update_maps*/ false);
174
175 21312 ++isotope_count;
176 21534 }
177 // End of
178 // while(!in.atEnd())
179
180 // qDebug() << "Finished creating all the Isotope instances.";
181
182
1/2
✓ Branch 1 taken 74 times.
✗ Branch 2 not taken.
74 file.close();
183
184 // At this point, it seems that the loading went fine.
185
186 // Because we have touched the m_isotopes vector, we need to update the
187 // mono/avg masses map.
188
189
2/4
✓ Branch 1 taken 74 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 74 times.
74 if(!msp_isotopicData->updateMassMaps())
190 qFatal("Programming error. Failed to update the mass maps.");
191
192
2/4
✓ Branch 1 taken 74 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 74 times.
74 if(isotope_count != msp_isotopicData->size())
193 qFatal("Programming error. Failed to load all the isotopes to file.");
194
195
1/2
✓ Branch 1 taken 74 times.
✗ Branch 2 not taken.
74 return msp_isotopicData->size();
196 152 }
197
198 /*!
199 \brief Writes isotopic data to \a file_name.
200
201 If \a file_name is empty, \l{m_fileName} is tried. If both are empty, the
202 function returns 0. If any one of the file names are correct (file_name takes
203 precedence over m_fileName), then m_fileName is set to that file name.
204
205 The format of the file consists in a single line of data per \l Isotope as
206 created using the Isotope::toString() function. Each isotope is output to
207 its own line.
208
209 Returns the count of \l{Isotope}s written to file or 0 if the file does not
210 exist or is not readable.
211 */
212 qsizetype
213 1 IsotopicDataUserConfigHandler::writeData(const QString &file_name)
214 {
215 // Although the isotopic data were loaded from the IsoSpec library tables, we
216 // might be willing to store these data to a file.
217
218
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 QString local_file_name = file_name;
219
220
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if(local_file_name.isEmpty())
221 local_file_name = m_fileName;
222
223
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if(local_file_name.isEmpty())
224 {
225 qCritical("File name is emtpy. Failed to open file for writing.");
226 return 0;
227 }
228
229
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 QFile file(local_file_name);
230
231
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
1 if(!file.open(QIODevice::WriteOnly | QIODevice::Text))
232 {
233 qCritical("Failed to open file for writing.");
234 return 0;
235 }
236
237
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 QTextStream out_stream(&file);
238
239 1 out_stream
240
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 << "# This file contains isotopic data in a format that can accommodate\n";
241 1 out_stream
242
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 << "# comments in the form of lines beginning with the '#' character.\n\n";
243
244 1 qsizetype isotope_count = 0;
245
246
5/8
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 288 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 288 times.
✓ Branch 9 taken 1 times.
289 for(auto item : msp_isotopicData->m_isotopes)
247 {
248
2/4
✓ Branch 1 taken 288 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 288 times.
✗ Branch 5 not taken.
288 out_stream << item->toString();
249
250 // We need to add it because toString() does not terminate the line with
251 // a new line character.
252
1/2
✓ Branch 1 taken 288 times.
✗ Branch 2 not taken.
288 out_stream << "\n";
253
254 288 ++isotope_count;
255 288 }
256
257
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 out_stream.flush();
258
259
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 file.close();
260
261
2/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
1 if(isotope_count != msp_isotopicData->size())
262 qFatal("Programming error. Failed to write all the isotopes to file.");
263
264 // Now we know that local_file_name is fine. Store into m_fileName.
265 1 m_fileName = local_file_name;
266
267 1 return isotope_count;
268 2 }
269
270 /*!
271 \brief Returns the size of the member IsotopicData.
272 */
273 qsizetype
274 IsotopicDataUserConfigHandler::checkConsistency()
275 {
276 return msp_isotopicData->size();
277 }
278
279
280 } // namespace libXpertMassCore
281
282 } // namespace MsXpS
283
284
285 #if 0
286
287 Example from IsoSpec.
288
289 const int elementNumber = 2;
290 const int isotopeNumbers[2] = {2,3};
291
292 const int atomCounts[2] = {2,1};
293
294
295 const double hydrogen_masses[2] = {1.00782503207, 2.0141017778};
296 const double oxygen_masses[3] = {15.99491461956, 16.99913170, 17.9991610};
297
298 const double* isotope_masses[2] = {hydrogen_masses, oxygen_masses};
299
300 const double hydrogen_probs[2] = {0.5, 0.5};
301 const double oxygen_probs[3] = {0.5, 0.3, 0.2};
302
303 const double* probs[2] = {hydrogen_probs, oxygen_probs};
304
305 IsoLayeredGenerator iso(Iso(elementNumber, isotopeNumbers, atomCounts,
306 isotope_masses, probs), 0.99);
307
308 #endif
309