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)
 
(6 intermediate revisions by 4 users not shown)
Line 9: 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 50: Line 50:
 
'''Важное замечание''': значение <code>InCnt</code> выражено в '''bytes''', в то время как <code>OutCnt</code> - в '''WideChars'''.
 
'''Важное замечание''': значение <code>InCnt</code> выражено в '''bytes''', в то время как <code>OutCnt</code> - в '''WideChars'''.
  
Функция должна уменьшить <code>InCnt</code> and <code>OutCnt</code> в соответствии с количеством обработанных данных. Каждый обработанный символ уменьшает <code>OutCnt</code> на единицу (или на 2 в случае, если в буфер записывается суррогатная пара); то, на сколько уменьшается <code>InCnt</code>, зависит от входной кодировки.
+
Функция должна уменьшить <code>InCnt</code> и <code>OutCnt</code> в соответствии с количеством обработанных данных. Каждый обработанный символ уменьшает <code>OutCnt</code> на единицу (или на 2 в случае, если в буфер записывается суррогатная пара); то, на сколько уменьшается <code>InCnt</code>, зависит от входной кодировки.
  
Функция не должна делать каких-либо предположений о начальном размере буферов: для примера, Reader может вызвать декодер с недостаточной длиной входного буфера. В этом случае функция decoder должна возвратить 0, индицируя о том, она ничего не декодировала, после чего reader прочитает дополнительные входные данные и вызовет decoder снова.
+
Функция не должна делать каких-либо предположений о начальном размере буферов: для примера, парсер может вызвать <code>Decode</code> с недостаточной длиной входного буфера. В этом случае <code>Decode</code> должна возвратить 0, индицируя о том, она ничего не декодировала, после чего парсер прочитает дополнительные входные данные и вызовет <code>Decode</code> снова.
  
Функция должна возвращать положительный результат, если она что-то обработала, ноль - если нет (по причине отсутствия места во входном или выходном буфере), и отрицательное значение в случае, если входные данные содержат недопустимую последовательность. В настоящее время любое отрицательное значение просто прерывает чтение с сообщением об ошибке декодирования, но в дальнейшем, возможно, будут определены разновидности ошибок.
+
Функция должна возвращать положительный результат, если она что-то обработала, ноль - если нет (по причине отсутствия места во входном или выходном буфере) и отрицательное значение в случае, если входные данные содержат недопустимую последовательность. В настоящее время любое отрицательное значение просто прерывает чтение с сообщением об ошибке декодирования, но в дальнейшем, возможно, будут определены разновидности ошибок.
  
В случае обнаружения ошибки во входных данных декодер все равно должен уменьшить значение <code>OutCnt</code> на количество успешно обработанных символов. Это позволяет парсеру сообщать точное расположение ошибки в тексте.
+
В случае обнаружения ошибки во входных данных, декодер все равно должен уменьшить значение <code>OutCnt</code> на количество успешно обработанных символов. Это позволяет парсеру сообщать точное расположение ошибки в тексте.
 +
 
 +
=== Пример декодера ===
 +
 
 +
Ниже приводится пример декодера для кодировки cp866. Он не имеет внутреннего состояния и не использует поля <code>Cleanup</code> и <code>Context</code>. Декодер легко модифицировать для обработки любой аналогичной однобайтовой кодировки путем замены таблицы преобразования.
 +
 
 +
<syntaxhighlight lang=pascal>
 +
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.
 +
</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.