ruby / stringio

Pseudo `IO` class from/to `String`.
BSD 2-Clause "Simplified" License
36 stars 26 forks source link

CP932を指定しても暗黙にUTF-8となる #13

Closed babatakao closed 4 years ago

babatakao commented 4 years ago

概要

CP932で初期化したStringIOに範囲外文字を渡すと、暗黙のうちにUTF-8に変換されます。

再現手順

require "stringio"

p (StringIO.new.set_encoding("cp932") << "あ").string.encoding
p (StringIO.new.set_encoding("cp932") << "あ🍣").string.encoding
p (StringIO.new.set_encoding("cp932") << "🍣").string.encoding
p (StringIO.new.set_encoding("cp932") << "a" << "🍣").string.encoding
p (StringIO.new.set_encoding("cp932") << "あ" << "🍣").string.encoding

結果

#<Encoding:Windows-31J>
#<Encoding:UTF-8>
#<Encoding:UTF-8>
#<Encoding:UTF-8>
Traceback (most recent call last):
        2: from test.rb:7:in `<main>'
        1: from test.rb:7:in `<<'
test.rb:7:in `write': incompatible character encodings: Windows-31J and UTF-8 (Encoding::CompatibilityError)

1番目と5番目は期待通りの動作です。 2,3,4番目は Encoding::CompatibilityError が発生すると期待していましたが、UTF-8に変換されています。

補足

当初 https://github.com/ruby/csv/issues/118 に報告したところStringIOの問題だと教えていただきました。

kou commented 4 years ago

@nobu どう思います?

nobu commented 4 years ago

直しました。 0.1.2を出そうと思います。

nobu commented 4 years ago

rubyのmasterにマージして試したところ、csvのテストでエラーになりました。

TestCSVEncodings#test_encoding_is_upgraded_for_ascii_content_during_writing_as_needed:
Encoding::CompatibilityError: incompatible character encodings: ISO-8859-1 and UTF-8

CSV.generate_line で最初の String である引数のエンコーディングに変換しているので、上の4番目と同じ状態です。 CSVとしてはどちらが期待される動作でしょうか。

kou commented 4 years ago

なるほどー! https://bugs.ruby-lang.org/issues/4063 のケースですか。

CSVとしては互換性を壊したくないのでこのテストは壊れないのがいいんですが、これはCSVの方で処理するのでStringIOはこのままでいいです!

diff --git a/lib/csv.rb b/lib/csv.rb
index 8389889..63309f3 100644
--- a/lib/csv.rb
+++ b/lib/csv.rb
@@ -1289,8 +1289,20 @@ class CSV
       str = +""
       if options[:encoding]
         str.force_encoding(options[:encoding])
-      elsif field = row.find {|f| f.is_a?(String)}
-        str.force_encoding(field.encoding)
+      else
+        fallback_encoding = nil
+        output_encoding = nil
+        row.each do |field|
+          next unless field.is_a?(String)
+          fallback_encoding ||= field.encoding
+          next if field.ascii_only?
+          output_encoding = field.encoding
+          break
+        end
+        output_encoding ||= fallback_encoding
+        if output_encoding
+          str.force_encoding(output_encoding)
+        end
       end
       (new(str, **options) << row).string
     end
kou commented 4 years ago

@nobu 0.1.2に https://github.com/ruby/stringio/commit/f528538d105378cb650749300e9b8d0da717362b が入っていなくてクラッシュするのでこのコミットを入れて0.1.3を出してもらえますか?

nobu commented 4 years ago

@kou 0.1.3を出しました。csvのほうはいつごろリリース予定でしょうか。

kou commented 4 years ago

リリースしました! stringio 0.1.3以上を必須にするようにしました。