INSTINCT Code Coverage Report


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