mxlib
c++ tools for analyzing astronomical data and other tasks by Jared R. Males. [git repo]
Loading...
Searching...
No Matches
readColumns.hpp
Go to the documentation of this file.
1/** \file readColumns.hpp
2 * \author Jared R. Males
3 * \brief A utility to read in columns from a text file.
4 * \ingroup asciiutils
5 */
6
7//***********************************************************************//
8// Copyright 2015, 2016, 2017 Jared R. Males (jaredmales@gmail.com)
9//
10// This file is part of mxlib.
11//
12// mxlib is free software: you can redistribute it and/or modify
13// it under the terms of the GNU General Public License as published by
14// the Free Software Foundation, either version 3 of the License, or
15// (at your option) any later version.
16//
17// mxlib is distributed in the hope that it will be useful,
18// but WITHOUT ANY WARRANTY; without even the implied warranty of
19// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20// GNU General Public License for more details.
21//
22// You should have received a copy of the GNU General Public License
23// along with mxlib. If not, see <http://www.gnu.org/licenses/>.
24//***********************************************************************//
25
26#ifndef __readColumns_hpp__
27#define __readColumns_hpp__
28
29#include <cctype>
30#include <cstring>
31#include <fstream>
32#include <format>
33#include <string>
34
35#include "../mxlib.hpp"
36
37#include "stringUtils.hpp"
38
39#define MX_READCOL_MISSINGVALSTR "-99"
40
41namespace mx
42{
43namespace ioutils
44{
45
46#ifndef ioutils_readColumns_detail_hpp
47#define ioutils_readColumns_detail_hpp
48namespace readColumnsDetail
49{
50
51/** \cond */
52/// File-stream stages exposed to deterministic exception and error tests.
53enum class operation
54{
55 afterOpen,
56 afterReadLine,
57 afterClose
58};
59
60/// Signature of the resettable file-stream operation hook.
61using operationHookT = void ( * )( operation, std::ifstream & );
62
63/// Production no-op for the file-stream operation hook.
64inline void noOperationFailure( operation, std::ifstream & )
65{
66}
67
68/// Access the process-wide file-stream operation hook.
69inline operationHookT &operationHook()
70{
71 static operationHookT hook = noOperationFailure;
72 return hook;
73}
74/** \endcond */
75
76} // namespace readColumnsDetail
77#endif // ioutils_readColumns_detail_hpp
78
79#ifdef MXLIBTEST_NAMESPACE
80namespace MXLIBTEST_NAMESPACE
81{
82#endif
83
84struct readColSpaceDelim
85{
86 static constexpr char delim = ' ';
87 static constexpr char strDelim = '"';
88 static constexpr char eol = '\n';
89 static constexpr char comment = '#';
90 static constexpr const char *missingValStr = MX_READCOL_MISSINGVALSTR;
91};
92
93struct readColCommaDelim
94{
95 static constexpr char delim = ',';
96 static constexpr char strdelim = '"';
97 static constexpr char eol = '\n';
98 static constexpr char comment = '#';
99 static constexpr const char *missingValStr = MX_READCOL_MISSINGVALSTR;
100};
101
102template <class delimT, class verboseT>
103error_t readcol( [[maybe_unused]] const char *sin, [[maybe_unused]] int sz, [[maybe_unused]] int &colno )
104{
105 return error_t::noerror;
106}
107
108template <class delimT, class verboseT, typename arrT, typename... arrTs>
109error_t readcol( const char *sin, int sz, int &colno, arrT &array, arrTs &...arrays )
110{
111 try
112 {
113 std::string str;
114
115 int i = 0;
116 int l = strlen( sin );
117
118 if( l < 1 )
119 {
120 return error_t::noerror;
121 }
122
123 // Eat white space
124 while( i < l && std::isspace( static_cast<unsigned char>( sin[i] ) ) && sin[i] != delimT::eol )
125 {
126 ++i;
127 }
128 sin = sin + i;
129 sz = sz - i;
130
131 // If there's nothing here, we still need to populate the vector
132 if( sz == 0 )
133 {
134 mx::error_t errc;
135 array.push_back( stoT<typename arrT::value_type>( "", &errc ) );
136
137 if( errc != mx::error_t::noerror )
138 {
139 return internal::mxlib_error_report<verboseT>( errc, std::format( "processing column {}", colno ) );
140 }
141
143 }
144
145 std::stringstream sinstr( sin );
146
147 std::getline( sinstr, str, delimT::delim );
148
149 // Last entry in line might contain eol
150 if( !str.empty() && str[str.size() - 1] == delimT::eol )
151 {
152 str.erase( str.size() - 1 );
153 }
154
155 mx::error_t errc;
156 if( str.size() == 0 )
157 {
158 array.push_back( stoT<typename arrT::value_type>( MX_READCOL_MISSINGVALSTR, &errc ) );
159 }
160 else
161 {
162 array.push_back( stoT<typename arrT::value_type>( str, &errc ) );
163 }
164
165 if( errc != mx::error_t::noerror )
166 {
167 return internal::mxlib_error_report<verboseT>( errc, std::format( "processing column {}", colno ) );
168 }
169
170 sin += ( str.size() + 1 ) * sizeof( char );
171 sz -= ( str.size() + 1 ) * sizeof( char );
172 }
173 catch( const std::invalid_argument &e )
174 {
175 // We always catch this one
176 return internal::mxlib_error_report<verboseT>( error_t::std_invalid_argument,
177 std::format( "processing column {}: {}", colno, e.what() ) );
178 }
179 catch( const std::out_of_range &e )
180 {
181 // We always catch this one
182 return internal::mxlib_error_report<verboseT>( error_t::std_out_of_range,
183 std::format( "processing column {}: {}", colno, e.what() ) );
184 }
185 catch( const std::bad_alloc &e )
186 {
187
188 // clang-format off
189 #if defined( MXLIB_CATCH_ALL_EXCEPTIONS )
190 return internal::mxlib_error_report<verboseT>( error_t::std_bad_alloc,
191 std::format( "processing column {}: {}", colno, e.what() ) );
192 #else
193 std::throw_with_nested(mx::exception<verboseT>(error_t::std_bad_alloc,
194 std::format( "processing column {}: {}", colno, e.what() ) ));
195 #endif
196 // clang-format on
197 }
198 catch( const std::exception &e )
199 {
200 // clang-format off
201 #if defined( MXLIB_CATCH_ALL_EXCEPTIONS ) || defined( MXLIB_CATCH_NONALLOC_EXCEPTIONS )
202 return internal::mxlib_error_report<verboseT>( error_t::std_exception,
203 std::format( "processing column {}: {}", colno, e.what() ) );
204
205 #else
206 std::throw_with_nested(mx::exception<verboseT>(error_t::std_exception,
207 std::format( "processing column {}: {}", colno, e.what() ) ));
208
209 #endif
210 // clang-format on
211 }
212 catch( ... )
213 {
214 // clang-format off
215 #if defined( MXLIB_CATCH_ALL_EXCEPTIONS ) || defined( MXLIB_CATCH_NONALLOC_EXCEPTIONS )
216 return internal::mxlib_error_report<verboseT>( error_t::exception,
217 std::format( "processing column {}", colno) );
218
219 #else
220 std::throw_with_nested(mx::exception<verboseT>(error_t::exception,
221 std::format( "processing column {}", colno) ));
222
223 #endif
224 }
225
226 ++colno;
227 return readcol<delimT, verboseT>( sin, sz, colno, arrays... );
228}
229
230/// Read in columns from a text file
231/** This function opens a file containing data formatted in columns and reads in the data row by row.
232 * The data are stored in std::vectors, which should not be pre-allocated (though they could be reserve()-ed).
233 *
234 * Example:
235 * \code
236 * std::vector<int> i1;
237 * std::vector<float> f1;
238 * std::vector<double> d1;
239 *
240 * readColumns("data_file.txt", i1, f1, d1);
241 * \endcode
242 *
243 * Note that the types of the vectors do not need to be specified as template arguments.
244 *
245 * The format of the file can be specified with template arguments like
246 * \code
247 * readColumns<',', ';', '\r'>("data_file.csv", i1, f1, d1);
248 * \endcode
249 * which sets the delimmiter to comma, the comment character to ;, and the end-of-line to \\r.
250 *
251 * Columns can be skipped using mx::ioutils::skipCol.
252 *
253 * \tparam delimT specifies the delimiters. By default this is `mx::ioutils::readColSpaceDelim`.
254 * \tparam verbose specifies the error reporting verbosity. See \ref mx::verbose
255 *
256 *
257 * \ingroup asciiutils
258 */
259template <class delimT = readColSpaceDelim, class verboseT = verbose::d, typename... arrTs>
260error_t readColumns( const std::string &fname, ///< [in] is the file name to read from
261 arrTs &...arrays /**< [out] a variadic list of std::vectors. Any number with mixed
262 value_type can be specified. Neither allocated nor cleared,
263 so repeated calls will append data.*/
264)
265{
266 // open file
267 errno = 0;
268 std::ifstream fin;
269 fin.open( fname );
270 readColumnsDetail::operationHook()( readColumnsDetail::operation::afterOpen, fin );
271
272 if( !fin.good() )
273 {
274 error_t errc;
275 if( errno != 0 )
276 {
277 errc = errno2error_t( errno );
278 }
279 else
280 {
281 errc = error_t::fileoerr;
282 }
283
284 return internal::mxlib_error_report<verboseT>( errc, "Opening " + fname + " for reading" );
285 }
286
287 std::string line;
288
289 int64_t lineno = -1;
290
291 while( fin.good() )
292 {
293 ++lineno;
294 try
295 {
296 std::getline( fin, line, delimT::eol );
297 readColumnsDetail::operationHook()( readColumnsDetail::operation::afterReadLine, fin );
298 }
299 catch( const std::bad_alloc &e )
300 {
301
302 // clang-format off
303 #if defined( MXLIB_CATCH_ALL_EXCEPTIONS )
305 std::format( "Reading from {} at line {}: {}.",
306 fname, lineno, e.what() ) );
307;
308 #else
309 std::throw_with_nested( mx::exception<verboseT>( error_t::std_bad_alloc,
310 std::format( "Reading from {} at line {}: {}.",
311 fname, lineno, e.what() ) ) );
312 #endif
313 // clang-format on
314 }
315 catch( const std::exception &e )
316 {
317
318 // clang-format off
319 #if defined( MXLIB_CATCH_ALL_EXCEPTIONS ) || defined(MXLIB_CATCH_NONALLOC_EXCEPTIONS)
320
322 std::format( "Reading from {} at line {}: {}.",
323 fname,
324 lineno,
325 e.what() ) );
326 #else
327 std::throw_with_nested(mx::exception<verboseT>( error_t::std_exception,
328 std::format( "Reading from {} at line {}: {}.",
329 fname, lineno, e.what() ) ) );
330 #endif
331 // clang-format on
332 }
333 catch( ... )
334 {
335
336 // clang-format off
337 #if defined( MXLIB_CATCH_ALL_EXCEPTIONS ) || defined(MXLIB_CATCH_NONALLOC_EXCEPTIONS)
338
340 std::format( "Reading from {} at line {}",
341 fname,
342 lineno ) );
343 #else
344 std::throw_with_nested(mx::exception<verboseT>( error_t::exception,
345 std::format( "Reading from {} at line {}",
346 fname, lineno) ) );
347 #endif
348 // clang-format on
349 }
350
351 if( fin.bad() )
352 {
353 break;
354 }
355
356 if( line.size() == 0 )
357 {
358 continue;
359 }
360
361 // Find start of comment and end line at that point.
362 size_t i = 0;
363 bool nonspace = false; // record if we find a non-space character before the comment
364 while( i < line.size() && line[i] != delimT::comment )
365 {
366 if( !nonspace && !std::isspace( static_cast<unsigned char>( line[i] ) ) )
367 {
368 nonspace = true;
369 }
370 ++i;
371 }
372
373 // Check if line is all comment
374 if( i == 0 || !nonspace )
375 {
376 continue;
377 }
378
379 if( i < line.size() ) // i is > 0 if we're here
380 {
381 line.erase( line.begin() + i, line.end() ); // does not throw
382 }
383
384 int colno = 0;
385 error_t errc = readcol<delimT, verboseT>( line.c_str(), line.size(), colno, arrays... );
386
387 if( errc != error_t::noerror )
388 {
390 errc,
391 std::format( "Reading from {} at line {} column {}", fname, lineno + 1, colno + 1 ) );
392 }
393 }
394
395 // getline will have set fail if there was no new line on the last line.
396 if( fin.bad() )
397 {
398 error_t errc;
399 if( errno != 0 )
400 {
401 errc = errno2error_t( errno );
402 }
403 else
404 {
405 errc = error_t::filererr;
406 }
407
408 return internal::mxlib_error_report<verboseT>( errc, "Reading from " + fname );
409 }
410
411 fin.clear(); // Clear the fail bit which may have been set by getline
412 errno = 0;
413 fin.close();
414 readColumnsDetail::operationHook()( readColumnsDetail::operation::afterClose, fin );
415
416 if( fin.fail() )
417 {
418 error_t errc;
419 if( errno != 0 )
420 {
421 errc = errno2error_t( errno );
422 }
423 else
424 {
425 errc = error_t::filecerr;
426 }
427
428 return internal::mxlib_error_report<verboseT>( errc, "Closing" + fname );
429 }
430
431 return error_t::noerror;
432}
433
434/// A dummy class to allow mx::readColumns to skip a column(s) in a file without requiring memory allocation.
435/** The alternative is to use dummy vectors, which result in excess memory allocations and deallocations.
436 * Usage:
437 \code
438 std::vector<T> col1, col5;
439 skipCol sk;
440 readColumns("filename.txt", col1, sk, sk, sk, col5); //This results in only columns 1 and 5 being stored.
441 \endcode
442 *
443 * \ingroup asciiutils
444 */
446{
447 typedef std::string value_type; ///< value_type is defined as std::string so that no conversions take place.
448
449 template <typename T>
450 void push_back( const T &arg )
451 {
452 return;
453 }
454};
455
456#ifdef MXLIBTEST_NAMESPACE
457} // namespace MXLIBTEST_NAMESPACE
458#endif
459
460} // namespace ioutils
461} // namespace mx
462
463#endif //__readColumns_hpp__
Augments an exception with the source file and line.
Definition exception.hpp:42
error_t readColumns(const std::string &fname, arrTs &...arrays)
Read in columns from a text file.
error_t
The mxlib error codes.
Definition error_t.hpp:26
static constexpr error_t errno2error_t(const int &err)
Convert an errno code to error_t.
Definition error_t.hpp:2056
@ noerror
No error has occurred.
Definition error_t.hpp:27
@ std_exception
An exception was thrown.
Definition error_t.hpp:52
@ filererr
An error occurred while reading from a file.
Definition error_t.hpp:42
@ exception
An exception was thrown.
Definition error_t.hpp:51
@ std_bad_alloc
A bad allocation exception was thrown.
Definition error_t.hpp:53
@ filecerr
An error occurred while closing a file.
Definition error_t.hpp:43
@ fileoerr
An error occurred while opening a file.
Definition error_t.hpp:40
error_t mxlib_error_report(const error_t &code, const std::string &expl, const std::source_location &loc=std::source_location::current())
Print a report to stderr given an mxlib error_t code and explanation and return the code.
Definition error.hpp:331
Declarations of some libarary wide utilities.
The mxlib c++ namespace.
Definition mxlib.hpp:37
Utilities for working with strings.
A dummy class to allow mx::readColumns to skip a column(s) in a file without requiring memory allocat...
std::string value_type
value_type is defined as std::string so that no conversions take place.