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.