Вам может понадобится такая кодовая страница, если у вас есть фрагмент двоичных данных с внедрённым текстом ASCII. Вы бы хотели иметь возможность извлекать текст ASCII и даже манипулировать им, но при этом и также обрабатывать части, не относящиеся к ASCII, как загадочные символы, не имеющие смысла. Но вам также нужно иметь возможность преобразовать их обратно в исходные байты.
Например, формат двоичных данных может быть таким: строка ASCII, за которой следует 32-разрядное целое число в формате big-endian. Вы хотите проанализировать строку ASCII, затем взять следующие четыре символа, перевернуть их (преобразовать в little-endian), а затем преобразовать всё вместе обратно в байты, чтобы можно было извлечь целое число (integer).
В современных языках программирования (типа Delphi и C#) есть много удобных средств для управления текстом, но для этого требуются строковые переменные, которые выражаются как последовательность кодов UTF16-LE. Итак, вам нужен способ конвертировать байты в коды UTF16-LE - причём так, что байты меньше 128 сопоставлялись бы с соответствующими символами ASCII, а байты 128 и выше сопоставлялись бы с чем-то обратимым.
К примеру, кодовая страница UTF-8 не подойдёт, потому что в UTF-8 существуют недопустимые последовательности байтов, которые не будут преобразованы в текст (и обратно). Другой вариант, который вы можете выбрать - это кодовая страница 1252, но она также не будет работать, потому что в ней существует несколько неопределенных кодовых позиций (#$81, #$8D, #$8F, #$90, #$9D - прим. пер.). Это означает, что эти байты будут преобразованы в #$FFFD (REPLACEMENT CHARACTER), который является специальным символом Unicode, означающим "Здесь был символ, но я не могу выразить его в Unicode". Обычно этот символ используется для представления ошибок кодирования. (Прим.пер.: или же подобные байты могут быть вообще удалены из результирующей строки Unicode.)
Хотя вы вряд ли будете пробовать, но я также замечу, что двухбайтовые кодовые страницы тоже не будут работать, потому что они берут пары байтов и преобразуют их в Unicode. Это означает, что изменение порядка символов Unicode (для нашей задачи преобразования endian) не позволит сделать обратное преобразование.
Хорошо, я перейду сразу к делу. Кодовая страница, которую я использую для такого рода вещей - это кодовая страница 437 (прим. пер.: DOSLatinUS - кодовая страница, использовавшаяся в первоначальной версии IBM PC 1981 года). Каждый байт в ней определяется и сопоставляется с уникальной кодовой точкой Unicode, и она совпадает с ASCII для первых 128 значений.
uses StrUtils; // для ReverseString procedure TForm1.Button1Click(Sender: TObject); type CP437String = type AnsiString(437); var CP437: CP437String; UStr: String; X: Integer; // = UnicodeString begin // Заполнили строку // (в реальной программе вы бы загружали строку из потока данных) SetLength(CP437, 256); for X := 0 to 255 do CP437[X + 1] := AnsiChar(X); // Преобразовали CP437 в Unicode UStr := String(CP437); // Меняем порядок _символов_ UStr := ReverseString(UStr); // Преобразовали Unicode обратно в CP437 CP437 := CP437String(UStr); // Проверяем обратное преобразование // Должен быть изменён порядок _байт_ for X := 0 to 255 do if Ord(CP437[X + 1]) <> 255 - X then raise Exception.CreateFmt('Ошибка преобразования для #%d', [255 - X]); // Сообщение должно быть показано ShowMessage('Всё верно'); end;Я взял все байты 0 до 255 и преобразовал их в строку с помощью кодовой страницы 437. Потом строка переворачивается и конвертируется обратно в байты. После чего проверяем, что результирующие байты также меняются местами.
CP437String = type AnsiString;
ОтветитьУдалитьCP437String = type AnsiString(866);
CP437String = type AnsiString(874);
CP437String = type AnsiString(1250);
CP437String = type AnsiString(1251);
CP437String = type AnsiString(1252);
CP437String = type AnsiString(1253);
CP437String = type AnsiString(1254);
CP437String = type AnsiString(1255);
CP437String = type AnsiString(1256);
CP437String = type AnsiString(1257);
CP437String = type AnsiString(1258);
Все это прекрасно работает в приведенном вами примере...
Да, это называется "надеяться на детали реализации".
УдалитьЭто работает сейчас, потому что реализация автоматического конвертирования строк в RTL вызывает MultiByteToWideChar, который выполняет стратегию "best fit": https://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WindowsBestFit/bestfit1252.txt - т.е. оставляет "неопределённые" точки без изменений.
Best-fit стратегии не документированы (в частности, о них нет упоминания в документации на MultiByteToWideChar), и полагаться на них не стоит.
Это особенно верно, если вы собираетесь передавать строки между разными реализациями. Например, DLL может использовать .NET или ICU - и у них может быть иное представление, как следует поступать с неопределёнными кодовыми позициями. Или даже если ваш код будет потом перекомпилироваться под другую платформу (Mac, iOS, Android, Linux) - там правила преобразования тоже могут быть иными.
К примеру, #$0081 - это управляющий символ High Octet Preset, #$008D - Reverse Line Feed, и т.д. Т.е. какая-то реализация может не посчитать правильным поведение Windows по внедрению в строку ранее отсутствующих в ней символов.
Поэтому, лучше использовать кодовою страницу с полностью определённым двусторонним преобразованием. Но она, конечно, не единственная.
Я не понимаю исходной проблемы. Разве не подойдёт любая однобайтовая кодировка, являющаяся надмножеством 7-битной ASCII и у которой определены все 256 символов? Но ведь подавляющее большинство кодировок удовлетворяют (наверное) этим условиям, те же кириллические 866 и 1251 тоже прекрасно сработают и переживут преобразование в Юникод и назад. Что такого особенного в 437?
ОтветитьУдалитьДа, подойдёт. Просто надо учитывать, что это у нас 1251 - а оригинальный пост написан для англоязычной аудитории, которая пользуется 1252 и в душе не чает, кто такой этот 1251.
УдалитьНикаких особенностей за 437 нет.