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