277 : out(&_out), quote_minimal(_quote_minimal) {}
280#if CSV_EXCEPTIONS_ENABLED
281 template<
typename T = OutputStream,
282 csv::enable_if_t<std::is_same<T, std::ofstream>::value,
int> = 0>
283 DelimWriter(
const std::string& filename,
bool _quote_minimal =
true)
284 :
DelimWriter(std::unique_ptr<OutputStream>(new OutputStream(filename, std::ios::out)),
286 if (!owned_out->is_open())
287 internals::throw_failed_open_for_writing(filename);
294 template<
typename T = OutputStream,
295 csv::enable_if_t<std::is_same<T, std::ofstream>::value,
int> = 0>
297 const std::string& filename,
CSVError& error,
bool quote_minimal =
true
300 std::unique_ptr<OutputStream> stream(
new OutputStream(filename, std::ios::out));
301 if (!stream->is_open()) {
303 std::string(internals::ERROR_FAILED_OPEN_WRITE) + filename);
306 return std::unique_ptr<DelimWriter>(
new DelimWriter(std::move(stream), quote_minimal));
313 : owned_out(std::move(other.owned_out)),
315 quote_minimal(other.quote_minimal),
316 auto_flush_(other.auto_flush_),
317 batch_buffer_(std::move(other.batch_buffer_)) {
318 if (owned_out && out) {
319 out = owned_out.get();
322 other.quote_minimal =
true;
325 DelimWriter& operator=(DelimWriter&& other)
noexcept {
326 if (
this == &other)
return *
this;
328 owned_out = std::move(other.owned_out);
330 quote_minimal = other.quote_minimal;
331 auto_flush_ = other.auto_flush_;
332 batch_buffer_ = std::move(other.batch_buffer_);
334 if (owned_out && out) {
335 out = owned_out.get();
339 other.quote_minimal =
true;
345 this->auto_flush_ = value;
351 this->auto_flush_ = value;
352 return std::move(*
this);
357 return this->auto_flush_;
364 if (!out || !ready_for_destruction(*out, 0))
return;
376 template<
typename T,
size_t N>
378 write_range_impl(record);
383 template<
typename T,
size_t N>
385 write_range_impl(record);
394#if CSV_EXCEPTIONS_ENABLED
395 template<
typename KeyType>
397 append_range_fields(frame.columns());
399 flush_batch_if_needed();
405 for (
const auto& row : frame) {
406 append_range_fields(std::vector<std::string>(row));
408 flush_batch_if_needed();
429 auto write_row(T&& record) ->
decltype(*this << std::forward<T>(record)) {
430 return *this << std::forward<T>(record);
442 template<
typename T,
typename U,
typename... Rest>
444 this->write_tuple<0>(std::forward_as_tuple(
445 std::forward<T>(first), std::forward<U>(second), std::forward<Rest>(rest)...));
465 template<std::ranges::input_range Rows>
468 for (
auto&& row : rows) {
469 append_row_like(row);
470 flush_batch_if_needed();
483 template<std::ranges::input_range Range>
485 requires std::ranges::input_range<Range>
486 && std::convertible_to<std::ranges::range_reference_t<Range>,
csv::string_view> {
487 write_range_impl(container);
492 template<
typename RowLike>
496 append_row_like(row);
509 template<
typename Range>
510 typename std::enable_if<
513 && !std::is_same<Range, std::string>::value
514 && !std::is_same<Range, csv::string_view>::value,
517 write_range_impl(record);
523 template<
typename... T>
525 this->write_tuple<0, T...>(record);
542 csv::enable_if_t<internals::has_checked_csv_fields<T>::value
543 || internals::is_data_frame<typename std::decay<T>::type>::value,
int> = 0>
545 return this->checked_write(error, std::forward<T>(record));
548 template<
typename... Args>
550 ->
decltype(this->
write_row(std::forward<Args>(args)...), bool()) {
551 return this->checked_write(error, std::forward<Args>(args)...);
556 template<std::ranges::input_range Rows>
557 requires (internals::csv_write_rows_input_range<Rows>
558 || internals::has_checked_csv_fields<std::ranges::range_reference_t<Rows>>::value)
560 return this->checked_operation(error, [
this, &rows, &error]() {
561 for (
auto&& row : rows) {
562 if (!this->append_checked_row(row, error, 0))
return false;
563 this->flush_batch_if_needed();
564 if (!this->check_write_status(error))
return false;
566 this->finish_write_call();
574 return this->checked_operation(error, [
this]() {
584 template<
typename T = OutputStream,
585 csv::enable_if_t<std::is_same<T, std::ofstream>::value,
int> = 0>
588 if (!owned_out || !out) {
590 "try_close() requires an open, owned output file.");
593 const bool flushed = this->
try_flush(error);
600 CSV_CATCH((
const std::ios_base::failure& failure), {
604 if (!flushed)
return false;
605 if (owned_out->fail()) {
612 DelimWriter(std::unique_ptr<OutputStream> stream,
bool quote_minimal)
613 : owned_out(std::move(stream)), out(owned_out.get()), quote_minimal(quote_minimal) {}
617 template<
typename Stream>
618 static auto exceptions_disabled(
const Stream& stream,
int)
619 ->
decltype(stream.exceptions(), bool()) {
620 return stream.exceptions() == std::ios_base::goodbit;
623 template<
typename Stream>
624 static bool exceptions_disabled(
const Stream&,
long) {
return true; }
626 template<
typename Stream>
627 static auto ready_for_destruction(
const Stream& stream,
int)
628 ->
decltype(stream.fail(), bool()) {
629#if CSV_EXCEPTIONS_ENABLED
630 return !stream.fail();
632 return !stream.fail() && exceptions_disabled(stream, 0);
636 template<
typename Stream>
637 static bool ready_for_destruction(
const Stream&,
long) {
return true; }
639 template<
typename Fn>
640 bool checked_operation(CSVError& error, Fn&& operation) {
645#if !CSV_EXCEPTIONS_ENABLED
646 if (!exceptions_disabled(*out, 0)) {
648 "Checked CSV output requires a stream with exceptions disabled.");
655 if (!std::forward<Fn>(operation)())
return false;
657 CSV_CATCH((
const std::ios_base::failure& failure), {
660 return this->check_write_status(error);
663 bool check_write_status(CSVError& error)
const {
665 "CSV output stream has failed.");
668 template<
typename... Args>
669 bool checked_write(CSVError& error, Args&&... args) {
670 return this->checked_operation(error, [&]() {
671 return this->checked_write_call(error, std::forward<Args>(args)...);
675 template<
typename Row>
676 static auto checked_record_valid(
const Row& row,
int)
677 ->
decltype(row.is_valid(), bool()) {
678 return row.is_valid();
681 template<
typename Row>
682 static bool checked_record_valid(
const Row&,
long) {
return true; }
685 bool checked_write_call(CSVError& error, T&& record) {
686 return this->checked_write_record(std::forward<T>(record), error, 0);
689 template<
typename T,
typename U,
typename... Rest>
690 bool checked_write_call(CSVError&, T&& first, U&& second, Rest&&... rest) {
691 this->
write_row(std::forward<T>(first), std::forward<U>(second), std::forward<Rest>(rest)...);
695 template<
typename KeyType>
696 bool checked_write_record(DataFrame<KeyType>& frame, CSVError& error,
int) {
697 return this->checked_write_record(
static_cast<const DataFrame<KeyType>&
>(frame), error, 0);
700 template<
typename KeyType>
701 bool checked_write_record(DataFrame<KeyType>&& frame, CSVError& error,
int) {
702 return this->checked_write_record(
static_cast<const DataFrame<KeyType>&
>(frame), error, 0);
705 template<
typename KeyType>
706 bool checked_write_record(
const DataFrame<KeyType>& frame, CSVError& error,
int) {
707 append_range_fields(frame.columns());
709 flush_batch_if_needed();
710 if (!this->check_write_status(error))
return false;
711 for (
const auto& row : frame) {
712 if (!this->append_checked_row(row, error, 0))
return false;
713 flush_batch_if_needed();
714 if (!this->check_write_status(error))
return false;
720 template<
typename Row>
721 auto checked_write_record(Row&& row, CSVError& error,
int)
722 ->
decltype(row.try_field(
size_t{0}, error).get_sv(), bool()) {
723 if (!this->append_checked_row(row, error, 0))
return false;
728 template<
typename Row>
729 bool checked_write_record(Row&& row, CSVError&,
long) {
737 template<
typename Row>
738 auto append_checked_row(Row&& row, CSVError& error,
int)
739 ->
decltype(row.try_field(
size_t{0}, error).get_sv(), bool()) {
740 if (!checked_record_valid(row, 0)) {
743 const size_t row_start = batch_buffer_.size();
744 for (
size_t i = 0; i < row.size(); ++i) {
745 const auto field = row.try_field(i, error);
749 batch_buffer_.resize(row_start);
752 if (i != 0) batch_buffer_.push_back(Delim);
753 write_escaped_field(field.get_sv());
760 template<
typename Row>
761 bool append_checked_row(Row&& row, CSVError&,
long) {
762 append_row_like(std::forward<Row>(row));
772 template<
typename Range>
773 inline void append_range_fields(Range&& record) {
774 auto it = std::begin(record);
775 auto end = std::end(record);
782 for (; it != end; ++it) {
783 batch_buffer_.push_back(Delim);
790 size_t pos = internals::find_next_non_special(in, 0, simd_sentinels_);
792 for (; pos < in.size(); ++pos) {
794 if (ch == Quote || ch == Delim || ch ==
'\r' || ch ==
'\n')
804 template<
typename Range>
805 inline void write_range_impl(
const Range& record) {
806 append_range_fields(record);
813 template<
typename Row>
814 void append_row_like(Row&& row) {
816 append_range_fields(std::forward<Row>(row));
819 append_range_fields(row.to_sv_range());
829 !std::is_convertible<T, std::string>::value
830 && !std::is_convertible<T, csv::string_view>::value
833 void write_field(T in) {
834 const std::string serialized = internals::to_string(in);
835 write_raw(serialized);
841 std::is_convertible<T, std::string>::value
842 || std::is_convertible<T, csv::string_view>::value
845 void write_field(T in) {
846 IF_CONSTEXPR(std::is_convertible<T, csv::string_view>::value) {
847 write_escaped_field(in);
850 const std::string serialized(in);
851 write_escaped_field(serialized);
857 batch_buffer_.append(in.data(), in.size());
861 const size_t first_special = find_first_special_for_writer(in);
863 if (first_special == in.size()) {
864 if (!quote_minimal) {
865 batch_buffer_.push_back(Quote);
867 batch_buffer_.push_back(Quote);
874 write_quoted_field(in, first_special);
878 batch_buffer_.push_back(Quote);
880 size_t chunk_start = 0;
881 size_t pos = first_special;
882 while (pos < in.size()) {
883 if (in[pos] == Quote) {
884 write_raw(in.substr(chunk_start, pos - chunk_start));
885 batch_buffer_.push_back(Quote);
886 batch_buffer_.push_back(Quote);
887 chunk_start = pos + 1;
893 write_raw(in.substr(chunk_start));
894 batch_buffer_.push_back(Quote);
898 template<
size_t Index = 0,
typename... T>
899 typename std::enable_if<Index <
sizeof...(T),
void>::type write_tuple(
const std::tuple<T...>& record) {
900 write_field(std::get<Index>(record));
902 CSV_MSVC_PUSH_DISABLE(4127)
903 IF_CONSTEXPR (Index + 1 < sizeof...(T)) batch_buffer_.push_back(Delim);
906 this->write_tuple<Index + 1>(record);
910 template<
size_t Index = 0, typename... T>
911 typename std::enable_if<Index == sizeof...(T),
void>::type write_tuple(const std::tuple<T...>& record) {
919 batch_buffer_.push_back(
'\n');
922 void finish_write_call() {
923 if (this->auto_flush_) {
929 flush_batch_if_needed();
933 if (batch_buffer_.empty())
return;
935 out->write(batch_buffer_.data(),
static_cast<std::streamsize
>(batch_buffer_.size()));
936 batch_buffer_.clear();
939 void flush_batch_if_needed() {
940 if (batch_buffer_.size() >= batch_flush_threshold_)
949 std::unique_ptr<OutputStream> owned_out;
955 bool auto_flush_ =
true;
956 static constexpr size_t batch_flush_threshold_ = 64 * 1024;
957 std::string batch_buffer_;
958 internals::SentinelVecs simd_sentinels_{Delim, Quote};