Mark van Renswoude
12eba2e832
Added: saving / reverting changes Fixed: access violation on preview with an empty map list
445 lines
10 KiB
ObjectPascal
445 lines
10 KiB
ObjectPascal
unit UDKIniFile;
|
|
|
|
interface
|
|
uses
|
|
System.Classes,
|
|
System.IniFiles,
|
|
System.SysUtils;
|
|
|
|
|
|
type
|
|
{ Basically a TMemIniFile with benefits (TMemIniFile isn't virtual enough
|
|
to allow simple additions to it). Adds helpers to handle multiple keys
|
|
with the same name. }
|
|
TUDKIniFile = class(TCustomIniFile)
|
|
private
|
|
FSections: TStringList;
|
|
FEncoding: TEncoding;
|
|
|
|
function GetCaseSensitive: Boolean;
|
|
procedure SetCaseSensitive(Value: Boolean);
|
|
protected
|
|
function AddSection(const ASection: string): TStrings;
|
|
procedure LoadValues;
|
|
|
|
property Sections: TStringList read FSections;
|
|
public
|
|
constructor Create(const AFileName: string); overload;
|
|
constructor Create(const AFileName: string; AEncoding: TEncoding); overload;
|
|
destructor Destroy; override;
|
|
|
|
procedure Clear;
|
|
|
|
function ReadString(const ASection, AIdent, ADefault: string): string; override;
|
|
procedure DeleteKey(const ASection, AIdent: string); override;
|
|
procedure EraseSection(const ASection: string); override;
|
|
procedure ReadSection(const ASection: string; AStrings: TStrings); override;
|
|
procedure ReadSections(AStrings: TStrings); override;
|
|
procedure ReadSectionValues(const ASection: string; AStrings: TStrings); override;
|
|
procedure UpdateFile; override;
|
|
procedure WriteString(const ASection, AIdent, AValue: string); override;
|
|
|
|
procedure GetStrings(AList: TStrings);
|
|
procedure SetStrings(AList: TStrings);
|
|
|
|
{ Helpers for duplicate keys }
|
|
procedure ReadDuplicateStrings(const ASection, AIdent: string; AList: TStrings);
|
|
procedure WriteDuplicateString(const ASection, AIdent, AValue: string);
|
|
procedure DeleteDuplicateKeys(const ASection, AIdent: string);
|
|
|
|
property CaseSensitive: Boolean read GetCaseSensitive write SetCaseSensitive;
|
|
property Encoding: TEncoding read FEncoding write FEncoding;
|
|
end;
|
|
|
|
|
|
implementation
|
|
type
|
|
{ Added here so we can access the protected members }
|
|
TUDKHashedStringList = class(THashedStringList);
|
|
|
|
|
|
{ TUDKIniFile }
|
|
constructor TUDKIniFile.Create(const AFileName: string);
|
|
begin
|
|
Create(AFilename, nil);
|
|
end;
|
|
|
|
|
|
constructor TUDKIniFile.Create(const AFileName: string; AEncoding: TEncoding);
|
|
begin
|
|
inherited Create(AFileName);
|
|
|
|
FEncoding := AEncoding;
|
|
FSections := TUDKHashedStringList.Create;
|
|
LoadValues;
|
|
end;
|
|
|
|
|
|
destructor TUDKIniFile.Destroy;
|
|
begin
|
|
Clear;
|
|
FreeAndNil(FSections);
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
|
|
procedure TUDKIniFile.Clear;
|
|
var
|
|
sectionIndex: Integer;
|
|
|
|
begin
|
|
for sectionIndex := 0 to Pred(Sections.Count) do
|
|
Sections.Objects[sectionIndex].Free;
|
|
|
|
Sections.Clear;
|
|
end;
|
|
|
|
|
|
function TUDKIniFile.ReadString(const ASection, AIdent, ADefault: string): string;
|
|
var
|
|
sectionIndex: Integer;
|
|
strings: TStrings;
|
|
|
|
begin
|
|
sectionIndex := Sections.IndexOf(ASection);
|
|
if sectionIndex > -1 then
|
|
begin
|
|
strings := TStrings(Sections.Objects[sectionIndex]);
|
|
sectionIndex := strings.IndexOfName(AIdent);
|
|
|
|
if sectionIndex > -1 then
|
|
begin
|
|
Result := Copy(strings[sectionIndex], Length(AIdent) + 2, MaxInt);
|
|
exit;
|
|
end;
|
|
end;
|
|
|
|
Result := ADefault;
|
|
end;
|
|
|
|
|
|
procedure TUDKIniFile.DeleteKey(const ASection, AIdent: string);
|
|
var
|
|
sectionIndex: Integer;
|
|
keyIndex: Integer;
|
|
Strings: TStrings;
|
|
|
|
begin
|
|
sectionIndex := FSections.IndexOf(ASection);
|
|
if sectionIndex > -1 then
|
|
begin
|
|
strings := TStrings(FSections.Objects[sectionIndex]);
|
|
keyIndex := strings.IndexOfName(AIdent);
|
|
|
|
if keyIndex > -1 then
|
|
strings.Delete(keyIndex);
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TUDKIniFile.EraseSection(const ASection: string);
|
|
var
|
|
sectionIndex: Integer;
|
|
|
|
begin
|
|
sectionIndex := Sections.IndexOf(ASection);
|
|
if sectionIndex > -1 then
|
|
begin
|
|
TStrings(Sections.Objects[sectionIndex]).Free;
|
|
Sections.Delete(sectionIndex);
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TUDKIniFile.ReadSection(const ASection: string; AStrings: TStrings);
|
|
var
|
|
sectionIndex: Integer;
|
|
keyIndex: Integer;
|
|
sectionStrings: TStrings;
|
|
|
|
begin
|
|
AStrings.BeginUpdate;
|
|
try
|
|
AStrings.Clear;
|
|
|
|
sectionIndex := Sections.IndexOf(ASection);
|
|
if sectionIndex > -1 then
|
|
begin
|
|
sectionStrings := TStrings(Sections.Objects[sectionIndex]);
|
|
for keyIndex := 0 to Pred(sectionStrings.Count) do
|
|
AStrings.Add(sectionStrings.Names[keyIndex]);
|
|
end;
|
|
finally
|
|
AStrings.EndUpdate;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TUDKIniFile.ReadSections(AStrings: TStrings);
|
|
begin
|
|
AStrings.Assign(Sections);
|
|
end;
|
|
|
|
|
|
procedure TUDKIniFile.ReadSectionValues(const ASection: string; AStrings: TStrings);
|
|
var
|
|
sectionIndex: Integer;
|
|
|
|
begin
|
|
AStrings.BeginUpdate;
|
|
try
|
|
AStrings.Clear;
|
|
|
|
sectionIndex := Sections.IndexOf(ASection);
|
|
if sectionIndex > -1 then
|
|
AStrings.Assign(TStrings(Sections.Objects[sectionIndex]));
|
|
finally
|
|
AStrings.EndUpdate;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TUDKIniFile.UpdateFile;
|
|
var
|
|
list: TStringList;
|
|
|
|
begin
|
|
list := TStringList.Create;
|
|
try
|
|
GetStrings(list);
|
|
list.SaveToFile(FileName, FEncoding);
|
|
finally
|
|
FreeAndNil(list);
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TUDKIniFile.WriteString(const ASection, AIdent, AValue: string);
|
|
var
|
|
sectionIndex: Integer;
|
|
keyIndex: Integer;
|
|
value: string;
|
|
strings: TStrings;
|
|
|
|
begin
|
|
sectionIndex := Sections.IndexOf(ASection);
|
|
if sectionIndex > -1 then
|
|
strings := TStrings(Sections.Objects[sectionIndex])
|
|
else
|
|
strings := AddSection(ASection);
|
|
|
|
value := AIdent + '=' + AValue;
|
|
keyIndex := strings.IndexOfName(AIdent);
|
|
|
|
if keyIndex > -1 then
|
|
strings[keyIndex] := value
|
|
else
|
|
strings.Add(value);
|
|
end;
|
|
|
|
|
|
procedure TUDKIniFile.GetStrings(AList: TStrings);
|
|
var
|
|
sectionIndex: Integer;
|
|
strings: TStrings;
|
|
value: string;
|
|
|
|
begin
|
|
AList.BeginUpdate;
|
|
try
|
|
for sectionIndex := 0 to Pred(Sections.Count) do
|
|
begin
|
|
AList.Add('[' + Sections[sectionIndex] + ']');
|
|
|
|
strings := TStrings(Sections.Objects[sectionIndex]);
|
|
for value in strings do
|
|
AList.Add(value);
|
|
|
|
AList.Add('');
|
|
end;
|
|
finally
|
|
AList.EndUpdate;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TUDKIniFile.SetStrings(AList: TStrings);
|
|
var
|
|
lineIndex: Integer;
|
|
line: string;
|
|
separatorPos: Integer;
|
|
strings: TStrings;
|
|
|
|
begin
|
|
Clear;
|
|
strings := nil;
|
|
|
|
for lineIndex := 0 to Pred(AList.Count) do
|
|
begin
|
|
line := Trim(AList[lineIndex]);
|
|
|
|
if (Length(line) > 0) and (line[1] <> ';') then
|
|
begin
|
|
if (line[1] = '[') and (line[Length(line)] = ']') then
|
|
begin
|
|
Delete(line, 1, 1);
|
|
SetLength(line, Length(line) - 1);
|
|
|
|
strings := AddSection(Trim(line));
|
|
end
|
|
else
|
|
if Assigned(strings) then
|
|
begin
|
|
separatorPos := Pos('=', line);
|
|
if separatorPos > 0 then
|
|
strings.Add(Trim(Copy(line, 1, Pred(separatorPos))) + '=' + Trim(Copy(line, Succ(separatorPos), MaxInt)))
|
|
else
|
|
strings.Add(line);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TUDKIniFile.ReadDuplicateStrings(const ASection, AIdent: string; AList: TStrings);
|
|
var
|
|
sectionIndex: Integer;
|
|
keyIndex: Integer;
|
|
Strings: TStrings;
|
|
|
|
begin
|
|
sectionIndex := FSections.IndexOf(ASection);
|
|
if sectionIndex > -1 then
|
|
begin
|
|
strings := TStrings(FSections.Objects[sectionIndex]);
|
|
|
|
for keyIndex := 0 to Pred(strings.Count) do
|
|
begin
|
|
if (CaseSensitive and (strings.Names[keyIndex] = AIdent)) or
|
|
((not CaseSensitive) and SameText(strings.Names[keyIndex], AIdent)) then
|
|
begin
|
|
AList.Add(strings.ValueFromIndex[keyIndex]);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TUDKIniFile.WriteDuplicateString(const ASection, AIdent, AValue: string);
|
|
var
|
|
sectionIndex: Integer;
|
|
strings: TStrings;
|
|
|
|
begin
|
|
sectionIndex := Sections.IndexOf(ASection);
|
|
if sectionIndex > -1 then
|
|
strings := TStrings(Sections.Objects[sectionIndex])
|
|
else
|
|
strings := AddSection(ASection);
|
|
|
|
strings.Add(AIdent + '=' + AValue);
|
|
end;
|
|
|
|
|
|
procedure TUDKIniFile.DeleteDuplicateKeys(const ASection, AIdent: string);
|
|
var
|
|
sectionIndex: Integer;
|
|
keyIndex: Integer;
|
|
Strings: TStrings;
|
|
|
|
begin
|
|
sectionIndex := Sections.IndexOf(ASection);
|
|
if sectionIndex > -1 then
|
|
begin
|
|
strings := TStrings(Sections.Objects[sectionIndex]);
|
|
|
|
for keyIndex := Pred(strings.Count) downto 0 do
|
|
begin
|
|
if (CaseSensitive and (strings.Names[keyIndex] = AIdent)) or
|
|
((not CaseSensitive) and SameText(strings.Names[keyIndex], AIdent)) then
|
|
begin
|
|
strings.Delete(keyIndex)
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
function TUDKIniFile.AddSection(const ASection: string): TStrings;
|
|
begin
|
|
Result := THashedStringList.Create;
|
|
try
|
|
THashedStringList(Result).CaseSensitive := CaseSensitive;
|
|
FSections.AddObject(ASection, Result);
|
|
except
|
|
FreeAndNil(Result);
|
|
raise;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TUDKIniFile.LoadValues;
|
|
var
|
|
size: Integer;
|
|
buffer: TBytes;
|
|
list: TStringList;
|
|
stream: TFileStream;
|
|
|
|
begin
|
|
if (Length(FileName) > 0) and FileExists(FileName) then
|
|
begin
|
|
stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
|
|
try
|
|
{ Load file into buffer and detect encoding }
|
|
size := stream.Size - stream.Position;
|
|
SetLength(buffer, size);
|
|
stream.Read(buffer[0], size);
|
|
|
|
size := TEncoding.GetBufferEncoding(buffer, FEncoding);
|
|
|
|
{ Load strings from buffer }
|
|
list := TStringList.Create;
|
|
try
|
|
list.Text := FEncoding.GetString(buffer, size, Length(buffer) - size);
|
|
SetStrings(list);
|
|
finally
|
|
FreeAndNil(list);
|
|
end;
|
|
finally
|
|
FreeAndNil(stream);
|
|
end;
|
|
end else
|
|
Clear;
|
|
end;
|
|
|
|
|
|
function TUDKIniFile.GetCaseSensitive: Boolean;
|
|
begin
|
|
Result := Sections.CaseSensitive;
|
|
end;
|
|
|
|
|
|
procedure TUDKIniFile.SetCaseSensitive(Value: Boolean);
|
|
var
|
|
sectionIndex: Integer;
|
|
sectionList: TUDKHashedStringList;
|
|
|
|
begin
|
|
if Value <> Sections.CaseSensitive then
|
|
begin
|
|
Sections.CaseSensitive := Value;
|
|
|
|
for sectionIndex := 0 to Pred(Sections.Count) do
|
|
begin
|
|
sectionList := TUDKHashedStringList(Sections.Objects[sectionIndex]);
|
|
sectionList.CaseSensitive := Value;
|
|
sectionList.Changed;
|
|
end;
|
|
|
|
TUDKHashedStringList(Sections).Changed;
|
|
end;
|
|
end;
|
|
|
|
end.
|