1 {
2 *****************************************************************************
3 This file is part of the Lazarus Component Library (LCL)
4
5 See the file COPYING.modifiedLGPL.txt, included in this distribution,
6 for details about the license.
7 *****************************************************************************
8
9 Author: Mattias Gaertner
10
11 Abstract:
12 Methods and types for simple HTML help.
13 }
14 unit LazHelpHTML;
15
16 {$mode objfpc}{$H+}
17
18 interface
19
20 uses
21 {$IFDEF MSWindows}Windows, ShellApi,{$ENDIF} // needed for ShellExecute, not good for WinCE, issue #36558
22 Classes, SysUtils,
23 // LazUtils
24 LazFileUtils, UTF8Process, LazUTF8, LazConfigStorage,
25 // LCL
26 LCLProc, LCLIntf, LCLStrConsts, HelpIntfs, LazHelpIntf;
27
28 type
29 { THTMLHelpDatabase
30
31 KeywordPrefix: if set, then the database will handle all Keywords
32 beginning with this value. And when the path is created by replacing
33 the prefix with the BaseURL.
34 For example:
35 Put a THTMLHelpDatabase on a form.
36 Set AutoRegister to true.
37 Set KeywordPrefix to 'MyHelp/'
38 Set BaseURL to 'file://'
39
40 Put a THTMLBrowserHelpViewer on the form.
41 Set AutoRegister to true.
42 Set BrowserPath to '/usr/bin/mozilla'
43
44 Put a TEdit on a form.
45 Set HelpType to htKeyword
46 Set HelpKeyword to 'MyHelp/page.html'
47
48 Run the program.
49 Focus the edit field and press F1. The page 'page.html' will be shown.
50 }
51
52 THTMLHelpDatabase = class(THelpDatabase)
53 private
54 FBaseURL: string;
55 FDefaultBaseURL: string;
56 FKeywordPrefix: string;
57 FKeywordPrefixNode: THelpNode;
IsBaseURLStorednull58 function IsBaseURLStored: boolean;
59 procedure SetBaseURL(const AValue: string);
60 procedure SetBuiltInBaseURL(const AValue: string);
61 procedure SetDefaultBaseURL(const AValue: string);
62 public
63 constructor Create(TheOwner: TComponent); override;
64 destructor Destroy; override;
ShowURLnull65 function ShowURL(const URL, Title: string;
66 var ErrMsg: string): TShowHelpResult; virtual;
ShowHelpnull67 function ShowHelp(Query: THelpQuery; BaseNode, NewNode: THelpNode;
68 QueryItem: THelpQueryItem;
69 var ErrMsg: string): TShowHelpResult; override;
GetNodesForKeywordnull70 function GetNodesForKeyword(const HelpKeyword: string;
71 var ListOfNodes: THelpNodeQueryList;
72 var ErrMsg: string): TShowHelpResult; override;
GetEffectiveBaseURLnull73 function GetEffectiveBaseURL: string;
74 procedure Load(Storage: TConfigStorage); override;
75 procedure Save(Storage: TConfigStorage); override;
76 property DefaultBaseURL: string read FDefaultBaseURL write SetDefaultBaseURL;// used, if BaseURL is empty
77 published
78 property BuiltInBaseURL: string read FDefaultBaseURL write SetBuiltInBaseURL;// read only, shown in the IDE help options
79 property BaseURL: string read FBaseURL write SetBaseURL stored IsBaseURLStored;
80 property AutoRegister;
81 property KeywordPrefix: string read FKeywordPrefix write FKeywordPrefix;// see above
82 end;
83
84
85 { THTMLBrowserHelpViewer
86
87 If no browser is specified it searches for a common browser. }
88
89 TOnFindDefaultBrowser = procedure(var DefaultBrowser, Params: string) of object;
90
91 THTMLBrowserHelpViewer = class(THelpViewer)
92 private
93 FBrowserParams: string;
94 FBrowserPath: string;
95 FDefaultBrowser: string;
96 FDefaultBrowserParams: string;
97 FOnFindDefaultBrowser: TOnFindDefaultBrowser;
98 procedure SetBrowserParams(const AValue: string);
99 procedure SetBrowserPath(const AValue: string);
100 public
101 constructor Create(TheOwner: TComponent); override;
ShowNodenull102 function ShowNode(Node: THelpNode; var ErrMsg: string): TShowHelpResult; override;
103 procedure FindDefaultBrowser(out Browser, Params: string); virtual;
104 procedure Assign(Source: TPersistent); override;
105 procedure Load(Storage: TConfigStorage); override;
106 procedure Save(Storage: TConfigStorage); override;
GetLocalizedNamenull107 function GetLocalizedName: string; override;
108 property OnFindDefaultBrowser: TOnFindDefaultBrowser
109 read FOnFindDefaultBrowser write FOnFindDefaultBrowser;
110 published
111 property BrowserPath: string read FBrowserPath write SetBrowserPath;
112 property BrowserParams: string read FBrowserParams write SetBrowserParams;
113 property AutoRegister;
114 end;
115
116
117 procedure Register;
118
119 implementation
120
121 procedure Register;
122 begin
123 RegisterComponents('System',[THTMLHelpDatabase,THTMLBrowserHelpViewer]);
124 end;
125
126 { THTMLHelpDatabase }
127
128 procedure THTMLHelpDatabase.SetBaseURL(const AValue: string);
129 begin
130 if FBaseURL=AValue then exit;
131 //debugln('THTMLHelpDatabase.SetBaseURL ',dbgsName(Self),' ',AValue);
132 if AValue=DefaultBaseURL then
133 FBaseURL:=''
134 else
135 FBaseURL:=AValue;
136 end;
137
138 procedure THTMLHelpDatabase.SetBuiltInBaseURL(const AValue: string);
139 begin
140 if AValue=BuiltInBaseURL then exit;
141 raise Exception.Create(rsTheBuiltInURLIsReadOnlyChangeTheBaseURLInstead);
142 end;
143
144 procedure THTMLHelpDatabase.SetDefaultBaseURL(const AValue: string);
145 begin
146 if FDefaultBaseURL=AValue then exit;
147 if (FBaseURL='') or (FBaseURL=FDefaultBaseURL) then
148 FBaseURL:=FDefaultBaseURL;
149 FDefaultBaseURL:=AValue;
150 end;
151
THTMLHelpDatabase.IsBaseURLStorednull152 function THTMLHelpDatabase.IsBaseURLStored: boolean;
153 begin
154 Result:=FBaseURL<>DefaultBaseURL;
155 end;
156
157 constructor THTMLHelpDatabase.Create(TheOwner: TComponent);
158 begin
159 inherited Create(TheOwner);
160 AddSupportedMimeType('text/html');
161 end;
162
163 destructor THTMLHelpDatabase.Destroy;
164 begin
165 FreeAndNil(FKeywordPrefixNode);
166 inherited Destroy;
167 end;
168
THTMLHelpDatabase.ShowURLnull169 function THTMLHelpDatabase.ShowURL(const URL, Title: string; var ErrMsg: string
170 ): TShowHelpResult;
171 var
172 URLType, URLPath, URLParams: string;
173 BaseURLType, BaseURLPath, BaseURLParams: string;
174 Viewer: THelpViewer;
175 EffBaseURL: String;
176 Node: THelpNode;
177 FullURL: String;
178 begin
179 //DebugLn('THTMLHelpDatabase.ShowURL A URL="',URL,'" Title="',Title,'"');
180
181 // find HTML viewer
182 Result:=FindViewer('text/html',ErrMsg,Viewer);
183 if Result<>shrSuccess then exit;
184
185 // make URL absolute
186 SplitURL(URL,URLType,URLPath,URLParams);
187 //debugln('THTMLHelpDatabase.ShowURL A NewNode.URL=',URL,' URLType=',URLType,' URLPath=',URLPath,' URLParams=',URLParams);
188
189 if URLType='file' then begin
190 if not URLFilenameIsAbsolute(URLPath) then begin
191 EffBaseURL:=GetEffectiveBaseURL;
192 //DebugLn('THTMLHelpDatabase.ShowURL file relative, making absolute... EffBaseURL="',EffBaseURL,'"');
193 if EffBaseURL<>'' then begin
194 SplitURL(EffBaseURL,BaseURLType,BaseURLPath,BaseURLParams);
195 if (BaseURLPath<>'') then
196 URLPath:=BaseURLPath+URLPath;
197 URLType:=BaseURLType;
198 end;
199 end;
200 if (URLType='file') and (not URLFilenameIsAbsolute(URLPath)) then
201 URLPath:=FilenameToURLPath(TrimFilename(GetCurrentDirUTF8+PathDelim))+URLPath;
202
203 if (URLType='file') and (not FileExistsUTF8(URLPath)) then begin
204 Result:=shrContextNotFound;
205 ErrMsg:=Format(hhsHelpTheHelpDatabaseWasUnableToFindFile, [ID, URLPath]);
206 exit;
207 end;
208 end;
209 FullURL:=CombineURL(URLType,URLPath,URLParams);
210 {$IFNDEF DisableChecks}
211 debugln('THTMLHelpDatabase.ShowURL B URL=',URL,' URLType=',URLType,' URLPath=',URLPath,' URLParams=',URLParams);
212 {$ENDIF}
213
214 // call viewer
215 Node:=nil;
216 try
217 Node:=THelpNode.CreateURL(Self,Title,FullURL);
218 Result:=Viewer.ShowNode(Node,ErrMsg);
219 finally
220 Node.Free;
221 end;
222 end;
223
ShowHelpnull224 function THTMLHelpDatabase.ShowHelp(Query: THelpQuery;
225 BaseNode, NewNode: THelpNode; QueryItem: THelpQueryItem;
226 var ErrMsg: string): TShowHelpResult;
227 begin
228 ErrMsg:='';
229 Result:=shrContextNotFound;
230 if NewNode.URLValid then begin
231 Result:=ShowURL(NewNode.URL,NewNode.Title,ErrMsg);
232 end else begin
233 Result:=shrContextNotFound;
234 ErrMsg:='THTMLHelpDatabase.ShowHelp Node.URLValid=false Node.URL="'+NewNode.URL+'"';
235 end;
236 end;
237
GetNodesForKeywordnull238 function THTMLHelpDatabase.GetNodesForKeyword(const HelpKeyword: string;
239 var ListOfNodes: THelpNodeQueryList; var ErrMsg: string): TShowHelpResult;
240 var
241 Path: String;
242 begin
243 Result:=inherited GetNodesForKeyword(HelpKeyword, ListOfNodes, ErrMsg);
244 if Result<>shrSuccess then exit;
245
246 if not (csDesigning in ComponentState)
247 and (KeywordPrefix<>'')
248 and (LeftStr(HelpKeyword,length(KeywordPrefix))=KeywordPrefix) then begin
249 // HelpKeyword starts with KeywordPrefix -> add default node
250 if FKeywordPrefixNode=nil then
251 FKeywordPrefixNode:=THelpNode.CreateURL(Self,'','');
252 Path:=copy(HelpKeyword,length(KeywordPrefix)+1,length(HelpKeyword));
253 FKeywordPrefixNode.Title:='Show page '+Path;
254 FKeywordPrefixNode.URL:='file://'+Path;
255 CreateNodeQueryListAndAdd(FKeywordPrefixNode,nil,ListOfNodes,true);
256 end;
257 end;
258
GetEffectiveBaseURLnull259 function THTMLHelpDatabase.GetEffectiveBaseURL: string;
260 begin
261 Result:='';
262 if BaseURL<>'' then begin
263 Result:=BaseURL;
264 if (Databases<>nil) then begin
265 Databases.SubstituteMacros(Result);
266 Result:=FilenameToURLPath(Result);
267 end;
268 //debugln('THTMLHelpDatabase.GetEffectiveBaseURL using BaseURL="',Result,'"');
269 end else if (BasePathObject<>nil) and (Databases<>nil) then begin
270 Result:=Databases.GetBaseURLForBasePathObject(BasePathObject);
271 //debugln('THTMLHelpDatabase.GetEffectiveBaseURL using BasePathObject="',Result,'"');
272 end;
273 if (Result='') and (DefaultBaseURL<>'') then begin
274 Result:=DefaultBaseURL;
275 if (Databases<>nil) then begin
276 Databases.SubstituteMacros(Result);
277 Result:=FilenameToURLPath(Result);
278 end;
279 //debugln('THTMLHelpDatabase.GetEffectiveBaseURL using DefaultBaseURL="',Result,'"');
280 end;
281 Result:=AppendURLPathDelim(Result);
282 end;
283
284 procedure THTMLHelpDatabase.Load(Storage: TConfigStorage);
285 begin
286 inherited Load(Storage);
287 BaseURL:=Storage.GetValue('BaseURL/Value',DefaultBaseURL);
288 end;
289
290 procedure THTMLHelpDatabase.Save(Storage: TConfigStorage);
291 begin
292 inherited Save(Storage);
293 Storage.SetDeleteValue('BaseURL/Value',BaseURL,DefaultBaseURL);
294 end;
295
296 { THTMLBrowserHelpViewer }
297
298 procedure THTMLBrowserHelpViewer.SetBrowserParams(const AValue: string);
299 begin
300 if FBrowserParams=AValue then exit;
301 FBrowserParams:=AValue;
302 end;
303
304 procedure THTMLBrowserHelpViewer.SetBrowserPath(const AValue: string);
305 begin
306 if FBrowserPath=AValue then exit;
307 FBrowserPath:=AValue;
308 end;
309
310 constructor THTMLBrowserHelpViewer.Create(TheOwner: TComponent);
311 begin
312 inherited Create(TheOwner);
313 AddSupportedMimeType('text/html');
314 FBrowserParams:='%s';
315 ParameterHelp:=hhsHelpTheMacroSInBrowserParamsWillBeReplacedByTheURL;
316 end;
317
THTMLBrowserHelpViewer.ShowNodenull318 function THTMLBrowserHelpViewer.ShowNode(Node: THelpNode; var ErrMsg: string
319 ): TShowHelpResult;
320 var
321 URLMacroPos: LongInt;
322 BrowserProcess: TProcessUTF8;
323 Executable, ParamsStr: String;
324 IsShellStr: Boolean = false;
325 begin
326 Result:=shrViewerError;
327 ErrMsg:='';
328 if (not Node.URLValid) then begin
329 ErrMsg:='THTMLBrowserHelpViewer.ShowNode Node.URLValid=false';
330 exit;
331 end;
332 if (Node.URL='') then begin
333 ErrMsg:='THTMLBrowserHelpViewer.ShowNode Node.URL empty';
334 exit;
335 end;
336
337 // check browser path
338 Executable:=BrowserPath;
339 ParamsStr:=BrowserParams;
340 if Executable='' then
341 FindDefaultBrowser(Executable, ParamsStr);
342 if Executable='' then begin
343 if (HelpDatabases<>nil)
344 and (CompareText(HelpDatabases.ClassName,'TIDEHelpDatabases')=0) then
345 ErrMsg:=Format(hhsHelpNoHTMLBrowserFoundPleaseDefineOne,[LineEnding])
346 else
347 ErrMsg:=hhsHelpNoHTMLBrowserFound;
348 exit;
349 end;
350 {$ifdef windows}
351 //The result of FindDefaultBrowser may or may not be quoted on Windows
352 //Since on Windows, a filename cannot contain a double quote, we simply remove them
353 //otherwise FileExistsUf8 and FileIsExecutable fail. Issue #0030502
354 if (Length(Executable) > 1) and (Executable[1] = '"') and (Executable[Length(Executable)] = '"') then
355 Executable := Copy(Executable, 2, Length(Executable)-2);
356 // Preparation of special handling for Microsoft Edge in Win10, issue #35659
357 IsShellStr := UpperCase(LeftStr(Executable,Pos(':',Executable)))='SHELL:';
358 {$endif windows}
359 if not IsShellStr then begin
360 if (not FileExistsUTF8(Executable)) then begin
361 ErrMsg:=Format(hhsHelpBrowserNotFound, [Executable]);
362 exit;
363 end;
364 if (not FileIsExecutable(Executable)) then begin
365 ErrMsg:=Format(hhsHelpBrowserNotExecutable, [Executable]);
366 exit;
367 end;
368 end;
369 //debugln('THTMLBrowserHelpViewer.ShowNode Node.URL=',Node.URL);
370
371 // create params and replace %ParamsStr for URL
372 URLMacroPos:=Pos('%s',ParamsStr);
373 if URLMacroPos>=1 then
374 ReplaceSubstring(ParamsStr,URLMacroPos,2,Node.URL)
375 else begin
376 if ParamsStr<>'' then
377 ParamsStr:=ParamsStr+' ';
378 ParamsStr:=ParamsStr+Node.URL;
379 end;
380
381 {$IFNDEF DisableChecks}
382 debugln('THTMLBrowserHelpViewer.ShowNode Executable="',Executable,'" Params="',ParamsStr,'"');
383 {$ENDIF}
384
385 // run
386 {$IFDEF MSWindows} // not good for WinCE! Issue #36558.
387 // Special handling for Microsoft Edge in Win10, issue #35659
388 if IsShellStr then begin
389 if ShellExecute(0,'open',PChar(Executable),PChar(ParamsStr),'',SW_SHOWNORMAL)<=32 then
390 ErrMsg := Format(hhsHelpErrorWhileExecuting,[Executable+' ',ParamsStr, LineEnding, 'ShellExecute'])
391 else
392 Result := shrSuccess;
393 end else
394 {$ENDIF}
395 try
396 BrowserProcess:=TProcessUTF8.Create(nil);
397 try
398 BrowserProcess.InheritHandles:=false;
399 BrowserProcess.Executable:=Executable;
400 SplitCmdLineParams(ParamsStr,BrowserProcess.Parameters);
401 BrowserProcess.Execute;
402 finally
403 BrowserProcess.Free;
404 end;
405 Result:=shrSuccess;
406 except
407 on E: Exception do begin
408 ErrMsg:=Format(hhsHelpErrorWhileExecuting, [Executable+' '+ParamsStr, LineEnding, E.Message]);
409 end;
410 end;
411 end;
412
413 procedure THTMLBrowserHelpViewer.FindDefaultBrowser(out Browser, Params: string);
414 begin
415 if FDefaultBrowser='' then
416 begin
417 if Assigned(OnFindDefaultBrowser) then
418 OnFindDefaultBrowser(FDefaultBrowser, FDefaultBrowserParams);
419 end;
420 if FDefaultBrowser = '' then
421 LCLIntf.FindDefaultBrowser(FDefaultBrowser, FDefaultBrowserParams);
422
423 Browser := FDefaultBrowser;
424 Params := FDefaultBrowserParams;
425
426 //DebugLn('THTMLBrowserHelpViewer.FindDefaultBrowser Browser=',Browser,' Params=',Params);
427 end;
428
429 procedure THTMLBrowserHelpViewer.Assign(Source: TPersistent);
430 var
431 Viewer: THTMLBrowserHelpViewer;
432 begin
433 if Source is THTMLBrowserHelpViewer then begin
434 Viewer:=THTMLBrowserHelpViewer(Source);
435 BrowserPath:=Viewer.BrowserPath;
436 BrowserParams:=Viewer.BrowserParams;
437 end;
438 inherited Assign(Source);
439 end;
440
441 procedure THTMLBrowserHelpViewer.Load(Storage: TConfigStorage);
442 begin
443 BrowserPath:=Storage.GetValue('Browser/Path','');
444 BrowserParams:=Storage.GetValue('Browser/Params','%s');
445 end;
446
447 procedure THTMLBrowserHelpViewer.Save(Storage: TConfigStorage);
448 begin
449 Storage.SetDeleteValue('Browser/Path',BrowserPath,'');
450 Storage.SetDeleteValue('Browser/Params',BrowserParams,'%s');
451 end;
452
THTMLBrowserHelpViewer.GetLocalizedNamenull453 function THTMLBrowserHelpViewer.GetLocalizedName: string;
454 begin
455 Result:='HTML Browser';
456 end;
457
458 end.
459
460