(*****************************************************************************)
(*                                                                           *)
(*        filename        : XINI.PAS                                         *)
(*        author          : Max Maischein  /       FidoNet :  2:249/6.17     *)
(*        adapted         : Stefan Boether / Compuserve Id : 100023,275      *)
(*                                                 FidoNet :  2:243/91.331   *)
(*                                  Internet: 100023.275@CompuServe.COM      *)
(*        system          : BP 7.0                                           *)
(*        changes         :                                                  *)
(*        when    what                                                who    *)
(*---------------------------------------------------------------------------*)
(*       16.01.93 Use the PChar-Type came with BP 7.0 also for DOS    Stefc  *)
(*****************************************************************************)
(*  Description :  An object for handling *.INI files !                      *)
(*****************************************************************************)
{Header-End}
(*
   Notification :  The most of the work came from Max !! Many thanks
                   to him from me. I adapated it to my Xlibary's for
                   my own suppose, so if you want the original
                   unit please contact Max not me ! In his original
                   unit there also is a little more flexible than
                   my version. But my is smaller because many of
                   the function he has in his, I've in my own libs !
                   And I have use the IScan function from the
                   EDITORS Unit here, so it may be some faster !
                   If you find some bugs in this source, please
                   let me know ?

                                       - Mfg Stefc -

*)

UNIT   xIni; {$O+,D+,I-}

INTERFACE

USES   Dos,
       Objects,
       Strings;

TYPE   PProfile= ^TProfile;
       TProfile= object( TObject )
         constructor Init( FileName: PathStr; AGroup: String );
         destructor  Done; virtual;
         function    GetString  ( ItemName:String; Default:String) : String;
         procedure   WriteString( ItemName:String; Value  :String );
         function    GetInt     ( ItemName:String; Default:Integer):Integer;
         procedure   WriteInt   ( ItemName:String; Value  :Integer);
       private
         Changed    : Boolean;
         TheBuffer  : PChar;
         TheFile    : file;

         Group      : String;
         GroupStart : PChar;
         GroupSize  : Word;

         Function  SetGroup( NewGroup : String ) : Boolean;
         Procedure CreateGroup( NewGroup : String );
       End;

IMPLEMENTATION

const  cr   = #$0D;
       lf   = #$0A;
       crlf = cr+lf;

{ Thanks to Borland for their fast string search asm procs ! }
const sfSearchFailed = $FFFF;

function IScan(var Block; Size: Word; Str: String): Word; assembler;
 var S: String;
asm
	PUSH	DS
	MOV	AX,SS
	MOV	ES,AX
	LEA	DI,S
	LDS	SI,Str
	XOR	AH,AH
	LODSB
	STOSB
	MOV	CX,AX
	MOV	BX,AX
	JCXZ	@@9
@@1:	LODSB
	CMP	AL,'a'
	JB	@@2
	CMP	AL,'z'
	JA	@@2
	SUB	AL,20H
@@2:	STOSB
	LOOP	@@1
	SUB	DI,BX
	LDS	SI,Block
	MOV	CX,Size
	JCXZ	@@8
	CLD
	SUB	CX,BX
	JB	@@8
	INC	CX
@@4:	MOV	AH,ES:[DI]
	AND	AH,$DF
@@5:	LODSB
	AND	AL,$DF
	CMP	AL,AH
	LOOPNE	@@5
	JNE	@@8
	DEC	SI
	MOV	DX,CX
        MOV	CX,BX
@@6:	REPE	CMPSB
	JE	@@10
	MOV	AL,DS:[SI-1]
	CMP	AL,'a'
	JB	@@7
	CMP	AL,'z'
	JA	@@7
	SUB	AL,20H
@@7:	CMP	AL,ES:[DI-1]
	JE	@@6
	SUB	CX,BX
	ADD	SI,CX
	ADD	DI,CX
	INC	SI
	MOV	CX,DX
        OR      CX,CX
	JNE	@@4
@@8:	XOR	AX,AX
	JMP	@@11
@@9:    MOV	AX, 1
	JMP	@@11
@@10:	SUB	SI,BX
	MOV	AX,SI
	SUB	AX,WORD PTR Block
	INC	AX
@@11:	DEC	AX
	POP	DS
end;

{  - Thanks to Freddy Ertl and Ralph Machholz for the following two procs ! }
function Str2PChar(var St:String):PChar;
  var i : Integer;
begin
  i := Length(St);
  Move( St[1], St[0], I );
  St[i] := #0;
  Str2PChar := PChar(@St);
end;

function PChar2Str(var St:String):String;
  var i : Integer;
begin
  i := 0 ;
  while (St[i] <> #0) do inc(i);
  If i > 254 then RunError(255);
  Move(St[0],St[1],I);
  St[0]:=Chr(i);
  PChar2Str := St;
end;

{ - Some stuff came from me ! }
function UpCaseStr( St:String):string;
  var I : BYTE;
begin
  for I := 1 TO LENGTH( St ) DO
    St[I] := UpCase( St[i] );
  UpCaseStr := St;
END;

procedure CheckGroup(var NewGroup:String);
begin
  If NewGroup[ 1 ] <> '[' then
     NewGroup := '[' + NewGroup;
  If NewGroup[Length(NewGroup)] <> ']' then
     NewGroup := NewGroup + ']';
end;

procedure CheckItem(var ItemName:String);
begin
  if ItemName[Length(ItemName)] <> '=' then
     ItemName := ItemName + '=';
end;

(************************************************************************)
 (*                                                                      *)
  (*        Object : TProFile                                             *)
   (*                                                                      *)
    (************************************************************************)

constructor TProfile.Init;
  const fmDenyWrite  = $20;
  var   TheSize : word;
        SavFileMode : Word;
begin
  inherited Init;
  If Pos( '.',FileName)= 0 then FileName := FileName + '.INI';

  SavFileMode := filemode;
  filemode := fmDenyWrite;     { Other only can read the file !!! }
  Assign( TheFile, FileName );
  Reset ( TheFile, 1 );
  if ioresult <> 0 then begin
     rewrite( TheFile, 1 );
     if ioresult <> 0 then
        fail
     else
        TheSize := 0;
  end else
     TheSize := filesize(TheFile);
  filemode := SavFilemode;

  GetMem( TheBuffer, Succ(TheSize)); { Get enough memory to hold the entire File }
  BlockRead( TheFile, TheBuffer^,TheSize);
  StrLCopy( TheBuffer,TheBuffer,TheSize);

  GroupSize  := 0;
  GroupStart := TheBuffer;

  If not SetGroup( AGroup ) then
     CreateGroup( AGroup );

  Changed := False;
End;

Destructor TProfile.Done;
Begin
  If Changed then begin
     ReWrite( TheFile, 1 );
     BlockWrite( TheFile, TheBuffer^, StrLen(TheBuffer));
  end;
  Close( TheFile );
  StrDispose(TheBuffer);
  inherited Done;
End;

{ - Go to the specific group }
Function TProfile.SetGroup;
  Var MyPos  : Word;
      P      : PChar;
Begin
  If NewGroup = '' then Begin
     GroupStart := TheBuffer;
     GroupSize  := StrLen(TheBuffer);
     SetGroup   := True;
     Exit; { could be better, but ;-) }
  End;

  CheckGroup(NewGroup);
  MyPos    := IScan( TheBuffer^, StrLen(TheBuffer), UpcaseStr(NewGroup));
  If MyPos <> sfSearchFailed then Begin
     GroupStart := TheBuffer + MyPos;
     Group      := NewGroup;
     P          := StrScan( GroupStart+Length(NewGroup), '[' );
     If P = nil then
        GroupSize := StrLen(GroupStart)
     else
        GroupSize := P-GroupStart;
     SetGroup := True;
  End else
     SetGroup := False;
End;

{ - Append a new group into the INI-File - }
Procedure TProfile.CreateGroup;
 Var NewBuffer : PChar;
Begin
  CheckGroup(NewGroup);
  NewGroup := NewGroup + CRLF;

  GetMem  ( NewBuffer, StrLen(TheBuffer)+Length(NewGroup));
  StrLCopy( NewBuffer, TheBuffer,StrLen(TheBuffer));
  StrCat  ( NewBuffer, Str2PChar(NewGroup));

  StrDispose(TheBuffer);
  TheBuffer := NewBuffer;
  PChar2Str(NewGroup);
  Delete( NewGroup, Pred(Length(NewGroup)), 2 );
  SetGroup(NewGroup);
  Changed := True;
End;

{ - Get a string-item from the group }
Function TProfile.GetString;
Var MyPos : Word;
    P,Q   : PChar;
    Tmp   : array[0..255] of char;
Begin
  GetString := Default;
  CheckItem( ItemName );
  MyPos := IScan(GroupStart^,GroupSize,UpcaseStr(ItemName));
  If MyPos <> sfSearchFailed then begin
     Q := GroupStart + (MyPos + Length(ItemName));
     P := StrScan(Q, CR );
     If P <> nil then
        GetString := StrPas(StrLCopy(Tmp,Q,P-Q));
  end
End;

{ - Write a string-item to the group }
Procedure TProfile.WriteString;
  Var NewBuffer : PChar;
      NewString : String;
      MyPos     : Word;

  procedure ResetBuffer;
  begin
    StrDispose(TheBuffer);
    TheBuffer := NewBuffer;
    SetGroup(Group);
    Changed := True;
  end;

Begin
  CheckItem(ItemName);
  If GetString(ItemName,'') <> '' then begin { remove old Item + Value }
     NewString := ItemName + GetString( ItemName,'') + CRLF;
     GetMem( NewBuffer, StrLen(TheBuffer)-Length(NewString));
     MyPos:= IScan( GroupStart^, GroupSize, UpcaseStr(ItemName));
     StrLCopy( NewBuffer, TheBuffer,(GroupStart-TheBuffer)+MyPos);
     StrCat  ( NewBuffer, GroupStart + MyPos + Length(NewString));
     ResetBuffer;
  End;

  If Value <> '' then Begin  { add new item + value }
     NewString:= ItemName + Value + CRLF;
     GetMem  ( NewBuffer, StrLen(TheBuffer)+Length(NewString));
     StrLCopy( NewBuffer, TheBuffer, (GroupStart-TheBuffer)+GroupSize);
     StrCat  ( NewBuffer, Str2PChar( NewString ));
     StrCat  ( NewBuffer, GroupStart + GroupSize );
     ResetBuffer;
  End;
End;

{- Get am integer value from the INI-file }
function TProfile.GetInt;
  var St:String; V,C: Integer;
begin
  St:=GetString(ItemName,'');
  IF St = '' then
     GetInt := Default
  else begin
     Val(St,V,C);
     IF C = 0 then GetInt := V
              else GetInt := Default;
  end;
end;

{- Write an integer value to the INI-file }
procedure TProfile.WriteInt;
  var St:String[6];
begin
  str(Value,St);
  WriteString(ItemName,St);
end;

end.
