2
$Id: laz_xmlcfg.pas 28744 2010-12-17 19:12:20Z mattias $
3
This file was part of the Free Component Library and was adapted to use UTF8
4
strings instead of widestrings.
6
Implementation of TXMLConfig class
7
Copyright (c) 1999 - 2001 by Sebastian Guenther, sg@freepascal.org
9
See the file COPYING.modifiedLGPL.txt, included in this distribution,
10
for details about the copyright.
12
This program is distributed in the hope that it will be useful,
13
but WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16
**********************************************************************}
19
TXMLConfig enables applications to use XML files for storing their
32
{off $DEFINE MEM_CHECK}
36
{$IFDEF MEM_CHECK}MemCheck,{$ENDIF}
39
Laz2_DOM, Laz2_XMLRead, Laz2_XMLWrite,
41
Laz_DOM, Laz_XMLRead, Laz_XMLWrite,
47
{"APath" is the path and name of a value: A XML configuration file is
48
hierachical. "/" is the path delimiter, the part after the last "/"
49
is the name of the value. The path components will be mapped to XML
50
elements, the name will be an element attribute.}
54
TXMLConfig = class(TComponent)
58
FReadFlags: TXMLReaderFlags;
60
procedure SetFilename(const AFilename: String);
64
fDoNotLoadFromFile: boolean;
65
fAutoLoadFromSource: string;
67
fPathNodeCache: array of TDomNode; // starting with doc.DocumentElement, then first child node of first sub path
68
procedure Loaded; override;
69
function ExtendedToStr(const e: extended): string;
70
function StrToExtended(const s: string; const ADefault: extended): extended;
71
procedure ReadXMLFile(out ADoc: TXMLDocument; const AFilename: String); virtual;
72
procedure WriteXMLFile(ADoc: TXMLDocument; const AFileName: String); virtual;
73
procedure FreeDoc; virtual;
74
procedure SetPathNodeCache(Index: integer; Node: TDomNode);
75
function GetPathNodeCache(Index: integer): TDomNode;
76
procedure InvalidateCacheTilEnd(StartIndex: integer);
77
function InternalFindNode(const APath: String; PathLen: integer;
78
CreateNodes: boolean = false): TDomNode;
79
procedure InternalCleanNode(Node: TDomNode);
81
constructor Create(const AFilename: String); overload; // create and load
82
constructor CreateClean(const AFilename: String); // create new
83
constructor CreateWithSource(const AFilename, Source: String); // create new and load from Source
84
destructor Destroy; override;
86
procedure Flush; // Writes the XML file
87
procedure ReadFromStream(s: TStream);
88
procedure WriteToStream(s: TStream);
90
function GetValue(const APath, ADefault: String): String;
91
function GetValue(const APath: String; ADefault: Integer): Integer;
92
function GetValue(const APath: String; ADefault: Boolean): Boolean;
93
function GetExtendedValue(const APath: String;
94
const ADefault: extended): extended;
95
procedure SetValue(const APath, AValue: String);
96
procedure SetDeleteValue(const APath, AValue, DefValue: String);
97
procedure SetValue(const APath: String; AValue: Integer);
98
procedure SetDeleteValue(const APath: String; AValue, DefValue: Integer);
99
procedure SetValue(const APath: String; AValue: Boolean);
100
procedure SetDeleteValue(const APath: String; AValue, DefValue: Boolean);
101
procedure SetExtendedValue(const APath: String; const AValue: extended);
102
procedure SetDeleteExtendedValue(const APath: String;
103
const AValue, DefValue: extended);
104
procedure DeletePath(const APath: string);
105
procedure DeleteValue(const APath: string);
106
function FindNode(const APath: String; PathHasValue: boolean): TDomNode;
107
function HasPath(const APath: string; PathHasValue: boolean): boolean; // checks if the path has values, set PathHasValue=true to skip the last part
108
function HasChildPaths(const APath: string): boolean;
109
property Modified: Boolean read FModified write FModified;
110
procedure InvalidatePathCache;
112
property Filename: String read FFilename write SetFilename;
113
property Document: TXMLDocument read doc;
115
property ReadFlags: TXMLReaderFlags read FReadFlags write FReadFlags;
120
// ===================================================================
124
constructor TXMLConfig.Create(const AFilename: String);
126
//DebugLn(['TXMLConfig.Create ',AFilename]);
128
FReadFlags:=[xrfAllowLowerThanInAttributeValue,xrfAllowSpecialCharsInAttributeValue];
130
inherited Create(nil);
131
SetFilename(AFilename);
134
constructor TXMLConfig.CreateClean(const AFilename: String);
136
//DebugLn(['TXMLConfig.CreateClean ',AFilename]);
137
fDoNotLoadFromFile:=true;
139
FModified:=FileExistsCached(AFilename);
142
constructor TXMLConfig.CreateWithSource(const AFilename, Source: String);
144
fAutoLoadFromSource:=Source;
146
CreateClean(AFilename);
148
fAutoLoadFromSource:='';
152
destructor TXMLConfig.Destroy;
154
if Assigned(doc) then
162
procedure TXMLConfig.Clear;
168
// create new document
169
doc := TXMLDocument.Create;
170
cfg :=TDOMElement(doc.FindNode('CONFIG'));
171
if not Assigned(cfg) then begin
172
cfg := doc.CreateElement('CONFIG');
173
doc.AppendChild(cfg);
177
procedure TXMLConfig.Flush;
179
if Modified and (Filename<>'') then
181
//DebugLn(['TXMLConfig.Flush ',Filename]);
182
WriteXMLFile(doc, Filename);
187
procedure TXMLConfig.ReadFromStream(s: TStream);
191
Laz2_XMLRead.ReadXMLFile(Doc,s,ReadFlags);
193
Laz_XMLRead.ReadXMLFile(Doc,s);
199
procedure TXMLConfig.WriteToStream(s: TStream);
202
Laz2_XMLWrite.WriteXMLFile(Doc,s);
204
Laz_XMLWrite.WriteXMLFile(Doc,s);
208
function TXMLConfig.GetValue(const APath, ADefault: String): String;
210
Node, Attr: TDOMNode;
214
//CheckHeapWrtMemCnt('TXMLConfig.GetValue A '+APath);
217
StartPos:=length(APath)+1;
218
while (StartPos>1) and (APath[StartPos-1]<>'/') do dec(StartPos);
219
if StartPos>length(APath) then exit;
220
Node:=InternalFindNode(APath,StartPos-1);
223
//CheckHeapWrtMemCnt('TXMLConfig.GetValue E');
224
NodeName:=copy(APath,StartPos,length(APath));
225
//CheckHeapWrtMemCnt('TXMLConfig.GetValue G');
226
Attr := Node.Attributes.GetNamedItem(NodeName);
227
if Assigned(Attr) then
228
Result := Attr.NodeValue;
229
//writeln('TXMLConfig.GetValue END Result="',Result,'"');
232
function TXMLConfig.GetValue(const APath: String; ADefault: Integer): Integer;
234
Result := StrToIntDef(GetValue(APath, IntToStr(ADefault)),ADefault);
237
function TXMLConfig.GetValue(const APath: String; ADefault: Boolean): Boolean;
246
s := GetValue(APath, s);
248
if CompareText(s,'TRUE')=0 then
250
else if CompareText(s,'FALSE')=0 then
256
function TXMLConfig.GetExtendedValue(const APath: String;
257
const ADefault: extended): extended;
259
Result:=StrToExtended(GetValue(APath,ExtendedToStr(ADefault)),ADefault);
262
procedure TXMLConfig.SetValue(const APath, AValue: String);
268
StartPos:=length(APath)+1;
269
while (StartPos>1) and (APath[StartPos-1]<>'/') do dec(StartPos);
270
if StartPos>length(APath) then exit;
271
Node:=InternalFindNode(APath,StartPos-1,true);
274
NodeName:=copy(APath,StartPos,length(APath));
275
if (not Assigned(TDOMElement(Node).GetAttributeNode(NodeName))) or
276
(TDOMElement(Node)[NodeName] <> AValue) then
278
TDOMElement(Node)[NodeName] := AValue;
283
procedure TXMLConfig.SetDeleteValue(const APath, AValue, DefValue: String);
285
if AValue=DefValue then
288
SetValue(APath,AValue);
291
procedure TXMLConfig.SetValue(const APath: String; AValue: Integer);
293
SetValue(APath, IntToStr(AValue));
296
procedure TXMLConfig.SetDeleteValue(const APath: String; AValue,
299
if AValue=DefValue then
302
SetValue(APath,AValue);
305
procedure TXMLConfig.SetValue(const APath: String; AValue: Boolean);
308
SetValue(APath, 'True')
310
SetValue(APath, 'False');
313
procedure TXMLConfig.SetDeleteValue(const APath: String; AValue,
316
if AValue=DefValue then
319
SetValue(APath,AValue);
322
procedure TXMLConfig.SetExtendedValue(const APath: String;
323
const AValue: extended);
325
SetValue(APath,ExtendedToStr(AValue));
328
procedure TXMLConfig.SetDeleteExtendedValue(const APath: String; const AValue,
331
if AValue=DefValue then
334
SetExtendedValue(APath,AValue);
337
procedure TXMLConfig.DeletePath(const APath: string);
340
ParentNode: TDOMNode;
342
Node:=InternalFindNode(APath,length(APath));
343
if (Node=nil) or (Node.ParentNode=nil) then exit;
344
ParentNode:=Node.ParentNode;
345
ParentNode.RemoveChild(Node);
348
InternalCleanNode(ParentNode);
351
procedure TXMLConfig.DeleteValue(const APath: string);
357
Node:=FindNode(APath,true);
358
if (Node=nil) then exit;
359
StartPos:=length(APath);
360
while (StartPos>0) and (APath[StartPos]<>'/') do dec(StartPos);
361
NodeName:=copy(APath,StartPos+1,length(APath)-StartPos);
362
if Assigned(TDOMElement(Node).GetAttributeNode(NodeName)) then begin
363
TDOMElement(Node).RemoveAttribute(NodeName);
366
InternalCleanNode(Node);
369
procedure TXMLConfig.Loaded;
372
if Length(Filename) > 0 then
373
SetFilename(Filename); // Load the XML config file
376
function TXMLConfig.FindNode(const APath: String;
377
PathHasValue: boolean): TDomNode;
381
PathLen:=length(APath);
382
if PathHasValue then begin
383
while (PathLen>0) and (APath[PathLen]<>'/') do dec(PathLen);
384
while (PathLen>0) and (APath[PathLen]='/') do dec(PathLen);
386
Result:=InternalFindNode(APath,PathLen);
389
function TXMLConfig.HasPath(const APath: string; PathHasValue: boolean
392
Result:=FindNode(APath,PathHasValue)<>nil;
395
function TXMLConfig.HasChildPaths(const APath: string): boolean;
399
Node:=FindNode(APath,false);
400
Result:=(Node<>nil) and Node.HasChildNodes;
403
procedure TXMLConfig.InvalidatePathCache;
406
InvalidateCacheTilEnd(0);
409
function TXMLConfig.ExtendedToStr(const e: extended): string;
411
OldDecimalSeparator: Char;
412
OldThousandSeparator: Char;
414
OldDecimalSeparator:=DefaultFormatSettings.DecimalSeparator;
415
OldThousandSeparator:=DefaultFormatSettings.ThousandSeparator;
416
DefaultFormatSettings.DecimalSeparator:='.';
417
DefaultFormatSettings.ThousandSeparator:=',';
418
Result:=FloatToStr(e);
419
DefaultFormatSettings.DecimalSeparator:=OldDecimalSeparator;
420
DefaultFormatSettings.ThousandSeparator:=OldThousandSeparator;
423
function TXMLConfig.StrToExtended(const s: string; const ADefault: extended): extended;
425
OldDecimalSeparator: Char;
426
OldThousandSeparator: Char;
428
OldDecimalSeparator:=DefaultFormatSettings.DecimalSeparator;
429
OldThousandSeparator:=DefaultFormatSettings.ThousandSeparator;
430
DefaultFormatSettings.DecimalSeparator:='.';
431
DefaultFormatSettings.ThousandSeparator:=',';
432
Result:=StrToFloatDef(s,ADefault);
433
DefaultFormatSettings.DecimalSeparator:=OldDecimalSeparator;
434
DefaultFormatSettings.ThousandSeparator:=OldThousandSeparator;
437
procedure TXMLConfig.ReadXMLFile(out ADoc: TXMLDocument; const AFilename: String
442
Laz2_XMLRead.ReadXMLFile(ADoc,AFilename,ReadFlags);
444
Laz_XMLRead.ReadXMLFile(ADoc,AFilename);
448
procedure TXMLConfig.WriteXMLFile(ADoc: TXMLDocument; const AFileName: String);
451
Laz2_XMLWrite.WriteXMLFile(ADoc,AFileName);
453
Laz_XMLWrite.WriteXMLFile(ADoc,AFileName);
455
InvalidateFileStateCache(AFileName);
458
procedure TXMLConfig.FreeDoc;
464
procedure TXMLConfig.SetPathNodeCache(Index: integer; Node: TDomNode);
470
OldLength:=length(fPathNodeCache);
471
if OldLength<=Index then begin
472
NewSize:=OldLength*2+4;
473
if NewSize<Index then NewSize:=Index;
474
SetLength(fPathNodeCache,NewSize);
475
for i:=OldLength to length(fPathNodeCache)-1 do
476
fPathNodeCache[i]:=nil;
478
fPathNodeCache[Index]:=Node;
481
function TXMLConfig.GetPathNodeCache(Index: integer): TDomNode;
483
if Index<length(fPathNodeCache) then
484
Result:=fPathNodeCache[Index]
489
procedure TXMLConfig.InvalidateCacheTilEnd(StartIndex: integer);
493
for i:=StartIndex to length(fPathNodeCache)-1 do begin
494
if fPathNodeCache[i]=nil then break;
495
fPathNodeCache[i]:=nil;
499
function TXMLConfig.InternalFindNode(const APath: String; PathLen: integer;
500
CreateNodes: boolean): TDomNode;
503
StartPos, EndPos: integer;
508
//debugln(['TXMLConfig.InternalFindNode APath="',copy(APath,1,PathLen),'" CreateNodes=',CreateNodes]);
510
Result:=GetPathNodeCache(PathIndex);
511
if Result=nil then begin
512
Result := TDOMElement(doc.FindNode('CONFIG'));
513
SetPathNodeCache(PathIndex,Result);
515
if PathLen=0 then exit;
517
while (Result<>nil) do begin
519
while (EndPos<=PathLen) and (APath[EndPos]<>'/') do inc(EndPos);
520
NameLen:=EndPos-StartPos;
521
if NameLen=0 then break;
524
Result:=GetPathNodeCache(PathIndex);
525
if (Result<>nil) and (length(Result.NodeName)=NameLen)
526
and CompareMem(PChar(Result.NodeName),@APath[StartPos],NameLen) then begin
529
// different path => search
530
InvalidateCacheTilEnd(PathIndex);
531
NodePath:=copy(APath,StartPos,NameLen);
532
Result:=Parent.FindNode(NodePath);
533
if Result=nil then begin
534
if not CreateNodes then exit;
535
// create missing node
536
Result := Doc.CreateElement(NodePath);
537
Parent.AppendChild(Result);
538
if EndPos>PathLen then exit;
540
SetPathNodeCache(PathIndex,Result);
543
if StartPos>PathLen then exit;
548
procedure TXMLConfig.InternalCleanNode(Node: TDomNode);
550
ParentNode: TDOMNode;
552
if (Node=nil) then exit;
553
while (Node.FirstChild=nil) and (Node.ParentNode<>nil)
554
and (Node.ParentNode.ParentNode<>nil) do begin
555
if (Node is TDOMElement) and (not TDOMElement(Node).IsEmpty) then break;
556
ParentNode:=Node.ParentNode;
557
ParentNode.RemoveChild(Node);
564
procedure TXMLConfig.SetFilename(const AFilename: String);
569
{$IFDEF MEM_CHECK}CheckHeapWrtMemCnt('TXMLConfig.SetFilename A '+AFilename);{$ENDIF}
570
if FFilename = AFilename then exit;
571
FFilename := AFilename;
574
if csLoading in ComponentState then
577
if Assigned(doc) then
584
//debugln(['TXMLConfig.SetFilename ',not fDoNotLoadFromFile,' ',FileExistsCached(Filename)]);
585
if (not fDoNotLoadFromFile) and FileExistsCached(Filename) then
586
ReadXMLFile(doc,Filename)
587
else if fAutoLoadFromSource<>'' then begin
588
ms:=TMemoryStream.Create;
590
ms.Write(fAutoLoadFromSource[1],length(fAutoLoadFromSource));
593
Laz2_XMLRead.ReadXMLFile(doc,ms,ReadFlags);
595
Laz_XMLRead.ReadXMLFile(doc,ms);
602
if not Assigned(doc) then
603
doc := TXMLDocument.Create;
605
cfg :=TDOMElement(doc.FindNode('CONFIG'));
606
//debugln(['TXMLConfig.SetFilename ',DbgSName(cfg)]);
607
if not Assigned(cfg) then begin
608
cfg := doc.CreateElement('CONFIG');
609
doc.AppendChild(cfg);
611
{$IFDEF MEM_CHECK}CheckHeapWrtMemCnt('TXMLConfig.SetFilename END');{$ENDIF}