[Back to OOP SWAG index] [Back to Main SWAG index] [Original]
{$A+,F+,I-,R-,S-,V-}
unit IniTV; {unit for managing INI files using TurboVision/OWL}
{*********************************************}
{* INITV.PAS 1.04 *}
{* Copyright (c) Steve Sneed 1993 *}
{*********************************************}
{*
NOTE: This code was quickly adapted from some using Object Professional's
DoubleList object.
*}
{$IFNDEF Ver70}
!! STOP COMPILE: This unit requires BP7 !!
{$ENDIF}
{if Object Professional is available, use its string routines}
{.$DEFINE UseOPro}
interface
uses
{$IFDEF UseOPro}
OpString,
{$ENDIF}
Objects;
const
EncryptionKey : String[80] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
FBufSize = 4096;
type
PLine = ^TLine;
TLine =
object(TObject)
PL : PString;
constructor Init(S : String);
destructor Done; virtual;
procedure Update(S : String);
end;
PIni = ^TIni;
TIni =
object(TCollection)
IniName : String;
FBufr : PChar;
constructor Init(ALimit, ADelta : Integer;
FN : String;
Sparse, Create : Boolean);
{-Construct our INI file object. if Sparse=True, load only "active"
lines (file is considered read-only.) File always updates on
changes; use SetFlushMode to control.}
destructor Done; virtual;
{-Destroy object when done}
procedure Reload;
{-Reload the INI file after it may have changed externally}
procedure FlushFile;
{-Force an update of the physical file from the current list}
procedure SetFlushMode(Always : Boolean);
{-Turn off/on auto-updating of file when an item is modified}
procedure SetExitFlushMode(DoIt : Boolean);
{-Turn off/on updating of file when the object is disposed}
function GetProfileString(Title, Group, Default : String) : String;
{-Return string item "Title" in "[Group]", or default if not found}
function GetEncryptedProfileString(Title, Group, Default : String) : String;
{-Same as GetProfileString but decrypts the found string}
function GetProfileBool(Title, Group : String; Default : Boolean) : Boolean;
{-Return boolean item "Title" in "[Group]", or default if not found}
function GetProfileByte(Title, Group : String; Default : Byte) : Byte;
{-Return byte item "Title" in "[Group]", or default if not
found or Not A Number}
function GetProfileInt(Title, Group : String; Default : Integer) : Integer;
{-Return integer item "Title" in "[Group]", or default if not
found or NAN}
function GetProfileWord(Title, Group : String; Default : Word) : Word;
{-Return word item "Title" in "[Group]", or default if not
found or NAN}
function GetProfileLong(Title, Group : String; Default : LongInt) : LongInt;
{-Return longint item "Title" in "[Group]", or default if not
found or NAN}
function SetProfileString(Title, Group, NewVal : String) : Boolean;
{-Change existing item "Title" in "[Group]" to "NewVal"}
function SetEncryptedProfileString(Title, Group, NewVal : String) : Boolean;
{-Change existing item "Title" in "[Group]" to "NewVal"}
function AddProfileString(Title, Group, NewVal : String) : Boolean;
{-Add new item "Title=NewVal" to "[Group]". Creates [Group] if not
found or if "Title" = '', else adds "Title=NewVal" as last item in
[Group]}
function AddEncryptedProfileString(Title, Group, NewVal : String) : Boolean;
{-Same as AddProfileString but encrypts "NewVal" when adding}
function KillProfileItem(Title, Group : String) : Boolean;
{-Completely remove the "Title" entry in "[Group]"}
function KillProfileGroup(Group : String) : Boolean;
{-Kill the entire group "[Group]", including group header}
function EnumGroups(P : PStringCollection; Clr : Boolean) : Boolean;
{-Return P loaded with the names of all groups in the file. Returns
false only on error. On return P is in file order rather than
sorted order.}
function EnumGroupItems(P : PStringCollection; Group : String; Clr : Boolean) : Boolean;
{-Return P loaded with all items in group [Group]. Returns false
if Group not found or error. On return P is in file order rather
than sorted order}
private {these used internally only}
IniF : Text;
NeedUpd : Boolean;
AlwaysUpd : Boolean;
IsSparse : Boolean;
ExitFlush : Boolean;
function GetIniNode(Title, Group : String) : PLine;
function GetLastNodeInGroup(Group : String) : PLine;
function GetProfilePrim(Title, Group : String) : String;
end;
procedure SetEncryptionKey(NewKey : String);
{-define the encryption key}
implementation
function NewStr(const S: String): PString;
{-NOTE: The default NewStr returns a nil pointer for empty strings. This
will cause problems, so we define a NewStr that always allocates a ptr.}
var
P: PString;
begin
GetMem(P, Length(S) + 1);
P^ := S;
NewStr := P;
end;
procedure CleanHexStr(var S : string);
{-handle ASM- and C-style hex notations}
var
SLen : Byte absolute S;
begin
while S[SLen] = ' ' do
Dec(SLen);
if (SLen > 1) and (Upcase(S[SLen]) = 'H') then begin
Move(S[1], S[2], SLen-1);
S[1] := '$';
end
else if (SLen > 2) and (S[1] = '0') and (Upcase(S[2]) = 'X') then begin
Dec(SLen);
Move(S[3], S[2], SLen-1);
S[1] := '$';
end;
end;
{$IFNDEF UseOPro}
{-If we're not using OPro, define the string manipulation routines we need.}
const
Digits : Array[0..$F] of Char = '0123456789ABCDEF';
function HexB(B : Byte) : string;
{-Return hex string for byte}
begin
HexB[0] := #2;
HexB[1] := Digits[B shr 4];
HexB[2] := Digits[B and $F];
end;
function Trim(S : string) : string;
{-Return a string with leading and trailing white space removed}
var
I : Word;
SLen : Byte absolute S;
begin
while (SLen > 0) and (S[SLen] <= ' ') do
Dec(SLen);
I := 1;
while (I <= SLen) and (S[I] <= ' ') do
Inc(I);
Dec(I);
if I > 0 then
Delete(S, 1, I);
Trim := S;
end;
function StUpcase(S : String) : String;
{-Convert a string to all uppercase. Ignores internationalization issues}
var
I : Byte;
begin
for I := 1 to Length(S) do
S[i] := Upcase(S[i]);
StUpcase := S;
end;
{$ENDIF}
function StripBrackets(S : String) : String;
var
B : Byte absolute S;
begin
S := Trim(S);
if S[b] = ']' then
Dec(B);
if S[1] = '[' then begin
Move(S[2], S[1], B-1);
Dec(B);
end;
StripBrackets := StUpcase(S);
end;
procedure SetEncryptionKey(NewKey : String);
{-Define the encryption key to use}
begin
EncryptionKey := NewKey;
end;
function Crypt(S : String) : String;
{-simple self-reversing xor encryption}
var
SI, KI : Byte;
T : String;
begin
T := '';
KI := 1;
for SI := 1 to Length(S) do begin
T := T + Chr(Byte(S[SI]) xor Byte(EncryptionKey[KI]));
Inc(KI);
if KI > Length(EncryptionKey) then
KI := 1;
end;
Crypt := T;
end;
function Encrypt(S : String) : String;
{-Convert S to XOR-encrypted string, then "hex-ize"}
var
T, U : String;
I : Integer;
begin
U := '';
T := Crypt(S);
for I := 1 to Length(T) do
U := U + HexB(Byte(T[i]));
Encrypt := U;
end;
function Decrypt(S : String) : String;
{-Convert "hex-ized" string to encrypted raw string, and decrypt}
var
T,U : String;
I,C : Integer;
begin
T := '';
while S <> '' do begin
U := '$'+Copy(S, 1, 2);
Delete(S, 1, 2);
Val(U, I, C);
T := T + Char(I);
end;
Decrypt := Crypt(T);
end;
{---------------------------------------------------------------------------}
constructor TLine.Init(S : String);
begin
inherited Init;
PL := NewStr(S);
end;
destructor TLine.Done;
begin
DisposeStr(PL);
inherited Done;
end;
procedure TLine.Update(S : String);
begin
DisposeStr(PL);
PL := NewStr(S);
end;
{---------------------------------------------------------------------------}
constructor TIni.Init(ALimit, ADelta : Integer;
FN : String;
Sparse, Create : Boolean);
var
P : PLine;
S : String;
begin
inherited Init(ALimit, ADelta);
GetMem(FBufr, FBufSize);
IsSparse := Sparse;
NeedUpd := False;
AlwaysUpd := False;
ExitFlush := False;
{load INI file}
IniName := FN;
Assign(IniF, IniName);
SetTextBuf(IniF, FBufr[0], FBufSize);
Reset(IniF);
if IOResult <> 0 then begin
{file doesn't yet exist; drop out}
if not Create then begin
Done;
Fail;
end
else begin
NeedUpd := True;
Exit;
end;
end;
while not EOF(IniF) do begin
ReadLn(IniF, S);
if IOResult <> 0 then begin
{read error here means something is wrong; bomb it}
Close(IniF); if IOresult = 0 then ;
Done;
Fail;
end;
{add the string to the collection}
S := Trim(S);
if (not(Sparse)) or ((S <> '') and (S[1] <> ';')) then begin
New(P, Init(S));
if P = nil then begin
{out of memory, bomb it}
Close(IniF);
if IOResult = 0 then ;
Done;
Fail;
end;
Insert(P);
end;
end;
Close(IniF);
if IOResult = 0 then ;
AlwaysUpd := True;
ExitFlush := True;
end;
destructor TIni.Done;
begin
if (NeedUpd) and (ExitFlush) then
FlushFile;
FreeMem(FBufr, FBufSize);
inherited Done;
end;
procedure TIni.Reload;
var
P : PLine;
S : String;
begin
FreeAll;
Assign(IniF, IniName);
SetTextBuf(IniF, FBufr[0], FBufSize);
Reset(IniF);
if IOResult <> 0 then
Exit;
while not EOF(IniF) do begin
ReadLn(IniF, S);
if IOResult <> 0 then begin
{read error here means something is wrong; bomb it}
Close(IniF); if IOresult = 0 then ;
Exit;
end;
S := Trim(S);
if (not(IsSparse)) or ((S <> '') and (S[1] <> ';')) then begin
New(P, Init(S));
if P = nil then begin
{out of memory, bomb it}
Close(IniF); if IOResult = 0 then ;
Exit;
end;
Insert(P);
end;
end;
Close(IniF);
if IOResult = 0 then ;
end;
procedure TIni.SetFlushMode(Always : Boolean);
begin
AlwaysUpd := Always;
end;
procedure TIni.SetExitFlushMode(DoIt : Boolean);
begin
ExitFlush := DoIt;
end;
procedure TIni.FlushFile;
{-Force the INI file to be rewritten}
var
S : String;
P : PLine;
I : Integer;
begin
if IsSparse then
Exit;
Assign(IniF, IniName);
SetTextBuf(IniF, FBufr[0], FBufSize);
Rewrite(IniF);
if IOResult <> 0 then
Exit;
I := 0;
while I < Count do begin
P := PLine(At(I));
WriteLn(IniF, P^.PL^);
if IOResult <> 0 then begin
Close(IniF);
if IOResult = 0 then ;
exit;
end;
Inc(I);
end;
Close(IniF);
if IOResult = 0 then ;
NeedUpd := False;
end;
function TIni.GetIniNode(Title, Group : String) : PLine;
{-Return the Title node in Group, or nil if not found}
var
P : PLine;
S : String;
I : Integer;
GroupSeen : Boolean;
begin
GetIniNode := nil;
if Count = 0 then exit;
{fixup strings as needed}
if Group[1] <> '[' then
Group := '['+Group+']';
Group := StUpcase(Group);
Title := StUpcase(Title);
{search}
GroupSeen := False;
I := 0;
while I < Count do begin
P := PLine(At(I));
if P^.PL^[1] = '[' then begin
{a group header...}
if StUpcase(P^.PL^) = Group then
{in our group}
GroupSeen := True
else if GroupSeen then
{exhausted all options in our group; get out}
exit;
end
else if (GroupSeen) and (P^.PL^[1] <> ';') then begin
{in our group, see if the title matches}
S := Copy(P^.PL^, 1, Pos('=', P^.PL^)-1);
S := Trim(S);
S := StUpcase(S);
if Title = S then begin
GetIniNode := P;
exit;
end;
end;
Inc(I);
end;
end;
function TIni.GetLastNodeInGroup(Group : String) : PLine;
{-Return the last node in Group, or nil if not found}
var
P,Q : PLine;
S : String;
I : Integer;
GroupSeen : Boolean;
begin
GetLastNodeInGroup := nil;
if Count = 0 then exit;
{fixup strings as needed}
if Group[1] <> '[' then
Group := '['+Group+']';
Group := StUpcase(Group);
{search}
GroupSeen := False;
Q := nil;
I := 0;
while I < Count do begin
P := PLine(At(I));
if P^.PL^[1] = '[' then begin
{a group header...}
if StUpcase(P^.PL^) = Group then
{in our group}
GroupSeen := True
else if (GroupSeen) then begin
{exhausted all lines in our group, return the last pointer}
if Q = nil then
Q := PLine(At(I-1));
I := IndexOf(Q);
while (I >= 0) and (PLine(At(I))^.PL^ = '') do
Dec(I);
if I < 0 then
GetLastNodeInGroup := nil
else
GetLastNodeInGroup := PLine(At(I));
exit;
end;
end;
Q := P;
Inc(I);
end;
if GroupSeen then
GetLastNodeInGroup := Q
else
GetLastNodeInGroup := nil;
end;
function TIni.GetProfilePrim(Title, Group : String) : String;
{-Primitive to return the string at Title in Group}
var
P : PLine;
S : String;
B : Byte absolute S;
begin
P := GetIniNode(Title, Group);
if P = nil then
GetProfilePrim := ''
else begin
S := P^.PL^;
S := Copy(S, Pos('=', S)+1, 255);
S := Trim(S);
if (S[1] = '"') and (S[b] = '"') then begin
Move(S[2], S[1], B-1);
Dec(B, 2);
end;
GetProfilePrim := S;
end;
end;
function TIni.KillProfileItem(Title, Group : String) : Boolean;
{-Removes Title item in Group from the list}
var
P : PLine;
begin
KillProfileItem := False;
if IsSparse then Exit;
P := GetIniNode(Title, Group);
if P <> nil then begin
Free(P);
KillProfileItem := True;
if AlwaysUpd then
FlushFile
else
NeedUpd := True;
end;
end;
function TIni.KillProfileGroup(Group : String) : Boolean;
{-Removes all items in Group from the list}
var
P : PLine;
I : Integer;
S : String;
begin
KillProfileGroup := False;
if IsSparse then Exit;
{fixup string as needed}
if Group[1] <> '[' then
Group := '['+Group+']';
Group := StUpcase(Group);
{search}
I := 0;
while I < Count do begin
P := PLine(At(I));
if (P^.PL^[1] = '[') and (StUpcase(P^.PL^) = Group) then begin
Inc(I);
while (I < Count) and (PLine(At(I))^.PL^[1] <> '[') do
Free(At(I));
Free(P);
KillProfileGroup := True;
if AlwaysUpd then
FlushFile
else
NeedUpd := True;
Exit;
end;
Inc(I);
end;
end;
function TIni.GetProfileString(Title, Group, Default : String) : String;
{-Returns Title item in Group, or Default if not found}
var
S : String;
begin
S := GetProfilePrim(Title, Group);
if S = '' then
S := Default;
GetProfileString := S;
end;
function TIni.GetEncryptedProfileString(Title, Group, Default : String) : String;
{-Returns decrypted Title item in Group, or Default if not found}
var
S : String;
begin
S := GetProfilePrim(Title, Group);
if S = '' then
S := Default
else
S := DeCrypt(S);
GetEncryptedProfileString := S;
end;
function TIni.GetProfileBool(Title, Group : String; Default : Boolean) : Boolean;
var
S : String;
begin
S := Trim(GetProfilePrim(Title, Group));
if S <> '' then begin
S := StUpcase(S);
if (S = 'TRUE') or (S = '1') or (S = 'Y') or (S = 'YES') or (S = 'ON') then
GetProfileBool := True
else if (S = 'FALSE') or (S = '0') or (S = 'N') or (S = 'NO') or (S = 'OFF') then
GetProfileBool := False
else
GetProfileBool := Default;
end
else
GetProfileBool := Default;
end;
function TIni.GetProfileByte(Title, Group : String; Default : Byte) : Byte;
var
S : String;
C : Integer;
B : Byte;
begin
S := Trim(GetProfilePrim(Title, Group));
if S <> '' then begin
CleanHexStr(S);
Val(S, B, C);
if C = 0 then
GetProfileByte := B
else
GetProfileByte := Default;
end
else
GetProfileByte := Default;
end;
function TIni.GetProfileInt(Title, Group : String; Default : Integer) : Integer;
var
S : String;
I,C : Integer;
begin
S := Trim(GetProfilePrim(Title, Group));
if S <> '' then begin
CleanHexStr(S);
Val(S, I, C);
if C = 0 then
GetProfileInt := I
else
GetProfileInt := Default;
end
else
GetProfileInt := Default;
end;
function TIni.GetProfileWord(Title, Group : String; Default : Word) : Word;
var
S : String;
W : Word;
C : Integer;
begin
S := Trim(GetProfilePrim(Title, Group));
if S <> '' then begin
CleanHexStr(S);
Val(S, W, C);
if C = 0 then
GetProfileWord := W
else
GetProfileWord := Default;
end
else
GetProfileWord := Default;
end;
function TIni.GetProfileLong(Title, Group : String; Default : LongInt) : LongInt;
var
S : String;
I : LongInt;
C : Integer;
begin
S := Trim(GetProfilePrim(Title, Group));
if S <> '' then begin
CleanHexStr(S);
Val(S, I, C);
if C = 0 then
GetProfileLong := I
else
GetProfileLong := Default;
end
else
GetProfileLong := Default;
end;
function TIni.SetProfileString(Title, Group, NewVal : String) : Boolean;
var
S : String;
P : PLine;
begin
SetProfileString := False;
if IsSparse then exit;
P := GetIniNode(Title, Group);
if P = nil then
SetProfileString := AddProfileString(Title, Group, NewVal)
else begin
S := P^.PL^;
System.Delete(S, Pos('=', S)+1, 255);
S := S + NewVal;
P^.Update(S);
SetProfileString := True;
if AlwaysUpd then
FlushFile
else
NeedUpd := True;
end;
end;
function TIni.SetEncryptedProfileString(Title, Group, NewVal : String) : Boolean;
var
S : String;
P : PLine;
begin
SetEncryptedProfileString := False;
if IsSparse then exit;
P := GetIniNode(Title, Group);
if P = nil then
SetEncryptedProfileString := AddEncryptedProfileString(Title, Group, NewVal)
else begin
S := P^.PL^;
System.Delete(S, Pos('=', S)+1, 255);
S := S + EnCrypt(NewVal);
P^.Update(S);
SetEncryptedProfileString := True;
if AlwaysUpd then
FlushFile
else
NeedUpd := True;
end;
end;
function TIni.AddProfileString(Title, Group, NewVal : String) : Boolean;
{-add new node and/or group to the list}
var
P : PLine;
I : Integer;
begin
AddProfileString := False;
if IsSparse then exit;
{fixup strings as needed}
if Group[1] <> '[' then
Group := '['+Group+']';
P := GetLastNodeInGroup(Group);
if P = nil then begin
{group not found, create a new one}
{add a blank line for spacing}
New(P, Init(''));
if P = nil then Exit;
Insert(P);
New(P, Init(Group));
if P = nil then Exit;
Insert(P);
I := Count;
end
else
I := IndexOf(P)+1;
{add our new element after}
if Title = '' then
AddProfileString := True
else begin
New(P, Init(Title+'='+NewVal));
if P <> nil then begin
AtInsert(I, P);
AddProfileString := True;
if AlwaysUpd then
FlushFile
else
NeedUpd := True;
end;
end;
end;
function TIni.AddEncryptedProfileString(Title, Group, NewVal : String) : Boolean;
{-add new encrypted node and/or group to the list}
var
P,Q : PLine;
I : Integer;
begin
AddEncryptedProfileString := False;
if IsSparse then exit;
{fixup strings as needed}
if Group[1] <> '[' then
Group := '['+Group+']';
P := GetLastNodeInGroup(Group);
if P = nil then begin
{group not found, create a new one}
{add a blank line for spacing}
New(P, Init(''));
if P = nil then Exit;
Insert(P);
New(P, Init(Group));
if P = nil then Exit;
Insert(P);
I := Count;
end
else
I := IndexOf(P)+1;
{add our new element after}
if Title = '' then
AddEncryptedProfileString := True
else begin
New(P, Init(Title+'='+Encrypt(NewVal)));
if P <> nil then begin
AtInsert(I, P);
AddEncryptedProfileString := True;
if AlwaysUpd then
FlushFile
else
NeedUpd := True;
end;
end;
end;
function TIni.EnumGroups(P : PStringCollection; Clr : Boolean) : Boolean;
{-Return P loaded with the names of all groups in the file. Returns
false only on error. Uses AtInsert rather than Insert so collection
items are in file order rather than sorted order.}
var
Q : PLine;
R : PString;
I : Integer;
begin
EnumGroups := False;
if Clr then
P^.FreeAll;
I := 0;
while I < Count do begin
Q := PLine(At(I));
if Q^.PL^[1] = '[' then begin
R := NewStr(StripBrackets(Q^.PL^));
P^.AtInsert(P^.Count, R);
end;
Inc(I);
end;
EnumGroups := True;
end;
function TIni.EnumGroupItems(P : PStringCollection; Group : String; Clr : Boolean) : Boolean;
{-Return P loaded with all items in group [Group]. Returns false
if Group not found or error. Uses AtInsert rather than Insert so
collection items are in file order rather than sorted order.}
var
Q : PLine;
R : PString;
S : String;
I : Integer;
begin
EnumGroupItems := False;
if Clr then
P^.FreeAll;
{fixup strings as needed}
if Group[1] <> '[' then
Group := '['+Group+']';
Group := StUpcase(Group);
I := 0;
while I < Count do begin
Q := PLine(At(I));
if StUpcase(Q^.PL^) = Group then begin
Inc(I);
while (I < Count) and (PLine(At(I))^.PL^[1] <> '[') do begin
S := Trim(PLine(At(I))^.PL^);
if (S <> '') and (S[1] <> ';') then begin
if Pos('=', S) > 0 then
S[0] := Char(Pos('=', S)-1);
S := Trim(S);
R := NewStr(S);
P^.AtInsert(P^.Count, R);
end;
Inc(I);
end;
EnumGroupItems := True;
Exit;
end;
Inc(I);
end;
end;
end.
[Back to OOP SWAG index] [Back to Main SWAG index] [Original]