2015-11-24 71 views
6

我有這2 UTF-8字符串:紅寶石,比較字符串使用UTF-8字符的問題

a = "N\u01b0\u0303" 
b = "N\u1eef" 

他們看起來很不同,但都是一樣的,一旦他們被渲染:

irb(main):039:0> puts "#{a} - #{b}" 
Nữ - Nữ 

a版本是我存儲在數據庫中的版本。 b版本是一個來自瀏覽器的POST請求,我不知道爲什麼瀏覽器發送的是UTF8字符的不同組合,而且它並不總是發生,我無法重現該問題。我的開發環境,它發生在生產中並佔總請求的一定比例。

的情況是,我試圖比較他們兩人,但他們返回false

irb(main):035:0> a == b 
=> false 

我已經試過像迫使編碼不同的東西:

irb(main):022:0> c.force_encoding("UTF-8") == a.force_encoding("UTF-8") 
=> false 

另一個有趣的事實是:

irb(main):005:0> a.chars 
=> ["N", "ư", "̃"] 
irb(main):006:0> b.chars 
=> ["N", "ữ"] 

如何比較這些字符串?

+0

你從同一瀏覽器和操作系統獲得a和b嗎?看起來像特定的瀏覽器/操作系統字符渲染問題給我。 也許你可以嘗試點替換表,然後進行反向替換。 – Cyrill

回答

8

這是Unicode equivalence的問題。

aa版本的字符串包含字符ư(U + 01B0:拉丁字母小號帶喇叭),然後是U + 0303 COMBINING TILDE。顧名思義,第二個字符是combining character,它在渲染時與前一個字符結合生成最終的字形。

b版本的字符串使用字符(U + 1EEF,有角和TILDE拉丁小寫字母U)這是一個單個字符,並且是等效到先前的組合,但使用不同的字節序列代表它。

爲了比較這些字符串,你需要規範化它們,以便它們都對這些類型的字符使用相同的字節序列。當前版本的Ruby有內置的版本(在早期版本中需要使用第三方庫)。

所以目前你有

a == b 

這是false,但如果你

a.unicode_normalize == b.unicode_normalize 

你應該得到true

如果您使用的是較早版本的Ruby,則有幾個選項。Rails有normalize方法,其支持多字節的一部分,因此,如果您使用Rails,你可以這樣做:如果你沒有用Rails

ActiveSupport::Multibyte::Unicode.normalize(a) == ActiveSupport::Multibyte::Unicode.normalize(b) 

,則:

a.mb_chars.normalize == b.mb_chars.normalize 

或者是類似的東西你可以看一下unicode_utils gem,做這樣的事情:

UnicodeUtils.nfkc(a) == UnicodeUtils.nfkc(b) 

nfkc指範式,它是一樣的DEFA )

有各種不同的方法來標準化unicode字符串(即,無論你使用分解還是組合版本),而這個例子只是使用默認值。我會留給你研究差異。

+0

我正在使用Ruby 2.0.0p247,它看起來沒有集成這個模塊。任何第三方圖書館推薦?我發現[這一個](https://github.com/rubysl/rubysl-unicode_normalize),但沒有任何啓動Github,也有我安裝它的問題。 – fguillen

+0

@fguillen我已經通過回答更新了一些建議。你的問題被標記爲Rails,所以使用Rails的支持可能是我認爲的最好的解決方案。 – matt

+0

你是對的我在Rails的內部Unicode模塊中沒有想到。我已將這個案例添加到您的答案中,如果不正確請糾正。 – fguillen

3

你可以看到這些是不同的字符。 Firstsecond。在第一種情況下,它使用修飾符「combining tilde」。

維基百科對這樣的部分:

被定義爲規範等價代碼點序列被假定打印或顯示時具有相同的外觀和含義。例如,代碼點U + 006E(拉丁語小寫「n」)後接U + 0303(合成波形符「◌」)由Unicode定義爲與單個代碼點U + 00F1(小寫字母字母「ñ」的西班牙字母)。因此,這些序列應該以相同的方式顯示,應該以相同的方式對待,例如按字母順序排列的名稱或搜索,並且可以相互替代。

該標準還定義了一個文本規範化程序,被稱爲Unicode的歸一化,用於替換的字符的等價序列,使得是等同的任何兩個文本將被減少到的碼點的相同序列,稱爲原文的標準化格式或標準格式。

看來,Ruby支持這種標準化,但only as of Ruby 2.2

http://ruby-doc.org/stdlib-2.2.0/libdoc/unicode_normalize/rdoc/String.html

a = "N\u01b0\u0303".unicode_normalize 
b = "N\u1eef".unicode_normalize 

a == b # true 

或者,如果你正在使用Ruby on Rails,似乎是正常化built-in method