Ruby читает CSV файл как UTF-8 и/или конвертирует кодировку ASCII-8Bit в UTF-8

Я использую ruby ​​1.9.2

Я пытаюсь разобрать CSV файл, содержащий несколько французских слов (например, spécifié) и размещать содержимое в базе данных MySQL.

Когда я читаю строки из файла CSV,

file_contents = CSV.read("csvfile.csv", col_sep: "$")

Элементы возвращаются в виде строк, которые ASCII-8BIT закодированы (spécifié становится sp\xE9cifi\xE9), и строки, подобные "spécifié", затем НЕ надлежащим образом сохраняются в моей базе данных MySQL.

Иегуда Кац говорит, что ASCII-8BIT - это действительно "двоичные" данные, что означает, что CSV не знает, как читать соответствующую кодировку.

Итак, если я попытаюсь заставить CSV заставить кодировку следующим образом:

file_contents = CSV.read("csvfile.csv", col_sep: "$", encoding: "UTF-8")

Я получаю следующую ошибку

ArgumentError: invalid byte sequence in UTF-8: 

Если я вернусь к исходным закодированным строкам ASCII-8BIT и рассмотрю строку, которую мой CSV читает как ASCII-8BIT, она выглядит как "Non sp\xE9cifi\xE9" вместо "Non spécifié".

Я не могу преобразовать "Non sp\xE9cifi\xE9" в "Non spécifié", делая это "Non sp\xE9cifi\xE9".encode("UTF-8")

потому что я получаю эту ошибку:

Encoding::UndefinedConversionError: "\xE9" from ASCII-8BIT to UTF-8,

который указал Кац, потому что ASCII-8BIT на самом деле не является надлежащей строковой "кодировкой".

Вопросы:

  • Могу ли я получить CSV для чтения моего файла в соответствующей кодировке? Если да, то как?
  • Как преобразовать строку ASCII-8BIT в UTF-8 для правильного хранения в MySQL?

Ответ 1

deceze прав, то есть кодированный текст ISO8859-1 (AKA Latin-1). Попробуйте следующее:

file_contents = CSV.read("csvfile.csv", col_sep: "$", encoding: "ISO8859-1")

И если это не сработает, вы можете использовать Iconv, чтобы зафиксировать отдельные строки с чем-то вроде этого:

require 'iconv'
utf8_string = Iconv.iconv('utf-8', 'iso8859-1', latin1_string).first

Если latin1_string - "Non sp\xE9cifi\xE9", то utf8_string будет "Non spécifié". Кроме того, Iconv.iconv может разворачивать целые массивы за раз:

utf8_strings = Iconv.iconv('utf-8', 'iso8859-1', *latin1_strings)

С более новыми Rubies вы можете делать такие вещи:

utf8_string = latin1_string.force_encoding('iso-8859-1').encode('utf-8')

где latin1_string считает, что он находится в ASCII-8BIT, но действительно находится в ISO-8859-1.

Ответ 2

С ruby >= 1.9 вы можете использовать

file_contents = CSV.read("csvfile.csv", col_sep: "$", encoding: "ISO8859-1:utf-8")

Значение ISO8859-1:utf-8 имеет значение: csv файл кодируется ISO8859-1, но конвертирует содержимое в utf-8

Если вы предпочитаете более подробный код, вы можете использовать:

file_contents = CSV.read("csvfile.csv", col_sep: "$", 
    external_encoding: "ISO8859-1", 
    internal_encoding: "utf-8"
  )

Ответ 3

Я занимался этой проблемой некоторое время, а не какие-то другие решения, которые работали для меня.

То, что сделало трюк, состояло в том, чтобы сохранить конфликтную строку в двоичном файле, а затем прочитать файл в обычном режиме и использовать эту строку для подачи CSV-модуля:

tempfile = Tempfile.new("conflictive_string")
tempfile.binmode
tempfile.write(conflictive_string)
tempfile.close
cleaned_string = File.read(tempfile.path)
File.delete(tempfile.path)
csv = CSV.new(cleaned_string)