Difference between revisions of "XML Decoders/ru"

From Lazarus wiki
Jump to navigationJump to search
m (Fixed syntax highlighting; deleted category included in page template)
 
(18 intermediate revisions by 5 users not shown)
Line 1: Line 1:
 +
{{XML Decoders}}
 +
 
== XML декодеры ==
 
== XML декодеры ==
 +
 +
Дополнение к [[XML_Tutorial/ru]].
  
 
Начиная с ревизии SVN 12582, XMLReader в состоянии обработать данные в любой кодировке при использовании внешних декодеров. Эта статья - краткое описание, как оно работает.
 
Начиная с ревизии SVN 12582, XMLReader в состоянии обработать данные в любой кодировке при использовании внешних декодеров. Эта статья - краткое описание, как оно работает.
Line 5: Line 9:
 
=== Доступные декодеры ===
 
=== Доступные декодеры ===
  
В настоящее время доступен декодер, который использует libiconv. У него есть две различные реализации:  
+
В состав пакета fcl-xml входит декодер, использующий библиотеку libiconv. Он имеет две разновидности:  
  
 
1. Модуль '''xmliconv.pas''', который использует существующий пакет iconvenc и предназначен для операционных систем Linux, FreeBSD и Darwin.  
 
1. Модуль '''xmliconv.pas''', который использует существующий пакет iconvenc и предназначен для операционных систем Linux, FreeBSD и Darwin.  
  
2. Модуль '''xmliconv_windows.pas''' для Windows. Он связан с библиотекой iconv.dll, которую Вы должны распространить вместе с приложением.
+
2. Модуль '''xmliconv_windows.pas''' для Windows. Он связан с библиотекой iconv.dll "родной" сборки (т.е. не из состава cygwin или mingw), которую придется распространять вместе с приложением.
  
 
=== Структура декодера ===
 
=== Структура декодера ===
Line 20: Line 24:
 
Вот краткое описание:
 
Вот краткое описание:
  
==== Получение декодера ====
+
==== Функция GetDecoder ====
  
 
  function GetDecoder(const AEncoding: string; out Decoder: TDecoder): Boolean; stdcall;
 
  function GetDecoder(const AEncoding: string; out Decoder: TDecoder): Boolean; stdcall;
  
Во время инициализации программы декодер регистрирует себя, вызывая процедуру <code>XMLRead.RegisterDecoder</code>, которой в качестве параметра передаётся функция <code>GetDecoder</code>.
+
Во время инициализации программы декодер необходимо зарегистрировать путем вызова процедуры <code>XMLRead.RegisterDecoder</code>, которой в качестве параметра передаётся функция <code>GetDecoder</code>.
  
Всякий раз, когда XMLReader сталкивается с меткой кодирования, которую он не может обработать сам, то вызывает все по очереди зарегистрированные функцией <code>GetDecoder</code> декодеры, пока один из них не возвратит ''True''.
+
Если в процессе чтения XMLReader обнаруживает кодировку, которую он не может обработать сам, то он вызывает все зарегистрированные функции <code>GetDecoder</code> в том же порядке, в котором они были зарегистрированы, до тех пор, пока одна из них не возвратит ''True''.
Параметры функции <code>GetDecoder</code> - названия кодировок и запись типа <code>TDecoder</code>, которую должна заполнить функция. Название кодировок ограничено символами из набора [.. 'Z'.. 'z', '0'.. '9', '.', '-,' _'] вне зависимости от регистра. Если декодер поддерживает данную кодировку, функция должна установить по крайней мере поле <code>Decode</code> в записи Decoder и возвратить ''Nhet''. Установка других полей записи <code>Decoder</code> является необязательным.
+
Параметры функции <code>GetDecoder</code> - название кодировки и запись типа <code>TDecoder</code>, которую должна заполнить функция. Название кодировки содержит только символамы из множества ['A'..'Z', 'a'..'z', '0'.. '9', '.', '-,' _'], сравнивать названия кодировок следует независимо от регистра. Если декодер поддерживает данную кодировку, функция должна установить по крайней мере поле <code>Decode</code> в записи Decoder и возвратить ''True''. Установка остальных полей записи <code>Decoder</code> не является обязательной.
  
==== Очистка ====
+
==== Процедура Cleanup ====
  
 
  procedure Cleanup(Context: Pointer); stdcall;
 
  procedure Cleanup(Context: Pointer); stdcall;
  
If <code>GetDecoder</code> sets the <code>Decoder.Cleanup</code> member, it is called by reader once, after processing of the current entity is finished. As the name suggests, the decoder should then free all resources it allocated.
+
Если функция <code>GetDecoder</code> установила поле <code>Decoder.Cleanup</code>, то указанная процедура будет вызвана один раз, когда чтение объекта завершено и декодер больше не нужен. Как следует из названия, декодер должен освободить все занятые ресурсы.
  
The value of <code>Decoder.Context</code> is passed to <code>Decode</code> and <code>Cleanup</code> procedures each time they are called. The reader does not assign any meaning to this value.
+
Значение <code>Decoder.Context</code> передается в качестве аргумента процедур <code>Decode</code> и <code>Cleanup</code> при каждом вызове. XMLReader сам не присваивает значение этому полю.
  
==== Декодирование ====
+
==== Функция Decode ====
  
 
  function Decode(Context: Pointer; InBuf: PChar; var InCnt: Cardinal;
 
  function Decode(Context: Pointer; InBuf: PChar; var InCnt: Cardinal;
 
                 OutBuf: PWideChar; var OutCnt: Cardinal): Integer; stdcall;
 
                 OutBuf: PWideChar; var OutCnt: Cardinal): Integer; stdcall;
  
The <code>Decode</code> function does the main job. It should convert the input data pointed by <code>InBuf</code> into UTF-16 in the current platform endianness and place it into <code>OutBuf</code>. The size of input buffer is supplied in
+
Функция <code>Decode</code> выполняет основную работу. Она должна преобразовать входные данные, указатель на которые находится в <code>InBuf</code>, в UTF-16 и записать перевод в буфер, на который указывает <code>OutBuf</code>. Размер входного буфера находится в <code>InCnt</code>, а размер буфера вывода находится в <code>OutCnt</code>.
<code>InCnt</code>, the space avaliable in output buffer is in <code>OutCnt</code>.
+
 
 +
'''Важное замечание''': значение <code>InCnt</code> выражено в '''bytes''', в то время как <code>OutCnt</code> - в '''WideChars'''.
 +
 
 +
Функция должна уменьшить <code>InCnt</code> и <code>OutCnt</code> в соответствии с количеством обработанных данных. Каждый обработанный символ уменьшает <code>OutCnt</code> на единицу (или на 2 в случае, если в буфер записывается суррогатная пара); то, на сколько уменьшается <code>InCnt</code>, зависит от входной кодировки.
 +
 
 +
Функция не должна делать каких-либо предположений о начальном размере буферов: для примера, парсер может вызвать <code>Decode</code> с недостаточной длиной входного буфера. В этом случае <code>Decode</code> должна возвратить 0, индицируя о том, она ничего не декодировала, после чего парсер прочитает дополнительные входные данные и вызовет <code>Decode</code> снова.
 +
 
 +
Функция должна возвращать положительный результат, если она что-то обработала, ноль - если нет (по причине отсутствия места во входном или выходном буфере) и отрицательное значение в случае, если входные данные содержат недопустимую последовательность. В настоящее время любое отрицательное значение просто прерывает чтение с сообщением об ошибке декодирования, но в дальнейшем, возможно, будут определены разновидности ошибок.
 +
 
 +
В случае обнаружения ошибки во входных данных, декодер все равно должен уменьшить значение <code>OutCnt</code> на количество успешно обработанных символов. Это позволяет парсеру сообщать точное расположение ошибки в тексте.
 +
 
 +
=== Пример декодера ===
 +
 
 +
Ниже приводится пример декодера для кодировки cp866. Он не имеет внутреннего состояния и не использует поля <code>Cleanup</code> и <code>Context</code>. Декодер легко модифицировать для обработки любой аналогичной однобайтовой кодировки путем замены таблицы преобразования.
 +
 
 +
<syntaxhighlight lang=pascal>
 +
unit xmlcp866;
 +
 
 +
interface
 +
 
 +
implementation
  
The important difference to note is that <code>InCnt</code> is given in '''bytes''', while <code>OutCnt</code> is in '''WideChars'''.
+
uses
 +
  SysUtils, xmlread;
  
The function must decrement <code>InCnt</code> and <code>OutCnt</code> according to the amount of data it processes. Each processed character decrements <code>OutCnt</code> by one (or by two in case the surrogate pair is written); the amount of <code>InCnt</code> decrement depends on the actual encoding.
+
const
 +
  cp866table: array[#128..#255] of WideChar=(
 +
      #$0410, #$0411, #$0412, #$0413, #$0414, #$0415, #$0416, #$0417,
 +
      #$0418, #$0419, #$041A, #$041B, #$041C, #$041D, #$041E, #$041F,
 +
      #$0420, #$0421, #$0422, #$0423, #$0424, #$0425, #$0426, #$0427,
 +
      #$0428, #$0429, #$042A, #$042B, #$042C, #$042D, #$042E, #$042F,
 +
      #$0430, #$0431, #$0432, #$0433, #$0434, #$0435, #$0436, #$0437,
 +
      #$0438, #$0439, #$043A, #$043B, #$043C, #$043D, #$043E, #$043F,
 +
      #$2591, #$2592, #$2593, #$2502, #$2524, #$2561, #$2562, #$2556,
 +
      #$2555, #$2563, #$2551, #$2557, #$255D, #$255C, #$255B, #$2510,
 +
      #$2514, #$2534, #$252C, #$251C, #$2500, #$253C, #$255E, #$255F,
 +
      #$255A, #$2554, #$2569, #$2566, #$2560, #$2550, #$256C, #$2567,
 +
      #$2568, #$2564, #$2565, #$2559, #$2558, #$2552, #$2553, #$256B,
 +
      #$256A, #$2518, #$250C, #$2588, #$2584, #$258C, #$2590, #$2580,
 +
      #$0440, #$0441, #$0442, #$0443, #$0444, #$0445, #$0446, #$0447,
 +
      #$0448, #$0449, #$044A, #$044B, #$044C, #$044D, #$044E, #$044F,
 +
      #$0401, #$0451, #$0404, #$0454, #$0407, #$0457, #$040E, #$045E,
 +
      #$00B0, #$2219, #$00B7, #$221A, #$2116, #$00A4, #$25A0, #$00A0);
  
No assumptions should be made about initial size of buffers: for example, the reader may call decoder with only a few bytes in input buffer. The decoder function then should return zero indicating nothing is processed, and the reader will fetch more input and call decoder again.
+
function cp866Decode(Context: Pointer; InBuf: PChar; var InCnt: Cardinal; OutBuf: PWideChar;
 +
                    var OutCnt: Cardinal): Integer; stdcall;
 +
var
 +
  I: Integer;
 +
  cnt: Cardinal;
 +
begin
 +
  cnt := OutCnt;        // число widechars
 +
  if cnt > InCnt then
 +
    cnt := InCnt;
 +
  for I := 0 to cnt-1 do
 +
  begin
 +
    if InBuf[I] < #128 then
 +
      OutBuf[I] := WideChar(ord(InBuf[I]))
 +
    else
 +
      OutBuf[I] := cp866table[InBuf[I]];
 +
  end;
 +
  Dec(InCnt, cnt);
 +
  Dec(OutCnt, cnt);
 +
  Result := cnt;
 +
end;
  
The function should return positive value if it had processed something, zero if it had not (e.g. because no space available in either input or output buffer), and negative value in cause the input data contains illegal sequence. In the future, there may be attempt to categorize the decoding errors, but currently any negative return simply aborts the reader with the 'Decoding error' message.
+
function GetCP866Decoder(const AEncoding: string; out Decoder: TDecoder): Boolean; stdcall;
 +
begin
 +
// Большинство кодировок имеет один или несколько 'псевдонимов'.
 +
  if SameText(AEncoding, 'IBM866') or
 +
    SameText(AEncoding, 'cp866') or
 +
    SameText(AEncoding, '866') or
 +
    SameText(AEncoding, 'csIBM866') then
 +
  begin
 +
    Decoder.Decode := @cp866Decode;
 +
    Decoder.Cleanup := nil;
 +
    Decoder.Context := nil;
 +
    Result := True;
 +
  end
 +
  else
 +
    Result := False;
 +
end;
  
In case of error in input data the decoder should still decrement <code>OutCnt</code> to reflect the number of successfully processed characters. This will be used by reader to provide location information in the exception error message.
+
initialization
 +
  RegisterDecoder(@GetCP866Decoder);
 +
end.
 +
</syntaxhighlight>

Latest revision as of 08:05, 3 March 2020

English (en) español (es) русский (ru) 中文(中国大陆)‎ (zh_CN)

XML декодеры

Дополнение к XML_Tutorial/ru.

Начиная с ревизии SVN 12582, XMLReader в состоянии обработать данные в любой кодировке при использовании внешних декодеров. Эта статья - краткое описание, как оно работает.

Доступные декодеры

В состав пакета fcl-xml входит декодер, использующий библиотеку libiconv. Он имеет две разновидности:

1. Модуль xmliconv.pas, который использует существующий пакет iconvenc и предназначен для операционных систем Linux, FreeBSD и Darwin.

2. Модуль xmliconv_windows.pas для Windows. Он связан с библиотекой iconv.dll "родной" сборки (т.е. не из состава cygwin или mingw), которую придется распространять вместе с приложением.

Структура декодера

Интерфейс с внешними декодерами сделан в простом процедурном стиле. Для написания декодера по существу используют следующие три процедуры:

  1. GetDecoder
  2. Decode
  3. Cleanup (опционально)

Вот краткое описание:

Функция GetDecoder

function GetDecoder(const AEncoding: string; out Decoder: TDecoder): Boolean; stdcall;

Во время инициализации программы декодер необходимо зарегистрировать путем вызова процедуры XMLRead.RegisterDecoder, которой в качестве параметра передаётся функция GetDecoder.

Если в процессе чтения XMLReader обнаруживает кодировку, которую он не может обработать сам, то он вызывает все зарегистрированные функции GetDecoder в том же порядке, в котором они были зарегистрированы, до тех пор, пока одна из них не возвратит True. Параметры функции GetDecoder - название кодировки и запись типа TDecoder, которую должна заполнить функция. Название кодировки содержит только символамы из множества ['A'..'Z', 'a'..'z', '0'.. '9', '.', '-,' _'], сравнивать названия кодировок следует независимо от регистра. Если декодер поддерживает данную кодировку, функция должна установить по крайней мере поле Decode в записи Decoder и возвратить True. Установка остальных полей записи Decoder не является обязательной.

Процедура Cleanup

procedure Cleanup(Context: Pointer); stdcall;

Если функция GetDecoder установила поле Decoder.Cleanup, то указанная процедура будет вызвана один раз, когда чтение объекта завершено и декодер больше не нужен. Как следует из названия, декодер должен освободить все занятые ресурсы.

Значение Decoder.Context передается в качестве аргумента процедур Decode и Cleanup при каждом вызове. XMLReader сам не присваивает значение этому полю.

Функция Decode

function Decode(Context: Pointer; InBuf: PChar; var InCnt: Cardinal;
                OutBuf: PWideChar; var OutCnt: Cardinal): Integer; stdcall;

Функция Decode выполняет основную работу. Она должна преобразовать входные данные, указатель на которые находится в InBuf, в UTF-16 и записать перевод в буфер, на который указывает OutBuf. Размер входного буфера находится в InCnt, а размер буфера вывода находится в OutCnt.

Важное замечание: значение InCnt выражено в bytes, в то время как OutCnt - в WideChars.

Функция должна уменьшить InCnt и OutCnt в соответствии с количеством обработанных данных. Каждый обработанный символ уменьшает OutCnt на единицу (или на 2 в случае, если в буфер записывается суррогатная пара); то, на сколько уменьшается InCnt, зависит от входной кодировки.

Функция не должна делать каких-либо предположений о начальном размере буферов: для примера, парсер может вызвать Decode с недостаточной длиной входного буфера. В этом случае Decode должна возвратить 0, индицируя о том, она ничего не декодировала, после чего парсер прочитает дополнительные входные данные и вызовет Decode снова.

Функция должна возвращать положительный результат, если она что-то обработала, ноль - если нет (по причине отсутствия места во входном или выходном буфере) и отрицательное значение в случае, если входные данные содержат недопустимую последовательность. В настоящее время любое отрицательное значение просто прерывает чтение с сообщением об ошибке декодирования, но в дальнейшем, возможно, будут определены разновидности ошибок.

В случае обнаружения ошибки во входных данных, декодер все равно должен уменьшить значение OutCnt на количество успешно обработанных символов. Это позволяет парсеру сообщать точное расположение ошибки в тексте.

Пример декодера

Ниже приводится пример декодера для кодировки cp866. Он не имеет внутреннего состояния и не использует поля Cleanup и Context. Декодер легко модифицировать для обработки любой аналогичной однобайтовой кодировки путем замены таблицы преобразования.

unit xmlcp866;

interface

implementation

uses
  SysUtils, xmlread;

const
  cp866table: array[#128..#255] of WideChar=(
      #$0410, #$0411, #$0412, #$0413, #$0414, #$0415, #$0416, #$0417,
      #$0418, #$0419, #$041A, #$041B, #$041C, #$041D, #$041E, #$041F,
      #$0420, #$0421, #$0422, #$0423, #$0424, #$0425, #$0426, #$0427,
      #$0428, #$0429, #$042A, #$042B, #$042C, #$042D, #$042E, #$042F,
      #$0430, #$0431, #$0432, #$0433, #$0434, #$0435, #$0436, #$0437,
      #$0438, #$0439, #$043A, #$043B, #$043C, #$043D, #$043E, #$043F,
      #$2591, #$2592, #$2593, #$2502, #$2524, #$2561, #$2562, #$2556,
      #$2555, #$2563, #$2551, #$2557, #$255D, #$255C, #$255B, #$2510,
      #$2514, #$2534, #$252C, #$251C, #$2500, #$253C, #$255E, #$255F,
      #$255A, #$2554, #$2569, #$2566, #$2560, #$2550, #$256C, #$2567,
      #$2568, #$2564, #$2565, #$2559, #$2558, #$2552, #$2553, #$256B,
      #$256A, #$2518, #$250C, #$2588, #$2584, #$258C, #$2590, #$2580,
      #$0440, #$0441, #$0442, #$0443, #$0444, #$0445, #$0446, #$0447,
      #$0448, #$0449, #$044A, #$044B, #$044C, #$044D, #$044E, #$044F,
      #$0401, #$0451, #$0404, #$0454, #$0407, #$0457, #$040E, #$045E,
      #$00B0, #$2219, #$00B7, #$221A, #$2116, #$00A4, #$25A0, #$00A0);

function cp866Decode(Context: Pointer; InBuf: PChar; var InCnt: Cardinal; OutBuf: PWideChar;
                     var OutCnt: Cardinal): Integer; stdcall;
var
  I: Integer;
  cnt: Cardinal;
begin
  cnt := OutCnt;         // число widechars
  if cnt > InCnt then
    cnt := InCnt;
  for I := 0 to cnt-1 do
  begin
    if InBuf[I] < #128 then
      OutBuf[I] := WideChar(ord(InBuf[I]))
    else
      OutBuf[I] := cp866table[InBuf[I]];
  end;
  Dec(InCnt, cnt);
  Dec(OutCnt, cnt);
  Result := cnt;
end;

function GetCP866Decoder(const AEncoding: string; out Decoder: TDecoder): Boolean; stdcall;
begin
// Большинство кодировок имеет один или несколько 'псевдонимов'.
  if SameText(AEncoding, 'IBM866') or
     SameText(AEncoding, 'cp866') or
     SameText(AEncoding, '866') or
     SameText(AEncoding, 'csIBM866') then
  begin
    Decoder.Decode := @cp866Decode;
    Decoder.Cleanup := nil;
    Decoder.Context := nil;
    Result := True;
  end
  else
    Result := False;
end;

initialization
  RegisterDecoder(@GetCP866Decoder);
end.