xmlconf.pp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. {
  2. This file is part of the Free Component Library
  3. Implementation of TXMLConfig class
  4. Copyright (c) 1999 - 2005 by Sebastian Guenther, [email protected]
  5. Modified in 2007 by Sergei Gorelkin, [email protected]
  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. {
  13. TXMLConfig enables applications to use XML files for storing their
  14. configuration data
  15. }
  16. {$IFDEF FPC}
  17. {$MODE objfpc}
  18. {$H+}
  19. {$ENDIF}
  20. unit XMLConf;
  21. interface
  22. uses
  23. SysUtils, Classes, DOM, XMLRead, XMLWrite;
  24. resourcestring
  25. SWrongRootName = 'XML file has wrong root element name: expected "%s" but was "%s"';
  26. type
  27. EXMLConfigError = class(Exception);
  28. TPathFlags = set of (pfHasValue, pfWriteAccess);
  29. {"APath" is the path and name of a value: A XML configuration file is
  30. hierachical. "/" is the path delimiter, the part after the last "/"
  31. is the name of the value. The path components will be mapped to XML
  32. elements, the name will be an element attribute.}
  33. { TXMLConfig }
  34. TXMLConfig = class(TComponent)
  35. private
  36. FFilename: String;
  37. FStartEmpty: Boolean;
  38. FRootName: DOMString;
  39. FDummy: DOMString;
  40. FPathStack: array of DOMString;
  41. FPathCount: Integer;
  42. FPathDirty: Boolean;
  43. FElement: TDOMElement;
  44. procedure DoSetFilename(const AFilename: String; ForceReload: Boolean);
  45. procedure SetFilename(const AFilename: String);
  46. procedure SetStartEmpty(AValue: Boolean);
  47. procedure SetRootName(const AValue: DOMString);
  48. function DoFindNode(const APath: DOMString; var Ident: DOMString;
  49. Flags: TPathFlags): TDomElement;
  50. function FindNode(const APath: DOMString; out Ident: DOMString;
  51. Flags: TPathFlags): TDOMElement;
  52. protected
  53. Doc: TXMLDocument;
  54. FModified: Boolean;
  55. FReadOnly: Boolean;
  56. procedure Loaded; override;
  57. public
  58. constructor Create(AOwner: TComponent); override;
  59. destructor Destroy; override;
  60. procedure Clear;
  61. procedure Flush; // Writes the XML file
  62. procedure OpenKey(const aPath: DOMString);
  63. procedure CloseKey;
  64. procedure ResetKey;
  65. procedure SaveToFile(Const AFileName: string);
  66. procedure SaveToStream(S : TStream);
  67. procedure LoadFromFile(Const AFileName: string);
  68. procedure LoadFromStream(S : TStream);
  69. function GetValue(const APath: DOMString; const ADefault: DOMString): DOMString; overload;
  70. function GetValue(const APath: DOMString; ADefault: Integer): Integer; overload;
  71. function GetValue(const APath: DOMString; ADefault: Boolean): Boolean; overload;
  72. procedure SetValue(const APath: DOMString; const AValue: DOMString); overload;
  73. procedure SetValue(const APath: DOMString; AValue: Integer); overload;
  74. procedure SetValue(const APath: DOMString; AValue: Boolean); overload;
  75. procedure SetDeleteValue(const APath: DOMString; const AValue, DefValue: DOMString); overload;
  76. procedure SetDeleteValue(const APath: DOMString; AValue, DefValue: Integer); overload;
  77. procedure SetDeleteValue(const APath: DOMString; AValue, DefValue: Boolean); overload;
  78. procedure DeletePath(const APath: DOMString);
  79. procedure DeleteValue(const APath: DOMString);
  80. property Modified: Boolean read FModified;
  81. published
  82. property Filename: String read FFilename write SetFilename;
  83. property StartEmpty: Boolean read FStartEmpty write SetStartEmpty;
  84. property RootName: DOMString read FRootName write SetRootName;
  85. property ReadOnly: Boolean read FReadOnly write FReadOnly;
  86. end;
  87. // ===================================================================
  88. implementation
  89. constructor TXMLConfig.Create(AOwner: TComponent);
  90. begin
  91. inherited Create(AOwner);
  92. FRootName := 'CONFIG';
  93. Doc := TXMLDocument.Create;
  94. Doc.AppendChild(Doc.CreateElement(FRootName));
  95. end;
  96. destructor TXMLConfig.Destroy;
  97. begin
  98. if Assigned(Doc) then
  99. begin
  100. Flush;
  101. Doc.Free;
  102. end;
  103. inherited Destroy;
  104. end;
  105. procedure TXMLConfig.Clear;
  106. begin
  107. Doc.ReplaceChild(Doc.CreateElement(FRootName), Doc.DocumentElement);
  108. end;
  109. procedure TXMLConfig.Flush;
  110. begin
  111. if Modified and not FReadOnly then
  112. if (FFileName<>'') then
  113. SaveToFile(FFilename)
  114. end;
  115. procedure TXMLConfig.SaveToFile(const AFileName: string);
  116. Var
  117. F : TFileStream;
  118. begin
  119. F:=TFileStream.Create(AFileName,fmCreate);
  120. try
  121. SaveToStream(F);
  122. FFileName:=AFileName;
  123. finally
  124. F.Free;
  125. end;
  126. end;
  127. procedure TXMLConfig.SaveToStream(S: TStream);
  128. begin
  129. WriteXMLFile(Doc,S);
  130. FModified := False;
  131. end;
  132. procedure TXMLConfig.LoadFromFile(const AFileName: string);
  133. Var
  134. F : TFileStream;
  135. begin
  136. F:=TFileStream.Create(AFileName,fmOpenread or fmShareDenyWrite);
  137. try
  138. FFileName := '';
  139. ReadXMLFile(Doc, AFilename);
  140. FFileName:=AFileName;
  141. finally
  142. F.Free;
  143. end;
  144. end;
  145. procedure TXMLConfig.LoadFromStream(S: TStream);
  146. begin
  147. ReadXMLFile(Doc,S);
  148. FModified := False;
  149. if (Doc.DocumentElement.NodeName<>FRootName) then
  150. raise EXMLConfigError.CreateFmt(SWrongRootName,[FRootName,Doc.DocumentElement.NodeName]);
  151. end;
  152. function TXMLConfig.GetValue(const APath: DOMString; const ADefault: DOMString): DOMString;
  153. var
  154. Node: TDOMElement;
  155. Attr: TDOMAttr;
  156. Ident: DOMString;
  157. begin
  158. Result := ADefault;
  159. Node := FindNode(APath, Ident, [pfHasValue]);
  160. if Assigned(Node) then
  161. begin
  162. Attr := Node.GetAttributeNode(Ident);
  163. if Assigned(Attr) then
  164. Result := Attr.NodeValue;
  165. end;
  166. end;
  167. function TXMLConfig.GetValue(const APath: DOMString; ADefault: Integer): Integer;
  168. begin
  169. Result := StrToIntDef(GetValue(APath, ''),ADefault);
  170. end;
  171. function TXMLConfig.GetValue(const APath: DOMString; ADefault: Boolean): Boolean;
  172. var
  173. s: DOMString;
  174. begin
  175. s := GetValue(APath, '');
  176. if WideSameText(s, 'TRUE') then
  177. Result := True
  178. else if WideSameText(s, 'FALSE') then
  179. Result := False
  180. else
  181. Result := ADefault;
  182. end;
  183. procedure TXMLConfig.SetValue(const APath: DOMString; const AValue: DOMString);
  184. var
  185. Node: TDOMElement;
  186. Attr: TDOMAttr;
  187. Ident: DOMString;
  188. begin
  189. Node := FindNode(APath, Ident, [pfHasValue, pfWriteAccess]);
  190. Attr := Node.GetAttributeNode(Ident);
  191. if (Attr = nil) or (Attr.NodeValue <> AValue) then
  192. begin
  193. Node[Ident] := AValue;
  194. FModified := True;
  195. end;
  196. end;
  197. procedure TXMLConfig.SetDeleteValue(const APath: DOMString; const AValue, DefValue: DOMString);
  198. begin
  199. if AValue = DefValue then
  200. DeleteValue(APath)
  201. else
  202. SetValue(APath, AValue);
  203. end;
  204. procedure TXMLConfig.SetValue(const APath: DOMString; AValue: Integer);
  205. begin
  206. SetValue(APath, IntToStr(AValue));
  207. end;
  208. procedure TXMLConfig.SetDeleteValue(const APath: DOMString; AValue,
  209. DefValue: Integer);
  210. begin
  211. if AValue = DefValue then
  212. DeleteValue(APath)
  213. else
  214. SetValue(APath, AValue);
  215. end;
  216. procedure TXMLConfig.SetValue(const APath: DOMString; AValue: Boolean);
  217. begin
  218. if AValue then
  219. SetValue(APath, 'True')
  220. else
  221. SetValue(APath, 'False');
  222. end;
  223. procedure TXMLConfig.SetDeleteValue(const APath: DOMString; AValue,
  224. DefValue: Boolean);
  225. begin
  226. if AValue = DefValue then
  227. DeleteValue(APath)
  228. else
  229. SetValue(APath,AValue);
  230. end;
  231. procedure TXMLConfig.DeletePath(const APath: DOMString);
  232. var
  233. Node: TDomNode;
  234. Ident: DOMString;
  235. begin
  236. Node := FindNode(APath, Ident, []);
  237. if Assigned(Node) and Assigned(Node.ParentNode) then
  238. begin
  239. Node.ParentNode.RemoveChild(Node);
  240. FPathDirty := True;
  241. FElement := nil;
  242. FModified := True;
  243. end;
  244. end;
  245. procedure TXMLConfig.DeleteValue(const APath: DOMString);
  246. var
  247. Node: TDOMElement;
  248. Ident: DOMString;
  249. Parent: TDOMNode;
  250. begin
  251. Node := FindNode(APath, Ident, [pfHasValue]);
  252. if Assigned(Node) then
  253. begin
  254. if Assigned(Node.GetAttributeNode(Ident)) then
  255. begin
  256. Node.RemoveAttribute(Ident);
  257. FModified := True;
  258. end;
  259. while (Node.FirstChild=nil) and Assigned(Node.ParentNode)
  260. and Assigned(Node.ParentNode.ParentNode) do
  261. begin
  262. if Node.HasAttributes then
  263. Break;
  264. Parent := Node.ParentNode;
  265. Parent.RemoveChild(Node);
  266. Node := TDOMElement(Parent);
  267. FPathDirty := True;
  268. FElement := nil;
  269. FModified := True;
  270. end;
  271. end;
  272. end;
  273. procedure TXMLConfig.Loaded;
  274. begin
  275. inherited Loaded;
  276. if Length(Filename) > 0 then
  277. DoSetFilename(Filename,True); // Load the XML config file
  278. end;
  279. // TODO: copied from dom.pp, make public there and delete here
  280. function CompareDOMStrings(const s1, s2: DOMPChar; l1, l2: integer): integer;
  281. var i: integer;
  282. begin
  283. Result:=l1-l2;
  284. i:=0;
  285. while (i<l1) and (Result=0) do begin
  286. Result:=ord(s1[i])-ord(s2[i]);
  287. inc(i);
  288. end;
  289. end;
  290. function TXMLConfig.FindNode(const APath: DOMString; out Ident: DOMString;
  291. Flags: TPathFlags): TDOMElement;
  292. var
  293. I: Integer;
  294. begin
  295. if FPathDirty then
  296. begin
  297. for I := 0 to FPathCount-1 do
  298. FElement := DoFindNode(FPathStack[I], FDummy, Flags - [pfHasValue]);
  299. if Assigned(FElement) then FPathDirty := False;
  300. end;
  301. Result := DoFindNode(APath, Ident, Flags);
  302. end;
  303. function TXMLConfig.DoFindNode(const APath: DOMString; var Ident: DOMString;
  304. Flags: TPathFlags): TDomElement;
  305. var
  306. StartPos, EndPos: integer;
  307. PathLen: integer;
  308. Child: TDOMNode;
  309. begin
  310. if Assigned(FElement) and (Length(APath) > 0) and (APath[1] <> '/') then
  311. Result := FElement
  312. else
  313. Result := Doc.DocumentElement;
  314. PathLen := Length(APath);
  315. StartPos := 1;
  316. if APath[StartPos] = '/' then Inc(StartPos);
  317. while Assigned(Result) do
  318. begin
  319. EndPos := StartPos;
  320. while (EndPos <= PathLen) and (APath[EndPos] <> '/') do
  321. Inc(EndPos);
  322. if (EndPos > PathLen) and (pfHasValue in Flags) then
  323. begin
  324. SetString(Ident, PWideChar(@APath[StartPos]), PathLen-StartPos+1);
  325. exit;
  326. end;
  327. if EndPos = StartPos then
  328. break;
  329. Child := Result.FirstChild;
  330. while Assigned(Child) and not ((Child.NodeType = ELEMENT_NODE)
  331. and (0 = CompareDOMStrings(DOMPChar(TDOMElement(Child).TagName), @APath[StartPos],
  332. Length(TDOMElement(Child).TagName), EndPos-StartPos))) do
  333. Child := Child.NextSibling;
  334. if (Child = nil) and (pfWriteAccess in Flags) then
  335. begin
  336. Child := Doc.CreateElementBuf(@APath[StartPos], EndPos-StartPos);
  337. Result.AppendChild(Child);
  338. end;
  339. Result := TDOMElement(Child);
  340. StartPos := EndPos + 1;
  341. if StartPos > PathLen then
  342. exit;
  343. end;
  344. Result := nil;
  345. end;
  346. procedure TXMLConfig.DoSetFilename(const AFilename: String; ForceReload: Boolean);
  347. begin
  348. if (not ForceReload) and (FFilename = AFilename) then
  349. exit;
  350. Flush;
  351. FreeAndNil(Doc);
  352. if csLoading in ComponentState then
  353. begin
  354. FFilename := AFilename;
  355. exit;
  356. end;
  357. if FileExists(AFilename) and not FStartEmpty then
  358. LoadFromFile(AFilename)
  359. else if not Assigned(Doc) then
  360. begin
  361. FFileName:=AFileName;
  362. Doc := TXMLDocument.Create;
  363. Doc.AppendChild(Doc.CreateElement(FRootName))
  364. end;
  365. end;
  366. procedure TXMLConfig.SetFilename(const AFilename: String);
  367. begin
  368. DoSetFilename(AFilename, False);
  369. end;
  370. procedure TXMLConfig.SetRootName(const AValue: DOMString);
  371. var
  372. Cfg, Root: TDOMElement;
  373. begin
  374. if AValue <> FRootName then
  375. begin
  376. FRootName := AValue;
  377. if not (ComponentState * [csLoading,csDesigning] = []) then
  378. Exit;
  379. Root := Doc.DocumentElement;
  380. Cfg := Doc.CreateElement(AValue);
  381. while Assigned(Root.FirstChild) do
  382. Cfg.AppendChild(Root.FirstChild);
  383. Doc.ReplaceChild(Cfg, Root);
  384. FModified := True;
  385. end;
  386. end;
  387. procedure TXMLConfig.SetStartEmpty(AValue: Boolean);
  388. begin
  389. if AValue <> StartEmpty then
  390. begin
  391. FStartEmpty := AValue;
  392. if (not AValue) and not Modified then
  393. DoSetFilename(Filename, True);
  394. end;
  395. end;
  396. procedure TXMLConfig.CloseKey;
  397. begin
  398. if FPathCount > 0 then
  399. begin
  400. FPathStack[FPathCount-1] := '';
  401. Dec(FPathCount);
  402. FElement := nil;
  403. FPathDirty := True;
  404. end;
  405. end;
  406. procedure TXMLConfig.OpenKey(const aPath: DOMString);
  407. begin
  408. if aPath <> '' then
  409. begin
  410. if FPathCount >= Length(FPathStack) then
  411. SetLength(FPathStack, FPathCount + 5);
  412. FPathStack[FPathCount] := aPath;
  413. Inc(FPathCount);
  414. FElement := nil;
  415. FPathDirty := True;
  416. end;
  417. end;
  418. procedure TXMLConfig.ResetKey;
  419. var
  420. I: Integer;
  421. begin
  422. for I := Length(FPathStack)-1 downto 0 do
  423. FPathStack[I] := '';
  424. FElement := nil;
  425. FPathDirty := False;
  426. FPathCount := 0;
  427. end;
  428. end.