unit IniList;

{ TIniList (C)opyright 2001 Version 1.01
  Autor : Simon Reinhardt
  eMail : reinhardt@picsoft.de
  Internet : http://www.picsoft.de

  Die Klasse TIniList ist eine Stringliste, die um die Methoden
  von TIniFile erweitert wurde. Man hat somit eine Möglichkeit,
  Werte verschiedener Formate im Speicher zu halten und diese
  erst bei Bedarf in ein Inifile zu speichern oder aus einem
  Inifile zu laden.

  Die Komponente ist Public Domain, das Urheberrecht liegt aber
  beim Autor. }

interface

{$I SRDefine.inc}

uses Classes;

type
  TIniList = class(TComponent)
  private
    FLines           : TStringList;
    FSeparator       : char;
    FCaseSens,
    FDateFloat       : boolean;
    FFileName,
    FTitle           : string;
    FOnChange,
    FOnChanging,
    FOnSectionChange,
    FOnKeyChange,
    FOnValueChange   : TNotifyEvent;

    procedure SetSeparator(NewValue: char);
    procedure SetTitle(NewValue: string);

  protected
    procedure Changed; virtual;
    procedure Changing; virtual;
    procedure SectionChange; virtual;
    procedure KeyChange; virtual;
    procedure ValueChange; virtual;

    function AddKey(const SectionIdx:integer;const Key, Value: string):integer;
    function AddSection(const Section: string):integer;
    function GetKeyFromLine(const Line:string):string;
    function GetKeyIndex(const Section, Key: string):integer;
    function GetKeyIndexFromLine(const SectionIdx:integer;const Key: string):integer;
    function GetSectionIndex(const Section: string):integer;
    function GetValueFromLine(const Line:string):string;
    function ReadValue(const Section, Key: string):string;
    procedure WriteValue(const Section, Key: string; Value: string);

  public
    constructor Create(AComponent: TComponent); override;
    destructor Destroy; override;

    procedure Clear;
    {$IFDEF SR_Delphi4_Up}
    function LoadFromFile:boolean; overload;
    function LoadFromFile(const FileName:string):boolean; overload;
    {$ELSE}
    function LoadFromFile:boolean;
    {$ENDIF}
    function LoadFromStream(AStream: TStream):boolean;
    {$IFDEF SR_Delphi4_Up}
    function SaveToFile:boolean; overload;
    function SaveToFile(const FileName:string):boolean; overload;
    {$ELSE}
    function SaveToFile:boolean;
    {$ENDIF}
    function SaveToStream(AStream: TStream):boolean;

    procedure DeleteKey(const Section, Key: String);
    procedure EraseSection(const Section: string);
    function KeyCount(const Section: string):integer;
    function SectionCount:integer;

    function ReadBool(const Section, Key: string; Default: Boolean): Boolean;
    function ReadDate(const Section, Key: string; Default: TDateTime): TDateTime;
    function ReadDateTime(const Section, Key: string; Default: TDateTime): TDateTime;
    function ReadFloat(const Section, Key: string; Default: Double): Double;
    function ReadInteger(const Section, Key: string; Default: Longint): Longint;
    function ReadString(const Section, Key, Default: string): string;
    function ReadTime(const Section, Key: string; Default: TDateTime): TDateTime;

    procedure ReadSection(const Section: string; AStrings: TStrings);
    procedure ReadSections(AStrings: TStrings);
    procedure ReadSectionKeys(const Section: string; AStrings: TStrings);
    procedure ReadSectionValues(const Section: string; AStrings: TStrings);

    procedure WriteBool(const Section, Key: string; Value: Boolean);
    procedure WriteDate(const Section, Key: string; Value: TDateTime);
    procedure WriteDateTime(const Section, Key: string; Value: TDateTime);
    procedure WriteFloat(const Section, Key: string; Value: Double);
    procedure WriteInteger(const Section, Key: string; Value: Longint);
    procedure WriteString(const Section, Key, Value: String);
    procedure WriteTime(const Section, Key: string; Value: TDateTime);

  published
    property CaseSensitive: boolean read FCaseSens write FCaseSens;
    property DateTimeAsFloat: boolean read FDateFloat write FDateFloat;
    property IniFilename: string read FFileName write FFileName;
    property Separator: char read FSeparator write SetSeparator;
    property Title: string read FTitle write SetTitle;

    property OnChange: TNotifyEvent read FOnChange write FOnChange;
    property OnChanging: TNotifyEvent read FOnChanging write FOnChanging;
    property OnSectionChange: TNotifyEvent read FOnSectionChange write FOnSectionChange;
    property OnKeyChange: TNotifyEvent read FOnKeyChange write FOnKeyChange;
    property OnValueChange: TNotifyEvent read FOnValueChange write FOnValueChange;

  end;

procedure Register;

implementation

{$IFDEF SR_Delphi2_Up}
{$R *.D32}
{$ELSE}
{$R *.D16}
{$ENDIF}

uses Consts, SysUtils, SRUtils;

constructor TIniList.Create(AComponent: TComponent);
begin
  inherited Create(AComponent);

  FSeparator:='=';
  FCaseSens:=false;
  FDateFloat:=false;
  FFileName:='';
  FTitle:='';

  FLines:=TStringList.Create;
  FLines.Add('; NoTitle');
end;

destructor TIniList.Destroy;
begin
  FOnChange := nil;
  FOnChanging := nil;

  if assigned(FLines) then
    FreeAndNil(FLines);

  inherited Destroy;
end; {Destroy}

function TIniList.AddKey(const SectionIdx:integer;const Key, Value: string):integer;
var AIndex : integer;
begin
  Changing;
  AIndex:=GetKeyIndexFromLine(SectionIdx, Key);
  if AIndex>=0 then begin
    Result:=AIndex;
    FLines[AIndex]:=Key+FSeparator+Value;
    ValueChange;
  end
  else begin
    Result:=SectionIdx+1;
    FLines.Insert(SectionIdx+1, Key+FSeparator+Value);
    KeyChange;
  end;
  Changed;
end; {AddKey}

function TIniList.AddSection(const Section: string):integer;
var AIndex : integer;
begin
  AIndex:=GetSectionIndex(Section);
  if AIndex>=0 then
    Result:=AIndex
  else begin
    Changing;
    FLines.Add('');
    Result:=FLines.Add('['+Section+']');
    SectionChange;
    Changed;
  end;
end; {AddSection}

procedure TIniList.Changed;
begin
  if Assigned(FOnChange) then
    FOnChange(Self);
end; {Changed}

procedure TIniList.Changing;
begin
  if Assigned(FOnChanging) then
    FOnChanging(Self);
end; {Changing}

procedure TIniList.SectionChange;
begin
  if Assigned(FOnSectionChange) then
    FOnSectionChange(Self);
end; {Changed}

procedure TIniList.KeyChange;
begin
  if Assigned(FOnKeyChange) then
    FOnKeyChange(Self);
end; {Changed}

procedure TIniList.ValueChange;
begin
  if Assigned(FOnValueChange) then
    FOnValueChange(Self);
end; {Changed}

procedure TIniList.Clear;
begin
  Changing;
  FLines.Clear;
  Changed;
end; {Clear}

procedure TIniList.DeleteKey(const Section, Key: String);
var KeyIndex  : integer;
begin
  KeyIndex:=GetKeyIndex(Section, Key);
  if KeyIndex>=0 then begin
    Changing;
    FLines.Delete(KeyIndex);
    KeyChange;
    if KeyCount(Section)=0 then
      EraseSection(Section);
    Changed;
  end;
end; {DeleteKey}

procedure TIniList.EraseSection(const Section: string);
var SectIndex : integer;
    ALine     : string;
    Abort     : boolean;
begin
  SectIndex:=GetSectionIndex(Section);
  if SectIndex>=0 then begin
    if SectIndex>0 then begin
      ALine:=Trim(FLines[SectIndex-1]);
      if ALine='' then begin
        dec(SectIndex);
        FLines.Delete(SectIndex);
      end;
    end;
    ALine:='';
    Abort:=false;
    Changing;
    FLines.BeginUpdate;
    repeat
      FLines.Delete(SectIndex);
      if SectIndex<FLines.Count then
        ALine:=FLines[SectIndex]
      else
        Abort:=true;
    until Abort or (ALine='') or ((ALine<>'') and (ALine[1]='['));
    FLines.EndUpdate;
    SectionChange;
    Changed;
  end;
end; {EraseSection}

function TIniList.GetKeyFromLine(const Line:string):string;
var P : integer;
begin
  Result:='';
  P:=Pos(FSeparator, Line);
  if P>1 then
    Result:=copy(Line, 1, P-1);
end; {GetKeyFromLine}

function TIniList.GetKeyIndex(const Section, Key: string):integer;
var AIndex,
    ACount : integer;
    ALine,
    Key1,
    Key2   : string;
begin
  Result:=-1;
  ACount:=FLines.Count;
  AIndex:=GetSectionIndex(Section);
  if (ACount>0) and (AIndex>=0) then begin
    if FCaseSens then
      Key1:=Key
    else
      Key1:=UpperCase(Key);
    repeat
      inc(AIndex);
      if AIndex<ACount then begin
        ALine:=FLines[AIndex];
        if FCaseSens then
          Key2:=GetKeyFromLine(ALine)
        else
          Key2:=UpperCase(GetKeyFromLine(ALine));
        if Key1=Key2 then
          Result:=AIndex;
      end;
    until (Result>=0) or (AIndex>=ACount) or (ALine='')
     or ((ALine<>'') and (ALine[1]='['));
  end;
end; {GetKeyIndex}

function TIniList.GetKeyIndexFromLine(const SectionIdx:integer;const Key: string):integer;
var AIndex,
    ACount : integer;
    ALine,
    Key1,
    Key2   : string;
begin
  Result:=-1;
  ACount:=FLines.Count;
  if (ACount>0) and (SectionIdx>=0) then begin
    if FCaseSens then
      Key1:=Key
    else
      Key1:=UpperCase(Key);
    AIndex:=SectionIdx;
    repeat
      inc(AIndex);
      if AIndex<ACount then begin
        ALine:=FLines[AIndex];
        if FCaseSens then
          Key2:=GetKeyFromLine(ALine)
        else
          Key2:=UpperCase(GetKeyFromLine(ALine));
        if Key1=Key2 then
          Result:=AIndex;
      end;
    until (Result>=0) or (AIndex>=ACount) or (ALine='')
     or ((ALine<>'') and (ALine[1]='['));
  end;
end; {GetKeyIndexFromLine}

function TIniList.GetSectionIndex(const Section: string):integer;
var AIndex,
    ACount : integer;
    ASect,
    ALine  : string;
begin
  Result:=-1;
  ACount:=FLines.Count;
  if ACount>0 then begin
    if FCaseSens then
      ASect:=Section
    else
      ASect:=UpperCase(Section);
    for AIndex:=0 to ACount-1 do begin
      if FCaseSens then
        ALine:=FLines[AIndex]
      else
        ALine:=UpperCase(FLines[AIndex]);
      if (ALine<>'') and (ALine[1]='[') and
       (copy(ALine, 2, length(ALine)-2)=ASect) then begin
        Result:=AIndex;
        Exit;
      end;
    end;
  end;
end; {GetSectionIndex}

function TIniList.GetValueFromLine(const Line:string):string;
var P,L : integer;
begin
  Result:='';
  P:=Pos(FSeparator, Line);
  L:=length(Line);
  if (P>0) and (P<L) then
    Result:=copy(Line, P+1, L-P);
end; {GetValueFromLine}

function TIniList.KeyCount(const Section: string):integer;
var SectionIdx,
    KCount,
    AIndex,
    ACount     : integer;
    ALine      : string;
begin
  KCount:=0;
  ACount:=FLines.Count;
  SectionIdx:=GetSectionIndex(Section);
  if (ACount>0) and (SectionIdx>=0) then begin
    AIndex:=SectionIdx;
    repeat
      inc(AIndex);
      if AIndex<ACount then begin
        ALine:=Trim(FLines[AIndex]);
        if (ALine<>'') and (ALine[1]<>'[') and (ALine[1]<>';') then
          inc(KCount);
      end;
    until (AIndex>=ACount) or (ALine='')
     or ((ALine<>'') and (ALine[1]='['));
  end;
  Result:=KCount;
end; {KeyCount}

function TIniList.LoadFromFile:boolean;
begin
  Result:=false;
  if FFileName<>'' then begin
    try
      Changing;
      FLines.LoadFromFile(FFileName);
      SetTitle(FTitle);
      Changed;
      Result:=true;
    except
      Raise;
    end;
  end;
end; {LoadFromFile}

{$IFDEF SR_Delphi4_Up}
function TIniList.LoadFromFile(const FileName:string):boolean;
begin
  Result:=false;
  if FileName<>'' then begin
    try
      Changing;
      FLines.LoadFromFile(FileName);
      SetTitle(FTitle);
      Changed;
      Result:=true;
    except
      Raise;
    end;
  end;
end; {LoadFromFile}
{$ENDIF}

function TIniList.LoadFromStream(AStream: TStream):boolean;
begin
  Result:=false;
  if assigned(AStream) then begin
    try
      Changing;
      FLines.LoadFromStream(AStream);
      SetTitle(FTitle);
      Changed;
      Result:=true;
    except
      Raise;
    end;
  end;
end; {LoadFromStream}

function TIniList.ReadBool(const Section, Key: string; Default: Boolean): Boolean;
var AText : string;
begin
  Result:=Default;
  AText:=ReadValue(Section, Key);
  if AText='0' then
    Result:=false;
  if AText='1' then
    Result:=true;
end; {ReadBool}

function TIniList.ReadDate(const Section, Key: string; Default: TDateTime): TDateTime;
var AText : string;
begin
  AText:=ReadValue(Section, Key);
  if FDateFloat then
    Result:=StrToFloatDef(AText, Default)
  else
    Result:=StrToDateDef(AText, Default);
end; {ReadDate}

function TIniList.ReadDateTime(const Section, Key: string; Default: TDateTime): TDateTime;
var AText : string;
begin
  AText:=ReadValue(Section, Key);
  if FDateFloat then
    Result:=StrToFloatDef(AText, Default)
  else
    Result:=StrToDateTimeDef(AText, Default);
end; {ReadDateTime}

function TIniList.ReadFloat(const Section, Key: string; Default: Double): Double;
var AText : string;
begin
  AText:=ReadValue(Section, Key);
  Result:=StrToFloatDef(AText, Default);
end; {ReadFloat}

function TIniList.ReadInteger(const Section, Key: string; Default: Longint): Longint;
var AText : string;
begin
  AText:=ReadValue(Section, Key);
  Result:=StrToIntDef(AText, Default);
end; {ReadInteger}

function TIniList.ReadString(const Section, Key, Default: string): string;
var AText : string;
begin
  AText:=ReadValue(Section, Key);
  if AText='' then
    Result:=Default
  else
    Result:=AText;
end; {ReadString}

function TIniList.ReadTime(const Section, Key: string; Default: TDateTime): TDateTime;
var AText : string;
begin
  AText:=ReadValue(Section, Key);
  if FDateFloat then
    Result:=StrToFloatDef(AText, Default)
  else
    Result:=StrToTimeDef(AText, Default);
end; {ReadTime}

procedure TIniList.ReadSection(const Section: string; AStrings: TStrings);
var SectionIdx,
    AIndex,
    ACount     : integer;
    ALine      : string;
begin
  ACount:=FLines.Count;
  SectionIdx:=GetSectionIndex(Section);
  if (ACount>0) and (SectionIdx>=0) and assigned(AStrings) then begin
    AStrings.BeginUpdate;
    AStrings.Clear;
    AIndex:=SectionIdx;
    repeat
      inc(AIndex);
      if AIndex<ACount then begin
        ALine:=Trim(FLines[AIndex]);
        if (ALine<>'') and (ALine[1]<>'[') and (ALine[1]<>';') then
          AStrings.Add(ALine);
      end;
    until (AIndex>=ACount) or (ALine='')
     or ((ALine<>'') and (ALine[1]='['));
    AStrings.EndUpdate;
  end;
end; {ReadSection}

procedure TIniList.ReadSections(AStrings: TStrings);
var AIndex,
    ACount : integer;
    ALine  : string;
begin
  ACount:=FLines.Count;
  if (ACount>0) and assigned(AStrings) then begin
    AStrings.BeginUpdate;
    AStrings.Clear;
    for AIndex:=0 to ACount-1 do begin
      ALine:=Trim(FLines[AIndex]);
      if (ALine<>'') and (ALine[1]='[') and (ALine[length(ALine)]=']') then
        AStrings.Add(copy(ALine, 2, length(ALine)-2));
    end;
    AStrings.EndUpdate;
  end;
end; {ReadSections}

procedure TIniList.ReadSectionKeys(const Section: string; AStrings: TStrings);
var SectionIdx,
    AIndex,
    ACount     : integer;
    ALine      : string;
begin
  ACount:=FLines.Count;
  SectionIdx:=GetSectionIndex(Section);
  if (ACount>0) and (SectionIdx>=0) and assigned(AStrings) then begin
    AStrings.BeginUpdate;
    AStrings.Clear;
    AIndex:=SectionIdx;
    repeat
      inc(AIndex);
      if AIndex<ACount then begin
        ALine:=Trim(FLines[AIndex]);
        if (ALine<>'') and (ALine[1]<>'[') and (ALine[1]<>';') then
          AStrings.Add(GetKeyFromLine(ALine));
      end;
    until (AIndex>=ACount) or (ALine='')
     or ((ALine<>'') and (ALine[1]='['));
    AStrings.EndUpdate;
  end;
end; {ReadSectionKeys}

procedure TIniList.ReadSectionValues(const Section: string; AStrings: TStrings);
var SectionIdx,
    AIndex,
    ACount     : integer;
    ALine      : string;
begin
  ACount:=FLines.Count;
  SectionIdx:=GetSectionIndex(Section);
  if (ACount>0) and (SectionIdx>=0) and assigned(AStrings) then begin
    AStrings.BeginUpdate;
    AStrings.Clear;
    AIndex:=SectionIdx;
    repeat
      inc(AIndex);
      if AIndex<ACount then begin
        ALine:=Trim(FLines[AIndex]);
        if (ALine<>'') and (ALine[1]<>'[') and (ALine[1]<>';') then
          AStrings.Add(GetValueFromLine(ALine));
      end;
    until (AIndex>=ACount) or (ALine='')
     or ((ALine<>'') and (ALine[1]='['));
    AStrings.EndUpdate;
  end;
end; {ReadSectionValues}

function TIniList.ReadValue(const Section, Key: string):string;
var KeyIndex  : integer;
begin
  Result:='';
  KeyIndex:=GetKeyIndex(Section, Key);
  if KeyIndex>=0 then
    Result:=GetValueFromLine(FLines[KeyIndex]);
end; {ReadValue}

function TIniList.SaveToFile:boolean;
begin
  Result:=false;
  if FFileName<>'' then begin
    try
      FLines.SaveToFile(FFileName);
      Result:=true;
    except
      Raise;
    end;
  end;
end; {SaveToFile}

{$IFDEF SR_Delphi4_Up}
function TIniList.SaveToFile(const FileName:string):boolean;
begin
  Result:=false;
  if FileName<>'' then begin
    try
      FLines.SaveToFile(FileName);
      Result:=true;
    except
      Raise;
    end;
  end;
end; {SaveToFile}
{$ENDIF}

function TIniList.SaveToStream(AStream: TStream):boolean;
begin
  Result:=false;
  if assigned(AStream) then begin
    try
      FLines.SaveToStream(AStream);
      Result:=true;
    except
      Raise;
    end;
  end;
end; {SaveToStream}

function TIniList.SectionCount:integer;
var AIndex,
    ACount,
    SCount : integer;
    ALine  : string;
begin
  SCount:=0;
  ACount:=FLines.Count;
  if ACount>0 then begin
    for AIndex:=0 to ACount-1 do begin
      ALine:=Trim(FLines[AIndex]);
      if (ALine<>'') and (ALine[1]='[') and (ALine[length(ALine)]=']') then
        inc(SCount);
    end;
  end;
  Result:=SCount;
end; {SectionCount}

procedure TIniList.SetSeparator(NewValue: char);
var ACount,
    i,P      : integer;
    ALine    : string;
    Modified : boolean;
begin
  if NewValue<>FSeparator then begin
    ACount:=FLines.Count;
    if ACount>0 then begin
      Changing;
      Modified:=false;
      for i:=0 to ACount-1 do begin
        ALine:=FLines[i];
        if (ALine<>'') and (ALine[1]<>';') and (Pos('[', ALine)=0) then begin
          P:=Pos(FSeparator, ALine);
          if P>0 then begin
            ALine[P]:=NewValue;
            FLines[i]:=ALine;
            Modified:=true;
          end;
        end;
      end;
      if Modified then
        Changed;
    end;
    FSeparator:=NewValue;
  end;
end; {SetSeparator}

procedure TIniList.SetTitle(NewValue: string);
var ALine : string;
begin
  FTitle:=NewValue;
  if NewValue='' then
    NewValue:='NoTitle';
  if FLines.Count>0 then begin
    ALine:=FLines[0];
    if (ALine<>'') and (ALine[1]=';') then begin
      ALine:='; '+NewValue;
      FLines[0]:=ALine;
    end
    else begin
      FLines.Insert(0, '');
      FLines.Insert(0, '; '+NewValue);
    end;
  end
  else begin
    FLines.Add('; '+NewValue);
    FLines.Add('');
  end;
end; {SetTitle}

procedure TIniList.WriteBool(const Section, Key: string; Value: Boolean);
begin
  WriteValue(Section, Key, IntToStr(ord(Value)));
end; {WriteBool}

procedure TIniList.WriteDate(const Section, Key: string; Value: TDateTime);
var AText : string;
begin
  if FDateFloat then
    AText:=FloatToStr(Value)
  else
    AText:=DateToStrDef(Value, '', false);
  if AText<>'' then
    WriteValue(Section, Key, AText);
end; {WriteDate}

procedure TIniList.WriteDateTime(const Section, Key: string; Value: TDateTime);
var AText : string;
begin
  if FDateFloat then
    AText:=FloatToStr(Value)
  else
    AText:=DateTimeToStrDef(Value, '', false);
  if AText<>'' then
    WriteValue(Section, Key, AText);
end; {WriteDateTime}

procedure TIniList.WriteFloat(const Section, Key: string; Value: Double);
begin
  WriteValue(Section, Key, FloatToStr(Value));
end; {WriteFloat}

procedure TIniList.WriteInteger(const Section, Key: string; Value: Longint);
begin
  WriteValue(Section, Key, IntToStr(Value));
end; {WriteInteger}

procedure TIniList.WriteString(const Section, Key, Value: String);
begin
  WriteValue(Section, Key, Value);
end; {WriteString}

procedure TIniList.WriteTime(const Section, Key: string; Value: TDateTime);
var AText : string;
begin
  if FDateFloat then
    AText:=FloatToStr(Value)
  else
    AText:=TimeToStrDef(Value, '');
  if AText<>'' then
    WriteValue(Section, Key, AText);
end; {WriteTime}

procedure TIniList.WriteValue(const Section, Key: string; Value: string);
var SectIdx : integer;
begin
  SectIdx:=AddSection(Section);
  if SectIdx>=0 then
    AddKey(SectIdx, Key, Value);
end; {WriteBool}

procedure Register;
begin
  RegisterComponents('Simon', [TIniList]);
end;

end.
