ChivalryServerLauncher/source/UDKIniFile.pas
Mark van Renswoude 12eba2e832 Added: tracking of Modified state
Added: saving / reverting changes
Fixed: access violation on preview with an empty map list
2014-07-02 14:54:01 +00:00

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.