{$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.