search
LoginSignup
3
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

Organization

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

はじめに

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 について、この機会に改めて調べられてよかったです。

参考

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
What you can do with signing up
3
Help us understand the problem. What are the problem?