wchmhwrap.pas 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  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. if (length(item.local)>0) and (item.local[1]<>'/') then
  140. tli:=TopicLinks^.AddItem('/'+item.local)
  141. else
  142. tli:=TopicLinks^.AddItem(item.local);
  143. TLI:=EncodeHTMLCtx(ID,TLI+1);
  144. IndexEntries^.Insert(NewIndexEntry( FormatAlias(item.text),ID,TLI));
  145. end;
  146. {$ifdef wdebug}
  147. debugmessageS({$i %file%},'TCHMWrapper: endloadindex ',{$i %line%},'1',0,0);
  148. {$endif}
  149. floaded:=true;
  150. result:=true;
  151. end;
  152. procedure splitline(idestream:PMemoryTextFile;s:ansistring);
  153. function scanvalue:integer; // searches for a possible breaking point left of char 255.
  154. var n,i : integer;
  155. lastpoint:integer;
  156. inquote : boolean;
  157. begin
  158. lastpoint:=-1;
  159. n:=length(s);
  160. if n>250 then n:=250;
  161. i:=1; inquote:=false;
  162. while (i<=n) do
  163. begin
  164. while (s[i]<>' ') and (s[i]<>'"') and (i<=n) do inc(i);
  165. if (s[i]=' ') and not inquote then lastpoint:=i;
  166. if (s[i]='"') then inquote:=not inquote;
  167. inc(i);
  168. end;
  169. scanvalue:=lastpoint;
  170. end;
  171. var position : longint;
  172. begin
  173. position:=0;
  174. while (length(s)>250) and (position<>-1) do
  175. begin
  176. position:=scanvalue;
  177. if position<>-1 then
  178. begin
  179. idestream.addline(copy(s,1,position-1));
  180. delete(s,1,position);
  181. end;
  182. end;
  183. if length(s)<>0 then
  184. idestream.addline(s);
  185. end;
  186. function TChmWrapper.GetTopic(name:string):PMemoryTextFile;
  187. var
  188. m : Classes.TMemorystream;
  189. linedata:Classes.TStringList;
  190. i : integer;
  191. begin
  192. result:=nil;
  193. if not assigned(fchmr) or (name='') then exit;
  194. If (name[1]<>'/') and (copy(name,1,7)<>'ms-its:') Then
  195. name:='/'+name;
  196. linedata:=Classes.TStringList.create;
  197. try
  198. {$ifdef wdebug}
  199. debugmessageS({$i %file%},'TCHMWrapper: Getting file '+name,{$i %line%},'1',0,0);
  200. {$endif}
  201. // if uppercase(name)='TABLE OF CONTENTS' Then
  202. // m:=fchmr.getobject(fchmr.tocfile)
  203. // else
  204. m:=fchmr.getobject(name);
  205. if not assigned(m) then exit;
  206. linedata.loadfromstream(m);
  207. result:=new(PMemoryTextFile,Init);
  208. for i:=0 to linedata.count-1 do
  209. begin
  210. if length(linedata[i])>250 Then
  211. splitline(result,linedata[i])
  212. else
  213. result.addline(linedata[i]);
  214. end;
  215. finally
  216. m.free;
  217. linedata.free;
  218. end;
  219. end;
  220. destructor TChmWrapper.Destroy;
  221. var i : integer;
  222. begin
  223. i:=chmindex.indexof(fshortname);
  224. if i<>-1 then
  225. begin
  226. chmindex.delete(i);
  227. {$ifdef wdebug}
  228. debugmessageS({$i %file%},'TCHMWrapper: deregistering '+fshortname,{$i %line%},'1',0,0);
  229. {$endif}
  230. end;
  231. freeandnil(ftopic);
  232. freeandnil(findex);
  233. freeandnil(fchmr);
  234. {$ifdef wdebug}
  235. debugmessageS({$i %file%},'TCHMWrapper: destroying ',{$i %line%},'1',0,0);
  236. {$endif}
  237. end;
  238. function CHMResolve( href: ansistring; var AFileId,ALinkId : longint):boolean;
  239. var filename, restlink : ansistring;
  240. I :integer;
  241. chmw: TCHMWrapper;
  242. begin
  243. result:=false;
  244. if copy(href,1,7)='ms-its:' then
  245. begin
  246. {$ifdef wdebug}
  247. debugmessageS({$i %file%},'TCHMWrapper: resolving '+href,{$i %line%},'1',0,0);
  248. {$endif}
  249. delete(href,1,7);
  250. i:=pos('::',href);
  251. if i<>0 then
  252. begin
  253. filename:=lowercase(copy(href,1,i-1));
  254. restlink:=lowercase(copy(href,i+2,length(href)-(I+2)+1));
  255. i:=chmindex.indexof(filename);
  256. if i<>-1 then
  257. begin
  258. {$ifdef wdebug}
  259. debugmessageS({$i %file%},'TCHMWrapper: resolving '+filename+' '+inttostr(i),{$i %line%},'1',0,0);
  260. debugmessageS({$i %file%},'TCHMWrapper: resolving '+restlink+' ',{$i %line%},'1',0,0);
  261. {$endif}
  262. chmw:=TCHMWrapper(chmindex.objects[i]);
  263. Afileid:=chmw.fileid;
  264. alinkid:=chmw.fTopicLinks.additem(restlink);
  265. result:=true;
  266. end;
  267. end;
  268. end
  269. end;
  270. function stringreplace(const s:ansistring;const oldstr:ansistring; const newstr:ansistring):ansistring;
  271. begin
  272. result:=sysutils.stringreplace(s,oldstr,newstr,[rfreplaceall]);
  273. end;
  274. initialization
  275. ChmIndex:=TStringlist.create;
  276. ChmIndex.sorted:=true;
  277. finalization
  278. ChmIndex.Free;
  279. end.