| 1 | Unit IPFFileFormatUnit;
|
|---|
| 2 |
|
|---|
| 3 | // NewView - a new OS/2 Help Viewer
|
|---|
| 4 | // Copyright 2003 Aaron Lawrence (aaronl at consultant dot com)
|
|---|
| 5 | // This software is released under the Gnu Public License - see readme.txt
|
|---|
| 6 |
|
|---|
| 7 | Interface
|
|---|
| 8 |
|
|---|
| 9 | // Definition of IPF file header and other structures
|
|---|
| 10 |
|
|---|
| 11 | uses
|
|---|
| 12 | SysUtils;
|
|---|
| 13 |
|
|---|
| 14 | type
|
|---|
| 15 | uint32 = longword;
|
|---|
| 16 | uint16 = word;
|
|---|
| 17 | uint8 = byte;
|
|---|
| 18 | pUInt16 = ^ uint16;
|
|---|
| 19 | pUInt32 = ^ uint32;
|
|---|
| 20 | pUInt8 = ^ uint8;
|
|---|
| 21 |
|
|---|
| 22 | PCharArray = array[ 0..0 ] of PCHar;
|
|---|
| 23 | UInt32Array = array[ 0..0 ] of UInt32;
|
|---|
| 24 | UInt16Array = array[ 0..0 ] of UInt16;
|
|---|
| 25 | UInt8Array = array[ 0..0 ] of UInt8;
|
|---|
| 26 |
|
|---|
| 27 | PCharArrayPointer = ^ PCharArray;
|
|---|
| 28 | UInt32ArrayPointer = ^ UInt32Array;
|
|---|
| 29 | UInt16ArrayPointer = ^ UInt16Array;
|
|---|
| 30 | UInt8ArrayPointer = ^ UInt8Array;
|
|---|
| 31 |
|
|---|
| 32 | TBooleanArray = array[ 0..0 ] of boolean;
|
|---|
| 33 | BooleanArrayPointer = ^TBooleanArray;
|
|---|
| 34 |
|
|---|
| 35 | EHelpFileException = class( Exception )
|
|---|
| 36 | end;
|
|---|
| 37 |
|
|---|
| 38 | EWindowsHelpFormatException = class( Exception )
|
|---|
| 39 | end;
|
|---|
| 40 |
|
|---|
| 41 | var
|
|---|
| 42 | ErrorCorruptHelpFile: string;
|
|---|
| 43 |
|
|---|
| 44 | const
|
|---|
| 45 | INF_HEADER_ID = $5348;
|
|---|
| 46 |
|
|---|
| 47 | Type
|
|---|
| 48 | THelpFileHeader = record
|
|---|
| 49 | ID: uint16; // ID magic word (5348h = "HS")
|
|---|
| 50 | unknown1: uint8; // unknown purpose, could be third letter of ID
|
|---|
| 51 | flags: uint8; // probably a flag word...
|
|---|
| 52 | // bit 0: set if INF style file
|
|---|
| 53 | // bit 4: set if HLP style file
|
|---|
| 54 | // patching this byte allows reading HLP files
|
|---|
| 55 | // using the VIEW command, while help files
|
|---|
| 56 | // seem to work with INF settings here as well.
|
|---|
| 57 | hdrsize: uint16; // total size of header
|
|---|
| 58 | unknown2: uint16; // unknown purpose
|
|---|
| 59 |
|
|---|
| 60 | ntoc: uint16; // number of entries in the tocarray
|
|---|
| 61 | tocstart: uint32; // file offset of the start of the toc
|
|---|
| 62 | toclen: uint32; // number of bytes in file occupied by the toc
|
|---|
| 63 | tocoffsetsstart: uint32; // file offset of the start of array of toc offsets
|
|---|
| 64 | nres: uint16; // number of panels with ressource numbers
|
|---|
| 65 | resstart: uint32; // 32 bit file offset of ressource number table
|
|---|
| 66 | nname: uint16; // number of panels with textual name
|
|---|
| 67 | namestart: uint32; // 32 bit file offset to panel name table
|
|---|
| 68 | nindex: uint16; // number of index entries
|
|---|
| 69 | indexstart: uint32; // 32 bit file offset to index table
|
|---|
| 70 | indexlen: uint32; // size of index table
|
|---|
| 71 | unknown3: array[ 0..9 ] of uint8; // unknown purpose
|
|---|
| 72 | searchstart: uint32; // 31 bit file offset of full text search table
|
|---|
| 73 | // Note: top bit indicates 32 bit search record!
|
|---|
| 74 | searchlen: uint32; // size of full text search table
|
|---|
| 75 | nslots: uint16; // number of "slots"
|
|---|
| 76 | slotsstart: uint32; // file offset of the slots array
|
|---|
| 77 | dictlen: uint32; // number of bytes occupied by the "dictionary"
|
|---|
| 78 | ndict: uint16; // number of entries in the dictionary
|
|---|
| 79 | dictstart: uint32; // file offset of the start of the dictionary
|
|---|
| 80 | imgstart: uint32; // file offset of image data
|
|---|
| 81 | unknown4: uint8; // unknown purpose
|
|---|
| 82 | nlsstart: uint32; // 32 bit file offset of NLS table
|
|---|
| 83 | nlslen: uint32; // size of NLS table
|
|---|
| 84 | extstart: uint32; // 32 bit file offset of extended data block
|
|---|
| 85 | reserved: array[ 0..2 ] of uint32; // for future use. set to zero.
|
|---|
| 86 | title: array[ 0..47 ] of char; // ASCII title of database
|
|---|
| 87 | end;
|
|---|
| 88 | TPHelpFileHeader = ^ THelpFileHeader;
|
|---|
| 89 |
|
|---|
| 90 | TExtendedHelpFileHeader = record
|
|---|
| 91 | NumFontEntry: uint16; // FONT TABLE: Number entries
|
|---|
| 92 | FontTableOffset: uint32; // FONT TABLE: Offset in file
|
|---|
| 93 | NumDataBase: uint16; // DATA BASE: Number of files
|
|---|
| 94 | DataBaseOffset: uint32; // DATA BASE: Offset in file
|
|---|
| 95 | DataBaseSize: uint32; // DATA BASE: Size in bytes
|
|---|
| 96 | EntryInGNameTable: uint16; // GLOBAL NAMES: Number entries
|
|---|
| 97 | HelpPanelGNameTblOffset: uint32; // GLOBAL NAMES: Offset in file
|
|---|
| 98 | StringsOffset: uint32; // STRINGS : Offset in file
|
|---|
| 99 | StringsSize: uint16; // STRINGS : Total bytes of all strings
|
|---|
| 100 | ChildPagesOffset: uint32; // CHILDPAGES : Offset in file
|
|---|
| 101 | ChildPagesSize: uint32; // CHILDPAGES : Total bytes of all strings
|
|---|
| 102 | NumGIndexEntry: uint32; // Total number of Global Index items
|
|---|
| 103 | CtrlOffset: uint32; // CTRL BUTTONS : offset in file
|
|---|
| 104 | CtrlSize: uint32; // CTRL BUTTONS : size in bytes
|
|---|
| 105 | Reserved: array[0..3] of uint32; // For future use. Set to zero
|
|---|
| 106 | end;
|
|---|
| 107 | TPExtendedHelpFileHeader = ^ TExtendedHelpFileHeader;
|
|---|
| 108 |
|
|---|
| 109 | Type
|
|---|
| 110 | TTOCEntryStart = record
|
|---|
| 111 | length: uint8; // length of the entry including this byte
|
|---|
| 112 | flags: uint8; // flag byte, description folows (MSB first)
|
|---|
| 113 | // bit1 haschildren; // following nodes are a higher level
|
|---|
| 114 | // bit1 hidden; // this entry doesn't appear in VIEW.EXE's
|
|---|
| 115 | // presentation of the toc
|
|---|
| 116 | // bit1 extended; // extended entry format
|
|---|
| 117 | // bit1 stuff; // ??
|
|---|
| 118 | // int4 level; // nesting level
|
|---|
| 119 | numSlots: uint8; // number of "slots" occupied by the text for
|
|---|
| 120 | // this toc entry
|
|---|
| 121 | end;
|
|---|
| 122 | pTTOCEntryStart = ^TTOCEntryStart;
|
|---|
| 123 |
|
|---|
| 124 | TExtendedTOCEntry = record
|
|---|
| 125 | w1: uint8;
|
|---|
| 126 | w2: uint8;
|
|---|
| 127 | end;
|
|---|
| 128 | pExtendedTOCEntry = ^TExtendedTOCEntry;
|
|---|
| 129 |
|
|---|
| 130 | TTOCEntryOffsetArray = array[ 0..0 ] of uint32;
|
|---|
| 131 | pTTOCEntryOffsetArray = ^ TTOCEntryOffsetArray;
|
|---|
| 132 |
|
|---|
| 133 | Const
|
|---|
| 134 | TOCEntryExtended = 32;
|
|---|
| 135 | TOCEntryHidden = 64;
|
|---|
| 136 | TOCEntryHasChildren = 128;
|
|---|
| 137 |
|
|---|
| 138 | type
|
|---|
| 139 | THelpXYPair = record
|
|---|
| 140 | Flags: uint8;
|
|---|
| 141 | X: uint16;
|
|---|
| 142 | Y: uint16;
|
|---|
| 143 | end;
|
|---|
| 144 | pHelpXYPair = ^ THelpXYPair;
|
|---|
| 145 |
|
|---|
| 146 | TSlotHeader = record
|
|---|
| 147 | stuff: uint8; // always 0??
|
|---|
| 148 | localdictpos: uint32; // file offset of the local dictionary
|
|---|
| 149 | nlocaldict: uint8; // number of entries in the local dict
|
|---|
| 150 | ntext: uint16; // number of bytes in the text
|
|---|
| 151 | end;
|
|---|
| 152 | pSlotHeader = ^TSlotHeader;
|
|---|
| 153 |
|
|---|
| 154 | THelpFontSpec = record
|
|---|
| 155 | FaceName: array[ 0..32 ] of char;
|
|---|
| 156 | Height: uint16;
|
|---|
| 157 | Width: uint16;
|
|---|
| 158 | Codepage: uint16;
|
|---|
| 159 | end;
|
|---|
| 160 | pTHelpFontSpec = ^ THelpFontSpec;
|
|---|
| 161 |
|
|---|
| 162 | // List of IPF escape codes.
|
|---|
| 163 |
|
|---|
| 164 | const
|
|---|
| 165 | // Basic byte codes
|
|---|
| 166 | IPF_END_PARA = $fa;
|
|---|
| 167 | IPF_CENTER = $fb;
|
|---|
| 168 | IPF_INVERT_SPACING = $fc;
|
|---|
| 169 | IPF_LINEBREAK = $fd;
|
|---|
| 170 | IPF_SPACE = $fe;
|
|---|
| 171 | IPF_ESC = $ff; // followed by one of the ecXXX codes below
|
|---|
| 172 |
|
|---|
| 173 | // FF XX
|
|---|
| 174 | ecSetLeftMargin = $02;
|
|---|
| 175 | ecHighlight1 = $04; // hp1,2,3,5,6,7
|
|---|
| 176 | ecLinkStart = $05;
|
|---|
| 177 | ecFootnoteLinkStart = $07;
|
|---|
| 178 | ecLinkEnd = $08;
|
|---|
| 179 | ecStartCharGraphics = $0b;
|
|---|
| 180 | ecEndCharGraphics = $0c;
|
|---|
| 181 | ecHighlight2 = $0d; // hp4,8,9
|
|---|
| 182 | ecImage = $0e;
|
|---|
| 183 | ecLinkedImage = $0f;
|
|---|
| 184 | ecProgramLink = $10;
|
|---|
| 185 | ecSetLeftMarginNewLine = $11;
|
|---|
| 186 | ecSetLeftMarginFit = $12;
|
|---|
| 187 | ecForegroundColor = $13;
|
|---|
| 188 | ecBackgroundColor = $14;
|
|---|
| 189 | ecFontChange = $19;
|
|---|
| 190 | ecStartLines = $1a;
|
|---|
| 191 | ecEndLines = $1b;
|
|---|
| 192 | ecSetLeftMarginHere = $1c;
|
|---|
| 193 | ecStartLinkByResourceID = $1d;
|
|---|
| 194 | ecExternalLink = $1f;
|
|---|
| 195 |
|
|---|
| 196 | // Subescape codes of
|
|---|
| 197 | HPART_DEFINE = 0;
|
|---|
| 198 | HPART_PT_HDREF = 1;
|
|---|
| 199 | HPART_PT_FNREF = 2;
|
|---|
| 200 | HPART_PT_SPREF = 3;
|
|---|
| 201 | HPART_HDREF = 4;
|
|---|
| 202 | HPART_FNREF = 5;
|
|---|
| 203 | HPART_SPREF = 6;
|
|---|
| 204 | HPART_LAUNCH = 7;
|
|---|
| 205 | HPART_PT_LAUNCH = 8;
|
|---|
| 206 | HPART_INFORM = 9;
|
|---|
| 207 | HPART_PT_INFORM = 10;
|
|---|
| 208 | // ?? 11 ??
|
|---|
| 209 | HPART_EXTERN_PT_HDREF = 12;
|
|---|
| 210 | HPART_EXTERN_PT_SPREF = 13;
|
|---|
| 211 | HPART_EXTERN_HDREF = 14;
|
|---|
| 212 | HPART_EXTERN_SPREF = 15;
|
|---|
| 213 | HPART_GLOBAL_HDREF = 16;
|
|---|
| 214 | HPART_GLOBAL_PT_HDREF = 17;
|
|---|
| 215 |
|
|---|
| 216 | // -----------------------------------------------------------
|
|---|
| 217 | // Operations on Int32 arrays, used for searching
|
|---|
| 218 | // These could be optimised heavily if needed.
|
|---|
| 219 | procedure AllocUInt32Array( Var pArray: UInt32ArrayPointer;
|
|---|
| 220 | Size: longint );
|
|---|
| 221 | procedure FreeUInt32Array( Var pArray: UInt32ArrayPointer;
|
|---|
| 222 | Size: longint );
|
|---|
| 223 |
|
|---|
| 224 | procedure FillUInt32Array( pArray: UInt32ArrayPointer;
|
|---|
| 225 | Size: longint;
|
|---|
| 226 | Value: UInt32 );
|
|---|
| 227 |
|
|---|
| 228 | procedure AddUInt32Array( pSource: UInt32ArrayPointer;
|
|---|
| 229 | pDest: UInt32ArrayPointer;
|
|---|
| 230 | Size: longint );
|
|---|
| 231 |
|
|---|
| 232 | // Dest = Dest + source * Multiplier
|
|---|
| 233 | procedure AddMultConstUInt32Array( pSource: UInt32ArrayPointer;
|
|---|
| 234 | Multiplier: longint;
|
|---|
| 235 | pDest: UInt32ArrayPointer;
|
|---|
| 236 | Size: longint );
|
|---|
| 237 |
|
|---|
| 238 | procedure AndUInt32Array( pSource: UInt32ArrayPointer;
|
|---|
| 239 | pDest: UInt32ArrayPointer;
|
|---|
| 240 | Size: longint );
|
|---|
| 241 |
|
|---|
| 242 | // If both source and dest > 0 then
|
|---|
| 243 | // add source to dest
|
|---|
| 244 | procedure AndAddUInt32Array( pSource: UInt32ArrayPointer;
|
|---|
| 245 | pDest: UInt32ArrayPointer;
|
|---|
| 246 | Size: longint );
|
|---|
| 247 |
|
|---|
| 248 | // if Source > 0 then dest is set to 0
|
|---|
| 249 | procedure AndNotUInt32Array( pSource: UInt32ArrayPointer;
|
|---|
| 250 | pDest: UInt32ArrayPointer;
|
|---|
| 251 | Size: longint );
|
|---|
| 252 |
|
|---|
| 253 | // dest = dest or source;
|
|---|
| 254 | // if source > 0 then set dest to > 0
|
|---|
| 255 | procedure OrUInt32Array( pSource: UInt32ArrayPointer;
|
|---|
| 256 | pDest: UInt32ArrayPointer;
|
|---|
| 257 | Size: longint );
|
|---|
| 258 |
|
|---|
| 259 | // if source = 0 then dest set to >0
|
|---|
| 260 | procedure NotOrUInt32Array( pSource: UInt32ArrayPointer;
|
|---|
| 261 | pDest: UInt32ArrayPointer;
|
|---|
| 262 | Size: longint );
|
|---|
| 263 |
|
|---|
| 264 | procedure CopyUInt32Array( pSource: UInt32ArrayPointer;
|
|---|
| 265 | pDest: UInt32ArrayPointer;
|
|---|
| 266 | Size: longint );
|
|---|
| 267 |
|
|---|
| 268 | procedure ClearUInt32Array( pArray: UInt32ArrayPointer;
|
|---|
| 269 | Size: longint );
|
|---|
| 270 | procedure SetUInt32Array( pArray: UInt32ArrayPointer;
|
|---|
| 271 | Size: longint );
|
|---|
| 272 |
|
|---|
| 273 | // returns the result of ORing every array element.
|
|---|
| 274 | // Can be useful for debugging e.g. seeing at a glance
|
|---|
| 275 | // if any element is non-zero
|
|---|
| 276 | function OrAllUInt32Array( pArray: UInt32ArrayPointer;
|
|---|
| 277 | Size: longint ): longint;
|
|---|
| 278 |
|
|---|
| 279 |
|
|---|
| 280 | Implementation
|
|---|
| 281 |
|
|---|
| 282 | uses
|
|---|
| 283 | ACLUtility;
|
|---|
| 284 |
|
|---|
| 285 | // Operations on int32 arrays
|
|---|
| 286 | // -----------------------------------------------------------
|
|---|
| 287 |
|
|---|
| 288 | procedure AllocUInt32Array( Var pArray: UInt32ArrayPointer;
|
|---|
| 289 | Size: longint );
|
|---|
| 290 | begin
|
|---|
| 291 | GetMem( pArray,
|
|---|
| 292 | Size
|
|---|
| 293 | * sizeof( UInt32 ) );
|
|---|
| 294 | end;
|
|---|
| 295 |
|
|---|
| 296 | procedure FreeUInt32Array( Var pArray: UInt32ArrayPointer;
|
|---|
| 297 | Size: longint );
|
|---|
| 298 | begin
|
|---|
| 299 | FreeMem( pArray,
|
|---|
| 300 | Size
|
|---|
| 301 | * sizeof( UInt32 ) );
|
|---|
| 302 | end;
|
|---|
| 303 |
|
|---|
| 304 | // This is a nice fast implementation of filling an
|
|---|
| 305 | // array of dwords (Int32/longword)
|
|---|
| 306 | procedure FillUInt32Array( pArray: UInt32ArrayPointer;
|
|---|
| 307 | Size: longint;
|
|---|
| 308 | Value: UInt32 );
|
|---|
| 309 | begin
|
|---|
| 310 | assert( Size >= 0 );
|
|---|
| 311 | Asm
|
|---|
| 312 | Mov EAX, Value
|
|---|
| 313 | Mov EDI, pArray
|
|---|
| 314 | Mov ECX, Size
|
|---|
| 315 | CLD // direction = up
|
|---|
| 316 | REP STOSD // store double word, until ECX = 0
|
|---|
| 317 | End;
|
|---|
| 318 | end;
|
|---|
| 319 |
|
|---|
| 320 | procedure ClearUInt32Array( pArray: UInt32ArrayPointer;
|
|---|
| 321 | Size: longint );
|
|---|
| 322 | begin
|
|---|
| 323 | FillUInt32Array( pArray, Size, 0 );
|
|---|
| 324 | end;
|
|---|
| 325 |
|
|---|
| 326 | procedure SetUInt32Array( pArray: UInt32ArrayPointer;
|
|---|
| 327 | Size: longint );
|
|---|
| 328 | begin
|
|---|
| 329 | FillUInt32Array( pArray, Size, $ffffffff );
|
|---|
| 330 | end;
|
|---|
| 331 |
|
|---|
| 332 | procedure AddUInt32Array( pSource: UInt32ArrayPointer;
|
|---|
| 333 | pDest: UInt32ArrayPointer;
|
|---|
| 334 | Size: longint );
|
|---|
| 335 | var
|
|---|
| 336 | i: longint;
|
|---|
| 337 | begin
|
|---|
| 338 | for i := 0 to Size - 1 do
|
|---|
| 339 | inc( pDest^[ i ], pSource^[ i ] );
|
|---|
| 340 | end;
|
|---|
| 341 |
|
|---|
| 342 | procedure AddMultConstUInt32Array( pSource: UInt32ArrayPointer;
|
|---|
| 343 | Multiplier: longint;
|
|---|
| 344 | pDest: UInt32ArrayPointer;
|
|---|
| 345 | Size: longint );
|
|---|
| 346 | var
|
|---|
| 347 | i: longint;
|
|---|
| 348 | begin
|
|---|
| 349 | for i := 0 to Size - 1 do
|
|---|
| 350 | inc( pDest^[ i ], pSource^[ i ] * Multiplier );
|
|---|
| 351 | end;
|
|---|
| 352 |
|
|---|
| 353 | procedure OrUInt32Array( pSource: UInt32ArrayPointer;
|
|---|
| 354 | pDest: UInt32ArrayPointer;
|
|---|
| 355 | Size: longint );
|
|---|
| 356 | var
|
|---|
| 357 | i: longint;
|
|---|
| 358 | begin
|
|---|
| 359 | for i := 0 to Size - 1 do
|
|---|
| 360 | pDest^[ i ] := pDest^[ i ] or pSource^[ i ];
|
|---|
| 361 | end;
|
|---|
| 362 |
|
|---|
| 363 | procedure CopyUInt32Array( pSource: UInt32ArrayPointer;
|
|---|
| 364 | pDest: UInt32ArrayPointer;
|
|---|
| 365 | Size: longint );
|
|---|
| 366 | begin
|
|---|
| 367 | MemCopy( pSource, pDest, Size * sizeof( uint32 ) );
|
|---|
| 368 | end;
|
|---|
| 369 |
|
|---|
| 370 | procedure NotOrUInt32Array( pSource: UInt32ArrayPointer;
|
|---|
| 371 | pDest: UInt32ArrayPointer;
|
|---|
| 372 | Size: longint );
|
|---|
| 373 | var
|
|---|
| 374 | i: longint;
|
|---|
| 375 | begin
|
|---|
| 376 | for i := 0 to Size - 1 do
|
|---|
| 377 | if pSource^[ i ] = 0 then
|
|---|
| 378 | pDest^[ i ] := 1;
|
|---|
| 379 | end;
|
|---|
| 380 |
|
|---|
| 381 | procedure AndUInt32Array( pSource: UInt32ArrayPointer;
|
|---|
| 382 | pDest: UInt32ArrayPointer;
|
|---|
| 383 | Size: longint );
|
|---|
| 384 | var
|
|---|
| 385 | i: longint;
|
|---|
| 386 | begin
|
|---|
| 387 | for i := 0 to Size - 1 do
|
|---|
| 388 | pDest^[ i ] := pDest^[ i ] and pSource^[ i ];
|
|---|
| 389 | end;
|
|---|
| 390 |
|
|---|
| 391 | procedure AndAddUInt32Array( pSource: UInt32ArrayPointer;
|
|---|
| 392 | pDest: UInt32ArrayPointer;
|
|---|
| 393 | Size: longint );
|
|---|
| 394 | var
|
|---|
| 395 | i: longint;
|
|---|
| 396 | begin
|
|---|
| 397 | for i := 0 to Size - 1 do
|
|---|
| 398 | if ( pSource^[ i ] > 0 )
|
|---|
| 399 | and ( pDest^[ i ] > 0 ) then
|
|---|
| 400 | inc( pDest^[ i ], pSource^[ i ] )
|
|---|
| 401 | else
|
|---|
| 402 | pDest^[ i ] := 0;
|
|---|
| 403 | end;
|
|---|
| 404 |
|
|---|
| 405 | procedure AndNotUInt32Array( pSource: UInt32ArrayPointer;
|
|---|
| 406 | pDest: UInt32ArrayPointer;
|
|---|
| 407 | Size: longint );
|
|---|
| 408 | var
|
|---|
| 409 | i: longint;
|
|---|
| 410 | begin
|
|---|
| 411 | for i := 0 to Size - 1 do
|
|---|
| 412 | if pSource^[ i ] > 0 then
|
|---|
| 413 | pDest^[ i ] := 0;
|
|---|
| 414 | end;
|
|---|
| 415 |
|
|---|
| 416 | function OrAllUInt32Array( pArray: UInt32ArrayPointer;
|
|---|
| 417 | Size: longint ): longint;
|
|---|
| 418 | var
|
|---|
| 419 | i: longint;
|
|---|
| 420 | begin
|
|---|
| 421 | Result := 0;
|
|---|
| 422 | for i := 0 to Size - 1 do
|
|---|
| 423 | Result := Result or pArray^[ i ];
|
|---|
| 424 | end;
|
|---|
| 425 |
|
|---|
| 426 | Initialization
|
|---|
| 427 | End.
|
|---|