whelp.pas 26 KB

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