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/api/JdxConverter.hpp"
19 : #include "api/Point2D.hpp"
20 : #include "jdx/JdxParser.hpp"
21 : #include "util/StringUtils.hpp"
22 :
23 : #include <fstream>
24 : #include <iostream>
25 : #include <sstream>
26 : #include <stdexcept>
27 : #include <string>
28 :
29 : #ifdef __EMSCRIPTEN__
30 : #include <emscripten/bind.h>
31 : #endif
32 :
33 22 : libjdx::jdx::api::JdxConverter::JdxConverter(const std::string& path)
34 : : m_rootBlock{std::make_unique<libjdx::jdx::Block>(
35 22 : libjdx::jdx::JdxParser::parse(path))}
36 : {
37 18 : }
38 :
39 35 : libjdx::api::Node libjdx::jdx::api::JdxConverter::read(const std::string& path)
40 : {
41 : // std::cout << "C++: JdxConverter.read(): " << path << '\n';
42 70 : std::vector<size_t> nodeIndices = convertPathToNodeIndices(path);
43 35 : auto node = retrieveNode(nodeIndices);
44 64 : return node;
45 : }
46 :
47 35 : std::vector<size_t> libjdx::jdx::api::JdxConverter::convertPathToNodeIndices(
48 : const std::string& path)
49 : {
50 105 : auto pathSegments = util::split(path, "/", true);
51 35 : if (!pathSegments.at(0).empty() || pathSegments.size() < 2)
52 : {
53 0 : throw std::invalid_argument("Illegal path for reading node: " + path);
54 : }
55 :
56 35 : std::vector<size_t> nodeIndices{};
57 35 : if (pathSegments.size() != 2 || !pathSegments.at(1).empty())
58 : {
59 : // path not only "/"
60 49 : for (size_t i = 1; i < pathSegments.size(); i++)
61 : {
62 30 : auto pathIndex = std::stoul(pathSegments.at(i));
63 30 : nodeIndices.push_back(pathIndex);
64 : }
65 : }
66 :
67 70 : return nodeIndices;
68 : }
69 :
70 : // NOLINTBEGIN(readability-function-cognitive-complexity)
71 35 : libjdx::api::Node libjdx::jdx::api::JdxConverter::retrieveNode(
72 : const std::vector<size_t>& nodeIndices)
73 : {
74 2 : auto raiseIllegalPathError = [](size_t nodeIndex, const Block* block) {
75 : throw std::invalid_argument(
76 : "Illegal path for reading node. Block: "
77 4 : + block->getLdr("TITLE").value().getLabel()
78 6 : + ", child index: " + std::to_string(nodeIndex));
79 : };
80 :
81 35 : const Block* block = &(*m_rootBlock);
82 35 : size_t iterationIndex = 0;
83 48 : for (const auto nodeIndex : nodeIndices)
84 : {
85 25 : const size_t brukerRelaxStartIndex = 0;
86 : const size_t brukerRelaxEndIndexExclusive
87 25 : = brukerRelaxStartIndex + block->getBrukerRelaxSections().size();
88 25 : const size_t brukerParamsStartIndex = brukerRelaxEndIndexExclusive;
89 : const size_t brukerParamsEndIndexExclusive
90 : = brukerParamsStartIndex
91 25 : + block->getBrukerSpecificParameters().size();
92 25 : const size_t nTuplesIndex = brukerParamsEndIndexExclusive;
93 : const size_t auditTrailIndex
94 25 : = block->getNTuples().has_value() ? nTuplesIndex + 1 : nTuplesIndex;
95 25 : const size_t childBlocksStartIndex = block->getAuditTrail().has_value()
96 25 : ? auditTrailIndex + 1
97 25 : : auditTrailIndex;
98 :
99 : // nodeIndex >= brukerRelaxStartIndex, always true
100 25 : if (nodeIndex < brukerRelaxEndIndexExclusive
101 25 : && !block->getBrukerRelaxSections().empty())
102 : {
103 : // Bruker ##$RELAX section
104 3 : if (iterationIndex < nodeIndices.size() - 1)
105 : {
106 : // not a leaf node
107 1 : raiseIllegalPathError(nodeIndex, block);
108 : }
109 2 : return mapBrukerRelaxSection(block->getBrukerRelaxSections().at(
110 11 : nodeIndex - brukerRelaxStartIndex));
111 : }
112 22 : if (nodeIndex >= brukerParamsStartIndex
113 22 : && nodeIndex < brukerParamsEndIndexExclusive
114 44 : && !block->getBrukerSpecificParameters().empty())
115 : {
116 : // $$ Bruker specific parameters section
117 3 : if (iterationIndex < nodeIndices.size() - 1)
118 : {
119 : // not a leaf node
120 1 : raiseIllegalPathError(nodeIndex, block);
121 : }
122 : return mapBrukerSpecificParameters(
123 2 : block->getBrukerSpecificParameters().at(
124 2 : nodeIndex - brukerParamsStartIndex));
125 : }
126 19 : if (nodeIndex == nTuplesIndex && block->getNTuples().has_value())
127 : {
128 : // consider NTUPLES LDR as child node
129 : // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
130 4 : auto startIt = nodeIndices.cbegin() + iterationIndex;
131 : auto nTuplesIndices
132 8 : = std::vector<size_t>{++startIt, nodeIndices.cend()};
133 8 : return mapNTuples(block->getNTuples().value(), nTuplesIndices,
134 4 : isPeakData(*block));
135 : }
136 15 : if (nodeIndex == auditTrailIndex && block->getAuditTrail().has_value())
137 : {
138 : // consider AUDIT TRAIL LDR as child node
139 1 : if (iterationIndex < nodeIndices.size() - 1)
140 : {
141 : // not a leaf node
142 0 : raiseIllegalPathError(nodeIndex, block);
143 : }
144 1 : return mapAuditTrail(block->getAuditTrail().value());
145 : }
146 : const auto& childBlock
147 14 : = block->getBlocks().at(nodeIndex - childBlocksStartIndex);
148 13 : block = &childBlock;
149 13 : ++iterationIndex;
150 : }
151 23 : return mapBlock(*block, isPeakData(*block));
152 : }
153 : // NOLINTEND(readability-function-cognitive-complexity)
154 :
155 23 : libjdx::api::Node libjdx::jdx::api::JdxConverter::mapBlock(
156 : const Block& block, bool isPeakData)
157 : {
158 69 : auto name = block.getLdr("TITLE").value().getValue();
159 :
160 46 : std::vector<libjdx::api::KeyValueParam> parameters{};
161 160 : for (auto const& ldr : block.getLdrs())
162 : {
163 137 : parameters.push_back({ldr.getLabel(), ldr.getValue()});
164 : }
165 :
166 46 : auto data = mapData(block);
167 :
168 46 : auto peakTable = libjdx::api::Table{};
169 23 : if (block.getPeakAssignments().has_value())
170 : {
171 1 : peakTable = mapPeakAssignments(block.getPeakAssignments().value());
172 : }
173 22 : else if (block.getPeakTable().has_value())
174 : {
175 2 : peakTable = mapPeakTable(block.getPeakTable().value());
176 :
177 2 : if (isPeakData && data.empty())
178 : {
179 : // for MS, map peak table as data if no other data is present
180 1 : data = mapPeakTableAsData(block.getPeakTable().value());
181 : }
182 : }
183 :
184 46 : std::vector<std::string> childNodeNames{};
185 35 : for (auto const& brukerRelaxSection : block.getBrukerRelaxSections())
186 : {
187 24 : auto childNodeName = brukerRelaxSection.getName();
188 12 : childNodeNames.push_back(childNodeName);
189 : }
190 12 : for (auto const& brukerSpecificSection :
191 47 : block.getBrukerSpecificParameters())
192 : {
193 24 : auto childNodeName = brukerSpecificSection.getName();
194 12 : childNodeNames.push_back(childNodeName);
195 : }
196 23 : if (block.getNTuples().has_value())
197 : {
198 : // consider NTUPLES LDR as child node
199 14 : auto nTuplesName = block.getNTuples().value().getDataForm();
200 7 : childNodeNames.push_back(nTuplesName);
201 : }
202 23 : if (block.getAuditTrail().has_value())
203 : {
204 : // consider AUDIT TRAIL LDR as child node
205 0 : auto auditTrailLabel = block.getAuditTrail().value().getLabel();
206 0 : childNodeNames.push_back(auditTrailLabel);
207 : }
208 113 : for (auto const& childBlock : block.getBlocks())
209 : {
210 270 : auto childNodeName = childBlock.getLdr("TITLE").value().getValue();
211 90 : childNodeNames.push_back(childNodeName);
212 : }
213 :
214 46 : auto metadata = mapMetadata(block);
215 :
216 46 : return {name, parameters, data, metadata, peakTable, childNodeNames};
217 : }
218 :
219 50 : bool libjdx::jdx::api::JdxConverter::isPeakData(const Block& block)
220 : {
221 : auto dataType
222 150 : = block.getLdr("DATATYPE").value_or(StringLdr{"", ""}).getValue();
223 50 : util::toLower(dataType);
224 100 : return dataType == "mass spectrum";
225 : }
226 :
227 2 : libjdx::api::Node libjdx::jdx::api::JdxConverter::mapBrukerRelaxSection(
228 : const BrukerRelaxSection& section)
229 : {
230 4 : std::vector<libjdx::api::KeyValueParam> parameters{};
231 2 : parameters.push_back({"", section.getContent()});
232 : return {
233 : section.getName(),
234 : parameters,
235 : std::vector<libjdx::api::Point2D>{},
236 : std::map<std::string, std::string>{},
237 : libjdx::api::Table{},
238 : std::vector<std::string>{},
239 4 : };
240 : }
241 :
242 2 : libjdx::api::Node libjdx::jdx::api::JdxConverter::mapBrukerSpecificParameters(
243 : const BrukerSpecificParameters& section)
244 : {
245 4 : std::vector<libjdx::api::KeyValueParam> parameters{};
246 9 : for (const auto& ldr : section.getContent())
247 : {
248 7 : parameters.push_back({ldr.getLabel(), ldr.getValue()});
249 : }
250 : return {
251 : section.getName(),
252 : parameters,
253 : std::vector<libjdx::api::Point2D>{},
254 : std::map<std::string, std::string>{},
255 : libjdx::api::Table{},
256 : std::vector<std::string>{},
257 4 : };
258 : }
259 :
260 4 : libjdx::api::Node libjdx::jdx::api::JdxConverter::mapNTuples(
261 : const NTuples& nTuples, const std::vector<size_t>& nodeIndices,
262 : bool isPeakData)
263 : {
264 4 : if (nodeIndices.empty())
265 : {
266 : // map NTUPLES record
267 2 : auto name = nTuples.getDataForm();
268 :
269 2 : std::vector<libjdx::api::KeyValueParam> parameters{};
270 14 : for (const auto& ldr : nTuples.getLdrs())
271 : {
272 13 : parameters.push_back({ldr.getLabel(), ldr.getValue()});
273 : }
274 :
275 2 : std::vector<libjdx::api::Point2D> data{};
276 :
277 2 : auto peakTable = libjdx::api::Table{};
278 :
279 2 : std::vector<std::string> childNodeNames{};
280 3 : for (size_t pageIndex = 0; pageIndex < nTuples.getNumPages();
281 : ++pageIndex)
282 : {
283 2 : const auto& page = nTuples.getPage(pageIndex);
284 2 : childNodeNames.push_back(mapNTuplesPageName(page));
285 : }
286 :
287 2 : auto metadata = std::map<std::string, std::string>{};
288 :
289 1 : return {name, parameters, data, metadata, peakTable, childNodeNames};
290 : }
291 :
292 3 : if (nodeIndices.size() > 1 || nodeIndices.at(0) > nTuples.getNumPages())
293 : {
294 0 : std::string indices{};
295 0 : for (auto nodeIndex : nodeIndices)
296 : {
297 0 : indices += " " + std::to_string(nodeIndex);
298 : }
299 : throw std::invalid_argument(
300 0 : "Illegal indices for reading NTUPLES node: " + indices);
301 : }
302 :
303 : // map PAGE
304 3 : return mapNTuplesPage(nTuples.getPage(nodeIndices.at(0)), isPeakData);
305 : }
306 :
307 3 : libjdx::api::Node libjdx::jdx::api::JdxConverter::mapNTuplesPage(
308 : const Page& page, bool isPeakData)
309 : {
310 6 : auto name = mapNTuplesPageName(page);
311 :
312 6 : std::vector<libjdx::api::KeyValueParam> parameters{};
313 4 : for (auto&& ldr : page.getPageLdrs())
314 : {
315 1 : parameters.push_back({ldr.getLabel(), ldr.getValue()});
316 : }
317 :
318 6 : std::vector<libjdx::api::Point2D> data{};
319 6 : libjdx::api::Table peakTable{};
320 3 : if (page.getDataTable())
321 : {
322 6 : auto dataTable = page.getDataTable().value();
323 3 : if (dataTable.getPlotDescriptor().has_value())
324 : {
325 6 : parameters.push_back(
326 3 : {"Plot Descriptor", dataTable.getPlotDescriptor().value()});
327 : }
328 3 : data = mapXyData(dataTable.getData());
329 :
330 3 : if (isPeakData)
331 : {
332 1 : peakTable = mapDataAsPeakTable(dataTable.getData());
333 : }
334 : }
335 :
336 6 : std::vector<std::string> childNodeNames{};
337 :
338 6 : auto metadata = mapMetadata(page, isPeakData);
339 :
340 6 : return {name, parameters, data, metadata, peakTable, childNodeNames};
341 : }
342 :
343 5 : std::string libjdx::jdx::api::JdxConverter::mapNTuplesPageName(const Page& page)
344 : {
345 5 : auto name = page.getPageVariables();
346 5 : if (page.getDataTable())
347 : {
348 5 : const auto& dataTable = page.getDataTable().value();
349 5 : name += " - " + dataTable.getAttributes().yAttributes.varName;
350 : }
351 5 : return name;
352 : }
353 :
354 23 : std::vector<libjdx::api::Point2D> libjdx::jdx::api::JdxConverter::mapData(
355 : const Block& block)
356 : {
357 23 : std::optional<std::vector<std::pair<double, double>>> rawData{};
358 23 : if (block.getXyData())
359 : {
360 : // TODO: fix const definitions that prevent rawData =
361 : // block.getXyData().value().getData();
362 1 : auto data = block.getXyData().value();
363 1 : rawData = data.getData();
364 : }
365 23 : if (block.getRaData())
366 : {
367 1 : auto data = block.getRaData().value();
368 1 : rawData = data.getData();
369 : }
370 23 : if (block.getXyPoints())
371 : {
372 1 : auto data = block.getXyPoints().value();
373 1 : rawData = data.getData();
374 : }
375 :
376 3 : return rawData ? mapXyData(rawData.value())
377 49 : : std::vector<libjdx::api::Point2D>{};
378 : }
379 :
380 6 : std::vector<libjdx::api::Point2D> libjdx::jdx::api::JdxConverter::mapXyData(
381 : const std::vector<std::pair<double, double>>& xyData)
382 : {
383 6 : std::vector<libjdx::api::Point2D> output{};
384 6 : output.reserve(xyData.size());
385 27 : for (const auto& pair : xyData)
386 : {
387 21 : output.push_back({pair.first, pair.second});
388 : }
389 :
390 6 : return output;
391 : }
392 :
393 23 : std::map<std::string, std::string> libjdx::jdx::api::JdxConverter::mapMetadata(
394 : const Block& block)
395 : {
396 23 : std::map<std::string, std::string> metadata{};
397 :
398 69 : if (auto xUnits = block.getLdr("XUNITS"))
399 : {
400 5 : metadata.emplace("x.unit", xUnits.value().getValue());
401 : }
402 37 : else if (auto rUnits = block.getLdr("RUNITS"))
403 : {
404 1 : metadata.emplace("x.unit", rUnits.value().getValue());
405 : }
406 :
407 23 : if (metadata.count("x.unit") > 0)
408 : {
409 18 : auto xUnit = metadata.at("x.unit");
410 6 : util::toLower(xUnit);
411 9 : if (xUnit == "1/cm" || xUnit == "1 / cm" || xUnit == "cm-1"
412 9 : || xUnit == "cm^-1")
413 : {
414 3 : metadata.emplace("x.label", "Wavenumber");
415 :
416 3 : if (block.getLdr("DATATYPE"))
417 : {
418 9 : auto dataType = block.getLdr("DATATYPE").value().getValue();
419 3 : util::toLower(dataType);
420 :
421 3 : if (dataType.find("infrared") != std::string::npos
422 3 : || dataType.find("raman") != std::string::npos)
423 : {
424 3 : metadata.emplace("x.reverse", "true");
425 : }
426 : }
427 : }
428 6 : else if (xUnit == "nanometers" || xUnit == "micrometers"
429 6 : || xUnit == "nm" || xUnit == "um")
430 : {
431 1 : metadata.emplace("x.label", "Wavelength");
432 : }
433 : }
434 :
435 69 : if (auto yUnits = block.getLdr("YUNITS"))
436 : {
437 5 : metadata.emplace("y.unit", yUnits.value().getValue());
438 : }
439 37 : else if (auto aUnits = block.getLdr("AUNITS"))
440 : {
441 1 : metadata.emplace("y.unit", aUnits.value().getValue());
442 : }
443 :
444 23 : if (isPeakData(block))
445 : {
446 1 : metadata.emplace("plot.style", "sticks");
447 : }
448 :
449 23 : return metadata;
450 : }
451 :
452 3 : std::map<std::string, std::string> libjdx::jdx::api::JdxConverter::mapMetadata(
453 : const Page& page, bool isPeakData)
454 : {
455 3 : std::map<std::string, std::string> metadata{};
456 :
457 3 : if (page.getDataTable().has_value())
458 : {
459 : const auto& pageAttributes
460 3 : = page.getDataTable().value().getAttributes();
461 3 : if (pageAttributes.xAttributes.units.has_value())
462 : {
463 : metadata.emplace(
464 3 : "x.unit", pageAttributes.xAttributes.units.value());
465 : }
466 3 : if (pageAttributes.yAttributes.units.has_value())
467 : {
468 : metadata.emplace(
469 3 : "y.unit", pageAttributes.yAttributes.units.value());
470 : }
471 3 : if (isPeakData)
472 : {
473 1 : metadata.emplace("plot.style", "sticks");
474 : }
475 : else
476 : {
477 : // don't use VAR_NAMEs for labels for MS
478 2 : metadata.emplace("x.label", pageAttributes.xAttributes.symbol);
479 2 : metadata.emplace("y.label", pageAttributes.yAttributes.symbol);
480 : }
481 : }
482 :
483 3 : return metadata;
484 : }
485 :
486 2 : libjdx::api::Table libjdx::jdx::api::JdxConverter::mapPeakTable(
487 : const libjdx::jdx::PeakTable& peakTable)
488 : {
489 4 : auto peakTableData = peakTable.getData();
490 2 : const auto has_w = std::any_of(peakTableData.cbegin(), peakTableData.cend(),
491 8 : [](const Peak& peak) { return peak.w.has_value(); });
492 2 : const auto has_m = std::any_of(peakTableData.cbegin(), peakTableData.cend(),
493 8 : [](const Peak& peak) { return peak.m.has_value(); });
494 :
495 2 : auto resultPeakTable = libjdx::api::Table{};
496 2 : resultPeakTable.columnNames.emplace_back("x", "Peak Position");
497 2 : resultPeakTable.columnNames.emplace_back("y", "Intensity");
498 2 : if (has_w)
499 : {
500 0 : resultPeakTable.columnNames.emplace_back("w", "Width");
501 : }
502 2 : if (has_m)
503 : {
504 0 : resultPeakTable.columnNames.emplace_back("m", "Multiplicity");
505 : }
506 :
507 10 : for (const auto& peak : peakTable.getData())
508 : {
509 16 : auto resultPeak = std::map<std::string, std::string>{};
510 8 : resultPeak.emplace("x", std::to_string(peak.x));
511 8 : resultPeak.emplace("y", std::to_string(peak.y));
512 8 : if (peak.w.has_value())
513 : {
514 0 : resultPeak.emplace("w", std::to_string(peak.w.value()));
515 : }
516 8 : if (peak.m.has_value())
517 : {
518 0 : resultPeak.emplace("m", peak.m.value());
519 : }
520 8 : resultPeakTable.rows.push_back(resultPeak);
521 : }
522 4 : return resultPeakTable;
523 : }
524 :
525 : std::vector<libjdx::api::Point2D>
526 1 : libjdx::jdx::api::JdxConverter::mapPeakTableAsData(
527 : const libjdx::jdx::PeakTable& peakTable)
528 : {
529 1 : std::vector<libjdx::api::Point2D> data{};
530 :
531 5 : for (const auto& peak : peakTable.getData())
532 : {
533 4 : data.push_back({peak.x, peak.y});
534 : }
535 :
536 1 : return data;
537 : }
538 :
539 1 : libjdx::api::Table libjdx::jdx::api::JdxConverter::mapDataAsPeakTable(
540 : const std::vector<std::pair<double, double>>& xyData)
541 : {
542 1 : auto resultPeakTable = libjdx::api::Table{};
543 :
544 1 : resultPeakTable.columnNames.emplace_back("x", "Peak Position");
545 1 : resultPeakTable.columnNames.emplace_back("y", "Intensity");
546 :
547 5 : for (const auto& xyPair : xyData)
548 : {
549 8 : auto resultPeak = std::map<std::string, std::string>{};
550 4 : resultPeak.emplace("x", std::to_string(xyPair.first));
551 4 : resultPeak.emplace("y", std::to_string(xyPair.second));
552 4 : resultPeakTable.rows.push_back(resultPeak);
553 : }
554 :
555 1 : return resultPeakTable;
556 : }
557 :
558 1 : libjdx::api::Table libjdx::jdx::api::JdxConverter::mapPeakAssignments(
559 : const libjdx::jdx::PeakAssignments& peakAssignments)
560 : {
561 2 : auto peakAssignmentsData = peakAssignments.getData();
562 1 : const auto has_y = std::any_of(peakAssignmentsData.cbegin(),
563 1 : peakAssignmentsData.cend(), [](const PeakAssignment& peakAssignment) {
564 1 : return peakAssignment.y.has_value();
565 : });
566 1 : const auto has_m = std::any_of(peakAssignmentsData.cbegin(),
567 1 : peakAssignmentsData.cend(), [](const PeakAssignment& peakAssignment) {
568 1 : return peakAssignment.m.has_value();
569 : });
570 1 : const auto has_w = std::any_of(peakAssignmentsData.cbegin(),
571 4 : peakAssignmentsData.cend(), [](const PeakAssignment& peakAssignment) {
572 4 : return peakAssignment.w.has_value();
573 : });
574 :
575 1 : auto resultPeakTable = libjdx::api::Table{};
576 1 : resultPeakTable.columnNames.emplace_back("x", "Peak Position");
577 1 : if (has_y)
578 : {
579 1 : resultPeakTable.columnNames.emplace_back("y", "Intensity");
580 : }
581 1 : if (has_m)
582 : {
583 1 : resultPeakTable.columnNames.emplace_back("m", "Multiplicity");
584 : }
585 1 : if (has_w)
586 : {
587 0 : resultPeakTable.columnNames.emplace_back("w", "Width");
588 : }
589 1 : resultPeakTable.columnNames.emplace_back("a", "Assignment");
590 :
591 5 : for (const auto& peakAssignment : peakAssignments.getData())
592 : {
593 8 : auto resultPeak = std::map<std::string, std::string>{};
594 4 : resultPeak.emplace("x", std::to_string(peakAssignment.x));
595 4 : if (peakAssignment.y.has_value())
596 : {
597 4 : resultPeak.emplace("y", std::to_string(peakAssignment.y.value()));
598 : }
599 4 : if (peakAssignment.w.has_value())
600 : {
601 0 : resultPeak.emplace("w", std::to_string(peakAssignment.w.value()));
602 : }
603 4 : if (peakAssignment.m.has_value())
604 : {
605 4 : resultPeak.emplace("m", peakAssignment.m.value());
606 : }
607 4 : resultPeak.emplace("a", peakAssignment.a);
608 4 : resultPeakTable.rows.push_back(resultPeak);
609 : }
610 2 : return resultPeakTable;
611 : }
612 :
613 1 : libjdx::api::Node libjdx::jdx::api::JdxConverter::mapAuditTrail(
614 : const libjdx::jdx::AuditTrail& auditTrail)
615 : {
616 2 : auto auditEntries = auditTrail.getData();
617 :
618 1 : const auto has_process = std::any_of(auditEntries.cbegin(),
619 : auditEntries.cend(),
620 1 : [](const AuditTrailEntry& entry) { return entry.process.has_value(); });
621 1 : const auto has_version = std::any_of(auditEntries.cbegin(),
622 : auditEntries.cend(),
623 1 : [](const AuditTrailEntry& entry) { return entry.version.has_value(); });
624 :
625 2 : libjdx::api::Table table{};
626 : // columns
627 1 : table.columnNames.emplace_back("number", "NUMBER");
628 1 : table.columnNames.emplace_back("when", "WHEN");
629 1 : table.columnNames.emplace_back("who", "WHO");
630 1 : table.columnNames.emplace_back("where", "WHERE");
631 1 : if (has_process)
632 : {
633 1 : table.columnNames.emplace_back("process", "PROCESS");
634 : }
635 1 : if (has_version)
636 : {
637 1 : table.columnNames.emplace_back("version", "VERSION");
638 : }
639 1 : table.columnNames.emplace_back("what", "WHAT");
640 :
641 : // rows
642 4 : for (const auto& entry : auditEntries)
643 : {
644 : std::map<std::string, std::string> row{
645 3 : {"number", std::to_string(entry.number)},
646 3 : {"when", entry.when},
647 3 : {"who", entry.who},
648 3 : {"where", entry.where},
649 3 : {"what", entry.what},
650 27 : };
651 3 : if (has_process && entry.process.has_value())
652 : {
653 3 : row.emplace("process", entry.process.value());
654 : }
655 3 : if (has_version && entry.version.has_value())
656 : {
657 3 : row.emplace("version", entry.version.value());
658 : }
659 :
660 3 : table.rows.push_back(row);
661 : }
662 :
663 1 : const auto& auditTrailLabel = auditTrail.getLabel();
664 :
665 : return {
666 : auditTrailLabel,
667 : std::vector<libjdx::api::KeyValueParam>{},
668 : std::vector<libjdx::api::Point2D>{},
669 : std::map<std::string, std::string>{},
670 : table,
671 : std::vector<std::string>{},
672 2 : };
673 : }
674 :
675 : #ifdef __EMSCRIPTEN__
676 : EMSCRIPTEN_BINDINGS(JdxConverter)
677 : {
678 : using namespace libjdx::api;
679 : using namespace libjdx::jdx::api;
680 : using namespace emscripten;
681 : class_<JdxConverter, base<Converter>>("JdxConverter")
682 : .smart_ptr_constructor(
683 : "JdxConverter", &std::make_shared<JdxConverter, const std::string&>)
684 : .function("read", &JdxConverter::read);
685 : }
686 : #endif
|