概要
先日の投稿
CSVと文字エンコードで悩んで、後からUTF-8に変換した、ruby力低い話
http://qiita.com/rojiuratech/items/8e348937960bf76fb783
について、以下のような指摘があったこと、
- 文字エンコードがわかるのなら、指定したほうが良い
- 指定しないとデフォルトの文字コードと認識されてしまう。
- そもそも、最初からUTF-8で与えることはできないのか?
加えてソースコードが汚すぎたので、やり直しすことにしました。
先にオチ
- CSVデータの受け取りは、1行のコードで簡単にできる。
- NKFコマンドで強制的に変換することも可能
- 文字コードが不明な場合、NKFで推定してから指定する方法もあり
- LibreOffice使えればいいけど、エンジニア以外ほぼMSOfficeユーザ
CSVデータを2次元配列として受け取る処理は、1行で済んだ
文字エンコードを指定してファイルを読み込み、UTF-8に変換する処理は1行にまとめられました。
mod_ary = CSV.parse(File.read(path,encoding: 'cp932').encode("UTF-8",:invalid => :replace))
処理内容を細かく分けると
File.read(path,encoding: 'cp932')
encode("UTF-8",:invalid => :replace)
参考 encodeメソッドに関する解説
CSV.parse(data)
付帯処理を入れても、こんな短いソースに
require 'csv'
path = ARGV[0]
# CP932形式で読み込んだCSVファイルを、UTF-8形式に変換した上でCSVにパースする。
mod_ary = CSV.parse(File.read(path,encoding: 'cp932').encode("UTF-8",:invalid => :replace))
# 各セルの値を持つ2次元配列を表示
mod_ary.each do |str|
p str
end
実行例
こんな具合の、いかにもDBの中身をダンプしたようなCSVファイルがあるとして
品名,種類,値段
棍棒,武器,60
薬草,drug,10
毒草,drug,1
,(空欄),2000
いつもの,何か,45くらい
こんな具合にセルの中身を配列の要素とするものが取れる。
(1行ずつ表示しています)
あとはモデルに書き込むなりyamlなどに変換するなり、ご自由に。
$ ruby read_sjis_csv.rb fake.csv
["品名", "種類", "値段"]
["棍棒", "武器", "60"]
["薬草", "drug", "10"]
["毒草", "drug", "1"]
[nil, "(空欄)", "2000"]
["いつもの", "何か", "45くらい"]
文字コードを指定しないと、デフォルトの指定になってしまう。
Rubyでは、ファイルを読み込む際の文字コードを
Encoding.default_external
で決めています(デフォルトではUTF-8)
pry(main)> Encoding.default_external
=> #<Encoding:UTF-8>
したがって、ファイルを読み込む際に文字コードを指定しないと、本当は異なる文字コードなのにUTF-8とみなしてしまう危険も。
pry(main)> hige = IO.read("fake_sjis.csv")
=> "\x95i\x96\xBC,\x8E\xED\x97\xDE,\x92l\x92i\r\x9E\x9E\x96_,\x95\x90\x8A\xED,60\r\x96\xF2\x91\x90,drug,10\r\x93ő\x90,drug,1\r,(\x8B\u{D74C1}j,2000\r\x82\xA2\x82\u0082\xE0\x82\xCC,\x89\xBD\x82\xA9,45\x82\xAD\x82炢"
pry(main)> hige.encoding
=> #<Encoding:UTF-8>
文字コードがわからない場合、NKFを利用して、強制的に変換
本当は文字コードを指定しておいたほうがいいんだろうけど
NKFで、特定の文字コードに変換することも可能。
require 'nkf'
require 'csv'
encoded_sheet = CSV.parse(NKF::nkf('-w',File.read(path)))
nkfコマンドの構造はnkf(opt,str)
- opt コマンド
- str 変換対象
なので
NKF::nkf('-w',File.read(path))
の場合、pathで指定したファイルデータをすべて読み込み、強制的にUTFにする。
nkf(Rubyリファレンスマニュアル)
http://docs.ruby-lang.org/ja/1.9.3/class/NKF.html
文字コードが不明なものについてNKF.guessで推測し、そのコードで開く
文字コードが与えられていない場合、推定したコードで開く。
というのもありかもしれない。(File.readのオプションで、不正なコードを置き換えるなどの指定が可能なので)
この場合、NKF.guessで推定していますが、拡張子も判断材料に含め、
拡張子が .csv
かつ 文字コードが Shift-JIS
ならMS-Office向きに作成されたものとみなし cp932
で開く。というのもありかも。
require 'nkf'
require 'csv'
path = ARGV[0]
sample = File.read(path,10)
enc = NKF.guess(sample)
puts "エンコード名 #{enc.name}"
encoded_sheet = CSV.parse(File.read(path,encoding: enc.name).encode("UTF-8",:invalid => :replace ))
encoded_sheet.each do |str|
p str
end
$ ruby read_nkf_name.rb fake.csv
エンコード名 Shift_JIS
["品名", "種類", "値段"]
["棍棒", "武器", "60"]
["薬草", "drug", "10"]
["毒草", "drug", "1"]
[nil, "(空欄)", "2000"]
["いつもの", "何か", "45くらい"]
コマンドに関しては、ここも参考にしました
参考:nkfライブラリによる文字コードの推測
そもそも、最初からUTF-8で与えることはできないのか?
LibreOffice使えば、CSVで書き出すことも可能ですが
現在の環境では、Webエンジニアが私しかいない&布教コストを考えると・・・。
と、こんな感じで文字エンコードに関して長々と書くのでありました。