XML Decoders/ru: what changed
Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.
Title: XML Decoders/ru
| Old | New | Line | |
|---|---|---|---|
| 1 | + | ## XML декодеры | |
| 2 | + | ||
| 3 | + | Дополнение к [[XML Tutorial/ru]]. | |
| 4 | + | ||
| 5 | + | Начиная с ревизии SVN 12582, XMLReader в состоянии обработать данные в любой кодировке при использовании внешних декодеров. Эта статья - краткое описание, как оно работает. | |
| 6 | + | ||
| 7 | + | ### Доступные декодеры | |
| 8 | + | ||
| 9 | + | В состав пакета fcl-xml входит декодер, использующий библиотеку libiconv. Он имеет две разновидности: | |
| 10 | + | ||
| 11 | + | 1\. Модуль **xmliconv.pas**, который использует существующий пакет iconvenc и предназначен для операционных систем Linux, FreeBSD и Darwin. | |
| 12 | + | ||
| 13 | + | 2\. Модуль **xmliconv_windows.pas** для Windows. Он связан с библиотекой iconv.dll "родной" сборки (т.е. не из состава cygwin или mingw), которую придется распространять вместе с приложением. | |
| 14 | + | ||
| 15 | + | ### Структура декодера | |
| 16 | + | ||
| 17 | + | Интерфейс с внешними декодерами сделан в простом процедурном стиле. Для написания декодера по существу используют следующие три процедуры: | |
| 18 | + | ||
| 19 | + | 1. `GetDecoder` | |
| 20 | + | 2. `Decode` | |
| 21 | + | 3. `Cleanup` (опционально) | |
| 22 | + | ||
| 23 | + | Вот краткое описание: | |
| 24 | + | ||
| 25 | + | #### Функция GetDecoder | |
| 26 | + | ||
| 27 | + | `function GetDecoder(const AEncoding: string; out Decoder: TDecoder): Boolean; stdcall;` | |
| 28 | + | ||
| 29 | + | Во время инициализации программы декодер необходимо зарегистрировать путем вызова процедуры `XMLRead.RegisterDecoder`, которой в качестве параметра передаётся функция `GetDecoder`. | |
| 30 | + | ||
| 31 | + | Если в процессе чтения XMLReader обнаруживает кодировку, которую он не может обработать сам, то он вызывает все зарегистрированные функции `GetDecoder` в том же порядке, в котором они были зарегистрированы, до тех пор, пока одна из них не возвратит *True*. Параметры функции `GetDecoder` - название кодировки и запись типа `TDecoder`, которую должна заполнить функция. Название кодировки содержит только символамы из множества \['A'..'Z', 'a'..'z', '0'.. '9', '.', '-,' _'\], сравнивать названия кодировок следует независимо от регистра. Если декодер поддерживает данную кодировку, функция должна установить по крайней мере поле `Decode` в записи Decoder и возвратить *True*. Установка остальных полей записи `Decoder` не является обязательной. | |
| 32 | + | ||
| 33 | + | #### Процедура Cleanup | |
| 34 | + | ||
| 35 | + | `procedure Cleanup(Context: Pointer); stdcall;` | |
| 36 | + | ||
| 37 | + | Если функция `GetDecoder` установила поле `Decoder.Cleanup`, то указанная процедура будет вызвана один раз, когда чтение объекта завершено и декодер больше не нужен. Как следует из названия, декодер должен освободить все занятые ресурсы. | |
| 38 | + | ||
| 39 | + | Значение `Decoder.Context` передается в качестве аргумента процедур `Decode` и `Cleanup` при каждом вызове. XMLReader сам не присваивает значение этому полю. | |
| 40 | + | ||
| 41 | + | #### Функция Decode | |
| 42 | + | ||
| 43 | + | `function Decode(Context: Pointer; InBuf: PChar; var InCnt: Cardinal;`\ | |
| 44 | + | ` OutBuf: PWideChar; var OutCnt: Cardinal): Integer; stdcall;` | |
| 45 | + | ||
| 46 | + | Функция `Decode` выполняет основную работу. Она должна преобразовать входные данные, указатель на которые находится в `InBuf`, в UTF-16 и записать перевод в буфер, на который указывает `OutBuf`. Размер входного буфера находится в `InCnt`, а размер буфера вывода находится в `OutCnt`. | |
| 47 | + | ||
| 48 | + | **Важное замечание**: значение `InCnt` выражено в **bytes**, в то время как `OutCnt` - в **WideChars**. | |
| 49 | + | ||
| 50 | + | Функция должна уменьшить `InCnt` и `OutCnt` в соответствии с количеством обработанных данных. Каждый обработанный символ уменьшает `OutCnt` на единицу (или на 2 в случае, если в буфер записывается суррогатная пара); то, на сколько уменьшается `InCnt`, зависит от входной кодировки. | |
| 51 | + | ||
| 52 | + | Функция не должна делать каких-либо предположений о начальном размере буферов: для примера, парсер может вызвать `Decode` с недостаточной длиной входного буфера. В этом случае `Decode` должна возвратить 0, индицируя о том, она ничего не декодировала, после чего парсер прочитает дополнительные входные данные и вызовет `Decode` снова. | |
| 53 | + | ||
| 54 | + | Функция должна возвращать положительный результат, если она что-то обработала, ноль - если нет (по причине отсутствия места во входном или выходном буфере) и отрицательное значение в случае, если входные данные содержат недопустимую последовательность. В настоящее время любое отрицательное значение просто прерывает чтение с сообщением об ошибке декодирования, но в дальнейшем, возможно, будут определены разновидности ошибок. | |
| 55 | + | ||
| 56 | + | В случае обнаружения ошибки во входных данных, декодер все равно должен уменьшить значение `OutCnt` на количество успешно обработанных символов. Это позволяет парсеру сообщать точное расположение ошибки в тексте. | |
| 57 | + | ||
| 58 | + | ### Пример декодера | |
| 59 | + | ||
| 60 | + | Ниже приводится пример декодера для кодировки cp866. Он не имеет внутреннего состояния и не использует поля `Cleanup` и `Context`. Декодер легко модифицировать для обработки любой аналогичной однобайтовой кодировки путем замены таблицы преобразования. | |
| 61 | + | ||
| 62 | + | ``` pascal | |
| 63 | + | unit xmlcp866; | |
| 64 | + | ||
| 65 | + | interface | |
| 66 | + | ||
| 67 | + | implementation | |
| 68 | + | ||
| 69 | + | uses | |
| 70 | + | SysUtils, xmlread; | |
| 71 | + | ||
| 72 | + | const | |
| 73 | + | cp866table: array[#128..#255] of WideChar=( | |
| 74 | + | #$0410, #$0411, #$0412, #$0413, #$0414, #$0415, #$0416, #$0417, | |
| 75 | + | #$0418, #$0419, #$041A, #$041B, #$041C, #$041D, #$041E, #$041F, | |
| 76 | + | #$0420, #$0421, #$0422, #$0423, #$0424, #$0425, #$0426, #$0427, | |
| 77 | + | #$0428, #$0429, #$042A, #$042B, #$042C, #$042D, #$042E, #$042F, | |
| 78 | + | #$0430, #$0431, #$0432, #$0433, #$0434, #$0435, #$0436, #$0437, | |
| 79 | + | #$0438, #$0439, #$043A, #$043B, #$043C, #$043D, #$043E, #$043F, | |
| 80 | + | #$2591, #$2592, #$2593, #$2502, #$2524, #$2561, #$2562, #$2556, | |
| 81 | + | #$2555, #$2563, #$2551, #$2557, #$255D, #$255C, #$255B, #$2510, | |
| 82 | + | #$2514, #$2534, #$252C, #$251C, #$2500, #$253C, #$255E, #$255F, | |
| 83 | + | #$255A, #$2554, #$2569, #$2566, #$2560, #$2550, #$256C, #$2567, | |
| 84 | + | #$2568, #$2564, #$2565, #$2559, #$2558, #$2552, #$2553, #$256B, | |
| 85 | + | #$256A, #$2518, #$250C, #$2588, #$2584, #$258C, #$2590, #$2580, | |
| 86 | + | #$0440, #$0441, #$0442, #$0443, #$0444, #$0445, #$0446, #$0447, | |
| 87 | + | #$0448, #$0449, #$044A, #$044B, #$044C, #$044D, #$044E, #$044F, | |
| 88 | + | #$0401, #$0451, #$0404, #$0454, #$0407, #$0457, #$040E, #$045E, | |
| 89 | + | #$00B0, #$2219, #$00B7, #$221A, #$2116, #$00A4, #$25A0, #$00A0); | |
| 90 | + | ||
| 91 | + | function cp866Decode(Context: Pointer; InBuf: PChar; var InCnt: Cardinal; OutBuf: PWideChar; | |
| 92 | + | var OutCnt: Cardinal): Integer; stdcall; | |
| 93 | + | var | |
| 94 | + | I: Integer; | |
| 95 | + | cnt: Cardinal; | |
| 96 | + | begin | |
| 97 | + | cnt := OutCnt; // число widechars | |
| 98 | + | if cnt > InCnt then | |
| 99 | + | cnt := InCnt; | |
| 100 | + | for I := 0 to cnt-1 do | |
| 101 | + | begin | |
| 102 | + | if InBuf[I] < #128 then | |
| 103 | + | OutBuf[I] := WideChar(ord(InBuf[I])) | |
| 104 | + | else | |
| 105 | + | OutBuf[I] := cp866table[InBuf[I]]; | |
| 106 | + | end; | |
| 107 | + | Dec(InCnt, cnt); | |
| 108 | + | Dec(OutCnt, cnt); | |
| 109 | + | Result := cnt; | |
| 110 | + | end; | |
| 111 | + | ||
| 112 | + | function GetCP866Decoder(const AEncoding: string; out Decoder: TDecoder): Boolean; stdcall; | |
| 113 | + | begin | |
| 114 | + | // Большинство кодировок имеет один или несколько 'псевдонимов'. | |
| 115 | + | if SameText(AEncoding, 'IBM866') or | |
| 116 | + | SameText(AEncoding, 'cp866') or | |
| 117 | + | SameText(AEncoding, '866') or | |
| 118 | + | SameText(AEncoding, 'csIBM866') then | |
| 119 | + | begin | |
| 120 | + | Decoder.Decode := @cp866Decode; | |
| 121 | + | Decoder.Cleanup := nil; | |
| 122 | + | Decoder.Context := nil; | |
| 123 | + | Result := True; | |
| 124 | + | end | |
| 125 | + | else | |
| 126 | + | Result := False; | |
| 127 | + | end; | |
| 128 | + | ||
| 129 | + | initialization | |
| 130 | + | RegisterDecoder(@GetCP866Decoder); | |
| 131 | + | end. | |
| 132 | + | ``` | |
| 133 | + | ||
| 134 | + | --- | |
| 135 | + | *From the Free Pascal wiki, page "XML Decoders/ru", as it stood on 2020-03-03 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* |
