xmlcfg.pp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  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. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. {
  12. TXMLConfig enables applications to use XML files for storing their
  13. configuration data
  14. }
  15. {$MODE objfpc}
  16. {$H+}
  17. unit XMLCfg;
  18. interface
  19. {off $DEFINE MEM_CHECK}
  20. uses
  21. {$IFDEF MEM_CHECK}MemCheck,{$ENDIF}
  22. SysUtils, Classes, DOM, XMLRead, XMLWrite;
  23. resourcestring
  24. SMissingPathName = 'A part of the pathname is invalid (missing)';
  25. SEscapingNecessary = 'Invalid pathname, escaping must be enabled';
  26. SWrongRootName = 'XML file has wrong root element name';
  27. type
  28. EXMLConfigError = class(Exception);
  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. FUseEscaping: Boolean;
  38. FRootName: DOMString;
  39. procedure SetFilename(const AFilename: String; ForceReload: Boolean);
  40. procedure SetFilename(const AFilename: String);
  41. procedure SetStartEmpty(AValue: Boolean);
  42. procedure SetRootName(const AValue: DOMString);
  43. protected
  44. Doc: TXMLDocument;
  45. FModified: Boolean;
  46. procedure Loaded; override;
  47. function FindNode(const APath: String; PathHasValue: boolean): TDomNode;
  48. function Escape(const s: String): String;
  49. public
  50. constructor Create(AOwner: TComponent); override;
  51. destructor Destroy; override;
  52. procedure Clear;
  53. procedure Flush; // Writes the XML file
  54. function GetValue(const APath, ADefault: String): String;
  55. function GetValue(const APath: String; ADefault: Integer): Integer;
  56. function GetValue(const APath: String; ADefault: Boolean): Boolean;
  57. procedure SetValue(const APath, AValue: String);
  58. procedure SetDeleteValue(const APath, AValue, DefValue: String);
  59. procedure SetValue(const APath: String; AValue: Integer);
  60. procedure SetDeleteValue(const APath: String; AValue, DefValue: Integer);
  61. procedure SetValue(const APath: String; AValue: Boolean);
  62. procedure SetDeleteValue(const APath: String; AValue, DefValue: Boolean);
  63. procedure DeletePath(const APath: string);
  64. procedure DeleteValue(const APath: string);
  65. property Modified: Boolean read FModified;
  66. published
  67. property Filename: String read FFilename write SetFilename;
  68. property StartEmpty: Boolean read FStartEmpty write SetStartEmpty;
  69. property UseEscaping: Boolean read FUseEscaping write FUseEscaping
  70. default True;
  71. property RootName: DOMString read FRootName write SetRootName;
  72. end;
  73. // ===================================================================
  74. implementation
  75. constructor TXMLConfig.Create(AOwner: TComponent);
  76. begin
  77. inherited Create(AOwner);
  78. FUseEscaping := True;
  79. FRootName := 'CONFIG';
  80. Doc := TXMLDocument.Create;
  81. Doc.AppendChild(Doc.CreateElement(RootName));
  82. end;
  83. destructor TXMLConfig.Destroy;
  84. begin
  85. if Assigned(Doc) then
  86. begin
  87. Flush;
  88. Doc.Free;
  89. end;
  90. inherited Destroy;
  91. end;
  92. procedure TXMLConfig.Clear;
  93. begin
  94. Doc.ReplaceChild(Doc.CreateElement(RootName), Doc.DocumentElement);
  95. end;
  96. procedure TXMLConfig.Flush;
  97. begin
  98. if Modified then
  99. begin
  100. WriteXMLFile(Doc, Filename);
  101. FModified := False;
  102. end;
  103. end;
  104. function TXMLConfig.GetValue(const APath, ADefault: String): String;
  105. var
  106. Node, Child, Attr: TDOMNode;
  107. NodeName: String;
  108. PathLen: integer;
  109. StartPos, EndPos: integer;
  110. begin
  111. Result := ADefault;
  112. PathLen := Length(APath);
  113. Node := Doc.DocumentElement;
  114. StartPos := 1;
  115. while True do
  116. begin
  117. EndPos := StartPos;
  118. while (EndPos <= PathLen) and (APath[EndPos] <> '/') do
  119. Inc(EndPos);
  120. if EndPos > PathLen then
  121. break;
  122. SetLength(NodeName, EndPos - StartPos);
  123. Move(APath[StartPos], NodeName[1], EndPos - StartPos);
  124. StartPos := EndPos + 1;
  125. Child := Node.FindNode(Escape(NodeName));
  126. if not Assigned(Child) then
  127. exit;
  128. Node := Child;
  129. end;
  130. if StartPos > PathLen then
  131. exit;
  132. SetLength(NodeName, PathLen - StartPos + 1);
  133. Move(APath[StartPos], NodeName[1], Length(NodeName));
  134. Attr := Node.Attributes.GetNamedItem(Escape(NodeName));
  135. if Assigned(Attr) then
  136. Result := Attr.NodeValue;
  137. end;
  138. function TXMLConfig.GetValue(const APath: String; ADefault: Integer): Integer;
  139. begin
  140. Result := StrToIntDef(GetValue(APath, IntToStr(ADefault)),ADefault);
  141. end;
  142. function TXMLConfig.GetValue(const APath: String; ADefault: Boolean): Boolean;
  143. var
  144. s: String;
  145. begin
  146. if ADefault then
  147. s := 'True'
  148. else
  149. s := 'False';
  150. s := GetValue(APath, s);
  151. if AnsiCompareText(s, 'TRUE')=0 then
  152. Result := True
  153. else if AnsiCompareText(s, 'FALSE')=0 then
  154. Result := False
  155. else
  156. Result := ADefault;
  157. end;
  158. procedure TXMLConfig.SetValue(const APath, AValue: String);
  159. var
  160. Node, Child: TDOMNode;
  161. NodeName: String;
  162. PathLen: integer;
  163. StartPos, EndPos: integer;
  164. begin
  165. Node := Doc.DocumentElement;
  166. PathLen := Length(APath);
  167. StartPos:=1;
  168. while True do
  169. begin
  170. EndPos := StartPos;
  171. while (EndPos <= PathLen) and (APath[EndPos] <> '/') do
  172. Inc(EndPos);
  173. if EndPos > PathLen then
  174. break;
  175. SetLength(NodeName, EndPos - StartPos);
  176. Move(APath[StartPos], NodeName[1], EndPos - StartPos);
  177. StartPos := EndPos + 1;
  178. NodeName := Escape(NodeName);
  179. Child := Node.FindNode(NodeName);
  180. if not Assigned(Child) then
  181. begin
  182. Child := Doc.CreateElement(NodeName);
  183. Node.AppendChild(Child);
  184. end;
  185. Node := Child;
  186. end;
  187. if StartPos > PathLen then
  188. exit;
  189. SetLength(NodeName, PathLen - StartPos + 1);
  190. Move(APath[StartPos], NodeName[1], Length(NodeName));
  191. NodeName := Escape(NodeName);
  192. if (not Assigned(TDOMElement(Node).GetAttributeNode(NodeName))) or
  193. (TDOMElement(Node)[NodeName] <> AValue) then
  194. begin
  195. TDOMElement(Node)[NodeName] := AValue;
  196. FModified := True;
  197. end;
  198. end;
  199. procedure TXMLConfig.SetDeleteValue(const APath, AValue, DefValue: String);
  200. begin
  201. if AValue = DefValue then
  202. DeleteValue(APath)
  203. else
  204. SetValue(APath, AValue);
  205. end;
  206. procedure TXMLConfig.SetValue(const APath: String; AValue: Integer);
  207. begin
  208. SetValue(APath, IntToStr(AValue));
  209. end;
  210. procedure TXMLConfig.SetDeleteValue(const APath: String; AValue,
  211. DefValue: Integer);
  212. begin
  213. if AValue = DefValue then
  214. DeleteValue(APath)
  215. else
  216. SetValue(APath, AValue);
  217. end;
  218. procedure TXMLConfig.SetValue(const APath: String; AValue: Boolean);
  219. begin
  220. if AValue then
  221. SetValue(APath, 'True')
  222. else
  223. SetValue(APath, 'False');
  224. end;
  225. procedure TXMLConfig.SetDeleteValue(const APath: String; AValue,
  226. DefValue: Boolean);
  227. begin
  228. if AValue = DefValue then
  229. DeleteValue(APath)
  230. else
  231. SetValue(APath,AValue);
  232. end;
  233. procedure TXMLConfig.DeletePath(const APath: string);
  234. var
  235. Node: TDomNode;
  236. begin
  237. Node := FindNode(APath, False);
  238. if (Node = nil) or (Node.ParentNode = nil) then
  239. exit;
  240. Node.ParentNode.RemoveChild(Node);
  241. FModified := True;
  242. end;
  243. procedure TXMLConfig.DeleteValue(const APath: string);
  244. var
  245. Node: TDomNode;
  246. StartPos: integer;
  247. NodeName: string;
  248. begin
  249. Node := FindNode(APath, True);
  250. if not Assigned(Node) then
  251. exit;
  252. StartPos := Length(APath);
  253. while (StartPos > 0) and (APath[StartPos] <> '/') do
  254. Dec(StartPos);
  255. NodeName := Escape(Copy(APath, StartPos+1, Length(APath) - StartPos));
  256. if (not Assigned(TDOMElement(Node).GetAttributeNode(NodeName))) then
  257. exit;
  258. TDOMElement(Node).RemoveAttribute(NodeName);
  259. FModified := True;
  260. end;
  261. procedure TXMLConfig.Loaded;
  262. begin
  263. inherited Loaded;
  264. if Length(Filename) > 0 then
  265. SetFilename(Filename); // Load the XML config file
  266. end;
  267. function TXMLConfig.FindNode(const APath: String;
  268. PathHasValue: boolean): TDomNode;
  269. var
  270. NodePath: String;
  271. StartPos, EndPos: integer;
  272. PathLen: integer;
  273. begin
  274. Result := Doc.DocumentElement;
  275. PathLen := Length(APath);
  276. StartPos := 1;
  277. while Assigned(Result) do
  278. begin
  279. EndPos := StartPos;
  280. while (EndPos <= PathLen) and (APath[EndPos] <> '/') do
  281. Inc(EndPos);
  282. if (EndPos > PathLen) and PathHasValue then
  283. exit;
  284. if EndPos = StartPos then
  285. break;
  286. SetLength(NodePath, EndPos - StartPos);
  287. Move(APath[StartPos], NodePath[1], Length(NodePath));
  288. Result := Result.FindNode(Escape(NodePath));
  289. StartPos := EndPos + 1;
  290. if StartPos > PathLen then
  291. exit;
  292. end;
  293. Result := nil;
  294. end;
  295. function TXMLConfig.Escape(const s: String): String;
  296. const
  297. AllowedChars = ['A'..'Z', 'a'..'z', '0'..'9', '.', '-', '_'];
  298. var
  299. EscapingNecessary: Boolean;
  300. i: Integer;
  301. begin
  302. if Length(s) < 1 then
  303. raise EXMLConfigError.Create(SMissingPathName);
  304. if not (s[1] in ['A'..'Z', 'a'..'z', '_']) then
  305. EscapingNecessary := True
  306. else
  307. begin
  308. EscapingNecessary := False;
  309. for i := 2 to Length(s) do
  310. if not (s[i] in AllowedChars) then
  311. begin
  312. EscapingNecessary := True;
  313. exit;
  314. end;
  315. end;
  316. if EscapingNecessary then
  317. if UseEscaping then
  318. begin
  319. Result := '_';
  320. for i := 1 to Length(s) do
  321. if s[i] in (AllowedChars - ['_']) then
  322. Result := Result + s[i]
  323. else
  324. Result := Result + '_' + IntToHex(Ord(s[i]), 2);
  325. end else
  326. raise EXMLConfigError.Create(SEscapingNecessary)
  327. else // No escaping necessary
  328. Result := s;
  329. end;
  330. procedure TXMLConfig.SetFilename(const AFilename: String; ForceReload: Boolean);
  331. begin
  332. {$IFDEF MEM_CHECK}CheckHeapWrtMemCnt('TXMLConfig.SetFilename A '+AFilename);{$ENDIF}
  333. if (not ForceReload) and (FFilename = AFilename) then
  334. exit;
  335. FFilename := AFilename;
  336. if csLoading in ComponentState then
  337. exit;
  338. Flush;
  339. FreeAndNil(Doc);
  340. if FileExists(AFilename) and (not FStartEmpty) then
  341. ReadXMLFile(Doc, AFilename);
  342. if not Assigned(Doc) then
  343. Doc := TXMLDocument.Create;
  344. if not Assigned(Doc.DocumentElement) then
  345. Doc.AppendChild(Doc.CreateElement(RootName))
  346. else
  347. if Doc.DocumentElement.NodeName <> RootName then
  348. raise EXMLConfigError.Create('XML file has wrong root element name');
  349. {$IFDEF MEM_CHECK}CheckHeapWrtMemCnt('TXMLConfig.SetFilename END');{$ENDIF}
  350. end;
  351. procedure TXMLConfig.SetFilename(const AFilename: String);
  352. begin
  353. SetFilename(AFilename, False);
  354. end;
  355. procedure TXMLConfig.SetRootName(const AValue: DOMString);
  356. var
  357. Cfg: TDOMElement;
  358. begin
  359. if AValue <> RootName then
  360. begin
  361. FRootName := AValue;
  362. Cfg := Doc.CreateElement(AValue);
  363. while Assigned(Doc.DocumentElement.FirstChild) do
  364. Cfg.AppendChild(Doc.DocumentElement.FirstChild);
  365. Doc.ReplaceChild(Cfg, Doc.DocumentElement);
  366. FModified := True;
  367. end;
  368. end;
  369. procedure TXMLConfig.SetStartEmpty(AValue: Boolean);
  370. begin
  371. if AValue <> StartEmpty then
  372. begin
  373. FStartEmpty := AValue;
  374. if (not AValue) and not Modified then
  375. SetFilename(Filename, True);
  376. end;
  377. end;
  378. end.