2015-11-24 12 views
6

Tôi có những 2 UTF-8 chuỗi:Ruby, vấn đề so sánh chuỗi với UTF-8 ký tự

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

Họ trông khá khác nhau nhưng đều giống nhau khi chúng được hiển thị:

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

Phiên bản a là phiên bản tôi đã lưu trữ trong DB. Phiên bản b là phiên bản đến từ trình duyệt trong yêu cầu POST, tôi không biết tại sao trình duyệt gửi kết hợp khác nhau của các ký tự UTF8 và không xảy ra luôn, tôi không thể tạo lại sự cố trong môi trường dev của tôi, nó xảy ra trong sản xuất và theo tỷ lệ phần trăm của tổng số yêu cầu.

trường hợp là tôi cố gắng để so sánh cả trong số họ nhưng họ trở false:

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

Tôi đã thử những điều khác nhau như buộc mã hóa:

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

Một thực tế thú vị là :

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

Làm cách nào để tôi có thể so sánh các loại chuỗi này?

+0

Bạn có nhận a và b từ cùng một trình duyệt và os không? Có vẻ như vấn đề hiển thị ký tự trình duyệt/nhân vật cụ thể đối với tôi. Có thể bạn có thể thử bảng thay thế điểm và sau đó thực hiện thay thế ngược. – Cyrill

Trả lời

8

Đây là vấn đề với Unicode equivalence.

Phiên bản a của chuỗi của bạn bao gồm các nhân vật ư (U + 01B0: LATIN NHỎ THƯ U VỚI HORN), tiếp theo là U + 0303 kết hợp dấu ngã. Ký tự thứ hai này, như tên cho thấy là combining character, khi kết xuất được kết hợp với ký tự trước đó để tạo ra hình tượng cuối cùng.

Phiên bản b của chuỗi sử dụng các nhân vật (U + 1EEF, LATIN NHỎ THƯ U VỚI HORN VÀ dấu ngã) mà là một nhân vật duy nhất, và là tương đương sự kết hợp trước, nhưng sử dụng một chuỗi byte khác nhau để đại diện cho nó.

Để so sánh các chuỗi này, bạn cần phải bình thường hóa chúng, để cả hai đều sử dụng cùng một chuỗi byte cho các loại ký tự này. Các phiên bản Ruby hiện tại đã được tích hợp sẵn (trong các phiên bản cũ hơn bạn cần sử dụng thư viện của bên thứ ba).

Vì vậy, hiện tại bạn có

a == b 

đó là false, nhưng nếu bạn làm

a.unicode_normalize == b.unicode_normalize 

bạn sẽ nhận được true.

Nếu bạn đang sử dụng phiên bản cũ hơn của Ruby, có một vài tùy chọn.Rails có một phương pháp normalize như một phần của sự ủng hộ nhiều byte của nó, vì vậy nếu bạn đang sử dụng Rails bạn có thể làm:

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

hoặc có lẽ một cái gì đó như:

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

Nếu bạn không sử dụng đường ray, sau đó bạn có thể nhìn vào unicode_utils gem, và làm điều gì đó như thế này:

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

(nfkc đề cập đến hình thức bình thường, nó cũng giống như các DEFA trong các kỹ thuật khác.)

Có nhiều cách khác nhau để bình thường hóa chuỗi unicode (tức là cho dù bạn sử dụng các phiên bản được phân tách hoặc kết hợp), và ví dụ này chỉ sử dụng mặc định. Tôi sẽ để nghiên cứu sự khác biệt cho bạn.

+0

Tôi đang sử dụng Ruby 2.0.0p247 có vẻ như không tích hợp mô-đun này. Bất kỳ thư viện phần ba nào được đề nghị? Tôi đã tìm thấy [này] (https://github.com/rubysl/rubysl-unicode_normalize) nhưng không phải bất kỳ bắt đầu trên Github và tôi cũng có vấn đề để cài đặt nó. – fguillen

+0

@fguillen Tôi đã cập nhật bằng câu trả lời với một số đề xuất. Câu hỏi của bạn được gắn thẻ với Rails, vì vậy việc sử dụng hỗ trợ của Rails có lẽ sẽ là giải pháp tốt nhất ở đây tôi nghĩ. – matt

+0

bạn đã đúng Tôi đã không nghĩ đến mô-đun Unicode bên trong của Rails.Tôi đã thêm ví dụ cho escenario này vào câu trả lời của bạn, vui lòng sửa nó nếu không đúng. – fguillen

3

Bạn có thể thấy đây là các ký tự riêng biệt. Firstsecond. Trong trường hợp đầu tiên, nó đang sử dụng công cụ sửa đổi "combining tilde".

Wikipedia có một phần về điều này:

chuỗi Mã thời điểm đó được quy định như giáo luật tương đương được giả định có sự xuất hiện cùng và ý nghĩa khi in hoặc hiển thị. Ví dụ, mã điểm U + 006E (chữ thường Latinh "n") theo sau là U + 0303 (dấu ngã kết hợp "◌̃") được định nghĩa bằng Unicode tương đương về mặt kinh điển với mã đơn U + 00F1 (chữ thường chữ "ñ" của bảng chữ cái tiếng Tây Ban Nha). Do đó, các trình tự đó sẽ được hiển thị theo cùng một cách, nên được xử lý theo cách tương tự bằng các ứng dụng như tên bảng chữ cái hoặc tìm kiếm, và có thể được thay thế cho nhau.

Các tiêu chuẩn cũng định nghĩa một thủ tục văn bản bình thường, được gọi là Unicode bình thường, có thể thay thế các chuỗi tương đương của các nhân vật để cho bất kỳ hai văn bản đó là tương đương sẽ được giảm xuống cùng một chuỗi các điểm mã , được gọi là biểu mẫu bình thường hoặc dạng thông thường của văn bản gốc.

Có vẻ như rằng Ruby hỗ trợ bình thường này, nhưng 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 

Ngoài ra, nếu bạn đang sử dụng Ruby on Rails, dường như có một built-in method cho bình thường.

Các vấn đề liên quan