wchmhwrap.pas 9.5 KB

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