Обработка AnsiString и его шестнадцатеричного кода в версиях Delphi с поддержкой Unicode

Я запускаю устаревшее приложение, построенное на Delphi2007, где мы использовали для обработки неанглийских символов, сохраняя 2-байтовый шестнадцатеричный код символа в БД. Во время чтения мы применяем char() для преобразования этого шестнадцатеричного кода в строку.

Преобразование строки в шестнадцатеричный формат (перед сохранением в БД):

strHex := Format( '%x', [ Byte( strText[ lIndex ] ) shr 4 ] );
DataStr[ lPos ] := strHex[ 1 ];
inc( lPos );

strHex := Format( '%x', [ Byte( strText[ lIndex ] ) and $0F ] );
DataStr[ lPos ] := strHex[ 1 ];
inc( lPos );

//in simple I am saving the Hex code to pcData

Hex в строку (после чтения из БД):

strText := strText + Chr( StrToInt('$'+ DataStr[lPos] + DataStr[lPos + 1]))

Этот код начал ломаться после перехода на Delphi XE7, где string обрабатывается как UniCode String, мы явно должны преобразовать строку в тип AnsiString.

Преобразование приведенной ниже строки в шестнадцатеричный
ТуцЕфылАшдеук8311
В Delphi 2007 дает:
\D2\F3\F6\C5\F4\FB\EB\C0\F8\E4 \E5\F3\EA8311
В Delphi XE7 выдает:
\22\43\46\1A\33\4B\4B\48\44\42\14\44\49 \33\351522


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


person user1897277    schedule 04.02.2016    source источник
comment
Это чрезвычайно странно. Очень странная кодировка, которую вы придумали. Ваша база данных поддерживала только 7-битный ASCII? Почему бы не использовать что-то стандартное, например UTF-8? Какие у тебя сейчас цели? Вы хотите читать и писать в эту странную кодировку, которую вы изобрели? Или просто читать оттуда. Вы не хотите использовать Unicode?   -  person David Heffernan    schedule 04.02.2016
comment
Наше приложение работает как на SqlServer, так и на Oracle, нам нужно придерживаться этого кода для поддержки данных, которые были перенесены из более старых версий. Мы знаем, что переход на UTF8 — это решение, и реализовали то же самое, но мне также пришлось бы писать код для обработки данных, которые вводятся из более старых версий.   -  person user1897277    schedule 04.02.2016
comment
Вы не ответили ни на один из моих вопросов.   -  person David Heffernan    schedule 04.02.2016
comment
Да, мое приложение должно читать это для обратной совместимости, в случае редактирования и сохранения мы конвертируем кодировку в UTF8, это отдельный поток, я не упоминал его здесь.   -  person user1897277    schedule 04.02.2016


Ответы (2)


Согласно комментариям, вам просто нужно декодировать эти данные в собственный Unicode string. Сделайте это так:

  1. Считайте закодированный текст из базы данных в переменную string.
  2. Декодируйте этот текст в массив байтов, а не в строку. Ваш код Delphi 2007 можно использовать практически так же, но он должен записывать в массив байтов, а не в строку.
  3. Этот массив байтов закодирован в стандарте ANSI 1251. Расшифруйте его с помощью TEncoding.GetString. Вам потребуется создать экземпляр класса TEncoding с правильной кодовой страницей, Encoding := TEncoding.GetEncoding(1251).
person David Heffernan    schedule 04.02.2016
comment
Вы должны использовать TEncoding.GetEncoding() вместо прямого вызова TEncoding.Create(). Альтернативой является объявление нового типа type Win1251String = type AnsiString(1251);, затем вы можете объявить переменную этого типа, прочитать необработанные данные БД непосредственно в нее и назначить/преобразовать ее в обычный String. Позвольте RTL выполнить преобразование за вас, TEncoding не требуется. - person Remy Lebeau; 04.02.2016
comment
@ Реми Спасибо. На самом деле я предпочитаю использовать здесь TEncoding. Мне он кажется более явным и точным. Очевидно, это вопрос предпочтений. - person David Heffernan; 06.02.2016

Во-первых, более простым способом создания шестнадцатеричной строки было бы использование собственной функции RTL BinToHex() вместо написания собственного кода преобразования, например:

var
  ...
  s: AnsiString;
  DataStr: string; 
  lPos: Integer;
  ...
begin
  ...
  s := '...';
  BinToHex(PAnsiChar(s), @DataStr[lPos], Length(s)); 
  Inc(lPos, Length(s)*2);
  ...
end;

Затем вы можете использовать HexToBin(), чтобы отменить его. И поскольку вы имеете дело с закодированными данными ANSI, вы можете объявить переменную AnsiString, которая имеет сходство с желаемой кодировкой кодовой страницы (в вашем случае, вероятно, 1251), прочитать шестнадцатеричный код непосредственно в эту переменную, а затем назначить/привести ее к обычный String и позвольте RTL выполнить преобразование в Unicode для вас:

type
  Win1251String = type AnsiString(1251);
var
  ...
  tmp: Win1251String;
  DataStr, strText: string;
  lPos: Integer;
  ...
begin
  ...
  SetLength(tmp, LengthOfHex div 2);
  HexToBin(@DataStr[lPos], PAnsiChar(tmp), Length(tmp));
  strText := String(tmp);
  ...
end;
person Remy Lebeau    schedule 04.02.2016