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)