Vince's CSV Parser
csv_writer.hpp
Go to the documentation of this file.
1 
5 #pragma once
6 #include <fstream>
7 #include <iostream>
8 #include <string>
9 #include <tuple>
10 #include <type_traits>
11 #include <vector>
12 
13 #include "common.hpp"
14 #include "data_type.hpp"
15 
16 namespace csv {
17  namespace internals {
18  static int DECIMAL_PLACES = 5;
19 
23  template<typename T = int>
24  inline T csv_abs(T x) {
25  return abs(x);
26  }
27 
28  template<>
29  inline int csv_abs(int x) {
30  return abs(x);
31  }
32 
33  template<>
34  inline long int csv_abs(long int x) {
35  return labs(x);
36  }
37 
38  template<>
39  inline long long int csv_abs(long long int x) {
40  return llabs(x);
41  }
42 
43  template<>
44  inline float csv_abs(float x) {
45  return fabsf(x);
46  }
47 
48  template<>
49  inline double csv_abs(double x) {
50  return fabs(x);
51  }
52 
53  template<>
54  inline long double csv_abs(long double x) {
55  return fabsl(x);
56  }
57 
61  template<
62  typename T,
63  csv::enable_if_t<std::is_arithmetic<T>::value, int> = 0
64  >
65  int num_digits(T x)
66  {
67  x = csv_abs(x);
68 
69  int digits = 0;
70 
71  while (x >= 1) {
72  x /= 10;
73  digits++;
74  }
75 
76  return digits;
77  }
78 
80  template<typename T,
81  csv::enable_if_t<std::is_unsigned<T>::value, int> = 0>
82  inline std::string to_string(T value) {
83  std::string digits_reverse = "";
84 
85  if (value == 0) return "0";
86 
87  while (value > 0) {
88  digits_reverse += (char)('0' + (value % 10));
89  value /= 10;
90  }
91 
92  return std::string(digits_reverse.rbegin(), digits_reverse.rend());
93  }
94 
96  template<
97  typename T,
98  csv::enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, int> = 0
99  >
100  inline std::string to_string(T value) {
101  if (value >= 0)
102  return to_string((size_t)value);
103 
104  return "-" + to_string((size_t)(value * -1));
105  }
106 
108  template<
109  typename T,
110  csv::enable_if_t<std::is_floating_point<T>::value, int> = 0
111  >
112  inline std::string to_string(T value) {
113 #ifdef __clang__
114  return std::to_string(value);
115 #else
116  // TODO: Figure out why the below code doesn't work on clang
117  std::string result = "";
118 
119  T integral_part;
120  T fractional_part = std::abs(std::modf(value, &integral_part));
121  integral_part = std::abs(integral_part);
122 
123  // Integral part
124  if (value < 0) result = "-";
125 
126  if (integral_part == 0) {
127  result += "0";
128  }
129  else {
130  for (int n_digits = num_digits(integral_part); n_digits > 0; n_digits --) {
131  int digit = (int)(std::fmod(integral_part, pow10(n_digits)) / pow10(n_digits - 1));
132  result += (char)('0' + digit);
133  }
134  }
135 
136  // Decimal part
137  result += ".";
138 
139  if (fractional_part > 0) {
140  fractional_part *= (T)(pow10(DECIMAL_PLACES));
141  for (int n_digits = DECIMAL_PLACES; n_digits > 0; n_digits--) {
142  int digit = (int)(std::fmod(fractional_part, pow10(n_digits)) / pow10(n_digits - 1));
143  result += (char)('0' + digit);
144  }
145  }
146  else {
147  result += "0";
148  }
149 
150  return result;
151 #endif
152  }
153  }
154 
159 #ifndef __clang___
160  inline static void set_decimal_places(int precision) {
161  internals::DECIMAL_PLACES = precision;
162  }
163 #endif
164 
167 
192  template<class OutputStream, char Delim, char Quote, bool Flush>
193  class DelimWriter {
194  public:
201  DelimWriter(OutputStream& _out, bool _quote_minimal = true)
202  : out(_out), quote_minimal(_quote_minimal) {};
203 
208  DelimWriter(const std::string& filename) : DelimWriter(std::ifstream(filename)) {};
209 
214  out.flush();
215  }
216 
225  template<typename T, size_t Size>
226  DelimWriter& operator<<(const std::array<T, Size>& record) {
227  for (size_t i = 0; i < Size; i++) {
228  out << csv_escape(record[i]);
229  if (i + 1 != Size) out << Delim;
230  }
231 
232  end_out();
233  return *this;
234  }
235 
237  template<typename... T>
238  DelimWriter& operator<<(const std::tuple<T...>& record) {
239  this->write_tuple<0, T...>(record);
240  return *this;
241  }
242 
248  template<
249  typename T, typename Alloc, template <typename, typename> class Container,
250 
251  // Avoid conflicting with tuples with two elements
252  csv::enable_if_t<std::is_class<Alloc>::value, int> = 0
253  >
254  DelimWriter& operator<<(const Container<T, Alloc>& record) {
255  const size_t ilen = record.size();
256  size_t i = 0;
257  for (const auto& field : record) {
258  out << csv_escape(field);
259  if (i + 1 != ilen) out << Delim;
260  i++;
261  }
262 
263  end_out();
264  return *this;
265  }
266 
270  void flush() {
271  out.flush();
272  }
273 
274  private:
275  template<
276  typename T,
277  csv::enable_if_t<
278  !std::is_convertible<T, std::string>::value
279  && !std::is_convertible<T, csv::string_view>::value
280  , int> = 0
281  >
282  std::string csv_escape(T in) {
283  return internals::to_string(in);
284  }
285 
286  template<
287  typename T,
288  csv::enable_if_t<
289  std::is_convertible<T, std::string>::value
290  || std::is_convertible<T, csv::string_view>::value
291  , int> = 0
292  >
293  std::string csv_escape(T in) {
294  IF_CONSTEXPR(std::is_convertible<T, csv::string_view>::value) {
295  return _csv_escape(in);
296  }
297 
298  return _csv_escape(std::string(in));
299  }
300 
301  std::string _csv_escape(csv::string_view in) {
308  // Do we need a quote escape
309  bool quote_escape = false;
310 
311  for (auto ch : in) {
312  if (ch == Quote || ch == Delim || ch == '\r' || ch == '\n') {
313  quote_escape = true;
314  break;
315  }
316  }
317 
318  if (!quote_escape) {
319  if (quote_minimal) return std::string(in);
320  else {
321  std::string ret(1, Quote);
322  ret += in.data();
323  ret += Quote;
324  return ret;
325  }
326  }
327 
328  // Start initial quote escape sequence
329  std::string ret(1, Quote);
330  for (auto ch: in) {
331  if (ch == Quote) ret += std::string(2, Quote);
332  else ret += ch;
333  }
334 
335  // Finish off quote escape
336  ret += Quote;
337  return ret;
338  }
339 
341  template<size_t Index = 0, typename... T>
342  typename std::enable_if<Index < sizeof...(T), void>::type write_tuple(const std::tuple<T...>& record) {
343  out << csv_escape(std::get<Index>(record));
344 
345  IF_CONSTEXPR (Index + 1 < sizeof...(T)) out << Delim;
346 
347  this->write_tuple<Index + 1>(record);
348  }
349 
351  template<size_t Index = 0, typename... T>
352  typename std::enable_if<Index == sizeof...(T), void>::type write_tuple(const std::tuple<T...>& record) {
353  (void)record;
354  end_out();
355  }
356 
358  void end_out() {
359  out << '\n';
360  IF_CONSTEXPR(Flush) out.flush();
361  }
362 
363  OutputStream & out;
364  bool quote_minimal;
365  };
366 
374  template<class OutputStream, bool Flush = true>
375  using CSVWriter = DelimWriter<OutputStream, ',', '"', Flush>;
376 
385  template<class OutputStream, bool Flush = true>
386  using TSVWriter = DelimWriter<OutputStream, '\t', '"', Flush>;
387 
389  template<class OutputStream>
390  inline CSVWriter<OutputStream> make_csv_writer(OutputStream& out, bool quote_minimal=true) {
391  return CSVWriter<OutputStream>(out, quote_minimal);
392  }
393 
395  template<class OutputStream>
396  inline CSVWriter<OutputStream, false> make_csv_writer_buffered(OutputStream& out, bool quote_minimal=true) {
397  return CSVWriter<OutputStream, false>(out, quote_minimal);
398  }
399 
401  template<class OutputStream>
402  inline TSVWriter<OutputStream> make_tsv_writer(OutputStream& out, bool quote_minimal=true) {
403  return TSVWriter<OutputStream>(out, quote_minimal);
404  }
405 
407  template<class OutputStream>
408  inline TSVWriter<OutputStream, false> make_tsv_writer_buffered(OutputStream& out, bool quote_minimal=true) {
409  return TSVWriter<OutputStream, false>(out, quote_minimal);
410  }
412 }
Class for writing delimiter separated values files.
Definition: csv_writer.hpp:193
DelimWriter & operator<<(const std::tuple< T... > &record)
Format a sequence of strings and write to CSV according to RFC 4180.
Definition: csv_writer.hpp:238
void flush()
Flushes the written data.
Definition: csv_writer.hpp:270
DelimWriter(OutputStream &_out, bool _quote_minimal=true)
Construct a DelimWriter over the specified output stream.
Definition: csv_writer.hpp:201
DelimWriter(const std::string &filename)
Construct a DelimWriter over the file.
Definition: csv_writer.hpp:208
~DelimWriter()
Destructor will flush remaining data.
Definition: csv_writer.hpp:213
DelimWriter & operator<<(const Container< T, Alloc > &record)
Format a sequence of strings and write to CSV according to RFC 4180.
Definition: csv_writer.hpp:254
DelimWriter & operator<<(const std::array< T, Size > &record)
Format a sequence of strings and write to CSV according to RFC 4180.
Definition: csv_writer.hpp:226
A standalone header file containing shared code.
#define IF_CONSTEXPR
Expands to if constexpr in C++17 and if otherwise.
Definition: common.hpp:84
Implements data type parsing functionality.
T csv_abs(T x)
Calculate the absolute value of a number.
Definition: csv_writer.hpp:24
int num_digits(T x)
Calculate the number of digits in a number.
Definition: csv_writer.hpp:65
HEDLEY_CONST CONSTEXPR_14 long double pow10(const T &n) noexcept
Compute 10 to the power of n.
Definition: data_type.hpp:40
std::string to_string(T value)
to_string() for unsigned integers
Definition: csv_writer.hpp:82
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)
Definition: csv_writer.hpp:408
CSVWriter< OutputStream > make_csv_writer(OutputStream &out, bool quote_minimal=true)
Return a csv::CSVWriter over the output stream.
Definition: csv_writer.hpp:390
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)
Definition: csv_writer.hpp:396
TSVWriter< OutputStream > make_tsv_writer(OutputStream &out, bool quote_minimal=true)
Return a csv::TSVWriter over the output stream.
Definition: csv_writer.hpp:402
nonstd::string_view string_view
The string_view class used by this library.
Definition: common.hpp:75