2011-12-19 35 views
8

Tôi vừa mới cập nhật từ ruby ​​1.9.2 lên ruby ​​1.9.3p0 (2011-10-30 bản sửa đổi 33570). Ứng dụng đường ray của tôi sử dụng postgresql làm phần cuối cơ sở dữ liệu của nó. Ngôn ngữ hệ thống là UTF8, cũng như mã hóa cơ sở dữ liệu. Mã hóa mặc định của ứng dụng đường ray cũng là UTF8. Tôi có người dùng Trung Quốc nhập các ký tự Trung Quốc cũng như các ký tự tiếng Anh. Các chuỗi được lưu trữ dưới dạng chuỗi được mã hóa UTF8.Rails: mã hóa tai ương với băm serialized mặc dù UTF8

Phiên bản đường ray: 3.0.9

Vì cập nhật một số chuỗi Trung Quốc hiện có trong cơ sở dữ liệu không còn hiển thị chính xác nữa. Điều này không ảnh hưởng đến tất cả các chuỗi, nhưng chỉ ảnh hưởng đến tất cả các chuỗi ký tự. Tất cả các chuỗi khác được lưu trữ dưới dạng các chuỗi đơn giản vẫn có vẻ đúng.


Ví dụ:

Đây là một hash serialized được lưu trữ như là một chuỗi UTF8 trong cơ sở dữ liệu:

broken = "--- !map:ActiveSupport::HashWithIndifferentAccess \ncheckbox: \"1\"\nchoice: \"Round Paper Clips \\xEF\\xBC\\x88\\xE5\\x9B\\x9E\\xE5\\xBD\\xA2\\xE9\\x92\\x88\\xEF\\xBC\\x89\\r\\n\"\ninfo: \"10\\xE7\\x9B\\x92\"\n" 

Để chuyển đổi chuỗi này để băm ruby, tôi deserialize nó với YAML.load:

broken_hash = YAML.load(broken) 

Điều này trả về một hàm băm có nội dung bị cắt xén:

{"checkbox"=>"1", "choice"=>"Round Paper Clips ï¼\u0088å\u009B\u009Eå½¢é\u0092\u0088ï¼\u0089\r\n", "info"=>"10ç\u009B\u0092"} 

Công cụ bị cắt xén được coi là tiếng Trung Quốc được mã hóa UTF8. broken_hash['info'].encoding cho tôi biết rằng ruby ​​nghĩ rằng đây là #<Encoding:UTF-8>. Tôi không đồng ý.

Điều thú vị là, tất cả các chuỗi khác không được tuần tự hóa trước khi được xem là tốt. Trong cùng một bản ghi, một trường khác chứa các ký tự Trung Quốc trông vừa phải --- trong bảng điều khiển đường ray, giao diện điều khiển psql và trình duyệt. Mỗi chuỗi --- không có vấn đề nếu băm serialized hoặc chuỗi đồng bằng --- lưu vào cơ sở dữ liệu kể từ khi cập nhật cũng tốt.


tôi đã cố gắng để chuyển đổi văn bản bị cắt xén từ một mã hóa sai có thể (như GB2312 hoặc ANSI) sang UTF-8 bất chấp tuyên bố của ruby ​​rằng đây đã là UTF-8 và dĩ nhiên là tôi đã thất bại. Đây là mã tôi đã sử dụng:

require 'iconv' 
Iconv.conv('UTF-8', 'GB2312', broken_hash['info']) 

Điều này không thành công vì Ruby không biết phải làm gì với chuỗi bất hợp pháp trong chuỗi.

Tôi thực sự chỉ muốn chạy tập lệnh để sửa tất cả các chuỗi băm được mã hóa cũ, có lẽ bị hỏng và được thực hiện với nó. Có cách nào để chuyển đổi những sợi dây bị hỏng này thành một thứ gì đó giống với Trung Quốc một lần nữa không?


Tôi vừa chơi với chuỗi UTF-8 được mã hóa trong chuỗi thô (được gọi là "bị hỏng" trong ví dụ trên). Đây là chuỗi Trung Quốc mà được mã hóa trong chuỗi tuần tự:

chinese = "\\xEF\\xBC\\x88\\xE5\\x9B\\x9E\\xE5\\xBD\\xA2\\xE9\\x92\\x88\\xEF\\xBC\\x89\\r\\n\"

tôi nhận thấy rằng nó rất dễ dàng để chuyển đổi này cho một người thực UTF-8 chuỗi mã hóa bằng cách tự thoát nó (loại bỏ các dấu xồ nguợc thoát).

chinese_ok = "\xEF\xBC\x88\xE5\x9B\x9E\xE5\xBD\xA2\xE9\x92\x88\xEF\xBC\x89\r\n"

này trả về một chuỗi thích hợp Trung Quốc UTF-8 mã hóa: "(回形针)\r\n"

Điều sụp đổ chỉ khi tôi sử dụng YAML.load(...) để chuyển đổi chuỗi thành một hash ruby. Có lẽ tôi nên xử lý chuỗi thô trước khi nó được cấp cho YAML.load. Chỉ cần làm cho tôi tự hỏi tại sao điều này lại như vậy ...


Thú vị! Điều này có thể là do động cơ YAML "psych" được sử dụng theo mặc định trong phiên bản 1.9.3. Tôi chuyển sang công cụ "syck" với YAML::ENGINE.yamler = 'syck' và các chuỗi bị hỏng được phân tích cú pháp chính xác.

+0

Loại cột cho băm được in ra? –

+0

@muistooshort: loại cột là 'văn bản'. – rekado

+0

Điều gì xảy ra nếu bạn thay đổi cột thành 'binary'? Điều đó sẽ nhận được chuỗi như "8bit ASCII" (tức là byte nguyên) và có thể sẽ khởi động 'YAML.load' thành hình dạng. Như một bài kiểm tra nhanh, bạn có thể 'broken.force_encoding ('binary')' trước 'YAML.load (broken)'. –

Trả lời

12

Điều này dường như đã được gây ra bởi sự khác biệt trong hành vi của hai động cơ YAML có sẵn "syck" và "psych". Để thiết lập các cơ YAML để syck:

YAML::ENGINE.yamler = 'syck'

Để thiết lập các cơ YAML trở lại psych:

YAML::ENGINE.yamler = 'psych'

Các "syck" engine xử lý chuỗi như mong đợi và chuyển đổi chúng sang băm với các chuỗi tiếng Trung thích hợp. Khi công cụ "psych" được sử dụng (mặc định trong ruby ​​1.9.3), kết quả chuyển đổi trong các chuỗi bị cắt xén.

Thêm dòng trên (đầu tiên của hai) vào config/application.rb khắc phục sự cố này. Các "syck" động cơ không còn được duy trì, vì vậy tôi nên có lẽ chỉ sử dụng workaround này để mua cho tôi một thời gian để làm cho các chuỗi chấp nhận được cho "tâm hồn".

+0

Dường như chúng tôi đang nhìn những thứ giống nhau cùng một lúc. Tôi sẽ mã hóa lại mọi thứ thành định dạng Psych hoặc mương YAML hoàn toàn và tự tuần tự hóa bằng cách sử dụng JSON hoặc một số định dạng di động/ổn định khác. –

+0

BTW, bạn có thể chấp nhận câu trả lời của riêng bạn và tôi nghĩ rằng nó có ý nghĩa để làm như vậy trong trường hợp này. –

9

Từ 1.9.3 NEWS file:

* yaml 
    * The default YAML engine is now Psych. You may downgrade to syck by setting 
    YAML::ENGINE.yamler = 'syck'. 

Rõ ràng các công cụ Syck và Psych YAML điều trị chuỗi ASCII trong nhiều cách khác nhau và không tương thích.

Cho một Hash như bạn có:

h = { 
    "checkbox" => "1", 
    "choice" => "Round Paper Clips (回形针)\r\n", 
    "info"  => "10盒" 
} 

Sử dụng động cơ Syck cũ:

>> YAML::ENGINE.yamler = 'syck' 
>> h.to_yaml 
=> "--- \ncheckbox: "1"\nchoice: "Round Paper Clips \\xEF\\xBC\\x88\\xE5\\x9B\\x9E\\xE5\\xBD\\xA2\\xE9\\x92\\x88\\xEF\\xBC\\x89\\r\\n"\ninfo: "10\\xE7\\x9B\\x92"\n" 

chúng tôi nhận được định dạng đúp xuyệc ngược xấu xí mà bạn hiện có trong cơ sở dữ liệu của bạn. Chuyển sang chế độ Psych:

>> YAML::ENGINE.yamler = 'psych' 
=> "psych" 
>> h.to_yaml 
=> "---\ncheckbox: '1'\nchoice: ! "Round Paper Clips (回形针)\\r\\n"\ninfo: 10盒\n" 

Các chuỗi ở định dạng UTF-8 bình thường. Nếu chúng tôi xử lý mã hóa theo cách thủ công bằng tiếng Latin-1:

>> Iconv.conv('UTF-8', 'ISO-8859-1', "\xEF\xBC\x88\xE5\x9B\x9E\xE5\xBD\xA2\xE9\x92\x88\xEF\xBC\x89") 
=> "ï¼\u0088å\u009B\u009Eå½¢é\u0092\u0088ï¼\u0089" 

thì chúng tôi sẽ nhận được loại vô nghĩa mà bạn đang thấy.

Tài liệu YAML khá mỏng nên tôi không biết liệu bạn có thể buộc Psych hiểu định dạng Syck cũ không. Tôi nghĩ bạn có ba tùy chọn:

  1. Sử dụng động cơ Syck cũ không được hỗ trợ và không dùng nữa, bạn cần phải YAML::ENGINE.yamler = 'syck' trước khi bạn YAML làm gì.
  2. Tải và giải mã tất cả YAML của bạn bằng Syck và sau đó mã hóa lại và lưu nó bằng Psych.
  3. Dừng sử dụng serialize để ưu tiên tuần tự/deserializing bằng cách sử dụng JSON (hoặc một số định dạng văn bản ổn định, có thể dự đoán và di động khác) hoặc sử dụng bảng kết hợp để bạn không lưu trữ dữ liệu tuần tự.
+0

Ha, thật thú vị: bạn đã gửi câu trả lời của mình một phút sau khi tôi đã tìm ra câu trả lời. Tôi đã tạm thời sửa các ứng dụng bằng cách buộc "syck" được sử dụng. Cuối cùng, tôi sẽ phải làm điều đó một cách khó khăn và mã hóa lại mọi thứ bằng "tâm hồn". Thực sự không thích những thay đổi không tương thích. – rekado

+2

@rekado: Tôi sẽ chuyển hoàn toàn khỏi YAML, tôi nghĩ đó là một định dạng khủng khiếp cho việc tuần tự hóa dữ liệu và các tên Rails ngu ngốc khi sử dụng nó cho 'serialize'. Nhưng tôi cũng là một kẻ dị giáo sinh ra tự nhiên :) –

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