Подтвердить что ты не робот

String # encode не фиксирует ошибку "неправильная последовательность байтов в UTF-8"

Я знаю, что есть несколько похожих вопросов об этой ошибке, и я пробовал много из них без везения. Проблема, с которой я сталкиваюсь, включает в себя байт \xA1 и бросает

ArgumentError: неверная последовательность байтов в UTF-8

Я пробовал следующее без успеха:

"\xA1".encode('UTF-8', :undef => :replace, :invalid => :replace,
    :replace => "").sub('', '')
"\xA1".encode('UTF-8', :undef => :replace, :invalid => :replace,
    :replace => "").force_encoding('UTF-8').sub('', '')
"\xA1".encode('UTF-8', :undef => :replace, :invalid => :replace,
    :replace => "").encode('UTF-8').sub('', '')

Каждая строка вызывает ошибку для меня. Что я делаю неправильно?

UPDATE:

Вышеуказанные строки выходят из строя только в IRB. Тем не менее, я изменил свое приложение, чтобы кодировать строки CVS файла, используя тот же метод и аргументы String # encode, и я получаю ту же ошибку при чтении строки из файла (примечание: оно работает, если вы выполняете операции с одной и той же строкой без использования IO).

bad_line = "col1\tcol2\tbad\xa1"

bad_line.sub('', '') # does NOT fail
puts bad_line # => col1 col2    bad?

tmp = Tempfile.new 'foo' # write the line to a file to emulate real problem
tmp.puts bad_line
tmp.close

tmp2 = Tempfile.new 'bar'

begin
  IO.foreach tmp.path do |line|
    line.encode!('UTF-8', :undef => :replace, :invalid => :replace, :replace => "")
    line.sub('', '') # fail: invalid byte sequence in UTF-8
    tmp2.puts line
  end
  tmp2.close

  # this would fail if the above error didn't halt execution
  CSV.foreach(tmp2.path) do |row|
    puts row.inspect # fail: invalid byte sequence in UTF-8
  end
ensure
  tmp.unlink
  tmp2.close
  tmp2.unlink
end
4b9b3361

Ответ 1

Казалось бы, Ruby думает, что строковая кодировка уже utf8, поэтому, когда вы делаете

line.encode!('UTF-8', :undef => :replace, :invalid => :replace, :replace => "")

он фактически ничего не делает, потому что конечная кодировка совпадает с текущей кодировкой (по крайней мере, моя интерпретация кода в transcode.c)

Реальный вопрос заключается в том, являются ли ваши исходные данные действительными в некоторой кодировке, которая не является utf-8, или это данные, которые должны быть utf-8, но имеют несколько бородавок в нем, которые вы хотите сбросить.

В первом случае правильная вещь - сказать рубине, что это за кодировка. Это можно сделать при открытии файла

File.open('somefile', 'r:iso-8859-1')

откроет файл, интерпретируя его содержимое как iso-8859-1

Вы даже можете получить ruby ​​для перекодирования для вас

File.open('somefile', 'r:iso-8859-1:utf-8')

откроет файл как iso-8859-1, но когда вы прочитаете данные из него, байты будут преобразованы в utf-8 для вас.

Вы также можете вызвать force_encoding, чтобы сообщить ruby, что такое строковое кодирование (это вообще не изменяет байты, оно просто сообщает ruby, как их интерпретировать).

Во втором случае, когда вы просто хотите сбросить все неприятные вещи в ваш utf-8, вы не можете просто вызвать encode!, как вы, потому что это не-op. В рубине 2.1 и выше вы можете использовать String # scrub, в предыдущих версиях вы можете сделать это

line.encode!('UTF-16', :undef => :replace, :invalid => :replace, :replace => "")
line.encode!('UTF-8')

Сначала мы конвертируем в utf-16. Поскольку это другая кодировка, рубин фактически заменит наши недопустимые последовательности. Затем мы можем преобразовать обратно в utf-8. Это не потеряет никаких дополнительных данных, потому что utf-8 и utf-16 - это всего лишь два разных способа кодирования одного и того же базового набора символов.

Ответ 2

Возможно, вы используете этот код в IRB. У меня было много проблем с кодировкой IRB. В этом случае попробуйте сохранить этот код как файл .rb и запустить код из командной строки.