A Discrete-Event Network Simulator
API
Loading...
Searching...
No Matches
csv-reader.cc
Go to the documentation of this file.
1/*
2 * Copyright (c) 2019 Lawrence Livermore National Laboratory
3 *
4 * SPDX-License-Identifier: GPL-2.0-only
5 *
6 * Author: Mathew Bielejeski <bielejeski1@llnl.gov>
7 */
8
9#include "csv-reader.h"
10
11#include "ns3/log.h"
12
13#include <algorithm>
14#include <cctype>
15#include <fstream>
16#include <iterator>
17#include <limits>
18#include <sstream>
19#include <vector>
20
21/**
22 * \file
23 * \ingroup csvreader
24 *
25 * ns3::CsvReader implementation
26 */
27
28NS_LOG_COMPONENT_DEFINE("CsvReader");
29
30namespace
31{
32
33/**
34 * Convert a string into another type.
35 *
36 * Uses a stringstream to deserialize the value stored in \p input
37 * to a value of type T and writes the deserialized value to \p output.
38 *
39 * \tparam T Data type of output.
40 * \param input String containing serialized data.
41 * \param output Place to store deserialized value.
42 *
43 * \return \c true if deserialization was successful, \c false otherwise.
44 */
45template <typename T>
46bool
47GenericTransform(std::string input, T& output)
48{
49 NS_LOG_FUNCTION(input);
50
51 std::istringstream stream(input);
52
53 stream >> output;
54
55 return static_cast<bool>(stream);
56}
57
58} // unnamed namespace
59
60namespace ns3
61{
62
63CsvReader::CsvReader(const std::string& filepath, char delimiter /* =',' */)
64 : m_delimiter(delimiter),
65 m_rowsRead(0),
66 m_fileStream(filepath),
67 m_stream(&m_fileStream)
68{
69 NS_LOG_FUNCTION(this << filepath);
70}
71
72CsvReader::CsvReader(std::istream& stream, char delimiter /* =',' */)
73 : m_delimiter(delimiter),
74 m_rowsRead(0),
75 m_fileStream(),
76 m_stream(&stream)
77{
78 NS_LOG_FUNCTION(this);
79}
80
84
85std::size_t
87{
88 NS_LOG_FUNCTION(this);
89
90 return m_columns.size();
91}
92
93std::size_t
95{
96 NS_LOG_FUNCTION(this);
97
98 return m_rowsRead;
99}
100
101char
103{
104 NS_LOG_FUNCTION(this);
105
106 return m_delimiter;
107}
108
109bool
111{
112 NS_LOG_FUNCTION(this);
113
114 std::string line;
115
116 if (m_stream->eof())
117 {
118 NS_LOG_LOGIC("Reached end of stream");
119 return false;
120 }
121
122 NS_LOG_LOGIC("Reading line " << m_rowsRead + 1);
123
124 std::getline(*m_stream, line);
125
126 if (m_stream->fail())
127 {
128 NS_LOG_ERROR("Reading line " << m_rowsRead + 1 << " failed");
129
130 return false;
131 }
132
133 ++m_rowsRead;
134
135 ParseLine(line);
136
137 return true;
138}
139
140bool
142{
143 return m_blankRow;
144}
145
146bool
147CsvReader::GetValueAs(std::string input, double& value) const
148{
149 NS_LOG_FUNCTION(this << input);
150
151 return GenericTransform(std::move(input), value);
152}
153
154bool
155CsvReader::GetValueAs(std::string input, float& value) const
156{
157 NS_LOG_FUNCTION(this << input);
158
159 return GenericTransform(std::move(input), value);
160}
161
162bool
163CsvReader::GetValueAs(std::string input, signed char& value) const
164{
165 typedef signed char byte_type;
166
167 NS_LOG_FUNCTION(this << input);
168
169 std::istringstream tempStream(input);
170
171 int16_t tempOutput = 0;
172 tempStream >> tempOutput;
173
174 if (tempOutput >= std::numeric_limits<byte_type>::min() &&
175 tempOutput <= std::numeric_limits<byte_type>::max())
176 {
177 value = static_cast<byte_type>(tempOutput);
178 }
179
180 bool success = static_cast<bool>(tempStream);
181
182 NS_LOG_DEBUG("Input='" << input << "', output=" << tempOutput << ", result=" << success);
183
184 return success;
185}
186
187bool
188CsvReader::GetValueAs(std::string input, short& value) const
189{
190 NS_LOG_FUNCTION(this << input);
191
192 return GenericTransform(std::move(input), value);
193}
194
195bool
196CsvReader::GetValueAs(std::string input, int& value) const
197{
198 NS_LOG_FUNCTION(this << input);
199
200 return GenericTransform(std::move(input), value);
201}
202
203bool
204CsvReader::GetValueAs(std::string input, long& value) const
205{
206 NS_LOG_FUNCTION(this << input);
207
208 return GenericTransform(std::move(input), value);
209}
210
211bool
212CsvReader::GetValueAs(std::string input, long long& value) const
213{
214 NS_LOG_FUNCTION(this << input);
215
216 return GenericTransform(std::move(input), value);
217}
218
219bool
220CsvReader::GetValueAs(std::string input, std::string& value) const
221{
222 NS_LOG_FUNCTION(this << input);
223
224 value = input;
225
226 return true;
227}
228
229bool
230CsvReader::GetValueAs(std::string input, unsigned char& value) const
231{
232 typedef unsigned char byte_type;
233
234 NS_LOG_FUNCTION(this << input);
235
236 std::istringstream tempStream(input);
237
238 uint16_t tempOutput = 0;
239 tempStream >> tempOutput;
240
241 if (tempOutput >= std::numeric_limits<byte_type>::min() &&
242 tempOutput <= std::numeric_limits<byte_type>::max())
243 {
244 value = static_cast<byte_type>(tempOutput);
245 }
246
247 bool success = static_cast<bool>(tempStream);
248
249 NS_LOG_DEBUG("Input='" << input << "', output=" << tempOutput << ", result=" << success);
250
251 return success;
252}
253
254bool
255CsvReader::GetValueAs(std::string input, unsigned short& value) const
256{
257 NS_LOG_FUNCTION(this << input);
258
259 return GenericTransform(std::move(input), value);
260}
261
262bool
263CsvReader::GetValueAs(std::string input, unsigned int& value) const
264{
265 NS_LOG_FUNCTION(this << input);
266
267 return GenericTransform(std::move(input), value);
268}
269
270bool
271CsvReader::GetValueAs(std::string input, unsigned long& value) const
272{
273 NS_LOG_FUNCTION(this << input);
274
275 return GenericTransform(std::move(input), value);
276}
277
278bool
279CsvReader::GetValueAs(std::string input, unsigned long long& value) const
280{
281 NS_LOG_FUNCTION(this << input);
282
283 return GenericTransform(std::move(input), value);
284}
285
286bool
288{
289 NS_LOG_FUNCTION(this << c);
290
291 return c == m_delimiter;
292}
293
294void
295CsvReader::ParseLine(const std::string& line)
296{
297 NS_LOG_FUNCTION(this << line);
298
299 std::string value;
300 m_columns.clear();
301
302 auto start_col = line.begin();
303 auto end_col = line.end();
304
305 while (start_col != line.end())
306 {
307 std::tie(value, end_col) = ParseColumn(start_col, line.end());
308
309 NS_LOG_DEBUG("ParseColumn() returned: " << value);
310
311 m_columns.push_back(std::move(value));
312
313 if (end_col != line.end())
314 {
315 ++end_col;
316 }
317
318 start_col = end_col;
319 }
320 m_blankRow = (m_columns.size() == 1) && (m_columns[0].empty());
321 NS_LOG_LOGIC("blank row: " << m_blankRow);
322}
323
324std::tuple<std::string, std::string::const_iterator>
325CsvReader::ParseColumn(std::string::const_iterator begin, std::string::const_iterator end)
326{
327 NS_LOG_FUNCTION(this << std::string(begin, end));
328
329 enum class State
330 {
331 BEGIN,
332 END_QUOTE,
333 FIND_DELIMITER,
334 QUOTED_STRING,
335 UNQUOTED_STRING,
336 END
337 };
338
339 State state = State::BEGIN;
340 std::string buffer;
341 auto iter = begin;
342
343 while (state != State::END)
344 {
345 if (iter == end)
346 {
347 NS_LOG_DEBUG("Found end iterator, switching to END state");
348
349 state = State::END;
350 continue;
351 }
352
353 auto c = *iter;
354
355 NS_LOG_DEBUG("Next character: '" << c << "'");
356
357 // handle common cases here to avoid duplicating logic
358 if (state != State::QUOTED_STRING)
359 {
360 if (IsDelimiter(c))
361 {
362 NS_LOG_DEBUG("Found field delimiter, switching to END state");
363
364 if (state == State::UNQUOTED_STRING)
365 {
366 NS_LOG_DEBUG("Removing trailing whitespace from unquoted field: '" << buffer
367 << "'");
368 auto len = buffer.size();
369
370 // remove trailing whitespace from the field
371 while (!buffer.empty() &&
372 std::isspace(static_cast<unsigned char>(buffer.back())))
373 {
374 buffer.pop_back();
375 }
376
377 auto finalLen = buffer.size();
378
379 NS_LOG_DEBUG("Removed " << (len - finalLen)
380 << " trailing whitespace characters");
381 }
382
383 state = State::END;
384
385 continue;
386 }
387 else if (c == '#')
388 {
389 NS_LOG_DEBUG("Found start of comment, switching to END state");
390
391 // comments consume the rest of the line, set iter to end
392 // to reflect that fact.
393 iter = end;
394 state = State::END;
395
396 continue;
397 }
398 }
399
400 switch (state)
401 {
402 case State::BEGIN: {
403 if (c == '"')
404 {
405 NS_LOG_DEBUG("Switching state: BEGIN -> QUOTED_STRING");
406
407 state = State::QUOTED_STRING;
408 }
409 else if (!std::isspace(c))
410 {
411 NS_LOG_DEBUG("Switching state: BEGIN -> UNQUOTED_STRING");
412
413 state = State::UNQUOTED_STRING;
414 buffer.push_back(c);
415 }
416 }
417 break;
418 case State::QUOTED_STRING: {
419 if (c == '"')
420 {
421 NS_LOG_DEBUG("Switching state: QUOTED_STRING -> END_QUOTE");
422 state = State::END_QUOTE;
423 }
424 else
425 {
426 buffer.push_back(c);
427 }
428 }
429 break;
430 case State::END_QUOTE: {
431 if (c == '"')
432 {
433 NS_LOG_DEBUG("Switching state: END_QUOTE -> QUOTED_STRING");
434
435 // an escape quote instead of an end quote
436 state = State::QUOTED_STRING;
437 buffer.push_back(c);
438 }
439 else
440 {
441 NS_LOG_DEBUG("Switching state: END_QUOTE -> FIND_DELIMITER");
442 state = State::FIND_DELIMITER;
443 }
444 }
445 break;
446 case State::UNQUOTED_STRING: {
447 buffer.push_back(c);
448 }
449 break;
450 case State::FIND_DELIMITER:
451 case State::END:
452 break;
453 }
454
455 ++iter;
456 }
457
458 NS_LOG_DEBUG("Field value: " << buffer);
459
460 return std::make_tuple(buffer, iter);
461}
462
463} // namespace ns3
virtual ~CsvReader()
Destructor.
Definition csv-reader.cc:81
std::size_t RowNumber() const
The number of lines that have been read.
Definition csv-reader.cc:94
char Delimiter() const
Returns the delimiter character specified during object construction.
std::istream * m_stream
Pointer to the input stream containing the data.
Definition csv-reader.h:390
bool IsDelimiter(char c) const
Returns true if the supplied character matches the delimiter.
CsvReader(const std::string &filepath, char delimiter=',')
Constructor.
Definition csv-reader.cc:63
void ParseLine(const std::string &line)
Scans the string and splits it into individual columns based on the delimiter.
std::size_t ColumnCount() const
Returns the number of columns in the csv data.
Definition csv-reader.cc:86
std::size_t m_rowsRead
Number of lines processed.
Definition csv-reader.h:382
bool m_blankRow
Line contains no data (blank line or comment only).
Definition csv-reader.h:384
bool FetchNextRow()
Reads one line from the input until a new line is encountered.
bool IsBlankRow() const
Check if the current row is blank.
Columns m_columns
Fields extracted from the current line.
Definition csv-reader.h:383
bool GetValueAs(std::string input, double &value) const
Attempt to convert from the string data stored at the specified column index into the specified type.
char m_delimiter
Character used to separate fields.
Definition csv-reader.h:381
std::tuple< std::string, std::string::const_iterator > ParseColumn(std::string::const_iterator begin, std::string::const_iterator end)
Extracts the data for one column in a csv row.
ns3::CsvReader declaration
#define NS_LOG_ERROR(msg)
Use NS_LOG to output a message of level LOG_ERROR.
Definition log.h:243
#define NS_LOG_COMPONENT_DEFINE(name)
Define a Log component with a specific name.
Definition log.h:191
#define NS_LOG_DEBUG(msg)
Use NS_LOG to output a message of level LOG_DEBUG.
Definition log.h:257
#define NS_LOG_LOGIC(msg)
Use NS_LOG to output a message of level LOG_LOGIC.
Definition log.h:271
#define NS_LOG_FUNCTION(parameters)
If log level LOG_FUNCTION is enabled, this macro will output all input parameters separated by ",...
bool GenericTransform(std::string input, T &output)
Convert a string into another type.
Definition csv-reader.cc:47
Every class exported by the ns3 library is enclosed in the ns3 namespace.
#define END
End of a line.