wnghelp.pas 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. {
  2. This file is part of the Free Pascal Integrated Development Environment
  3. Copyright (c) 2000 by Berczi Gabor
  4. Help support for Norton Guide (.NG) files
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. {$R-}
  12. {$H-}
  13. unit WNGHelp;
  14. {$ifdef cpullvm}
  15. {$modeswitch nestedprocvars}
  16. {$endif}
  17. interface
  18. uses Objects,
  19. WUtils,WHelp;
  20. const
  21. NGFileSignature = 'NG';
  22. NGXORByte = $1a;
  23. NGMinRecordSize = $1a;
  24. ng_rtContainer = Byte ($0);
  25. ng_rtTopic = Byte ($1);
  26. type
  27. TNGFileHeader = packed record
  28. Signature : array[1..2] of AnsiChar;
  29. Unknown : word;
  30. Version : word;
  31. MenuCount : word;
  32. GuideName : array[8..47] of AnsiChar;
  33. Credits : array[48..377] of AnsiChar;
  34. end;
  35. TNGRecordHeader = packed record
  36. RecType : word;
  37. RecLength : word;
  38. end;
  39. TNGContainerItem = packed record
  40. EntryNameOfs : word; { relative in record }
  41. SubItemsOfs : longint; { file offset to a new record header }
  42. end;
  43. PNGContainerRecord = ^TNGContainerRecord;
  44. TNGContainerRecord = packed record
  45. ItemCount : word;
  46. Unknown : word;
  47. IndexInParent : integer;
  48. ParentOfs : longint;
  49. MenuNo : integer;{ belongs to menu # }
  50. MenuItemNo : integer;{ belongs to menu item # }
  51. Unknown2 : array[18..25] of byte;
  52. Items : array[0..0] of TNGContainerItem;
  53. end;
  54. TNGTopicRecord = packed record
  55. NumberOfLines : word;
  56. SeeAlsoOfs : word;
  57. IndexInParent : integer;
  58. ParentOfs : longint;
  59. MenuNo : integer;{ belongs to menu # }
  60. MenuItemNo : integer;{ belongs to menu item # }
  61. PrevTopicOfs : longint;
  62. NextTopicOfs : longint;
  63. TopicLines : record end;
  64. { array of TNGSeeAlsoRec }
  65. end;
  66. PNGSeeAlsoRec = ^TNGSeeAlsoRec;
  67. TNGSeeAlsoRec = packed record
  68. EntryCount : word;
  69. Entries : record end;
  70. { array of LinkedRecOfs : longint; }
  71. { array of LinkNames : ASCIIZ; }
  72. end;
  73. PContainerItemRec = ^TContainerItemRec;
  74. TContainerItemRec = record
  75. Name : string;
  76. FilePos : longint;
  77. Container: PNGContainerRecord;
  78. end;
  79. PLinkRec = ^TLinkRec;
  80. TLinkRec = record
  81. Name : string;
  82. FilePos : longint;
  83. end;
  84. PNGHelpFile = ^TNGHelpFile;
  85. TNGHelpFile = object(THelpFile)
  86. constructor Init(AFileName: string; AID: word);
  87. destructor Done; virtual;
  88. public
  89. function LoadIndex: boolean; virtual;
  90. function ReadTopic(T: PTopic): boolean; virtual;
  91. private
  92. F: PStream;
  93. Header: TNGFileHeader;
  94. FirstRecordPos: longint;
  95. IndexLoaded: boolean;
  96. { NextHelpCtx: longint;}
  97. function ReadHeader: boolean;
  98. function ReadContainer(EnumProc: TCallbackProcParam): boolean;
  99. function ReadTopicRec(LineEnumProc: TCallbackProcParam; LinkEnumProc: TCallbackProcParam): boolean;
  100. function ReadRecord(var R: TRecord; ReadData: boolean): boolean;
  101. end;
  102. TNGGetAttrColorProc = function(Attr: AnsiChar; var Color: byte): boolean;
  103. function DefNGGetAttrColor(Attr: AnsiChar; var Color: byte): boolean;
  104. const NGGetAttrColor : TNGGetAttrColorProc = {$ifdef fpc}@{$endif}DefNGGetAttrColor;
  105. procedure RegisterHelpType;
  106. implementation
  107. function DefNGGetAttrColor(Attr: AnsiChar; var Color: byte): boolean;
  108. begin
  109. DefNGGetAttrColor:=false;
  110. end;
  111. function NGDecompressStr(const S: string): string;
  112. var NS: string;
  113. I: sw_integer;
  114. begin
  115. NS:='';
  116. I:=1;
  117. while (I<=length(S)) do
  118. begin
  119. if S[I]=#255 then
  120. begin
  121. NS:=NS+CharStr(' ',ord(S[I+1]));
  122. Inc(I);
  123. end
  124. else
  125. NS:=NS+S[I];
  126. Inc(I);
  127. end;
  128. NGDecompressStr:=NS;
  129. end;
  130. function TranslateStr(const S: string): string;
  131. var NS: string;
  132. I: sw_integer;
  133. InHiLite: boolean;
  134. Color: byte;
  135. begin
  136. NS:=''; InHiLite:=false;
  137. I:=1;
  138. while (I<=length(S)) do
  139. begin
  140. case S[I] of
  141. '^' : begin
  142. Inc(I);
  143. case S[I] of
  144. '^' : NS:=NS+'^';
  145. 'A'..'Z',
  146. 'a'..'z' :
  147. begin
  148. if InHiLite then
  149. NS:=NS+hscNormText
  150. else
  151. if NGGetAttrColor(S[I],Color) then
  152. NS:=NS+hscTextColor+chr(Color);
  153. InHiLite:=not InHiLite;
  154. end;
  155. else
  156. NS:=NS;
  157. end;
  158. end;
  159. else NS:=NS+S[I];
  160. end;
  161. Inc(I);
  162. end;
  163. if InHiLite then NS:=NS+hscNormText;
  164. TranslateStr:=NS;
  165. end;
  166. procedure TranslateLines(P: PUnsortedStringCollection);
  167. var S: string;
  168. I: sw_integer;
  169. begin
  170. for I:=0 to P^.Count-1 do
  171. begin
  172. S:=GetStr(P^.At(I));
  173. P^.AtFree(I);
  174. P^.AtInsert(I,NewStr(TranslateStr(S)));
  175. end;
  176. end;
  177. constructor TNGHelpFile.Init(AFileName: string; AID: word);
  178. var OK: boolean;
  179. begin
  180. if inherited Init(AID)=false then Fail;
  181. F:=New(PFastBufStream, Init(AFileName, stOpenRead, HelpStreamBufSize));
  182. OK:=F<>nil;
  183. if OK then OK:=(F^.Status=stOK);
  184. if OK then
  185. begin
  186. OK:=ReadHeader;
  187. if OK then
  188. FirstRecordPos:=F^.GetPos;
  189. end;
  190. if OK=false then
  191. begin
  192. Done;
  193. Fail;
  194. end;
  195. end;
  196. function TNGHelpFile.ReadHeader: boolean;
  197. var OK: boolean;
  198. begin
  199. F^.Read(Header,sizeof(Header));
  200. OK:=(F^.Status=stOK);
  201. OK:=OK and (Header.Signature=NGFileSignature);
  202. ReadHeader:=OK;
  203. end;
  204. function TNGHelpFile.ReadContainer(EnumProc: TCallbackProcParam): boolean;
  205. var OK: boolean;
  206. R: TRecord;
  207. I: longint;
  208. P: pointer;
  209. CIR: TContainerItemRec;
  210. begin
  211. OK:=ReadRecord(R, true);
  212. if OK then
  213. with TNGContainerRecord(R.Data^) do
  214. begin
  215. I:=0;
  216. while (I<ItemCount) do
  217. with Items[I] do
  218. begin
  219. P:=@(PByteArray(R.Data)^[NGMinRecordSize-sizeof(TNGRecordHeader)+EntryNameOfs]);
  220. FillChar(CIR,sizeof(CIR),0);
  221. with CIR do
  222. begin
  223. Container:=R.Data;
  224. Name:=NGDecompressStr(StrPas(P));
  225. FilePos:=SubItemsOfs;
  226. end;
  227. CallPointerLocal(EnumProc,get_caller_frame(get_frame,get_pc_addr),@CIR);
  228. Inc(I);
  229. end;
  230. end;
  231. DisposeRecord(R);
  232. ReadContainer:=OK;
  233. end;
  234. function TNGHelpFile.ReadTopicRec(LineEnumProc, LinkEnumProc: TCallbackProcParam): boolean;
  235. var OK: boolean;
  236. R: TRecord;
  237. I: sw_integer;
  238. LineP: pointer;
  239. S: string;
  240. ParamS: string;
  241. NextLinkOfsPtr,NextLinkNamePtr: pointer;
  242. SeeAlso: PNGSeeAlsoRec;
  243. LR: TLinkRec;
  244. begin
  245. OK:=ReadRecord(R, true);
  246. if OK then
  247. with TNGTopicRecord(R.Data^) do
  248. begin
  249. LineP:=@TopicLines;
  250. if Assigned(LineEnumProc) then
  251. for I:=1 to NumberOfLines do
  252. begin
  253. S:=StrPas(LineP);
  254. ParamS:=NGDecompressStr(S);
  255. CallPointerLocal(LineEnumProc,get_caller_frame(get_frame,get_pc_addr),@ParamS);
  256. Inc(Ptrint(LineP),length(S)+1);
  257. end;
  258. if Assigned(LinkEnumProc) and (SeeAlsoOfs>0) then
  259. begin
  260. SeeAlso:=@PByteArray(R.Data)^[NGMinRecordSize-sizeof(TNGRecordHeader)+SeeAlsoOfs];
  261. NextLinkOfsPtr:=@SeeAlso^.Entries;
  262. NextLinkNamePtr:=@PByteArray(NextLinkOfsPtr)^[SeeAlso^.EntryCount*4];
  263. for I:=1 to SeeAlso^.EntryCount do
  264. begin
  265. FillChar(LR,sizeof(LR),0);
  266. S:=StrPas(NextLinkNamePtr);
  267. LR.Name:=S;
  268. Move(NextLinkOfsPtr^,LR.FilePos,4);
  269. CallPointerLocal(LinkEnumProc,get_caller_frame(get_frame,get_pc_addr),@LR);
  270. Inc(Ptrint(NextLinkNamePtr),length(S)+1);
  271. Inc(Ptrint(NextLinkOfsPtr),4);
  272. end;
  273. end;
  274. end;
  275. DisposeRecord(R);
  276. ReadTopicRec:=OK;
  277. end;
  278. function TNGHelpFile.ReadRecord(var R: TRecord; ReadData: boolean): boolean;
  279. var OK: boolean;
  280. H: TNGRecordHeader;
  281. I: sw_integer;
  282. begin
  283. FillChar(R, SizeOf(R), 0);
  284. F^.Read(H,SizeOf(H));
  285. OK:=F^.Status=stOK;
  286. if OK then
  287. for I:=0 to SizeOf(H)-1 do
  288. PByteArray(@H)^[I]:=PByteArray(@H)^[I] xor NGXORByte;
  289. if OK then
  290. begin
  291. R.SClass:=H.RecType; R.Size:=H.RecLength+(NGMinRecordSize-sizeof(TNGRecordHeader));
  292. if (R.Size>0) and ReadData then
  293. begin
  294. GetMem(R.Data,R.Size);
  295. F^.Read(R.Data^,R.Size);
  296. if R.Size>0 then
  297. for I:=0 to R.Size-1 do
  298. PByteArray(R.Data)^[I]:=PByteArray(R.Data)^[I] xor NGXORByte;
  299. OK:=F^.Status=stOK;
  300. end;
  301. if OK=false then DisposeRecord(R);
  302. end;
  303. ReadRecord:=OK;
  304. end;
  305. function TNGHelpFile.LoadIndex: boolean;
  306. {function FormatAlias(Alias: string): string;
  307. var StartP,EndP: sw_integer;
  308. begin
  309. repeat
  310. StartP:=Pos(' ',Alias);
  311. if StartP>0 then
  312. begin
  313. EndP:=StartP;
  314. while (EndP+1<=length(Alias)) and (Alias[EndP+1]=' ') do Inc(EndP);
  315. Alias:=copy(Alias,1,StartP-1)+' | '+copy(Alias,EndP+1,High(Alias));
  316. end;
  317. until StartP=0;
  318. if Assigned(HelpFacility) then
  319. if length(Alias)>HelpFacility^.IndexTabSize-4 then
  320. Alias:=Trim(copy(Alias,1,HelpFacility^.IndexTabSize-4-2))+'..';
  321. FormatAlias:=Alias;
  322. end;}
  323. procedure AddToIndex(P: PContainerItemRec);
  324. var S: string;
  325. begin
  326. S:=Trim(P^.Name);
  327. S:=TranslateStr(S);
  328. S:=Trim({FormatAlias}(S));
  329. if (S<>'') and (P^.FilePos<>-1) then
  330. begin
  331. { Inc(NextHelpCtx);}
  332. AddIndexEntry(S,P^.FilePos);
  333. AddTopic(P^.FilePos,P^.FilePos,'',nil,0);
  334. end;
  335. end;
  336. var OK: boolean;
  337. FS: longint;
  338. R: TRecord;
  339. L: longint;
  340. begin
  341. if IndexLoaded then OK:=true else
  342. begin
  343. FS:=F^.GetSize;
  344. OK:=FirstRecordPos<>0;
  345. while OK do
  346. begin
  347. L:=F^.GetPos;
  348. if (L>=FS) then Break;
  349. OK:=ReadRecord(R,false);
  350. if (OK=false) then Break;
  351. case R.SClass of
  352. ng_rtContainer : begin F^.Seek(L); OK:=ReadContainer(TCallbackProcParam(@AddToIndex)); end;
  353. ng_rtTopic : ;
  354. else
  355. begin
  356. {$ifdef DEBUGMSG}
  357. ClearFormatParams;
  358. AddFormatParamInt(R.SClass);
  359. AddFormatParamInt(L);
  360. AddFormatParamInt(R.Size);
  361. ErrorBox('Uknown help record tag %x encountered, '+
  362. 'offset %x, size %d',@FormatParams);
  363. {$else}
  364. {Skip};
  365. {$endif}
  366. end;
  367. end;
  368. if OK then
  369. begin
  370. Inc(L, sizeof(TNGRecordHeader)+R.Size); F^.Seek(L);
  371. OK:=(F^.Status=stOK);
  372. end;
  373. end;
  374. IndexLoaded:=OK;
  375. end;
  376. LoadIndex:=OK;
  377. end;
  378. function TNGHelpFile.ReadTopic(T: PTopic): boolean;
  379. function ExtractStr(var Buf; BufSize: word): string;
  380. var S: string;
  381. I,Size: integer;
  382. StartP,EndP: sw_integer;
  383. begin
  384. S:='';
  385. Size:=BufSize;
  386. while (PByteArray(@Buf)^[Size-1]=0) and (Size>0) do
  387. Dec(Size);
  388. S:=MemToStr(Buf,Size);
  389. S:=NGDecompressStr(S);
  390. for I:=1 to length(S) do
  391. if S[I]=#0 then S[I]:=#32;
  392. { kill long spaces }
  393. repeat
  394. StartP:=Pos(' ',S);
  395. if StartP>0 then
  396. begin
  397. EndP:=StartP;
  398. while (EndP+1<=length(S)) and (S[EndP+1]=' ') do Inc(EndP);
  399. S:=copy(S,1,StartP-1)+hscLineBreak+copy(S,EndP+1,High(S));
  400. end;
  401. until StartP=0;
  402. S:=Trim(S);
  403. ExtractStr:=S;
  404. end;
  405. var Lines: PUnsortedStringCollection;
  406. procedure AddLine(const S: string);
  407. begin
  408. Lines^.InsertStr(S);
  409. end;
  410. procedure AddToTopic(P: PContainerItemRec);
  411. begin
  412. AddLine(hscLink+Trim(P^.Name)+hscLink);
  413. AddLinkToTopic(T,ID,P^.FilePos);
  414. end;
  415. procedure AddTopicLine(P: PString);
  416. begin
  417. AddLine(' '+GetStr(P));
  418. end;
  419. var LinkCount: sw_integer;
  420. procedure AddLink(P: PLinkRec);
  421. begin
  422. Inc(LinkCount);
  423. if LinkCount=1 then
  424. begin
  425. AddLine('');
  426. AddLine(' See also :');
  427. end;
  428. AddLine(' '+hscLink+Trim(P^.Name)+hscLink);
  429. AddLinkToTopic(T,ID,P^.FilePos);
  430. end;
  431. var OK: boolean;
  432. R: TRecord;
  433. begin
  434. LinkCount:=0;
  435. New(Lines, Init(100,100));
  436. F^.Seek(T^.FileOfs); OK:=F^.Status=stOK;
  437. if OK then OK:=ReadRecord(R,false);
  438. case R.SClass of
  439. ng_rtContainer :
  440. begin
  441. F^.Seek(T^.FileOfs);
  442. AddLine('');
  443. OK:=ReadContainer(TCallbackProcParam(@AddToTopic));
  444. RenderTopic(Lines,T);
  445. end;
  446. ng_rtTopic :
  447. begin
  448. F^.Seek(T^.FileOfs);
  449. AddLine('');
  450. OK:=ReadTopicRec(TCallbackProcParam(@AddTopicLine),TCallbackProcParam(@AddLink));
  451. TranslateLines(Lines);
  452. AddLine('');
  453. { include copyright info }
  454. { AddLine(CharStr('Ä',80));
  455. AddLine(ExtractStr(Header.GuideName,sizeof(Header.GuideName)));
  456. AddLine(ExtractStr(Header.Credits,sizeof(Header.Credits)));}
  457. RenderTopic(Lines,T);
  458. end;
  459. else OK:=false;
  460. end;
  461. Dispose(Lines, Done);
  462. ReadTopic:=OK;
  463. end;
  464. destructor TNGHelpFile.Done;
  465. begin
  466. if Assigned(F) then Dispose(F, Done); F:=nil;
  467. inherited Done;
  468. end;
  469. function CreateProc(const FileName,Param: string;Index : longint): PHelpFile;
  470. begin
  471. CreateProc:=New(PNGHelpFile, Init(FileName,Index));
  472. end;
  473. procedure RegisterHelpType;
  474. begin
  475. RegisterHelpFileType({$ifdef FPC}@{$endif}CreateProc);
  476. end;
  477. END.