whtmlhlp.pas 15 KB

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