profile-image

masatora.net

gocsvですべてのセルをダブルクォーテーションで囲む

Go
CSV

概要

gocsvで生成するCSVのすべてのセルをダブルクォーテーションで囲みたかった。 ネットで見つけた記事とは別パターンの実装をしてみた。

実装

方針

ネットで見つけた記事では、生成したCSVをデコードして、各セルをダブルクォーテーションで囲った後、もう一度エンコードしている。

自分は、以下の方法で実装してみた。 gocsvでは、デフォルトでは内部でencoding/csvのWriter構造体を使用している。そこで、

  1. gocsvのCSVWriterインターフェイスを実装する構造体を作成する
  2. ↑で作成した構造体に、encoding/csvのWriterの実装を参考に、すべてのセルをダブルクォーテーションで囲むWriteメソッドを生やす
  3. ↑を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)

参考