2012-04-17 61 views
25

我有一個字符串,我從某種輸入中讀取。ruby​​ 1.9,force_encoding,但是檢查

據我所知,它是UTF8。好的:

string.force_encoding("utf8") 

但是,如果這個字符串中包含的字節實際上不是合法的UTF8,我現在想知道並採取行動。

通常情況下,force_encoding(「utf8」)會遇到這樣的字節?我認爲它不會。

如果我在做#encode,我可以從方便的選項中選擇如何處理在源編碼(或目標編碼)中無效的字符。

但我沒有做#encode,我正在做一個#force_encoding。它沒有這樣的選擇。

會是有意義的

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

得到一個例外,對嗎?正常編碼 utf8 utf8沒有任何意義。但是,如果有無效字節,這可能是馬上讓它立即升起的方法嗎?或者使用: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,然後用別的東西來代替他們的 - 轉換爲不同的編碼時。但我不想想要轉換爲不同的編碼,我想留在utf8 - 但我可能想提出如果有一個無效的字節在那裏,或者我可能想用替換字符替換無效的字節。

難道沒有辦法讓ruby做到這一點嗎?

更新我相信這最終被添加到2.1中的ruby中,在2.1預覽版本中使用String#scrub來執行此操作。所以找那個!

回答

16

(更新:看https://github.com/jrochkind/scrub_rb

所以我編寫了一個解決方案,我需要的東西在這裏:https://github.com/jrochkind/ensure_valid_encoding/blob/master/lib/ensure_valid_encoding.rb

但只有更最近我才意識到這實際上是內置到STDLIB,你只需要,有些違反直覺,通過「二進制」作爲「源編碼」:

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

是的,這是正是我想要的。原來,這個內置的1.9版本stdlib,它只是沒有文檔,很少有人知道它(或許很少有人會說英語)。儘管我在某處的博客上看到了這種用法,所以別人也知道這一點!

+0

使用Ruby 1.9.3-p484,這錯誤地將ISO-8859-1文件中的\ xc0字節標記爲不正確的編碼。我發現,對於我的少數測試用例,編碼('binary',:undef =>:replace)似乎工作:iso-8859-1通過,但是序列不正確的UTF-8文件是抓住。 – 2014-02-10 19:56:06

+0

查看[這個新答案](http://stackoverflow.com/a/21686992/238886)的代碼,不會遇到我上面提到的問題。 – 2014-02-10 20:46:22

0

關於我能想到的唯一的事情就是轉碼的東西和背部,不會在往返損壞字符串:

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

似乎有點浪費,但。

+0

啊。除了浪費之外,它還要求你確定你知道什麼編碼可以循環而不丟失任何東西。我想要一個能夠處理任意輸入編碼的通用解決方案 - ruby​​知道如何在實際轉碼時使用任何編碼來做到這一點,爲什麼它不能爲我做到這一點?煩人。 – jrochkind 2012-04-18 01:42:09

+2

您可以隨時在任何UTF之間往返;無論您如何表示Unicode,Unicode都是Unicode。只有當你擺脫Unicode時,你纔會在翻譯中失去一些東西。 – 2012-04-19 00:29:43

+0

對,我想要一個不假設unicode的解決方案。 – jrochkind 2012-04-19 12:14:41

4

# 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 

更多的咆哮確保您的腳本文件本身被保存爲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] 

所以你的壞字被替換了,你可以馬上做如下

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

編輯:在這裏,在任意的編碼工作,首先僅僅編碼字符不好,第二個解決方案只是一個替代?

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 
+0

我不想將編碼更改爲ISO-8859-1。我想保留原始編碼。現在你會說「好吧,然後轉碼到8859 1,然後再回來。」我想要一個適用於任何編碼的解決方案;您無法將其轉碼爲8859,並且不會丟失任何編碼。 – jrochkind 2012-04-19 12:15:31

+0

好吧,只是編輯我的答案 – peter 2012-04-19 15:57:40

+0

謝謝。我獨立地得到了類似的東西,但是你能解釋一下它的作用:'c.encode!(Encoding.locale_charmap,:invalid =>:replace)'?這是一個轉碼嗎?我不希望對字符串進行轉碼(更改編碼),無論它開始使用什麼編碼以及我的默認區域設置編碼是什麼。但是我想我已經到達了你最終會考慮到這一點的地方,在這個問題上看到我的自我回答。 – jrochkind 2012-04-20 15:13:56

0

如果你是一個「真實」的用例這樣做 - 例如用於分析用戶輸入的不同的字符串,而不是隻爲的是能夠「解碼」完全隨機的文件所賦可以按照你的意願編寫儘可能多的編碼,那麼我想你至少可以假設每個字符串的所有字符都具有相同的編碼。

那麼,在這種情況下,你會怎麼想呢?

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 
} 

我不是紅寶石「親」以任何方式,所以請原諒,如果我的解決方法是錯誤的,甚至有點幼稚..

我只是想給回我所能,這是我來到的,而當我(我仍然)正在爲這個小解析器編寫任意編碼的字符串時,我正在爲一個研究項目做些什麼。

雖然我發佈這個,但我必須承認,我還沒有完全測試它..我只是得到了一些「積極」的結果,但我感到非常興奮,可能已經找到我是什麼努力尋找(並一直在閱讀關於這個的所有時間......),我只是覺得有必要儘快分享它,希望它能幫助人們節省一些時間給那些一直在尋找它的人只要我一直... ..如果它按預期工作:)

+0

這就是我最終做的:https://github.com/jrochkind/ensure_valid_encoding/ blob/master/lib/ensure_valid_encoding.rb 關鍵是我知道字符串_supposed_被編碼爲,但它可能有壞字節。你的解決方案更多地試圖猜測編碼是「真的」,這是一個不同的問題。 – jrochkind 2013-03-12 01:42:56

+0

回顧一下:1)你要麼有不好的編碼字符或數據損壞,(從你的github的理由你認爲這兩件事情可能是問題的原因),2)你似乎不關心錯誤的編碼,因爲你只想保持有效的UTF-8字符(你不要檢查壞數據是否對不同的編碼有效) - 人們建議轉換爲另一種編碼作爲檢查非有效字節的手段,但是你害怕丟失一些數據。如果你沒有首先驗證假定編碼的有效性,那麼有什麼意義?(如此可能會丟失數據?) – 2013-03-24 18:01:49

+0

謝謝你的回答,試圖說服我做我需要做的事情是愚蠢的,但顯然許多人不同意,因爲紅寶石將其添加到stdlib與在ruby 2.1中的字符串#擦洗!事實上,我明白自己在做什麼,並且在很多情況下這樣做是有意義的(在這種情況下,你是否嘗試過檢查vim或其他最喜歡的編輯器?),但是這張票不是爲了說服你這個事實。 – jrochkind 2014-11-06 20:25:50

0

一個簡單的方法挑起例外似乎是:

untrusted_string.match /./

+1

如果你只是想要一個無效字符串的異常,你可以簡單地這樣做:'引發Exception.new,除非string.valid_encoding?'它用替換字符替換壞字節更具挑戰性。 – jrochkind 2013-11-10 14:59:53

3

要檢查字符串沒有無效的序列,嘗試將其轉換爲二進制編碼

# 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 
0

這裏有兩個常見的情況,以及如何在紅寶石2.1+對付他們。我知道,這個問題涉及Ruby v1.9,但也許這有助於其他人通過Google找到此問題。

情況1

您有一個UTF-8字符串,可能具有一些無效字節
刪除無效字節:

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

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

情況2

你有一個字符串,可以是UTF-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: äöüß" 

注意

  • 上面的代碼片段假定紅寶石編碼您的所有字符串在UTF-8 。儘管這幾乎總是如此,但您可以通過# encoding: UTF-8開始腳本來確保這一點。

  • 如果無效,則可以通過編程方式檢測大多數多字節編碼,如UTF-8(在Ruby中,請參閱:#valid_encoding?)。但是,以編程方式檢測ISO-8859-1等單字節編碼的無效性不是(很容易)。因此,上述代碼片段不能以其他方式工作,即檢測字符串是否有效編碼ISO-8859-1

  • 即使UTF-8已經成爲在網絡,ISO-8859-1Latin1味道仍然在西方國家非常流行,特別是在北美地區的默認編碼越來越受歡迎。請注意,有幾個單字節編碼非常相似,但與ISO-8859-1略有不同。實例:CP1252(又名Windows-1252),ISO-8859-15

+0

雖然我不會傳遞一個參數來清理,但是我會將顯示爲unicode替換字符( )的壞字節全部刪除。我認爲默認是正確的適當的默認行爲。 – jrochkind 2016-02-18 21:25:14

+0

@jrochkind:我同意爲不同的應用程序你想有不同的行爲。如果一個人會查看轉換後的字符串,那麼很可能你想用缺省替換字符( )替換壞字節。但是,也有其他情況。舉一個例子:我來自哪裏,我們使用千兆字節的數據流進行不可靠的編碼。我們只想過濾某些信息。要正常工作,我們需要有效的UTF-8字符串,但我們不關心壞字節。在這種情況下,我建議刪除壞字節。 – 2016-02-19 23:19:50

+0

我確定有些情況是合適的,但它們是特殊用途的。無論有多少千兆字節的數據,我都不認爲我會想要一個不適當編碼的Macapá(巴西的一個城市)變成Macap(印度尼西亞的一個地方)而不是Macap 。作爲不知道某人的特殊用例的一般默認建議,使用unicode替換字符的例程默認是合適的 - 那些unicode人員知道他們在做什麼。 – jrochkind 2016-02-20 02:26:04