Vince's CSV Parser
Loading...
Searching...
No Matches
csv_writer.hpp
Go to the documentation of this file.
1
5#pragma once
6#include <fstream>
7#include <iostream>
8#include <memory>
9#include <stdexcept>
10#include <string>
11#include <tuple>
12#include <type_traits>
13#include <vector>
14
15#include "common.hpp"
16#include "data_type.hpp"
17
18namespace csv {
19 namespace internals {
20 static int DECIMAL_PLACES = 5;
21
25 template<typename T = int>
26 inline T csv_abs(T x) {
27 return abs(x);
28 }
29
30 template<>
31 inline int csv_abs(int x) {
32 return abs(x);
33 }
34
35 template<>
36 inline long int csv_abs(long int x) {
37 return labs(x);
38 }
39
40 template<>
41 inline long long int csv_abs(long long int x) {
42 return llabs(x);
43 }
44
45 template<>
46 inline float csv_abs(float x) {
47 return fabsf(x);
48 }
49
50 template<>
51 inline double csv_abs(double x) {
52 return fabs(x);
53 }
54
55 template<>
56 inline long double csv_abs(long double x) {
57 return fabsl(x);
58 }
59
63 template<
64 typename T,
65 csv::enable_if_t<std::is_arithmetic<T>::value, int> = 0
66 >
68 {
69 x = csv_abs(x);
70
71 int digits = 0;
72
73 while (x >= 1) {
74 x /= 10;
75 digits++;
76 }
77
78 return digits;
79 }
80
82 template<typename T,
83 csv::enable_if_t<std::is_unsigned<T>::value, int> = 0>
84 inline std::string to_string(T value) {
85 std::string digits_reverse = "";
86
87 if (value == 0) return "0";
88
89 while (value > 0) {
90 digits_reverse += (char)('0' + (value % 10));
91 value /= 10;
92 }
93
94 return std::string(digits_reverse.rbegin(), digits_reverse.rend());
95 }
96
98 template<
99 typename T,
100 csv::enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, int> = 0
101 >
102 inline std::string to_string(T value) {
103 if (value >= 0)
104 return to_string((size_t)value);
105
106 return "-" + to_string((size_t)(value * -1));
107 }
108
110 template<
111 typename T,
112 csv::enable_if_t<std::is_floating_point<T>::value, int> = 0
113 >
114 inline std::string to_string(T value) {
115#ifdef __clang__
116 return std::to_string(value);
117#else
118 // TODO: Figure out why the below code doesn't work on clang
119 std::string result = "";
120
122 T fractional_part = std::abs(std::modf(value, &integral_part));
123 integral_part = std::abs(integral_part);
124
125 // Integral part
126 if (value < 0) result = "-";
127
128 if (integral_part == 0) {
129 result += "0";
130 }
131 else {
132 for (int n_digits = num_digits(integral_part); n_digits > 0; n_digits --) {
133 int digit = (int)(std::fmod(integral_part, pow10(n_digits)) / pow10(n_digits - 1));
134 result += (char)('0' + digit);
135 }
136 }
137
138 // Decimal part
139 result += ".";
140
141 if (fractional_part > 0) {
142 fractional_part *= (T)(pow10(DECIMAL_PLACES));
143 for (int n_digits = DECIMAL_PLACES; n_digits > 0; n_digits--) {
144 int digit = (int)(std::fmod(fractional_part, pow10(n_digits)) / pow10(n_digits - 1));
145 result += (char)('0' + digit);
146 }
147 }
148 else {
149 result += "0";
150 }
151
152 return result;
153#endif
154 }
155 }
156
161#ifndef __clang___
162 inline static void set_decimal_places(int precision) {
163 internals::DECIMAL_PLACES = precision;
164 }
165#endif
166
169
194 template<class OutputStream, char Delim, char Quote, bool Flush>
196 public:
203 DelimWriter(OutputStream& _out, bool _quote_minimal = true)
204 : out(&_out), quote_minimal(_quote_minimal) {}
205
210 template<typename T = OutputStream,
211 csv::enable_if_t<std::is_same<T, std::ofstream>::value, int> = 0>
212 DelimWriter(const std::string& filename, bool _quote_minimal = true)
213 : owned_out(new std::ofstream(filename, std::ios::out)),
214 out(owned_out.get()),
215 quote_minimal(_quote_minimal) {
216 if (!owned_out->is_open())
217 throw std::runtime_error("Failed to open file for writing: " + filename);
218 }
219
224 out->flush();
225 }
226
235 template<typename T, size_t Size>
236 DelimWriter& operator<<(const std::array<T, Size>& record) {
237 for (size_t i = 0; i < Size; i++) {
238 (*out) << csv_escape(record[i]);
239 if (i + 1 != Size) (*out) << Delim;
240 }
241
242 end_out();
243 return *this;
244 }
245
247 template<typename... T>
248 DelimWriter& operator<<(const std::tuple<T...>& record) {
249 this->write_tuple<0, T...>(record);
250 return *this;
251 }
252
258 template<
259 typename T, typename Alloc, template <typename, typename> class Container,
260
261 // Avoid conflicting with tuples with two elements
262 csv::enable_if_t<std::is_class<Alloc>::value, int> = 0
263 >
264 DelimWriter& operator<<(const Container<T, Alloc>& record) {
265 const size_t ilen = record.size();
266 size_t i = 0;
267 for (const auto& field : record) {
268 (*out) << csv_escape(field);
269 if (i + 1 != ilen) (*out) << Delim;
270 i++;
271 }
272
273 end_out();
274 return *this;
275 }
276
280 void flush() {
281 out->flush();
282 }
283
284 private:
285 template<
286 typename T,
287 csv::enable_if_t<
288 !std::is_convertible<T, std::string>::value
289 && !std::is_convertible<T, csv::string_view>::value
290 , int> = 0
291 >
292 std::string csv_escape(T in) {
293 return internals::to_string(in);
294 }
295
296 template<
297 typename T,
298 csv::enable_if_t<
299 std::is_convertible<T, std::string>::value
300 || std::is_convertible<T, csv::string_view>::value
301 , int> = 0
302 >
303 std::string csv_escape(T in) {
304 IF_CONSTEXPR(std::is_convertible<T, csv::string_view>::value) {
305 return _csv_escape(in);
306 }
307 else {
308 return _csv_escape(std::string(in));
309 }
310 }
311
312 std::string _csv_escape(csv::string_view in) {
319 // Do we need a quote escape
320 bool quote_escape = false;
321
322 for (auto ch : in) {
323 if (ch == Quote || ch == Delim || ch == '\r' || ch == '\n') {
324 quote_escape = true;
325 break;
326 }
327 }
328
329 if (!quote_escape) {
330 if (quote_minimal) return std::string(in);
331 else {
332 std::string ret(1, Quote);
333 ret += in.data();
334 ret += Quote;
335 return ret;
336 }
337 }
338
339 // Start initial quote escape sequence
340 std::string ret(1, Quote);
341 for (auto ch: in) {
342 if (ch == Quote) ret += std::string(2, Quote);
343 else ret += ch;
344 }
345
346 // Finish off quote escape
347 ret += Quote;
348 return ret;
349 }
350
352 template<size_t Index = 0, typename... T>
353 typename std::enable_if<Index < sizeof...(T), void>::type write_tuple(const std::tuple<T...>& record) {
354 (*out) << csv_escape(std::get<Index>(record));
355
356 IF_CONSTEXPR (Index + 1 < sizeof...(T)) (*out) << Delim;
357
358 this->write_tuple<Index + 1>(record);
359 }
360
362 template<size_t Index = 0, typename... T>
363 typename std::enable_if<Index == sizeof...(T), void>::type write_tuple(const std::tuple<T...>& record) {
364 (void)record;
365 end_out();
366 }
367
369 void end_out() {
370 (*out) << '\n';
371 IF_CONSTEXPR(Flush) out->flush();
372 }
373
379 std::unique_ptr<OutputStream> owned_out;
380
382 OutputStream* out;
383
384 bool quote_minimal;
385 };
386
394 template<class OutputStream, bool Flush = true>
395 using CSVWriter = DelimWriter<OutputStream, ',', '"', Flush>;
396
405 template<class OutputStream, bool Flush = true>
406 using TSVWriter = DelimWriter<OutputStream, '\t', '"', Flush>;
407
409 template<class OutputStream>
410 inline CSVWriter<OutputStream> make_csv_writer(OutputStream& out, bool quote_minimal=true) {
411 return CSVWriter<OutputStream>(out, quote_minimal);
412 }
413
415 template<class OutputStream>
416 inline CSVWriter<OutputStream, false> make_csv_writer_buffered(OutputStream& out, bool quote_minimal=true) {
417 return CSVWriter<OutputStream, false>(out, quote_minimal);
418 }
419
421 template<class OutputStream>
422 inline TSVWriter<OutputStream> make_tsv_writer(OutputStream& out, bool quote_minimal=true) {
423 return TSVWriter<OutputStream>(out, quote_minimal);
424 }
425
427 template<class OutputStream>
428 inline TSVWriter<OutputStream, false> make_tsv_writer_buffered(OutputStream& out, bool quote_minimal=true) {
429 return TSVWriter<OutputStream, false>(out, quote_minimal);
430 }
432}
Class for writing delimiter separated values files.
DelimWriter & operator<<(const Container< T, Alloc > &record)
void flush()
Flushes the written data.
DelimWriter & operator<<(const std::array< T, Size > &record)
Format a sequence of strings and write to CSV according to RFC 4180.
DelimWriter(OutputStream &_out, bool _quote_minimal=true)
Construct a DelimWriter over the specified output stream.
~DelimWriter()
Destructor will flush remaining data.
DelimWriter(const std::string &filename, bool _quote_minimal=true)
Construct a DelimWriter over the file.
DelimWriter & operator<<(const std::tuple< T... > &record)
Format a sequence of strings and write to CSV according to RFC 4180.
A standalone header file containing shared code.
#define IF_CONSTEXPR
Expands to if constexpr in C++17 and if otherwise.
Definition common.hpp:108
Implements data type parsing functionality.
T csv_abs(T x)
Calculate the absolute value of a number.
CSV_CONST CONSTEXPR_14 long double pow10(const T &n) noexcept
Compute 10 to the power of n.
Definition data_type.hpp:40
int num_digits(T x)
Calculate the number of digits in a number.
CSV_CONST CONSTEXPR_17 OutArray arrayToDefault(T &&value)
Helper constexpr function to initialize an array with all the elements set to value.
std::string to_string(T value)
to_string() for unsigned integers
The all encompassing namespace.
TSVWriter< OutputStream, false > make_tsv_writer_buffered(OutputStream &out, bool quote_minimal=true)
Return a buffered csv::TSVWriter over the output stream (does not auto flush)
TSVWriter< OutputStream > make_tsv_writer(OutputStream &out, bool quote_minimal=true)
Return a csv::TSVWriter over the output stream.
CSVWriter< OutputStream, false > make_csv_writer_buffered(OutputStream &out, bool quote_minimal=true)
Return a buffered csv::CSVWriter over the output stream (does not auto flush)
CSVWriter< OutputStream > make_csv_writer(OutputStream &out, bool quote_minimal=true)
Return a csv::CSVWriter over the output stream.
nonstd::string_view string_view
The string_view class used by this library.
Definition common.hpp:99