storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/csvparser/writer.go (about)

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in https://golang.org/LICENSE
     4  
     5  package csv
     6  
     7  import (
     8  	"bufio"
     9  	"io"
    10  	"strings"
    11  	"unicode"
    12  	"unicode/utf8"
    13  )
    14  
    15  // A Writer writes records using CSV encoding.
    16  //
    17  // As returned by NewWriter, a Writer writes records terminated by a
    18  // newline and uses ',' as the field delimiter. The exported fields can be
    19  // changed to customize the details before the first call to Write or WriteAll.
    20  //
    21  // Comma is the field delimiter.
    22  //
    23  // If UseCRLF is true, the Writer ends each output line with \r\n instead of \n.
    24  //
    25  // The writes of individual records are buffered.
    26  // After all data has been written, the client should call the
    27  // Flush method to guarantee all data has been forwarded to
    28  // the underlying io.Writer.  Any errors that occurred should
    29  // be checked by calling the Error method.
    30  type Writer struct {
    31  	Comma       rune // Field delimiter (set to ',' by NewWriter)
    32  	Quote       rune // Fields quote character
    33  	QuoteEscape rune
    34  	AlwaysQuote bool // True to quote all fields
    35  	UseCRLF     bool // True to use \r\n as the line terminator
    36  	w           *bufio.Writer
    37  }
    38  
    39  // NewWriter returns a new Writer that writes to w.
    40  func NewWriter(w io.Writer) *Writer {
    41  	return &Writer{
    42  		Comma:       ',',
    43  		Quote:       '"',
    44  		QuoteEscape: '"',
    45  		w:           bufio.NewWriter(w),
    46  	}
    47  }
    48  
    49  // Write writes a single CSV record to w along with any necessary quoting.
    50  // A record is a slice of strings with each string being one field.
    51  // Writes are buffered, so Flush must eventually be called to ensure
    52  // that the record is written to the underlying io.Writer.
    53  func (w *Writer) Write(record []string) error {
    54  	if !validDelim(w.Comma) {
    55  		return errInvalidDelim
    56  	}
    57  
    58  	for n, field := range record {
    59  		if n > 0 {
    60  			if _, err := w.w.WriteRune(w.Comma); err != nil {
    61  				return err
    62  			}
    63  		}
    64  
    65  		// If we don't have to have a quoted field then just
    66  		// write out the field and continue to the next field.
    67  		if !w.AlwaysQuote && !w.fieldNeedsQuotes(field) {
    68  			if _, err := w.w.WriteString(field); err != nil {
    69  				return err
    70  			}
    71  			continue
    72  		}
    73  
    74  		if _, err := w.w.WriteRune(w.Quote); err != nil {
    75  			return err
    76  		}
    77  
    78  		specialChars := "\r\n" + string(w.Quote)
    79  
    80  		for len(field) > 0 {
    81  			// Search for special characters.
    82  			i := strings.IndexAny(field, specialChars)
    83  			if i < 0 {
    84  				i = len(field)
    85  			}
    86  
    87  			// Copy verbatim everything before the special character.
    88  			if _, err := w.w.WriteString(field[:i]); err != nil {
    89  				return err
    90  			}
    91  			field = field[i:]
    92  
    93  			// Encode the special character.
    94  			if len(field) > 0 {
    95  				var err error
    96  				switch nextRune([]byte(field)) {
    97  				case w.Quote:
    98  					_, err = w.w.WriteRune(w.QuoteEscape)
    99  					if err != nil {
   100  						break
   101  					}
   102  					_, err = w.w.WriteRune(w.Quote)
   103  				case '\r':
   104  					if !w.UseCRLF {
   105  						err = w.w.WriteByte('\r')
   106  					}
   107  				case '\n':
   108  					if w.UseCRLF {
   109  						_, err = w.w.WriteString("\r\n")
   110  					} else {
   111  						err = w.w.WriteByte('\n')
   112  					}
   113  				}
   114  				field = field[1:]
   115  				if err != nil {
   116  					return err
   117  				}
   118  			}
   119  		}
   120  		if _, err := w.w.WriteRune(w.Quote); err != nil {
   121  			return err
   122  		}
   123  	}
   124  	var err error
   125  	if w.UseCRLF {
   126  		_, err = w.w.WriteString("\r\n")
   127  	} else {
   128  		err = w.w.WriteByte('\n')
   129  	}
   130  	return err
   131  }
   132  
   133  // Flush writes any buffered data to the underlying io.Writer.
   134  // To check if an error occurred during the Flush, call Error.
   135  func (w *Writer) Flush() {
   136  	w.w.Flush()
   137  }
   138  
   139  // Error reports any error that has occurred during a previous Write or Flush.
   140  func (w *Writer) Error() error {
   141  	_, err := w.w.Write(nil)
   142  	return err
   143  }
   144  
   145  // WriteAll writes multiple CSV records to w using Write and then calls Flush,
   146  // returning any error from the Flush.
   147  func (w *Writer) WriteAll(records [][]string) error {
   148  	for _, record := range records {
   149  		err := w.Write(record)
   150  		if err != nil {
   151  			return err
   152  		}
   153  	}
   154  	return w.w.Flush()
   155  }
   156  
   157  // fieldNeedsQuotes reports whether our field must be enclosed in quotes.
   158  // Fields with a Comma, fields with a quote or newline, and
   159  // fields which start with a space must be enclosed in quotes.
   160  // We used to quote empty strings, but we do not anymore (as of Go 1.4).
   161  // The two representations should be equivalent, but Postgres distinguishes
   162  // quoted vs non-quoted empty string during database imports, and it has
   163  // an option to force the quoted behavior for non-quoted CSV but it has
   164  // no option to force the non-quoted behavior for quoted CSV, making
   165  // CSV with quoted empty strings strictly less useful.
   166  // Not quoting the empty string also makes this package match the behavior
   167  // of Microsoft Excel and Google Drive.
   168  // For Postgres, quote the data terminating string `\.`.
   169  func (w *Writer) fieldNeedsQuotes(field string) bool {
   170  	if field == "" {
   171  		return false
   172  	}
   173  	if field == `\.` || strings.ContainsAny(field, "\r\n"+string(w.Quote)+string(w.Comma)) {
   174  		return true
   175  	}
   176  
   177  	r1, _ := utf8.DecodeRuneInString(field)
   178  	return unicode.IsSpace(r1)
   179  }