MiConfigXML.pas 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. {
  2. MiConfigXml
  3. ===========
  4. Por Tito Hinostroza 29/07/2016
  5. Descripción
  6. ===========
  7. Unidad con rutinas de lectura/escritura de propiedades en archivos XML. Permite crear
  8. fácilmente, una ventana de configuración, con las opciones: ACEPTAR y CANCELAR.
  9. Es similar a MiConfigINI, pero trabaja con archivos XML.
  10. Para alamacenar las propiedades, se debe crear un objeto TMiConfigXML. Sin embargo,
  11. la unidad crea por defecto, una instancia de TMiConfigXML, llamada "cfgFile", que toma
  12. como nombre <nombre del proyecto>.xml
  13. Tiene como dependencia a la librería MisUtils.
  14. Por Tito Hinostroza 29/07/2016
  15. }
  16. unit MiConfigXML;
  17. {$mode objfpc}{$H+}
  18. interface
  19. uses
  20. Classes, SysUtils, Graphics, Forms, LCLType, Laz2_XMLCfg, MiConfigBasic;
  21. type
  22. { TMiConfigXML }
  23. {Clase base que es usada para manejar los campos de configuración.}
  24. TMiConfigXML = class(TMiConfigBasic)
  25. protected
  26. fileName : string; //archivo XML
  27. function DefaultFileName: string;
  28. procedure FileProperty(xmlCfg: TXMLConfig; const r: TParElem; FileToProp: boolean);
  29. function LoadXMLFile(filName: string; out xmlCfg: TXMLConfig): boolean;
  30. public
  31. secINI: string; //sección donde se guardarán los datos en un archivo INI
  32. procedure VerifyFile;
  33. function FileToProperties: boolean; virtual;
  34. function FileToPropertiesCat(xmlFil: string; cat: integer): boolean;
  35. function PropertiesToWindowCat(cat: integer): boolean;
  36. function PropertiesToFile: boolean; virtual;
  37. function GetFileName: string;
  38. procedure SetFileName(AValue: string);
  39. public //Constructor y Destructor
  40. constructor Create(XMLfile0: string);
  41. destructor Destroy; override;
  42. end;
  43. var
  44. cfgFile: TMiConfigXML; //Default XML Config file
  45. implementation
  46. { TMiConfigXML }
  47. function TMiConfigXML.LoadXMLFile(filName: string; out xmlCfg: TXMLConfig): boolean;
  48. {Carga el archivo "filName" en xmlCfg. Si hay error, actualiza "MsjError" y
  49. devuelve FALSE. Función creada para uso interno de la clase.}
  50. begin
  51. msjErr := '';
  52. Result := true;
  53. if not FileExists(filName) then begin
  54. ctlErr := nil;
  55. MsjErr := 'XML file does not exist.'; //errro
  56. exit(false); //para que no intente leer
  57. end;
  58. try
  59. xmlCfg := TXMLConfig.Create(nil);
  60. xmlCfg.Filename := filName; //lee archivo XML, al asignar propiedad
  61. except
  62. ctlErr := nil;
  63. MsjErr := Format('Error reading XML file: %s', [filName]);
  64. xmlCfg.Free;
  65. exit(false);
  66. end;
  67. end;
  68. function TMiConfigXML.DefaultFileName: string;
  69. {Devuelve el nombre por defecto del archvio de configuración}
  70. begin
  71. Result := ChangeFileExt(Application.ExeName,'.xml');
  72. end;
  73. procedure TMiConfigXML.VerifyFile;
  74. //Verifica si el archivo XML "FileName" existe. Si no, muestra un mensaje y lo crea.
  75. var
  76. F: textfile;
  77. begin
  78. if not FileExists(fileName) then begin
  79. Application.MessageBox(
  80. Pchar(Format('No XML file found: %s', [fileName])),
  81. '', MB_ICONERROR);
  82. //Crea uno vacío para leer las opciones por defecto
  83. AssignFile(F, fileName);
  84. Rewrite(F);
  85. writeln(F, '<?xml version="1.0" encoding="utf-8"?>');
  86. writeln(F, '<CONFIG>');
  87. writeln(F, '</CONFIG>');
  88. CloseFile(F);
  89. end;
  90. end;
  91. procedure TMiConfigXML.FileProperty(xmlCfg: TXMLConfig; const r: TParElem; FileToProp: boolean);
  92. {Permite leer o escribir una propiedad en el archivo XML}
  93. var
  94. s, defStr: String;
  95. c: TColor;
  96. list: TStringList;
  97. begin
  98. if r.varRef = nil then exit; //se inició con NIL
  99. case r.asType of
  100. tp_Int, tp_Int_TEdit, tp_Int_TSpinEdit, tp_Int_TRadioGroup:
  101. if FileToProp then begin //lee entero
  102. r.AsInteger := xmlCfg.GetValue(r.asLabel + '/Val', r.defInt)
  103. end else begin
  104. xmlCfg.SetValue(r.asLabel + '/Val', r.AsInteger) ;
  105. end;
  106. //---------------------------------------------------------------------
  107. tp_Dbl, tp_Dbl_TEdit, tp_Dbl_TFloatSpinEdit:
  108. //No hay métodos para leer/escribir números flotantes. Se usarán cadena
  109. if FileToProp then begin
  110. defStr := FloatToStr(r.defDbl);
  111. s := xmlCfg.GetValue(r.asLabel + '/Val', defStr); //lee como cadena
  112. r.AsDouble := StrToFloat(s);
  113. end else begin
  114. s := FloatToStr(r.AsDouble);
  115. xmlCfg.SetValue(r.asLabel + '/Val', s) ;
  116. end;
  117. //---------------------------------------------------------------------
  118. tp_Str, tp_Str_TEdit, tp_Str_TEditButton, tp_Str_TCmbBox:
  119. if FileToProp then begin //lee cadena
  120. r.AsString := xmlCfg.GetValue(r.asLabel + '/Val', r.defStr);
  121. end else begin
  122. xmlCfg.SetValue(r.asLabel + '/Val', r.AsString) ;
  123. end;
  124. //---------------------------------------------------------------------
  125. tp_Bol, tp_Bol_TCheckBox, tp_Bol_TRadBut:
  126. if FileToProp then begin //lee booleano
  127. r.AsBoolean := xmlCfg.GetValue(r.asLabel + '/Val', r.defBol);
  128. end else begin
  129. xmlCfg.SetValue(r.asLabel + '/Val', r.AsBoolean);
  130. end;
  131. //---------------------------------------------------------------------
  132. tp_Enum, tp_Enum_TRadBut, tp_Enum_TRadGroup:
  133. if FileToProp then begin //lee enumerado como entero
  134. if r.varSiz = 4 then begin //tamaño común de las variable enumeradas
  135. r.AsInt32 := xmlCfg.GetValue(r.asLabel + '/Val', r.defInt);
  136. end else begin //tamaño no implementado
  137. msjErr := 'Enumerated type no handled.';
  138. exit;
  139. end;
  140. end else begin
  141. if r.varSiz = 4 then begin
  142. xmlCfg.SetValue(r.asLabel + '/Val', r.AsInt32) ;
  143. end else begin //tamaño no implementado
  144. msjErr := 'Enumerated type no handled.';
  145. exit;
  146. end;
  147. end;
  148. //---------------------------------------------------------------------
  149. tp_TCol_TColBut, tp_TCol_TColBox:
  150. if FileToProp then begin //lee TColor
  151. r.AsTColor := xmlCfg.GetValue(r.asLabel + '/Val', r.defCol); //lee como entero
  152. end else begin
  153. c := r.AsTColor;
  154. xmlCfg.SetValue(r.asLabel + '/Val', c) ; //escribe como entero
  155. end;
  156. tp_StrList, tp_StrList_TListBox:
  157. if FileToProp then begin //lee TStringList
  158. list := TStringList(r.varRef^);
  159. list.Text := xmlCfg.GetValue(r.asLabel + '/Val', ''); //lee como texto
  160. end else begin
  161. list := TStringList(r.varRef^);
  162. xmlCfg.SetValue(r.asLabel + '/Val', list.Text); //escribe como texto
  163. end;
  164. else //no se ha implementado bien
  165. msjErr := 'Design error.';
  166. exit;
  167. end;
  168. end;
  169. function TMiConfigXML.FileToProperties: boolean;
  170. {Lee de disco las propiedades registradas
  171. Si encuentra error devuelve FALSE, y el mensaje de error en "MsjErr", y el elemento
  172. con error en "ctlErr".}
  173. var
  174. r: TParElem;
  175. xmlCfg: TXMLConfig;
  176. begin
  177. if not LoadXMLFile(fileName, xmlCfg) then exit(false);
  178. for r in listParElem do begin
  179. FileProperty(xmlCfg, r, true);
  180. if msjErr<>'' then begin
  181. ctlErr := r; //elemento que produjo el error
  182. xmlCfg.Free; //libera
  183. exit(false); //sale con error
  184. end;
  185. if r.OnFileToProperty<>nil then r.OnFileToProperty;
  186. end;
  187. //Terminó con éxito. Actualiza los cambios
  188. if OnPropertiesChanges<>nil then OnPropertiesChanges;
  189. ctlErr := nil;
  190. xmlCfg.Free; //libera
  191. exit(true); //sale sin error
  192. end;
  193. function TMiConfigXML.FileToPropertiesCat(xmlFil: string; cat: integer): boolean;
  194. {Lee de disco las propiedades registradas con una categoría específica.
  195. Si encuentra error devuelve FALSE, y el mensaje de error en "MsjErr", y el elemento
  196. con error en "ctlErr".
  197. Es similar a FileToProperties(), pero no genera eventos. Se creó pensando usarse
  198. en casos como una rutina independiente para cargar solo ciertas propiedades de un
  199. archivo de configuración, como cuando se manejan configuraciones de colores (temas)}
  200. var
  201. r: TParElem;
  202. xmlCfg: TXMLConfig;
  203. begin
  204. if not LoadXMLFile(xmlFil, xmlCfg) then exit(false);
  205. for r in listParElem do begin
  206. if r.categ<>cat then continue; //ignora los de otra categoría
  207. FileProperty(xmlCfg, r, true);
  208. if msjErr<>'' then begin
  209. ctlErr := r; //elemento que produjo el error
  210. xmlCfg.Free; //libera
  211. exit(false); //sale con error
  212. end;
  213. end;
  214. //Terminó con éxito. Actualiza los cambios
  215. ctlErr := nil;
  216. xmlCfg.Free; //libera
  217. exit(true); //sale sin error
  218. end;
  219. function TMiConfigXML.PropertiesToWindowCat(cat: integer): boolean;
  220. {Versión de PropertiesToWindow, que solo trabaja con una categoría, y no genera
  221. eventos.}
  222. var
  223. r: TParElem;
  224. begin
  225. msjErr := '';
  226. for r in listParElem do begin
  227. if r.categ<>cat then continue; //ignora los de otra categoría
  228. PropertyWindow(r, true);
  229. if msjErr<>'' then begin
  230. ctlErr := r; //guarda la referencia al elemento, en caso de que haya error
  231. end;
  232. end;
  233. Result := (msjErr='');
  234. end;
  235. function TMiConfigXML.PropertiesToFile: boolean;
  236. {Guarda en disco las propiedades registradas
  237. Si encuentra error devuelve FALSE, y el mensaje de error en "MsjErr", y el elemento
  238. con error en "ctlErr".}
  239. var
  240. r: TParElem;
  241. xmlCfg: TXMLConfig;
  242. begin
  243. if FileExists(fileName) then begin //ve si existe
  244. if FileIsReadOnly(fileName) then begin
  245. ctlErr := nil;
  246. MsjErr := 'XML file is only read.';
  247. exit(false);
  248. end;
  249. end;
  250. try
  251. xmlCfg := TXMLConfig.Create(nil);
  252. xmlCfg.Filename := fileName; //lee archivo XML
  253. xmlCfg.Clear;
  254. except
  255. ctlErr := nil;
  256. MsjErr := Format('Error writing XML file: %s', [fileName]);
  257. xmlCfg.Free;
  258. exit(false);
  259. end;
  260. msjErr := '';
  261. for r in listParElem do begin
  262. if r.OnPropertyToFile<>nil then r.OnPropertyToFile; //se ejecuta antes
  263. FileProperty(xmlCfg, r, false);
  264. if msjErr<>'' then begin
  265. ctlErr := r; //elemento que produjo el error
  266. xmlCfg.Free; //libera
  267. exit(false); //sale con error
  268. end;
  269. end;
  270. xmlCfg.Flush;
  271. ctlErr := nil;
  272. xmlCfg.Free;
  273. exit(true); //sin error
  274. end;
  275. function TMiConfigXML.GetFileName: string;
  276. begin
  277. Result := fileName;
  278. end;
  279. procedure TMiConfigXML.SetFileName(AValue: string);
  280. begin
  281. fileName := AValue;
  282. end;
  283. //Constructor y Destructor
  284. constructor TMiConfigXML.Create(XMLfile0: string);
  285. begin
  286. inherited Create;
  287. fileName := XMLfile0;
  288. end;
  289. destructor TMiConfigXML.Destroy;
  290. begin
  291. inherited Destroy;
  292. end;
  293. initialization
  294. cfgFile := TMiConfigXML.Create(cfgFile.DefaultFileName);
  295. finalization
  296. cfgFile.Destroy;
  297. end.