whtmlhlp.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. unit WHTMLHlp;
  2. interface
  3. uses Objects,WHTML,WHelp;
  4. const
  5. ListIndent = 2;
  6. DefIndent = 4;
  7. MaxTopicLinks = 500;
  8. type
  9. PTopicLinkCollection = ^TTopicLinkCollection;
  10. TTopicLinkCollection = object(TStringCollection)
  11. procedure Insert(Item: Pointer); virtual;
  12. function At(Index: sw_Integer): PString;
  13. function AddItem(Item: string): integer;
  14. end;
  15. TParagraphAlign = (paLeft,paCenter,paRight);
  16. PHTMLTopicRenderer = ^THTMLTopicRenderer;
  17. THTMLTopicRenderer = object(THTMLParser)
  18. function BuildTopic(P: PTopic; AURL: string; HTMLFile: PTextFile; ATopicLinks: PTopicLinkCollection): boolean;
  19. public
  20. function DocAddTextChar(C: char): boolean; virtual;
  21. procedure DocSoftBreak; virtual;
  22. procedure DocTYPE; virtual;
  23. procedure DocHTML(Entered: boolean); virtual;
  24. procedure DocHEAD(Entered: boolean); virtual;
  25. procedure DocMETA; virtual;
  26. procedure DocTITLE(Entered: boolean); virtual;
  27. procedure DocBODY(Entered: boolean); virtual;
  28. procedure DocAnchor(Entered: boolean); virtual;
  29. procedure DocHeading(Level: integer; Entered: boolean); virtual;
  30. procedure DocParagraph(Entered: boolean); virtual;
  31. procedure DocBreak; virtual;
  32. procedure DocImage; virtual;
  33. procedure DocBold(Entered: boolean); virtual;
  34. procedure DocCite(Entered: boolean); virtual;
  35. procedure DocCode(Entered: boolean); virtual;
  36. procedure DocEmphasized(Entered: boolean); virtual;
  37. procedure DocItalic(Entered: boolean); virtual;
  38. procedure DocKbd(Entered: boolean); virtual;
  39. procedure DocPreformatted(Entered: boolean); virtual;
  40. procedure DocSample(Entered: boolean); virtual;
  41. procedure DocStrong(Entered: boolean); virtual;
  42. procedure DocTeleType(Entered: boolean); virtual;
  43. procedure DocVariable(Entered: boolean); virtual;
  44. procedure DocList(Entered: boolean); virtual;
  45. procedure DocOrderedList(Entered: boolean); virtual;
  46. procedure DocListItem; virtual;
  47. procedure DocDefList(Entered: boolean); virtual;
  48. procedure DocDefTerm; virtual;
  49. procedure DocDefExp; virtual;
  50. procedure DocHorizontalRuler; virtual;
  51. private
  52. URL: string;
  53. Topic: PTopic;
  54. TopicLinks: PTopicLinkCollection;
  55. TextPtr: sw_word;
  56. InTitle: boolean;
  57. InBody: boolean;
  58. InAnchor: boolean;
  59. InParagraph: boolean;
  60. InPreformatted: boolean;
  61. TopicTitle: string;
  62. Indent: integer;
  63. AnyCharsInLine: boolean;
  64. CurHeadLevel: integer;
  65. PAlign: TParagraphAlign;
  66. LinkIndexes: array[0..MaxTopicLinks] of sw_integer;
  67. LinkPtr: sw_integer;
  68. LastTextChar: char;
  69. { Anchor: TAnchor;}
  70. procedure AddText(S: string);
  71. procedure AddChar(C: char);
  72. end;
  73. PHTMLHelpFile = ^THTMLHelpFile;
  74. THTMLHelpFile = object(THelpFile)
  75. constructor Init(AFileName: string; AID: word; ATOCEntry: string);
  76. destructor Done; virtual;
  77. public
  78. function LoadIndex: boolean; virtual;
  79. function SearchTopic(HelpCtx: THelpCtx): PTopic; virtual;
  80. function ReadTopic(T: PTopic): boolean; virtual;
  81. private
  82. Renderer: PHTMLTopicRenderer;
  83. FileName: string;
  84. CurFileName: string;
  85. TOCEntry: string;
  86. TopicLinks: PTopicLinkCollection;
  87. end;
  88. implementation
  89. uses WUtils,
  90. Dos;
  91. const
  92. {$ifdef LINUX}
  93. dirsep = '/';
  94. {$else}
  95. dirsep = '\';
  96. {$endif}
  97. function FormatPath(Path: string): string;
  98. var P: sw_integer;
  99. SC: char;
  100. begin
  101. if ord(DirSep)=ord('/') then
  102. SC:='\'
  103. else
  104. SC:='/';
  105. repeat
  106. P:=Pos(SC,Path);
  107. if P>0 then Path[P]:=DirSep;
  108. until P=0;
  109. FormatPath:=Path;
  110. end;
  111. function CompletePath(const Base, InComplete: string): string;
  112. var Drv,BDrv: string[40]; D,BD: DirStr; N,BN: NameStr; E,BE: ExtStr;
  113. P: sw_integer;
  114. Complete: string;
  115. begin
  116. Complete:=FormatPath(InComplete);
  117. FSplit(FormatPath(InComplete),D,N,E);
  118. P:=Pos(':',D); if P=0 then Drv:='' else begin Drv:=copy(D,1,P); Delete(D,1,P); end;
  119. FSplit(FormatPath(Base),BD,BN,BE);
  120. P:=Pos(':',BD); if P=0 then BDrv:='' else begin BDrv:=copy(BD,1,P); Delete(BD,1,P); end;
  121. if copy(D,1,1)<>'\' then
  122. Complete:=BD+D+N+E;
  123. if Drv='' then
  124. Complete:=BDrv+Complete;
  125. Complete:=FExpand(Complete);
  126. CompletePath:=Complete;
  127. end;
  128. function CompleteURL(const Base, URLRef: string): string;
  129. var P: integer;
  130. Drive: string[20];
  131. IsComplete: boolean;
  132. S: string;
  133. begin
  134. IsComplete:=false;
  135. P:=Pos(':',URLRef);
  136. if P=0 then Drive:='' else Drive:=UpcaseStr(copy(URLRef,1,P-1));
  137. if Drive<>'' then
  138. if (Drive='MAILTO') or (Drive='FTP') or (Drive='HTTP') or (Drive='GOPHER') then
  139. IsComplete:=true;
  140. if IsComplete then S:=URLRef else
  141. S:=CompletePath(Base,URLRef);
  142. CompleteURL:=S;
  143. end;
  144. function EncodeHTMLCtx(FileID: integer; LinkNo: word): longint;
  145. var Ctx: longint;
  146. begin
  147. Ctx:=(longint(FileID) shl 16)+LinkNo;
  148. EncodeHTMLCtx:=Ctx;
  149. end;
  150. procedure DecodeHTMLCtx(Ctx: longint; var FileID: word; var LinkNo: word);
  151. begin
  152. if (Ctx shr 16)=0 then
  153. begin
  154. FileID:=$ffff; LinkNo:=0;
  155. end
  156. else
  157. begin
  158. FileID:=Ctx shr 16; LinkNo:=Ctx and $ffff;
  159. end;
  160. end;
  161. function CharStr(C: char; Count: byte): string;
  162. var S: string;
  163. begin
  164. S[0]:=chr(Count);
  165. if Count>0 then FillChar(S[1],Count,C);
  166. CharStr:=S;
  167. end;
  168. procedure TTopicLinkCollection.Insert(Item: Pointer);
  169. begin
  170. AtInsert(Count,Item);
  171. end;
  172. function TTopicLinkCollection.At(Index: sw_Integer): PString;
  173. begin
  174. At:=inherited At(Index);
  175. end;
  176. function TTopicLinkCollection.AddItem(Item: string): integer;
  177. var Idx: sw_integer;
  178. begin
  179. if Item='' then Idx:=-1 else
  180. if Search(@Item,Idx)=false then
  181. begin
  182. AtInsert(Count,NewStr(Item));
  183. Idx:=Count-1;
  184. end;
  185. AddItem:=Idx;
  186. end;
  187. function THTMLTopicRenderer.DocAddTextChar(C: char): boolean;
  188. var Added: boolean;
  189. begin
  190. Added:=false;
  191. if InTitle then
  192. begin
  193. TopicTitle:=TopicTitle+C;
  194. Added:=true;
  195. end
  196. else
  197. if InBody then
  198. begin
  199. if (InPreFormatted) or (C<>#32) or (LastTextChar<>C) then
  200. if (C<>#32) or (AnyCharsInLine=true) or (InPreFormatted=true) then
  201. begin
  202. AddChar(C);
  203. LastTextChar:=C;
  204. Added:=true;
  205. end;
  206. end;
  207. DocAddTextChar:=Added;
  208. end;
  209. procedure THTMLTopicRenderer.DocSoftBreak;
  210. begin
  211. if InPreformatted then DocBreak else
  212. if AnyCharsInLine then
  213. begin
  214. AddChar(' ');
  215. LastTextChar:=' ';
  216. end;
  217. end;
  218. procedure THTMLTopicRenderer.DocTYPE;
  219. begin
  220. end;
  221. procedure THTMLTopicRenderer.DocHTML(Entered: boolean);
  222. begin
  223. end;
  224. procedure THTMLTopicRenderer.DocHEAD(Entered: boolean);
  225. begin
  226. end;
  227. procedure THTMLTopicRenderer.DocMETA;
  228. begin
  229. end;
  230. procedure THTMLTopicRenderer.DocTITLE(Entered: boolean);
  231. begin
  232. if Entered then
  233. begin
  234. TopicTitle:='';
  235. end
  236. else
  237. begin
  238. { render topic title here }
  239. if TopicTitle<>'' then
  240. begin
  241. AddText(' '+TopicTitle+' Ü'); DocBreak;
  242. AddText(' '+CharStr('ß',length(TopicTitle)+3)); DocBreak;
  243. end;
  244. end;
  245. InTitle:=Entered;
  246. end;
  247. procedure THTMLTopicRenderer.DocBODY(Entered: boolean);
  248. begin
  249. InBody:=Entered;
  250. end;
  251. procedure THTMLTopicRenderer.DocAnchor(Entered: boolean);
  252. var HRef: string;
  253. begin
  254. if Entered and InAnchor then DocAnchor(false);
  255. if Entered then
  256. begin
  257. if DocGetTagParam('HREF',HRef)=false then HRef:='';
  258. if (HRef<>'') and (copy(HRef,1,1)<>'#') then
  259. begin
  260. InAnchor:=true;
  261. AddChar(hscLink);
  262. HRef:=CompleteURL(URL,HRef);
  263. LinkIndexes[LinkPtr]:=TopicLinks^.AddItem(HRef);
  264. Inc(LinkPtr);
  265. end;
  266. end
  267. else
  268. begin
  269. if InAnchor=true then AddChar(hscLink);
  270. InAnchor:=false;
  271. end;
  272. end;
  273. procedure DecodeAlign(Align: string; var PAlign: TParagraphAlign);
  274. begin
  275. Align:=UpcaseStr(Align);
  276. if Align='LEFT' then PAlign:=paLeft else
  277. if Align='CENTER' then PAlign:=paCenter else
  278. if Align='RIGHT' then PAlign:=paRight;
  279. end;
  280. procedure THTMLTopicRenderer.DocHeading(Level: integer; Entered: boolean);
  281. var Align: string;
  282. begin
  283. if Entered then
  284. begin
  285. DocBreak;
  286. CurHeadLevel:=Level;
  287. PAlign:=paLeft;
  288. if DocGetTagParam('ALIGN',Align) then
  289. DecodeAlign(Align,PAlign);
  290. end
  291. else
  292. begin
  293. CurHeadLevel:=0;
  294. DocBreak;
  295. end;
  296. end;
  297. procedure THTMLTopicRenderer.DocParagraph(Entered: boolean);
  298. var Align: string;
  299. begin
  300. if Entered and InParagraph then DocParagraph(false);
  301. if Entered then
  302. begin
  303. if AnyCharsInLine then DocBreak;
  304. if DocGetTagParam('ALIGN',Align) then
  305. DecodeAlign(Align,PAlign);
  306. end
  307. else
  308. begin
  309. { if AnyCharsInLine then }DocBreak;
  310. PAlign:=paLeft;
  311. end;
  312. InParagraph:=Entered;
  313. end;
  314. procedure THTMLTopicRenderer.DocBreak;
  315. begin
  316. if (CurHeadLevel=1) or (PAlign=paCenter) then
  317. AddChar(hscCenter);
  318. if (PAlign=paRight) then
  319. AddChar(hscRight);
  320. AddChar(hscLineBreak);
  321. if Indent>0 then
  322. AddText(CharStr(#255,Indent)+hscLineStart);
  323. AnyCharsInLine:=false;
  324. end;
  325. procedure THTMLTopicRenderer.DocImage;
  326. var Alt: string;
  327. begin
  328. if DocGetTagParam('ALT',Alt)=false then Alt:='IMG';
  329. if Alt<>'' then
  330. begin
  331. AddText('['+Alt+']');
  332. end;
  333. end;
  334. procedure THTMLTopicRenderer.DocBold(Entered: boolean);
  335. begin
  336. end;
  337. procedure THTMLTopicRenderer.DocCite(Entered: boolean);
  338. begin
  339. end;
  340. procedure THTMLTopicRenderer.DocCode(Entered: boolean);
  341. begin
  342. end;
  343. procedure THTMLTopicRenderer.DocEmphasized(Entered: boolean);
  344. begin
  345. end;
  346. procedure THTMLTopicRenderer.DocItalic(Entered: boolean);
  347. begin
  348. end;
  349. procedure THTMLTopicRenderer.DocKbd(Entered: boolean);
  350. begin
  351. end;
  352. procedure THTMLTopicRenderer.DocPreformatted(Entered: boolean);
  353. begin
  354. if AnyCharsInLine then DocBreak;
  355. DocBreak;
  356. InPreformatted:=Entered;
  357. end;
  358. procedure THTMLTopicRenderer.DocSample(Entered: boolean);
  359. begin
  360. end;
  361. procedure THTMLTopicRenderer.DocStrong(Entered: boolean);
  362. begin
  363. end;
  364. procedure THTMLTopicRenderer.DocTeleType(Entered: boolean);
  365. begin
  366. end;
  367. procedure THTMLTopicRenderer.DocVariable(Entered: boolean);
  368. begin
  369. end;
  370. procedure THTMLTopicRenderer.DocList(Entered: boolean);
  371. begin
  372. if Entered then
  373. begin
  374. Inc(Indent,ListIndent);
  375. DocBreak;
  376. end
  377. else
  378. begin
  379. Dec(Indent,ListIndent);
  380. if AnyCharsInLine then DocBreak;
  381. end;
  382. end;
  383. procedure THTMLTopicRenderer.DocOrderedList(Entered: boolean);
  384. begin
  385. DocList(Entered);
  386. end;
  387. procedure THTMLTopicRenderer.DocListItem;
  388. begin
  389. if AnyCharsInLine then
  390. DocBreak;
  391. AddText('þ'+hscLineStart);
  392. end;
  393. procedure THTMLTopicRenderer.DocDefList(Entered: boolean);
  394. begin
  395. if Entered then
  396. begin
  397. { if LastChar<>hscLineBreak then DocBreak;}
  398. end
  399. else
  400. begin
  401. if AnyCharsInLine then DocBreak;
  402. end;
  403. end;
  404. procedure THTMLTopicRenderer.DocDefTerm;
  405. begin
  406. DocBreak;
  407. end;
  408. procedure THTMLTopicRenderer.DocDefExp;
  409. begin
  410. Inc(Indent,DefIndent);
  411. DocBreak;
  412. Dec(Indent,DefIndent);
  413. end;
  414. procedure THTMLTopicRenderer.DocHorizontalRuler;
  415. var OAlign: TParagraphAlign;
  416. begin
  417. OAlign:=PAlign;
  418. if AnyCharsInLine then DocBreak;
  419. PAlign:=paCenter;
  420. DocAddText(' '+CharStr('Ä',60)+' ');
  421. DocBreak;
  422. PAlign:=OAlign;
  423. end;
  424. procedure THTMLTopicRenderer.AddChar(C: char);
  425. begin
  426. if (Topic=nil) or (TextPtr=MaxBytes) then Exit;
  427. Topic^.Text^[TextPtr]:=ord(C);
  428. Inc(TextPtr);
  429. if (C>#15) and ((C<>' ') or (InPreFormatted=true)) then
  430. AnyCharsInLine:=true;
  431. end;
  432. procedure THTMLTopicRenderer.AddText(S: string);
  433. var I: sw_integer;
  434. begin
  435. for I:=1 to length(S) do
  436. AddChar(S[I]);
  437. end;
  438. function THTMLTopicRenderer.BuildTopic(P: PTopic; AURL: string; HTMLFile: PTextFile;
  439. ATopicLinks: PTopicLinkCollection): boolean;
  440. var OK: boolean;
  441. TP: pointer;
  442. I: sw_integer;
  443. begin
  444. URL:=AURL;
  445. Topic:=P; TopicLinks:=ATopicLinks;
  446. OK:=Assigned(Topic) and Assigned(HTMLFile) and Assigned(TopicLinks);
  447. if OK then
  448. begin
  449. if (Topic^.TextSize<>0) and Assigned(Topic^.Text) then
  450. begin
  451. FreeMem(Topic^.Text,Topic^.TextSize);
  452. Topic^.TextSize:=0; Topic^.Text:=nil;
  453. end;
  454. Topic^.TextSize:=MaxHelpTopicSize;
  455. GetMem(Topic^.Text,Topic^.TextSize);
  456. TopicTitle:='';
  457. InTitle:=false; InBody:={false}true; InAnchor:=false;
  458. InParagraph:=false; InPreformatted:=false;
  459. Indent:=0; CurHeadLevel:=0;
  460. PAlign:=paLeft;
  461. TextPtr:=0; LinkPtr:=0;
  462. AnyCharsInLine:=false;
  463. LastTextChar:=#0;
  464. OK:=Process(HTMLFile);
  465. if OK then
  466. begin
  467. { --- topic links --- }
  468. if (Topic^.Links<>nil) and (Topic^.LinkSize>0) then
  469. begin
  470. FreeMem(Topic^.Links,Topic^.LinkSize);
  471. Topic^.Links:=nil; Topic^.LinkCount:=0;
  472. end;
  473. Topic^.LinkCount:=LinkPtr{TopicLinks^.Count}; { <- eeeeeek! }
  474. GetMem(Topic^.Links,Topic^.LinkSize);
  475. for I:=0 to Topic^.LinkCount-1 do
  476. begin
  477. Topic^.Links^[I].FileID:=Topic^.FileID;
  478. Topic^.Links^[I].Context:=EncodeHTMLCtx(Topic^.FileID,LinkIndexes[I]+1);
  479. end;
  480. { --- topic text --- }
  481. GetMem(TP,TextPtr);
  482. Move(Topic^.Text^,TP^,TextPtr);
  483. FreeMem(Topic^.Text,Topic^.TextSize);
  484. Topic^.Text:=TP; Topic^.TextSize:=TextPtr;
  485. end
  486. else
  487. begin
  488. DisposeTopic(Topic);
  489. Topic:=nil;
  490. end;
  491. end;
  492. BuildTopic:=OK;
  493. end;
  494. constructor THTMLHelpFile.Init(AFileName: string; AID: word; ATOCEntry: string);
  495. begin
  496. inherited Init(AID);
  497. FileName:=AFileName; TOCEntry:=ATOCEntry;
  498. if FileName='' then Fail;
  499. New(Renderer, Init);
  500. New(TopicLinks, Init(50,500));
  501. end;
  502. function THTMLHelpFile.LoadIndex: boolean;
  503. begin
  504. IndexEntries^.Insert(NewIndexEntry(TOCEntry,ID,0));
  505. LoadIndex:=true;
  506. end;
  507. function THTMLHelpFile.SearchTopic(HelpCtx: THelpCtx): PTopic;
  508. function MatchCtx(P: PTopic): boolean; {$ifndef FPC}far;{$endif}
  509. begin
  510. MatchCtx:=P^.HelpCtx=HelpCtx;
  511. end;
  512. var FileID,LinkNo: word;
  513. P: PTopic;
  514. FName: string;
  515. begin
  516. DecodeHTMLCtx(HelpCtx,FileID,LinkNo);
  517. if (HelpCtx<>0) and (FileID<>ID) then P:=nil else
  518. if (FileID=ID) and (LinkNo>TopicLinks^.Count) then P:=nil else
  519. begin
  520. P:=Topics^.FirstThat(@MatchCtx);
  521. if P=nil then
  522. begin
  523. if LinkNo=0 then
  524. FName:=FileName
  525. else
  526. FName:=TopicLinks^.At(LinkNo-1)^;
  527. P:=NewTopic(ID,HelpCtx,0,FName);
  528. Topics^.Insert(P);
  529. end;
  530. end;
  531. SearchTopic:=P;
  532. end;
  533. function THTMLHelpFile.ReadTopic(T: PTopic): boolean;
  534. var OK: boolean;
  535. HTMLFile: PMemoryTextFile;
  536. Name: string;
  537. Link: string;
  538. P: sw_integer;
  539. begin
  540. OK:=T<>nil;
  541. if OK then
  542. begin
  543. if T^.HelpCtx=0 then Name:=FileName else
  544. begin
  545. Link:=TopicLinks^.At((T^.HelpCtx and $ffff)-1)^;
  546. Link:=FormatPath(Link);
  547. P:=Pos('#',Link); if P>0 then Delete(Link,P,255);
  548. { if CurFileName='' then Name:=Link else
  549. Name:=CompletePath(CurFileName,Link);}
  550. Name:=Link;
  551. end;
  552. HTMLFile:=New(PDOSTextFile, Init(Name));
  553. if HTMLFile=nil then
  554. begin
  555. New(HTMLFile, Init);
  556. HTMLFile^.AddLine('<HEAD><TITLE>Page not available</TITLE></HEAD>');
  557. HTMLFile^.AddLine(
  558. '<BODY>'+
  559. 'Sorry, can''t access the URL: '+Name+'... <br><br>'+
  560. '</BODY>');
  561. end;
  562. OK:=Renderer^.BuildTopic(T,Name,HTMLFile,TopicLinks);
  563. if OK then CurFileName:=Name;
  564. if HTMLFile<>nil then Dispose(HTMLFile, Done);
  565. end;
  566. ReadTopic:=OK;
  567. end;
  568. destructor THTMLHelpFile.Done;
  569. begin
  570. inherited Done;
  571. if Renderer<>nil then Dispose(Renderer, Done);
  572. if TopicLinks<>nil then Dispose(TopicLinks, Done);
  573. end;
  574. END.