source: trunk/NewView/HelpFile.pas@ 342

Last change on this file since 342 was 342, checked in by RBRi, 16 years ago

index is a real object now
support for env variables to make glossary simulation work

  • Property svn:eol-style set to native
File size: 29.9 KB
RevLine 
[18]1Unit HelpFile;
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
7Interface
8
9// Encapsulates the basic reading of a help file's structure.
10
11uses
[32]12 Classes,
13 BseDos,
14 Os2Def,
15 SysUtils,
16 Graphics,
17 IPFFileFormatUnit,
18 HelpTopic,
19 HelpBitmap,
20 ACLUtility,
21 SearchTable;
[18]22
23type
[342]24
25 TIndexEntry = class
26 private
27 name: String;
28 topic: TTopic;
29 flags: uint8;
30 public
31 CONSTRUCTOR Create(aName: String; aTopic: TTopic; aFlags: uint8);
32 DESTRUCTOR Destroy; override;
33
34 PROPERTY getTopic: TTopic read topic;
35 FUNCTION getLabel: String;
36 FUNCTION isGlobal: boolean;
37 FUNCTION getLevel: integer;
38 end;
39
40
41 TIndex = class
42 private
43 entries: TStringList;
44 // labels: TStringList;
45 public
46 CONSTRUCTOR Create;
47 DESTRUCTOR Destroy; override;
48
49 FUNCTION Count: longint;
50 FUNCTION GetLabels: TStringList;
51 FUNCTION GetTopic(aPos: longint): TTopic;
52 PROCEDURE Add(anIndexEntry: TIndexEntry);
53 end;
54
55
[18]56 THelpFile = class
57 protected
58 _Filename : string;
59 _FileSize : longint;
60 _Handle: HFILE;
61
62 _pSlotData: pUInt16;
63 _SlotDataSize: longint;
64
65 _Title: string;
66
67 _Topics: TList; // of TTopics
68
69 _Dictionary: TList; // pointers to strings.
70
[342]71 _Index: TIndex;
[18]72
73 _SearchTable: TSearchTable;
74
75 _ReferencedFiles: TStringList;
76
77 _FontTable: TList;
78
79 _pHeader: TPHelpFileHeader;
80 _pExtendedHeader: TPExtendedHelpFileHeader;
81 _pContentsData: pointer;
82 _pResourceData: pointer;
83 _pSearchData: pointer;
84 _pHighlightWords: UInt32ArrayPointer;
85 _pSlotOffsets: Uint32ArrayPointer;
86 _pDictionaryData: pointer;
87 _pFontTableData: pointer;
88 _pTopicNameData: pointer;
89 _pTopicGlobalNamesData: pointer;
90
91 procedure InitMembers;
92 procedure Open;
93 procedure Close;
94 procedure ReadFileBlock( Var Dest: pointer;
95 const StartPosition: ULONG;
96 const Length: ULONG );
97
98 procedure ReadHeader;
99 procedure ReadContents;
100 procedure ReadDictionary;
101 procedure ReadSearchTable;
102
103 procedure ReadIndex;
104
105 procedure ReadReferencedFilesTable;
106 procedure ReadFontTableData;
107 procedure ParseFontTable;
108
109 function GetTopic( Index: longint ): TTopic;
110 function GetTopicCount: longint;
111
112 function GetDictionaryCount: longint;
113 function GetDictionaryWord( Index: longint ): string;
114 function GetDictionaryWordPtr( Index: longint ): pstring;
115
116 function GetHighlightWords: UInt32ArrayPointer;
117
118 function GetSearchTable: TSearchTable;
119
120 // Lookup global or local panel name list
121 function FindTopicByName( const Name: string;
122 Var pData: pointer;
123 Count: longint;
124 Offset: longint ): TTopic;
125
126 public
[342]127 constructor Create( const aFileName: string );
[18]128
129 destructor Destroy; override;
130
[342]131 function GetIndex: TIndex;
[18]132
133 property Title: string read _Title;
134 property Topics[ Index: longint ]: TTopic read GetTopic;
135 property TopicList: TList read _Topics;
136 property TopicCount: longint read GetTopicCount;
[342]137 property Index: TIndex read GetIndex;
[18]138 property Filename: string read _FileName;
139
140 property ReferencedFiles: TStringList read _ReferencedFiles;
141
142 procedure GetImages( ImageOffsets: TList;
143 Images: TImageList );
144
145 function GetImage( ImageOffset: longint ): THelpBitmap;
146
147 property DictionaryCount: longint read GetDictionaryCount;
148 property DictionaryWords[ Index: longint ]: string read GetDictionaryWord;
149 property DictionaryWordPtrs[ Index: longint ]: pstring read GetDictionaryWordPtr;
150
151 function IndexOfTopic( Topic: TTopic ): longint;
152
153 property SearchTable: TSearchTable read GetSearchTable;
154
155 function FindTopicByResourceID( ID: uint16 ): TTopic;
156
157 function FindTopicByLocalName( const Name: string ): TTopic;
158 function FindTopicByGlobalName( const Name: string ): TTopic;
159
160 function FindTopicByTitleStartsWith( const SearchText: string ): TTopic;
161 function FindTopicByTitleContains( const SearchText: string ): TTopic;
162
163 function FindTopicByIndexStartsWith( const SearchText: string ): TTopic;
164 function FindTopicByIndexContains( const SearchText: string ): TTopic;
165
166 procedure FindResourceIDsForTopic( Topic: TTopic;
167 ResourceIDs: TList );
168
169 property HighlightWords: UInt32ArrayPointer read GetHighlightWords;
170
171 property FileSize: longint read _FileSize;
172
173 procedure SetupFontSubstitutes( Substitutions: string );
174 public
175 NotesLoaded: boolean; // used externally
176
177 end;
178
179// Returns helpfile that the given topic is within
180Function TopicFile( Topic: TTopic ): THelpFile;
181
182function GetHelpFileTitle( const Filename: string ): string;
183
184Implementation
185
186uses
187 BseErr,
[124]188 StringUtilsUnit,
[140]189 CharUtilsUnit,
[43]190 DebugUnit,
[32]191 ACLFileIOUtility,
192 ACLLanguageUnit;
[18]193
194// Load "missing" bitmap
195{$R Images}
196
197var
198 FileErrorNotFound: string;
199 FileErrorAccessDenied: string;
200 FileErrorInUse: string;
201 FileErrorInvalidHeader: string;
202
[342]203
204 // -----------
205 // TIndexEntry
206 // -----------
207
208 CONSTRUCTOR TIndexEntry.Create(aName: String; aTopic: TTopic; aFlags: uint8);
209 begin
210 LogEvent(LogObjConstDest, 'TIndexEntry.Create');
211 name := aName;
212 topic := aTopic;
213 flags := aFlags;
214 end;
215
216
217 DESTRUCTOR TIndexEntry.Destroy;
218 begin
219 LogEvent(LogObjConstDest, 'TIndexEntry.Destroy');
220 topic := nil;
221 // inherited Destroy;
222 end;
223
224
225 FUNCTION TIndexEntry.getLabel: String;
226 begin
227 result := name;
228
229 // index level check (level 1 or 2)
230 if (getLevel) > 1 then
231 begin
232 result := '- ' + result;
233 end;
234
235 if isGlobal then
236 begin
237 result := result + ' (g)';
238 end;
239 end;
240
241
242 FUNCTION TIndexEntry.isGlobal: boolean;
243 begin
244 result := (flags and 64) > 0
245 end;
246
247
248 FUNCTION TIndexEntry.getLevel: integer;
249 begin
250 result := 1;
251
252 // index level check (level 1 or 2)
253 if (flags and 2 ) > 0 then
254 begin
255 result := 2;
256 end;
257 end;
258
259
260
261
262 // -----------
263 // TIndex
264 // -----------
265 CONSTRUCTOR TIndex.Create;
266 begin
267 inherited Create;
268
269 entries := TStringList.Create;
270 // labels := nil; // lazy
271 end;
272
273
274 DESTRUCTOR TIndex.Destroy;
275 var
276 i : longint;
277 tmpEntry : TIndexEntry;
278 begin
279 LogEvent(LogObjConstDest, 'TIndex.Destroy (size:' + IntToStr(entries.Count) + ')');
280
281 for i := 0 to entries.Count - 1 do
282 begin
283 tmpEntry := TIndexEntry(entries.Objects[i]);
284 if tmpEntry <> nil then
285 begin
286 tmpEntry.Destroy;
287 entries.Objects[i] := nil;
288 end;
289 end;
290 entries.Destroy;
291
292 inherited Destroy;
293 end;
294
295
296 FUNCTION TIndex.Count: longint;
297 begin
298 result := entries.Count;
299 end;
300
301
302 FUNCTION TIndex.GetLabels: TStringList;
303 begin
304 result := entries;
305 end;
306
307
308 FUNCTION TIndex.GetTopic(aPos: longint): TTopic;
309 begin
310 result := TTopic(entries.Objects[aPos]);
311 end;
312
313
314 PROCEDURE TIndex.add(anIndexEntry: TIndexEntry);
315 begin
316 // LogEvent(LogDebug, 'TIndex.add(' + aName + ', ' + anIndexEntry.ClassName);
317 entries.AddObject(anIndexEntry.getLabel, anIndexEntry);
318 end;
319
320
321
322
[18]323Procedure OnLanguageEvent( Language: TLanguageFile;
324 const Apply: boolean );
[228]325var
326 tmpPrefix : String;
[18]327begin
[228]328 tmpPrefix := 'HelpFile' + LANGUAGE_LABEL_DELIMITER;
[18]329
[228]330 Language.LL( Apply, FileErrorNotFound, tmpPrefix + 'FileErrorNotFound', 'File not found' );
331 Language.LL( Apply, FileErrorAccessDenied, tmpPrefix + 'FileErrorAccessDenied', 'Access denied' );
332 Language.LL( Apply, FileErrorInUse, tmpPrefix + 'FileErrorInUse', 'File in use by another program' );
[18]333 Language.LL( Apply,
334 FileErrorInvalidHeader,
[228]335 tmpPrefix + 'FileErrorInvalidHeader',
[18]336 'File doesn''t appear to be an OS/2 Help document (header ID not correct)' );
337 Language.LL( Apply,
338 ErrorCorruptHelpFile,
[228]339 tmpPrefix + 'ErrorCorruptHelpFile',
[18]340 'File is corrupt' );
341end;
342
343Function TopicFile( Topic: TTopic ): THelpFile;
344Begin
345 Result := Topic.HelpFile as THelpFile;
346end;
347
348procedure THelpFile.InitMembers;
349begin
350 _SlotDataSize := 0;
351
352 _pHeader := nil;
353 _pExtendedHeader := nil;
354 _pContentsData := nil;
355 _pSlotOffsets := nil;
356 _pResourceData := nil;
357 _pSearchData := nil;
358 _pDictionaryData := nil;
359// _pIndexData := nil;
360 _pFontTableData := nil;
361
362 _pHighlightWords := nil;
363
364 _Dictionary := TList.Create;
365 _Topics := TList.Create;
366// _Index := TStringList.Create;
367 _ReferencedFiles := TStringList.Create;
368 _FontTable := TList.Create;
369
370 NotesLoaded := false;
371end;
372
[342]373
374constructor THelpFile.Create(const aFileName: string);
[18]375begin
[342]376 LogEvent(LogObjConstDest, 'THelpFile.Create (file:' + aFileName + ')');
377 LogEvent(LogParse, 'Helpfile Load: ' + aFileName);
[18]378
[342]379 _FileName := aFileName;
[18]380
381 InitMembers;
382
383 Open;
384
385 // we always need these basics:
386 try
387 ReadHeader;
388 ReadContents;
389 // ReadIndex;
390 ReadDictionary;
391 ReadFontTableData;
392 ParseFontTable;
393 ReadReferencedFilesTable;
394 except
395 Close;
396 raise;
397 end;
398
399 // the rest is loaded on demand
400end;
401
[342]402
[18]403destructor THelpFile.Destroy;
404begin
[342]405 LogEvent(LogObjConstDest, 'THelpFile.Destroy');
[18]406 DeallocateMemory( _pHeader );
407 DeallocateMemory( _pExtendedHeader );
408 DeallocateMemory( _pContentsData );
409 DeallocateMemory( _pSlotOffsets );
410 DeallocateMemory( _pResourceData );
411 DeallocateMemory( _pSearchData );
412 DeallocateMemory( _pDictionaryData );
413// DeallocateMemory( _pIndexData );
414 DeallocateMemory( _pFontTableData );
415
416 DeallocateMemory( _pHighlightWords );
417
[342]418 // index entries are pointing to topics
419 // so let us clean them first
420 if Assigned( _Index ) then
421 begin
422 _Index.Destroy;
423 end;
424
[18]425 if Assigned( _Topics ) then
[342]426 begin
[18]427 DestroyListAndObjects( _Topics );
[342]428 end;
[18]429
430 _Dictionary.Free;
431 _SearchTable.Free;
432 _ReferencedFiles.Free;
433 _FontTable.Free;
434
435 DosClose( _Handle );
436end;
437
438procedure THelpFile.Open;
439var
440 OpenAction: ULong;
441 rc: APIRET;
442 szName: Cstring;
443 FileInfo: FILESTATUS3;
444begin
445 if not FileExists( _Filename ) then
446 raise EHelpFileException.Create( FileErrorNotFound );
447
448 szName := _FileName;
449 rc := DosOpen( szName,
450 _Handle,
451 OpenAction,
452 0, // file size - irrelevant, not creating,
453 0, // attributes - ''
454 OPEN_ACTION_OPEN_IF_EXISTS,
455 OPEN_SHARE_DENYNONE + OPEN_ACCESS_READONLY,
456 nil ); // no extended attributes
457 if rc<> 0 then
458 begin
459 case rc of
460 ERROR_FILE_NOT_FOUND: // crap, this doesn't actually occur!
461 raise EHelpFileException.Create( FileErrorNotFound );
462
463 ERROR_ACCESS_DENIED:
464 raise EHelpFileException.Create( FileErrorAccessDenied );
465
466 ERROR_SHARING_VIOLATION:
467 raise EHelpFileException.Create( FileErrorInUse );
468
469 else
470 raise EHelpFileException.Create( SysErrorMessage( rc ) );
471 end;
472 end;
473
474 DosQueryFileInfo( _Handle,
475 FIL_STANDARD,
476 FileInfo,
477 sizeof( FileInfo ) );
478 _FileSize := FileInfo.cbFile; // file size
479end;
480
481procedure THelpFile.Close;
482begin
483 if _Handle <> 0 then
484 DosClose( _Handle );
485 _Handle := 0;
486end;
487
488procedure THelpFile.ReadFileBlock( Var Dest: pointer;
489 const StartPosition: ULONG;
490 const Length: ULONG );
491begin
492 if not ACLFileIOUtility.ReadFileBlock( _Handle,
493 Dest,
494 StartPosition,
495 Length ) then
496 raise EHelpFileException.Create( ErrorCorruptHelpFile );
497end;
498
499// -------------------------------------------------------------------------
500
501procedure THelpFile.ReadHeader;
502begin
[43]503 LogEvent(LogParse, 'Read header');
[18]504
505 ReadFileBlock( _pHeader,
506 0,
507 sizeof( _pHeader^ ) );
508
509 if _pHeader^.ID <> INF_HEADER_ID then
510 begin
511 // not an OS/2 help file.
512 if _pHeader^.ID = $5f3f then
513 raise EWindowsHelpFormatException.Create( 'Win16' );
514
515 raise EHelpFileException.Create( FileErrorInvalidHeader );
516 end;
517
518 _Title := StrPas( _pHeader^.Title );
519
520 if _pHeader^.extstart > 0 then
521 begin
522 // read extended header
523 ReadFileBlock( _pExtendedHeader,
524 _pHeader^.extstart,
525 sizeof( _pExtendedHeader^ ) );
526 end;
527end;
528
529procedure THelpFile.ReadContents;
530var
531 Topic: TTopic;
532 EntryIndex: longint;
533 pEntry: pTTOCEntryStart;
534 pEnd: pTTOCEntryStart;
535begin
[43]536 LogEvent(LogParse, 'Read contents');
[18]537
538 if _pHeader^.ntoc = 0 then
539 exit; // explicit check required since ntoc is unsigned
540
541 // Presize the topics list to save reallocation time
542 _Topics.Capacity := _pHeader^.ntoc;
543
544 // read slots first so that Topics can refer to it.
545 ReadFileBlock( _pSlotOffsets,
546 _pHeader^.slotsstart,
547 _pHeader^.nslots * sizeof( uint32 ) );
548
549 ReadFileBlock( _pContentsData,
550 _pHeader^.tocstart,
551 _pHeader^.toclen );
552
553 pEntry := _pContentsData;
554 pEnd := _pContentsData + _pHeader^.toclen;
555
556 for EntryIndex := 0 to _pHeader^.ntoc - 1 do
557 begin
558 if pEntry >= pEnd then
559 // runs off end of data!
560 raise EHelpFileException.Create( ErrorCorruptHelpFile );
561
562 Topic := TTopic.Create( _Handle,
563 _pSlotOffsets,
564 _Dictionary,
565 pEntry,
566 _FontTable,
567 _ReferencedFiles );
568
569 Topic.HelpFile := Self;
570 Topic.Index := EntryIndex;
571
572 _Topics.Add( Topic );
573
574 inc( pEntry, pEntry ^. Length );
575 end;
576end;
577
578procedure THelpFile.ReadDictionary;
579var
580 i: longint;
581 Len: uint8;
582 p: pbyte;
583 pEnd: pbyte;
584begin
[43]585 LogEvent(LogParse, 'Read dictionary');
[18]586
587 if _pHeader^.ndict = 0 then
588 exit; // explicit check required since ndict is unsigned
589
590 ReadFileBlock( _pDictionaryData,
591 _pHeader^.dictstart,
592 _pHeader^.dictlen );
593
594 P := _pDictionaryData;
595 pEnd := _pDictionaryData + _pHeader^.dictlen;
596
597 // Presize the dictionary to save reallocation
598 _Dictionary.Capacity := _pHeader^.ndict;
599 for i := 0 to _pHeader^.ndict - 1 do
600 begin
601 // adjust length so we can use as a Pascal string
602 // (file uses length including length byte,
603 // Pascal string have length excluding length byte)
604 if p >= pEnd then
605 // ran off end of data
606 raise EHelpFileException.Create( ErrorCorruptHelpFile );
607
608 Len := p^ - 1;
609 p^ := Len;
610 _Dictionary.Add( P );
611 inc( P, Len + 1 );
612 end;
613end;
614
[342]615
616function THelpFile.GetIndex: TIndex;
[18]617begin
618 if _Index = nil then
619 ReadIndex;
620 Result := _Index;
621end;
622
623type
624 TIndexEntryHeader = record
625 TextLength: uint8;
626 Flags: uint8;
627 NumberOfRoots: uint8;
628 TOCIndex: uint16;
629 end;
630
631procedure THelpFile.ReadIndex;
632var
633 IndexIndex: longint; // I can't resist :-)
634 pEntryHeader: ^TIndexEntryHeader;
635 EntryText: string;
636 IndexTitleLen: longint;
637 p: pointer;
638 pEnd: pointer;
639 pIndexData: pointer;
[342]640
641 tmpIndexEntry: TIndexEntry;
[18]642begin
[43]643 LogEvent(LogParse, 'Read index');
[18]644
[342]645 _Index := TIndex.Create;
[18]646
647 if _pHeader^.nindex = 0 then
648 exit; // explicit check required since ndict is unsigned
649
650 pIndexData := nil;
651 ReadFileBlock( pIndexData,
652 _pHeader^.indexstart,
653 _pHeader^.indexlen );
654
655 P := pIndexData;
656 pEnd := pIndexData + _pHeader^.indexlen;
657
658 for IndexIndex := 0 to _pHeader^.nindex - 1 do
659 begin
660 if p >= pEnd then
661 // ran off end of data
662 raise EHelpFileException.Create( ErrorCorruptHelpFile );
663
664 pEntryHeader := p;
665 IndexTitleLen := pEntryHeader^.TextLength;
666 inc( p, sizeof( TIndexEntryHeader ) );
667
668 GetMemString( p, EntryText, IndexTitleLen );
[342]669
[18]670 if pEntryHeader^.TOCIndex < _Topics.Count then
[342]671 begin
672 tmpIndexEntry := TIndexEntry.Create(EntryText, _Topics[pEntryHeader^.TOCIndex], pEntryHeader^.flags);
673 _Index.Add(tmpIndexEntry);
674 end
[18]675 else
676// raise EHelpFileException.Create( 'Error reading help file index - out of range topic reference' );
677 ; // pass! something special
678
679 inc( p, IndexTitleLen
680 + pEntryHeader^.NumberOfRoots
681 * sizeof( uint32 ) ); // skip 'roots' for index search
682 end;
683
684 DeallocateMemory( pIndexData );
685end;
686
687function THelpFile.GetSearchTable: TSearchTable;
688begin
689 if _SearchTable = nil then
690 ReadSearchTable;
691 Result := _SearchTable;
692end;
693
694procedure THelpFile.ReadSearchTable;
695var
696 SearchTableOffset: longint;
697 SearchTableRecordLengthIs16Bit: boolean;
698begin
[43]699 LogEvent(LogParse, 'Read search table');
[18]700
701 if _pHeader^.SearchLen = 0 then
702 begin
[43]703 LogEvent(LogParse, 'Read search table (len = 0');
[18]704 exit;
705 end;
706
707 SearchTableOffset := _pHeader^.SearchStart and $7fffffff;
708 SearchTableRecordLengthIs16Bit := _pHeader^.SearchStart and $80000000 > 0;
709 ReadFileBlock( _pSearchData,
710 SearchTableOffset,
711 _pHeader^.SearchLen );
712
713 _SearchTable := TSearchTable.Create( _pSearchData,
714 SearchTableRecordLengthIs16Bit,
715 _Dictionary.Count,
716 _Topics.Count );
717end;
718
719function THelpFile.GetHighlightWords: UInt32ArrayPointer;
720begin
721 if _pHighlightWords = nil then
722 _pHighlightWords := AllocateMemory( _Dictionary.Count * sizeof( UInt32 ) );
723 Result := _pHighlightWords;
724end;
725
726function THelpFile.FindTopicByResourceID( ID: uint16 ): TTopic;
727var
728 i: longint;
729 pResourceIDs: UInt16ArrayPointer;
730 pTopicIndices: UInt16ArrayPointer;
731 FileResourceID: uint16;
732 TopicIndex: uint16;
733begin
734 Result := nil;
735
736 if _pHeader^.nres = 0 then
737 // since nres is unsigned
738 exit;
739
740 if _pResourceData = nil then
741 ReadFileBlock( _pResourceData,
742 _pHeader^.resstart,
743 _pHeader^.nres * sizeof( uint16 ) * 2 ); // list of IDs, list of topics
744
745 pResourceIDs := _pResourceData;
746 pTopicIndices := _pResourceData
747 + _pHeader^.nres * sizeof( uint16 );
748
749 for i := 0 to _pHeader^.nres - 1 do
750 begin
751 FileResourceID := pResourceIDs^[ i ];
752 if FileResourceID = ID then
753 begin
754 // found
755 TopicIndex := pTopicIndices^[ i ];
756 Result := _Topics[ TopicIndex ];
757 exit;
758 end;
759 end;
760end;
761
762// Look up a local "panel name" and return associated topic, if any.
763function THelpFile.FindTopicByLocalName( const Name: string ): TTopic;
764begin
765 Result := FindTopicByName( Name,
766 _pTopicNameData,
767 _pHeader^.nname,
768 _pHeader^.namestart );
769end;
770
771function THelpFile.FindTopicByGlobalName( const Name: string ): TTopic;
772begin
773 Result := nil;
774
775 if _pExtendedHeader = nil then
776 // no extended header - no global list to lookup
777 exit;
778
779 Result := FindTopicByName( Name,
780 _pTopicGlobalNamesData,
781 _pExtendedHeader ^. EntryInGNameTable,
782 _pExtendedHeader ^. HelpPanelGNameTblOffset );
783
784end;
785
786// The text of the names are stored in the (global) dictionary
787// with a table referencing them.
788// We could use a binary search here... but whatever...
789function THelpFile.FindTopicByName( const Name: string;
790 Var pData: pointer;
791 Count: longint;
792 Offset: longint ): TTopic;
793var
794 i: longint;
795 pNameTable: UInt16ArrayPointer;
796 pTopicIndices: UInt16ArrayPointer;
797 TopicIndex: uint16;
798
799 TopicNameWordIndex: uint16;
800 pTopicName: pstring;
801begin
802 Result := nil;
803
804 if Count = 0 then
805 // since it's unsigned
806 exit;
807
808 if pData = nil then
809 ReadFileBlock( pData,
810 Offset,
811 Count * sizeof( uint16 ) * 2 ); // list of name words, list of topics
812
813 // get pointers to the two parts of the table
814 pNameTable := pData;
815 pTopicIndices := pData
816 + Count * sizeof( uint16 );
817
818 for i := 0 to Count - 1 do
819 begin
820 TopicNameWordIndex := pNameTable[ i ];
821 pTopicName := DictionaryWordPtrs[ TopicNameWordIndex ];
822
823 if CompareText( pTopicName^, Name ) = 0 then
824 begin
825 // found
826 TopicIndex := pTopicIndices^[ i ];
827 Result := _Topics[ TopicIndex ];
828 exit;
829 end;
830 end;
831end;
832
[342]833
834// TODO move to index class
[18]835function THelpFile.FindTopicByIndexStartsWith( const SearchText: string ): TTopic;
836var
837 i: longint;
[342]838 tmpLabel: String;
[18]839begin
840 result := nil;
841 GetIndex; // make sure it's read
[342]842
[18]843 for i := 0 to _Index.Count - 1 do
844 begin
[342]845 tmpLabel := _Index.GetLabels.ValuePtrs[i]^;
846 if StrStartsWithIgnoringCase(tmpLabel, SearchText) then
[18]847 begin
848 // found
[342]849 result := Index.getTopic(i);
[18]850 exit;
851 end;
852 end;
853end;
854
[342]855
856function THelpFile.FindTopicByIndexContains(const SearchText: string): TTopic;
[18]857var
858 i: longint;
[342]859 tmpLabel: String;
[18]860begin
861 result := nil;
862 GetIndex; // make sure it's read
[342]863
[18]864 for i := 0 to _Index.Count - 1 do
865 begin
[342]866 tmpLabel := _Index.GetLabels.ValuePtrs[i]^;
867 if CaseInsensitivePos(SearchText, tmpLabel) > 0 then
[18]868 begin
869 // found
[342]870 result := Index.getTopic(i);
[18]871 exit;
872 end;
873 end;
874end;
875
[342]876
[18]877function THelpFile.FindTopicByTitleStartsWith( const SearchText: string ): TTopic;
878var
879 i: longint;
[74]880 tmpTopic: TTopic;
881 tmpLevel : integer;
882 tmpMore : boolean;
[18]883begin
884 result := nil;
[74]885
886 tmpLevel := 0;
887 repeat
888 tmpMore := false;
889 inc(tmpLevel);
890 for i := 0 to _Topics.Count - 1 do
[18]891 begin
[74]892 tmpTopic := _Topics[i];
893 if tmpLevel = tmpTopic.ContentsLevel then
894 begin
[252]895 if StrStartsWithIgnoringCase(tmpTopic.TitlePtr^, SearchText) then
[74]896 begin
897 result := tmpTopic;
898 exit;
899 end;
900 end;
901 if tmpLevel < tmpTopic.ContentsLevel then
902 begin
903 tmpMore := True;
904 end;
[18]905 end;
[74]906 until NOT tmpMore;
[18]907end;
908
909function THelpFile.FindTopicByTitleContains( const SearchText: string ): TTopic;
910var
911 i: longint;
[74]912 tmpTopic: TTopic;
913 tmpLevel : integer;
914 tmpMore : boolean;
[18]915begin
916 result := nil;
[74]917
918 tmpLevel := 0;
919 repeat
920 tmpMore := false;
921 inc(tmpLevel);
922 for i := 0 to _Topics.Count - 1 do
[18]923 begin
[74]924 tmpTopic := _Topics[i];
925 if tmpLevel = tmpTopic.ContentsLevel then
926 begin
927 if CaseInsensitivePos( SearchText, tmpTopic.TitlePtr ^ ) > 0 then
928 begin
929 result := tmpTopic;
930 exit;
931 end;
932 end;
933 if tmpLevel < tmpTopic.ContentsLevel then
934 begin
935 tmpMore := True;
936 end;
[18]937 end;
[74]938 until NOT tmpMore;
[18]939end;
940
941procedure THelpFile.FindResourceIDsForTopic( Topic: TTopic;
942 ResourceIDs: TList );
943var
944 i: longint;
945 pResourceIDs: UInt16ArrayPointer;
946 pTopicIndices: UInt16ArrayPointer;
947begin
948 ResourceIDs.Clear;
949
950 if _pHeader^.nres = 0 then
951 // since nres is unsigned
952 exit;
953
954 if _pResourceData = nil then
955 ReadFileBlock( _pResourceData,
956 _pHeader^.resstart,
957 _pHeader^.nres * sizeof( uint16 ) * 2 ); // list of IDs, list of topics
958
959 pResourceIDs := _pResourceData;
960 pTopicIndices := _pResourceData
961 + _pHeader^.nres * sizeof( uint16 );
962
963 for i := 0 to _pHeader^.nres - 1 do
964 begin
965 if pTopicIndices^[ i ] = Topic.Index then
966 begin
967 // found
968 ResourceIDs.Add( pointer( pResourceIDs^[ i ] ) );
969 end;
970 end;
971end;
972
973procedure THelpFile.ReadReferencedFilesTable;
974var
975 i: longint;
976 p: pointer;
977 pData: pointer;
978 DatabaseName: string;
979 pLength: pByte;
980begin
981 if _pExtendedHeader = nil then
982 // no extended header -> no referenced files table
983 exit;
984
985 if _pExtendedHeader ^.Numdatabase = 0 then
986 exit;
987
988 pData := nil; // please allocate...
989 ReadFileBlock( pData,
990 _pExtendedHeader^.DatabaseOffset,
991 _pExtendedHeader^.DatabaseSize );
992
993 p := pData;
994 for i := 0 to _pExtendedHeader^.Numdatabase - 1 do
995 begin
996 pLength := p; // length byte, including itself
997 GetMemString( p + 1, DatabaseName, ( pLength ^ ) - 1 );
998 _ReferencedFiles.Add( DatabaseName );
999 inc( p, pLength ^ ); // skip to next entry
1000 end;
1001 DeallocateMemory( pData );
1002end;
1003
1004procedure THelpFile.ReadFontTableData;
1005begin
1006 if _pExtendedHeader = nil then
1007 // no extended header -> no font table
1008 exit;
1009
1010 if _pExtendedHeader^.NumFontEntry = 0 then
1011 exit;
1012
1013 ReadFileBlock( _pFontTableData,
1014 _pExtendedHeader^.FontTableOffset,
1015 _pExtendedHeader^.NumFontEntry * sizeof( THelpFontSpec ) );
1016end;
1017
1018procedure THelpFile.ParseFontTable;
1019var
1020 i: longint;
1021 p: pointer;
1022 pFontSpec: pTHelpFontSpec;
1023begin
1024 _FontTable.Clear;
1025
1026 p := _pFontTableData;
1027 if p = nil then
1028 exit; // no data
1029
1030 for i := 0 to _pExtendedHeader^.NumFontEntry - 1 do
1031 begin
1032 pFontSpec := p + i * sizeof( THelpFontSpec );
1033 _FontTable.Add( pFontSpec );
1034 end;
1035end;
1036
1037procedure THelpFile.GetImages( ImageOffsets: TList;
1038 Images: TImageList );
1039var
1040 ListIndex: longint;
1041 ImageOffset: longint;
1042 Bitmap: THelpBitmap;
1043begin
1044 Images.Clear;
1045
1046 for ListIndex := 0 to ImageOffsets.Count - 1 do
1047 begin
1048 ImageOffset := longint( ImageOffsets[ ListIndex ] );
1049 try
1050 Bitmap := THelpBitmap.CreateFromHelpFile( _Handle,
1051 _pHeader^.imgstart
1052 + ImageOffset );
1053 except
1054 on e: EHelpBitmapException do
1055{ raise EHelpFileException.Create( 'Error loading help bitmap at'
1056 + IntToStr( ImageOffset )
1057 + ': '
1058 + e.Message );}
1059 begin
1060 Bitmap := THelpBitmap.Create;
1061 Bitmap.LoadFromResourceName( 'MissingBitmap' );
1062 end;
1063 end;
1064
1065 Images.Add( Bitmap, nil );
1066 Bitmap.Destroy;
1067
1068 end;
1069end;
1070
1071function THelpFile.GetImage( ImageOffset: longint ): THelpBitmap;
1072begin
1073 try
1074 Result := THelpBitmap.CreateFromHelpFile( _Handle,
1075 _pHeader^.imgstart
1076 + ImageOffset );
1077 except
1078 on e: EHelpBitmapException do
1079{ raise EHelpFileException.Create( 'Error loading help bitmap at'
1080 + IntToStr( ImageOffset )
1081 + ': '
1082 + e.Message );}
1083 begin
1084 result := nil;
1085 end;
1086 end;
1087end;
1088
1089function THelpFile.GetTopic( Index: longint ): TTopic;
1090begin
1091 if ( Index < 0 )
1092 or ( Index > _Topics.Count - 1 ) then
1093 Result := nil
1094 else
1095 Result := _Topics[ Index ];
1096end;
1097
1098function THelpFile.GetTopicCount: longint;
1099begin
1100 Result := _Topics.Count;
1101end;
1102
1103function THelpFile.IndexOfTopic( Topic: TTopic ): longint;
1104begin
1105 Result := _Topics.IndexOf( Topic );
1106end;
1107
1108function THelpFile.GetDictionaryCount: longint;
1109begin
1110 Result := _Dictionary.Count;
1111end;
1112
1113function THelpFile.GetDictionaryWord( Index: longint ): string;
1114begin
1115 Result := pstring( _Dictionary[ Index ] )^;
1116end;
1117
1118function THelpFile.GetDictionaryWordPtr( Index: longint ): pstring;
1119begin
1120 Result := pstring( _Dictionary[ Index ] );
1121end;
1122
1123
1124// Looks for fonts that should be substitued to the
1125// users selected fixed font
1126// doesn't make a lot of sense for this to be here...
1127procedure THelpFile.SetupFontSubstitutes( Substitutions: string );
1128var
1129 Item: string;
1130 FontName: string;
1131 SpacePos: longint;
1132 W: longint;
1133 H: longint;
1134 i: longint;
1135 pFontSpec: pTHelpFontSpec;
[140]1136 tmpSubstitutionItems : TStringList;
1137 tmpCounter : integer;
1138 tmpDimensionParts : TStringList;
[18]1139begin
1140 ParseFontTable; // (re)load table from raw data
1141
[140]1142 tmpSubstitutionItems := TStringList.Create;
1143 StrExtractStrings(tmpSubstitutionItems, Substitutions, [';'], #0);
1144
1145 for tmpCounter := 0 to tmpSubstitutionItems.Count - 1 do
[18]1146 begin
[140]1147 Item := tmpSubstitutionItems[tmpCounter];
[18]1148 try
1149 if Item <> '' then
1150 begin
1151 // Look for space in xxxx WxH
1152
[140]1153 SpacePos := LastPosOfChar(' ', Item);
[18]1154 if SpacePos > 0 then
1155 begin
1156 // fontname comes before
1157 FontName := StrLeft( Item, SpacePos - 1 );
1158 Delete( Item, 1, SpacePos );
[140]1159
[18]1160 // width and height after, with an X between
[140]1161 tmpDimensionParts := TStringList.Create;
1162 StrExtractStrings(tmpDimensionParts, Item, ['x'], #0);
1163 W := StrToInt(tmpDimensionParts[0]);
1164 H := StrToInt(tmpDimensionParts[1]);
1165 tmpDimensionParts.Destroy;
[18]1166 if ( W > 0 ) and ( H > 0 ) then
1167 begin
1168 // Now look through the font table for matches
1169 for i := 0 to _FontTable.Count - 1 do
1170 begin
1171 pFontSpec := _FontTable[ i ];
[140]1172 if StrPasWithLength( pFontSpec^.FaceName, sizeof( pFontSpec^.FaceName ) ) = FontName then
[18]1173 begin
1174 // same face name...
1175 if ( W = pFontSpec ^. Height ) and ( H = pFontSpec ^. Width ) then
1176 begin
1177 // match
1178 _FontTable[ i ] := SubstituteFixedFont;
1179 end;
1180 end;
1181 end;
1182 end;
1183 end;
1184 end;
1185 except
1186 end;
1187 end;
[140]1188
1189 tmpSubstitutionItems.Destroy;
[18]1190end;
1191
1192
1193// -------------------------------------------------------------
1194// Get the title only from specific help file (if possible)
1195
1196function GetHelpFileTitle( const Filename: string ): string;
1197var
1198 OpenAction: ULong;
1199 rc: APIRET;
1200 szName: Cstring;
1201
1202 Header: THelpFileHeader;
1203 Handle: HFILE;
1204 Ext: string;
1205begin
1206 Ext := ExtractFileExt( Filename );
1207 Result := '';
1208
[140]1209 if StrEqualIgnoringCase( Ext, '.inf' )
1210 or StrEqualIgnoringCase( Ext, '.hlp' ) then
[18]1211 begin
1212 szName := Filename;
1213 rc := DosOpen( szName,
1214 Handle,
1215 OpenAction,
1216 0, // file size - irrelevant, not creating,
1217 0, // attributes - ''
1218 OPEN_ACTION_OPEN_IF_EXISTS,
1219 OPEN_SHARE_DENYNONE + OPEN_ACCESS_READONLY,
1220 nil ); // no extended attributes
1221 if rc = 0 then
1222 begin
1223 FillChar( Header, sizeof( Header ), 0 );
1224 if MyRead( Handle, Addr( Header ), sizeof( Header ) ) then
1225 if Header.ID = INF_HEADER_ID then
1226 Result := StrPas( Header.Title );
1227 DosClose( Handle );
1228 end;
1229 end;
1230end;
1231
1232Initialization
1233 RegisterProcForLanguages( OnLanguageEvent );
1234End.
1235
Note: See TracBrowser for help on using the repository browser.