{
SettingFiles содержит классы для работы с файлами инициализации (.ini).
Copyright © 2016, 2019, 2022–2023 Малик Разработчик
Это свободная программа: вы можете перераспространять её и/или
изменять её на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
в каком она была опубликована Фондом свободного программного обеспечения;
либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.
Эта программа распространяется в надежде, что она может быть полезна,
но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЁННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
общественной лицензии GNU.
Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
вместе с этой программой. Если это не так, см.
<http://www.gnu.org/licenses/>.
}
unit SettingFiles;
{$MODE DELPHI}
interface
uses
Lang,
IOStreams,
FileIO,
TextFiles;
{%region public }
type
InvalidSectionIdentException = class;
AbstractInitializationFile = class;
NormalInitializationFile = class;
InvalidSectionIdentException = class(Exception)
public
constructor create(); overload;
constructor create(const message: AnsiString); overload;
end;
AbstractInitializationFile = class(_Object)
public
function readSections(): AnsiString_Array1d; virtual; abstract;
function readSection(const section: AnsiString): AnsiString_Array1d; virtual; abstract;
function readString(const section, ident, default: AnsiString): AnsiString; virtual; abstract;
procedure writeString(const section, ident, value: AnsiString); virtual; abstract;
procedure eraseSection(const section: AnsiString); virtual; abstract;
procedure deleteIdent(const section, ident: AnsiString); virtual; abstract;
procedure loadFromStream(stream: Input); virtual; abstract;
procedure saveToStream(stream: Output); virtual; abstract;
procedure updateFile(); virtual; abstract;
function isSectionExists(const section: AnsiString): boolean; virtual;
function isIdentExists(const section, ident: AnsiString): boolean; virtual;
function readBoolean(const section, ident: AnsiString; default: boolean): boolean; virtual;
function readInt(const section, ident: AnsiString; default: int): int; virtual;
function readLong(const section, ident: AnsiString; default: long): long; virtual;
function readFloat(const section, ident: AnsiString; default: float): float; virtual;
function readDouble(const section, ident: AnsiString; default: double): double; virtual;
function readReal(const section, ident: AnsiString; default: real): real; virtual;
procedure writeBoolean(const section, ident: AnsiString; value: boolean); virtual;
procedure writeInt(const section, ident: AnsiString; value: int); virtual;
procedure writeLong(const section, ident: AnsiString; value: long); virtual;
procedure writeFloat(const section, ident: AnsiString; value: float); virtual;
procedure writeDouble(const section, ident: AnsiString; value: double); virtual;
procedure writeReal(const section, ident: AnsiString; value: real); virtual;
end;
NormalInitializationFile = class(AbstractInitializationFile)
private
fileName: AnsiString;
sections: int_Array1d;
strings: AnsiString_Array1d;
sectionsCount: int;
stringsCount: int;
function getSectionAt(index: int): AnsiString;
function indexOfSection(const section: AnsiString): int;
function indexOfIdent(const section, ident: AnsiString): int;
procedure insertString(index: int; const str: AnsiString);
procedure deleteString(index: int);
public
constructor create(); overload;
constructor create(const fileName: AnsiString); overload;
function isSectionExists(const section: AnsiString): boolean; override;
function isIdentExists(const section, ident: AnsiString): boolean; override;
function readSections(): AnsiString_Array1d; override;
function readSection(const section: AnsiString): AnsiString_Array1d; override;
function readString(const section, ident, default: AnsiString): AnsiString; override;
procedure writeString(const section, ident, value: AnsiString); override;
procedure eraseSection(const section: AnsiString); override;
procedure deleteIdent(const section, ident: AnsiString); override;
procedure loadFromStream(stream: Input); override;
procedure saveToStream(stream: Output); override;
procedure updateFile(); override;
function getFileName(): AnsiString; virtual;
strict private const
BRACKET_LEFT = '[';
BRACKET_RIGHT = ']';
IDENT_VALUE_SEPARATOR = '=';
LINE_ESCAPE = '\';
COMMENT_START = ';';
end;
{%endregion}
implementation
{%region routine }
function booleanToString(value: boolean): AnsiString;
begin
if value = false then begin
result := '0';
end else begin
result := '1';
end;
end;
{%endregion}
{%region InvalidSectionIdentException }
constructor InvalidSectionIdentException.create();
begin
inherited create();
end;
constructor InvalidSectionIdentException.create(const message: AnsiString);
begin
inherited create(message);
end;
{%endregion}
{%region AbstractInitializationFile }
function AbstractInitializationFile.isSectionExists(const section: AnsiString): boolean;
var
str: AnsiString;
s: AnsiString_Array1d;
i: int;
begin
str := trim(section);
s := readSections();
for i := length(s) - 1 downto 0 do begin
if trim(s[i]) = str then begin
result := true;
exit;
end;
end;
result := false;
end;
function AbstractInitializationFile.isIdentExists(const section, ident: AnsiString): boolean;
var
str: AnsiString;
s: AnsiString_Array1d;
i: int;
begin
str := trim(ident);
s := readSection(trim(section));
for i := length(s) - 1 downto 0 do begin
if trim(s[i]) = str then begin
result := true;
exit;
end;
end;
result := false;
end;
function AbstractInitializationFile.readBoolean(const section, ident: AnsiString; default: boolean): boolean;
var
s: AnsiString;
begin
s := trim(readString(section, ident, booleanToString(default)));
if length(s) <> 1 then begin
result := default;
exit;
end;
case s[1] of
'1':
result := true;
'0':
result := false;
else
result := default;
end;
end;
function AbstractInitializationFile.readInt(const section, ident: AnsiString; default: int): int;
var
s: AnsiString;
begin
s := trim(readString(section, ident, toDecString(default)));
if startsWith('0x', s) then begin
result := parseHexInt(copy(s, 3, length(s) - 2), default);
exit;
end;
if startsWith('$', s) then begin
result := parseHexInt(copy(s, 2, length(s) - 1), default);
exit;
end;
result := parseDecInt(s, default);
end;
function AbstractInitializationFile.readLong(const section, ident: AnsiString; default: long): long;
var
s: AnsiString;
begin
s := trim(readString(section, ident, toDecString(default)));
if startsWith('0x', s) then begin
result := parseHexLong(copy(s, 3, length(s) - 2), default);
exit;
end;
if startsWith('$', s) then begin
result := parseHexLong(copy(s, 2, length(s) - 1), default);
exit;
end;
result := parseDecLong(s, default);
end;
function AbstractInitializationFile.readFloat(const section, ident: AnsiString; default: float): float;
var
r: real;
begin
r := toReal(default);
result := toFloat(parseReal(trim(readString(section, ident, toDecString(r))), r));
end;
function AbstractInitializationFile.readDouble(const section, ident: AnsiString; default: double): double;
var
r: real;
begin
r := toReal(default);
result := toFloat(parseReal(trim(readString(section, ident, toDecString(r))), r));
end;
function AbstractInitializationFile.readReal(const section, ident: AnsiString; default: real): real;
begin
result := toFloat(parseReal(trim(readString(section, ident, toDecString(default))), default));
end;
procedure AbstractInitializationFile.writeBoolean(const section, ident: AnsiString; value: boolean);
begin
writeString(section, ident, booleanToString(value));
end;
procedure AbstractInitializationFile.writeInt(const section, ident: AnsiString; value: int);
begin
writeString(section, ident, toDecString(value));
end;
procedure AbstractInitializationFile.writeLong(const section, ident: AnsiString; value: long);
begin
writeString(section, ident, toDecString(value));
end;
procedure AbstractInitializationFile.writeFloat(const section, ident: AnsiString; value: float);
begin
writeString(section, ident, toDecString(toReal(value)));
end;
procedure AbstractInitializationFile.writeDouble(const section, ident: AnsiString; value: double);
begin
writeString(section, ident, toDecString(toReal(value)));
end;
procedure AbstractInitializationFile.writeReal(const section, ident: AnsiString; value: real);
begin
writeString(section, ident, toDecString(value));
end;
{%endregion}
{%region NormalInitializationFile }
constructor NormalInitializationFile.create();
begin
create('');
end;
constructor NormalInitializationFile.create(const fileName: AnsiString);
var
stream: FileInputStream;
begin
inherited create();
self.fileName := fileName;
if length(fileName) = 0 then begin
exit;
end;
stream := FileInputStream.create(fileName);
if stream.isInvalidHandle() then begin
stream.free();
exit;
end;
loadFromStream(stream);
end;
function NormalInitializationFile.getSectionAt(index: int): AnsiString;
begin
if (index < 0) or (index >= sectionsCount) then begin
result := '';
exit;
end;
index := sections[index];
result := trim(strings[index]);
result := trim(copy(result, 2, length(result) - 2));
end;
function NormalInitializationFile.indexOfSection(const section: AnsiString): int;
var
i: int;
s: AnsiString;
begin
s := trim(section);
for i := 0 to sectionsCount - 1 do begin
if getSectionAt(i) = s then begin
result := i;
exit;
end;
end;
result := -1;
end;
function NormalInitializationFile.indexOfIdent(const section, ident: AnsiString): int;
var
e: int;
i: int;
j: int;
l: int;
s: AnsiString_Array1d;
t: AnsiString;
d: AnsiString;
begin
j := indexOfSection(section);
if j < 0 then begin
result := -1;
exit;
end;
s := strings;
d := trim(ident);
for i := sections[j] + 1 to stringsCount - 1 do begin
t := trim(s[i]);
l := length(t);
if (l <= 0) or (t[1] = COMMENT_START) then begin
continue;
end;
if (t[1] = BRACKET_LEFT) and (t[l] = BRACKET_RIGHT) then begin
break;
end;
e := pos(IDENT_VALUE_SEPARATOR, t);
if e <= 0 then begin
continue;
end;
if trim(copy(t, 1, e - 1)) = d then begin
result := i;
exit;
end;
end;
result := -1;
end;
procedure NormalInitializationFile.insertString(index: int; const str: AnsiString);
var
s: AnsiString_Array1d;
t: int_Array1d;
c: int;
i: int;
j: int;
ts: AnsiString;
begin
s := strings;
c := stringsCount;
if c = length(s) then begin
s := String_Array1d_create((c shl 1) + 1);
arraycopy(strings, 0, s, 0, c);
strings := s;
end;
arraycopy(s, index, s, index + 1, c - index);
s[index] := str;
stringsCount := c + 1;
t := sections;
c := sectionsCount;
for i := c - 1 downto 0 do begin
j := t[i];
if j < index then begin
break;
end;
t[i] := j + 1;
end;
ts := trim(str);
if startsWith(BRACKET_LEFT, ts) and endsWith(BRACKET_RIGHT, ts) then begin
j := c;
for i := 0 to c - 1 do begin
if t[i] > index then begin
j := i;
break;
end;
end;
if c = length(t) then begin
t := int_Array1d_create((c shl 1) + 1);
arraycopy(sections, 0, t, 0, c);
sections := t;
end;
arraycopy(t, j, t, j + 1, c - j);
t[j] := index;
sectionsCount := c + 1;
end;
end;
procedure NormalInitializationFile.deleteString(index: int);
var
s: AnsiString_Array1d;
t: int_Array1d;
c: int;
i: int;
j: int;
begin
s := strings;
c := stringsCount;
arraycopy(s, index + 1, s, index, c - index - 1);
s[c - 1] := '';
stringsCount := c - 1;
t := sections;
c := sectionsCount;
for i := c - 1 downto 0 do begin
j := t[i];
if j = index then begin
arraycopy(t, i + 1, t, i, c - i - 1);
sectionsCount := c - 1;
break;
end;
if j < index then begin
break;
end;
t[i] := j - 1;
end;
end;
function NormalInitializationFile.isSectionExists(const section: AnsiString): boolean;
begin
result := indexOfSection(section) >= 0;
end;
function NormalInitializationFile.isIdentExists(const section, ident: AnsiString): boolean;
begin
result := indexOfIdent(section, ident) >= 0;
end;
function NormalInitializationFile.readSections(): AnsiString_Array1d;
var
i: int;
begin
result := String_Array1d_create(sectionsCount);
for i := sectionsCount - 1 downto 0 do begin
result[i] := getSectionAt(i);
end;
end;
function NormalInitializationFile.readSection(const section: AnsiString): AnsiString_Array1d;
var
e: int;
i: int;
j: int;
l: int;
z: int;
s: AnsiString_Array1d;
x: AnsiString_Array1d;
t: AnsiString;
begin
j := indexOfSection(section);
if j < 0 then begin
result := nil;
exit;
end;
z := 0;
s := strings;
x := String_Array1d_create(8);
for i := sections[j] + 1 to stringsCount - 1 do begin
t := trim(s[i]);
l := length(t);
if (l <= 0) or (t[1] = COMMENT_START) then begin
continue;
end;
if (t[1] = BRACKET_LEFT) and (t[l] = BRACKET_RIGHT) then begin
break;
end;
e := pos(IDENT_VALUE_SEPARATOR, t);
if e <= 0 then begin
continue;
end;
if length(x) = z then begin
result := x;
x := String_Array1d_create((z shl 1) + 1);
arraycopy(result, 0, x, 0, z);
result := nil;
end;
x[z] := trim(copy(t, 1, e - 1));
inc(z);
end;
result := String_Array1d_create(z);
arraycopy(x, 0, result, 0, z);
end;
function NormalInitializationFile.readString(const section, ident, default: AnsiString): AnsiString;
var
i: int;
j: int;
s: AnsiString;
begin
i := indexOfIdent(section, ident);
if i < 0 then begin
result := default;
exit;
end;
s := strings[i];
j := pos(IDENT_VALUE_SEPARATOR, s);
result := trim(copy(s, j + 1, length(s) - j));
end;
procedure NormalInitializationFile.writeString(const section, ident, value: AnsiString);
var
i: int;
begin
if length(section) = 0 then begin
raise InvalidSectionIdentException.create('Название секции не может быть пустой строкой.');
end;
if length(ident) = 0 then begin
raise InvalidSectionIdentException.create('Название параметра не может быть пустой строкой.');
end;
if pos(IDENT_VALUE_SEPARATOR, ident) > 0 then begin
raise InvalidSectionIdentException.create('Название параметра не может содержать символ «' + IDENT_VALUE_SEPARATOR + '».');
end;
i := indexOfIdent(section, ident);
if i < 0 then begin
i := indexOfSection(section);
if i < 0 then begin
insertString(stringsCount, '');
insertString(stringsCount, BRACKET_LEFT + trim(section) + BRACKET_RIGHT);
insertString(stringsCount, trim(ident) + IDENT_VALUE_SEPARATOR + trim(value));
exit;
end;
insertString(sections[i] + 1, trim(ident) + IDENT_VALUE_SEPARATOR + trim(value));
exit;
end;
strings[i] := trim(ident) + IDENT_VALUE_SEPARATOR + trim(value);
end;
procedure NormalInitializationFile.eraseSection(const section: AnsiString);
var
i: int;
j: int;
k: int;
t: int_Array1d;
s: AnsiString_Array1d;
begin
k := indexOfSection(section);
if k < 0 then begin
exit;
end;
t := sections;
i := t[k];
if k < sectionsCount - 1 then begin
j := t[k + 1];
end else begin
j := stringsCount;
end;
s := strings;
k := stringsCount;
arraycopy(s, j, s, i, k - j);
stringsCount := k - (j - i);
for i := stringsCount to k - 1 do begin
s[i] := '';
end;
end;
procedure NormalInitializationFile.deleteIdent(const section, ident: AnsiString);
var
i: int;
begin
i := indexOfIdent(section, ident);
if i >= 0 then begin
deleteString(i);
end;
end;
procedure NormalInitializationFile.loadFromStream(stream: Input);
var
i: int;
j: int;
k: int;
t: int_Array1d;
s: AnsiString_Array1d;
str: AnsiString;
begin
s := loadStringsFromStream(stream);
k := length(s);
for i := k - 1 downto 1 do begin
str := s[i - 1];
if endsWith(LINE_ESCAPE, str) then begin
s[i - 1] := copy(str, 1, length(str) - 1) + trim(s[i]);
arraycopy(s, i + 1, s, i, k - i - 1);
dec(k);
s[k] := '';
end;
end;
j := 0;
for i := 0 to k - 1 do begin
str := trim(s[i]);
if startsWith(BRACKET_LEFT, str) and endsWith(BRACKET_RIGHT, str) then begin
inc(j);
end;
end;
t := int_Array1d_create(j);
j := 0;
for i := 0 to k - 1 do begin
str := trim(s[i]);
if startsWith(BRACKET_LEFT, str) and endsWith(BRACKET_RIGHT, str) then begin
t[j] := i;
inc(j);
end;
end;
sections := t;
strings := s;
sectionsCount := j;
stringsCount := k;
end;
procedure NormalInitializationFile.saveToStream(stream: Output);
begin
saveStringsToStream(stream, strings, stringsCount);
end;
procedure NormalInitializationFile.updateFile();
var
f: FileOutputStream;
fn: AnsiString;
begin
fn := fileName;
if length(fn) = 0 then begin
raise IOException.create('Имя файла не было задано при создании этого экземпляра класса ' + self.getClass().getName());
end;
f := FileOutputStream.create(fn);
if f.isInvalidHandle() then begin
f.free();
raise FileNotOpenException.create('Не удалось записать данные в файл: ' + fn);
end;
saveToStream(f);
end;
function NormalInitializationFile.getFileName(): AnsiString;
begin
result := fileName;
end;
{%endregion}
end.