"Сырое" преобразование из двойного 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 является более распространенным.

Другие вопросы по тегам