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.h"
15 
16 namespace csv {
17  namespace internals {
18  static int DECIMAL_PLACES = 5;
19 
21  template<typename T,
22  csv::enable_if_t<std::is_unsigned<T>::value, int> = 0>
23  inline std::string to_string(T value) {
24  std::string digits_reverse = "";
25 
26  if (value == 0) return "0";
27 
28  while (value > 0) {
29  digits_reverse += (char)('0' + (value % 10));
30  value /= 10;
31  }
32 
33  return std::string(digits_reverse.rbegin(), digits_reverse.rend());
34  }
35 
37  template<
38  typename T,
39  csv::enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, int> = 0
40  >
41  inline std::string to_string(T value) {
42  if (value >= 0)
43  return to_string((size_t)value);
44 
45  return "-" + to_string((size_t)(value * -1));
46  }
47 
49  template<
50  typename T,
51  csv::enable_if_t<std::is_floating_point<T>::value, int> = 0
52  >
53  inline std::string to_string(T value) {
54 #ifdef __clang__
55  return std::to_string(value);
56 #else
57  // TODO: Figure out why the below code doesn't work on clang
58  std::string result;
59 
60  T integral_part;
61  T fractional_part = std::abs(std::modf(value, &integral_part));
62  integral_part = std::abs(integral_part);
63 
64  // Integral part
65  if (value < 0) result = "-";
66 
67  if (integral_part == 0) {
68  result = "0";
69  }
70  else {
71  for (int n_digits = (int)(std::log(integral_part) / std::log(10));
72  n_digits + 1 > 0; n_digits --) {
73  int digit = (int)(std::fmod(integral_part, pow10(n_digits + 1)) / pow10(n_digits));
74  result += (char)('0' + digit);
75  }
76  }
77 
78  // Decimal part
79  result += ".";
80 
81  if (fractional_part > 0) {
82  fractional_part *= (T)(pow10(DECIMAL_PLACES));
83  for (int n_digits = DECIMAL_PLACES; n_digits > 0; n_digits--) {
84  int digit = (int)(std::fmod(fractional_part, pow10(n_digits)) / pow10(n_digits - 1));
85  result += (char)('0' + digit);
86  }
87  }
88  else {
89  result += "0";
90  }
91 
92  return result;
93 #endif
94  }
95  }
96 
101 #ifndef __clang___
102  inline static void set_decimal_places(int precision) {
103  internals::DECIMAL_PLACES = precision;
104  }
105 #endif
106 
109 
134  template<class OutputStream, char Delim, char Quote, bool Flush>
135  class DelimWriter {
136  public:
143  DelimWriter(OutputStream& _out, bool _quote_minimal = true)
144  : out(_out), quote_minimal(_quote_minimal) {};
145 
150  DelimWriter(const std::string& filename) : DelimWriter(std::ifstream(filename)) {};
151 
156  out.flush();
157  }
158 
167  template<typename T, size_t Size>
168  DelimWriter& operator<<(const std::array<T, Size>& record) {
169  for (size_t i = 0; i < Size; i++) {
170  out << csv_escape(record[i]);
171  if (i + 1 != Size) out << Delim;
172  }
173 
174  end_out();
175  return *this;
176  }
177 
179  template<typename... T>
180  DelimWriter& operator<<(const std::tuple<T...>& record) {
181  this->write_tuple<0, T...>(record);
182  return *this;
183  }
184 
190  template<
191  typename T, typename Alloc, template <typename, typename> class Container,
192 
193  // Avoid conflicting with tuples with two elements
194  csv::enable_if_t<std::is_class<Alloc>::value, int> = 0
195  >
196  DelimWriter& operator<<(const Container<T, Alloc>& record) {
197  const size_t ilen = record.size();
198  size_t i = 0;
199  for (const auto& field : record) {
200  out << csv_escape(field);
201  if (i + 1 != ilen) out << Delim;
202  i++;
203  }
204 
205  end_out();
206  return *this;
207  }
208 
212  void flush() {
213  out.flush();
214  }
215 
216  private:
217  template<
218  typename T,
219  csv::enable_if_t<
220  !std::is_convertible<T, std::string>::value
221  && !std::is_convertible<T, csv::string_view>::value
222  , int> = 0
223  >
224  std::string csv_escape(T in) {
225  return internals::to_string(in);
226  }
227 
228  template<
229  typename T,
230  csv::enable_if_t<
231  std::is_convertible<T, std::string>::value
232  || std::is_convertible<T, csv::string_view>::value
233  , int> = 0
234  >
235  std::string csv_escape(T in) {
236  IF_CONSTEXPR(std::is_convertible<T, csv::string_view>::value) {
237  return _csv_escape(in);
238  }
239 
240  return _csv_escape(std::string(in));
241  }
242 
243  std::string _csv_escape(csv::string_view in) {
250  // Do we need a quote escape
251  bool quote_escape = false;
252 
253  for (auto ch : in) {
254  if (ch == Quote || ch == Delim || ch == '\r' || ch == '\n') {
255  quote_escape = true;
256  break;
257  }
258  }
259 
260  if (!quote_escape) {
261  if (quote_minimal) return std::string(in);
262  else {
263  std::string ret(1, Quote);
264  ret += in.data();
265  ret += Quote;
266  return ret;
267  }
268  }
269 
270  // Start initial quote escape sequence
271  std::string ret(1, Quote);
272  for (auto ch: in) {
273  if (ch == Quote) ret += std::string(2, Quote);
274  else ret += ch;
275  }
276 
277  // Finish off quote escape
278  ret += Quote;
279  return ret;
280  }
281 
283  template<size_t Index = 0, typename... T>
284  typename std::enable_if<Index < sizeof...(T), void>::type write_tuple(const std::tuple<T...>& record) {
285  out << csv_escape(std::get<Index>(record));
286 
287  IF_CONSTEXPR (Index + 1 < sizeof...(T)) out << Delim;
288 
289  this->write_tuple<Index + 1>(record);
290  }
291 
293  template<size_t Index = 0, typename... T>
294  typename std::enable_if<Index == sizeof...(T), void>::type write_tuple(const std::tuple<T...>& record) {
295  (void)record;
296  end_out();
297  }
298 
300  void end_out() {
301  out << '\n';
302  IF_CONSTEXPR(Flush) out.flush();
303  }
304 
305  OutputStream & out;
306  bool quote_minimal;
307  };
308 
316  template<class OutputStream, bool Flush = true>
317  using CSVWriter = DelimWriter<OutputStream, ',', '"', Flush>;
318 
327  template<class OutputStream, bool Flush = true>
328  using TSVWriter = DelimWriter<OutputStream, '\t', '"', Flush>;
329 
331  template<class OutputStream>
332  inline CSVWriter<OutputStream> make_csv_writer(OutputStream& out, bool quote_minimal=true) {
333  return CSVWriter<OutputStream>(out, quote_minimal);
334  }
335 
337  template<class OutputStream>
338  inline CSVWriter<OutputStream, false> make_csv_writer_buffered(OutputStream& out, bool quote_minimal=true) {
339  return CSVWriter<OutputStream, false>(out, quote_minimal);
340  }
341 
343  template<class OutputStream>
344  inline TSVWriter<OutputStream> make_tsv_writer(OutputStream& out, bool quote_minimal=true) {
345  return TSVWriter<OutputStream>(out, quote_minimal);
346  }
347 
349  template<class OutputStream>
350  inline TSVWriter<OutputStream, false> make_tsv_writer_buffered(OutputStream& out, bool quote_minimal=true) {
351  return TSVWriter<OutputStream, false>(out, quote_minimal);
352  }
354 }
Class for writing delimiter separated values files.
Definition: csv_writer.hpp:135
DelimWriter & operator<<(const std::tuple< T... > &record)
Format a sequence of strings and write to CSV according to RFC 4180.
Definition: csv_writer.hpp:180
void flush()
Flushes the written data.
Definition: csv_writer.hpp:212
DelimWriter(OutputStream &_out, bool _quote_minimal=true)
Construct a DelimWriter over the specified output stream.
Definition: csv_writer.hpp:143
DelimWriter(const std::string &filename)
Construct a DelimWriter over the file.
Definition: csv_writer.hpp:150
~DelimWriter()
Destructor will flush remaining data.
Definition: csv_writer.hpp:155
DelimWriter & operator<<(const Container< T, Alloc > &record)
Format a sequence of strings and write to CSV according to RFC 4180.
Definition: csv_writer.hpp:196
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:168
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.
HEDLEY_CONST CONSTEXPR_14 long double pow10(const T &n) noexcept
Compute 10 to the power of n.
Definition: data_type.h:39
std::string to_string(T value)
to_string() for unsigned integers
Definition: csv_writer.hpp:23
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:350
CSVWriter< OutputStream > make_csv_writer(OutputStream &out, bool quote_minimal=true)
Return a csv::CSVWriter over the output stream.
Definition: csv_writer.hpp:332
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:338
TSVWriter< OutputStream > make_tsv_writer(OutputStream &out, bool quote_minimal=true)
Return a csv::TSVWriter over the output stream.
Definition: csv_writer.hpp:344
nonstd::string_view string_view
The string_view class used by this library.
Definition: common.hpp:75