whelp.pas 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948
  1. {
  2. $Id$
  3. This file is part of the Free Pascal Integrated Development Environment
  4. Copyright (c) 1998 by Berczi Gabor
  5. Help support & Borland OA .HLP reader objects and routines
  6. See the file COPYING.FPC, included in this distribution,
  7. for details about the copyright.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  11. **********************************************************************}
  12. {$R-}
  13. unit WHelp;
  14. interface
  15. uses Objects;
  16. const
  17. MinFormatVersion = $34;
  18. Signature = '$*$* &&&&$*$'#0;
  19. ncRawChar = $F;
  20. ncRepChar = $E;
  21. rtFileHeader = Byte ($0);
  22. rtContext = Byte ($1);
  23. rtText = Byte ($2);
  24. rtKeyWord = Byte ($3);
  25. rtIndex = Byte ($4);
  26. rtCompression = Byte ($5);
  27. rtIndexTags = Byte ($6);
  28. ctNone = $00;
  29. ctNibble = $02;
  30. hscLineBreak = #0;
  31. hscLink = #2;
  32. hscLineStart = #3;
  33. hscCode = #5;
  34. hscCenter = #10;
  35. hscRight = #11;
  36. type
  37. FileStamp = array [0..32] of char; {+ null terminator + $1A }
  38. FileSignature = array [0..12] of char; {+ null terminator }
  39. THelpCtx = longint;
  40. THLPVersion = packed record
  41. FormatVersion : byte;
  42. TextVersion : byte;
  43. end;
  44. THLPRecordHeader = packed record
  45. RecType : byte; {TPRecType}
  46. RecLength : word;
  47. end;
  48. THLPContextPos = packed record
  49. LoW: word;
  50. HiB: byte;
  51. end;
  52. THLPContexts = packed record
  53. ContextCount : word;
  54. Contexts : array[0..0] of THLPContextPos;
  55. end;
  56. THLPFileHeader = packed record
  57. Options : word;
  58. MainIndexScreen : word;
  59. Maxscreensize : word;
  60. Height : byte;
  61. Width : byte;
  62. LeftMargin : byte;
  63. end;
  64. THLPCompression = packed record
  65. CompType : byte;
  66. CharTable : array [0..13] of byte;
  67. end;
  68. THLPIndexDescriptor = packed record
  69. LengthCode : byte;
  70. UniqueChars : array [0..0] of byte;
  71. Context : word;
  72. end;
  73. THLPIndexTable = packed record
  74. IndexCount : word;
  75. Entries : record end;
  76. end;
  77. THLPKeywordDescriptor = record
  78. KwContext : word;
  79. end;
  80. THLPKeyWordRecord = record
  81. UpContext : word;
  82. DownContext : word;
  83. KeyWordCount : word;
  84. Keywords : array[0..0] of THLPKeywordDescriptor;
  85. end;
  86. TRecord = packed record
  87. SClass : byte;
  88. Size : word;
  89. Data : pointer;
  90. end;
  91. PIndexEntry = ^TIndexEntry;
  92. TIndexEntry = packed record
  93. Tag : PString;
  94. HelpCtx : THelpCtx;
  95. FileID : word;
  96. end;
  97. PKeywordDescriptor = ^TKeywordDescriptor;
  98. TKeywordDescriptor = packed record
  99. FileID : word;
  100. Context : THelpCtx;
  101. end;
  102. PKeywordDescriptors = ^TKeywordDescriptors;
  103. TKeywordDescriptors = array[0..10900] of TKeywordDescriptor;
  104. PTopic = ^TTopic;
  105. TTopic = record
  106. HelpCtx : THelpCtx;
  107. FileOfs : longint;
  108. TextSize : word;
  109. Text : PByteArray;
  110. LinkCount : word;
  111. LinkSize : word;
  112. Links : PKeywordDescriptors;
  113. LastAccess : longint;
  114. FileID : word;
  115. end;
  116. PUnsortedStringCollection = ^TUnsortedStringCollection;
  117. TUnsortedStringCollection = object(TCollection)
  118. function At(Index: Integer): PString;
  119. procedure FreeItem(Item: Pointer); virtual;
  120. end;
  121. PTopicCollection = ^TTopicCollection;
  122. TTopicCollection = object(TCollection)
  123. function At(Index: Integer): PTopic;
  124. procedure FreeItem(Item: Pointer); virtual;
  125. function SearchTopic(AHelpCtx: THelpCtx): PTopic;
  126. end;
  127. PIndexEntryCollection = ^TIndexEntryCollection;
  128. TIndexEntryCollection = object(TSortedCollection)
  129. function At(Index: Sw_Integer): PIndexEntry;
  130. procedure FreeItem(Item: Pointer); virtual;
  131. function Compare(Key1, Key2: Pointer): Sw_Integer; virtual;
  132. end;
  133. PHelpFile = ^THelpFile;
  134. THelpFile = object(TObject)
  135. ID : word;
  136. Topics : PTopicCollection;
  137. IndexEntries : PIndexEntryCollection;
  138. constructor Init(AID: word);
  139. function LoadTopic(HelpCtx: THelpCtx): PTopic; virtual;
  140. destructor Done; virtual;
  141. public
  142. function LoadIndex: boolean; virtual;
  143. function SearchTopic(HelpCtx: THelpCtx): PTopic; virtual;
  144. function ReadTopic(T: PTopic): boolean; virtual;
  145. private
  146. procedure MaintainTopicCache;
  147. end;
  148. POAHelpFile = ^TOAHelpFile;
  149. TOAHelpFile = object(THelpFile)
  150. Version : THLPVersion;
  151. Header : THLPFileHeader;
  152. Compression : THLPCompression;
  153. constructor Init(AFileName: string; AID: word);
  154. destructor Done; virtual;
  155. public
  156. function LoadIndex: boolean; virtual;
  157. function ReadTopic(T: PTopic): boolean; virtual;
  158. private
  159. F: PBufStream;
  160. TopicsRead : boolean;
  161. IndexTableRead : boolean;
  162. CompressionRead: boolean;
  163. IndexTagsRead : boolean;
  164. IndexTagsPos : longint;
  165. IndexTablePos : longint;
  166. function ReadHeader: boolean;
  167. function ReadTopics: boolean;
  168. function ReadIndexTable: boolean;
  169. function ReadCompression: boolean;
  170. function ReadIndexTags: boolean;
  171. function ReadRecord(var R: TRecord; ReadData: boolean): boolean;
  172. end;
  173. PHelpFileCollection = PCollection;
  174. PHelpFacility = ^THelpFacility;
  175. THelpFacility = object(TObject)
  176. HelpFiles: PHelpFileCollection;
  177. IndexTabSize: integer;
  178. constructor Init;
  179. function AddOAHelpFile(FileName: string): boolean;
  180. function AddHTMLHelpFile(FileName, TOCEntry: string): boolean;
  181. function LoadTopic(SourceFileID: word; Context: THelpCtx): PTopic; virtual;
  182. function TopicSearch(Keyword: string; var FileID: word; Context: THelpCtx): boolean; virtual;
  183. function BuildIndexTopic: PTopic; virtual;
  184. destructor Done; virtual;
  185. private
  186. LastID: word;
  187. function SearchFile(ID: byte): PHelpFile;
  188. function SearchTopicInHelpFile(F: PHelpFile; Context: THelpCtx): PTopic;
  189. function SearchTopicOwner(SourceFileID: word; Context: THelpCtx): PHelpFile;
  190. function AddFile(H: PHelpFile): boolean;
  191. end;
  192. const TopicCacheSize : integer = 10;
  193. HelpStreamBufSize : integer = 4096;
  194. HelpFacility : PHelpFacility = nil;
  195. MaxHelpTopicSize : word = 65520;
  196. function NewTopic(FileID: byte; HelpCtx: THelpCtx; Pos: longint): PTopic;
  197. procedure DisposeTopic(P: PTopic);
  198. function NewIndexEntry(Tag: string; FileID: word; HelpCtx: THelpCtx): PIndexEntry;
  199. procedure DisposeIndexEntry(P: PIndexEntry);
  200. implementation
  201. uses
  202. Dos,
  203. WHTMLHlp,
  204. Drivers;
  205. type
  206. PByteArray = ^TByteArray;
  207. TByteArray = array[0..65520] of byte;
  208. function CharStr(C: char; Count: byte): string;
  209. var S: string;
  210. begin
  211. S[0]:=chr(Count);
  212. FillChar(S[1],Count,C);
  213. CharStr:=S;
  214. end;
  215. function RExpand(S: string; MinLen: byte): string;
  216. begin
  217. if length(S)<MinLen then
  218. S:=S+CharStr(' ',MinLen-length(S));
  219. RExpand:=S;
  220. end;
  221. function UpcaseStr(S: string): string;
  222. var I: integer;
  223. begin
  224. for I:=1 to length(S) do
  225. S[I]:=Upcase(S[I]);
  226. UpcaseStr:=S;
  227. end;
  228. procedure DisposeRecord(var R: TRecord);
  229. begin
  230. with R do
  231. if (Size>0) and (Data<>nil) then FreeMem(Data, Size);
  232. FillChar(R, SizeOf(R), 0);
  233. end;
  234. function NewTopic(FileID: byte; HelpCtx: THelpCtx; Pos: longint): PTopic;
  235. var P: PTopic;
  236. begin
  237. New(P); FillChar(P^,SizeOf(P^), 0);
  238. P^.HelpCtx:=HelpCtx; P^.FileOfs:=Pos; P^.FileID:=FileID;
  239. NewTopic:=P;
  240. end;
  241. procedure DisposeTopic(P: PTopic);
  242. begin
  243. if P<>nil then
  244. begin
  245. if (P^.TextSize>0) and (P^.Text<>nil) then
  246. FreeMem(P^.Text,P^.TextSize);
  247. P^.Text:=nil;
  248. if (P^.LinkCount>0) and (P^.Links<>nil) then
  249. FreeMem(P^.Links,P^.LinkSize);
  250. P^.Links:=nil;
  251. Dispose(P);
  252. end;
  253. end;
  254. function CloneTopic(T: PTopic): PTopic;
  255. var NT: PTopic;
  256. begin
  257. New(NT); Move(T^,NT^,SizeOf(NT^));
  258. if NT^.Text<>nil then
  259. begin GetMem(NT^.Text,NT^.TextSize); Move(T^.Text^,NT^.Text^,NT^.TextSize); end;
  260. if NT^.Links<>nil then
  261. begin GetMem(NT^.Links,NT^.LinkSize); Move(T^.Links^,NT^.Links^,NT^.LinkSize); end;
  262. CloneTopic:=NT;
  263. end;
  264. function NewIndexEntry(Tag: string; FileID: word; HelpCtx: THelpCtx): PIndexEntry;
  265. var P: PIndexEntry;
  266. begin
  267. New(P); FillChar(P^,SizeOf(P^), 0);
  268. P^.Tag:=NewStr(Tag); P^.FileID:=FileID; P^.HelpCtx:=HelpCtx;
  269. NewIndexEntry:=P;
  270. end;
  271. procedure DisposeIndexEntry(P: PIndexEntry);
  272. begin
  273. if P<>nil then
  274. begin
  275. if P^.Tag<>nil then DisposeStr(P^.Tag);
  276. Dispose(P);
  277. end;
  278. end;
  279. function TUnsortedStringCollection.At(Index: Integer): PString;
  280. begin
  281. At:=inherited At(Index);
  282. end;
  283. procedure TUnsortedStringCollection.FreeItem(Item: Pointer);
  284. begin
  285. if Item<>nil then DisposeStr(Item);
  286. end;
  287. function TTopicCollection.At(Index: Integer): PTopic;
  288. begin
  289. At:=inherited At(Index);
  290. end;
  291. procedure TTopicCollection.FreeItem(Item: Pointer);
  292. begin
  293. if Item<>nil then DisposeTopic(Item);
  294. end;
  295. function TTopicCollection.SearchTopic(AHelpCtx: THelpCtx): PTopic;
  296. function Match(P: PTopic): boolean;{$ifndef FPC}far;{$endif}
  297. begin Match:=(P^.HelpCtx=AHelpCtx); end;
  298. begin
  299. SearchTopic:=FirstThat(@Match);
  300. end;
  301. function TIndexEntryCollection.At(Index: Sw_Integer): PIndexEntry;
  302. begin
  303. At:=inherited At(Index);
  304. end;
  305. procedure TIndexEntryCollection.FreeItem(Item: Pointer);
  306. begin
  307. if Item<>nil then DisposeIndexEntry(Item);
  308. end;
  309. function TIndexEntryCollection.Compare(Key1, Key2: Pointer): Sw_Integer;
  310. var K1: PIndexEntry absolute Key1;
  311. K2: PIndexEntry absolute Key2;
  312. R: Sw_integer;
  313. S1,S2: string;
  314. begin
  315. S1:=UpcaseStr(K1^.Tag^); S2:=UpcaseStr(K2^.Tag^);
  316. if S1<S2 then R:=-1 else
  317. if S1>S2 then R:=1 else
  318. R:=0;
  319. Compare:=R;
  320. end;
  321. constructor THelpFile.Init(AID: word);
  322. begin
  323. inherited Init;
  324. ID:=AID;
  325. New(Topics, Init(500,500));
  326. New(IndexEntries, Init(200,100));
  327. end;
  328. function THelpFile.LoadTopic(HelpCtx: THelpCtx): PTopic;
  329. var T: PTopic;
  330. begin
  331. T:=SearchTopic(HelpCtx);
  332. if (T<>nil) then
  333. if T^.Text=nil then
  334. begin
  335. MaintainTopicCache;
  336. if ReadTopic(T)=false then T:=nil;
  337. if (T<>nil) and (T^.Text=nil) then T:=nil;
  338. end;
  339. if T<>nil then
  340. begin T^.LastAccess:=GetDosTicks; T:=CloneTopic(T); end;
  341. LoadTopic:=T;
  342. end;
  343. function THelpFile.LoadIndex: boolean;
  344. begin
  345. Abstract;
  346. end;
  347. function THelpFile.SearchTopic(HelpCtx: THelpCtx): PTopic;
  348. var T: PTopic;
  349. begin
  350. T:=Topics^.SearchTopic(HelpCtx);
  351. SearchTopic:=T;
  352. end;
  353. function THelpFile.ReadTopic(T: PTopic): boolean;
  354. begin
  355. Abstract;
  356. end;
  357. procedure THelpFile.MaintainTopicCache;
  358. var Count: integer;
  359. MinP: PTopic;
  360. MinLRU: longint;
  361. procedure CountThem(P: PTopic); {$ifndef FPC}far;{$endif}
  362. begin if (P^.Text<>nil) or (P^.Links<>nil) then Inc(Count); end;
  363. procedure SearchLRU(P: PTopic); {$ifndef FPC}far;{$endif}
  364. begin if P^.LastAccess<MinLRU then begin MinLRU:=P^.LastAccess; MinP:=P; end; end;
  365. var P: PTopic;
  366. begin
  367. Count:=0; Topics^.ForEach(@CountThem);
  368. if (Count>=TopicCacheSize) then
  369. begin
  370. MinLRU:=MaxLongint; P:=nil; Topics^.ForEach(@SearchLRU);
  371. if P<>nil then
  372. begin
  373. FreeMem(P^.Text,P^.TextSize); P^.TextSize:=0; P^.Text:=nil;
  374. FreeMem(P^.Links,P^.LinkSize); P^.LinkSize:=0; P^.LinkCount:=0; P^.Links:=nil;
  375. end;
  376. end;
  377. end;
  378. destructor THelpFile.Done;
  379. begin
  380. if Topics<>nil then Dispose(Topics, Done);
  381. if IndexEntries<>nil then Dispose(IndexEntries, Done);
  382. inherited Done;
  383. end;
  384. constructor TOAHelpFile.Init(AFileName: string; AID: word);
  385. var OK: boolean;
  386. FS,L: longint;
  387. R: TRecord;
  388. begin
  389. inherited Init(AID);
  390. New(F, Init(AFileName, stOpenRead, HelpStreamBufSize));
  391. OK:=F<>nil;
  392. if OK then OK:=(F^.Status=stOK);
  393. if OK then begin FS:=F^.GetSize; OK:=ReadHeader; end;
  394. while OK do
  395. begin
  396. L:=F^.GetPos;
  397. if (L>=FS) then Break;
  398. OK:=ReadRecord(R,false);
  399. if (OK=false) or (R.SClass=0) or (R.Size=0) then Break;
  400. case R.SClass of
  401. rtContext : begin F^.Seek(L); OK:=ReadTopics; end;
  402. rtText : {Skip};
  403. rtKeyword : {Skip};
  404. rtIndex : begin IndexTablePos:=L; {OK:=ReadIndexTable; }end;
  405. rtCompression : begin F^.Seek(L); OK:=ReadCompression; end;
  406. rtIndexTags : begin IndexTagsPos:=L; {OK:=ReadIndexTags; }end;
  407. else {Skip};
  408. end;
  409. if OK then
  410. begin Inc(L, SizeOf(THLPRecordHeader)); Inc(L, R.Size); F^.Seek(L); OK:=(F^.Status=stOK); end
  411. end;
  412. OK:=OK and (TopicsRead=true);
  413. if OK=false then Fail;
  414. end;
  415. function TOAHelpFile.LoadIndex: boolean;
  416. begin
  417. LoadIndex:=ReadIndexTable;
  418. end;
  419. function TOAHelpFile.ReadHeader: boolean;
  420. var S: string;
  421. P: longint;
  422. R: TRecord;
  423. OK: boolean;
  424. begin
  425. F^.Seek(0);
  426. F^.Read(S[1],255); S[0]:=#255;
  427. OK:=(F^.Status=stOK); P:=Pos(Signature,S);
  428. OK:=OK and (P>0);
  429. if OK then
  430. begin
  431. F^.Seek(P+length(Signature)-1);
  432. F^.Read(Version,SizeOf(Version));
  433. OK:=(F^.Status=stOK) and (Version.FormatVersion>=MinFormatVersion);
  434. if OK then OK:=ReadRecord(R,true);
  435. OK:=OK and (R.SClass=rtFileHeader) and (R.Size=SizeOf(Header));
  436. if OK then Move(R.Data^,Header,SizeOf(Header));
  437. DisposeRecord(R);
  438. end;
  439. ReadHeader:=OK;
  440. end;
  441. function TOAHelpFile.ReadTopics: boolean;
  442. var OK: boolean;
  443. R: TRecord;
  444. L,I: longint;
  445. function GetCtxPos(C: THLPContextPos): longint;
  446. begin
  447. GetCtxPos:=longint(C.HiB) shl 16 + C.LoW;
  448. end;
  449. begin
  450. OK:=ReadRecord(R, true);
  451. if OK then
  452. with THLPContexts(R.Data^) do
  453. for I:=1 to ContextCount-1 do
  454. begin
  455. if Topics^.Count=MaxCollectionSize then Break;
  456. L:=GetCtxPos(Contexts[I]);
  457. if (L and $800000)<>0 then L:=not L;
  458. if (L=-1) and (Header.MainIndexScreen>0) then
  459. L:=GetCtxPos(Contexts[Header.MainIndexScreen]);
  460. if (L>0) then
  461. Topics^.Insert(NewTopic(ID,I,L));
  462. end;
  463. DisposeRecord(R);
  464. TopicsRead:=OK;
  465. ReadTopics:=OK;
  466. end;
  467. function TOAHelpFile.ReadIndexTable: boolean;
  468. var OK: boolean;
  469. R: TRecord;
  470. I: longint;
  471. LastTag,S: string;
  472. CurPtr,HelpCtx: word;
  473. LenCode,CopyCnt,AddLen: byte;
  474. begin
  475. if IndexTableRead then OK:=true else
  476. begin
  477. LastTag:=''; CurPtr:=0;
  478. OK:=(IndexTablePos<>0);
  479. if OK then begin F^.Seek(IndexTablePos); OK:=F^.Status=stOK; end;
  480. if OK then OK:=ReadRecord(R, true);
  481. if OK then
  482. with THLPIndexTable(R.Data^) do
  483. for I:=0 to IndexCount-1 do
  484. begin
  485. LenCode:=PByteArray(@Entries)^[CurPtr];
  486. AddLen:=LenCode and $1f; CopyCnt:=LenCode shr 5;
  487. S[0]:=chr(AddLen); Move(PByteArray(@Entries)^[CurPtr+1],S[1],AddLen);
  488. LastTag:=copy(LastTag,1,CopyCnt)+S;
  489. Move(PByteArray(@Entries)^[CurPtr+1+AddLen],HelpCtx,2);
  490. IndexEntries^.Insert(NewIndexEntry(LastTag,ID,HelpCtx));
  491. Inc(CurPtr,1+AddLen+2);
  492. end;
  493. DisposeRecord(R);
  494. IndexTableRead:=OK;
  495. end;
  496. ReadIndexTable:=OK;
  497. end;
  498. function TOAHelpFile.ReadCompression: boolean;
  499. var OK: boolean;
  500. R: TRecord;
  501. begin
  502. OK:=ReadRecord(R, true);
  503. OK:=OK and (R.Size=SizeOf(THLPCompression));
  504. if OK then Move(R.Data^,Compression,SizeOf(Compression));
  505. DisposeRecord(R);
  506. CompressionRead:=OK;
  507. ReadCompression:=OK;
  508. end;
  509. function TOAHelpFile.ReadIndexTags: boolean;
  510. var OK: boolean;
  511. begin
  512. OK:={ReadRecord(R, true)}true;
  513. IndexTagsRead:=OK;
  514. ReadIndexTags:=OK;
  515. end;
  516. function TOAHelpFile.ReadRecord(var R: TRecord; ReadData: boolean): boolean;
  517. var OK: boolean;
  518. H: THLPRecordHeader;
  519. begin
  520. FillChar(R, SizeOf(R), 0);
  521. F^.Read(H,SizeOf(H));
  522. OK:=F^.Status=stOK;
  523. if OK then
  524. begin
  525. R.SClass:=H.RecType; R.Size:=H.RecLength;
  526. if (R.Size>0) and ReadData then
  527. begin
  528. GetMem(R.Data,R.Size);
  529. F^.Read(R.Data^,R.Size);
  530. OK:=F^.Status=stOK;
  531. end;
  532. if OK=false then DisposeRecord(R);
  533. end;
  534. ReadRecord:=OK;
  535. end;
  536. function TOAHelpFile.ReadTopic(T: PTopic): boolean;
  537. var SrcPtr,DestPtr: word;
  538. NewR: TRecord;
  539. function ExtractTextRec(var R: TRecord): boolean;
  540. function GetNextNibble: byte;
  541. var B,N: byte;
  542. begin
  543. B:=PByteArray(R.Data)^[SrcPtr div 2];
  544. N:=( B and ($0f shl (4*(SrcPtr mod 2))) ) shr (4*(SrcPtr mod 2));
  545. Inc(SrcPtr);
  546. GetNextNibble:=N;
  547. end;
  548. procedure AddChar(C: char);
  549. begin
  550. PByteArray(NewR.Data)^[DestPtr]:=ord(C);
  551. Inc(DestPtr);
  552. end;
  553. var OK: boolean;
  554. C: char;
  555. P: pointer;
  556. function GetNextChar: char;
  557. var C: char;
  558. I,N,Cnt: byte;
  559. begin
  560. N:=GetNextNibble;
  561. case N of
  562. $00 : C:=#0;
  563. $01..$0D : C:=chr(Compression.CharTable[N]);
  564. ncRawChar : C:=chr(GetNextNibble*16+GetNextNibble);
  565. ncRepChar : begin
  566. Cnt:=2+GetNextNibble;
  567. C:=GetNextChar{$ifdef FPC}(){$endif};
  568. for I:=1 to Cnt-1 do AddChar(C);
  569. end;
  570. end;
  571. GetNextChar:=C;
  572. end;
  573. begin
  574. OK:=Compression.CompType in[ctNone,ctNibble];
  575. if OK then
  576. case Compression.CompType of
  577. ctNone : ;
  578. ctNibble :
  579. begin
  580. NewR.SClass:=R.SClass;
  581. NewR.Size:=MaxHelpTopicSize; { R.Size*2 <- bug fixed, i didn't care of RLL codings }
  582. GetMem(NewR.Data, NewR.Size);
  583. SrcPtr:=0; DestPtr:=0;
  584. while SrcPtr<(R.Size*2) do
  585. begin
  586. C:=GetNextChar;
  587. AddChar(C);
  588. end;
  589. DisposeRecord(R); R:=NewR;
  590. if (R.Size>DestPtr) then
  591. begin
  592. P:=R.Data; GetMem(R.Data,DestPtr);
  593. Move(P^,R.Data^,DestPtr); FreeMem(P,R.Size); R.Size:=DestPtr;
  594. end;
  595. end;
  596. else OK:=false;
  597. end;
  598. ExtractTextRec:=OK;
  599. end;
  600. var OK: boolean;
  601. TextR,KeyWR: TRecord;
  602. W,I: word;
  603. begin
  604. OK:=T<>nil;
  605. if OK and (T^.Text=nil) then
  606. begin
  607. FillChar(TextR,SizeOf(TextR),0); FillChar(KeyWR,SizeOf(KeyWR),0);
  608. F^.Seek(T^.FileOfs); OK:=F^.Status=stOK;
  609. if OK then OK:=ReadRecord(TextR,true);
  610. OK:=OK and (TextR.SClass=rtText);
  611. if OK then OK:=ReadRecord(KeyWR,true);
  612. OK:=OK and (KeyWR.SClass=rtKeyword);
  613. if OK then OK:=ExtractTextRec(TextR);
  614. if OK then
  615. begin
  616. if TextR.Size>0 then
  617. begin
  618. T^.Text:=TextR.Data; T^.TextSize:=TextR.Size;
  619. TextR.Data:=nil; TextR.Size:=0;
  620. end;
  621. with THLPKeywordRecord(KeyWR.Data^) do
  622. begin
  623. T^.LinkCount:=KeywordCount;
  624. W:=T^.LinkCount*SizeOf(T^.Links^[0]);
  625. T^.LinkSize:=W; GetMem(T^.Links,T^.LinkSize);
  626. if KeywordCount>0 then
  627. for I:=0 to KeywordCount-1 do
  628. begin
  629. T^.Links^[I].Context:=Keywords[I].KwContext;
  630. T^.Links^[I].FileID:=ID;
  631. end;
  632. end;
  633. end;
  634. DisposeRecord(TextR); DisposeRecord(KeyWR);
  635. end;
  636. ReadTopic:=OK;
  637. end;
  638. destructor TOAHelpFile.Done;
  639. begin
  640. if F<>nil then Dispose(F, Done);
  641. inherited Done;
  642. end;
  643. constructor THelpFacility.Init;
  644. begin
  645. inherited Init;
  646. New(HelpFiles, Init(10,10));
  647. IndexTabSize:=40;
  648. end;
  649. function THelpFacility.AddOAHelpFile(FileName: string): boolean;
  650. var H: PHelpFile;
  651. begin
  652. H:=New(POAHelpFile, Init(FileName, LastID+1));
  653. AddOAHelpFile:=AddFile(H);
  654. end;
  655. function THelpFacility.AddHTMLHelpFile(FileName, TOCEntry: string): boolean;
  656. var H: PHelpFile;
  657. begin
  658. H:=New(PHTMLHelpFile, Init(FileName, LastID+1, TOCEntry));
  659. AddHTMLHelpFile:=AddFile(H);;
  660. end;
  661. function THelpFacility.AddFile(H: PHelpFile): boolean;
  662. begin
  663. if H<>nil then
  664. begin
  665. HelpFiles^.Insert(H);
  666. Inc(LastID);
  667. end;
  668. AddFile:=H<>nil;
  669. end;
  670. function THelpFacility.SearchTopicOwner(SourceFileID: word; Context: THelpCtx): PHelpFile;
  671. var P: PTopic;
  672. HelpFile: PHelpFile;
  673. function Search(F: PHelpFile): boolean; {$ifndef FPC}far;{$endif}
  674. begin
  675. P:=SearchTopicInHelpFile(F,Context); if P<>nil then HelpFile:=F;
  676. Search:=P<>nil;
  677. end;
  678. begin
  679. HelpFile:=nil;
  680. if SourceFileID=0 then P:=nil else
  681. begin
  682. HelpFile:=SearchFile(SourceFileID);
  683. P:=SearchTopicInHelpFile(HelpFile,Context);
  684. end;
  685. if P=nil then HelpFiles^.FirstThat(@Search);
  686. if P=nil then HelpFile:=nil;
  687. SearchTopicOwner:=HelpFile;
  688. end;
  689. function THelpFacility.LoadTopic(SourceFileID: word; Context: THelpCtx): PTopic;
  690. var P: PTopic;
  691. H: PHelpFile;
  692. begin
  693. if (SourceFileID=0) and (Context=0) then
  694. P:=BuildIndexTopic else
  695. begin
  696. H:=SearchTopicOwner(SourceFileID,Context);
  697. if (H=nil) then P:=nil else
  698. P:=H^.LoadTopic(Context);
  699. end;
  700. LoadTopic:=P;
  701. end;
  702. function THelpFacility.TopicSearch(Keyword: string; var FileID: word; Context: THelpCtx): boolean;
  703. function ScanHelpFile(H: PHelpFile): boolean; {$ifndef FPC}far;{$endif}
  704. function Search(P: PIndexEntry): boolean; {$ifndef FPC}far;{$endif}
  705. begin
  706. Search:=copy(UpcaseStr(P^.Tag^),1,length(Keyword))=Keyword;
  707. end;
  708. var P: PIndexEntry;
  709. begin
  710. H^.LoadIndex;
  711. P:=H^.IndexEntries^.FirstThat(@Search);
  712. if P<>nil then begin FileID:=H^.ID; Context:=P^.HelpCtx; end;
  713. ScanHelpFile:=P<>nil;
  714. end;
  715. begin
  716. Keyword:=UpcaseStr(Keyword);
  717. TopicSearch:=HelpFiles^.FirstThat(@ScanHelpFile)<>nil;
  718. end;
  719. function THelpFacility.BuildIndexTopic: PTopic;
  720. var T: PTopic;
  721. Keywords: PIndexEntryCollection;
  722. Lines: PUnsortedStringCollection;
  723. procedure InsertKeywordsOfFile(H: PHelpFile); {$ifndef FPC}far;{$endif}
  724. function InsertKeywords(P: PIndexEntry): boolean; {$ifndef FPC}far;{$endif}
  725. begin
  726. Keywords^.Insert(P);
  727. InsertKeywords:=Keywords^.Count>=MaxCollectionSize;
  728. end;
  729. begin
  730. H^.LoadIndex;
  731. if Keywords^.Count<MaxCollectionSize then
  732. H^.IndexEntries^.FirstThat(@InsertKeywords);
  733. end;
  734. procedure AddLine(S: string);
  735. begin
  736. if S='' then S:=' ';
  737. Lines^.Insert(NewStr(S));
  738. end;
  739. procedure RenderTopic;
  740. var Size,CurPtr,I: word;
  741. S: string;
  742. function CountSize(P: PString): boolean; {$ifndef FPC}far;{$endif} begin Inc(Size, length(P^)+1); CountSize:=Size>65200; end;
  743. begin
  744. Size:=0; Lines^.FirstThat(@CountSize);
  745. T^.TextSize:=Size; GetMem(T^.Text,T^.TextSize);
  746. CurPtr:=0;
  747. for I:=0 to Lines^.Count-1 do
  748. begin
  749. S:=Lines^.At(I)^;
  750. Size:=length(S)+1; S[Size]:=hscLineBreak;
  751. Move(S[1],PByteArray(T^.Text)^[CurPtr],Size);
  752. Inc(CurPtr,Size);
  753. if CurPtr>=T^.TextSize then Break;
  754. end;
  755. end;
  756. var Line: string;
  757. procedure FlushLine;
  758. begin
  759. if Line<>'' then AddLine(Line); Line:='';
  760. end;
  761. var KWCount,NLFlag: integer;
  762. LastFirstChar: char;
  763. procedure NewSection(FirstChar: char);
  764. begin
  765. if FirstChar<=#64 then FirstChar:=#32;
  766. FlushLine;
  767. AddLine('');
  768. AddLine(FirstChar);
  769. AddLine('');
  770. LastFirstChar:=FirstChar;
  771. NLFlag:=0;
  772. end;
  773. procedure AddKeyword(KWS: string);
  774. begin
  775. Inc(KWCount); if KWCount=1 then NLFlag:=0;
  776. if (KWCount=1) or
  777. ( (Upcase(KWS[1])<>LastFirstChar) and ( (LastFirstChar>#64) or (KWS[1]>#64) ) ) then
  778. NewSection(Upcase(KWS[1]));
  779. if (NLFlag mod 2)=0
  780. then Line:=' '+#2+KWS+#2
  781. else begin
  782. Line:=RExpand(Line,IndexTabSize)+#2+KWS+#2;
  783. FlushLine;
  784. end;
  785. Inc(NLFlag);
  786. end;
  787. var KW: PIndexEntry;
  788. I: integer;
  789. begin
  790. New(Keywords, Init(5000,1000));
  791. HelpFiles^.ForEach(@InsertKeywordsOfFile);
  792. New(Lines, Init((Keywords^.Count div 2)+100,100));
  793. T:=NewTopic(0,0,0);
  794. if HelpFiles^.Count=0 then
  795. begin
  796. AddLine('');
  797. AddLine(' No help files installed.')
  798. end else
  799. begin
  800. AddLine(' Help index');
  801. KWCount:=0; Line:='';
  802. T^.LinkCount:=Keywords^.Count;
  803. T^.LinkSize:=T^.LinkCount*SizeOf(T^.Links^[0]);
  804. GetMem(T^.Links,T^.LinkSize);
  805. for I:=0 to Keywords^.Count-1 do
  806. begin
  807. KW:=Keywords^.At(I);
  808. AddKeyword(KW^.Tag^);
  809. T^.Links^[I].Context:=KW^.HelpCtx; T^.Links^[I].FileID:=KW^.FileID;
  810. end;
  811. FlushLine;
  812. AddLine('');
  813. end;
  814. RenderTopic;
  815. Dispose(Lines, Done);
  816. Keywords^.DeleteAll; Dispose(Keywords, Done);
  817. BuildIndexTopic:=T;
  818. end;
  819. function THelpFacility.SearchFile(ID: byte): PHelpFile;
  820. function Match(P: PHelpFile): boolean; {$ifndef FPC}far;{$endif}
  821. begin
  822. Match:=(P^.ID=ID);
  823. end;
  824. begin
  825. SearchFile:=HelpFiles^.FirstThat(@Match);
  826. end;
  827. function THelpFacility.SearchTopicInHelpFile(F: PHelpFile; Context: THelpCtx): PTopic;
  828. var P: PTopic;
  829. begin
  830. if F=nil then P:=nil else
  831. P:=F^.SearchTopic(Context);
  832. SearchTopicInHelpFile:=P;
  833. end;
  834. destructor THelpFacility.Done;
  835. begin
  836. inherited Done;
  837. Dispose(HelpFiles, Done);
  838. end;
  839. END.
  840. {
  841. $Log$
  842. Revision 1.3 1999-02-08 10:37:46 peter
  843. + html helpviewer
  844. Revision 1.2 1998/12/28 15:47:56 peter
  845. + Added user screen support, display & window
  846. + Implemented Editor,Mouse Options dialog
  847. + Added location of .INI and .CFG file
  848. + Option (INI) file managment implemented (see bottom of Options Menu)
  849. + Switches updated
  850. + Run program
  851. Revision 1.4 1998/12/22 10:39:55 peter
  852. + options are now written/read
  853. + find and replace routines
  854. }