Как надежно угадать кодировку между MacRoman, CP1252, Latin1, UTF-8 и ASCII

99

На работе кажется, что ни одна неделя не проходит без каких-либо потасовок, связанных с кодированием, бедствий или катастроф. Проблема обычно исходит от программистов, которые думают, что могут надежно обработать «текстовый» файл без указания кодировки. Но ты не можешь.

Поэтому отныне было решено запретить файлам иметь имена, оканчивающиеся на *.txtили *.text. Считается, что эти расширения вводят в заблуждение случайного программиста до тупого самоуспокоения относительно кодирования, а это приводит к неправильной обработке. Было бы лучше вообще не иметь расширения, потому что, по крайней мере, тогда вы знаете, что не знаете, что у вас есть.

Однако мы не собираемся заходить так далеко. Вместо этого вы должны будете использовать имя файла, оканчивающееся на кодировку. Так что для текстовых файлов, например, это было бы что - то вроде README.ascii, README.latin1, README.utf8и т.д.

Для файлов, требующих определенного расширения, если можно указать кодировку внутри самого файла, например, в Perl или Python, вы должны это сделать. Для файлов, таких как исходный код Java, где такие средства не существуют внутри файла, вы поместите кодировку перед расширением, например SomeClass-utf8.java.

Для вывода настоятельно рекомендуется использовать UTF-8 .

Но для ввода нам нужно выяснить, как работать с тысячами файлов в нашей кодовой базе с именем *.txt. Мы хотим переименовать их все, чтобы они соответствовали нашему новому стандарту. Но мы не можем рассматривать их всех. Итак, нам нужна библиотека или программа, которые действительно работают.

Они представлены в различных форматах ASCII, ISO-8859-1, UTF-8, Microsoft CP1252 или Apple MacRoman. Несмотря на то, что мы знаем, что можем определить, является ли что-то ASCII, и у нас есть хорошая возможность узнать, вероятно ли что-то в UTF-8, насчет 8-битных кодировок нас не интересует. Поскольку мы работаем в смешанной среде Unix (Solaris, Linux, Darwin) с большинством настольных компьютеров Mac, у нас довольно много надоедливых файлов MacRoman. И это особенно проблема.

Некоторое время я искал способ программно определить, какой из

  1. ASCII
  2. ISO-8859-1
  3. CP1252
  4. МакРоман
  5. UTF-8

файл находится внутри, и я не нашел программы или библиотеки, которые могли бы надежно различить эти три различных 8-битных кодировки. У нас, вероятно, есть только более тысячи файлов MacRoman, поэтому какой бы детектор кодировки мы ни использовали, он должен уметь их обнаруживать. Ничего из того, на что я смотрел, не помогло. Я возлагал большие надежды на библиотеку детекторов кодировки ICU , но она не может справиться с MacRoman. Я также смотрел модули, которые делают то же самое как в Perl, так и в Python, но снова и снова это всегда одна и та же история: нет поддержки для обнаружения MacRoman.

Поэтому я ищу существующую библиотеку или программу, которая надежно определяет, в какой из этих пяти кодировок находится файл - и желательно больше. В частности, он должен различать три 3-битные кодировки, которые я процитировал, особенно MacRoman . Файлы содержат более 99% текста на английском языке; есть несколько на других языках, но не много.

Если это код библиотеки, мы предпочитаем, чтобы он был на Perl, C, Java или Python и именно в таком порядке. Если это просто программа, то нам все равно, на каком языке она написана, если она идет в полном исходном коде, работает в Unix и полностью свободна.

У кого-нибудь еще была эта проблема с миллионом старых текстовых файлов, случайно закодированных? Если да, то как вы пытались ее решить и насколько вам это удалось? Это самый важный аспект моего вопроса, но меня также интересует, считаете ли вы, что поощрение программистов называть (или переименовывать) свои файлы с фактической кодировкой, в которой находятся эти файлы, поможет нам избежать проблемы в будущем. Кто-нибудь когда-нибудь пытался добиться этого на институциональной основе, и если да, то было ли это успешным или нет, и почему?

И да, я полностью понимаю, почему нельзя гарантировать однозначный ответ, учитывая характер проблемы. Это особенно касается небольших файлов, где у вас недостаточно данных для продолжения. К счастью, наши файлы редко бывают маленькими. За исключением случайного READMEфайла, большинство из них имеют размер от 50 до 250 КБ, а многие больше. Все, что превышает несколько килобайт, гарантированно будет на английском языке.

Проблемной областью является биомедицинский анализ текста, поэтому мы иногда имеем дело с обширными и чрезвычайно большими корпусами, такими как все репозитории открытого доступа PubMedCentral. Довольно огромный файл - это BioThesaurus 6.0, размером 5,7 гигабайт. Этот файл особенно раздражает, потому что почти весь он UTF-8. Однако какой-то тупица пошел и засунул в него несколько строк в какой-то 8-битной кодировке - мне кажется, Microsoft CP1252. Прежде чем вы наткнетесь на него, пройдет немало времени. :(

Христос
источник
См. Stackoverflow.com/questions/4255305/… для решения
mpenkov

Ответы:

86

Во-первых, простые случаи:

ASCII

Если ваши данные не содержат байтов выше 0x7F, то это ASCII. (Или 7-битная кодировка ISO646, но она очень устарела.)

UTF-8

Если ваши данные проверяются как UTF-8, вы можете с уверенностью предположить, что это UTF-8. Из-за строгих правил проверки UTF-8 ложные срабатывания крайне редки.

ISO-8859-1 против windows-1252

Единственное различие между этими двумя кодировками состоит в том, что ISO-8859-1 имеет управляющие символы C1, тогда как windows-1252 имеет печатные символы € ‚ƒ„… † ‡ ˆ Š ‹ŒŽ ''« »• –—˜ ™ š› œžŸ. Я видел множество файлов, в которых используются фигурные кавычки или тире, но ни один из них не использует управляющие символы C1. Так что даже не связывайтесь с ними или ISO-8859-1, просто вместо этого обнаруживайте windows-1252.

Остается только один вопрос.

Как отличить MacRoman от cp1252?

Это намного сложнее.

Неопределенные символы

Байты 0x81, 0x8D, 0x8F, 0x90, 0x9D не используются в windows-1252. Если они возникают, предположите, что это данные MacRoman.

Идентичные персонажи

Байты 0xA2 (¢), 0xA3 (£), 0xA9 (©), 0xB1 (±), 0xB5 (µ) совпадают в обеих кодировках. Если это единственные байты, отличные от ASCII, то не имеет значения, выберете ли вы MacRoman или cp1252.

Статистический подход

Считайте частоту символов (НЕ байтов!) В данных, которые вы знаете как UTF-8. Определите наиболее частые символы. Затем используйте эти данные, чтобы определить, какие символы более распространены: cp1252 или MacRoman.

Например, при поиске, который я только что выполнил по 100 случайным английским статьям Википедии, наиболее распространенными являются символы, отличные от ASCII ·•–é°®’èö—. Исходя из этого факта,

  • Байты 0x92, 0x95, 0x96, 0x97, 0xAE, 0xB0, 0xB7, 0xE8, 0xE9 или 0xF6 предполагают windows-1252.
  • Байты 0x8E, 0x8F, 0x9A, 0xA1, 0xA5, 0xA8, 0xD0, 0xD1, 0xD5 или 0xE1 предполагают MacRoman.

Подсчитайте байты, предлагающие cp1252, и байты, предлагающие MacRoman, и выберите тот, который больше.

dan04
источник
6
Я принял ваш ответ, потому что никто не представился лучше, и вы хорошо написали те самые проблемы, над которыми я возился. У меня действительно есть программы для перехвата этих байтов, хотя у вас их примерно вдвое больше, чем я сам придумал.
tchrist
10
Наконец-то дошли до реализации этого. Оказывается, Википедия - не очень хорошие данные для обучения. Из 1k случайных статей en.wikipedia, не считая раздела LANGUAGES, я получил 50k кодовых точек unASCII, но распределение не заслуживает доверия: средняя точка и маркер слишком высоки, & c & c & c. Поэтому я использовал корпус PubMed Open Access, полностью состоящий из UTF8, добывая + 14 миллионов кодовых точек unASCII. Я использую их для построения модели относительной частоты всех 8-битных кодировок, более красивой, чем ваша, но основанной на этой идее. Это доказывает высокую предсказуемость кодирования биомедицинских текстов, целевого домена. Я должен опубликовать это. Спасибо!
tchrist
5
У меня до сих пор нет файлов MacRoman, но использование CR в качестве разделителей строк не могло бы стать полезным тестом. Это будет работать для более старых версий Mac OS, хотя я не знаю об OS9.
Milliways
10

Mozilla nsUniversalDetector (привязки Perl: Encode :: Detect / Encode :: Detect :: Detector ) многократно доказан.

Daxim
источник
Дополнительную документацию можно найти здесь: mozilla.org/projects/intl/detectorsrc.html , оттуда предполагается, что если вы покопаетесь в документации, вы сможете найти поддерживаемые кодировки
Джоэл Бергер
@ Джоэл: Я покопался в источнике. Это был риторический вопрос. x-mac-cyrillicподдерживается, x-mac-hebrewподробно обсуждается в комментариях, x-mac-anything-elseне упоминается.
Джон Мачин
@John Machin: странно, что кириллица и иврит получили одобрение, но ничего больше. Я просто добавлял другой источник документации, я не читал дальше, спасибо за это!
Joel Berger
7

Моя попытка такой эвристики (при условии, что вы исключили ASCII и UTF-8):

  • Если 0x7f - 0x9f вообще не появляются, это, вероятно, ISO-8859-1, потому что это очень редко используемые коды управления.
  • Если 0x91–0x94 появляются на участке, это, вероятно, Windows-1252, потому что это «умные кавычки», наиболее вероятные символы в этом диапазоне для использования в английском тексте. Для большей уверенности можно поискать пары.
  • В противном случае это MacRoman, особенно если вы видите много значений от 0xd2 до 0xd5 (именно там типографские кавычки находятся в MacRoman).

Примечание:

Для файлов, таких как исходный код Java, в которых нет такой возможности внутри файла, вы поместите кодировку перед расширением, например SomeClass-utf8.java.

Не делай этого!!

Компилятор Java ожидает, что имена файлов будут соответствовать именам классов, поэтому переименование файлов сделает исходный код некомпилируемым. Правильнее было бы угадать кодировку, а затем использовать native2asciiинструмент для преобразования всех символов, отличных от ASCII, в escape-последовательности Unicode .

Майкл Боргвардт
источник
7
Стоопид компилор! Нет, мы не можем сказать людям, что они могут использовать только ASCII; это уже не 1960-е. Не было бы проблем, если бы существовала аннотация @encoding, чтобы тот факт, что источник находится в определенной кодировке, не должен был храниться вне исходного кода, действительно идиотский недостаток Java, от которого не страдают ни Perl, ни Python . Это должно быть в источнике. Однако это не наша главная проблема; это тысячи *.textфайлов.
tchrist
3
@tchrist: На самом деле было бы не так уж сложно написать собственный процессор аннотаций, поддерживающий такую ​​аннотацию. По-прежнему досадная оплошность, что его нет в стандартном API.
Майкл Боргвардт
Даже если бы Java действительно поддерживала @encoding, это не гарантировало бы правильность объявления кодировки .
dan04
4
@ dan04: То же самое можно сказать и об объявлении кодировки в XML, HTML или где-либо еще. Но, как и в случае с этими примерами, если бы он был определен в стандартном API, большинство инструментов, которые работают с исходным кодом (особенно редакторы и IDE), поддержали бы его, что довольно надежно предотвратило бы случайное создание людьми файлов, кодировка содержимого которых не соответствует заявление.
Майкл Боргвардт
4
«Компилятор Java ожидает, что имена файлов будут соответствовать именам классов». Это правило применяется, только если файл определяет общедоступный класс верхнего уровня.
Мэтью Флашен
6

«Perl, C, Java или Python, и именно в таком порядке»: интересное отношение :-)

«мы переживаем хорошее изменение знания, вероятно ли что-то UTF-8»: на самом деле вероятность того, что файл, содержащий значимый текст, закодированный в какой-то другой кодировке, которая использует байты с высоким битом, будет успешно декодироваться, как UTF-8, исчезающе мала.

Стратегии UTF-8 (на наименее предпочтительном языке):

# 100% Unicode-standard-compliant UTF-8
def utf8_strict(text):
    try:
        text.decode('utf8')
        return True
    except UnicodeDecodeError:
        return False

# looking for almost all UTF-8 with some junk
def utf8_replace(text):
    utext = text.decode('utf8', 'replace')
    dodgy_count = utext.count(u'\uFFFD') 
    return dodgy_count, utext
    # further action depends on how large dodgy_count / float(len(utext)) is

# checking for UTF-8 structure but non-compliant
# e.g. encoded surrogates, not minimal length, more than 4 bytes:
# Can be done with a regex, if you need it

Если вы решили, что это не ASCII и не UTF-8:

Известные мне детекторы кодировки Mozilla не поддерживают MacRoman и в любом случае плохо работают с 8-битными кодировками, особенно с английским языком, потому что AFAICT они зависят от проверки того, имеет ли декодирование смысл в данном язык, игнорируя знаки препинания, и основанный на широком выборе документов на этом языке.

Как отмечали другие, у вас действительно есть только символы пунктуации с высоким битом, чтобы различать cp1252 и макроман. Я бы посоветовал обучить модель типа Mozilla на ваших собственных документах, а не на Шекспире, Хансарде или Библии KJV, и принять во внимание все 256 байтов. Я предполагаю, что в ваших файлах нет разметки (HTML, XML и т. Д.) - это исказит вероятность чего-то шокирующего.

Вы упомянули файлы, которые в основном имеют формат UTF-8, но не могут декодироваться. Вы также должны очень подозрительно относиться к:

(1) файлы, которые якобы закодированы в ISO-8859-1, но содержат «управляющие символы» в диапазоне от 0x80 до 0x9F включительно ... это настолько распространено, что проект стандарта HTML5 говорит о декодировании ВСЕХ потоков HTML, объявленных как ISO-8859 -1, используя cp1252.

(2) файлы, которые декодируют OK как UTF-8, но результирующий Unicode содержит «управляющие символы» в диапазоне от U + 0080 до U + 009F включительно ... это может быть результатом перекодирования cp1252 / cp850 (это уже было видно!) / И т. Д. файлы из "ISO-8859-1" в UTF-8.

Предыстория: у меня есть проект влажного воскресенья и полудня по созданию детектора кодировки на основе Python, ориентированного на файлы (вместо веб-ориентированного) и хорошо работающего с 8-битными наборами символов, включая legacy ** nтакие, как cp850 и cp437. Пока еще далеко не прайм-тайм. Меня интересуют учебные файлы; Ваши файлы ISO-8859-1 / cp1252 / MacRoman столь же "необременительны", как вы ожидаете от любого решения для кода?

Джон Мачин
источник
1
Причина упорядочения языков - окружающая среда. Большинство наших основных приложений, как правило, находятся на java, второстепенные утилиты, а некоторые приложения - на Perl. Здесь и там у нас есть немного кода, написанного на Python. Я в основном программист на C и Perl, по крайней мере, по первому выбору, поэтому я искал либо java-решение для подключения к нашей библиотеке приложений, либо библиотеку Perl для того же. Если бы C, я мог бы создать клеевой слой XS, чтобы подключить его к интерфейсу perl, но я никогда раньше не делал этого на python.
tchrist
3

Как вы обнаружили, идеального способа решения этой проблемы не существует, потому что без неявного знания о том, какую кодировку использует файл, все 8-битные кодировки будут одинаковыми: набором байтов. Все байты действительны для всех 8-битных кодировок.

Лучшее, на что вы можете надеяться, - это своего рода алгоритм, который анализирует байты и на основе вероятностей использования определенного байта на определенном языке с определенной кодировкой будет угадывать, какую кодировку используют файлы. Но это должно знать, какой язык использует файл, и становится совершенно бесполезным, когда у вас есть файлы со смешанными кодировками.

С другой стороны, если вы знаете, что текст в файле написан на английском языке, то вы вряд ли заметите какую-либо разницу, какую бы кодировку вы ни выбрали для этого файла, поскольку все различия между всеми упомянутыми кодировками локализованы в части кодировок, которые определяют символы, обычно не используемые в английском языке. У вас могут возникнуть проблемы, когда в тексте используется специальное форматирование или специальные версии знаков препинания (например, CP1252 имеет несколько версий символов кавычек), но для сути текста проблем, вероятно, не будет.

Эпцилон
источник
1

Если вы можете обнаружить каждую кодировку, ЗА ИСКЛЮЧЕНИЕМ для макромана, было бы логично предположить, что те, которые не могут быть расшифрованы, находятся в макроманале. Другими словами, просто составьте список файлов, которые не могут быть обработаны, и обрабатывайте их, как если бы они были макроманами.

Другой способ отсортировать эти файлы - создать серверную программу, позволяющую пользователям решать, какая кодировка не искажена. Конечно, это будет внутри компании, но если 100 сотрудников будут выполнять несколько задач каждый день, тысячи файлов будут созданы в кратчайшие сроки.

Наконец, не лучше ли просто преобразовать все существующие файлы в один формат и потребовать, чтобы новые файлы были в этом формате.

Эрик Поли
источник
5
Веселая! Когда я впервые прочитал этот комментарий после того, как меня прервали на 30 минут, я прочитал «macroman» как «макро-человек» и не установил соединение с MacRoman, пока я не запустил поиск этой строки, чтобы увидеть, упомянул ли ее OP.
Адриан Пронк,
+1 этот ответ довольно интересный. не уверен, хорошая это идея или плохая. Кто-нибудь может подумать о существующей кодировке, которая также может остаться незамеченной? есть вероятность, что он появится в будущем?
имя пользователя
1

У кого-нибудь еще была эта проблема с миллионом старых текстовых файлов, случайно закодированных? Если да, то как вы пытались ее решить и насколько вам это удалось?

В настоящее время я пишу программу, которая переводит файлы в XML. Он должен автоматически определять тип каждого файла, что является расширением проблемы определения кодировки текстового файла. Для определения кодировки я использую байесовский подход. То есть мой классификационный код вычисляет вероятность (вероятность) того, что текстовый файл имеет определенную кодировку для всех кодировок, которые он понимает. Затем программа выбирает наиболее вероятный декодер. Байесовский подход работает так для каждого кодирования.

  1. Установите начальную ( априорную ) вероятность того, что файл находится в кодировке, на основе частот каждого кодирования.
  2. По очереди исследуйте каждый байт в файле. Найдите значение байта, чтобы определить корреляцию между присутствующим значением байта и файлом, фактически находящимся в этой кодировке. Используйте эту корреляцию для вычисления нового ( апостериорного ) вероятность того, что файл находится в кодировке. Если у вас есть больше байтов для исследования, используйте апостериорную вероятность этого байта в качестве априорной вероятности при проверке следующего байта.
  3. Когда вы дойдете до конца файла (на самом деле я смотрю только на первые 1024 байта), вероятность, что у вас есть, - это вероятность того, что файл находится в кодировке.

Выясняется , что Байеса теорема становится очень легко сделать , если вместо вычисления вероятностей, то вычислить содержание информации , которая является логарифмом шансов : info = log(p / (1.0 - p)).

Вам нужно будет вычислить исходную априорную вероятность и корреляции, исследуя корпус файлов, которые вы вручную классифицировали.

Raedwald
источник