wchmhwrap.pas 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. {
  2. This file is part of the Free Pascal Integrated Development Environment
  3. Copyright (c) 2008 by Marco van de Voort
  4. Wrapper for CHM reading to avoid having to import Delphi units into whtmlhlp,
  5. which can cause all kinds of namespace conflicts.
  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. unit wchmhwrap;
  13. interface
  14. {$Mode Delphi}
  15. Uses wutils,whelp,whtml,SysUtils,ChmReader,ChmSiteMap,Classes;
  16. Type
  17. // TopicLinks: PTopicLinkCollection;IndexEntries : PUnsortedIndexEntryCollection;
  18. TChmWrapper = Class
  19. private
  20. ffs : Classes.TFileStream;
  21. fchmr : TChmReader;
  22. findex : TChmSiteMap;
  23. ftopic : TChmSiteMap;
  24. floaded : boolean;
  25. fileid : integer;
  26. fshortname : string;
  27. flongname : string;
  28. fTopicLinks : PTopicLinkCollection;
  29. public
  30. constructor Create(name:String;aid:integer;TopicLinks:PTopicLinkCollection);
  31. function LoadIndex(id:integer;TopicLinks: PTopicLinkCollection;IndexEntries : PUnsortedIndexEntryCollection;helpfacility:PHelpFacility):boolean;
  32. function GetTopic(name:string):PMemoryTextFile;
  33. destructor Destroy;override;
  34. end;
  35. function combinepaths(relpath,basepath:String):String;
  36. function CHMResolve( href: ansistring; var AFileId,ALinkId : longint):boolean;
  37. function stringreplace(const s:ansistring;const oldstr:ansistring; const newstr:ansistring):ansistring;
  38. implementation
  39. var CHMIndex : TStringList; // list to register open CHMs.
  40. function combinepaths(relpath,basepath:String):String;
  41. begin
  42. {$ifdef combinedebug}
  43. debugmessageS({$i %file%},'combine in "'+relpath+'" and "'+basepath+'"',{$i %line%},'1',0,0);
  44. {$endif}
  45. if relpath='' then exit;
  46. if relpath[length(relpath)]<>'/' Then
  47. basepath:=extractfiledir(basepath);
  48. while (length(relpath)>0) and (copy(relpath,1,3)='../') do
  49. begin
  50. basepath:=extractfiledir(basepath);
  51. delete(relpath,1,3);
  52. end;
  53. {$ifdef combinedebug}
  54. debugmessageS({$i %file%},'combine out "'+relpath+'" and "'+basepath+'"',{$i %line%},'1',0,0);
  55. {$endif}
  56. if (length(basepath)>0) and (length(relpath)>0) then
  57. begin
  58. if (relpath[1]<>'/') and (basepath[length(basepath)]<>'/') then
  59. basepath:=basepath+'/';
  60. {$ifdef combinedebug}
  61. debugmessageS({$i %file%},'combine out2 "'+relpath+'" and "'+basepath+'"',{$i %line%},'1',0,0);
  62. {$endif}
  63. end;
  64. result:=basepath+relpath;
  65. end;
  66. Constructor TChmWrapper.Create(name:string;aid:integer;TopicLinks:PTopicLinkCollection);
  67. begin
  68. ffs:=Classes.TFileStream.create(name,fmOpenRead or fmsharedenynone);
  69. fchmr:=TChmReader.Create(ffs,True); // owns ffs
  70. findex:=nil;
  71. FTopicLinks:=TopicLinks;
  72. if not fchmr.isvalidfile then
  73. begin
  74. freeandnil(fchmr);
  75. freeandnil(ffs);
  76. exit;
  77. end;
  78. fileid:=aid;
  79. flongname:=name;
  80. fshortname:=lowercase(extractfilename(name)); // We assume ms-its: urls are case insensitive wrt filename.
  81. chmindex.addobject(fshortname,self);
  82. {$ifdef wdebug}
  83. debugmessageS({$i %file%},'TCHMWrapper.Create: before sitemap creation '+fshortname+' id='+inttostr(aid),{$i %line%},'1',0,0);
  84. {$endif}
  85. findex:=TChmSiteMap.create(stindex);
  86. ftopic:=TChmSiteMap.create(sttoc);
  87. floaded:=false;
  88. end;
  89. function TChmWrapper.LoadIndex(id:integer;TopicLinks: PTopicLinkCollection;IndexEntries : PUnsortedIndexEntryCollection;helpfacility:PHelpFacility):boolean;
  90. function FormatAlias(Alias: string): string;
  91. begin
  92. if Assigned(HelpFacility) then
  93. if length(Alias)>HelpFacility^.IndexTabSize-4 then
  94. Alias:=Trim(copy(Alias,1,HelpFacility^.IndexTabSize-4-2))+'..';
  95. // if (length(alias)>0) and (alias[1]<>'/') then Alias:='/'+alias;
  96. FormatAlias:=Alias;
  97. end;
  98. var
  99. m : Classes.TMemoryStream;
  100. i,j : integer;
  101. item : TChmSiteMapItem;
  102. tli: integer;
  103. begin
  104. result:=false;
  105. if floaded then exit;
  106. if not assigned (fchmr) then exit;
  107. {$ifdef wdebug}
  108. debugmessageS({$i %file%},'TCHMWrapper: indexfilename:'+fchmr.indexfile,{$i %line%},'1',0,0);
  109. {$endif}
  110. findex:=fchmr.GetIndexSitemap(false);
  111. (* m:=fchmr.getobject(fchmr.indexfile);
  112. try
  113. if assigned(m) then
  114. begin
  115. {$ifdef wdebug}
  116. debugmessageS({$i %file%},'TCHMWrapper: stream size loaded :'+inttostr(m.size),{$i %line%},'1',0,0);
  117. {$endif}
  118. findex.loadfromStream(m);
  119. end;
  120. finally
  121. freeandnil(m);
  122. end;
  123. *)
  124. {$ifdef wdebug}
  125. debugmessageS({$i %file%},'TCHMWrapper: loadindex after final ',{$i %line%},'1',0,0);
  126. {$endif}
  127. tli:=TopicLinks^.AddItem(fchmr.defaultpage);
  128. TLI:=EncodeHTMLCtx(ID,TLI+1);
  129. IndexEntries^.Insert(NewIndexEntry( FormatAlias('Table of contents'),ID,TLI));
  130. if findex= Nil Then
  131. begin
  132. floaded:=true;
  133. exit(true);
  134. end;
  135. if assigned(findex.items) and (findex.items.count>0) Then
  136. for i:=0 to findex.items.count-1 do
  137. begin
  138. item:=findex.items.item[i];
  139. tli:=TopicLinks^.AddItem('/'+item.local);
  140. TLI:=EncodeHTMLCtx(ID,TLI+1);
  141. IndexEntries^.Insert(NewIndexEntry( FormatAlias(item.text),ID,TLI));
  142. end;
  143. {$ifdef wdebug}
  144. debugmessageS({$i %file%},'TCHMWrapper: endloadindex ',{$i %line%},'1',0,0);
  145. {$endif}
  146. floaded:=true;
  147. result:=true;
  148. end;
  149. procedure splitline(idestream:PMemoryTextFile;s:ansistring);
  150. function scanvalue:integer; // searches for a possible breaking point left of char 255.
  151. var n,i : integer;
  152. lastpoint:integer;
  153. inquote : boolean;
  154. begin
  155. lastpoint:=-1;
  156. n:=length(s);
  157. if n>250 then n:=250;
  158. i:=1; inquote:=false;
  159. while (i<=n) do
  160. begin
  161. while (s[i]<>' ') and (s[i]<>'"') and (i<=n) do inc(i);
  162. if (s[i]=' ') and not inquote then lastpoint:=i;
  163. if (s[i]='"') then inquote:=not inquote;
  164. inc(i);
  165. end;
  166. scanvalue:=lastpoint;
  167. end;
  168. var position : longint;
  169. begin
  170. position:=0;
  171. while (length(s)>250) and (position<>-1) do
  172. begin
  173. position:=scanvalue;
  174. if position<>-1 then
  175. begin
  176. idestream.addline(copy(s,1,position-1));
  177. delete(s,1,position);
  178. end;
  179. end;
  180. if length(s)<>0 then
  181. idestream.addline(s);
  182. end;
  183. function TChmWrapper.GetTopic(name:string):PMemoryTextFile;
  184. var
  185. m : Classes.TMemorystream;
  186. linedata:Classes.TStringList;
  187. i : integer;
  188. begin
  189. result:=nil;
  190. if not assigned(fchmr) or (name='') then exit;
  191. If (name[1]<>'/') and (copy(name,1,7)<>'ms-its:') Then
  192. name:='/'+name;
  193. linedata:=Classes.TStringList.create;
  194. try
  195. {$ifdef wdebug}
  196. debugmessageS({$i %file%},'TCHMWrapper: Getting file '+name,{$i %line%},'1',0,0);
  197. {$endif}
  198. // if uppercase(name)='TABLE OF CONTENTS' Then
  199. // m:=fchmr.getobject(fchmr.tocfile)
  200. // else
  201. m:=fchmr.getobject(name);
  202. if not assigned(m) then exit;
  203. linedata.loadfromstream(m);
  204. result:=new(PMemoryTextFile,Init);
  205. for i:=0 to linedata.count-1 do
  206. begin
  207. if length(linedata[i])>250 Then
  208. splitline(result,linedata[i])
  209. else
  210. result.addline(linedata[i]);
  211. end;
  212. finally
  213. m.free;
  214. linedata.free;
  215. end;
  216. end;
  217. destructor TChmWrapper.Destroy;
  218. var i : integer;
  219. begin
  220. i:=chmindex.indexof(fshortname);
  221. if i<>-1 then
  222. begin
  223. chmindex.delete(i);
  224. {$ifdef wdebug}
  225. debugmessageS({$i %file%},'TCHMWrapper: deregistering '+fshortname,{$i %line%},'1',0,0);
  226. {$endif}
  227. end;
  228. freeandnil(ftopic);
  229. freeandnil(findex);
  230. freeandnil(fchmr);
  231. {$ifdef wdebug}
  232. debugmessageS({$i %file%},'TCHMWrapper: destroying ',{$i %line%},'1',0,0);
  233. {$endif}
  234. end;
  235. function CHMResolve( href: ansistring; var AFileId,ALinkId : longint):boolean;
  236. var filename, restlink : ansistring;
  237. I :integer;
  238. chmw: TCHMWrapper;
  239. begin
  240. result:=false;
  241. if copy(href,1,7)='ms-its:' then
  242. begin
  243. {$ifdef wdebug}
  244. debugmessageS({$i %file%},'TCHMWrapper: resolving '+href,{$i %line%},'1',0,0);
  245. {$endif}
  246. delete(href,1,7);
  247. i:=pos('::',href);
  248. if i<>0 then
  249. begin
  250. filename:=lowercase(copy(href,1,i-1));
  251. restlink:=lowercase(copy(href,i+2,length(href)-(I+2)+1));
  252. i:=chmindex.indexof(filename);
  253. if i<>-1 then
  254. begin
  255. {$ifdef wdebug}
  256. debugmessageS({$i %file%},'TCHMWrapper: resolving '+filename+' '+inttostr(i),{$i %line%},'1',0,0);
  257. debugmessageS({$i %file%},'TCHMWrapper: resolving '+restlink+' ',{$i %line%},'1',0,0);
  258. {$endif}
  259. chmw:=TCHMWrapper(chmindex.objects[i]);
  260. Afileid:=chmw.fileid;
  261. alinkid:=chmw.fTopicLinks.additem(restlink);
  262. result:=true;
  263. end;
  264. end;
  265. end
  266. end;
  267. function stringreplace(const s:ansistring;const oldstr:ansistring; const newstr:ansistring):ansistring;
  268. begin
  269. result:=sysutils.stringreplace(s,oldstr,newstr,[rfreplaceall]);
  270. end;
  271. initialization
  272. ChmIndex:=TStringlist.create;
  273. ChmIndex.sorted:=true;
  274. finalization
  275. ChmIndex.Free;
  276. end.