wchmhwrap.pas 9.7 KB

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