【ruby/kconv/nkf】CSVデータをSJISへ変換したら半角カナが全角になって困った話
編集履歴
2018/10/07 string:encode
によるエンコーディング方法の追記
この記事のまとめ
-
kconv
を使って文字コード変換(tosjis
など)をすると、半角カナは全角になる
- 例)
ホゲ
→ ホゲ
対策はnkf
を使った文字コード変換を行うこと
# -x:半角カナを全角の変換を抑止
NKF.nkf('--sjis -x', csv_str)
- 文字コード返還は
nkf
を使った方が良いと感じた
-
nkf
はkconv
と比べて複雑なオプション指定が必要で分かりづらいが、意図しない変換を防ぐためにも、nkf
を使った方が良い
kconv
を使って文字コード変換(tosjis
など)をすると、半角カナは全角になる
- 例)
ホゲ
→ホゲ
対策はnkf
を使った文字コード変換を行うこと
# -x:半角カナを全角の変換を抑止
NKF.nkf('--sjis -x', csv_str)
nkf
を使った方が良いと感じた
-
nkf
はkconv
と比べて複雑なオプション指定が必要で分かりづらいが、意図しない変換を防ぐためにも、nkf
を使った方が良い
2018/10/07_追記箇所
- 頂いたコメントを参考にして素直に
string:encode
によるエンコーディングが良い。- Unicode系の文字コードを CP932(Windows 31J)といった変換をする際に、変換できない文字をどうするか考える必要がある。
-
nkf
は変換できない場合に当該文字が消えてしまうため、事前に適切な変換指定が必要 - 一方、
string:encode
は変換できない場合「?」に変換されること、変換できない場合の動作をオプションで指定できること、変換オプションが多様である。
追記箇所(終了)
こっから本文
上のまとめで言いたいこと全て述べているのですが、それだけでは味気ないので多少コードを交えながら説明します。
動作環境
- ruby
- ruby 2.5.1p57 (2018-03-29 revision 63029)
- rails
- Rails 5.2.0
CSVの文字コード
- ruby 2.5.1p57 (2018-03-29 revision 63029)
- Rails 5.2.0
Webアプリを作っているとOSやブラウザ毎の差を意識する必要があります。その中でも文字コードは意識しないと文字化けしてしまうことが頻繁にあります。
文字コードについては特にWindows/IE環境を意識する必要があり、SV側のUTF-8からCL側が取得する際はSJISへ変換することで文字化けを防ぐという取り組みはどこでも行なっているかと思います。
今回の文字コード変換を考える上での題材として、railsでcsvダウンロード機能を実装を考えて見たいと思います。
RailsでCSVダウンロード機能を考えてみる
railsでcsvダウンロード機能を実装を考える、だいたい以下のような実装になるかと思います。
ルーティング
formatにcsvを指定して、CSVを取得するルーティングを定義。
get 'messages/export', to: 'messages#export', format: 'csv'
コントローラー層
コントローラー層では、レスポンスヘッダーにCSVファイル名と文字コードを指定、及びCSVに含めるリソースを取得(@messages
)をするかと思います。
レスポンスヘッダーに文字コードを明示的に指定しないと、ブラウザが文字コードを自動判定してしまうので、IEやFirefoxでファイル名が文字化けしてしまいます。
class MessagesController < ApplicationController
def export
filename = "メッセージログ.csv"
encoded = URI.encode_www_form_component(filename)
headers['Content-Disposition'] = "attachment; filename=#{filename}; filename*=UTF-8''#{encoded}"
@messages = Message.where(params)
end
end
View
最後にViewで、csvの中身(Body)を作成し、文字コードをSJISに変換して完了です。
文字コード変換はkconv
モジュールのtosjis
メソッドで実行しています。
require 'kconv'
require 'csv'
header = ['id', 'メッセージ本文']
CSV.generate("", :headers => header, :write_headers => true) do |csv|
@messages.each do |message|
csv << [message['id'], message['text']]
end
# データが無い場合、headerが入らないので空データ追加
csv << [] if csv.lineno == 0
end.tosjis
これで想定通りのSJIS形式のCSVダウンロード機能が完成するのですが、想定外の動作が起きていました。
半角カナが全角へ変換されてしまう
冒頭で紹介した通り、kconv
モジュールのtosjis
メソッドを使ったSJISへの変換では半角カナが全角へと変換されてしまいます。これについてはリファレンスにしっかり書いてありました。
tosjis(str) -> String[permalink][rdoc]
文字列 str のエンコーディングを shift_jis に変換して返します。このメソッドは MIME エンコードされた文字列を展開し、いわゆる半角カナを全角に変換します。
https://docs.ruby-lang.org/ja/latest/class/Kconv.html#M_TOSJIS
対策はnkf
半角カナを全角へと変換することを防ぎたい場合は、nkf
というライブラリを使えば良いです。ちなみに先ほどのkconv
はnkf
のラッパークラスであり、kconv
の処理中でnkf
による変換処理が行われています。
nkf
はkconv
とは異なりオプションで様々な変換形式を指定することができるようになっています。
require 'nkf'
NKF.nkf('-s', str) # 変換元の文字コードを自動判定し、sjisへ変換
NKF.nkf('-w', str) # 変換元の文字コードを自動判定し、utf-8へ変換
それを踏まえて、今回のケースではnkf
ライブラリを用いて半角カナから全角への変換を抑止するオプションである-x
を指定した変換を行えばOKです。
require 'nkf'
require 'csv'
header = ['id', 'メッセージ本文']
csv_str = CSV.generate("", :headers => header, :write_headers => true) do |csv|
@messages.each do |message|
csv << [message['id'], message['text']]
end
# データが無い場合、headerが入らないので空データ追加
csv << [] if csv.lineno == 0
end
# NKFで変換(-x:半角カナを全角の変換を抑止)
NKF.nkf('--sjis -x', csv_str)
nkf
とkconv
の使い分けは???
以上nkf
とkconv
の使い方を見てましたが、二種類の文字コード変換ライブラリはどのように使えば良いのか疑問に思うかもしれません(自分は思いました)
主観的意見ですが、2つのメリット/デメリットは以下の点だと思います
kconv
kconvのメリット
-
nkf
のラッパークラスということもあり、変換メソッドがrubyの特徴である読みやすい、可読性のある書き方である(例:tosjis
)
kconvのデメリット
- 半角カナ→全角へ変換されてしまうように、細かい変換について痒いところに手が届かない
nkf
nkfのメリット
- 様々なオプション指定によって多様な変換方法を実現
- 明示的に変換指定を行うことができ、意図しない変換を防ぐことができる
nkfのデメリット
- オプション指定が複雑で覚えづらく、分かりづらい
ざっとこんなもんだと思います。
以上を踏まえて、個人的には 文字コード変換はnkf
を使った方が良い と感じました。その理由は意図しない変換を防げる点 です。やはり、今回のように意図しない変換によって、サービスを使うユーザーに悪影響を与えてしまう点は防ぎたいですしね。
2018/10/07_追記箇所(↓↓↓)
string:encode
を使う
@scivolaさんより頂いたコメントもとに、素直にstring::encode
を使えば、目的が果たせること、記述量がシンプルになりより良いことがわかりました。ありがとうございました。
require 'csv'
header = ['id', 'メッセージ本文']
CSV.generate("", :headers => header, :write_headers => true) do |csv|
@messages.each do |message|
csv << [message['id'], message['text']]
end
csv << [] if csv.lineno == 0
# encodeで`UTF-8`から`Shift_JIS`へ変換
end.encode("Shift_JIS", "UTF-8")
また、string::encode
は、nkf
よりも以下の点でより良いと感じました。
- Unicode系の文字コードを CP932(Windows 31J)といった変換をする際に、変換できない文字をどうするか考える必要がある。
-
nkf
は変換できない場合に当該文字が消えてしまうため、事前に適切な変換指定が必要 - 一方、
string:encode
は変換できない場合「?」に変換されること、変換できない場合の動作をオプションで指定できること、変換オプションが多様である。
CP932(Windows 31J)へ変換できない文字に対する動作を試してみると、以下のようになります。デフォルトでは例外送出で、オプション指定で変換できない場合の動作を指定できます。便利ですね!!!
> irb
irb(main):002:0> "\u{301C}"
=> "〜"
# 変換できない場合のデフォルトの動作
irb(main):003:0> "\u{301C}".encode(Encoding::Windows_31J)
Traceback (most recent call last):
3: from /Users/shinji.uyama/.anyenv/envs/rbenv/versions/2.5.1/bin/irb:11:in `<main>'
2: from (irb):3
1: from (irb):3:in `encode'
Encoding::UndefinedConversionError (U+301C from UTF-8 to Windows-31J)
# 変換できない場合置き換えを行う指定を行なった場合(undefオプション)
irb(main):004:0> "\u{301C}".encode(Encoding::Windows_31J, undef: :replace)
=> "?"
# 変換できない場合、指定文字へと置き換えを行う指定を行なった場合(undef+replaceオプション)
irb(main):006:0> "\u{301C}".encode(Encoding::Windows_31J, undef: :replace, replace: "UNDEF_CHARCTER!!!!")
=> "UNDEF_CHARCTER!!!!"
追記箇所(↑↑↑)
最後に
文字コードは前職の銀行システムを作っていた時も非常に悩まされた点ですが、IT業界に関わる上で文字コードは避けて通れない部分だと改めて感じました。今後とも理解を深めていきたいです。
参考文献
Author And Source
この問題について(【ruby/kconv/nkf】CSVデータをSJISへ変換したら半角カナが全角になって困った話), 我々は、より多くの情報をここで見つけました https://qiita.com/Ushinji/items/adf0ba077530ab812f59著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .