/uXMLConfig.pas

http://github.com/silentroach/TXMLConfig · Pascal · 432 lines · 347 code · 77 blank · 8 comment · 38 complexity · 6e39a41947e286d66d721540cba5d2e1 MD5 · raw file

  1. unit uXMLConfig;
  2. interface
  3. uses
  4. Classes, XMLIntf;
  5. type
  6. EncryptedString = class(TCustomAttribute);
  7. TXMLConfigNode = class(TPersistent);
  8. TXMLConfigNodes = class
  9. private
  10. FClass: TClass;
  11. FList: TList;
  12. function GetXMLConfigNode(const Index: integer): TXMLConfigNode;
  13. function GetCount: integer;
  14. public
  15. constructor Create(const ItemClass: TClass);
  16. destructor Destroy; override;
  17. function Add(const XMLConfigNode: TXMLConfigNode): TXMLConfigNode;
  18. procedure Clear;
  19. property Items[const Index: integer]: TXMLConfigNode read GetXMLConfigNode;
  20. property Count: integer read GetCount;
  21. end;
  22. TXMLConfig = class(TXMLConfigNode)
  23. private
  24. FXMLFilePath: string;
  25. FApplicationName: string;
  26. FRootNodeName: string;
  27. FVersion: byte;
  28. function isEncryptedString(const oObject: TObject; const Propertie: string): boolean;
  29. function EncryptString(const sInput: string): string;
  30. function DecryptString(const sInput: string): string;
  31. protected
  32. procedure SaveClass(oObject: TObject; Node: IXMLNode);
  33. procedure LoadClass(oObject: TObject; Node: IXMLNode);
  34. procedure SaveObjects(oObject: TXMLConfigNodes; Node: IXMLNode);
  35. procedure LoadObjects(oObject: TXMLConfigNodes; Node: IXMLNode);
  36. public
  37. constructor Create(const AppName, XMLFilePath: string; RootNodeName: string = 'config');
  38. procedure Initialize; virtual; abstract;
  39. procedure Load;
  40. procedure Save;
  41. procedure LoadDefaults; virtual; abstract;
  42. property ApplicationName: string read FApplicationName write FApplicationName;
  43. property RootNodeName: string read FRootNodeName;
  44. property Version: byte read FVersion write FVersion default 1;
  45. end;
  46. implementation
  47. uses
  48. TypInfo, XMLDoc, SysUtils, Windows, Rtti, EncdDecd;
  49. resourcestring
  50. rsApplication = 'app';
  51. rsFormat = 'ver';
  52. rsPasswordField = 'password';
  53. { TXMLConfig }
  54. {$REGION 'Initialization'}
  55. constructor TXMLConfig.Create(const AppName, XMLFilePath: string; RootNodeName: string = 'config');
  56. begin
  57. Initialize;
  58. FApplicationName := AppName;
  59. FXMLFilePath := XMLFilePath;
  60. FRootNodeName := RootNodeName;
  61. LoadDefaults;
  62. end;
  63. procedure TXMLConfig.LoadObjects(oObject: TXMLConfigNodes; Node: IXMLNode);
  64. var
  65. i: integer;
  66. TempObject: TXMLConfigNode;
  67. begin
  68. oObject.Clear;
  69. for i := 0 to Node.ChildNodes.Count - 1 do
  70. begin
  71. TempObject := TXMLConfigNode(AllocMem(oObject.FClass.InstanceSize));
  72. oObject.FClass.InitInstance(TempObject);
  73. LoadClass(TempObject, Node.ChildNodes.Nodes[i]);
  74. oObject.Add(TempObject);
  75. end;
  76. end;
  77. {$ENDREGION}
  78. {$REGION 'Loading'}
  79. procedure TXMLConfig.LoadClass(oObject: TObject; Node: IXMLNode);
  80. procedure GetProperty(PropInfo: PPropInfo);
  81. var
  82. sValue: string;
  83. TempNode: IXMLNode;
  84. LObject: TObject;
  85. begin
  86. TempNode := Node.ChildNodes.FindNode(String(PropInfo.Name));
  87. if TempNode = nil then
  88. exit;
  89. if PropInfo.PropType^.Kind <> tkClass then
  90. sValue := TempNode.Text;
  91. case PropInfo.PropType^.Kind of
  92. tkEnumeration:
  93. if GetTypeData(PropInfo.PropType^).BaseType = TypeInfo(Boolean)
  94. then SetPropValue(oObject, PropInfo, Boolean(StrToBool(sValue)))
  95. else SetPropValue(oObject, PropInfo, StrToInt(sValue));
  96. tkInteger, tkChar, tkWChar, tkSet:
  97. SetPropValue(oObject, PropInfo, StrToInt(sValue));
  98. tkFloat:
  99. SetPropValue(oObject, PropInfo, StrToFloat(sValue));
  100. tkString, tkUString, tkLString, tkWString:
  101. if isEncryptedString(oObject, String(PropInfo.Name))
  102. then SetPropValue(oObject, PropInfo, DecryptString(sValue))
  103. else SetPropValue(oObject, PropInfo, sValue);
  104. tkClass:
  105. begin
  106. LObject := GetObjectProp(oObject, PropInfo);
  107. if LObject <> nil then
  108. LoadClass(LObject, TempNode);
  109. end;
  110. end;
  111. end;
  112. var
  113. i, iCount: integer;
  114. PropInfo: PPropInfo;
  115. PropList: PPropList;
  116. begin
  117. if not (oObject is TXMLConfigNodes) then
  118. begin
  119. iCount := GetTypeData(oObject.ClassInfo).PropCount;
  120. if iCount > 0 then
  121. begin
  122. GetMem(PropList, iCount * SizeOf(Pointer));
  123. GetPropInfos(oObject.ClassInfo, PropList);
  124. try
  125. for i := 0 to iCount - 1 do
  126. begin
  127. PropInfo := PropList[i];
  128. if PropInfo = nil then
  129. break;
  130. GetProperty(PropInfo);
  131. end;
  132. finally
  133. FreeMem(PropList, iCount * SizeOf(Pointer));
  134. end;
  135. end;
  136. end else
  137. LoadObjects(oObject as TXMLConfigNodes, Node);
  138. end;
  139. function getEncryptionKey: word;
  140. var
  141. Count: dword;
  142. sName: string;
  143. i: integer;
  144. begin
  145. Result := 0;
  146. Count := 257;
  147. SetLength(sName, Count);
  148. if not GetUserName(PChar(sName), Count) then
  149. sName := 'fakekey';
  150. for i := 1 to Length(sName) do
  151. begin
  152. if sName[i] = #0 then
  153. break;
  154. Result := Abs(Result - Ord(sName[i]));
  155. end;
  156. end;
  157. function TXMLConfig.DecryptString(const sInput: string): string;
  158. begin
  159. Result := EncdDecd.DecodeString(sInput);
  160. end;
  161. function TXMLConfig.EncryptString(const sInput: string): string;
  162. begin
  163. Result := EncdDecd.EncodeString(sInput);
  164. end;
  165. function TXMLConfig.isEncryptedString(const oObject: TObject;
  166. const Propertie: string): boolean;
  167. var
  168. ctx: TRttiContext;
  169. ctt: TRttiType;
  170. ctp: TRttiProperty;
  171. cta: TCustomAttribute;
  172. begin
  173. Result := false;
  174. ctx := TRttiContext.Create;
  175. try
  176. ctt := ctx.GetType(oObject.ClassType);
  177. for ctp in ctt.GetProperties do
  178. if ctp.Name = Propertie then
  179. begin
  180. for cta in ctp.GetAttributes do
  181. if cta is EncryptedString then
  182. begin
  183. Result := true;
  184. break;
  185. end;
  186. break;
  187. end;
  188. finally
  189. ctx.Free;
  190. end;
  191. end;
  192. procedure TXMLConfig.Load;
  193. var
  194. XMLRoot: IXMLNode;
  195. XML: IXMLDocument;
  196. begin
  197. LoadDefaults;
  198. if not FileExists(FXMLFilePath) then
  199. exit;
  200. try
  201. XML := LoadXMLDocument(FXMLFilePath);
  202. XMLRoot := XML.DocumentElement;
  203. if (XMLRoot.NodeName <> FRootNodeName) or
  204. (XMLRoot.Attributes[rsApplication] <> FApplicationName) then
  205. exit;
  206. FVersion := XMLRoot.Attributes[rsFormat];
  207. LoadClass(Self, XMLRoot);
  208. except
  209. LoadDefaults;
  210. end;
  211. end;
  212. {$ENDREGION}
  213. {$REGION 'Saving'}
  214. procedure TXMLConfig.SaveClass(oObject: TObject; Node: IXMLNode);
  215. procedure WriteProperty(PropInfo: PPropInfo);
  216. var
  217. sValue: string;
  218. LObject: TObject;
  219. TempNode: IXMLNode;
  220. begin
  221. case PropInfo.PropType^.Kind of
  222. tkEnumeration:
  223. if GetTypeData(PropInfo.PropType^).BaseType = TypeInfo(Boolean)
  224. then sValue := BoolToStr(Boolean(GetOrdProp(oObject, PropInfo)), true)
  225. else sValue := IntToStr(GetOrdProp(oObject, PropInfo));
  226. tkInteger, tkChar, tkWChar, tkSet:
  227. sValue := IntToStr(GetOrdProp(oObject, PropInfo));
  228. tkFloat:
  229. sValue := FloatToStr(GetFloatProp(oObject, PropInfo));
  230. tkString, tkUString, tkLString, tkWString:
  231. begin
  232. if isEncryptedString(oObject, String(PropInfo.Name))
  233. then sValue := EncryptString(GetUnicodeStrProp(oObject, PropInfo))
  234. else sValue := GetUnicodeStrProp(oObject, PropInfo);
  235. end;
  236. tkClass:
  237. if Assigned(PropInfo.GetProc) and Assigned(PropInfo.SetProc) then
  238. begin
  239. LObject := GetObjectProp(oObject, PropInfo);
  240. if LObject <> nil then
  241. begin
  242. TempNode := Node.AddChild(String(PropInfo.Name));
  243. SaveClass(LObject, TempNode);
  244. end;
  245. end;
  246. end;
  247. if PropInfo.PropType^.Kind <> tkClass then
  248. with Node.AddChild(String(PropInfo.Name)) do
  249. Text := sValue;
  250. end;
  251. var
  252. PropInfo: PPropInfo;
  253. PropList: PPropList;
  254. i, iCount: integer;
  255. begin
  256. if not (oObject is TXMLConfigNodes) then
  257. begin
  258. iCount := GetTypeData(oObject.ClassInfo).PropCount;
  259. if iCount > 0 then
  260. begin
  261. GetMem(PropList, iCount * SizeOf(Pointer));
  262. try
  263. GetPropInfos(oObject.ClassInfo, PropList);
  264. for i := 0 to iCount - 1 do
  265. begin
  266. PropInfo := PropList[i];
  267. if PropInfo = nil then
  268. Break;
  269. WriteProperty(PropInfo);
  270. end;
  271. finally
  272. FreeMem(PropList, iCount * SizeOf(Pointer));
  273. end;
  274. end;
  275. end else
  276. SaveObjects(oObject as TXMLConfigNodes, Node);
  277. end;
  278. procedure TXMLConfig.SaveObjects(oObject: TXMLConfigNodes; Node: IXMLNode);
  279. var
  280. i: integer;
  281. TempNode: IXMLNode;
  282. begin
  283. for i := 0 to oObject.Count - 1 do
  284. begin
  285. TempNode := Node.AddChild('Item');
  286. TempNode.Attributes['id'] := i;
  287. SaveClass(oObject.Items[i], TempNode);
  288. end;
  289. end;
  290. procedure TXMLConfig.Save;
  291. var
  292. FRootNode: IXMLNode;
  293. FBackFileName: string;
  294. XML: IXMLDocument;
  295. begin
  296. FBackFileName := ChangeFileExt(FXMLFilePath, '.bak');
  297. try
  298. if FileExists(FXMLFilePath) then
  299. DeleteFile(PChar(FXMLFilePath));
  300. try
  301. XML := NewXMLDocument;
  302. with XML do
  303. begin
  304. Encoding := 'UTF-8';
  305. Version := '1.0';
  306. end;
  307. FRootNode := XML.AddChild(FRootNodeName);
  308. FRootNode.Attributes[rsApplication] := FApplicationName;
  309. FRootNode.Attributes[rsFormat] := FVersion;
  310. SaveClass(Self, FRootNode);
  311. XML.SaveToFile(FXMLFilePath);
  312. except
  313. if FileExists(FBackFileName) then
  314. RenameFile(FBackFileName, FXMLFilePath);
  315. end;
  316. finally
  317. if FileExists(FBackFileName) then
  318. DeleteFile(PChar(FBackFileName));
  319. end;
  320. end;
  321. {$ENDREGION}
  322. { TXMLConfigNodes }
  323. function TXMLConfigNodes.Add(const XMLConfigNode: TXMLConfigNode): TXMLConfigNode;
  324. begin
  325. FList.Add(XMLConfigNode);
  326. Result := XMLConfigNode;
  327. end;
  328. procedure TXMLConfigNodes.Clear;
  329. begin
  330. while Count > 0 do
  331. FList.Delete(0);
  332. end;
  333. constructor TXMLConfigNodes.Create(const ItemClass: TClass);
  334. begin
  335. FList := TList.Create;
  336. FClass := ItemClass;
  337. end;
  338. destructor TXMLConfigNodes.Destroy;
  339. begin
  340. if Assigned(FList) then
  341. begin
  342. Clear;
  343. FList.Free;
  344. end;
  345. inherited;
  346. end;
  347. function TXMLConfigNodes.GetCount: integer;
  348. begin
  349. Result := FList.Count;
  350. end;
  351. function TXMLConfigNodes.GetXMLConfigNode(const Index: integer): TXMLConfigNode;
  352. begin
  353. Result := TXMLConfigNode(FList.Items[Index]);
  354. end;
  355. end.