{
platform.independent.streamformat.text.plain.ini — модуль для чтения и записи
формата конфигурации INI (text/plain, ini).
Copyright © 2021, 2026 Малик Разработчик
Это свободная программа: вы можете перераспространять её и/или изменять
её на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
в каком она была опубликована Фондом свободного программного обеспечения;
либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.
Эта программа распространяется в надежде, что она будет полезной,
но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЁННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
общественной лицензии GNU.
Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
вместе с этой программой. Если это не так, см.
<https://www.gnu.org/licenses/>.
}
unit platform.independent.streamformat.text.plain.ini;
{$MODE OBJFPC}
interface
{%region} uses
pascalx.lang,
pascalx.lang.table,
pascalx.io,
platform.independent.streamformat,
platform.independent.streamformat.text.plain;
{%endregion}
{$TYPEINFO ON}
{$CALLING REGISTER}
const UNIT_NAME = 'platform.independent.streamformat.text.plain.ini';
{%region} type
IniSection = class;
IniConfig = class;
IniSection = class(&Object, DataHolder, MutableDataHolder, InputAdapter, OutputAdapter, StreamAdapter, DataDecoder, DataEncoder, DataCodec)
protected type
HashtableOfAnsiStringToValue = specialize HashtableOfAnsiString<Value>;
protected
fldContent: HashtableOfAnsiStringToValue;
function getLength(): int; virtual;
public
constructor create();
destructor destroy; override;
procedure loadFromInputStream(stream: ByteReader); virtual;
procedure saveToOutputStream(stream: ByteWriter); virtual;
procedure clear(); virtual;
procedure removeParameter(const name: AnsiString); virtual;
procedure setParameter(const name, value: AnsiString); virtual;
procedure writeBooleanValue(const name: AnsiString; const value: boolean);
procedure writeIntValue(const name: AnsiString; const value: int);
procedure writeLongValue(const name: AnsiString; const value: long);
procedure writeStringValue(const name: AnsiString; const value: AnsiString);
procedure writeBooleanArray(const name: AnsiString; const value: boolean_Array1d);
procedure writeIntArray(const name: AnsiString; const value: int_Array1d);
procedure writeLongArray(const name: AnsiString; const value: long_Array1d);
procedure writeStringArray(const name: AnsiString; const value: AnsiString_Array1d);
function isEmpty(): boolean; virtual;
function isParameterExists(const name: AnsiString): boolean; virtual;
function nameAt(index: int): AnsiString; virtual;
function getParameter(const name: AnsiString): AnsiString; virtual;
function readBooleanValue(const name: AnsiString; const default: boolean): boolean;
function readIntValue(const name: AnsiString; const default: int): int;
function readLongValue(const name: AnsiString; const default: long): long;
function readStringValue(const name: AnsiString; const default: AnsiString): AnsiString;
function readBooleanArray(const name: AnsiString; const default: boolean_Array1d): boolean_Array1d;
function readIntArray(const name: AnsiString; const default: int_Array1d): int_Array1d;
function readLongArray(const name: AnsiString; const default: long_Array1d): long_Array1d;
function readStringArray(const name: AnsiString; const default: AnsiString_Array1d): AnsiString_Array1d;
property length: int read getLength;
end;
IniConfig = class(&Object, DataHolder, MutableDataHolder, InputAdapter, OutputAdapter, StreamAdapter, DataDecoder, DataEncoder, DataCodec)
protected type
HashtableOfAnsiStringToIniSection = specialize HashtableOfAnsiString<IniSection>;
protected
fldContent: HashtableOfAnsiStringToIniSection;
function getLength(): int; virtual;
function createSection(const name: AnsiString): IniSection; virtual;
public
constructor create();
destructor destroy; override;
procedure loadFromInputStream(stream: ByteReader); virtual;
procedure saveToOutputStream(stream: ByteWriter); virtual;
procedure clear(); virtual;
procedure removeSection(const name: AnsiString); virtual;
function isEmpty(): boolean; virtual;
function isSectionExists(const name: AnsiString): boolean; virtual;
function nameAt(index: int): AnsiString; virtual;
function getSection(const name: AnsiString): IniSection; virtual;
function addSection(const name: AnsiString): IniSection; virtual;
property length: int read getLength;
end;
{%endregion}
implementation
{%region IniSection }
function IniSection.getLength(): int;
begin
result := fldContent.length;
end;
constructor IniSection.create();
begin
inherited create();
fldContent := HashtableOfAnsiStringToValue.create();
end;
destructor IniSection.destroy;
begin
fldContent.free();
inherited destroy;
end;
procedure IniSection.loadFromInputStream(stream: ByteReader);
var
pos: int;
line: AnsiString;
name: AnsiString;
value: AnsiString;
begin
clear();
with DataInputStream.create(stream), bigEndianDataInput do try
repeat
line := readln().toUTF8();
if line.length <= 0 then break;
pos := line.indexOf(';');
if pos > 0 then line := line.substring(1, pos);
pos := line.indexOf('=');
if pos < 1 then continue;
value := line.substring(1 + pos).trim();
name := line.substring(1, pos).trim();
setParameter(name, value);
until false;
finally
free();
end;
end;
procedure IniSection.saveToOutputStream(stream: ByteWriter);
var
index: int;
name: AnsiString;
begin
with DataOutputStream.create(stream), bigEndianDataOutput do try
for index := 0 to getLength() - 1 do begin
name := nameAt(index);
writeln((name + ' = ' + getParameter(name)).toUTF16());
end;
finally
free();
end;
end;
procedure IniSection.clear();
begin
fldContent.clear();
end;
procedure IniSection.removeParameter(const name: AnsiString);
begin
fldContent[name] := nil;
end;
procedure IniSection.setParameter(const name, value: AnsiString);
begin
fldContent[name] := CoAnsiString.create(value);
end;
procedure IniSection.writeBooleanValue(const name: AnsiString; const value: boolean);
var
vstr: AnsiString;
begin
if value then begin
vstr := '1';
end else begin
vstr := '0';
end;
setParameter(name, vstr);
end;
procedure IniSection.writeIntValue(const name: AnsiString; const value: int);
begin
setParameter(name, CoInt.toString(value));
end;
procedure IniSection.writeLongValue(const name: AnsiString; const value: long);
begin
setParameter(name, CoLong.toString(value));
end;
procedure IniSection.writeStringValue(const name: AnsiString; const value: AnsiString);
begin
setParameter(name, value);
end;
procedure IniSection.writeBooleanArray(const name: AnsiString; const value: boolean_Array1d);
var
pos: int;
vindex: int;
vlength: int;
vstr: AnsiString;
begin
pos := 1;
vlength := system.length(value);
vstr := AnsiString.create((vlength shl 1) - 1);
for vindex := 0 to vlength - 1 do begin
if vindex > 0 then begin
vstr[pos] := ',';
inc(pos);
end;
if value[vindex] then begin
vstr[pos] := '1';
end else begin
vstr[pos] := '0';
end;
inc(pos);
end;
setParameter(name, vstr);
end;
procedure IniSection.writeIntArray(const name: AnsiString; const value: int_Array1d);
var
index: int;
vstr: AnsiString;
begin
vstr := '';
for index := 0 to system.length(value) - 1 do begin
if index > 0 then vstr := vstr + ',';
vstr := vstr + CoInt.toString(value[index]);
end;
setParameter(name, vstr);
end;
procedure IniSection.writeLongArray(const name: AnsiString; const value: long_Array1d);
var
index: int;
vstr: AnsiString;
begin
vstr := '';
for index := 0 to system.length(value) - 1 do begin
if index > 0 then vstr := vstr + ',';
vstr := vstr + CoLong.toString(value[index]);
end;
setParameter(name, vstr);
end;
procedure IniSection.writeStringArray(const name: AnsiString; const value: AnsiString_Array1d);
var
index: int;
vstr: AnsiString;
begin
vstr := '';
for index := 0 to system.length(value) - 1 do begin
if index > 0 then vstr := vstr + ',';
vstr := vstr + value[index];
end;
setParameter(name, vstr);
end;
function IniSection.isEmpty(): boolean;
begin
result := fldContent.length <= 0;
end;
function IniSection.isParameterExists(const name: AnsiString): boolean;
begin
result := fldContent.contains(name);
end;
function IniSection.nameAt(index: int): AnsiString;
begin
result := fldContent.keyAt(index);
end;
function IniSection.getParameter(const name: AnsiString): AnsiString;
var
value: pascalx.lang.Value;
begin
value := fldContent[name];
if value = nil then begin
result := '';
exit;
end;
result := value.asAnsiString();
end;
function IniSection.readBooleanValue(const name: AnsiString; const default: boolean): boolean;
var
vstr: AnsiString;
begin
vstr := getParameter(name);
if vstr = '1' then begin
result := true;
exit;
end;
if vstr = '0' then begin
result := false;
exit;
end;
result := default;
end;
function IniSection.readIntValue(const name: AnsiString; const default: int): int;
var
vstr: AnsiString;
begin
vstr := getParameter(name);
try
result := CoInt.parse(vstr);
except
result := default;
end;
end;
function IniSection.readLongValue(const name: AnsiString; const default: long): long;
var
vstr: AnsiString;
begin
vstr := getParameter(name);
try
result := CoLong.parse(vstr);
except
result := default;
end;
end;
function IniSection.readStringValue(const name: AnsiString; const default: AnsiString): AnsiString;
begin
if isParameterExists(name) then begin
result := getParameter(name);
exit;
end;
result := default;
end;
function IniSection.readBooleanArray(const name: AnsiString; const default: boolean_Array1d): boolean_Array1d;
var
posa: int;
posb: int;
vindex: int;
vlength: int;
vstr: AnsiString;
cstr: AnsiString;
value: boolean_Array1d;
begin
vstr := getParameter(name);
vlength := 1;
posa := 0;
repeat
posa := vstr.indexOf(',', posa + 1);
if posa < 1 then break;
inc(vlength);
until false;
value := &Array.newBoolean1d(vlength);
posa := 1;
for vindex := 0 to vlength - 1 do begin
posb := vstr.indexOf(',', posa);
if posb < 1 then begin
cstr := vstr.substring(posa);
end else begin
cstr := vstr.substring(posa, posb);
end;
if cstr = '1' then begin
value[vindex] := true;
end else
if cstr = '0' then begin
value[vindex] := false;
end else begin
result := default;
exit;
end;
posa := posb + 1;
end;
result := value;
end;
function IniSection.readIntArray(const name: AnsiString; const default: int_Array1d): int_Array1d;
var
posa: int;
posb: int;
vindex: int;
vlength: int;
vstr: AnsiString;
cstr: AnsiString;
value: int_Array1d;
begin
vstr := getParameter(name);
vlength := 1;
posa := 0;
repeat
posa := vstr.indexOf(',', posa + 1);
if posa < 1 then break;
inc(vlength);
until false;
value := &Array.newInt1d(vlength);
posa := 1;
for vindex := 0 to vlength - 1 do begin
posb := vstr.indexOf(',', posa);
if posb < 1 then begin
cstr := vstr.substring(posa);
end else begin
cstr := vstr.substring(posa, posb);
end;
try
value[vindex] := CoInt.parse(cstr);
except
result := default;
exit;
end;
posa := posb + 1;
end;
result := value;
end;
function IniSection.readLongArray(const name: AnsiString; const default: long_Array1d): long_Array1d;
var
posa: int;
posb: int;
vindex: int;
vlength: int;
vstr: AnsiString;
cstr: AnsiString;
value: long_Array1d;
begin
vstr := getParameter(name);
vlength := 1;
posa := 0;
repeat
posa := vstr.indexOf(',', posa + 1);
if posa < 1 then break;
inc(vlength);
until false;
value := &Array.newLong1d(vlength);
posa := 1;
for vindex := 0 to vlength - 1 do begin
posb := vstr.indexOf(',', posa);
if posb < 1 then begin
cstr := vstr.substring(posa);
end else begin
cstr := vstr.substring(posa, posb);
end;
try
value[vindex] := CoLong.parse(cstr);
except
result := default;
exit;
end;
posa := posb + 1;
end;
result := value;
end;
function IniSection.readStringArray(const name: AnsiString; const default: AnsiString_Array1d): AnsiString_Array1d;
var
posa: int;
posb: int;
vindex: int;
vlength: int;
vstr: AnsiString;
cstr: AnsiString;
value: AnsiString_Array1d;
begin
if not isParameterExists(name) then begin
result := default;
exit;
end;
vstr := getParameter(name);
vlength := 1;
posa := 0;
repeat
posa := vstr.indexOf(',', posa + 1);
if posa < 1 then break;
inc(vlength);
until false;
value := &Array.newAnsiString1d(vlength);
posa := 1;
for vindex := 0 to vlength - 1 do begin
posb := vstr.indexOf(',', posa);
if posb < 1 then begin
cstr := vstr.substring(posa);
end else begin
cstr := vstr.substring(posa, posb);
end;
value[vindex] := cstr;
posa := posb + 1;
end;
result := value;
end;
{%endregion}
{%region IniConfig }
function IniConfig.getLength(): int;
begin
result := fldContent.length;
end;
function IniConfig.createSection(const name: AnsiString): IniSection;
begin
result := nil;
end;
constructor IniConfig.create();
begin
inherited create();
fldContent := HashtableOfAnsiStringToIniSection.create();
end;
destructor IniConfig.destroy;
var
index: int;
name: AnsiString;
content: HashtableOfAnsiStringToIniSection;
begin
content := fldContent;
for index := 0 to content.length - 1 do begin
name := content.keyAt(index);
content[name].free();
end;
content.free();
inherited destroy;
end;
procedure IniConfig.loadFromInputStream(stream: ByteReader);
var
pos: int;
index: int;
line: AnsiString;
name: AnsiString;
value: AnsiString;
lines: AnsiString_Array1d;
section: IniSection;
begin
clear();
with PlainCodec.create() do try
loadFromInputStream(stream);
lines := getText().toUTF8().split();
finally
free();
end;
section := nil;
for index := 0 to system.length(lines) - 1 do begin
line := lines[index];
pos := line.indexOf(';');
if pos > 0 then line := line.substring(1, pos);
pos := line.indexOf('=');
if pos < 1 then begin
line := line.trim();
if line.startsWith('[') and line.endsWith(']') then begin
section := addSection(line.substring(2, line.length).trim());
end;
continue;
end;
if section = nil then section := addSection('');
value := line.substring(1 + pos).trim();
name := line.substring(1, pos).trim();
section.setParameter(name, value);
end;
end;
procedure IniConfig.saveToOutputStream(stream: ByteWriter);
var
sindex: int;
pindex: int;
sname: AnsiString;
pname: AnsiString;
begin
with DataOutputStream.create(stream), bigEndianDataOutput do try
for sindex := 0 to getLength() - 1 do begin
sname := nameAt(sindex);
writeln('');
if sname.length > 0 then writeln(('[' + sname + ']').toUTF16());
with getSection(sname) do for pindex := 0 to getLength() - 1 do begin
pname := nameAt(pindex);
writeln((pname + ' = ' + getParameter(pname)).toUTF16());
end;
end;
finally
free();
end;
end;
procedure IniConfig.clear();
var
index: int;
name: AnsiString;
content: HashtableOfAnsiStringToIniSection;
begin
content := fldContent;
for index := 0 to content.length - 1 do begin
name := content.keyAt(index);
content[name].free();
end;
content.clear();
end;
procedure IniConfig.removeSection(const name: AnsiString);
var
section: IniSection;
content: HashtableOfAnsiStringToIniSection;
begin
content := fldContent;
section := content[name];
if section <> nil then begin
content[name] := nil;
section.free();
end;
end;
function IniConfig.isEmpty(): boolean;
begin
result := fldContent.length <= 0;
end;
function IniConfig.isSectionExists(const name: AnsiString): boolean;
begin
result := fldContent.contains(name);
end;
function IniConfig.nameAt(index: int): AnsiString;
begin
result := fldContent.keyAt(index);
end;
function IniConfig.getSection(const name: AnsiString): IniSection;
begin
result := fldContent[name];
end;
function IniConfig.addSection(const name: AnsiString): IniSection;
var
section: IniSection;
content: HashtableOfAnsiStringToIniSection;
begin
content := fldContent;
section := content[name];
if section = nil then begin
section := createSection(name);
if section = nil then section := IniSection.create();
content[name] := section;
end;
result := section;
end;
{%endregion}
end.