Contributor: BRAD ZAVITSKY

{I found a few errors and made a few improvements with my INI file
handler I posted this earlier.}

{$A+,B-,D-,F-,G-,I-,L-,N-,O-,P-,Q-,R-,S+,T-,V-,X+}

{INI  Version 1.2   Copr. 1996 Brad Zavitsky
 FREEWARE: Swag/Commercial use fine

Thanks to Andrew Eigus for his great EnhDos unit from which I got
Pas2Pchar

Version Info
1.1
====
  o Fixed error, needed StrNew instead of StrCopy
  o Added support for TP6- users.

1.2
====
  o  Better memory detection: now find paragraph size
  o  Optimized memory testing
  o  Entries will not be added if the entry or value is blank
  o  Fixed bug in split
  o  Added a comment delimeter set
  o  Added comments to source code
  o  Optimized space/tab stripping for memory
  o  Made FName a constant
  o  Made list global
}

unit INI; {031496}

interface

{$IFDEF VER70}
uses Strings;
{$ENDIF}

type
  CSet = set of Char;
  TEntry = string[32];
  {Linked list to store INI entries and values}
  PEntryList = ^TEntryList;
  TEntryList = record
     Entry,
     Value: PChar;
     Next: PEntrylist;
  end;

const
  {If these characters precede a line, it will be counted as a comment}
  CommentDelim: CSet = [';','[','#'];

var
  List: PEntryList; {Holds entries and values}


{Load the ini file into a linked list; this must be done before 
attempting
to get any entries
Returns:
  0 = Everything is fine
  1 = Error opening (ie.. File not found)
  2 = Not enough memory (remember to call freeini to get rid of already
      stored variables}
function LoadINI(const FName: string): Integer;
{Frees the memory allocated from LoadINI}
procedure FreeINI;
{Gets the value from a specified entry}
function GetEntry(Entry: TEntry; const Default: string): string;

implementation

var
  S,E,V: string; {Temporary strings, E=Entry, V=Value}
  Temp: PChar;   {Used to store a string as a PChar for StrComp}
  T: Text;       {INI text file}
  LNew: PEntryList; {New link}

{Pascal string to ASCIIZ; Thanks to Andrew Eigus}
function Pas2PChar(const S : string) : PChar; assembler;
asm
  les di,S
  mov al,byte ptr [es:di]
  cmp al,0
  je  @@1
  push di
  sub ah,ah
  cld
  inc al
  stosb
  add di,ax
  dec di
  sub al,al
  stosb
  pop di
@@1:
  inc di
  mov dx,es
  mov ax,di
end; { Pas2PChar }

{ASCIIZ to Pascal}
function PChar2Pas(P: PChar): string;
var
  IDX: Integer;
begin
  Idx := 0;
  while P[IDX] <> #0 do
  begin
    PChar2Pas[succ(Idx)] := P[IDX];
    inc(Idx);
  end;
  PChar2Pas[0] := Chr(Idx);
end;

{Fast uppercase function}
function UpperCase(const S: string): string; assembler;
asm
  push ds
  lds si, s
  les di, @result
  lodsb
  stosb
  xor ch, ch
  mov cl, al
  jcxz @empty
@upperloop:
  lodsb
  cmp al, 'a'
  jb @cont
  cmp al, 'z'
  ja @cont
  sub al, ' '
@cont:
  stosb
  loop @upperloop
@empty:
  pop ds
end;

{Removes all instances of DELCHAR from the right side of S}
function CutRight(const S: string; DelChar: Char): string;
var
  Len: Byte;
begin
  CutRight := S;
  Len := Ord(S[0]);
  while S[Len] = DelChar do Dec(Len);
  CutRight[0] := Chr(Len);
end;

{Removes all instances of DELCHAR from the left side of S}
function CutLeft(const S: string; DelChar: Char): string;
var
  Cnt: Byte;
begin
  Cnt := 1;
  while S[Cnt] = DelChar do Inc(Cnt);
  CutLeft := Copy(S, Cnt, Length(S)-pred(Cnt));
end;

{Splits a INI string into 2 parts: the entry and value}
procedure Split(const S: string; var E,V: string);
var
  len: Byte;
begin
  Len := Pos('=',S);
  if Len <> 0 then
  begin
    V := Copy(S, succ(Len), succ(Length(S)-Len));
    E := S;
    E[0] := chr(pred(Len));
    V := CutLeft(V, #32);
    V := CutLeft(V, #9);
    V := CutRight(V, #32);
    V := CutRight(V, #9);
    E := CutRight(E, #32);
    E := CutRight(E, #9);
  end else
  begin      {Invalid entry}
    E := '';
    V := '';
  end;
end;

{String unit emulation}
{$IFNDEF VER70}
function StrNew(S: PChar): PChar;
var
  I,
  L: Word;
  P: PChar;
begin
  StrNew := nil;
  if (S<>nil) and (S^ <> #0) then
  begin
    L := 0;
    while S[L] <> #0 do inc(L);
    inc(L);
    GetMem(P,L);
    if P <> nil then for I := 0 to L do P[I] := S[I];
    StrNew := P;
  end;
end;

procedure StrDispose(S: PChar);
var
  L: Word;
begin
  L := 0;
  while S[L] <> #0 do inc(L);
  if S <> nil then FreeMem(S,succ(L));
end;

function StrComp(Str1, Str2: PChar): Integer;
var
  L1,
  L2: Word;
begin
  StrComp := 1;
  L1 := 0;
  while Str1[L1] <> #0 do inc(L1);
  l2 := 0;
  while Str2[L2] <> #0 do inc(L2);
  if L1 <> L2 then exit;
  for L1 := 0 to L2 do if Str1[L1] <> Str2[L1] then exit;
  StrComp := 0;
end;

{$ENDIF}

{Calculates the amount of memory that would be actually allocated;
 Counts it in 16byte paragraphs}
function MemUsed(M: Word): Word;
var
  R: Byte;
begin
  MemUsed := succ(M shr 4) shl 4;
end;

function LoadINI(const FName: string): Integer;
begin
  assign(T, FName);
  reset(T);
  if IOResult <> 0 then {Exit if there is a file error}
  begin
    LoadINI := 1;
    Exit;
  end;

  while not Eof(T) do
  begin
    Readln(T, S);
    S := uppercase(S);
    S := CutLeft(S, #32); {Remove spaces and tabs}
    S := CutLeft(S, #9);
    {Make sure string is not a comment}
    if not (S[1] in CommentDelim) and (Length(S) > 0) then
    begin
      Split(S, E, V); {Split string into entry and value}

      {When low on memory, start checking to make sure we have enough}
      if MaxAvail < 300 then if MaxAvail < MemUsed(SizeOf(TEntryList)) +
        MemUsed(succ(Length(V)))+ MemUsed(Succ(Length(E))) then
      begin
        LoadINI := 2;
        {FreeINI;}
        CLose(T);
        Exit;
      end;

      if (V <> '') and (E <> '') then
      begin
        {Add new link to list}
        New(LNew);
        {Allocate new copies on the stack; we don't want the PChar's
         pointing to E and V}
        Lnew^.Entry := StrNew(Pas2Pchar(E));
        Lnew^.Value := StrNew(Pas2Pchar(V));
        Lnew^.Next := List;
        List := LNew;
      end;
    end;
  end;

  LoadINI := 0;
  close(T);
end;

procedure FreeINI;
begin
  while List <> nil do
  begin
    StrDispose(List^.Entry);
    StrDispose(List^.Value);
    LNew := List;
    List := List^.Next;
    Dispose(Lnew);
  end;
end;

function GetEntry(Entry: TEntry; const Default: string): string;
var
  NotFound: Boolean;
begin
  NotFound := True;
  LNew := List;
  Entry := uppercase(Entry);
  {Make this a PCHAR so we can use StrComp on it}
  Temp := Pas2Pchar(Entry);
  while (LNew <> nil) and NotFound do
  begin
    if StrComp(Lnew^.Entry, Temp) = 0 then
    begin
      NotFound := False;
      GetEntry := PChar2Pas(LNew^.Value);
    end;
    Lnew := LNew^.Next;
  end;
  {Return Default uppercase, to provide consistancy}
  if NotFound then GetEntry := uppercase(Default);
end;

end.