Line data Source code
1 : /* Copyright (C) 2025 Robert Schiwon
2 : *
3 : * This file is part of libjdx.
4 : *
5 : * libjdx is free software: you can redistribute it and/or modify it under the
6 : * terms of the GNU Lesser General Public License as published by the Free
7 : * Software Foundation, either version 3 of the License, or (at your option) any
8 : * later version.
9 : *
10 : * libjdx is distributed in the hope that it will be useful, but WITHOUT ANY
11 : * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 : * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
13 : * details.
14 : *
15 : * You should have received a copy of the GNU Lesser General Public License
16 : * along with libjdx. If not, see <https://www.gnu.org/licenses/>.
17 : */
18 : #include "jdx/Block.hpp"
19 : #include "util/LdrUtils.hpp"
20 : #include "util/StringUtils.hpp"
21 :
22 25 : libjdx::jdx::Block::Block(std::unique_ptr<io::TextReader> readerPtr)
23 25 : : m_readerPtr{std::move(readerPtr)}
24 64 : , m_reader{*m_readerPtr}
25 : {
26 50 : auto firstLine = m_reader.readLine();
27 47 : auto titleFirstLine = parseFirstLine(firstLine);
28 22 : std::optional<std::string> nextLine;
29 22 : parseInput(titleFirstLine, nextLine);
30 22 : }
31 :
32 20 : libjdx::jdx::Block::Block(io::TextReader& reader)
33 : : m_readerPtr{nullptr}
34 137 : , m_reader{reader}
35 : {
36 40 : auto firstLine = reader.readLine();
37 39 : auto titleFirstLine = parseFirstLine(firstLine);
38 19 : std::optional<std::string> nextLine;
39 19 : parseInput(titleFirstLine, nextLine);
40 11 : }
41 :
42 102 : libjdx::jdx::Block::Block(const std::string& title, io::TextReader& reader,
43 102 : std::optional<std::string>& nextLine)
44 : : m_readerPtr{nullptr}
45 102 : , m_reader{reader}
46 : {
47 102 : parseInput(title, nextLine);
48 102 : }
49 :
50 1159 : std::optional<const libjdx::jdx::StringLdr> libjdx::jdx::Block::getLdr(
51 : const std::string& label) const
52 : {
53 1159 : return util::findLdr(m_ldrs, label);
54 : }
55 :
56 31 : const std::vector<libjdx::jdx::StringLdr>& libjdx::jdx::Block::getLdrs() const
57 : {
58 31 : return m_ldrs;
59 : }
60 :
61 39 : const std::vector<libjdx::jdx::Block>& libjdx::jdx::Block::getBlocks() const
62 : {
63 39 : return m_blocks;
64 : }
65 :
66 1 : const std::vector<std::string>& libjdx::jdx::Block::getLdrComments() const
67 : {
68 1 : return m_ldrComments;
69 : }
70 :
71 30 : const std::optional<libjdx::jdx::XyData>& libjdx::jdx::Block::getXyData() const
72 : {
73 30 : return m_xyData;
74 : }
75 :
76 27 : const std::optional<libjdx::jdx::RaData>& libjdx::jdx::Block::getRaData() const
77 : {
78 27 : return m_raData;
79 : }
80 :
81 : const std::optional<libjdx::jdx::XyPoints>&
82 27 : libjdx::jdx::Block::getXyPoints() const
83 : {
84 27 : return m_xyPoints;
85 : }
86 :
87 : const std::optional<libjdx::jdx::PeakTable>&
88 28 : libjdx::jdx::Block::getPeakTable() const
89 : {
90 28 : return m_peakTable;
91 : }
92 :
93 : const std::optional<libjdx::jdx::PeakAssignments>&
94 26 : libjdx::jdx::Block::getPeakAssignments() const
95 : {
96 26 : return m_peakAssignments;
97 : }
98 :
99 : const std::optional<libjdx::jdx::NTuples>&
100 69 : libjdx::jdx::Block::getNTuples() const
101 : {
102 69 : return m_nTuples;
103 : }
104 :
105 : const std::optional<libjdx::jdx::AuditTrail>&
106 55 : libjdx::jdx::Block::getAuditTrail() const
107 : {
108 55 : return m_auditTrail;
109 : }
110 :
111 : const std::vector<libjdx::jdx::BrukerSpecificParameters>&
112 55 : libjdx::jdx::Block::getBrukerSpecificParameters() const
113 : {
114 55 : return m_brukerSpecificParameters;
115 : }
116 :
117 : const std::vector<libjdx::jdx::BrukerRelaxSection>&
118 54 : libjdx::jdx::Block::getBrukerRelaxSections() const
119 : {
120 54 : return m_brukerRelaxSections;
121 : }
122 :
123 45 : std::string libjdx::jdx::Block::parseFirstLine(const std::string& firstLine)
124 : {
125 45 : if (!util::isLdrStart(firstLine))
126 : {
127 3 : throw BlockParseException("Malformed LDR start: " + firstLine);
128 : }
129 84 : auto [label, value] = util::parseLdrStart(firstLine);
130 42 : if (label != s_blockStartLabel)
131 : {
132 : throw BlockParseException(
133 1 : "Malformed Block start, wrong label: " + firstLine);
134 : }
135 82 : return value;
136 : }
137 :
138 : // NOLINTBEGIN(readability-function-cognitive-complexity)
139 143 : void libjdx::jdx::Block::parseInput(
140 : const std::string& titleValue, std::optional<std::string>& nextLine)
141 : {
142 151 : std::string title = titleValue;
143 143 : nextLine = parseStringValue(title, m_reader);
144 143 : m_ldrs.emplace_back(s_blockStartLabel, title);
145 :
146 1306 : while (nextLine.has_value())
147 : {
148 1305 : if (util::isPureComment(nextLine.value()))
149 : {
150 : // check for Bruker quirk
151 17 : if (util::isBrukerSpecificSectionStart(nextLine.value()))
152 : {
153 16 : m_brukerSpecificParameters.emplace_back(m_reader, nextLine);
154 17 : continue;
155 : }
156 1 : util::skipPureComments(m_reader, nextLine, true);
157 1 : continue;
158 : }
159 : // "auto [label, value] = util::parseLdrStart(nextLine.value());" cannot
160 : // be used as lambdas (below) cannot capture these variables
161 : // see:
162 : // https://stackoverflow.com/questions/46114214/lambda-implicit-capture-fails-with-variable-declared-from-structured-binding
163 1295 : std::string label;
164 1295 : std::string value;
165 1288 : std::tie(label, value) = util::parseLdrStart(nextLine.value());
166 1288 : if (label.empty())
167 : {
168 : // LDR start is an LDR comment "##="
169 2 : nextLine = parseStringValue(value, m_reader);
170 2 : m_ldrComments.push_back(value);
171 : }
172 1286 : else if ("END" == label)
173 : {
174 : // end of block
175 135 : break;
176 : }
177 1151 : else if (s_blockStartLabel == label)
178 : {
179 : // nested block
180 204 : auto block = Block(value, m_reader, nextLine);
181 102 : m_blocks.push_back(std::move(block));
182 : }
183 1049 : else if ("XYDATA" == label)
184 : {
185 25 : addLdr<XyData>(title, "XYDATA", m_xyData, [&]() {
186 18 : return XyData(label, value, m_ldrs, m_reader, nextLine);
187 : });
188 : }
189 1030 : else if ("RADATA" == label)
190 : {
191 18 : addLdr<RaData>(title, "RADATA", m_raData, [&]() {
192 14 : return RaData(label, value, m_ldrs, m_reader, nextLine);
193 : });
194 : }
195 1015 : else if ("XYPOINTS" == label)
196 : {
197 18 : addLdr<XyPoints>(title, "XYPOINTS", m_xyPoints, [&]() {
198 14 : return XyPoints(label, value, m_ldrs, m_reader, nextLine);
199 : });
200 : }
201 1000 : else if ("PEAKTABLE" == label)
202 : {
203 28 : addLdr<PeakTable>(title, "PEAKTABLE", m_peakTable,
204 24 : [&]() { return PeakTable(label, value, m_reader, nextLine); });
205 : }
206 975 : else if ("PEAKASSIGNMENTS" == label)
207 : {
208 31 : addLdr<PeakAssignments>(
209 14 : title, "PEAKASSIGNMENTS", m_peakAssignments, [&]() {
210 13 : return PeakAssignments(label, value, m_reader, nextLine);
211 : });
212 : }
213 961 : else if ("NTUPLES" == label)
214 : {
215 31 : addLdr<NTuples>(title, "NTUPLES", m_nTuples, [&]() {
216 31 : return NTuples(label, value, m_ldrs, m_reader, nextLine);
217 : });
218 : }
219 930 : else if ("AUDITTRAIL" == label)
220 : {
221 12 : addLdr<AuditTrail>(title, "AUDITTRAIL", m_auditTrail,
222 12 : [&]() { return AuditTrail(label, value, m_reader, nextLine); });
223 : }
224 918 : else if ("$RELAX" == label)
225 : {
226 : // RELAX section start
227 : auto relaxSection
228 42 : = BrukerRelaxSection(label, value, m_reader, nextLine);
229 : // only add non blank sections
230 : // section may be blank if ##$RELAX= line is immediately followed by
231 : // $$ Bruker specific parameters
232 21 : if (!relaxSection.getName().empty()
233 21 : || !relaxSection.getContent().empty())
234 : {
235 14 : m_brukerRelaxSections.push_back(std::move(relaxSection));
236 : }
237 : }
238 : else
239 : {
240 : // LDR is a regular LDR
241 897 : nextLine = parseStringValue(value, m_reader);
242 : // duplicate?
243 1794 : std::optional<StringLdr> existingLdr = getLdr(label);
244 897 : if (existingLdr)
245 : {
246 : // reference implementation seems to overwrite LDR with
247 : // duplicate, but spec (JCAMP-DX IR 3.2) says
248 : // a duplicate LDR is illegal in a block
249 : // => accept if content is identical
250 2 : if (existingLdr.has_value()
251 2 : && existingLdr.value().getValue() != value)
252 : {
253 : throw BlockParseException(
254 1 : "Multiple non-identical values found for \"" + label
255 2 : + std::string{"\" in block: \"" + title + "\""});
256 : }
257 : }
258 896 : m_ldrs.emplace_back(label, value);
259 : }
260 : }
261 :
262 408 : if (!nextLine.has_value()
263 136 : || "END" != util::parseLdrStart(nextLine.value()).first)
264 : {
265 1 : throw BlockParseException("No", "END", title);
266 : }
267 : // make nextline the one following the ##END= LDR
268 372 : nextLine = m_reader.eof() ? std::nullopt
269 372 : : std::optional<std::string>{m_reader.readLine()};
270 135 : }
271 : // NOLINTEND(readability-function-cognitive-complexity)
|