web-dev-qa-db-ja.com

ruby 1.9、force_encoding、ただしチェック

ある種の入力から読み取った文字列があります。

私の知る限り、これはUTF8です。はい:

string.force_encoding("utf8")

しかし、この文字列に実際には有効なUTF8ではないバイトが含まれている場合は、今すぐに知りたいと思います。

通常、そのようなバイトに遭遇した場合、force_encoding( "utf8")は発生しますか?私は信じないそれはしません。

#encode を実行している場合は、ソースエンコーディング(または宛先エンコーディング)で無効な文字をどうするかという便利なオプションから選択できます。

しかし、私は#encodeではなく、#force_encodingを実行しています。そのようなオプションはありません。

それは意味がありますか

string.force_encoding("utf8").encode("utf8")

すぐに例外を取得するには?通常、fromutf8toをエンコードしても意味がありません。しかし、これが無効なバイトがある場合にすぐに発生させる方法でしょうか?または、:replaceオプションなど、無効なバイトで何か違うことをする?

しかし、いいえ、それもうまくいくようには見えません。

誰か知ってる?

1.9.3-p0 :032 > a = "bad: \xc3\x28 okay".force_encoding("utf-8")
=> "bad: \xC3( okay"
1.9.3-p0 :033 > a.valid_encoding?
=> false

わかりましたが、どのようにしてこれらの不良バイトを見つけて排除しますか?奇妙なことに、これは発生しません:

1.9.3-p0 :035 > a.encode("utf-8")
 => "bad: \xC3( okay"

別のエンコーディングに変換していた場合は、そうなります!

1.9.3-p0 :039 > a.encode("ISO-8859-1")
Encoding::InvalidByteSequenceError: "\xC3" followed by "(" on UTF-8

または、私が言った場合、「?」に置き換えられます。 =>

1.9.3-p0 :040 > a.encode("ISO-8859-1", :invalid => :replace)
=> "bad: ?( okay"

つまり、Rubyには、utf-8の不良バイトが何であるかを知って、emを別のエンコーディングに変換するときに、emを別のものに置き換えるという賢さがあります。しかし、私は別のエンコーディングに変換したくないので、utf8のままにしておきたい-しかし、そこに無効なバイトがある場合はレイズしたいかもしれないし、そうしたいかもしれない無効なバイトを置換文字で置き換える。

Rubyこれを行う方法はありませんか?

updateこれは、Rubyに2.1で追加され、2.1プレビューリリースにString#scrubが存在するようになりました。これを行うには、それを探してください!

25
jrochkind

(更新: https://github.com/jrochkind/scrub_rb を参照)

だから私はここに必要なものの解決策をコード化しました: https://github.com/jrochkind/ensure_valid_encoding/blob/master/lib/ensure_valid_encoding.rb

しかし、つい最近、これが実際にIS stdlibに組み込まれていることに気付きました。直感に反して、「ソースエンコーディング」として「バイナリ」を渡す必要があるだけです。

a = "bad: \xc3\x28 okay".force_encoding("utf-8")
a.encode("utf-8", "binary", :undef => :replace)
=> "bad: �( okay"

うん、それがまさに私が欲しかったものです。 IS 1.9 stdlibに組み込まれています。これは文書化されておらず、ほとんどの人がそれを知っていません(または英語を話す人がそれを知っているのではないでしょうか?)。どこかにブログがあるので、誰か他の人がそれを知っていました!

16
jrochkind

Ruby 2.1では、stdlibはscrubでこれをサポートします。

http://Ruby-doc.org/core-2.1.0/String.html#method-i-scrub

6
jrochkind

スクリプトファイル自体がUTF8として保存されていることを確認し、次のことを試してください

# encoding: UTF-8
p [a = "bad: \xc3\x28 okay", a.valid_encoding?]
p [a.force_encoding("utf-8"), a.valid_encoding?]
p [a.encode!("ISO-8859-1", :invalid => :replace), a.valid_encoding?]

これは私のwindows7システムに次を与えます

["bad: \xC3( okay", false]
["bad: \xC3( okay", false]
["bad: ?( okay", true]

だからあなたの悪いcharは置き換えられます、あなたは次のようにすぐにそれを行うことができます

a = "bad: \xc3\x28 okay".encode!("ISO-8859-1", :invalid => :replace)
=> "bad: ?( okay"

編集:ここでは任意のエンコーディングで機能するソリューションです。最初のコードは悪い文字のみをエンコードし、2番目は単に?

def validate_encoding(str)
  str.chars.collect do |c| 
    (c.valid_encoding?) ? c:c.encode!(Encoding.locale_charmap, :invalid => :replace)
  end.join 
end

def validate_encoding2(str)
  str.chars.collect do |c| 
    (c.valid_encoding?) ? c:'?'
  end.join 
end

a = "bad: \xc3\x28 okay"

puts validate_encoding(a)                  #=>bad: ?( okay
puts validate_encoding(a).valid_encoding?  #=>true


puts validate_encoding2(a)                  #=>bad: ?( okay
puts validate_encoding2(a).valid_encoding?  #=>true
4
peter

文字列に無効なシーケンスがないことを確認するには、文字列をbinaryエンコーディングに変換してみます。

# Returns true if the string has only valid sequences
def valid_encoding?(string)
  string.encode('binary', :undef => :replace)
  true
rescue Encoding::InvalidByteSequenceError => e
  false
end

p valid_encoding?("\xc0".force_encoding('iso-8859-1'))    # true
p valid_encoding?("\u1111")                               # true
p valid_encoding?("\xc0".force_encoding('utf-8'))         # false

バイナリで表現できない有効なシーケンスがあるかどうかは気にしないため、このコードは未定義の文字を置き換えます。無効なシーケンスがあるかどうかのみを考慮します。

このコードを少し変更すると、実際のエラーが返されます。これには、不適切なエンコードに関する貴重な情報が含まれています。

# Returns the encoding error, or nil if there isn't one.

def encoding_error(string)
  string.encode('binary', :undef => :replace)
  nil
rescue Encoding::InvalidByteSequenceError => e
  e.to_s
end

# Returns truthy if the string has only valid sequences

def valid_encoding?(string)
  !encoding_error(string)
end

puts encoding_error("\xc0".force_encoding('iso-8859-1'))    # nil
puts encoding_error("\u1111")                               # nil
puts encoding_error("\xc0".force_encoding('utf-8'))         # "\xC0" on UTF-8
3
Wayne Conrad

2つの一般的な状況と、それらをRuby 2.1 +で処理する方法を次に示します。質問はRuby v1.9を参照していますが、これはGoogleでこの質問を見つける他の人に役立つかもしれません。

状況1

TF-8文字列に無効なバイトが含まれている可能性があります
無効なバイトを削除します:

str = "Partly valid\xE4 UTF-8 encoding: äöüß"

str.scrub('')
 # => "Partly valid UTF-8 encoding: äöüß"

状況2

TF-8またはISO-8859-1エンコーディングのいずれかである可能性がある文字列があります
どのエンコーディングかを確認し、UTF-8に変換します(必要な場合):

str = "String in ISO-8859-1 encoding: \xE4\xF6\xFC\xDF"

unless str.valid_encoding?
  str.encode!( 'UTF-8', 'ISO-8859-1', invalid: :replace, undef: :replace, replace: '?' )
end #unless
 # => "String in ISO-8859-1 encoding: äöüß"

メモ

  • 上記のコードスニペットは、Rubyがデフォルトですべての文字列をUTF-8にエンコードすることを前提としています。ただし、ほとんどの場合、これが当てはまりますが、スクリプトを# encoding: UTF-8

  • 無効な場合、UTF-8(Rubyの場合は#valid_encoding?を参照)などのほとんどのマルチバイトエンコーディングをプログラムで検出できます。ただし、ISO-8859-1のようなシングルバイトエンコーディングの無効性をプログラムで検出することは(簡単に)不可能です。したがって、上記のコードスニペットは逆に機能しません。つまり、文字列が有効なISO-8859-1エンコーディングであるかどうかを検出します。

  • UTF-8は、Webのデフォルトのエンコーディングとしてますます人気が高まっていますが、ISO-8859-1およびその他のLatin1フレーバーは、欧米諸国、特に北米では依然として非常に人気があります。非常によく似ていますが、ISO-8859-1とは少し異なるいくつかのシングルバイトエンコーディングがあることに注意してください。例:CP1252(別名Windows-1252)、ISO-8859-15

0

例外を引き起こす簡単な方法は次のようです:

untrusted_string.match /./

0
Tallak Tveide

これを「実際の」ユースケースで実行している場合-たとえば、ユーザーが入力したさまざまな文字列を解析するためであり、できるだけ多くのエンコーディングで構成される完全にランダムなファイルを「デコード」できるようにするためだけではありません。あなたが望むように、私はあなたが少なくとも各文字列のすべての文字が同じエンコーディングを持っていると仮定できると思います。

次に、この場合、これについてどう思いますか?

strings = [ "UTF-8 string with some utf8 chars \xC3\xB2 \xC3\x93", 
             "ISO-8859-1 string with some iso-8859-1 chars \xE0 \xE8", "..." ]

strings.each { |s| 
    s.force_encoding "utf-8"
    if s.valid_encoding?
        next
    else
        while s.valid_encoding? == false 
                    s.force_encoding "ISO-8859-1"
                    s.force_encoding "..."
                end
        s.encode!("utf-8")
    end
}

私はRuby「プロ」ではありません。ですから、私の解決策が間違っていたり、少しでもナイーブだったりした場合は、ご容赦ください。

私ができることを返そうとするだけで、これが私が達成したものです。私が(まだ)私が研究プロジェクトのために行っている、任意にエンコードされた文字列のこの小さなパーサーに取り組んでいました。

私がこれを投稿している間、私はそれを完全にテストしてさえいないことを認めなければなりません..私はいくつかの「肯定的な」結果を得ただけですが、私が見つけるのに苦労していたものを見つけることができたことにとても興奮しました(そして、私がこれについてSOで読んだすべての時間の間、私はこれをできるだけ早く共有する必要性を感じただけであり、私がこれを探している限り、これを探していたすべての人に時間を節約するのに役立つことを願っていますされている... ..それが期待どおりに動作する場合:)

0
jj_

私が考えることができる唯一のことについては、往復で文字列に損傷を与えない何かにトランスコードすることです:

string.force_encoding("UTF-8").encode("UTF-32LE").encode("UTF-8")

ただし、かなり無駄が多いようです。

0
Mark Reed

わかりました、これは本当に不純な純粋な方法ですRubyそれを行う方法を私は考えました。それはおそらくがらくたのために実行されます。一体何をしているのですか?ルビー?現れて私たちにもっと良いものを与えてください。

 # Pass in a string, will raise an Encoding::InvalidByteSequenceError
 # if it contains an invalid byte for it's encoding; otherwise
 # returns an equivalent string.
 #
 # OR, like String#encode, pass in option `:invalid => :replace`
 # to replace invalid bytes with a replacement string in the
 # returned string.  Pass in the
 # char you'd like with option `:replace`, or will, like String#encode
 # use the unicode replacement char if it thinks it's a unicode encoding,
 # else ascii '?'.
 #
 # in any case, method will raise, or return a new string
 # that is #valid_encoding?
 def validate_encoding(str, options = {})
   str.chars.collect do |c|
     if c.valid_encoding?
       c
     else
       unless options[:invalid] == :replace
         # it ought to be filled out with all the metadata
         # this exception usually has, but what a pain!
         raise  Encoding::InvalidByteSequenceError.new
       else
         options[:replace] || (
          # surely there's a better way to tell if
          # an encoding is a 'Unicode encoding form'
          # than this? What's wrong with you Ruby 1.9?
          str.encoding.name.start_with?('UTF') ?
             "\uFFFD" :
             "?" )
       end
     end 
   end.join
 end

http://bibwild.wordpress.com/2012/04/17/checkingfixing-bad-bytes-in-Ruby-1-9-char-encoding/ でさらに怒鳴る

0
jrochkind