bufio.Scannerのend-of-line判断を変更してみる

  • 8
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

テキストファイルから1行ずつ取得して処理したい場合、
bufio.Scanner を使いますね。

bufio.Scanner には、Splitというメソッドがあり、
行末の判断基準を変更することができるようです。

デフォルトでは、LF(\n)または、CRLF(\r\n)を見つけると行末(end-of-line)だと判断して、
1行分のデータを返してくれます。
(CRLFが除かれたデータを取得できます)

http://golang.org/pkg/bufio/#ScanLines

改行が CR(\r) だけの場合には、正しく動作してくれません。

ということで、Scannerがデフォルトで使用している ScanLines を元にして、
チョイ足しで、改行が CR(\r)だけの場合に対応してみました。

まずは、type SplitFunc を実装する必要があります。
bufio の ScanLines を基にして、
3ステップ追加しただけですが、、、

func CustomScan(data []byte, atEOF bool) (advance int, token []byte, err error) {
    if atEOF && len(data) == 0 {
        return 0, nil, nil
    }
    var i int
    if i = bytes.IndexByte(data, '\n'); i >= 0 {
        // We have a full newline-terminated line.
        return i + 1, dropCR(data[0:i]), nil
    }
    if i = bytes.IndexByte(data, '\r'); i >= 0 {
        // ここを追加した。(CR があったら、そこまでのデータを返そう)
        return i + 1, data[0:i], nil
    }
    // If we're at EOF, we have a final, non-terminated line. Return it.
    if atEOF {
        return len(data), dropCR(data), nil
    }
    // Request more data.
    return 0, nil, nil
}

// dropCR drops a terminal \r from the data.
func dropCR(data []byte) []byte {
    if len(data) > 0 && data[len(data)-1] == '\r' {
        return data[0 : len(data)-1]
    }
    return data
}

このようなコードを自分のプログラムに追加して、
Splitメソッドに渡します。

func main() {
  var fp *os.File
  fp, _ = os.Open("./test.tsv")
  defer fp.Close()
  scanner := bufio.NewScanner(fp)
  scanner.Split(CustomScan)
  for scanner.Scan() {
    // 1行ずつ処理する
    ....
  }
}

それがどうした?って感じですが、
Atomエディタを使って、テキストファイルを作成したときに
改行が CR(\r) になってしまう現象に悩まされています。
Excel上のデータをコピーして、Atomにペースト(タブ区切り(TSV)の状態)すると発生します。
(解決策をご存知の方、教えてください。。。)

Atomエディタ側での解決策が見つからなかったので、bufioで何とかならんかとやってみた次第です。

もしかしたら、ReadString('\r') で対応してしまえばいいのかもしれませんが、
やっぱりCR,CRLFにも対応しておきたいし。

たいしたことはしていないですが、うまく動いたので良しです。