xmlconf.pp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  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. ReadXMLFile(Doc, AFilename);
  139. FFileName:=AFileName;
  140. finally
  141. F.Free;
  142. end;
  143. end;
  144. procedure TXMLConfig.LoadFromStream(S: TStream);
  145. begin
  146. ReadXMLFile(Doc,S);
  147. FModified := False;
  148. if (Doc.DocumentElement.NodeName<>FRootName) then
  149. raise EXMLConfigError.CreateFmt(SWrongRootName,[FRootName,Doc.DocumentElement.NodeName]);
  150. end;
  151. function TXMLConfig.GetValue(const APath: DOMString; const ADefault: DOMString): DOMString;
  152. var
  153. Node: TDOMElement;
  154. Attr: TDOMAttr;
  155. Ident: DOMString;
  156. begin
  157. Result := ADefault;
  158. Node := FindNode(APath, Ident, [pfHasValue]);
  159. if Assigned(Node) then
  160. begin
  161. Attr := Node.GetAttributeNode(Ident);
  162. if Assigned(Attr) then
  163. Result := Attr.NodeValue;
  164. end;
  165. end;
  166. function TXMLConfig.GetValue(const APath: DOMString; ADefault: Integer): Integer;
  167. begin
  168. Result := StrToIntDef(GetValue(APath, ''),ADefault);
  169. end;
  170. function TXMLConfig.GetValue(const APath: DOMString; ADefault: Boolean): Boolean;
  171. var
  172. s: DOMString;
  173. begin
  174. s := GetValue(APath, '');
  175. if WideSameText(s, 'TRUE') then
  176. Result := True
  177. else if WideSameText(s, 'FALSE') then
  178. Result := False
  179. else
  180. Result := ADefault;
  181. end;
  182. procedure TXMLConfig.SetValue(const APath: DOMString; const AValue: DOMString);
  183. var
  184. Node: TDOMElement;
  185. Attr: TDOMAttr;
  186. Ident: DOMString;
  187. begin
  188. Node := FindNode(APath, Ident, [pfHasValue, pfWriteAccess]);
  189. Attr := Node.GetAttributeNode(Ident);
  190. if (Attr = nil) or (Attr.NodeValue <> AValue) then
  191. begin
  192. Node[Ident] := AValue;
  193. FModified := True;
  194. end;
  195. end;
  196. procedure TXMLConfig.SetDeleteValue(const APath: DOMString; const AValue, DefValue: DOMString);
  197. begin
  198. if AValue = DefValue then
  199. DeleteValue(APath)
  200. else
  201. SetValue(APath, AValue);
  202. end;
  203. procedure TXMLConfig.SetValue(const APath: DOMString; AValue: Integer);
  204. begin
  205. SetValue(APath, IntToStr(AValue));
  206. end;
  207. procedure TXMLConfig.SetDeleteValue(const APath: DOMString; AValue,
  208. DefValue: Integer);
  209. begin
  210. if AValue = DefValue then
  211. DeleteValue(APath)
  212. else
  213. SetValue(APath, AValue);
  214. end;
  215. procedure TXMLConfig.SetValue(const APath: DOMString; AValue: Boolean);
  216. begin
  217. if AValue then
  218. SetValue(APath, 'True')
  219. else
  220. SetValue(APath, 'False');
  221. end;
  222. procedure TXMLConfig.SetDeleteValue(const APath: DOMString; AValue,
  223. DefValue: Boolean);
  224. begin
  225. if AValue = DefValue then
  226. DeleteValue(APath)
  227. else
  228. SetValue(APath,AValue);
  229. end;
  230. procedure TXMLConfig.DeletePath(const APath: DOMString);
  231. var
  232. Node: TDomNode;
  233. Ident: DOMString;
  234. begin
  235. Node := FindNode(APath, Ident, []);
  236. if Assigned(Node) and Assigned(Node.ParentNode) then
  237. begin
  238. Node.ParentNode.RemoveChild(Node);
  239. FPathDirty := True;
  240. FElement := nil;
  241. FModified := True;
  242. end;
  243. end;
  244. procedure TXMLConfig.DeleteValue(const APath: DOMString);
  245. var
  246. Node: TDOMElement;
  247. Ident: DOMString;
  248. Parent: TDOMNode;
  249. begin
  250. Node := FindNode(APath, Ident, [pfHasValue]);
  251. if Assigned(Node) then
  252. begin
  253. if Assigned(Node.GetAttributeNode(Ident)) then
  254. begin
  255. Node.RemoveAttribute(Ident);
  256. FModified := True;
  257. end;
  258. while (Node.FirstChild=nil) and Assigned(Node.ParentNode)
  259. and Assigned(Node.ParentNode.ParentNode) do
  260. begin
  261. if Node.HasAttributes then
  262. Break;
  263. Parent := Node.ParentNode;
  264. Parent.RemoveChild(Node);
  265. Node := TDOMElement(Parent);
  266. FPathDirty := True;
  267. FElement := nil;
  268. FModified := True;
  269. end;
  270. end;
  271. end;
  272. procedure TXMLConfig.Loaded;
  273. begin
  274. inherited Loaded;
  275. if Length(Filename) > 0 then
  276. DoSetFilename(Filename,True); // Load the XML config file
  277. end;
  278. // TODO: copied from dom.pp, make public there and delete here
  279. function CompareDOMStrings(const s1, s2: DOMPChar; l1, l2: integer): integer;
  280. var i: integer;
  281. begin
  282. Result:=l1-l2;
  283. i:=0;
  284. while (i<l1) and (Result=0) do begin
  285. Result:=ord(s1[i])-ord(s2[i]);
  286. inc(i);
  287. end;
  288. end;
  289. function TXMLConfig.FindNode(const APath: DOMString; out Ident: DOMString;
  290. Flags: TPathFlags): TDOMElement;
  291. var
  292. I: Integer;
  293. begin
  294. if FPathDirty then
  295. begin
  296. for I := 0 to FPathCount-1 do
  297. FElement := DoFindNode(FPathStack[I], FDummy, Flags - [pfHasValue]);
  298. if Assigned(FElement) then FPathDirty := False;
  299. end;
  300. Result := DoFindNode(APath, Ident, Flags);
  301. end;
  302. function TXMLConfig.DoFindNode(const APath: DOMString; var Ident: DOMString;
  303. Flags: TPathFlags): TDomElement;
  304. var
  305. StartPos, EndPos: integer;
  306. PathLen: integer;
  307. Child: TDOMNode;
  308. begin
  309. if Assigned(FElement) and (Length(APath) > 0) and (APath[1] <> '/') then
  310. Result := FElement
  311. else
  312. Result := Doc.DocumentElement;
  313. PathLen := Length(APath);
  314. StartPos := 1;
  315. if APath[StartPos] = '/' then Inc(StartPos);
  316. while Assigned(Result) do
  317. begin
  318. EndPos := StartPos;
  319. while (EndPos <= PathLen) and (APath[EndPos] <> '/') do
  320. Inc(EndPos);
  321. if (EndPos > PathLen) and (pfHasValue in Flags) then
  322. begin
  323. SetString(Ident, PWideChar(@APath[StartPos]), PathLen-StartPos+1);
  324. exit;
  325. end;
  326. if EndPos = StartPos then
  327. break;
  328. Child := Result.FirstChild;
  329. while Assigned(Child) and not ((Child.NodeType = ELEMENT_NODE)
  330. and (0 = CompareDOMStrings(DOMPChar(TDOMElement(Child).TagName), @APath[StartPos],
  331. Length(TDOMElement(Child).TagName), EndPos-StartPos))) do
  332. Child := Child.NextSibling;
  333. if (Child = nil) and (pfWriteAccess in Flags) then
  334. begin
  335. Child := Doc.CreateElementBuf(@APath[StartPos], EndPos-StartPos);
  336. Result.AppendChild(Child);
  337. end;
  338. Result := TDOMElement(Child);
  339. StartPos := EndPos + 1;
  340. if StartPos > PathLen then
  341. exit;
  342. end;
  343. Result := nil;
  344. end;
  345. procedure TXMLConfig.DoSetFilename(const AFilename: String; ForceReload: Boolean);
  346. begin
  347. if (not ForceReload) and (FFilename = AFilename) then
  348. exit;
  349. Flush;
  350. FreeAndNil(Doc);
  351. if csLoading in ComponentState then
  352. exit;
  353. if FileExists(AFilename) and not FStartEmpty then
  354. LoadFromFile(AFilename)
  355. else if not Assigned(Doc) then
  356. begin
  357. FFileName:=AFileName;
  358. Doc := TXMLDocument.Create;
  359. Doc.AppendChild(Doc.CreateElement(FRootName))
  360. end;
  361. end;
  362. procedure TXMLConfig.SetFilename(const AFilename: String);
  363. begin
  364. DoSetFilename(AFilename, False);
  365. end;
  366. procedure TXMLConfig.SetRootName(const AValue: DOMString);
  367. var
  368. Cfg, Root: TDOMElement;
  369. begin
  370. if AValue <> FRootName then
  371. begin
  372. FRootName := AValue;
  373. Root := Doc.DocumentElement;
  374. Cfg := Doc.CreateElement(AValue);
  375. while Assigned(Root.FirstChild) do
  376. Cfg.AppendChild(Root.FirstChild);
  377. Doc.ReplaceChild(Cfg, Root);
  378. FModified := True;
  379. end;
  380. end;
  381. procedure TXMLConfig.SetStartEmpty(AValue: Boolean);
  382. begin
  383. if AValue <> StartEmpty then
  384. begin
  385. FStartEmpty := AValue;
  386. if (not AValue) and not Modified then
  387. DoSetFilename(Filename, True);
  388. end;
  389. end;
  390. procedure TXMLConfig.CloseKey;
  391. begin
  392. if FPathCount > 0 then
  393. begin
  394. FPathStack[FPathCount-1] := '';
  395. Dec(FPathCount);
  396. FElement := nil;
  397. FPathDirty := True;
  398. end;
  399. end;
  400. procedure TXMLConfig.OpenKey(const aPath: DOMString);
  401. begin
  402. if aPath <> '' then
  403. begin
  404. if FPathCount >= Length(FPathStack) then
  405. SetLength(FPathStack, FPathCount + 5);
  406. FPathStack[FPathCount] := aPath;
  407. Inc(FPathCount);
  408. FElement := nil;
  409. FPathDirty := True;
  410. end;
  411. end;
  412. procedure TXMLConfig.ResetKey;
  413. var
  414. I: Integer;
  415. begin
  416. for I := Length(FPathStack)-1 downto 0 do
  417. FPathStack[I] := '';
  418. FElement := nil;
  419. FPathDirty := False;
  420. FPathCount := 0;
  421. end;
  422. end.