いろいろな文字コードのCSV

2022-06-01

csv, ruby

CSV読み込みエラーの問い合わせが多い

csv食わせてインポート とかよく作ると思う。
コレを作るとたいてい「インポート失敗します~」の問い合わせが絶えない

UTF-8にしてくださいね
Windows-31Jにしてくださいね
BOMを取り除いてくださいね
ダブルクォートで囲ってね(囲わないでね)
カラムの中にクォート使わないでね

言ってみるものの、あまり伝わらない。
だからいろんなファイル作ってしっかりテストしておきたい。

でも文字コード違いのファイル手で作るのめんどくさい…
という気持ちからスクリプトを書いた。

ruby any_csv.rb hoge.csv

nkf使えばUTF-8以外も受け付けられそうね

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
require 'csv'
require 'fileutils'

class AnyCsv
  BOM = "\uFEFF"

  attr_reader *%i[src_path base_name put_options put_path]

  def initialize(src_path)
    @src_path  = src_path
    @base_name = File.basename(src_path, ".*")
    @put_path = 'anycsv'
    @put_options = PutOptions[
      Encoding::SHIFT_JIS,
      Encoding::WINDOWS_31J,
      Encoding::UTF_8,
      bom: nil, quotes: [true, false]]
    @put_options += PutOptions[Encoding::UTF_8, bom: true, quotes: [true, false]] # bom付き

    FileUtils.mkdir_p(put_path)
  end

  # foo.csv => foo_UTF-8_BOM_QUOTED.csv
  def output_file(put_option)
    filename = [base_name, put_option.enc]
    filename << "BOM" if put_option.bom
    filename << "QUOTED" if put_option.quote
    filename = filename.join('_') + '.csv'

    File.join(put_path, filename)
  end

  def encoding(put_option)
    encoding = "UTF-8:#{put_option.enc}"
    encoding = "BOM|#{encoding}" if put_option.enc == Encoding::UTF_8
    encoding
  end

  def csv_options(put_option)
    { encoding: encoding(put_option),
      liberal_parsing: true,
      invalid: :replace,
      undef: :replace,
      replace: "*" }
  end

  def make
    put_options.each do |put_option|
      File.open(output_file(put_option), 'wb') do |f|
        f.write BOM if put_option.bom

        csv = CSV.new(f, force_quotes: put_option.quote)

        CSV.foreach(src_path, **csv_options(put_option)) do |row|
          csv << row
        end
      end
    end
  end

  class PutOptions
    def self.[](*encodings, bom: nil, quotes: nil)
      encodings.map do |enc|
        (quotes || [true]).map do |quote|
          PutOption.build(enc, bom:, quote:)
        end
      end.flatten
    end
  end

  class PutOption
    attr_reader :enc, :bom, :quote

    def self.build(encode, bom: nil, quote: true)
      new(encode, bom, quote)
    end

    def initialize(encode, bom, quote)
      @enc = encode
      @bom = bom if encode == Encoding::UTF_8
      @quote = quote
    end
  end
end

AnyCsv.new(ARGV[0]).make

コメント

投稿する

投稿したコメントはご自身で削除できません

不適切なコメントと判断した場合は管理側で削除することがあります