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 }