Delphi в мире Юникода, часть II: новые возможности RTL и классы для поддержки Юникода

By: Vladimir Panarin

Abstract: В этой статье описываются новые возможности Tiburon Runtime Library, которые помогут в работе с Юникод-строками.

    Введение

В части I я рассказал о том, насколько выгодна для Delphi-разработчиков поддержка Юникода, ведь она позволяет работать с любыми наборами символов в Юникод-вселенной. Мы рассмотрели новый тип UnicodeString и базовые приемы для работы с ним в Delphi.

В части II мы рассмотрим некоторые новые особенности Delphi Runtime Library, введенные для поддержки Юникода, и общие приемы работы со строками.

    Класс TCharacter

Tiburon RTL содержит новый класс TCharacter, описанный в модуле Character. Это закрытый класс, полностью состоящий из статичных функций. Разработчикам не нужно создавать экземпляры TCharacter, вместо этого лучше просто вызывать его статические методы. Этот класс выполняет различные функции, в том числе:

  • Преобразование символов в верхний или нижний регистр
  • Определение, к какому типу принадлежит символ, то есть буква ли это, цифра ли, знак препинания или что-то еще.

TCharacter использует стандарты, введенные консорциумом Юникода.

Разработчики могут использовать класс TCharacter, чтобы выполнять множество различных действий, которые раньше выполнялись над набором символов (set of char). К примеру, такой код:

uses
Character;

begin
if MyChar in [‘a’...’z’, ‘A’...’Z’] then
begin
  ...
end;
end;

легко может быть заменен таким:

uses
  Character;

begin
if TCharacter.IsLetter(MyChar) then
begin
    ...
end;
end;

Модуль Character также содержит ряд отдельных функций, выполняющих те же действия, что и аналогичные функции TCharacter. Поэтому, если Вы предпочитаете простой вызов функций, написанное выше можно написать и так:

uses
  Character;

begin
if IsLetter(MyChar) then
begin
    ...
end;
end;

Таким образом, класс TCharacter можно использовать для выполнения большинства манипуляций или проверок, которые Вам могут понадобиться.

Кроме того, TCharacter содержит методы для определения, является ли символ верхней или нижней частью Surrogate pair.

    Класс TEncoding

Tiburon RTL также содержит новый класс TEncoding. Его назначение – задавать специальные типы кодировок, чтобы Вы могли сообщить VCL, какой тип кодировки Вы используете в определенной ситуации.

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

begin
  ...
  MyStringList.SaveToFile(‘SomeFilename.txt’);  
  ...
end; 

и текст был бы записан в файл в ANSI-кодировке, использовавшейся по умолчанию. Это код по-прежнему прекрасно работает – он запишет текст в файл, используя, как и раньше, ANSI-кодировку, но теперь, когда Delphi поддерживает Юникод-строки, разработчикам может понадобиться записать строковые данные в какой-либо особенной кодировке. Итак, SaveToFile (как и LoadFromFile) теперь имеют второй необязательный параметр, задающий нужную кодировку:

begin
  ...
  MyStringList.SaveToFile(‘SomeFilename.txt’, TEncoding.Unicode);  
  ...
end; 

Выполните этот код, и файл будет записан в кодировке Юникод (UTF-16).

TEncoding также может конвертировать набор байтов из одной кодировки в другую, получать информацию о байтах и/или символах в строке или символьном массиве, конвертировать любую строку в массив байтов (array of byte (TBytes)) и многое другое, в зависимости от кодировки строки или символьного массива.

Класс TEncoding содержит следующие свойства, дающие доступ к различным кодировкам:

    class property ASCII: TEncoding read GetASCII;
    class property BigEndianUnicode: TEncoding read GetBigEndianUnicode;
    class property Default: TEncoding read GetDefault;
    class property Unicode: TEncoding read GetUnicode;
    class property UTF7: TEncoding read GetUTF7;
    class property UTF8: TEncoding read GetUTF8;

Свойство Default ссылается на активную кодовую ANSI-страницу. Свойство Unicode ссылается на UTF-16.

TEncoding также включает функцию

class function TEncoding.GetEncoding(CodePage: Integer): TEncoding;

возвращающую экземпляр TEncoding, соответствующий кодовой странице, переданной в параметре.

Кроме того, в нем есть следующая функция:

function GetPreamble: TBytes;

которая возвращает BOM для заданной кодировки.

TEncoding также совместим через интерфейс с .Net-классом Encoding.

    TStringBuilder

В RTL теперь есть класс TStringBuilder. Его назначение понятно из названия – это класс для построения (build up) строк. TStringBuilder содержит множество перегружаемых функций для добавления, замены и вставки элементов в строку. Этот класс делает простым создание строк из множества различных типов. Каждая из функций Append, Insert и Replace возвращает экземпляр TStringBuilder, поэтому они легко могут быть объединены для создания одной строки.

Например, Вы можете использовать TStringBuilder вместо сложного Format. К примеру, Вы можете написать следующий код:

procedure TForm86.Button2Click(Sender: TObject);
var
  MyStringBuilder: TStringBuilder;
  Price: double;
begin
  MyStringBuilder := TStringBuilder.Create('');
  try
    Price := 1.49;
    Label1.Caption := MyStringBuilder.Append('The apples are $').Append(Price). 
             ÄAppend(' a pound.').ToString;
  finally
    MyStringBuilder.Free;
  end;
end;

TStringBuilder также совместим через интерфейс с .Net-классом StringBuilder.

    Объявление новых строковых типов

Компилятор Tiburon’а позволяет Вам объявлять свои собственные типы строк, связанные с выбранной кодовой страницей. При этом доступно любой количество кодовых страниц (на MSDN есть прекрасное описание доступных страниц). Например, если Вам нужен строковый тип, связанный с ANSI-Кириллицей, объявляйте:

type
  // Кодовая страницы для ANSI-Кириллицы - 1251
  CyrillicString = type AnsiString(1251);

В этом случае новый строковый тип будет строкой, связанной с кодовой страницей Кириллицы.

    Дополнительная поддержка Юникода в RTL

В RTL добавлено множество подпрограмм, поддерживающих работу с Юникод-строками.

    StringElementSize

StringElementSize возвращает размер элемента ("кодовую точку") для данной строки. Рассмотрим следующий код:

procedure TForm88.Button3Click(Sender: TObject);
var
  A: AnsiString;
  U: UnicodeString;
begin
  A := 'This is an AnsiString';
  Memo1.Lines.Add('The ElementSize for an AnsiString is: ' + IntToStr(StringElementSize(A)));
  U := 'This is a UnicodeString';
  Memo1.Lines.Add('The ElementSize for an UnicodeString is: ' + IntToStr(StringElementSize(U)));
end;

Результат исполнения этого кода будет таким:

The ElementSize for an AnsiString is: 1
The ElementSize for an UnicodeString is: 2

    StringCodePage

StringCodePage вернет значение типа Word, соответствующее кодовой странице для данной строки.

Рассмотрим следующий код:

procedure TForm88.Button2Click(Sender: TObject);
type
  // The code page for ANSI-Cyrillic is 1251
  CyrillicString = type AnsiString(1251);
var
  A: AnsiString;
  U: UnicodeString;
  U8: UTF8String;
  C: CyrillicString;
begin
  A := 'This is an AnsiString';
  Memo1.Lines.Add('AnsiString Codepage: ' + IntToStr(StringCodePage(A)));
  U := 'This is a UnicodeString';
  Memo1.Lines.Add('UnicodeString Codepage: ' + IntToStr(StringCodePage(U)));
  U8 := 'This is a UTF8string';
  Memo1.Lines.Add('UTF8string Codepage: ' + IntToStr(StringCodePage(U8)));
  C := 'This is a CyrillicString';
  Memo1.Lines.Add('CyrillicString Codepage: ' + IntToStr(StringCodePage(C)));
end;

Результат исполнения этого кода будет таким:

The Codepage for an AnsiString is: 1252
The Codepage for an UnicodeString is: 1200
The Codepage for an UTF8string is: 65001
The Codepage for an CyrillicString is: 1251

    Другие возможности RTL для Юникода

Есть еще несколько подпрограмм для конвертирования строк из одной кодовой страницы в другую. Это:

UnicodeStringToUCS4String
UCS4StringToUnicodeString
UnicodeToUtf8
Utf8ToUnicode

Кроме того, в RTL введен тип RawByteString, который представляет собой строку, не связанную с какой-либо кодировкой:

  RawByteString = type AnsiString($FFFF);

Назначение типа RawByteString – сделать возможной передачу строковых данных для любой кодовой страницы без каких-либо преобразований кодировки. Это очень полезно для подпрограмм, для которых кодировка не имеет значения, вроде побайтового поиска в строке. Это означает, что параметры таких подпрограмм должны иметь тип RawByteString. Переменными типа RawByteString нужно пользоваться как можно меньше, так как это может привести к потере данных. Давайте посмотрим, почему.

Обычно, строковые типы поддерживают присвоения.

Например:

MyUnicodeString := MyAnsiString;

сработает, как и предполагалось – содержимое AnsiString будет помещено в UnicodeString. Как правило, Вы можете присваивать строку одного типа строке другого типа, и компилятор выполнит всю работу по преобразованию, если оно возможно.

Однако некоторые преобразования могут привести к потере данных, это следует учитывать при присвоении строки, имеющей тип с поддержкой Юникода, строке, тип которой не позволяет содержать Юникод-данные. К примеру, Вы можете присвоить UnicodeString переменной типа AnsiString, но если UnicodeString содержит символы, не предусмотренные активной ANSI-страницей, то эти символы будут потеряны при преобразовании. Рассмотрим следующий код:

procedure TForm88.Button4Click(Sender: TObject);
var
  U: UnicodeString;
  A: AnsiString;
begin
  U := 'This is a UnicodeString';
  A := U;
  Memo1.Lines.Add(A);
  U := 'Добро пожаловать в мир Юникода с использованием Дельфи 2009!!';
  A := U;
  Memo1.Lines.Add(A);
end;

Если для ОС активной является кодовая страница 1252, после выполнения этого кода на экране появится следующее:

This is a UnicodeString
????? ?????????? ? ??? ??????? ? ?????????????? ?????? 2009!!

Как видите, из-за того, что кириллические символы не предусмотрены в кодировке Windows-1252, информация потерялась при присвоении UnicodeString AnsiString-строке. В результате получился непонятный текст, так как UnicodeString содержала символы, непредставимые в кодовой странице для AnsiString, поэтому эти символы были потеряны и заменены знаком вопроса.

    SetCodePage

SetCodePage, объявленная в модуле System.pas как

procedure SetCodePage(var S: AnsiString; CodePage: Word; Convert: Boolean);

является новой функцией RTL, задающей новую кодовую страницу для данной ANSI-строки. Необязательный параметр Convert определяет, следует ли конвертировать передаваемую строку в заданную кодовую страницу. Если Convert=False, то у строки просто меняется кодовая страница. Если же Convert=True, то переданная строка будет конвертирована в данную кодовую страницу.

SetCodePage следует использовать редко и с большой осторожностью. Имейте ввиду, что если кодовая страница не подходит к существующей строке (то есть Convert=False), результат может получиться непредсказуемым. Также, если данные в строке были преобразованы и новая кодовая страница не может отобразить оригинальные символы, может произойти потеря данных.

    Получение TBytes из строк

RTL также содержит несколько перегружаемых подпрограмм для получения массива байтов из строк. Как Вы увидите в части III, лучше использовать в качестве буфера данных TBytes вместо обычной строки (string). RTL позволяет легко делать это, используя различные перегружаемые версии BytesOf(), которая принимает в качестве параметра различные типы строк.

    Заключение

Runtime Library Tiburon’а теперь полностью поддерживает новую Юникод-строку – UnicodeString. Она содержит новые классы и подпрограммы для обращения к Юникод-строкам, их обработки и преобразования, для управления кодовыми страницами и обеспечения простого перехода со старых версий.

В части III мы разберем особые конструкции, которые понадобятся Вам, чтобы проверить, готов ли Ваш код к Юникоду.

Server Response from: ETNASC03