概要
gocsvで生成するCSVのすべてのセルをダブルクォーテーションで囲みたかった。
ネットで見つけた記事とは別パターンの実装をしてみた。
実装
方針
ネットで見つけた記事では、生成したCSVをデコードして、各セルをダブルクォーテーションで囲った後、もう一度エンコードしている。
自分は、以下の方法で実装してみた。
gocsvでは、デフォルトでは内部でencoding/csvのWriter構造体を使用している。そこで、
- gocsvのCSVWriterインターフェイスを実装する構造体を作成する
- ↑で作成した構造体に、encoding/csvのWriterの実装を参考に、すべてのセルをダブルクォーテーションで囲むWriteメソッドを生やす
- ↑をgocsvのMarshalCSVメソッドに渡す
gocsvのCSVWriterインターフェイス
type CSVWriter interface {
Write(row []string) error
Flush()
Error() error
}
↑を実装するカスタム構造体
type customCSVWriter struct {
w *bufio.Writer
}
func NewCustomCSVWriter(w io.Writer) gocsv.CSVWriter {
return &customCSVWriter{
w: bufio.NewWriter(w),
}
}
// Write writes すべてのセルをダブルクォーテーションで囲んだCSV
// encoding/csvのWriteメソッド(https://cs.opensource.google/go/go/+/refs/tags/go1.19.2:src/encoding/csv/writer.go;l=48)を一部改修したもの
// 違いは以下
// 1. すべてのセルをダブルクォーテーションで囲む
// 2. 区切り文字は`,`固定
// 3. 改行文字に`\r\n`を使用するオプションは無効
func (w *customCSVWriter) Write(record []string) error {
for n, field := range record {
if n > 0 {
if _, err := w.w.WriteRune(','); err != nil {
return err
}
}
if err := w.w.WriteByte('"'); err != nil {
return err
}
for len(field) > 0 {
// Search for special characters.
i := strings.IndexAny(field, "\"\r\n")
if i < 0 {
i = len(field)
}
// Copy verbatim everything before the special character.
if _, err := w.w.WriteString(field[:i]); err != nil {
return err
}
field = field[i:]
// Encode the special character.
if len(field) > 0 {
var err error
switch field[0] {
case '"':
_, err = w.w.WriteString(`""`)
case '\r':
err = w.w.WriteByte('\r')
case '\n':
err = w.w.WriteByte('\n')
}
field = field[1:]
if err != nil {
return err
}
}
}
if err := w.w.WriteByte('"'); err != nil {
return err
}
}
err := w.w.WriteByte('\n')
return err
}
func (w *customCSVWriter) Flush() {
w.w.Flush()
}
func (w *customCSVWriter) Error() error {
_, err := w.w.Write(nil)
return err
}
↑を呼び出すコード
buffer := bytes.Buffer{}
csvWriter := NewCustomCSVWriter(&buffer)
err := gocsv.MarshalCSV(&(CSVにしたい構造体など), csvWriter)