Computer >> Máy Tính >  >> Lập trình >> Ruby

Chuẩn hóa Unicode trong Ruby

Gần đây tôi đã xuất bản một bài báo trong đó tôi đã thử nghiệm hầu hết các phương thức chuỗi của Ruby với một số ký tự Unicode nhất định để xem liệu chúng có hoạt động không mong muốn hay không. Nhiều người trong số họ đã làm.

Một số người chỉ trích bài báo đó là tôi đang sử dụng các chuỗi không chuẩn hóa để thử nghiệm. Thành thật mà nói, tôi đã hơi mờ nhạt trong việc chuẩn hóa Unicode. Tôi nghi ngờ rằng nhiều người theo chủ nghĩa Ruby.

Sử dụng chuẩn hóa, bạn có thể lấy nhiều chuỗi Unicode hoạt động không mong muốn trong các thử nghiệm của tôi và chuyển đổi chúng thành các chuỗi hoạt động tốt với các phương thức chuỗi của Ruby. Tuy nhiên:

  1. Chuyển đổi không phải lúc nào cũng hoàn hảo. Một số chuỗi unicode sẽ luôn khiến các phương thức chuỗi của Ruby hoạt động sai.
  2. Đó là điều bạn phải làm theo cách thủ công. Cả Ruby, Rails và DB đều không tự động chuẩn hóa theo mặc định.

Bài viết này sẽ giới thiệu ngắn gọn về chuẩn hóa Unicode trong Ruby. Hy vọng rằng nó sẽ cung cấp cho bạn một điểm khởi đầu cho những khám phá của riêng bạn.

Hãy chuẩn hóa một chuỗi

String#unicode_normalize phương thức được giới thiệu trong Ruby 2.2. Được viết bằng Ruby, nó không nhanh bằng các thư viện chuẩn hóa như utf8_proc và unicode gems tận dụng C.

Lý do chúng ta cần chuẩn hóa là trong Unicode có nhiều cách để viết một ký tự. Chữ cái "Å" có thể được biểu diễn dưới dạng điểm mã "\u00c5" hoặc như thành phần của chữ cái "A" và dấu:"A\u030A" .

Chuẩn hóa Unicode trong Ruby

Chuẩn hóa chuyển đổi một dạng này sang dạng khác:

"A\u030A".unicode_normalize        #=> 'Å' (same as "\u00C5")

Tất nhiên, không chỉ có một cách để chuẩn hóa Unicode. Điều đó sẽ quá đơn giản! Có bốn cách để chuẩn hóa, được gọi là "các hình thức chuẩn hóa". Chúng được đặt tên bằng cách sử dụng các từ viết tắt khó hiểu:NFD, NFC, NFKD và NFKC.

String#unicode_normalize sử dụng NFC theo mặc định, nhưng chúng tôi có thể yêu cầu nó sử dụng một dạng khác như sau:

"a\u0300".unicode_normalize(:nfkc)       #=> 'à' (same as "\u00E0")

Nhưng điều này thực sự có nghĩa là gì? Bốn hình thức chuẩn hóa thực sự làm gì? Hãy cùng xem.

Hình thức chuẩn hóa

Có hai loại hoạt động chuẩn hóa:

  • Thành phần: Chuyển đổi các ký tự nhiều điểm mã thành các điểm mã duy nhất. Ví dụ:"a\u0300" trở thành "\u00E0" , cả hai đều là cách mã hóa ký tự à .
  • Phân hủy: Sự đối lập của bố cục. Chuyển đổi các ký tự điểm mã đơn thành nhiều điểm mã. Ví dụ:"\u00E0" trở thành "a\u0300" .

Mỗi thành phần và phân hủy có thể được thực hiện theo hai cách:

  • Hợp quy: Bảo tồn glyphs. Ví dụ:"2⁵" còn lại "2⁵" mặc dù một số hệ thống có thể không hỗ trợ ký tự superscript-five.
  • Khả năng tương thích: Có thể thay thế glyphs bằng các ký tự tương thích của chúng. Ví dụ:"2⁵" sẽ được chuyển đổi thành "2 5" .

Hai hoạt động và hai tùy chọn được kết hợp theo nhiều cách khác nhau để tạo ra bốn "biểu mẫu chuẩn hóa". Tôi đã liệt kê tất cả chúng trong bảng bên dưới, cùng với các mô tả và ví dụ về đầu vào và đầu ra:

Tên Mô tả Đầu vào Đầu ra
NFD Phân tích hợp quy Å "\u00c5" Å "A\u030A"
NFC Phân tích hợp quy được theo sau bởi Thành phần hợp quy Å "A\u030A" Å "\u00c5"
NFKD Phân tích khả năng tương thích ẛ̣ "\u1e9b\u0323" "\u0073\u0323\u0307"
NFKC Phân tích khả năng tương thích Theo sau bởi Thành phần hợp quy ẛ̣ "\u1e9b\u0323" "\u1e69"

Nếu bạn nhìn vào bảng này trong vài phút, bạn có thể bắt đầu nhận thấy rằng các từ viết tắt có ý nghĩa:

  • "NF" là viết tắt của "hình thức chuẩn hóa".
  • "D" là viết tắt của "phân hủy"
  • "C" là viết tắt của "thành phần"
  • "K" là viết tắt của "kompatibility" :)

Để có thêm ví dụ và giải thích kỹ thuật kỹ lưỡng hơn, hãy xem Phụ lục số 15 của Chuẩn Unicode.

Chọn hình thức chuẩn hóa

Hình thức chuẩn hóa bạn nên sử dụng tùy thuộc vào nhiệm vụ hiện tại. Các đề xuất của tôi bên dưới dựa trên Câu hỏi thường gặp về chuẩn hóa Unicode.

Sử dụng NFC để tương thích với chuỗi

Nếu mục tiêu của bạn là làm cho các phương thức chuỗi của Ruby hoạt động tốt với hầu hết Unicode, thì rất có thể bạn muốn sử dụng NFC. Có một lý do khiến nó là mặc định cho String#unicode_normalize .

  • Nó tổng hợp các ký tự nhiều điểm mã thành các điểm mã duy nhất nếu có thể. Các ký tự nhiều điểm mã là nguồn gốc của hầu hết các vấn đề với các phương thức Chuỗi.
  • Nó không làm thay đổi glyphs, vì vậy người dùng cuối của bạn sẽ không nhận thấy bất kỳ thay đổi nào trong văn bản mà họ đã nhập.

Điều đó nói rằng, không phải tất cả các ký tự đa điểm mã đều có thể được tạo thành một điểm mã duy nhất. Trong những trường hợp đó, các phương thức Chuỗi của Ruby sẽ hoạt động kém:

s = "\u01B5\u0327\u0308"          # => "Ƶ̧̈", an un-composable character
s.unicode_normalize(:nfc).size    # => 3, even though there's only one character

Sử dụng NFKC để bảo mật và khả năng tương thích với DB

Nếu bạn đang làm việc với văn bản liên quan đến bảo mật, chẳng hạn như tên người dùng hoặc chủ yếu quan tâm đến việc văn bản có thể chơi tốt với cơ sở dữ liệu của bạn, thì NFKC có lẽ là một lựa chọn tốt.

  • Nó chuyển đổi các ký tự tiềm ẩn có vấn đề thành các ký tự tương thích của chúng.
  • Sau đó, nó tổng hợp tất cả các ký tự thành các điểm mã duy nhất.

Để xem tại sao điều này lại hữu ích cho bảo mật, hãy tưởng tượng rằng bạn có một người dùng có tên người dùng "HenryIV". Một kẻ xấu có thể cố gắng mạo danh người dùng này bằng cách đăng ký tên người dùng mới:"HenryⅣ".

Tôi biết, chúng trông giống nhau. Đó là điểm. Nhưng chúng thực sự là hai chuỗi khác nhau. Trước đây sử dụng các ký tự ascii "IV" trong khi cái sau sử dụng ký tự unicode cho chữ số La Mã 4:"Ⅳ" .

Bạn có thể ngăn chặn loại điều này bằng cách sử dụng NFKC để chuẩn hóa các chuỗi trước khi xác nhận tính duy nhất. Trong trường hợp này, NFKC chuyển đổi unicode "\u2163" đến các chữ cái ascii "IV".

a = "Henry\u2163"
b = "HenryIV"
a.unicode_normalize(:nfc) == b.unicode_normalize(:nfc) # => false, because NFC preserves glyphs
a.unicode_normalize(:nfkc) == b.unicode_normalize(:nfkc) # => true, because NFKC evaluates both to the ascii "IV"

Lời chia tay

Bây giờ tôi đã xem xét kỹ hơn, tôi hơi ngạc nhiên khi chuẩn hóa Unicode không phải là một chủ đề lớn hơn trong cộng đồng Ruby và Rails. Bạn có thể mong đợi nó được thực hiện cho bạn bởi Rails, nhưng theo như tôi có thể nói thì không. Và việc không chuẩn hóa dữ liệu mà người dùng cung cấp cho bạn có nghĩa là nhiều phương thức chuỗi của Ruby không đáng tin cậy.

Nếu bất kỳ độc giả thân yêu nào biết điều gì đó mà tôi không biết, vui lòng liên hệ qua twitter @StarrHorne hoặc gửi email theo địa chỉ starr@honeybadger.io. Unicode là một chủ đề lớn và tôi đã chứng minh rằng mình không biết mọi thứ về nó. :)