"Сырое" преобразование из двойного UTF-8 в UTF-8 (или из UTF-8 в ANSI)
Я имею дело с устаревшим файлом, который был дважды закодирован с использованием UTF-8. Например, кодовая точка ε
(U+03B5
) должен был быть закодирован как CE B5
но вместо этого был закодирован как C3 8E C2 B5
(CE 8E
такое кодировка UTF-8 U+00CE
, C2 B5
такое кодировка UTF-8 U+00B5
).
Второе кодирование было выполнено, предполагая, что данные были кодированы в CP-1252.
Чтобы вернуться к кодировке UTF-8, я использую следующую (кажется, неправильную) команду
iconv --from utf8 --to cp1252 <file.double-utf8 >file.utf8
Моя проблема в том, что iconv, похоже, не может конвертировать обратно некоторые символы. Точнее, iconv не может преобразовать символы, чье представление UTF-8 содержит символ, который отображается на контрольный символ в CP-1252. Одним из примеров является кодовая точка ρ
(U+03C1
):
- его кодировка UTF-8
CF 81
, - первый байт
CF
перекодируется вC3 8F
, - второй байт
81
перекодируется вC2 81
,
iconv отказывается конвертировать C2 81
вернуться к 81
Возможно, потому, что он не знает, как точно отобразить этот управляющий символ.
echo -e -n '\xc3\x8f\xc2\x81' | iconv --from utf8 --to cp1252
�iconv: illegal input sequence at position 2
Как я могу сказать iconv просто выполнить математическое преобразование UTF-8, не заботясь о отображениях?
2 ответа
В следующем коде используются функции низкоуровневого кодирования Ruby, чтобы принудительно переписать дважды кодированный UTF-8 (из CP1525) в обычный UTF-8.
#!/usr/bin/env ruby
ec = Encoding::Converter.new(Encoding::UTF_8, Encoding::CP1252)
prev_b = nil
orig_bytes = STDIN.read.force_encoding(Encoding::BINARY).bytes.to_a
real_utf8_bytes = ""
real_utf8_bytes.force_encoding(Encoding::BINARY)
orig_bytes.each_with_index do |b, i|
b = b.chr
situation = ec.primitive_convert(b.dup, real_utf8_bytes, nil, nil, Encoding::Converter::PARTIAL_INPUT)
if situation == :undefined_conversion
if prev_b != "\xC2"
$stderr.puts "ERROR found byte #{b.dump} in stream (prev #{(prev_b||'').dump})"
exit
end
real_utf8_bytes.force_encoding(Encoding::BINARY)
real_utf8_bytes << b
real_utf8_bytes.force_encoding(Encoding::CP1252)
end
prev_b = b
end
real_utf8_bytes.force_encoding(Encoding::BINARY)
puts real_utf8_bytes
Он предназначен для использования в конвейере:
cat $PROBLEMATIC_FILE | ./fix-double-utf8-encoding.rb > $CORRECTED_FILE
echo -e -n '\xc3\x8f\xc2\x81' | iconv --from utf8 --to iso8859-1
Windows-1252 отличается от ISO-8859-1 диапазоном 0x80-0x9F. Например, в вашем случае 0x81 - это U+0081 в ISO 8859-1, но недопустимо в Windows-1252.
Проверьте, не были ли остальные ваши данные неправильно истолкованы как Windows-1252 или ISO 8859-1. Обычно ISO 8859-1 является более распространенным.