LoginSignup
13
3

More than 3 years have passed since last update.

BOM 付き UTF-8 csv ファイルのカラムを RSpec で検証する時は BOM を取り除く必要があった

Last updated at Posted at 2019-12-21

はじめに

BOM 付き UTF-8 の csv ファイルのカラム名を RSpec で検証しようと思ったときにハマったので、備忘録を残しておきます。

tl;dr

BOM 付き UTF-8 の csv ファイルのカラム名を RSpec で検証するときは、CSV.parse するときにBOM を取り除く必要がありました。

csv = CSV.parse(csv_content.sub(/^\xEF\xBB\xBF/, ''))

背景

RSpec でアプリ側で作成した csv ファイルが適切に出力されていたか確認するために、csv のカラム名を検証しようとしていました。

サンプルは以下のような形です。

require 'rails_helper'

RSpec.describe Sample do
  describe '#method' do
    it 'checks if csv columns is valid' do
      # Sample.csv_content が csv ファイルを出力するメソッド
      csv_content = Sample.csv_content
      csv = CSV.parse(csv_content)
      expect(csv[0][0]).to eq '日付'
      expect(csv[0][1]).to eq '時刻'
    end 
  end
end

Sample.csv_content は以下のような形です。方法については以下を参考にしました。
(実際の処理は BOM 付き csv ファイルをそのままダウンロードして出力するような処理だったので、BOM を無理やりつけてはいません)

BOM付きUTF-8のCSVを作成する(Excel文字化け対策) - Qiita

class Sample
  def self.csv_content
    # bom = %w(EF BB BF).map { |e| e.hex.chr }.join
    # コメント踏まえ 2019/12/24 修正
    bom = "\uFEFF"
    content = CSV.generate do |csv|
      csv << ['日付', '時刻']
      csv << ['2019/12/22', '14:00']
    end 
    bom + content
  end 
end

出力する csv ファイルは以下の通りです。こちらが BOM つき UTF-8 のエンコーディングになっています。

日付,時刻
2019/12/22,14:00

BOM 付きのままだと検証が失敗する

しかし実際に RSpec を実行しようとすると、expect(csv[0][0]).to eq '日付' の部分で検証チェックに失敗します。エラーは以下のような形です。

expect(csv[0][0]).to eq '日付' expected: "日付" got: "日付" (compared using ==)<

一見、expected と got が同じ値に見えるので、何が原因なのか最初はわかりづらいですね。。

RSpec 実行中にデバッガーで処理を止め、文字列エンコードを ascii-8bit にして確認すると、

expected: "\xE6\x97\xA5\xE4\xBB\x98"
got: "\xEF\xBB\xBF\xE6\x97\xA5\xE4\xBB\x98"

got のほうが \xEF\xBB\xBF 分だけ長いことがわかりました。これは BOM そのもので、CSV.parse しても、文字列先頭の BOM はそのままついてくるということでした。。

解決方法

CSV.parse する対象の csv 文字列から、BOM を取り除く処理を書きます。文字列の最初に表示される BOM のみ sub で空文字に置換するイメージです。

csv_content = Sample.csv_content
csv = CSV.parse(csv_content.sub(/^\xEF\xBB\xBF/, ''))

そもそも BOM とは

Byte Order Mark の略です。Wikipedia から引用します。

プログラムがテキストデータを読み込む時、その先頭の数バイトからそのデータがUnicodeで表現されていること、また符号化形式(エンコーディング)としてどれを使用しているかを判別できるようにしたものである。

UnicodeがはじまったころはアメリカではASCII、ヨーロッパなどではISO-8859、日本ではShift_JISやEUC-JPが主流であり、使用されている符号化方式がUnicodeであることを明確に区別する必要があった。その方法として、先頭のデータにテキスト以外のデータを入れることが発案された。

復数の国で異なるエンコーディングが使われていた中で、世界共通の文字コードとして扱われる Unicode との区別し、ファイルを開く際に Unicode のエンコーディングかどうかを判別できるようにしたのですね。

終わりに

なんとなく言葉だけ知っていた BOM について、この機会に改めて調べられてよかったです。

参考

13
3
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
3