xmlconf.pp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  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';
  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 = class(TComponent)
  34. private
  35. FFilename: String;
  36. FStartEmpty: Boolean;
  37. FRootName: DOMString;
  38. FDummy: DOMString;
  39. FPathStack: array of WideString;
  40. FPathCount: Integer;
  41. FPathDirty: Boolean;
  42. FElement: TDOMElement;
  43. procedure DoSetFilename(const AFilename: String; ForceReload: Boolean);
  44. procedure SetFilename(const AFilename: String);
  45. procedure SetStartEmpty(AValue: Boolean);
  46. procedure SetRootName(const AValue: DOMString);
  47. function DoFindNode(const APath: WideString; var Ident: DOMString;
  48. Flags: TPathFlags): TDomElement;
  49. function FindNode(const APath: WideString; out Ident: DOMString;
  50. Flags: TPathFlags): TDOMElement;
  51. protected
  52. Doc: TXMLDocument;
  53. FModified: Boolean;
  54. procedure Loaded; override;
  55. public
  56. constructor Create(AOwner: TComponent); override;
  57. destructor Destroy; override;
  58. procedure Clear;
  59. procedure Flush; // Writes the XML file
  60. procedure OpenKey(const aPath: WideString);
  61. procedure CloseKey;
  62. procedure ResetKey;
  63. function GetValue(const APath: WideString; const ADefault: WideString): WideString; overload;
  64. function GetValue(const APath: WideString; ADefault: Integer): Integer; overload;
  65. function GetValue(const APath: WideString; ADefault: Boolean): Boolean; overload;
  66. procedure SetValue(const APath: WideString; const AValue: WideString); overload;
  67. procedure SetValue(const APath: WideString; AValue: Integer); overload;
  68. procedure SetValue(const APath: WideString; AValue: Boolean); overload;
  69. procedure SetDeleteValue(const APath: WideString; const AValue, DefValue: WideString); overload;
  70. procedure SetDeleteValue(const APath: WideString; AValue, DefValue: Integer); overload;
  71. procedure SetDeleteValue(const APath: WideString; AValue, DefValue: Boolean); overload;
  72. procedure DeletePath(const APath: WideString);
  73. procedure DeleteValue(const APath: WideString);
  74. property Modified: Boolean read FModified;
  75. published
  76. property Filename: String read FFilename write SetFilename;
  77. property StartEmpty: Boolean read FStartEmpty write SetStartEmpty;
  78. property RootName: DOMString read FRootName write SetRootName;
  79. end;
  80. // ===================================================================
  81. implementation
  82. constructor TXMLConfig.Create(AOwner: TComponent);
  83. begin
  84. inherited Create(AOwner);
  85. FRootName := 'CONFIG';
  86. Doc := TXMLDocument.Create;
  87. Doc.AppendChild(Doc.CreateElement(FRootName));
  88. end;
  89. destructor TXMLConfig.Destroy;
  90. begin
  91. if Assigned(Doc) then
  92. begin
  93. Flush;
  94. Doc.Free;
  95. end;
  96. inherited Destroy;
  97. end;
  98. procedure TXMLConfig.Clear;
  99. begin
  100. Doc.ReplaceChild(Doc.CreateElement(FRootName), Doc.DocumentElement);
  101. end;
  102. procedure TXMLConfig.Flush;
  103. begin
  104. if Modified and (Filename <> '') then
  105. begin
  106. WriteXMLFile(Doc, Filename);
  107. FModified := False;
  108. end;
  109. end;
  110. function TXMLConfig.GetValue(const APath: WideString; const ADefault: WideString): WideString;
  111. var
  112. Node: TDOMElement;
  113. Attr: TDOMAttr;
  114. Ident: DOMString;
  115. begin
  116. Result := ADefault;
  117. Node := FindNode(APath, Ident, [pfHasValue]);
  118. if Assigned(Node) then
  119. begin
  120. Attr := Node.GetAttributeNode(Ident);
  121. if Assigned(Attr) then
  122. Result := Attr.NodeValue;
  123. end;
  124. end;
  125. function TXMLConfig.GetValue(const APath: WideString; ADefault: Integer): Integer;
  126. begin
  127. Result := StrToIntDef(GetValue(APath, ''),ADefault);
  128. end;
  129. function TXMLConfig.GetValue(const APath: WideString; ADefault: Boolean): Boolean;
  130. var
  131. s: DOMString;
  132. begin
  133. s := GetValue(APath, '');
  134. if WideSameText(s, 'TRUE') then
  135. Result := True
  136. else if WideSameText(s, 'FALSE') then
  137. Result := False
  138. else
  139. Result := ADefault;
  140. end;
  141. procedure TXMLConfig.SetValue(const APath: WideString; const AValue: WideString);
  142. var
  143. Node: TDOMElement;
  144. Attr: TDOMAttr;
  145. Ident: DOMString;
  146. begin
  147. Node := FindNode(APath, Ident, [pfHasValue, pfWriteAccess]);
  148. Attr := Node.GetAttributeNode(Ident);
  149. if (Attr = nil) or (Attr.NodeValue <> AValue) then
  150. begin
  151. Node[Ident] := AValue;
  152. FModified := True;
  153. end;
  154. end;
  155. procedure TXMLConfig.SetDeleteValue(const APath: WideString; const AValue, DefValue: WideString);
  156. begin
  157. if AValue = DefValue then
  158. DeleteValue(APath)
  159. else
  160. SetValue(APath, AValue);
  161. end;
  162. procedure TXMLConfig.SetValue(const APath: WideString; AValue: Integer);
  163. begin
  164. SetValue(APath, IntToStr(AValue));
  165. end;
  166. procedure TXMLConfig.SetDeleteValue(const APath: WideString; AValue,
  167. DefValue: Integer);
  168. begin
  169. if AValue = DefValue then
  170. DeleteValue(APath)
  171. else
  172. SetValue(APath, AValue);
  173. end;
  174. procedure TXMLConfig.SetValue(const APath: WideString; AValue: Boolean);
  175. begin
  176. if AValue then
  177. SetValue(APath, 'True')
  178. else
  179. SetValue(APath, 'False');
  180. end;
  181. procedure TXMLConfig.SetDeleteValue(const APath: WideString; AValue,
  182. DefValue: Boolean);
  183. begin
  184. if AValue = DefValue then
  185. DeleteValue(APath)
  186. else
  187. SetValue(APath,AValue);
  188. end;
  189. procedure TXMLConfig.DeletePath(const APath: WideString);
  190. var
  191. Node: TDomNode;
  192. Ident: DOMString;
  193. begin
  194. Node := FindNode(APath, Ident, []);
  195. if Assigned(Node) and Assigned(Node.ParentNode) then
  196. begin
  197. Node.ParentNode.RemoveChild(Node);
  198. FPathDirty := True;
  199. FElement := nil;
  200. FModified := True;
  201. end;
  202. end;
  203. procedure TXMLConfig.DeleteValue(const APath: WideString);
  204. var
  205. Node: TDOMElement;
  206. Ident: DOMString;
  207. Parent: TDOMNode;
  208. begin
  209. Node := FindNode(APath, Ident, [pfHasValue]);
  210. if Assigned(Node) then
  211. begin
  212. if Assigned(Node.GetAttributeNode(Ident)) then
  213. begin
  214. Node.RemoveAttribute(Ident);
  215. FModified := True;
  216. end;
  217. while (Node.FirstChild=nil) and Assigned(Node.ParentNode)
  218. and Assigned(Node.ParentNode.ParentNode) do
  219. begin
  220. if Node.HasAttributes then
  221. Break;
  222. Parent := Node.ParentNode;
  223. Parent.RemoveChild(Node);
  224. Node := TDOMElement(Parent);
  225. FPathDirty := True;
  226. FElement := nil;
  227. FModified := True;
  228. end;
  229. end;
  230. end;
  231. procedure TXMLConfig.Loaded;
  232. begin
  233. inherited Loaded;
  234. if Length(Filename) > 0 then
  235. DoSetFilename(Filename,True); // Load the XML config file
  236. end;
  237. // TODO: copied from dom.pp, make public there and delete here
  238. function CompareDOMStrings(const s1, s2: DOMPChar; l1, l2: integer): integer;
  239. var i: integer;
  240. begin
  241. Result:=l1-l2;
  242. i:=0;
  243. while (i<l1) and (Result=0) do begin
  244. Result:=ord(s1[i])-ord(s2[i]);
  245. inc(i);
  246. end;
  247. end;
  248. function TXMLConfig.FindNode(const APath: WideString; out Ident: DOMString;
  249. Flags: TPathFlags): TDOMElement;
  250. var
  251. I: Integer;
  252. begin
  253. if FPathDirty then
  254. begin
  255. for I := 0 to FPathCount-1 do
  256. FElement := DoFindNode(FPathStack[I], FDummy, Flags - [pfHasValue]);
  257. if Assigned(FElement) then FPathDirty := False;
  258. end;
  259. Result := DoFindNode(APath, Ident, Flags);
  260. end;
  261. function TXMLConfig.DoFindNode(const APath: WideString; var Ident: DOMString;
  262. Flags: TPathFlags): TDomElement;
  263. var
  264. StartPos, EndPos: integer;
  265. PathLen: integer;
  266. Child: TDOMNode;
  267. begin
  268. if Assigned(FElement) and (Length(APath) > 0) and (APath[1] <> '/') then
  269. Result := FElement
  270. else
  271. Result := Doc.DocumentElement;
  272. PathLen := Length(APath);
  273. StartPos := 1;
  274. if APath[StartPos] = '/' then Inc(StartPos);
  275. while Assigned(Result) do
  276. begin
  277. EndPos := StartPos;
  278. while (EndPos <= PathLen) and (APath[EndPos] <> '/') do
  279. Inc(EndPos);
  280. if (EndPos > PathLen) and (pfHasValue in Flags) then
  281. begin
  282. SetString(Ident, PWideChar(@APath[StartPos]), PathLen-StartPos+1);
  283. exit;
  284. end;
  285. if EndPos = StartPos then
  286. break;
  287. Child := Result.FirstChild;
  288. while Assigned(Child) and not ((Child.NodeType = ELEMENT_NODE)
  289. and (0 = CompareDOMStrings(DOMPChar(TDOMElement(Child).TagName), @APath[StartPos],
  290. Length(TDOMElement(Child).TagName), EndPos-StartPos))) do
  291. Child := Child.NextSibling;
  292. if (Child = nil) and (pfWriteAccess in Flags) then
  293. begin
  294. Child := Doc.CreateElementBuf(@APath[StartPos], EndPos-StartPos);
  295. Result.AppendChild(Child);
  296. end;
  297. Result := TDOMElement(Child);
  298. StartPos := EndPos + 1;
  299. if StartPos > PathLen then
  300. exit;
  301. end;
  302. Result := nil;
  303. end;
  304. procedure TXMLConfig.DoSetFilename(const AFilename: String; ForceReload: Boolean);
  305. begin
  306. if (not ForceReload) and (FFilename = AFilename) then
  307. exit;
  308. Flush;
  309. FreeAndNil(Doc);
  310. FFilename := AFilename;
  311. if csLoading in ComponentState then
  312. exit;
  313. if FileExists(AFilename) and not FStartEmpty then
  314. ReadXMLFile(Doc, AFilename);
  315. if not Assigned(Doc) then
  316. Doc := TXMLDocument.Create;
  317. if not Assigned(Doc.DocumentElement) then
  318. Doc.AppendChild(Doc.CreateElement(FRootName))
  319. else
  320. if Doc.DocumentElement.NodeName <> FRootName then
  321. raise EXMLConfigError.Create(SWrongRootName);
  322. end;
  323. procedure TXMLConfig.SetFilename(const AFilename: String);
  324. begin
  325. DoSetFilename(AFilename, False);
  326. end;
  327. procedure TXMLConfig.SetRootName(const AValue: DOMString);
  328. var
  329. Cfg, Root: TDOMElement;
  330. begin
  331. if AValue <> FRootName then
  332. begin
  333. FRootName := AValue;
  334. Root := Doc.DocumentElement;
  335. Cfg := Doc.CreateElement(AValue);
  336. while Assigned(Root.FirstChild) do
  337. Cfg.AppendChild(Root.FirstChild);
  338. Doc.ReplaceChild(Cfg, Root);
  339. FModified := True;
  340. end;
  341. end;
  342. procedure TXMLConfig.SetStartEmpty(AValue: Boolean);
  343. begin
  344. if AValue <> StartEmpty then
  345. begin
  346. FStartEmpty := AValue;
  347. if (not AValue) and not Modified then
  348. DoSetFilename(Filename, True);
  349. end;
  350. end;
  351. procedure TXMLConfig.CloseKey;
  352. begin
  353. if FPathCount > 0 then
  354. begin
  355. FPathStack[FPathCount-1] := '';
  356. Dec(FPathCount);
  357. FElement := nil;
  358. FPathDirty := True;
  359. end;
  360. end;
  361. procedure TXMLConfig.OpenKey(const aPath: WideString);
  362. begin
  363. if aPath <> '' then
  364. begin
  365. if FPathCount >= Length(FPathStack) then
  366. SetLength(FPathStack, FPathCount + 5);
  367. FPathStack[FPathCount] := aPath;
  368. Inc(FPathCount);
  369. FElement := nil;
  370. FPathDirty := True;
  371. end;
  372. end;
  373. procedure TXMLConfig.ResetKey;
  374. var
  375. I: Integer;
  376. begin
  377. for I := Length(FPathStack) downto 0 do
  378. FPathStack[I] := '';
  379. FElement := nil;
  380. FPathDirty := False;
  381. FPathCount := 0;
  382. end;
  383. end.