Абсолютный минимум который каждый разработчик должен знать о Unicode и кодировках (без исключений) (перевод)
Запись от outoftime размещена 16.02.2018 в 01:01
Обновил(-а) outoftime 16.02.2018 в 21:28 (Правки от Байт)
Обновил(-а) outoftime 16.02.2018 в 21:28 (Правки от Байт)
(Примечание от переводчика: автор статьи занимается веб разработкой и, соответсвенно, ссылается на моменты которые касаются именно веба) Никогда не задумывались о мистическом мета-теге Content-Type? Ну, вы знаете, тот что предполагается размещать в HTML и не всегда понятно каково должно быть его значение. Вы когда либо получали сообщение по электронной почте от друзей из Болгарии с темой сообщения в виде “???? ?????? ??? ????”? Я был встревожен, узнав как много разработчиков не дружат с мистическим миром таблиц символов, кодировок, Unicode, всем в этом роде. Несколько лет тому назад, бета текстировщик FogBUGZ поитересовался, сможет ли он обработать электронное сообщение на Японском. Японском? У них есть электронная почта в Японии? Не знал. Когда я ближе посмотрел на комерчиский ActiveX, который мы использовали для парсинга MIME электронных сообщений, мы обнаружили что он делает в корне неверные вещи с таблицами символов, поэтому нам пришлось приложить героические усилия чтобы отменить неправильное преобразование которое было сделано и сделать его корректно. Когда я посмотрел другую коммерческую библиотеку, она также имела в корне неверную реализацию. Я связался с разработчиком этого пакета и он был мнения что ничего не может с этим поделать. Как и много программистов, он считал что его как-то пронесет. Но нет. Когда я обнаружил что популярный инструмент веб разработки - PHP. практически полностью игнорирует проблемы с кодировкой, беспечно используя 8 бит на символ, делая сложной вплоть до нереального, реализацию хорошего международного веб приложения, я подумал: "всё, достаточно." Поэтому у меня есть заявление которое я хочу сделать: если ты программист работающий в 2003 году и ты не знаешь азов символов, таблиц символов, кодировок и Unicode, я найду тебя, я заставлю тебя чистить луковицы 6 месяцев подряд на подводной лодке. Я обещаю, я сделаю это. И еще одна вещь: Это не на столько сложно. В этой статье статье я расскажу о том, что должен знать каждый программист. Представления о том, что "текст = ASCII = символы это 8 бит" это не просто неверные заблуждения, это в корне неверные заблуждения, и если ты до сих пор пишешь остновываясь на этих утверждениях, ты не на много лучше доктора которые не верит в микробов. Пожалуйста, не пиши ни строчки кода, пока не закончишь читать эту статью. Прежде чем я начну, я должен предупредить тебя что если ты один из тех немногих людей, которые знают о интернационализации, ты можешь найти всю эту статью, немного.. слишком упрощенной. Я действительно просто пытаюсь задать базу чтобы все понимали что происходит и могли писать код который имеет шансы работать с текстом на любом другом языке отличающемся от подмножества англоподобных что не включают слова с акцентами. И я должен тебя предупредить что обработка символов это всего лишь тоненькая прослойка того что нужно сделать для того чтобы создавать программные продукты работающие интернационально, но я могу писать только об одной вещи за раз, сегодня у нас таблицы символов. Вспомним прошлое Проще всего понять эту тему пройдя исторический путь развития. Возможно, ты подумал что я буду говорить об очень старых кодировках, таких как EBCDIC. Нет, не буду. EBCDIC не имеет отношения к твоей жизни. Нам нет нужды заглядывать настолько далеко. И всё было хорошо, если ты англоязычный. В итоге, OEM который получил множество разновидностей стал стандартом под кодом ANSI. В ANSI, все пришли к соглашению какие символы должны быть ниже 128, что было очень похоже на то что было в ASCII, но было множество разных путей обрабатывать символы от 128 и выше, в зависимости от того где ты живешь. Эти различия были задокументированы и получили название "страницы символов". Таким образом, в Израиле DOS использовал страницу символов 862, в то время как Греки использовали 737. Они все были одинаковыми до 128 но отличались начиная с 128 и выше, где были размещены все их буквы с закорючками. Национальная версия MS-DOS имела множество этих страниц символов, обрабатывающих всё от Английского языка до Исландского они даже имели несколько "многоязыковых" страниц символов которые могли отображать Эспера́нто и Галисийский на томже компьютере! Круто! Но, к слову говоря, писать на Иврите и на Греческеском на одном компьютере было абсолютно невозможным до тех пор пока ты не напишешь свою программу которая отображает всё используя битовые карты символов (bitmapped graphics), так как Иврит и Греческий использовали разные страницы символов, которые по разному интерпретировали старшие цифры. А тем временем, в Азии происходили еще более сумасшедшие вещи, ведь нужно иметь в виду что Азиатский алфавит имеет тысячи символов, которые никогда не влезут в 8 бит. Это обычно решалось используя запутанную систему названную DBCS (с англ. "double byte character set" - двух байтовая таблица символов) в которой некоторые символы сохранялись одним байтом а другие занимали два. Было легко читать строку вперед, но практически невозможно прочитать её задом наперед. Программистов воодушевляло то что можно не использовать s++ и s-- чтобы двигаться по строке вперед и назад, но вместо этого вызывать функции такие как AnsiNext и AnsiPrev которые знали что для этого делать. Но, всё еще, большинство людей всего лишь притворялись что байт это символ и что символ это 8 бит и до тех пор пока ты не передаешь строку с одного компьютера на другой, или разговариваешь больше чем на одном языке, такой подход может работать. Но естественно, как только появился Интернет, это стало повседневной задачей и вся эта возня с вышла на первый план. К счастью, изобрели Unicode. Unicode Unicode был смелой попыткой создания единой таблицы символов, которая включала бы все системы письма на планете и некоторые выдуманные, как, например, Клингонскую. Некоторые люди имеют неправильное представление и думают что Unicode это просто 16-ти битный код, где каждый символ занимает 16 бит и таким образом содержит 65 536 возможных символов. Но, это не верно. Это один из наиболее общих мифов о Unicode, поэтому если ты в это верил, не расстраивайся. На самом деле, Unicode по другому смотрит на символы, и тебе надо понять как Unicode на них смотрит, иначе всё будет бессмысленным. До сих пор, мы предполагали что буква отображается в виде некой последовательности бит которую ты можешь сохранить на диске или в памяти: A -> 0100 0001 В Unicode, буква отображается в виде нечто, названного кодовой точкой (в ориг. code point) которая, пока еще, всего лишь теоретическая концепция. Как эта кодовая точка отображается в памяти или на диске это совсем другая история. В Unicode, буква A это платонический идеал. Она летает в облаках: A Платоническая A отличается от B, и отличается от a, но таже что и A и A и A. Идея в том, что A в стиле Times New Roman это тот же символ что и A в стиле Helvetica, но отличается от "a" в нижнем регистре не вызывает вопросов, но в других языках, просто выяснение того что такое буква может вызвать споры. В Немецком языке буква ß это действительная буква или просто прикольный способ написать ss? Если форма буквы изменяется в конце слова, это другая буква? Иврит говорит да, Арабский - нет. В любом случае, умные люди из консорциума Unicode выясняли это несколько десятилетий или около того, проводя дебаты в достаточно вежливой манере, и тебе не следует об этом беспокоится. Они уже всё выяснили. Каждой платонической букве в каждом алфавите назначено магическое число консорциумом Unicode которое записывается в виде: U+0639. Это магическое число и называется кодовой точкой.. U+ означает "Unicode" и числа в шестнадцатиричной системе. U+0639 это Арабская буква Ayin. Английская буква A имеет кодовую точку U+0041. Их все можно найти используя утилиту charmap для Windows 2000/XP или посетив сайт Unicode. Нету действительного ограничения количества букв, которое определяет Unicode и, на самом деле, у них больше 65 535 кодовых точек, поэтому не все буквы Unicode могут быть представлены в виде двух байт. В любом случае, это был миф. Хорошо, допустим у нас есть строка: Hello которая в Unicode соответсвует следующим пяти кодовым точкам: U+0048 U+0065 U+006C U+006C U+006F. Просто набор кодовых точек. Если точнее - чисел. Мы до сих пор ничего не сказали о том, как хринить их в памяти или представлять в электронном сообщении. Кодировки Вот здесь, как раз, и нужны кодировки. Самая простая идея для Unicode кодировки, которая приводит к мифу о двух байтах гласит: эй, давай просто сохранять эти числа по два байта на каждое. Таким образом, Hello становится 00 48 00 65 00 6С 00 6С 00 6F Верно? Не так быстро! Почему бы ему также не быть в виде 48 00 65 00 6С 00 6С 00 6F 00 ? Технически, да, я верю что такое возможно, и, на самом деле, в ранних реализациях, разработчики хотели сохранять их кодовые точки Unicode в порядке от старших к младшим или наоборот, в зависимости от того, с чем ЦПУ работает быстрее, и вот, прошёл вечер, пришло утро и вот у нас два способа сохранять Unicode. Таким образом людям пришлось договориться о причудливом соглашении сохранения FE FF в начале каждой Unicode строки; это называется BOM (Unicode Byte Order Mark - маркер последовательности байтов) и если ты меняешь местами младшие и старшие байты метка будет иметь вид FF EF и человек, читающий твою строку будет знать что он должен поменять местами очередность байт. Фух. Не каждая Unicode строка в мире имеет маркер последовательности бай в начале. Какое-то время решение казалось достаточно хорошим, но программисты жаловались: "Посмотрите на эти нули!", так как они были Американцами и работали с Английским текстом который редко использует кодовые точки выше U+00FF. Одни не имел ничего против удвоения количества байт на символ, другие не хотели удваивать количество памяти которе надо для хранения информации. В любом случае, существовало множество документов использующих ANSI и DBCS кодировки и кто должен был их все конвертировать? Только лишь из-за этого люди решили игнорировать Unicode на протяжении нескольких лет, а тем временем стало еще хуже. Таким образом изобрели прекрасную концепцию UTF-8. UTF-8 это еще одна система для хранения строк из кодовых точек Unicode (U+ магических цифер) в памяти используя 8 битные байты. В UTF-8 каждая кодовая точка 0-127 сохраняется единым байтом. Только кодовые точки 128 и выше сохраняются используя 2, 3, на самом деле, вплоть до 6 байт. Хорошим побочным эффектом было то, что Английский текст выглядел идентично как в UTF-8, так и в ASCII, поэтому Американцы даже не заметили что что-то произошло. Только остальная часть мира должна была бежать с препятствиями. В частности, Hello, которое было U+0048 U+0065 U+006C U+006C U+006F, будет сохранено как 48 65 6C 6C 6F, что, удивительно, совпадает с кодировкой в ASCII и в ANSI, и любой OEM таблице символов на планете. Сейчас, если у тебя хватает смелости, чтобы использовать Греческие или Клингонские буквы с акцентами, тебе придётся использовать несколько байт чтобы сохранить одну кодовую точку, но Американцы даже не заметят. (UTF-8 также имеет приятное свойство, при котором старый код обработки строк, который хочет использовать байт 0 в качестве завершения строки, не будет обрезать строки). До сих пор, я рассказал тебе три способа кодировки Unicode. Традиционный 2х байтовый метод называют UCS-2 (Unicode Character Set - 2, так как он использует 2 байта ) или UTF-16 (потому что 16 бит) но тебе всё еще надо будет выяснить последовательность байт от младших к старшим или наоборот. А также, есть новый популярный стандарт UTF-8, который имеет приятное свойство работать с Английскими текстами в старых кодировках и программами, которые понятия не имеют что существуют кодировки помимо ASCII. На самом деле, есть еще несколько способов кодирования Unicode. Есть UTF-7, который как UTF-8, но гарантирует, что старший бит всё время будет равен нулю, поэтому если тебе надо передать Unicode через суровую систему отправки электронных сообщений полиции, которая считает что 7 бит вполне достаточно, текст проскользнет не провредившись. Также есть UCS-4, которая сохраняет каждую кодовую точку в 4 байтах и имеет приятное свойство, при котором каждая отдельная кодовая точка может быть сохранена с помощь одинакового количества байт. И, на самом деле, то что ты смотришь на вещи в понятиях платоничиских идей букв, которые представляются кодовыми точками Unicode, позволяет эти кодовые точки закодировать с помощью любой старой схемы кодирования. Например, ты можешь закодировать Unicode строку Hello (U+0048 U+0065 U+006C U+006C U+006F) в ASCII или старой Греческой OEM кодировке, или Ивритской ANSI кодировке, или любой их нескольких сотен кодировок которые изобрели по данный момент, с одним подвохом: некоторые буквы могут не отображаться! Если кодовой точке Unicode нету соответсвия в кодировке, в которой ты хочешь ее представить, она будет отображаться в виде вопросительного знака: ? или, если ты действительно хорош, в виде квадрата. Что у тебя вышло? -> � Существуют сотни традиционных кодировок, которые могут хранить только некоторые кодовые точки корректно, и заменяют все другие кодовые точки на вопросительные знаки. Некоторые популярные кодировки Английского текса это: Windows-1252 (стандартная кодировка Windows 9x для Западной Европы) и ISO-8859-1, аля Latin-1 (также полезная для любого западноевропейского языка). Но, как только ты попытаешься сохранить Русские или Ивритские буквы в этих кодировках, как получишь серию вопросительных знаков. UTF 7, 8, 16 и 32 все имеют приятное свойство сохранять любые кодовые точки правильно. Единственный, наиболее важный факт о кодировках Если ты полностью забыл всё о чем я рассказывал выше, пожалуйста, запомни одну невероятно важную вещь. Нету смысла держать строку не зная в какой она кодировке. Ты больше не можешь засунуть свою голову в песок и притворятся что любой чистый текст это ASCII. Нет такой вещи как чистый текст (plain text) Если у тебя есть строка в памяти, в файле, или в электронном сообщении, ты должен знать в какой она кодировке иначе ты не сможешь ее правильно интерпретировать и отобразить. Практически каждая тупая "мой вебсайт выглядит как тарабарщина" или "она не может прочитать мое электронное письмо когда я использую буквы с акцентами" проблема всплывает из-за одного наивного программиста, который не понимает простого факта что если ты не говоришь мне в какой кодировке закодирована строка, будь то UTF-8 или ASCII или ISO-8859-1 (Latin 1) или Windows 1252 (западноевропейская) ты попросту не сможешь ее корректно отобразить или даже узнать где она заканчивается. Существуют сотни кодировок и всё что находится выше кодовой точки 127 можно только гадать. Как мы представляем информацию о том, какая кодировка используется в строке? Существуют стандартные пути чтобы это сделать. Для электронных сообщений тебе следует иметь следующую строку в заголовке формы Content-Type: text/plain; charset="UTF-8" Для веб страницы, главная идея заключалась в том чтобы web сервер возвращал похожий Content-Type в заголовке вместе с самой веб страницей - не в самом HTML, а в заголовке ответа сервера, который отсылается перед HTML. Этот подход приводит к проблемам. Предположим, у тебя есть большой веб сервер с множеством сайтов и сотнями страниц написанными различными людьми на разных языках и все используют кодировку, которую использует их версия Microsoft FrontPage. Веб сервер не будет знать в какой кодировке написан каждый файл, поэтому он не сможет отправлять заголовок Content-Type. Было бы куда проще, если бы ты мог указать Content-Type HTML файла прямо в самом файле используя специальный тег. Идея слегка безумна... как ты можешь прочитать HTML файл до того как узнаешь в какой он кодировке? К счастью, практически все кодировки в широком использовании делают одно и тоже что касается символом между 32 и 127, поэтому ты можешь всегда использовать их чтобы указать кодировку, перед началом использоввания специфичных символов:
Что делают веб браузеры когда они не находят Content-Type ни в заголовке http ответа, не в meta теге? Internet Explorer делает очень интересную вещь: он пытается угадать кодировку основываясь на частоте использования тех или иных символов. Из-за того что множество старых 8 битных страниц символов устанавливали свои национальные буквы в промежутке с 128 по 255, и того факта что каждый человеческий язык имеет различные гистограммы использования буков, у этого подхода есть шанс на успех. Этот странный подход работает, но не достаточно хорошо. Наивные авторы веб страниц, которые никогда не знали что им надо заголовок с Content-Type, смотрят на свою страницу в браузере и такие "о, смотрится хорошо", ровно до того момента как напишут то, что не совпадет в точности с частотным распределением буков их языка, и Internet Explorer решит что это Корейский и честно отобразит его, доказывая, по моему мнению, что правило Джона Послета, которое говорит "Будь либерален к тому, что принимаешь, и требователен к тому, что отсылаешь" это, откровенно говоря, не самый лучший инженерный подход. В любом случае, что же делать бедному читателю, который пишет на Болгарском который, кажеться, должен быть Корейским (даже не упрощённом Корейском)? Ему надо использовать меню Вид -> Кодировки и пытаться применять разные из них (есть как минимум дюжина западноевропейских языков) до тех пор, пока всё не станет на свои места. И это в случае, если читатель знает как это делается, так как большинство людей не знают. В последней версии CityDest, програмном обеспечении управления веб сайтами, опубликованной моей компаниею, мы решили намеренно использовать UCS-2 (двух байтовый) Unicode везде. Это ровно то что Visual Basic, COM и Windows NT/2000/XP используют для своих строковых типов. В коде C++ мы просто объявляем строку как wchar_t (с англ. wide char - дословно "широкий символ") вместо char и используем wcs функции вместо str функций (например, wcscat и wcslen вместо strcat и strlen). Чтобы создать строковый литерал UCS-2 на Си, просто поставь L перед ней следующим образом: L"Hello". Когда CityDesk публикует веб страницу, он конвертирует ее в UTF-8, которая поддерживается веб браузерами уже много лет. Это то как были закодированы все из 29 языковых версий Joel on Software и до сих пор не было ни одного человека, у которого возникли бы проблемы с их просмотром. Эта статья вышла довольно долгой, и я, вероятно, не могу описать в ней всё что нужно знать о кодировках Unicode, но надеюсь что если ты дочитал ее до конца, ты знаешь достаточно чтобы вернуться к программированию и, используя антибиотики вместо пиаяок и магических заклинаний, начнешь решать задачи связанные с кодировками. Дата публикации оригинала: 8.10.2003 Оригинал статьи: https://www.joelonsoftware.com... o-excuses/ Переводчик: Руслан Ковтун |
Всего комментариев 6
Комментарии
-
Замеченные очепятки
коммеНческую
если ты единственный из тех немногих людей - наверное "один из"
Проблема была в тоМ
ИзраЕле (И) - во многих местах
закарлючками. - закорючками. Или это специально?
беСсмысленным.
Статья очень интересная. И написана живо. Приятно читать. Спасибо.
Вот перекурю и продолжу....Запись от Байт размещена 16.02.2018 в 19:01 -
Продолжим...
которе
Цитата:Хорошим побочным эффектом было то, что Английский текст выглядел идентично как в UTF-8, так и в ASCII
корРектно
идея заГлючалась - но описка симпатичная
начать заноГо - заНово
8 битных страниц симОВлов
гистограммы спользования буков - видимо, уже сказывается усталость
намерЯнно - намерЕнно
небыло - раздельно
пиАвок - пиЯвок
опубликованной моею компаниею, - моей компанией
Еще раз - Спасибо!Запись от Байт размещена 16.02.2018 в 19:37 -
Запись от Croessmah размещена 17.02.2018 в 09:03 -
Статья хоть и интересная, но уже порядком морально устарела...
Большинство современных разработчиков придерживаются использования кодировки - UTF-8 без BOM... Кто не придерживается - тот приходит на форум и узнаёт о её существовании и тоже начинает её активно юзать... В стандарте HTML5 при указании кодировки в метатеге:
в принципе нет необходимости указывать Content-Type... Да и проблемы с кодировкой в наше время не такое частое явление, если только на неправильно настроенных серверах или старых серверах можно столкнуться с реальными проблемами, но это дичь... За перевод автору спасибо... Познавательная статья...HTML5 1
<meta charset="utf-8">
Запись от Fedor92 размещена 18.02.2018 в 20:40 -
Блин, мне вот сегодня эти кодировки и подкузьмили, описанное в статье я уже более-менее знал, но возникла обратная задача, обрабатывать символы из "верхней" части таблицы (английские и символы перевода строки) в текстовых файлах, которые могут быть как в UTF-8, так и в ASCII (на самом деле они будут еще и в UTF16, но потом), поэтому было принято решение все файлы рассматривать как будто бы они в однобайтовой кодировке, а File.ReadAllText натыкался на BOM, плевал на жестко заданную Encoding и при сохранении получалась бяка
Прямо не статья, а "сон в руку"Запись от stpigidy размещена 18.02.2018 в 22:07 -
Возможно, картинка по этой ссылке будет весьма в тему
http://apikabu.ru/img_n/2012-09_2/m38.png
Лично я ее распечатал и повесил в самом красном углу.Запись от Байт размещена 18.02.2018 в 22:40