platform.independent.streamformat.text.plain.ini.pas

Переключить прокрутку окна
Загрузить этот исходный код

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