Как я могу глобально игнорировать неверные последовательности байтов в строках UTF-8?

17

У меня есть приложение Rails, оставшееся от миграции, поскольку Rails версии 1 и я хотел бы игнорировать неработающие байтовые последовательности all , чтобы сохранить обратную совместимость.

Я не могу знать кодировку ввода .

Пример:

> "- Men\xFC -".split("n")
ArgumentError: invalid byte sequence in UTF-8
    from (irb):4:in 'split'
    from (irb):4
    from /home/fotanus/.rvm/rubies/ruby-2.0.0-rc2/bin/irb:16:in '<main>'

Я могу решить эту проблему в одной строке, используя следующее, например:

> "- Men\xFC -".unpack("C*").pack("U*").split("n")
 => ["- Me", "ü -"] 

Однако я хотел бы всегда игнорировать недопустимые байтовые последовательности и отключать эти ошибки. На самом Ruby или Rails.

    
задан fotanus 07.06.2013 в 17:19
источник
  • Покажите несколько примеров недопустимых данных. Что такое кодировка в вашей базе данных или таблицах? Рельсы должны соответствовать этому. Получение данных Rails требует принуждения к той же кодировке, которую будет хранить база данных, в противном случае вы должны использовать двоичную <-> ASCII или двоичную <-> кодировку UTF-8. –  the Tin Man 07.06.2013 в 17:35
  • @fotanus: он работал с ruby ​​1.8, потому что ruby ​​1.8 не обрабатывал кодировку одинаково (фактически, вообще). См. yokolet.blogspot.com/2009/07/... и yehudakatz.com/2010/05/05/... –  Denis de Bernardy 07.06.2013 в 18:08
  • @ Денис, спасибо, я знаю, что он изменился, поэтому я борюсь с этими проблемами. –  fotanus 07.06.2013 в 18:18
  • Добавлен пример @theTinMan –  fotanus 07.06.2013 в 18:20
  • Вы можете попробовать просмотреть все свои строки и изменить их на что-то, что работает. Другой versoin состоял бы в том, чтобы повторно открыть класс :: String и манипулировать всеми методами. BTW Похоже, что это стандартная 8-битная кодировка, которая использовалась вашей системой по умолчанию. –  User 07.06.2013 в 22:22
Показать остальные комментарии

5 ответов

16

Я не думаю, что вы можете полностью отключить проверку UTF-8 без особых трудностей. Вместо этого я хотел бы сосредоточиться на исправлении всех строк, которые входят в ваше приложение, на границе, где они входят (например, когда вы запрашиваете базу данных или получаете HTTP-запросы).

Предположим, что входящие строки имеют BINARY (a.k.a. ASCII-8BIT-кодирование). Это можно моделировать следующим образом:

s = "Men\xFC".force_encoding('BINARY')  # => "Men\xFC"

Затем мы можем преобразовать их в UTF-8 с помощью String # encode и замените любые неопределенные символы символом замены UTF-8:

s = s.encode("UTF-8", invalid: :replace, undef: :replace)  # => "Men\uFFFD"
s.valid_encoding?  # => true

К сожалению, описанные выше шаги приведут к искажению множества кодовых точек UTF-8, потому что байты в них не будут распознаны. Если у вас есть трехбайтовые символы UTF-8, такие как «\ uFFFD», это будет интерпретироваться как три отдельных байта, и каждый из них будет преобразован в заменяющий символ. Возможно, вы могли бы сделать что-то вроде этого:

def to_utf8(str)
  str = str.force_encoding("UTF-8")
  return str if str.valid_encoding?
  str = str.force_encoding("BINARY")
  str.encode("UTF-8", invalid: :replace, undef: :replace)
end

Это лучшее, что я мог придумать. К сожалению, я не знаю отличный способ сказать Ruby рассматривать строку как UTF-8 и просто заменить все недопустимые байты.

    
ответ дан David Grayson 10.06.2013 в 18:34
источник
  • Спасибо за ваш ответ, это самое близкое к глобальному решению. Вы можете всегда переопределять строковые методы для этого, но я думаю, что мне придется отказаться от всего кода, связанного с каждым конкретным случаем, потому что добавление этого в строку было бы очень хакерским. –  fotanus 19.06.2013 в 19:57
6

В ruby ​​2.0 вы можете использовать метод String # b, который является коротким псевдонимом для String # force_encoding ("BINARY")

    
ответ дан Bruno Coimbra 14.06.2013 в 21:15
источник
  • Это очень информативно, и я доволен информацией, но, возможно, он лучше подходит для комментариев? –  fotanus 19.06.2013 в 19:51
3

Если вы просто хотите работать с необработанными байтами, вы можете попробовать кодировать его как ASCII-8BIT / BINARY.

str.force_encoding("BINARY").split("n")

Это не приведет к возврату вашего ü, так как ваша исходная строка в этом случае - ISO-8859-1 (или что-то вроде этого):

"- Men\xFC -".force_encoding("ISO-8859-1").encode("UTF-8")
 => "- Menü -"

Если вы хотите получить многобайтовые символы, вы имеете , чтобы узнать, что такое исходная кодировка. После того, как вы force_encoding в BINARY, вы буквально просто получите необработанные байты, поэтому многобайтовые символы не будут интерпретироваться соответственно.

Если данные взяты из вашей базы данных, вы можете изменить свой механизм соединения для использования кодировки ASCII-8BIT или BINARY; Ruby должен пометить их соответственно. В качестве альтернативы вы можете monkeypatch драйвер базы данных принудительно кодировать все строки, прочитанные из него. Это массивный молот, хотя и может быть абсолютно неправильным.

Ответ right будет заключаться в исправлении строковых кодировок. Это может потребовать исправления базы данных, исправления кодирования соединения с драйвером базы данных или их комбинации. Все байты все еще существуют, но если вы имеете дело с данной кодировкой, вы должны, если это вообще возможно, позволить Ruby знать, что вы ожидаете, что ваши данные будут в этой кодировке. Общей ошибкой является использование драйвера mysql2 для подключения к базе данных MySQL, которая имеет данные в кодировках latin1, но для указания кодировки utf-8 для соединения. Это приводит к тому, что Rails принимает данные latin1 из БД и интерпретирует его как utf-8, вместо того, чтобы интерпретировать его как latin1, который затем можно преобразовать в UTF-8.

Если вы можете уточнить, откуда берутся строки, может возникнуть более полный ответ. Вы также можете проверить этот ответ для возможного глобального (-ish) решения Rails для строковых кодировок по умолчанию.

    
ответ дан Chris Heald 10.06.2013 в 01:22
источник
2

Если вы можете настроить свою базу данных / страницу / все, чтобы передать вам строки в ASCII-8BIT, это даст вам реальную кодировку.

Используйте библиотеку угадывания кодировки stdlib Ruby. Передайте все свои строки через что-то вроде этого:

require 'nkf'
str = "- Men\xFC -"
str.force_encoding(NKF.guess(str))

Библиотека NKF угадывает кодировку (обычно успешно) и принудительно кодирует эту строку. Если вам не хочется полностью доверять библиотеке NKF, также создайте эту защиту вокруг операций с строкой:

begin
  str.split
rescue ArgumentError
  str.force_encoding('BINARY')
  retry
end

Это будет отказ от BINARY, если NKF не догадался правильно. Вы можете превратить это в оболочку метода:

def str_op(s)
  begin
    yield s
  rescue ArgumentError
    s.force_encoding('BINARY')
    retry
  end
end
    
ответ дан Linuxios 10.06.2013 в 18:10
источник
1

Кодирование в Ruby 1.9 и 2.0 кажется немного сложным. \ xFC - это код для специального символа ü в ISO-8859-1, но код FC также встречается в UTF-8 для ü U+00FC = \u0252 (и в UTF-16). Это может быть артефакт Ruby пакет / функции распаковки , Упаковка и распаковка символов Юникода с строкой шаблона U * для Unicode не проблематичны:

>> "- Menü -".unpack('U*').pack("U*")
=> "- Menü -"

Вы можете создать «неправильную» строку, то есть строку с неверной кодировкой, если вы сначала распакуете символы Unicode UTF-8 (U), а затем упакуете неподписанные символы (C):

>> "- Menü -".unpack('U*').pack("C*")
=> "- Men\xFC -"

Эта строка больше не имеет действительной кодировки. По-видимому, процесс преобразования можно отменить, применяя противоположный порядок (немного похожий на операторов в квантовой физике):

>> "- Menü -".unpack('U*').pack("C*").unpack("C*").pack("U*")
=> "- Menü -"

В этом случае также можно «исправить» сломанную строку, сначала преобразуя ее в ISO-8859-1, а затем в UTF-8, но я не уверен, что это работает случайно, потому что код содержится в этот набор символов

>> "- Men\xFC -".force_encoding("ISO-8859-1").encode("UTF-8")
=> "- Menü -"
>> "- Men\xFC -".encode("UTF-8", 'ISO-8859-1')
=> "- Menü -"
    
ответ дан 0x4a6f4672 02.10.2013 в 19:25
источник
  • Интересный пост, но на самом деле не отвечает на вопрос - это не глобальное решение, это только для одной строки. –  fotanus 02.10.2013 в 19:56
  • Да, наверное. У меня возникла аналогичная проблема, откуда появилась ваша недопустимая строка с символом \ xFC? У меня был текстовый файл с кодировкой UTF-8 со специальными символами, такими как ä, ö, ü, и каким-то образом File.open вернул неверные строки, хотя кодировка была правильно распознана как UTF-8 :-( –  0x4a6f4672 04.10.2013 в 10:42
  • Мои строки произошли из наихудшего места: файл, загруженный пользователем. –  fotanus 04.10.2013 в 15:39