Main Page
Статьи Компоненты Ссылки Разное

Работа с разными кодировками текста в базах данных

Андрей Боровский, kylixportal@narod.ru, 09/01/03

Эта статья представляет собой ответ на вопрос, который часто задают посетители моего сайта. Вопрос звучит примерно так: «У меня есть база данных, созданная в Windows (кодировка CP 1251), и Linux-СУБД, способная работать с этой базой данных. Как мне написать в Kylix приложение-клиент для работы с этой БД?» Проблема заключается в том, что на практике из всех русских кодировок Kylix может работать корректно только с KOI8-R, при этом данная кодировка должна быть установлена в системе по умолчанию. Естественно, что в такой ситуации графические элементы управления Kylix, да и другие элементы, связанные с обработкой наборов данных, не способны работать корректно с таблицами БД, созданными в кодировке 1251. В этой статье предлагается решение указанной проблемы на уровне приложения Kylix.

Решение, которое я предлагаю, заключается в перекодировке значений текстовых полей наборов данных «налету» перед передачей их компоненту-клиентскому набору данных, и обратной перекодировке перед передачей данных от клиентского набора компоненту-провайдеру. Напомню, что компонент провайдер играет роль посредника между первичным источником данных и клиентским набором данных. Среди прочего, компонент-провайдер DataSetProvider предоставляет два события: OnGetData и OnUpdateData. Эти события введены в классе TBaseProvider, являющемся предком компонента DataSetProvider. Как вы уже наверное поняли, речь идет не об изменении кодировки текста в базе данных, а о возможности работать в клиентских приложениях Kylix с таблицами БД в "чуждых" для Kylix кодировках.

У предложенного метода есть ограничения. Этот подход не применим при работе с XML-источниками данных, так как компонент XMLTransformProvider не является потомком класса TBaseProvider. Компонент SQLClientDataSet, хотя и использует внутренний провайдер, скрывает его события, так что непосредственно применить к нему данный метод также не удастся.

Событие OnGetData вызывается при получении провайдером данных от первичного источника и перед передачей их клиентскому набору данных. Событие OnUpdateData вызывается перед отправкой (измененных) данных от клиентского набора данных первичному источнику данных. Одним из параметров обработчиков обоих этих событий является указатель на экземпляр класса TCustomClientDataSet (параметр DataSet). Объект DataSet содержит данные, передаваемые провайдером клиентскому набору данных и обратно. Таким образом мы можем получить доступ к данным (и изменить их) перед тем как они будут переданы от базы данных клиентскому набору и перед тем как они будут переданы от клиентского набора базе данных.

Пусть у нас есть клиентское приложение баз данных со структурой, представленной на рисунке справа. Для перекодировки данных нам понадобятся функции перекодировки. Напишем две простые функции, приведенные ниже.


unit Encodings;

interface

const
   BASE_CHAR = 192;

   w2k : array[0..63] of Byte = (225, 226, 247, 231, 228, 229, 246, 250,
                                 233, 234, 235, 236, 237, 238, 239, 240,
                                 242, 243, 244, 245, 230, 232, 227, 254,
                                 251, 253, 255, 249, 248, 252, 224, 241,
                                 193, 194, 215, 199, 196, 197, 214, 218,
                                 201, 202, 203, 204, 205, 206, 207, 208,
                                 210, 211, 212, 213, 198, 200, 195, 222,
                                 219, 221, 223, 217, 216, 220, 192, 209);

   k2w : array[0..63] of Byte = (254, 224, 225, 246, 228, 229, 244, 227,
                                 245, 232, 233, 234, 235, 236, 237, 238,
                                 239, 255, 240, 241, 242, 243, 230, 226,
                                 252, 251, 231, 248, 253, 249, 247, 250,
                                 222, 192, 193, 214, 196, 197, 212, 195,
                                 213, 200, 201, 202, 203, 204, 205, 206,
                                 207, 223, 208, 209, 210, 211, 198, 194,
                                 220, 219, 199, 216, 221, 217, 215, 218);

function WinToK8R(const Text : String) : String;
function K8RToWin(const Text : String) : String;

implementation

function WinToK8R(const Text : String) : String;
var
  i : Integer;
begin
  Result := Text;
  for i := 1 to Length(Result) do
  if Result[i] >= Char(BASE_CHAR) then
  Result[i] := Char(w2k[Byte(Result[i])-BASE_CHAR]);
end;

function K8RToWin(const Text : String) : String;
var
  i : Integer;
begin
  Result := Text;
  for i := 1 to Length(Result) do
  if Result[i] >= Char(BASE_CHAR) then
  Result[i] := Char(k2w[Byte(Result[i])-BASE_CHAR]);
end;

end.

Функция WinToK8R выполняет перекодировку текста из кодировки 1251 в KOI8-R, а функция K8RToWin – обратную перекодировку. Разумеется, можно использовать и другие методы перекодироввки, а также реализовать перекодировку для других кодировок, например Unicode. Можно также реализовать механизм настройки приложения на работу с разными кодовыми страницами. Наши функции перекодировки выполняют роль примера и останавливаться на всех возможных вариантах мы не будем. Наша задача теперь заключается в том, чтобы назначить обработчики событиям OnGetData и OnUpdateData объекта DataSetProvider1.


procedure TForm1.DataSetProvider1GetData(Sender: TObject;
  DataSet: TCustomClientDataSet);
var
  S : String;
  i : Integer;
begin
  while not DataSet.Eof do
  begin
    for i := 0 to DataSet.Fields.Count-1 do
    begin
      if (DataSet.Fields[i].DataType = ftString) or
         (DataSet.Fields[i].DataType = ftFixedChar) then
      begin
        DataSet.Edit;
        S := DataSet.Fields[i].AsString;
        S := WinToK8R(S);
        DataSet.Fields[i].Value := S;
      end;
    end;
    DataSet.Next;
  end;
end;

procedure TForm1.DataSetProvider1UpdateData(Sender: TObject;
  DataSet: TCustomClientDataSet);
var
  S : String;
  i : Integer;
begin
  while not DataSet.Eof do
  begin
    for i := 0 to DataSet.Fields.Count-1 do
    begin
      if (DataSet.Fields[i].DataType = ftString) or
         (DataSet.Fields[i].DataType = ftFixedChar) then
      begin
        DataSet.Edit;
        S := DataSet.Fields[i].AsString;
        S := K8RToWin(S);
        DataSet.Fields[i].Value := S;
      end;
    end;
    DataSet.Next;
  end;
end;

Принцип действия этих обработчиков очень прост. Мы последовательно сканируем записи набора данных DataSet, находим в них поля текстового типа и выполняем перекодировку содержимого этих полей. Разумеется, если приложение предназначено для работы с таблицами заранее известной структуры, для ускорения работы можно не перебирать все поля записи, а обращаться к определенным полям с помощью метода FieldByName.

Благодаря этим обработчикам, объект ClientDataSet1 получает данные, перекодированные из кодировки 1251 в KOI8-R, а при внесении изменений в базу данных (с помощью метода ApplyUpdates), данные клиентского набора перекодируются из KOI8-R в кодировку 1251. При этом становятся возможнымиым корректная сортировка и выборка записей по текстовым ключам с использованием индексов и фильтров клиентского набора данных.

Перекодировку данных можно производить не только для полей типа строка, но и для полей двоичных объектов, содержащих текстовые данные. Ниже приводится пример перекодировки текста для некоего поля TextBLOB типа BLOB.


procedure TForm1.DataSetProvider1GetData(Sender: TObject;
  DataSet: TCustomClientDataSet);
var
  S : String;
  Text : TBlobField;
begin
  DataSet.Edit;
  Text := DataSet.FieldByName('TextBLOB') as TBlobField;
...
  S := Text.AsString;
  S := WinToK8R(S);
  Text.Value := S;
...

Статья и примеры программ © 2003 Андрей Наумович Боровский. Воспроизведение возможно только с разрешения автора.

Используются технологии uCoz