Найти в Дзене
Nuances of programming

Распознаём 50 видов текста на C++ с Plywood

Оглавление

Источник: Nuances of Programming

Посмотрим на скромный текстовый файл:

-2

Этот файл может содержать удивительное количество различных форматов. Текст может быть закодирован как ASCII, UTF-8, UTF-16 (с прямым или обратным порядком байтов), Windows-1252, Shift JIS или любой из десятков других кодировок. Файл может начинаться или не начинаться с метки порядка байтов (BOM). Строки текста могут заканчиваться символом конца строки \n, типичным для UNIX, последовательностью \r\n, типичной для Windows или, если файл создан в старой системе, какой-то другой последовательностью символов. Иногда невозможно определить кодировку в конкретном текстовом файле. Предположим, файл содержит такие байты:

A2 C2 A2 C2 A2 C2

Это может быть:

  • UTF-8, содержащий ¢¢¢;
  • UTF-16 с прямым порядком (или UCS-2) с символами ꋂꋂꋂ;
  • UTF-16 обратного порядка байтов с 슢슢슢;
  • Windows-1252 с ¢¢¢.

Пример искусственный. Суть в том, что текстовые файлы в своей основе неоднозначны. Неясности создают проблему для программного обеспечения, загружающего текст. Она существует уже некоторое время. К счастью, ландшафт текстовых файлов со временем стал проще. UTF-8 одержал победу над другими кодировками. Более 95% Интернета использует UTF-8. Впечатляет скорость, с которой изменилась цифра: она составляла менее 10% в 2006 году.

-3

Но UTF-8 ещё не захватил мир. Редактор реестра Windows по-прежнему сохраняет текстовые файлы в кодировке UTF-16. Когда же текстовый файл пишется в Python, кодировка по умолчанию зависит от платформы. На моей машине с Windows это Windows-1252. Говоря коротко, проблема неопределённости текста всё ещё актуальна. И даже если файл закодирован в UTF-8, всё равно есть вариации: он может начинаться или не начинаться с BOM; или может иметь разные стили окончания строк.

Как Plywood загружает текст?

Plywood  —  это кросс-платформенный фреймворк с открытым исходным кодом, выпущенный мной два месяца назад. При открытии текстового файла с помощью Plywood у вас есть несколько вариантов:

Возвращаемый этими функциями входной поток никогда не начинается с BOM, всегда кодируется в UTF-8 и завершает каждую строку ввода одним символом возврата каретки \n, независимо от исходного формата входного файла. Преобразование выполняется налету, когда необходимо, позволяя приложениям Plywood работать с единственной внутренней кодировкой.

Автоматическое определение формата

Вот так сейчас работает автоматическое определение формата текста в Plywood:

Plywood анализирует первые 4 КБ входного файла, чтобы предположить его формат. Двух начальных проверок достаточно подавляющему большинству текстовых файлов, с которыми я сталкивался. В UTF-8 есть много недопустимых последовательностей байтов, поэтому когда файл может быть декодирован как UTF-8 и не содержит никаких управляющих кодов, это почти наверняка UTF-8. Управляющий код  —  это код символа меньше 32, за исключением символов табуляции, перевода строки и возврата каретки.

Только при входе в нижнюю часть блок-схемы возникают некоторые догадки. Во-первых, Plywood решает, лучше ли интерпретировать файл как UTF-8 или как простые байты. Это делается, чтобы обработать, например, текст с ударениями в кодировке Windows-1252. В ней французское слово détail кодируется байтами 64 E9 74 61 69 6C, что вызывает ошибку декодирования UTF-8. UTF-8, в свою очередь, ожидает, что за байтом E9 следует байт в диапазоне от 80 до BF. После определённого количества ошибок Plywood делает выбор в пользу простого байта, а не UTF-8.

Система оценки корректности кодировки

Фреймворк пытается декодировать одни и те же данные, применяя 8-битный формат, little-endian UTF-16 и big-endian UTF-16. Он вычисляет балл для каждой кодировки таким образом:

  • Каждый декодированный символ пробела добавляет 2,5 балла. Пробельные символы очень полезны для идентификации кодировок, поскольку пробелы UTF-8 не могут быть распознаны в UTF-16, а пробелы UTF-16 содержат управляющие коды при интерпретации в 8-битной кодировке.
  • ASCII символы, за исключением управляющих, добавляют по 1 баллу.
  • Ошибки декодирования влекут штраф в 100 баллов.
  • Управляющие коды наказываются штрафами в 50 баллов.
  • Коды символов, превышающие U+FFFF, стоят 5 баллов, так как шансы столкнуться с такими символами в случайных данных невелики вне зависимости от кодировки. Яркий пример  —  эмоджи.

Баллы делятся на общее количество декодированных символов, затем выбирается лучший результат.

Откуда взялись 2,5, 1 и прочие значения? Я их выдумал. Они, вероятно, ещё не оптимальны. У алгоритма есть и другие слабые места. Plywood ещё не знает, как распознавать произвольные 8-битные кодировки. В настоящее время он интерпретирует каждый 8-битный текстовый файл UTF-8 как Windows-1252. Фреймворк также не поддерживает Shift JIS в настоящее время. Хорошая новость заключается в том, что Plywood  —  проект с открытым исходным кодом. Это означает, что улучшения могут публиковаться сразу после разработки.

Набор тестов

В репозитории вы найдете папку, содержащую 50 текстовых файлов различных форматов. Все эти файлы корректно определяются и загружаются функцией FileSystem::openTextForReadAutodetect().

-4

Многие современные текстовые редакторы, как и Plywood, определяют формат автоматически. Из любопытства я открывал этот набор в нескольких редакторах:

  • Notepad++ корректно открывает 38 из 50 файлов. Он терпит неудачу на всех файлах UTF-16 без BOM, за исключением little-endian, состоящих в основном из символов ASCII.
  • Sublime Text понимает 42 файла. Когда текст содержит ASCII, редактор делает верное предположение независимо от кодировки.
  • Visual Studio Code видит 40 фалов. VSC близок к Sublime Text, но ошибается на Windows-1252 с ударениями.
  • И самое впечатляющее: Блокнот Windows верно отображает 42 файла! Он правильно распознаёт все UTF-16 с прямым порядком без BOM, но не работает с UTF-16 обратного порядка байтов и без BOM.

По общему признанию это было нечестное соревнование: весь тестовый набор создан вручную специально для Plywood. Больше всего ошибок происходило на файле UTF-16 без BOM. Кажется, это редкий формат. Ни один из редакторов не позволяет сохранить такой файл.

Редакторы из списка выше в первую очередь вдохновляли стратегию автоматического обнаружения в Plywood. В C++ работа с Unicode всегда вызывала трудности. Ситуация весьма печальная: комитет по стандартизации C++ только недавно сформировал исследовательскую группу для решения проблемы. Между тем, я всегда задавался вопросом о том, почему загрузка текста в C++ не может быть такой же простой, как в современном текстовом редакторе? Если вам нравится направление движения Plywood и вы хотите, чтобы так и продолжалось, ваша поддержка на Patreon была бы признанием фреймворка.

-5

Если вы хотите сделать Plywood лучше любым из упомянутых способов, участвуйте на GitHub или на сервере Discord. Если вам нужен только исходный код, обнаруживающий кодировки, не стесняйтесь копировать исходник и изменять его, как посчитаете нужным.

Читайте также:

Читайте нас в телеграмме, vk

Перевод статьи Jeff Preshing: Automatically Detecting Text Encodings in C++