INSTINCT Code Coverage Report


Directory: src/
File: Nodes/DataProvider/CSV/CsvFile.cpp
Date: 2025-11-25 23:34:18
Exec Total Coverage
Lines: 12 127 9.4%
Functions: 4 15 26.7%
Branches: 10 244 4.1%

Line Branch Exec Source
1 // This file is part of INSTINCT, the INS Toolkit for Integrated
2 // Navigation Concepts and Training by the Institute of Navigation of
3 // the University of Stuttgart, Germany.
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
8
9 #include "CsvFile.hpp"
10 #include <algorithm>
11 #include <string>
12 #include <vector>
13
14 #include "util/Logger.hpp"
15 #include "util/StringUtil.hpp"
16
17 #include "internal/FlowManager.hpp"
18 #include "internal/gui/widgets/imgui_ex.hpp"
19
20 114 NAV::CsvFile::CsvFile()
21
3/6
✓ Branch 1 taken 114 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 114 times.
✗ Branch 5 not taken.
✓ Branch 8 taken 114 times.
✗ Branch 9 not taken.
114 : Node(typeStatic())
22 {
23 LOG_TRACE("{}: called", name);
24
25 114 _hasConfig = true;
26 114 _guiConfigDefaultWindowSize = { 530, 271 };
27
28
5/10
✓ Branch 2 taken 114 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 114 times.
✗ Branch 7 not taken.
✓ Branch 10 taken 114 times.
✗ Branch 11 not taken.
✓ Branch 14 taken 114 times.
✓ Branch 15 taken 114 times.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
456 CreateOutputPin(CsvData::type().c_str(), Pin::Type::Object, { CsvData::type() }, &_data);
29 228 }
30
31 228 NAV::CsvFile::~CsvFile()
32 {
33 LOG_TRACE("{}: called", nameId());
34 228 }
35
36 228 std::string NAV::CsvFile::typeStatic()
37 {
38
1/2
✓ Branch 1 taken 228 times.
✗ Branch 2 not taken.
456 return "CsvFile";
39 }
40
41 std::string NAV::CsvFile::type() const
42 {
43 return typeStatic();
44 }
45
46 114 std::string NAV::CsvFile::category()
47 {
48
1/2
✓ Branch 1 taken 114 times.
✗ Branch 2 not taken.
228 return "Data Provider";
49 }
50
51 void NAV::CsvFile::guiConfig()
52 {
53 if (auto res = FileReader::guiConfig(".csv,.*", { ".csv" }, size_t(id), nameId()))
54 {
55 LOG_DEBUG("{}: Path changed to {}", nameId(), _path);
56 flow::ApplyChanges();
57 if (res == FileReader::PATH_CHANGED)
58 {
59 doReinitialize();
60 }
61 else
62 {
63 doDeinitialize();
64 }
65 }
66
67 struct TextFilters
68 {
69 // Cuts off characters if the length exceeds 1
70 static int FilterSingleCharacter(ImGuiInputTextCallbackData* data)
71 {
72 while (data->BufTextLen > 1)
73 {
74 data->BufDirty = true;
75 data->DeleteChars(1, 1);
76 }
77 return 0;
78 }
79 };
80
81 std::string tmpStr(1, _delimiter);
82 if (ImGui::InputText(fmt::format("Delimiter character##{}", size_t(id)).c_str(), &tmpStr, ImGuiInputTextFlags_CallbackEdit, TextFilters::FilterSingleCharacter))
83 {
84 _delimiter = tmpStr.empty() ? '\0' : tmpStr.at(0);
85 LOG_DEBUG("{}: Delimiter character changed to {}", nameId(), _delimiter);
86 flow::ApplyChanges();
87 if (_delimiter)
88 {
89 doInitialize();
90 }
91 }
92
93 tmpStr = std::string(1, _comment);
94 if (ImGui::InputText(fmt::format("Comment character##{}", size_t(id)).c_str(), &tmpStr, ImGuiInputTextFlags_CallbackEdit, TextFilters::FilterSingleCharacter))
95 {
96 _comment = tmpStr.empty() ? '\0' : tmpStr.at(0);
97 LOG_DEBUG("{}: Comment character changed to {}", nameId(), _comment);
98 flow::ApplyChanges();
99 doInitialize();
100 }
101
102 if (ImGui::InputIntL(fmt::format("Skip lines##{}", size_t(id)).c_str(), &_skipLines, 0, std::numeric_limits<int>::max()))
103 {
104 LOG_DEBUG("{}: Skip lines changed to {}", nameId(), _skipLines);
105 flow::ApplyChanges();
106 doInitialize();
107 }
108
109 if (ImGui::Checkbox(fmt::format("Header line##{}", size_t(id)).c_str(), &_hasHeaderLine))
110 {
111 LOG_DEBUG("{}: HasHeaderLine changed to {}", nameId(), _hasHeaderLine);
112 flow::ApplyChanges();
113 doInitialize();
114 }
115
116 ImGui::Separator();
117
118 ImGui::Text("Amount of data lines in file: %zu", _data.lines.size());
119
120 // Header info
121 if (ImGui::BeginTable(fmt::format("##CSVHeaders ({})", size_t(id)).c_str(), 2,
122 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg))
123 {
124 ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed);
125 ImGui::TableSetupColumn("Description", ImGuiTableColumnFlags_WidthFixed);
126 ImGui::TableHeadersRow();
127
128 for (size_t i = 0; i < _data.description.size(); i++)
129 {
130 ImGui::TableNextRow();
131 ImGui::TableNextColumn();
132 ImGui::Text("%zu", i);
133 ImGui::TableNextColumn();
134 ImGui::TextUnformatted(_data.description[i].c_str());
135 }
136
137 ImGui::EndTable();
138 }
139 }
140
141 [[nodiscard]] json NAV::CsvFile::save() const
142 {
143 LOG_TRACE("{}: called", nameId());
144
145 json j;
146
147 j["FileReader"] = FileReader::save();
148 j["delimiter"] = _delimiter;
149 j["comment"] = _comment;
150 j["skipLines"] = _skipLines;
151 j["hasHeaderLine"] = _hasHeaderLine;
152
153 return j;
154 }
155
156 void NAV::CsvFile::restore(json const& j)
157 {
158 LOG_TRACE("{}: called", nameId());
159
160 if (j.contains("FileReader"))
161 {
162 FileReader::restore(j.at("FileReader"));
163 }
164 if (j.contains("delimiter"))
165 {
166 j.at("delimiter").get_to(_delimiter);
167 }
168 if (j.contains("comment"))
169 {
170 j.at("comment").get_to(_comment);
171 }
172 if (j.contains("skipLines"))
173 {
174 j.at("skipLines").get_to(_skipLines);
175 }
176 if (j.contains("hasHeaderLine"))
177 {
178 j.at("hasHeaderLine").get_to(_hasHeaderLine);
179 }
180 }
181
182 bool NAV::CsvFile::initialize()
183 {
184 LOG_TRACE("{}: called", nameId());
185
186 _data.description.clear();
187 _data.lines.clear();
188
189 if (!FileReader::initialize())
190 {
191 return false;
192 }
193
194 std::string line;
195 std::vector<size_t> emptyCols{};
196 while (!eof())
197 {
198 getline(line);
199 if (line.empty() || line.at(0) == _comment) { continue; } // Skip empty and comment lines
200
201 auto splittedData = str::split(line, _delimiter);
202 if (!splittedData.empty()) { _data.lines.emplace_back(); }
203
204 for (size_t i = 0; i < splittedData.size(); i++)
205 {
206 const auto& cell = splittedData.at(i);
207
208 if (cell.empty() && std::ranges::find(emptyCols, i) == emptyCols.end())
209 {
210 emptyCols.push_back(i);
211 LOG_WARN("{}: Data missing in column: '{}' at: {} s and possibly afterwards, too.", nameId(), _data.description.at(i), splittedData.front());
212 }
213
214 CsvData::CsvElement value;
215 try
216 {
217 value = std::stod(cell);
218 }
219 catch (...)
220 {
221 value = cell;
222 }
223 _data.lines.back().push_back(value);
224 }
225 }
226
227 LOG_TRACE("{}: initialize() finished. Read {} columns over {} lines.", nameId(), _data.description.size(), _data.lines.size());
228
229 return true;
230 }
231
232 bool NAV::CsvFile::resetNode()
233 {
234 LOG_TRACE("{}: called", nameId());
235 return true;
236 }
237
238 void NAV::CsvFile::deinitialize()
239 {
240 LOG_TRACE("{}: called", nameId());
241
242 _data.description.clear();
243 _data.lines.clear();
244
245 FileReader::deinitialize();
246 }
247
248 NAV::FileReader::FileType NAV::CsvFile::determineFileType()
249 {
250 return FileReader::FileType::ASCII;
251 }
252
253 void NAV::CsvFile::readHeader()
254 {
255 for (int i = 0; i < _skipLines; i++) { ignore(std::numeric_limits<std::streamsize>::max(), '\n'); } // Skip lines at the start of the file
256
257 std::string line;
258 if (_hasHeaderLine)
259 {
260 getline(line);
261 _data.description = str::split(line, _delimiter);
262 for (auto& desc : _data.description)
263 {
264 desc.erase(std::ranges::find_if(desc, [](int ch) { return std::iscntrl(ch); }), desc.end());
265 }
266 }
267 }
268