Андрей Боровский,
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 Андрей Наумович Боровский. Воспроизведение возможно только с разрешения автора.