whelp.pas 27 KB

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