chmfilewriter.pas 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404
  1. { Copyright (C) <2005> <Andrew Haines> chmfilewriter.pas
  2. This library is free software; you can redistribute it and/or modify it
  3. under the terms of the GNU Library General Public License as published by
  4. the Free Software Foundation; either version 2 of the License, or (at your
  5. option) any later version.
  6. This program is distributed in the hope that it will be useful, but WITHOUT
  7. ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  8. FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
  9. for more details.
  10. You should have received a copy of the GNU Library General Public License
  11. along with this library; if not, write to the Free Software Foundation,
  12. Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  13. }
  14. {
  15. See the file COPYING.FPC, included in this distribution,
  16. for details about the copyright.
  17. }
  18. unit chmfilewriter;
  19. {$mode objfpc}{$H+}
  20. interface
  21. uses
  22. Classes, SysUtils, chmwriter, inifiles, contnrs, chmsitemap, avl_tree,
  23. {for html scanning } dom,SAX_HTML,dom_html;
  24. type
  25. TChmProject = class;
  26. TChmProjectErrorKind = (chmerror,chmwarning,chmhint,chmnote,chmnone);
  27. TChmProgressCB = procedure (Project: TChmProject; CurrentFile: String) of object;
  28. TChmErrorCB = procedure (Project: TChmProject;errorkind:TChmProjectErrorKind;msg:String;detaillevel:integer=0);
  29. { TChmProject }
  30. TChmProject = class
  31. private
  32. FAutoFollowLinks: Boolean;
  33. FDefaultFont: String;
  34. FDefaultPage: String;
  35. FFiles: TStrings;
  36. FIndexFileName: String;
  37. FMakeBinaryTOC: Boolean;
  38. FMakeBinaryIndex: Boolean;
  39. FMakeSearchable: Boolean;
  40. FFileName: String;
  41. FOnProgress: TChmProgressCB;
  42. FOnError : TChmErrorCB;
  43. FOutputFileName: String;
  44. FTableOfContentsFileName: String;
  45. FTitle: String;
  46. FWindows : TObjectList;
  47. FMergeFiles : TStringlist;
  48. fDefaultWindow : string;
  49. fScanHtmlContents : Boolean;
  50. fOtherFiles : TStrings; // Files found in a scan.
  51. fAllowedExtensions: TStringList;
  52. fTotalFileList : TAvlTree;
  53. fAnchorList : TStringList;
  54. FSpareString : TStringIndex;
  55. FBasePath : String; // location of the .hhp file. Needed to resolve relative paths
  56. FReadmeMessage : String; // readme message
  57. FToc,
  58. FIndex : TCHMSiteMap;
  59. FTocStream,
  60. FIndexStream : TMemoryStream;
  61. FCores : Integer;
  62. FLocaleID : Word;
  63. protected
  64. function GetData(const DataName: String; out PathInChm: String; out FileName: String; var Stream: TStream): Boolean;
  65. procedure LastFileAdded(Sender: TObject);
  66. procedure readIniOptions(keyvaluepairs:tstringlist);
  67. procedure ScanHtml;
  68. procedure ScanList(toscan,newfiles:TStrings;recursion:boolean);
  69. procedure ScanSitemap(sitemap:TChmSiteMap;newfiles:TStrings;recursion:boolean);
  70. function FileInTotalList(const s:String):boolean;
  71. function SanitizeURL(const basepath, instring, localpath, localname:string; var outstring:String):Boolean;
  72. public
  73. constructor Create; virtual;
  74. destructor Destroy; override;
  75. procedure LoadFromFile(AFileName: String); virtual;
  76. procedure LoadFromhhp (AFileName:String;LeaveInclude:Boolean); virtual;
  77. procedure SaveToFile(AFileName: String); virtual;
  78. procedure SaveToHHP(AFileName: String);
  79. procedure WriteChm(AOutStream: TStream); virtual;
  80. procedure ShowUndefinedAnchors;
  81. function ProjectDir: String;
  82. procedure LoadSitemaps;
  83. procedure AddFileWithContext(contextid:integer;filename:ansistring;contextname:ansistring='');
  84. procedure Error(errorkind:TChmProjectErrorKind;msg:String;detaillevel:integer=0);
  85. // though stored in the project file, it is only there for the program that uses the unit
  86. // since we actually write to a stream
  87. property OutputFileName: String read FOutputFileName write FOutputFileName;
  88. property FileName: String read FFileName write FFileName;
  89. property Files: TStrings read FFiles write FFiles; // html files
  90. property OtherFiles: TStrings read FOtherFiles write FOtherFiles; // other files (.css, img etc)
  91. property AutoFollowLinks: Boolean read FAutoFollowLinks write FAutoFollowLinks;
  92. property TableOfContentsFileName: String read FTableOfContentsFileName write FTableOfContentsFileName;
  93. property MakeBinaryTOC: Boolean read FMakeBinaryTOC write FMakeBinaryTOC;
  94. property MakeBinaryIndex: Boolean read FMakeBinaryIndex write FMakeBinaryIndex;
  95. property Title: String read FTitle write FTitle;
  96. property IndexFileName: String read FIndexFileName write FIndexFileName;
  97. property MakeSearchable: Boolean read FMakeSearchable write FMakeSearchable;
  98. property DefaultPage: String read FDefaultPage write FDefaultPage;
  99. property DefaultFont: String read FDefaultFont write FDefaultFont;
  100. property Windows :TObjectList read FWindows write FWindows;
  101. property MergeFiles :TStringlist read FMergeFiles write FMergefiles;
  102. property OnProgress: TChmProgressCB read FOnProgress write FOnProgress;
  103. property OnError : TChmErrorCB read FOnError write FOnError;
  104. property DefaultWindow : String read FDefaultWindow write FDefaultWindow;
  105. property ScanHtmlContents : Boolean read fScanHtmlContents write fScanHtmlContents;
  106. property ReadmeMessage : String read FReadmeMessage write FReadmeMessage;
  107. property AllowedExtensions : TStringList read FAllowedExtensions;
  108. property Cores : integer read fcores write fcores;
  109. property LocaleID: word read FLocaleID write FLocaleID;
  110. end;
  111. TChmContextNode = Class
  112. URLName : AnsiString;
  113. ContextNumber : THelpContext;
  114. ContextName : AnsiString;
  115. End;
  116. Const
  117. ChmErrorKindText : array[TCHMProjectErrorKind] of string = ('Error','Warning','Hint','Note','');
  118. implementation
  119. uses XmlCfg, CHMTypes;
  120. type
  121. { TFirstReference }
  122. TFirstReference = class
  123. public
  124. Location: string;
  125. constructor Create(Const ALocation: string);
  126. end;
  127. { TFirstReference }
  128. constructor TFirstReference.Create(const ALocation: string);
  129. begin
  130. Location := ALocation;
  131. end;
  132. { TChmProject }
  133. function TChmProject.GetData(const DataName: String; out PathInChm: String; out
  134. FileName: String; var Stream: TStream): Boolean;
  135. begin
  136. Result := False; // Return true to abort compressing files
  137. TMemoryStream(Stream).LoadFromFile(ProjectDir+DataName);
  138. // clean up the filename
  139. FileName := StringReplace(ExtractFileName(DataName), '\', '/', [rfReplaceAll]);
  140. FileName := StringReplace(FileName, '//', '/', [rfReplaceAll]);
  141. PathInChm := '/'+ExtractFilePath(DataName);
  142. if Assigned(FOnProgress) then FOnProgress(Self, DataName);
  143. end;
  144. procedure TChmProject.LastFileAdded(Sender: TObject);
  145. var
  146. Writer: TChmWriter;
  147. begin
  148. // Assign the TOC and index files
  149. Writer := TChmWriter(Sender);
  150. writer.cores:=fcores;
  151. {$ifdef chmindex}
  152. Writeln('binindex filename ',IndexFileName);
  153. {$endif}
  154. if assigned(FIndexStream) then
  155. begin
  156. FIndexStream.position:=0;
  157. Writer.AppendIndex(FIndexStream);
  158. if MakeBinaryIndex then
  159. begin
  160. Error(chmnote,'CHM has binary index');
  161. Writer.AppendBinaryIndexFromSiteMap(FIndex,False);
  162. end;
  163. end;
  164. if assigned(FTocStream) then
  165. begin
  166. Writer.AppendTOC(FTOCStream);
  167. if MakeBinaryTOC then
  168. begin
  169. Error(chmnote,'CHM has binary toc');
  170. Writer.AppendBinaryTOCFromSiteMap(FToc);
  171. end;
  172. end;
  173. if not assigned(sender) then
  174. Writer.Free;
  175. end;
  176. constructor TChmProject.Create;
  177. begin
  178. FFiles := TStringList.Create;
  179. FOtherFiles := TStringList.Create;
  180. FAllowedExtensions:=TStringList.Create;
  181. FAllowedExtensions.add('.HTM');
  182. FAllowedExtensions.add('.HTML');
  183. FWindows:=TObjectList.Create(True);
  184. FMergeFiles:=TStringlist.Create;
  185. ScanHtmlContents:=False;
  186. FTotalFileList:=TAvlTree.Create(@CompareStrings);
  187. FSparestring :=TStringIndex.Create;
  188. fAnchorList := TStringList.Create;
  189. fAnchorList.Sorted := True;
  190. fAnchorList.OwnsObjects := True;
  191. end;
  192. destructor TChmProject.Destroy;
  193. var i : integer;
  194. begin
  195. for i:=0 to ffiles.count -1 do
  196. ffiles.objects[i].free;
  197. FMergeFiles.Free;
  198. FFiles.Free;
  199. FOtherFiles.Free;
  200. FWindows.Free;
  201. FSpareString.Free;
  202. FTotalFileList.FreeAndClear;
  203. FTotalFileList.Free;
  204. fAllowedExtensions.Free;
  205. FToc.free;
  206. FIndex.free;
  207. FTocStream.Free;
  208. FIndexStream.Free;
  209. fAnchorList.Free;
  210. inherited Destroy;
  211. end;
  212. Type
  213. TSectionEnum = (secOptions,secWindows,secFiles,secMergeFiles,secAlias,secMap,secInfoTypes,secTextPopups,secUnknown);
  214. TOptionEnum = (OPTAUTO_INDEX,OPTAUTO_TOC,OPTBINARY_INDEX,OPTBINARY_TOC,OPTCITATION,
  215. OPTCOMPRESS,OPTCOPYRIGHT,OPTCOMPATIBILITY,OPTCOMPILED_FILE,OPTCONTENTS_FILE,
  216. OPTCREATE_CHI_FILE,OPTDBCS,OPTDEFAULT_FONT,OPTDEFAULT_WINDOW,OPTDEFAULT_TOPIC,
  217. OPTDISPLAY_COMPILE_NOTES,OPTDISPLAY_COMPILE_PROGRESS,OPTENHANCED_DECOMPILATION,OPTERROR_LOG_FILE,OPTFLAT,
  218. OPTFULL_TEXT_SEARCH_STOP_LIST,OPTFULL_TEXT_SEARCH,OPTIGNORE,OPTINDEX_FILE,OPTLANGUAGE,OPTPREFIX,
  219. OPTSAMPLE_STAGING_PATH,OPTSAMPLE_LIST_FILE,OPTTMPDIR,OPTTITLE,OPTCUSTOM_TAB,OPTUNKNOWN);
  220. Const
  221. SectionNames : Array[TSectionEnum] of String =
  222. ('OPTIONS','WINDOWS','FILES','MERGE FILES','ALIAS','MAP','INFOTYPES','TEXT POPUPS','UNKNOWN');
  223. OptionKeys : array [TOptionEnum] of String =
  224. ('AUTO INDEX','AUTO TOC','BINARY INDEX','BINARY TOC','CITATION',
  225. 'COMPRESS','COPYRIGHT','COMPATIBILITY','COMPILED FILE','CONTENTS FILE',
  226. 'CREATE CHI FILE','DBCS','DEFAULT FONT','DEFAULT WINDOW','DEFAULT TOPIC',
  227. 'DISPLAY COMPILE NOTES','DISPLAY COMPILE PROGRESS','ENHANCED DECOMPILATION','ERROR LOG FILE','FLAT',
  228. 'FULL-TEXT SEARCH STOP LIST','FULL-TEXT SEARCH','IGNORE','INDEX FILE','LANGUAGE','PREFIX',
  229. 'SAMPLE STAGING PATH','SAMPLE LIST FILE','TMPDIR','TITLE','CUSTOM TAB','UNKNOWN');
  230. function FindSectionName (const name:string):TSectionEnum;
  231. begin
  232. result:=low(TSectionEnum);
  233. while (result<secUnknown) and (name<>SectionNames[Result]) do
  234. inc(result);
  235. end;
  236. function FindOptionName(Const name:string):TOptionEnum;
  237. begin
  238. result:=low(TOptionEnum);
  239. while (result<optUnknown) and (name<>OptionKeys[Result]) do
  240. inc(result);
  241. end;
  242. // hex codes of LCID (Locale IDs) see at http://msdn.microsoft.com/en-us/goglobal/bb964664.aspx
  243. function GetLanguageID(const sValue: String): word;
  244. const
  245. DefaultLCID = $0409; // default "English - United States", 0x0409
  246. var
  247. ACode: word;
  248. begin
  249. Result := DefaultLCID;
  250. if Length(sValue) >= 5 then
  251. begin
  252. Val(Trim(Copy(sValue, 1, 6)), Result, ACode);
  253. //if Code <> 0 then
  254. //Result := DefaultLCID;
  255. end
  256. end;
  257. procedure TChmProject.readIniOptions(keyvaluepairs:tstringlist);
  258. var i : integer;
  259. Opt : TOptionEnum;
  260. OptVal,
  261. OptValUpper : string;
  262. begin
  263. for i:=0 to keyvaluepairs.count-1 do
  264. begin
  265. Opt:=findoptionname(uppercase(keyvaluepairs.names[i]));
  266. optval :=keyvaluepairs.valuefromindex[i];
  267. optvalupper:=uppercase(OptVal);
  268. case Opt Of
  269. OPTAUTO_INDEX : ;
  270. OPTAUTO_TOC : ;
  271. OPTBINARY_INDEX : MakeBinaryIndex:=optvalupper='YES';
  272. OPTBINARY_TOC : MakeBinaryToc :=optvalupper='YES';
  273. OPTCITATION : ;
  274. OPTCOMPRESS : ; // Doesn't seem to have effect in workshop
  275. OPTCOPYRIGHT : ;
  276. OPTCOMPATIBILITY : ;
  277. OPTCOMPILED_FILE : OutputFilename:=optval;
  278. OPTCONTENTS_FILE : TableOfContentsFileName:=optval;
  279. OPTCREATE_CHI_FILE : ;
  280. OPTDBCS : ; // What this field makes unicode is not known?
  281. OPTDEFAULT_FONT : defaultfont:=optval;
  282. OPTDEFAULT_WINDOW : defaultwindow:=optval;
  283. OPTDEFAULT_TOPIC : defaultpage:=optval;
  284. OPTDISPLAY_COMPILE_NOTES : ;
  285. OPTDISPLAY_COMPILE_PROGRESS : ;
  286. OPTENHANCED_DECOMPILATION : ;
  287. OPTERROR_LOG_FILE : ;
  288. OPTFLAT : ;
  289. OPTFULL_TEXT_SEARCH_STOP_LIST: ;
  290. OPTFULL_TEXT_SEARCH : MakeSearchable:=optvalupper='YES';
  291. OPTIGNORE : ;
  292. OPTINDEX_FILE : Indexfilename:=optval;
  293. OPTLANGUAGE : LocaleID := GetLanguageID(optval);
  294. OPTPREFIX : ; // doesn't seem to have effect
  295. OPTSAMPLE_STAGING_PATH : ;
  296. OPTSAMPLE_LIST_FILE : ;
  297. OPTTMPDIR : ;
  298. OPTTITLE : Title:=optval;
  299. OPTCUSTOM_TAB : ;
  300. OPTUNKNOWN : ; // can be used for errors on unknown keys
  301. end;
  302. end;
  303. end;
  304. procedure TChmProject.LoadFromFile(AFileName: String);
  305. var
  306. Cfg: TXMLConfig;
  307. MergeFileCount,
  308. WinCount,
  309. FileCount: Integer;
  310. I : Integer;
  311. nd : TChmContextNode;
  312. win: TCHMWindow;
  313. s : String;
  314. begin
  315. Cfg := TXMLConfig.Create(nil);
  316. Cfg.Filename := AFileName;
  317. FileName := expandfilename(AFileName);
  318. FBasePath:=extractfilepath(FileName);
  319. Files.Clear;
  320. FileCount := Cfg.GetValue('Files/Count/Value', 0);
  321. for I := 0 to FileCount-1 do
  322. begin
  323. nd:=TChmContextNode.Create;
  324. nd.urlname:=Cfg.GetValue('Files/FileName'+IntToStr(I)+'/Value','');
  325. nd.contextnumber:=Cfg.GetValue('Files/FileName'+IntToStr(I)+'/ContextNumber',0);
  326. nd.contextname:=Cfg.GetValue('Files/FileName'+IntToStr(I)+'/ContextName','');
  327. Files.AddObject(nd.URLNAME,nd);
  328. end;
  329. FileCount := Cfg.GetValue('OtherFiles/Count/Value', 0);
  330. for I := 0 to FileCount-1 do
  331. begin
  332. s:=Cfg.GetValue('OtherFiles/FileName'+IntToStr(I)+'/Value','');
  333. OtherFiles.Add(s);
  334. end;
  335. WinCount:= Cfg.GetValue('Windows/Count/Value', 0);
  336. for i:=0 To WinCount-1 do
  337. begin
  338. win:=TCHMWindow.Create;
  339. win.loadfromxml(cfg,'Windows/item'+inttostr(i)+'/');
  340. fwindows.add(win);
  341. end;
  342. Mergefilecount:=Cfg.getValue('MergeFiles/Count/Value', 0);
  343. for i:=0 To MergeFileCount-1 do
  344. Mergefiles.add(Cfg.getValue('MergeFiles/FileName'+IntToStr(I)+'/value',''));
  345. // load some values that changed key backwards compatible.
  346. IndexFileName := Cfg.GetValue('Files/IndexFile/Value','');
  347. if IndexFileName='' Then
  348. IndexFileName := Cfg.GetValue('Settings/IndexFile/Value','');
  349. TableOfContentsFileName := Cfg.GetValue('Files/TOCFile/Value','');
  350. If TableOfContentsFileName='' then
  351. TableOfContentsFileName := Cfg.GetValue('Settings/TOCFile/Value','');
  352. // For chm file merging, bintoc must be false and binindex true. Change defaults in time?
  353. // OTOH, merging will be mostly done for fpdoc files, and that doesn't care about defaults.
  354. S:=Cfg.GetValue('Files/MakeBinaryTOC/Value', '');
  355. if s='' Then
  356. MakeBinaryTOC := Cfg.GetValue('Settings/MakeBinaryTOC/Value', True)
  357. else
  358. MakeBinaryTOC := Cfg.GetValue('Files/MakeBinaryTOC/Value', True);
  359. S:=Cfg.GetValue('Files/MakeBinaryIndex/Value', '');
  360. if s='' Then
  361. MakeBinaryIndex := Cfg.GetValue('Settings/MakeBinaryIndex/Value', False)
  362. else
  363. MakeBinaryIndex := Cfg.GetValue('Files/MakeBinaryIndex/Value', False);
  364. AutoFollowLinks := Cfg.GetValue('Settings/AutoFollowLinks/Value', False);
  365. MakeSearchable := Cfg.GetValue('Settings/MakeSearchable/Value', False);
  366. DefaultPage := Cfg.GetValue('Settings/DefaultPage/Value', '');
  367. Title := Cfg.GetValue('Settings/Title/Value', '');
  368. OutputFileName := Cfg.GetValue('Settings/OutputFileName/Value', '');
  369. DefaultFont := Cfg.GetValue('Settings/DefaultFont/Value', '');
  370. DefaultWindow:= Cfg.GetValue('Settings/DefaultWindow/Value', '');
  371. ScanHtmlContents:= Cfg.GetValue('Settings/ScanHtmlContents/Value', False);
  372. LocaleID := Cfg.GetValue('Settings/LocaleID/Value', $0409);
  373. Cfg.Free;
  374. end;
  375. function cleanupstring(const s:string):string;
  376. var
  377. i:integer;
  378. begin
  379. i:=pos(';',s);
  380. if i>0 then
  381. result:=trim(copy(s,1,i-1))
  382. else
  383. result:=trim(s);
  384. end;
  385. procedure TChmProject.LoadFromhhp (AFileName:String;LeaveInclude:Boolean);
  386. // leaveinclude=true leaves includefiles includefiles.
  387. procedure addalias(const key,value :string);
  388. var i,j : integer;
  389. node: TCHMContextNode;
  390. keyupper,valueupper : string;
  391. begin
  392. { Defaults other than global }
  393. MakeBinaryIndex:=True;
  394. {$ifdef hhp_debug}
  395. writeln('alias entry:',key,'=',value);
  396. {$endif}
  397. keyupper:=uppercase(value);
  398. i:=0; j:=files.count;
  399. while (i<j) and (uppercase(TCHMContextnode(files.objects[i]).UrlName)<>keyupper) do
  400. inc(i);
  401. if i=j then
  402. begin
  403. {$ifdef hhp_debug}
  404. writeln('alias new node:',key);
  405. {$endif}
  406. node:=TCHMContextNode.create;
  407. valueupper:=stringReplace(value, '\', '/', [rfReplaceAll]);
  408. valueupper:= StringReplace(valueupper, '//', '/', [rfReplaceAll]);
  409. node.URLName:=valueupper;
  410. node.contextname:=key;
  411. end
  412. else
  413. begin
  414. node:=TCHMContextNode(Files.objects[i]);
  415. node.ContextName:=key;
  416. end;
  417. end;
  418. procedure processalias(strs:TStringlist);
  419. var i,j : integer;
  420. s : string;
  421. strls2:tstringlist;
  422. begin
  423. for i:=0 to strs.count-1 do
  424. begin
  425. s:=cleanupstring(strs[i]);
  426. if uppercase(copy(s,1,8))='#INCLUDE' then
  427. begin
  428. delete(s,1,8);
  429. s:=trim(s);
  430. if fileexists(s) then
  431. begin
  432. strls2:=TstringList.create;
  433. strls2.loadfromfile(s);
  434. processalias(strls2);
  435. strls2.free;
  436. end;
  437. end
  438. else
  439. begin
  440. s:=cleanupstring(s);
  441. j:=pos('=',s);
  442. if j>0 then
  443. addalias(trim(copy(s,1,j-1)),copy(s,j+1,length(s)-j));
  444. end;
  445. end;
  446. end;
  447. procedure addmap(const key,value :string);
  448. var i,j : integer;
  449. node: TCHMContextNode;
  450. keyupper : string;
  451. begin
  452. {$ifdef hhp_debug}
  453. writeln('map entry:',key,'=',value);
  454. {$endif}
  455. keyupper:=uppercase(key);
  456. i:=0; j:=files.count;
  457. while (i<j) and (uppercase(TCHMContextnode(files.objects[i]).contextname)<>keyupper) do
  458. inc(i);
  459. if i=j then
  460. raise Exception.create('context "'+key+'" not found!')
  461. else
  462. begin
  463. node:=TCHMContextNode(Files.objects[i]);
  464. node.Contextnumber:=strtointdef(value,0);
  465. end;
  466. end;
  467. procedure processmap(strs:TStringlist);
  468. var i,j : integer;
  469. s : string;
  470. strls2:tstringlist;
  471. begin
  472. for i:=0 to strs.count-1 do
  473. begin
  474. s:=cleanupstring(strs[i]);
  475. {$ifdef hhp_debug}
  476. writeln('map item:',s);
  477. {$endif}
  478. if uppercase(copy(s,1,8))='#INCLUDE' then
  479. begin
  480. delete(s,1,8);
  481. s:=trim(s);
  482. if fileexists(s) then
  483. begin
  484. strls2:=TstringList.create;
  485. strls2.loadfromfile(s);
  486. processmap(strls2);
  487. strls2.free;
  488. end;
  489. end
  490. else
  491. begin
  492. s:=cleanupstring(s);
  493. if uppercase(copy(s,1,7))='#DEFINE' Then
  494. begin
  495. delete(s,1,7);
  496. s:=trim(s);
  497. j:=pos(' ',s);
  498. if j>0 then
  499. addmap(trim(copy(s,1,j-1)),copy(s,j+1,length(s)-j));
  500. end
  501. else
  502. begin
  503. {$ifdef hhp_debug}
  504. writeln('map leftover:',s);
  505. {$endif}
  506. end;
  507. end;
  508. end;
  509. end;
  510. var
  511. Fini : TMemIniFile; // TMemInifile is more compatible with Delphi. Delphi's API based TIniFile fails on .hhp files.
  512. secs,strs : TStringList;
  513. i,j : Integer;
  514. section : TSectionEnum;
  515. nd : TChmContextNode;
  516. begin
  517. { Defaults other than global }
  518. MakeBinaryIndex:=True;
  519. filename:=expandfilename(afilename);
  520. FBasePath:=extractfilepath(filename);
  521. Fini:=TMeminiFile.Create(AFileName);
  522. secs := TStringList.create;
  523. strs := TStringList.create;
  524. fini.readsections(secs);
  525. // Do the files section first so that we can emit errors if
  526. // other sections reference unknown files.
  527. fini.readsectionvalues(SectionNames[secFiles] ,strs);
  528. if strs.count>0 then
  529. for j:=0 to strs.count-1 do
  530. begin
  531. nd:=TChmContextNode.Create;
  532. nd.urlname:=StringReplace(strs[j],'\', '/', [rfReplaceAll]);
  533. nd.contextnumber:=0;
  534. nd.contextname:='';
  535. Files.AddObject(nd.urlname,nd);
  536. end;
  537. // aliases also add file nodes.
  538. fini.readsectionvalues(SectionNames[secAlias] ,strs); // resolve all aliases.
  539. if strs.count>0 then
  540. processalias(strs);
  541. // map files only add to existing file nodes.
  542. fini.readsectionvalues(SectionNames[secmap] ,strs);
  543. if strs.count>0 then
  544. processmap(strs);
  545. for i:=0 to secs.count-1 do
  546. begin
  547. section:=FindSectionName(Uppercase(Secs[i]));
  548. if section<>secunknown then
  549. fini.readsectionvalues(secs[i] ,strs);
  550. case section of
  551. secOptions : readinioptions(strs);
  552. secWindows : for j:=0 to strs.count-1 do
  553. FWindows.add(TCHMWindow.Create(strs[j]));
  554. secFiles : ; // already done
  555. secMergeFiles: FMergeFiles.Assign(Strs); // just a filelist
  556. secAlias : ; // already done
  557. secMap : ; // already done
  558. secInfoTypes : ; // unused for now.
  559. secTextPopups: ; // rarely used.
  560. end;
  561. end;
  562. secs.free;
  563. strs.free;
  564. fini.free;
  565. ScanHtmlContents:=true;
  566. end;
  567. procedure TChmProject.AddFileWithContext(contextid:integer;filename:ansistring;contextname:ansistring='');
  568. var x : integer;
  569. nd : TChmContextNode;
  570. begin
  571. x:=files.indexof(filename);
  572. if x=-1 then
  573. begin
  574. nd:=TChmContextNode.Create;
  575. nd.urlname:=filename;
  576. nd.contextnumber:=contextid;
  577. nd.contextname:=contextname;
  578. Files.AddObject(nd.urlname,nd);
  579. end
  580. else
  581. begin
  582. nd:=TChmContextNode(files.objects[x]);
  583. if not assigned(nd) then
  584. begin
  585. nd:=TChmContextNode.Create;
  586. nd.urlname:=filename;
  587. files.objects[x]:=nd;
  588. end;
  589. nd.contextnumber:=contextid;
  590. nd.contextname:=contextname;
  591. end;
  592. end;
  593. procedure TChmProject.SaveToFile(AFileName: String);
  594. var
  595. Cfg: TXMLConfig;
  596. I : Integer;
  597. nd : TChmContextNode;
  598. begin
  599. Cfg := TXMLConfig.Create(nil);
  600. Cfg.StartEmpty := True;
  601. Cfg.Filename := AFileName;
  602. Cfg.Clear;
  603. Cfg.SetValue('Files/Count/Value', Files.Count);
  604. for I := 0 to Files.Count-1 do
  605. begin
  606. nd:=TChmContextNode(files.objects[i]);
  607. Cfg.SetValue('Files/FileName'+IntToStr(I)+'/Value', Files.Strings[I]);
  608. if assigned(nd) then
  609. begin
  610. Cfg.SetValue('Files/FileName'+IntToStr(I)+'/ContextNumber', nd.contextnumber);
  611. Cfg.SetValue('Files/FileName'+IntToStr(I)+'/ContextName', nd.contextname);
  612. end;
  613. end;
  614. Cfg.SetValue('OtherFiles/Count/Value', OtherFiles.Count);
  615. for I := 0 to OtherFiles.Count-1 do
  616. Cfg.SetValue('OtherFiles/FileName'+IntToStr(I)+'/Value', OtherFiles.Strings[I]);
  617. Cfg.SetValue('Windows/Count/Value', FWindows.count);
  618. for i:=0 To FWindows.Count-1 do
  619. TCHMWindow(FWindows[i]).savetoxml(cfg,'Windows/item'+inttostr(i)+'/');
  620. Cfg.SetValue('MergeFiles/Count/Value', FMergeFiles.count);
  621. for i:=0 To FMergeFiles.Count-1 do
  622. Cfg.SetValue('MergeFiles/FileName'+IntToStr(I)+'/value',FMergeFiles[i]);
  623. // delete legacy keys.
  624. Cfg.DeleteValue('Files/IndexFile/Value');
  625. Cfg.DeleteValue('Files/TOCFile/Value');
  626. Cfg.DeleteValue('Files/MakeBinaryTOC/Value');
  627. Cfg.DeleteValue('Files/MakeBinaryIndex/Value');
  628. Cfg.SetValue('Settings/IndexFile/Value', IndexFileName);
  629. Cfg.SetValue('Settings/TOCFile/Value', TableOfContentsFileName);
  630. Cfg.SetValue('Settings/MakeBinaryTOC/Value',MakeBinaryTOC);
  631. Cfg.SetValue('Settings/MakeBinaryIndex/Value',MakeBinaryIndex);
  632. Cfg.SetValue('Settings/AutoFollowLinks/Value', AutoFollowLinks);
  633. Cfg.SetValue('Settings/MakeSearchable/Value', MakeSearchable);
  634. Cfg.SetValue('Settings/DefaultPage/Value', DefaultPage);
  635. Cfg.SetValue('Settings/Title/Value', Title);
  636. Cfg.SetValue('Settings/OutputFileName/Value', OutputFileName);
  637. Cfg.SetValue('Settings/DefaultFont/Value', DefaultFont);
  638. Cfg.SetValue('Settings/DefaultWindow/Value', DefaultWindow);
  639. Cfg.SetValue('Settings/ScanHtmlContents/Value', ScanHtmlContents);
  640. Cfg.SetValue('Settings/LocaleID/Value', LocaleID);
  641. Cfg.Flush;
  642. Cfg.Free;
  643. end;
  644. function TChmProject.ProjectDir: String;
  645. begin
  646. Result := ExtractFilePath(FileName);
  647. end;
  648. procedure TChmProject.Error(errorkind:TChmProjectErrorKind;msg:String;detaillevel:integer=0);
  649. begin
  650. if assigned(OnError) then
  651. OnError(self,errorkind,msg,detaillevel);
  652. end;
  653. const
  654. protocols : array[0..4] of string = ('HTTP:','HTTPS:','FTP:','MS-ITS:', 'MAILTO:');
  655. protocollen : array[0..4] of integer= ( 5 ,6, 4 ,7, 7);
  656. function TChmProject.SanitizeURL(const basepath,instring,localpath,localname:string;var outstring:String):Boolean;
  657. var i,j,len : integer;
  658. Anchor: String;
  659. begin
  660. result:=true; outstring:='';
  661. if instring='' then
  662. exit(false);
  663. len:=length(instring);
  664. if len=0 then
  665. exit(false);
  666. { Check for protocols before adding local path }
  667. i:=0;
  668. while (i<=high(protocols)) do
  669. begin
  670. if strlicomp(@protocols[i][1],@instring[1],protocollen[i])=0 then
  671. exit(false);
  672. inc(i);
  673. end;
  674. outstring:=localpath+instring;
  675. i:=pos('#',outstring);
  676. if i<>0 then begin
  677. if i<>length(outstring) then // trims lone '#' at end of url.
  678. begin
  679. if i > 1 then
  680. Anchor := outstring
  681. else
  682. Anchor := localname+outstring;
  683. j := fAnchorList.IndexOf(Anchor);
  684. if j < 0 then begin
  685. fAnchorList.AddObject(Anchor,TFirstReference.Create(localname));
  686. Anchor := '(new) '+Anchor;
  687. end;
  688. Error(CHMNote, 'Anchor found '+Anchor+' while scanning '+localname,1);
  689. end;
  690. delete(outstring,i,length(outstring)-i+1);
  691. end;
  692. outstring:=expandfilename(includetrailingpathdelimiter(fbasepath)+StringReplace(outstring,'%20',' ',[rfReplaceAll]));// expandfilename(instring));
  693. outstring:=extractrelativepath(basepath,outstring);
  694. outstring:=StringReplace(outstring,'\','/',[rfReplaceAll]);
  695. if outstring='' then
  696. result:=false;
  697. end;
  698. function TChmProject.FileInTotalList(const s:String):boolean;
  699. begin
  700. FSpareString.TheString:=S;
  701. result:=assigned(fTotalFileList.FindKey(FSpareString,@CompareStrings));
  702. end;
  703. procedure TChmProject.ScanList(toscan,newfiles:TStrings;recursion:boolean);
  704. // toscan, list to search for htmlfiles to scan.
  705. // newfiles, the resulting list of files.
  706. // totalfilelist, the list that contains all found and specified files to check against.
  707. // localfilelist (local var), files found in this file.
  708. var
  709. localpath : string;
  710. function findattribute(node:TDomNode;attributename:string):String;
  711. var
  712. Attributes: TDOMNamedNodeMap;
  713. atnode : TDomNode;
  714. n : integer;
  715. begin
  716. Result := '';
  717. if assigned(node) then
  718. begin
  719. Attributes:=node.Attributes;
  720. if assigned(attributes) then
  721. for n:=0 to attributes.length-1 do
  722. begin
  723. atnode :=attributes[n];
  724. if assigned(atnode) and (uppercase(atnode.nodename)=attributename) then
  725. exit(atnode.nodevalue);
  726. end;
  727. end;
  728. end;
  729. procedure checkattributes(node:TDomNode;attributename:string; const localname: string; filelist :TStringList);
  730. var
  731. fn : String;
  732. val : String;
  733. begin
  734. val := findattribute(node,attributename);
  735. if sanitizeurl(fbasepath,val,localpath,localname,fn) then
  736. if (Length(fn) > 0) { Skip links to self using named anchors }
  737. and not FileInTotalList(uppercase(fn)) then
  738. filelist.add(fn);
  739. end;
  740. procedure checkattributesA(node:TDomNode;const localname: string; filelist :TStringList);
  741. // workaround for "a" tag that has href and src. If src exists, don't check href, this
  742. // avoids spurious warnings.
  743. var
  744. fn : String;
  745. val : String;
  746. found : boolean;
  747. begin
  748. found:=false;
  749. val := findattribute(node,'SRC');
  750. if sanitizeurl(fbasepath,val,localpath,localname,fn) then
  751. found:=true;
  752. if not found then
  753. begin
  754. val := findattribute(node,'HREF');
  755. if sanitizeurl(fbasepath,val,localpath,localname,fn) then
  756. found:=true;
  757. end;
  758. if found and not FileInTotalList(uppercase(fn)) then
  759. filelist.add(fn);
  760. end;
  761. function scantags(prnt:TDomNode; const localname: string; filelist:TStringlist):TDomNode;
  762. var
  763. att : ansistring;
  764. procedure AddAnchor(const s:string);
  765. var
  766. i : Integer;
  767. begin
  768. i := fAnchorList.IndexOf(localname+'#'+s);
  769. if i < 0 then begin
  770. fAnchorList.Add(localname+'#'+s);
  771. Error(ChmNote,'New Anchor with '+att+' '+s+' found while scanning '+localname,1);
  772. end else if fAnchorList.Objects[i] = nil then
  773. Error(chmwarning,'Duplicate anchor definitions with '+att+' '+s+' found while scanning '+localname,1)
  774. else begin
  775. fAnchorList.Objects[i].Free;
  776. fAnchorList.Objects[i] := nil;
  777. Error(ChmNote,'Anchor with '+att+' '+s+' defined while scanning '+localname,1);
  778. end;
  779. end;
  780. var chld: TDomNode;
  781. s,attrval : ansistring;
  782. idfound : boolean;
  783. begin
  784. result:=nil;
  785. if assigned(prnt ) then
  786. begin
  787. chld:=prnt.firstchild;
  788. while assigned(chld) do
  789. begin
  790. scantags(chld, localname, filelist); // depth first.
  791. if (chld is TDomElement) then
  792. begin
  793. s:=uppercase(tdomelement(chld).tagname);
  794. att := 'ID';
  795. attrval := findattribute(chld, att);
  796. idfound:=attrval <> '' ;
  797. if idfound then
  798. addanchor(attrval);
  799. if s='LINK' then
  800. begin
  801. //printattributes(chld,'');
  802. checkattributes(chld,'HREF',localname,filelist);
  803. end;
  804. if s='SCRIPT' then
  805. begin
  806. //printattributes(chld,'');
  807. checkattributes(chld,'SRC',localname,filelist);
  808. end;
  809. if s='IMG' then
  810. begin
  811. //printattributes(chld,'');
  812. checkattributes(chld,'SRC',localname,filelist);
  813. end;
  814. if s='A' then
  815. begin
  816. //printattributes(chld,'');
  817. checkattributesA(chld,localname,filelist);
  818. if not idfound then
  819. begin
  820. att := 'NAME';
  821. attrval := findattribute(chld, att);
  822. if attrval <> '' then
  823. addanchor(attrval);
  824. end;
  825. end;
  826. end;
  827. chld:=chld.nextsibling;
  828. end;
  829. end;
  830. end;
  831. var
  832. localfilelist: TStringList;
  833. domdoc : THTMLDocument;
  834. i,j : Integer;
  835. fn,reffn : string;
  836. tmplst : Tstringlist;
  837. function trypath(const vn:string;const vl:string):boolean;
  838. var vn2: String;
  839. strrec : TStringIndex;
  840. begin
  841. vn2:=uppercase(vn);
  842. if FileInTotalList(vn2) then
  843. begin
  844. Error(ChmNote,'Found duplicate file '+vl+' while scanning '+fn,1);
  845. exit(true);
  846. end;
  847. result:=false;
  848. if fileexists(vn) then // correct for relative path .html file?
  849. begin
  850. result:=true;
  851. StrRec:=TStringIndex.Create;
  852. StrRec.TheString:=vn2;
  853. StrRec.Strid :=0;
  854. fTotalFileList.Add(StrRec);
  855. newfiles.add(vl);
  856. Error(ChmNote,'Found file '+vl+' while scanning '+fn,1);
  857. end;
  858. end;
  859. var efn : string;
  860. begin
  861. localfilelist:=TStringList.Create;
  862. for j:=0 to toscan.count-1 do
  863. begin
  864. fn:=toscan[j];
  865. localfilelist.clear;
  866. if (FAllowedExtensions.Indexof(uppercase(extractfileext(fn)))<>-1) then
  867. begin
  868. if fbasepath<>'' then
  869. efn:=IncludeTrailingPathDelimiter(FBasePath)+fn
  870. else
  871. efn:=fn;
  872. if fileexists(efn) then
  873. begin
  874. domdoc:=THtmlDocument.Create;
  875. try
  876. Error(chmnote,'Scanning file '+fn+'.',5);
  877. ReadHtmlFile(domdoc,efn);
  878. localpath:=extractfilepath(fn);
  879. if (length(localpath)>0) and not (localpath[length(localpath)] in ['/','\']) then
  880. localpath:=localpath+pathsep;
  881. scantags(domdoc,extractfilename(fn),localfilelist);
  882. for i:=0 to localFilelist.count-1 do
  883. begin
  884. reffn:=localfilelist[i];
  885. if not trypath(IncludeTrailingPathDelimiter(fbasepath)+reffn,reffn) then // if not trypath(localpath+s) then
  886. Error(ChmWarning,'Found file '+reffn+' while scanning '+fn+', but couldn''t find it on disk',2);
  887. end;
  888. except
  889. on e:EDomError do
  890. Error(ChmError,'Html parsing '+fn+', failed with a DOM error: '+e.Message);
  891. on e:exception do
  892. Error(ChmError,'Html parsing '+fn+', failed. Please submit a bug.');
  893. end;
  894. domdoc.free;
  895. end
  896. else
  897. begin
  898. Error(chmnote,'Can''t find file '+fn+' to scan it.',5);
  899. end;
  900. end
  901. else if FileExists(fn) and (uppercase(ExtractFileExt(fn))='.CSS') then
  902. begin
  903. tmplst:=TStringList.Create;
  904. try
  905. tmplst.LoadFromFile(fn);
  906. for i:=0 to tmplst.Count-1 do
  907. begin
  908. reffn:=tmplst[i];
  909. if pos('url(''', reffn)>0 then
  910. begin
  911. delete(reffn,1,pos('url(''', reffn)+4);
  912. reffn:=trim(copy(reffn,1,pos('''',reffn)-1));
  913. if not trypath(IncludeTrailingPathDelimiter(fbasepath)+reffn,reffn) then
  914. // if not trypath(localpath+s) then
  915. Error(ChmWarning,'Found file '+reffn+' while scanning '+fn+', but couldn''t find it on disk',2);
  916. end;
  917. end;
  918. finally
  919. tmplst.Free;
  920. end;
  921. end
  922. else
  923. Error(chmnote,'Not scanning file because of unknown extension '+fn,5);
  924. end;
  925. localfilelist.free;
  926. if (newfiles.count>0) and recursion then
  927. begin
  928. tmplst:=TStringList.Create;
  929. scanlist(newfiles,tmplst,true);
  930. newfiles.addstrings(tmplst);
  931. tmplst.free;
  932. end;
  933. end;
  934. procedure TChmProject.ScanSitemap(sitemap:TChmSiteMap;newfiles:TStrings;recursion:boolean);
  935. procedure scanitems(it:TChmSiteMapItems);
  936. var i,j : integer;
  937. x : TChmSiteMapItem;
  938. si : TChmSiteMapSubItem;
  939. s : string;
  940. strrec : TStringIndex;
  941. begin
  942. for i:=0 to it.count -1 do
  943. begin
  944. x:=it.item[i];
  945. for j:=0 to x.SubItemcount-1 do
  946. begin
  947. si:=x.SubItem[j];
  948. if sanitizeurl(fbasepath,si.local,'','Site Map for '+x.Text,S) then // sanitize, remove stuff etc.
  949. begin
  950. if not FileInTotalList(uppercase(s)) then
  951. begin
  952. if fileexists(s) then
  953. begin
  954. Error(chmnote,'Good url: '+s+'.',5);
  955. StrRec:=TStringIndex.Create;
  956. StrRec.TheString:=uppercase(s);
  957. StrRec.Strid :=0;
  958. fTotalFileList.Add(StrRec);
  959. newfiles.add(s);
  960. end
  961. else
  962. Error(chmnote,'duplicate url: '+s+'.',5);
  963. end
  964. else
  965. Error(chmnote,'duplicate url: '+s+'.',5);
  966. end
  967. else
  968. Error(chmnote,'Bad url: '+s+'.',5);
  969. end;
  970. if assigned(x.children) and (x.children.count>0) then
  971. scanitems(x.children);
  972. end;
  973. end;
  974. var
  975. localfilelist: TStringList;
  976. begin
  977. localfilelist:=TStringList.Create;
  978. scanitems(sitemap.items);
  979. scanlist(newfiles,localfilelist,true);
  980. newfiles.addstrings(localfilelist);
  981. localfilelist.free;
  982. end;
  983. procedure TChmProject.ScanHtml;
  984. var
  985. helplist,
  986. localfilelist: TStringList;
  987. i : integer;
  988. strrec : TStringIndex;
  989. begin
  990. for i:=0 to otherfiles.count-1 do
  991. begin
  992. StrRec:=TStringIndex.Create;
  993. StrRec.TheString:=uppercase(otherfiles[i]);
  994. StrRec.Strid :=0;
  995. fTotalFileList.Add(StrRec);
  996. end;
  997. for i:=0 to files.count-1 do
  998. begin
  999. StrRec:=TStringIndex.Create;
  1000. StrRec.TheString:=uppercase(files[i]);
  1001. StrRec.Strid :=0;
  1002. fTotalFileList.Add(StrRec);
  1003. end;
  1004. localfilelist:= TStringList.create;
  1005. scanlist(ffiles,localfilelist,true);
  1006. otherfiles.addstrings(localfilelist);
  1007. localfilelist.clear;
  1008. if (FDefaultpage<>'') and (not FileInTotalList(uppercase(fdefaultpage))) then
  1009. begin
  1010. Error(chmnote,'Scanning default file : '+fdefaultpage+'.',3);
  1011. helplist:=TStringlist.Create;
  1012. helplist.add(fdefaultpage);
  1013. scanlist(helplist,localfilelist,true);
  1014. otherfiles.addstrings(localfilelist);
  1015. localfilelist.clear;
  1016. end;
  1017. if assigned(FToc) then
  1018. begin
  1019. Error(chmnote,'Scanning TOC file : '+FTableOfContentsFileName+'.',3);
  1020. try
  1021. scansitemap(ftoc,localfilelist,true);
  1022. otherfiles.addstrings(localfilelist);
  1023. except
  1024. on e: Exception do
  1025. error(chmerror,'Error scanning TOC file ('+FTableOfContentsFileName+')');
  1026. end;
  1027. end;
  1028. LocalFileList.clear;
  1029. if assigned(FIndex) then
  1030. begin
  1031. Error(chmnote,'Scanning Index file : '+FIndexFileName+'.',3);
  1032. try
  1033. scansitemap(FIndex,localfilelist,true);
  1034. otherfiles.addstrings(localfilelist);
  1035. except
  1036. on e: Exception do
  1037. error(chmerror,'Error scanning index file ('+FIndexFileName+')');
  1038. end;
  1039. end;
  1040. localfilelist.free;
  1041. end;
  1042. procedure TChmProject.WriteChm(AOutStream: TStream);
  1043. var
  1044. Writer : TChmWriter;
  1045. TOCStream,
  1046. IndexStream: TFileStream;
  1047. nd : TChmContextNode;
  1048. I : Integer;
  1049. begin
  1050. LoadSiteMaps;
  1051. // Scan html for "rest" files.
  1052. If ScanHtmlContents Then
  1053. ScanHtml; // Since this is slowing we opt to skip this step, and only do this on html load.
  1054. IndexStream := nil;
  1055. TOCStream := nil;
  1056. Writer := TChmWriter.Create(AOutStream, False);
  1057. // our callback to get data
  1058. Writer.OnGetFileData := @GetData;
  1059. Writer.OnLastFile := @LastFileAdded;
  1060. // give it the list of html files
  1061. Writer.FilesToCompress.AddStrings(Files);
  1062. // give it the list of other files
  1063. Writer.FilesToCompress.AddStrings(OtherFiles);
  1064. // now some settings in the chm
  1065. Writer.DefaultPage := DefaultPage;
  1066. Writer.Title := Title;
  1067. Writer.DefaultFont := DefaultFont;
  1068. Writer.FullTextSearch := MakeSearchable;
  1069. Writer.HasBinaryTOC := MakeBinaryTOC;
  1070. Writer.HasBinaryIndex := MakeBinaryIndex;
  1071. Writer.IndexName := IndexFileName;
  1072. Writer.TocName := TableOfContentsFileName;
  1073. Writer.ReadmeMessage := ReadmeMessage;
  1074. Writer.DefaultWindow := FDefaultWindow;
  1075. Writer.LocaleID := FLocaleID;
  1076. for i:=0 to files.count-1 do
  1077. begin
  1078. nd:=TChmContextNode(files.objects[i]);
  1079. if not fileexists(IncludeTrailingPathDelimiter(FBasePath)+files[i]) then
  1080. Error(chmWarning,'File '+Files[i]+' does not exist');
  1081. if assigned(nd) and (nd.contextnumber<>0) then
  1082. Writer.AddContext(nd.ContextNumber,IncludeTrailingPathDelimiter(FBasePath)+files[i]);
  1083. end;
  1084. if FWIndows.Count>0 then
  1085. Writer.Windows:=FWIndows;
  1086. if FMergeFiles.Count>0 then
  1087. Writer.Mergefiles:=FMergeFiles;
  1088. if assigned(ftoc) then
  1089. Writer.TocSitemap:=ftoc;
  1090. // and write!
  1091. Error(chmnone,'Writing CHM '+OutputFileName,0);
  1092. Writer.Execute;
  1093. if Assigned(TOCStream) then TOCStream.Free;
  1094. if Assigned(IndexStream) then IndexStream.Free;
  1095. Writer.Free;
  1096. end;
  1097. procedure TChmProject.ShowUndefinedAnchors;
  1098. var
  1099. i:Integer;
  1100. begin
  1101. for i := 0 to fAnchorList.Count-1 do
  1102. if fAnchorList.Objects[i] <> nil then
  1103. Error(chmerror,'Anchor '+fAnchorList[i]+' undefined; first use '+TFirstReference(fAnchorList.Objects[i]).Location);
  1104. end;
  1105. procedure TChmProject.LoadSitemaps;
  1106. function tryfn(fn: string;var fnout : string):boolean;
  1107. begin
  1108. result:=true;
  1109. fnout:= IncludeTrailingPathDelimiter(ProjectDir()) + ExtractFileName(fn);
  1110. if not FileExists(fnout) then
  1111. begin
  1112. fnout:=fn;
  1113. if not FileExists(fnout) then
  1114. result:=false;
  1115. end;
  1116. end;
  1117. var
  1118. FullFileName: string;
  1119. // #IDXHDR (merged files) goes into the system file, and need to keep TOC sitemap around
  1120. begin
  1121. if FTableOfContentsFileName<>'' then
  1122. begin
  1123. if tryfn(FTableOfContentsFileName,FullFileName) then
  1124. begin
  1125. FreeAndNil(FTocStream);
  1126. FTocStream:=TMemoryStream.Create;
  1127. try
  1128. FTocStream.loadfromfile(FullFileName);
  1129. //writeln(ftableofcontentsfilename, ' ' ,ftocstream.size);
  1130. FTocStream.Position:=0;
  1131. FreeAndNil(FToc);
  1132. FToc:=TChmSiteMap.Create(sttoc);
  1133. FToc.loadfromstream(FTocStream);
  1134. except
  1135. on e:exception do
  1136. begin
  1137. error(chmerror,'Error loading TOC file '+FullFileName);
  1138. freeandnil(ftoc); freeandnil(FTocStream);
  1139. end;
  1140. end;
  1141. end
  1142. else
  1143. error(chmerror,'Can''t find TOC file '+FullFileName);
  1144. end;
  1145. if FIndexFileName<>'' then
  1146. begin
  1147. if tryfn(FIndexFileName,FullFileName) then
  1148. begin
  1149. FreeAndNil(FIndexStream);
  1150. FIndexStream:=TMemoryStream.Create;
  1151. try
  1152. FIndexStream.LoadFromFile(FullFileName);
  1153. FIndexStream.Position:=0;
  1154. FreeAndNil(FIndex);
  1155. FIndex:=TChmSiteMap.Create(stindex);
  1156. FIndex.loadfromfile(FullFileName);
  1157. Error(chmnote,'Index items:'+inttostr(findex.Items.count));
  1158. except
  1159. on e: Exception do
  1160. begin
  1161. error(chmerror,'Error loading index file '+FullFileName);
  1162. freeandnil(findex); freeandnil(findexstream);
  1163. end;
  1164. end;
  1165. end
  1166. else
  1167. error(chmerror,'Can''t find index file '+FullFileName);
  1168. end;
  1169. end;
  1170. function BoolAsStr(b: Boolean): string;
  1171. begin
  1172. if b then
  1173. Result := 'Yes'
  1174. else
  1175. Result := 'No';
  1176. end;
  1177. procedure TChmProject.SaveToHHP(AFileName: String);
  1178. var
  1179. sl: TStringList;
  1180. s : string;
  1181. i: Integer;
  1182. ContextItem: TChmContextNode;
  1183. procedure SetOption(const AKey, AValue: string);
  1184. begin
  1185. if AValue <> '' then
  1186. sl.Add(AKey + '=' + AValue);
  1187. end;
  1188. begin
  1189. sl := TStringList.Create();
  1190. try
  1191. sl.Add('[OPTIONS]');
  1192. SetOption('Title', Title);
  1193. SetOption('Compatibility', '1.1 or later');
  1194. SetOption('Compiled file', OutputFileName);
  1195. SetOption('Default Topic', DefaultPage);
  1196. SetOption('Default Font', DefaultFont);
  1197. SetOption('Default Window', DefaultWindow);
  1198. SetOption('Display compile progress', 'Yes');
  1199. //SetOption('Error log file', 'errors.log');
  1200. SetOption('Contents file', TableOfContentsFileName);
  1201. //SetOption('Auto Index', BoolAsStr(MakeBinaryIndex));
  1202. SetOption('Index file', IndexFileName);
  1203. SetOption('Binary Index', BoolAsStr(MakeBinaryIndex));
  1204. SetOption('Binary TOC', BoolAsStr(MakeBinaryTOC));
  1205. SetOption('Full-text search', BoolAsStr(MakeSearchable));
  1206. SetOption('Language', '0x' + IntToHex(LocaleID, 4));
  1207. sl.Add('');
  1208. sl.Add('[FILES]');
  1209. for i := 0 to Files.Count - 1 do
  1210. begin
  1211. s := StringReplace(Files.Strings[i], '/', '\', [rfReplaceAll]);
  1212. sl.Add(s);
  1213. end;
  1214. if MergeFiles.Count > 0 then
  1215. begin
  1216. sl.Add('');
  1217. sl.Add('[MERGE FILES]');
  1218. for i := 0 to MergeFiles.Count - 1 do
  1219. begin
  1220. sl.Add(MergeFiles.Strings[i]);
  1221. end;
  1222. end;
  1223. if Windows.Count > 0 then
  1224. begin
  1225. sl.Add('');
  1226. sl.Add('[WINDOWS]');
  1227. for i := 0 to Windows.Count-1 do
  1228. begin
  1229. TCHMWindow(Windows[i]).SaveToIni(s);
  1230. sl.Add(s);
  1231. end;
  1232. end;
  1233. if Files.Count > 0 then
  1234. begin
  1235. sl.Add('');
  1236. sl.Add('[ALIAS]');
  1237. for i := 0 to Files.Count - 1 do
  1238. begin
  1239. contextitem:=TChmContextNode(files.objects[i]);
  1240. if assigned(contextitem) then
  1241. sl.Add(ContextItem.ContextName + '=' + ContextItem.UrlName);
  1242. end;
  1243. sl.Add('');
  1244. sl.Add('[MAP]');
  1245. for I := 0 to Files.Count-1 do
  1246. begin
  1247. contextitem:=TChmContextNode(files.objects[i]);
  1248. if assigned(contextitem) then
  1249. sl.Add('#define ' + ContextItem.ContextName + ' ' + IntToStr(ContextItem.ContextNumber));
  1250. end;
  1251. end;
  1252. sl.SaveToFile(AFileName);
  1253. finally
  1254. sl.Free();
  1255. end;
  1256. end;
  1257. end.