settingfiles.pas

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

{
    SettingFiles содержит классы для работы с файлами инициализации (.ini).

    Copyright © 2016, 2017 Малик Разработчик

    Это свободная программа: вы можете перераспространять её и/или
    изменять её на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
    в каком она была опубликована Фондом свободного программного обеспечения;
    либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.

    Эта программа распространяется в надежде, что она может быть полезна,
    но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
    или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЁННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
    общественной лицензии GNU.

    Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
    вместе с этой программой. Если это не так, см.
    <http://www.gnu.org/licenses/>.
}

unit SettingFiles;

{$MODE DELPHI,EXTENDEDSYNTAX ON}

interface

uses
    Lang, IOStream, FileIO, TextFiles;

{$ASMMODE INTEL,CALLING REGISTER,INLINE ON,GOTO ON}
{$H+,I-,J-,M-,Q-,R-,T-}

type
    InvalidSectionIdentException = class;
    AbstractInitializationFile = class;
    NormalInitializationFile = class;

    InvalidSectionIdentException = class(Exception)
    public
        constructor create(const message: String);
    end;

    AbstractInitializationFile = class(_Object)
    public
        function readSections(): String_Array1d; virtual; abstract;
        function readSection(const section: String): String_Array1d; virtual; abstract;
        function readString(const section, ident, default: String): String; virtual; abstract;
        procedure writeString(const section, ident, value: String); virtual; abstract;
        procedure eraseSection(const section: String); virtual; abstract;
        procedure deleteIdent(const section, ident: String); virtual; abstract;
        procedure loadFromStream(stream: Input); virtual; abstract;
        procedure saveToStream(stream: Output); virtual; abstract;
        procedure updateFile(); virtual; abstract;
        function isSectionExists(const section: String): boolean; virtual;
        function isIdentExists(const section, ident: String): boolean; virtual;
        function readBoolean(const section, ident: String; default: boolean): boolean; virtual;
        function readInt(const section, ident: String; default: int): int; virtual;
        function readLong(const section, ident: String; default: long): long; virtual;
        function readFloat(const section, ident: String; default: float): float; virtual;
        function readDouble(const section, ident: String; default: double): double; virtual;
        function readReal(const section, ident: String; default: real): real; virtual;
        procedure writeBoolean(const section, ident: String; value: boolean); virtual;
        procedure writeInt(const section, ident: String; value: int); virtual;
        procedure writeLong(const section, ident: String; value: long); virtual;
        procedure writeFloat(const section, ident: String; value: float); virtual;
        procedure writeDouble(const section, ident: String; value: double); virtual;
        procedure writeReal(const section, ident: String; value: real); virtual;
    end;

    NormalInitializationFile = class(AbstractInitializationFile)
    private
        fileName: String;
        sections: int_Array1d;
        strings: String_Array1d;
        sectionsCount: int;
        stringsCount: int;
        function getSectionAt(index: int): String;
        function indexOfSection(const section: String): int;
        function indexOfIdent(const section, ident: String): int;
        procedure insertString(index: int; const str: String);
        procedure deleteString(index: int);
    public
        constructor create(); overload;
        constructor create(const fileName: String); overload;
        function isSectionExists(const section: String): boolean; override;
        function isIdentExists(const section, ident: String): boolean; override;
        function readSections(): String_Array1d; override;
        function readSection(const section: String): String_Array1d; override;
        function readString(const section, ident, default: String): String; override;
        procedure writeString(const section, ident, value: String); override;
        procedure eraseSection(const section: String); override;
        procedure deleteIdent(const section, ident: String); override;
        procedure loadFromStream(stream: Input); override;
        procedure saveToStream(stream: Output); override;
        procedure updateFile(); override;
        function getFileName(): String; virtual;

    strict private
        const BRACKET_LEFT = '[';
        const BRACKET_RIGHT = ']';
        const IDENT_VALUE_SEPARATOR = '=';
        const LINE_ESCAPE = '\';
        const COMMENT_START = ';';
    end;

resourcestring
    msgSectionNameCannotBeEmpty = 'Название секции не может быть пустой строкой.';
    msgIdentNameCannotBeEmpty = 'Название параметра не может быть пустой строкой.';
    msgIdentCannotContainSeparator = 'Название параметра не может содержать символ «=».';
    msgFileNameNotSpecified = 'Имя файла не было задано при создании этого экземпляра класса ';
    msgFileCannotBeWritten = 'Не удалось записать данные в файл: ';

implementation

{ unit private members }

function booleanToString(value: boolean): String;
begin
    if value = false then begin
        result := '0';
    end else begin
        result := '1';
    end;
end;

{ InvalidSectionIdentException }

constructor InvalidSectionIdentException.create(const message: String);
begin
    inherited create(message);
end;

{ AbstractInitializationFile }

function AbstractInitializationFile.isSectionExists(const section: String): boolean;
var
    str: String;
    s: String_Array1d;
    i: int;
begin
    str := stringTrim(section);
    s := readSections();
    for i := length(s) - 1 downto 0 do begin
        if stringTrim(s[i]) = str then begin
            result := true;
            exit;
        end;
    end;
    result := false;
end;

function AbstractInitializationFile.isIdentExists(const section, ident: String): boolean;
var
    str: String;
    s: String_Array1d;
    i: int;
begin
    str := stringTrim(ident);
    s := readSection(stringTrim(section));
    for i := length(s) - 1 downto 0 do begin
        if stringTrim(s[i]) = str then begin
            result := true;
            exit;
        end;
    end;
    result := false;
end;

function AbstractInitializationFile.readBoolean(const section, ident: String;
        default: boolean): boolean;
var
    s: String;
begin
    s := stringTrim(readString(section, ident, booleanToString(default)));
    if length(s) <> 1 then begin
        result := default;
        exit;
    end;
    case s[1] of
    '0': result := false;
    '1': result := true;
    else result := default;
    end;
end;

function AbstractInitializationFile.readInt(const section, ident: String;
        default: int): int;
var
    s: String;
begin
    s := stringTrim(readString(section, ident, intToString(default)));
    if stringStartsWith('0x', s) or stringStartsWith('0X', s) then begin
        result := stringParseInt(copy(s, 3, length(s) - 2), 16, default);
        exit;
    end;
    if stringStartsWith('$', s) then begin
        result := stringParseInt(copy(s, 2, length(s) - 1), 16, default);
        exit;
    end;
    result := stringParseInt(s, 10, default);
end;

function AbstractInitializationFile.readLong(const section, ident: String;
        default: long): long;
var
    s: String;
begin
    s := stringTrim(readString(section, ident, longToString(default)));
    if stringStartsWith('0x', s) or stringStartsWith('0X', s) then begin
        result := stringParseLong(copy(s, 3, length(s) - 2), 16, default);
        exit;
    end;
    if stringStartsWith('$', s) then begin
        result := stringParseLong(copy(s, 2, length(s) - 1), 16, default);
        exit;
    end;
    result := stringParseLong(s, 10, default);
end;

function AbstractInitializationFile.readFloat(const section, ident: String;
        default: float): float;
begin
    result := realToFloat(stringParseReal(stringTrim(readString(
            section, ident, realToString(default))), default));
end;

function AbstractInitializationFile.readDouble(const section, ident: String;
        default: double): double;
begin
    result := realToDouble(stringParseReal(stringTrim(readString(
            section, ident, realToString(default))), default));
end;

function AbstractInitializationFile.readReal(const section, ident: String;
        default: real): real;
begin
    result := stringParseReal(stringTrim(readString(
            section, ident, realToString(default))), default);
end;

procedure AbstractInitializationFile.writeBoolean(const section, ident: String; value: boolean);
begin
    writeString(section, ident, booleanToString(value));
end;

procedure AbstractInitializationFile.writeInt(const section, ident: String; value: int);
begin
    writeString(section, ident, intToString(value));
end;

procedure AbstractInitializationFile.writeLong(const section, ident: String; value: long);
begin
    writeString(section, ident, longToString(value));
end;

procedure AbstractInitializationFile.writeFloat(const section, ident: String; value: float);
begin
    writeString(section, ident, realToString(value));
end;

procedure AbstractInitializationFile.writeDouble(const section, ident: String; value: double);
begin
    writeString(section, ident, realToString(value));
end;

procedure AbstractInitializationFile.writeReal(const section, ident: String; value: real);
begin
    writeString(section, ident, realToString(value));
end;

{ NormalInitializationFile }

constructor NormalInitializationFile.create();
begin
    create('');
end;

constructor NormalInitializationFile.create(const fileName: String);
var
    stream: FileInputStream;
begin
    inherited create();
    self.fileName := fileName;
    if length(fileName) = 0 then begin
        exit;
    end;
    stream := FileInputStream.create(stringToUTF16(fileName));
    if stream.hasOpenError() then begin
        stream.free();
        exit;
    end;
    loadFromStream(stream);
end;

function NormalInitializationFile.getSectionAt(index: int): String;
begin
    if (index < 0) or (index >= sectionsCount) then begin
        result := '';
        exit;
    end;
    index := sections[index];
    result := stringTrim(strings[index]);
    result := stringTrim(copy(result, 2, length(result) - 2));
end;

function NormalInitializationFile.indexOfSection(const section: String): int;
var
    i: int;
    s: String;
begin
    s := stringTrim(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: String): int;
var
    e: int;
    i: int;
    j: int;
    l: int;
    s: String_Array1d;
    t: String;
    d: String;
begin
    j := indexOfSection(section);
    if j < 0 then begin
        result := -1;
        exit;
    end;
    s := strings;
    d := stringTrim(ident);
    for i := sections[j] + 1 to stringsCount - 1 do begin
        t := stringTrim(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 stringTrim(copy(t, 1, e - 1)) = d then begin
            result := i;
            exit;
        end;
    end;
    result := -1;
end;

procedure NormalInitializationFile.insertString(index: int; const str: String);
var
    s: String_Array1d;
    t: int_Array1d;
    c: int;
    i: int;
    j: int;
    ts: String;
begin
    s := strings;
    c := stringsCount;
    if c = length(s) then begin
        s := String_Array1d_create((c shl 1) + 1);
        arraycopyStrings(strings, 0, s, 0, c);
        strings := s;
    end;
    arraycopyStrings(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 := stringTrim(str);
    if stringStartsWith(BRACKET_LEFT, ts) and stringEndsWith(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);
            arraycopyPrimitives(sections, 0, t, 0, c);
            sections := t;
        end;
        arraycopyPrimitives(t, j, t, j + 1, c - j);
        t[j] := index;
        sectionsCount := c + 1;
    end;
end;

procedure NormalInitializationFile.deleteString(index: int);
var
    s: String_Array1d;
    t: int_Array1d;
    c: int;
    i: int;
    j: int;
begin
    s := strings;
    c := stringsCount;
    arraycopyStrings(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
            arraycopyPrimitives(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: String): boolean;
begin
    result := indexOfSection(section) >= 0;
end;

function NormalInitializationFile.isIdentExists(const section, ident: String): boolean;
begin
    result := indexOfIdent(section, ident) >= 0;
end;

function NormalInitializationFile.readSections(): String_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: String): String_Array1d;
var
    e: int;
    i: int;
    j: int;
    l: int;
    z: int;
    s: String_Array1d;
    x: String_Array1d;
    t: String;
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 := stringTrim(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);
            arraycopyStrings(result, 0, x, 0, z);
            result := nil;
        end;
        x[z] := stringTrim(copy(t, 1, e - 1));
        inc(z);
    end;
    result := String_Array1d_create(z);
    arraycopyStrings(x, 0, result, 0, z);
end;

function NormalInitializationFile.readString(const section, ident, default: String): String;
var
    i: int;
    j: int;
    s: String;
begin
    i := indexOfIdent(section, ident);
    if i < 0 then begin
        result := default;
        exit;
    end;
    s := strings[i];
    j := pos(IDENT_VALUE_SEPARATOR, s);
    result := stringTrim(copy(s, j + 1, length(s) - j));
end;

procedure NormalInitializationFile.writeString(const section, ident, value: String);
var
    i: int;
begin
    if length(section) = 0 then begin
        raise InvalidSectionIdentException.create(msgSectionNameCannotBeEmpty);
    end;
    if length(ident) = 0 then begin
        raise InvalidSectionIdentException.create(msgIdentNameCannotBeEmpty);
    end;
    if pos(IDENT_VALUE_SEPARATOR, ident) > 0 then begin
        raise InvalidSectionIdentException.create(msgIdentCannotContainSeparator);
    end;
    i := indexOfIdent(section, ident);
    if i < 0 then begin
        i := indexOfSection(section);
        if i < 0 then begin
            insertString(stringsCount, '');
            insertString(stringsCount, BRACKET_LEFT + stringTrim(section) + BRACKET_RIGHT);
            insertString(stringsCount, stringTrim(ident) +
                    IDENT_VALUE_SEPARATOR + stringTrim(value));
            exit;
        end;
        insertString(sections[i] + 1, stringTrim(ident) +
                IDENT_VALUE_SEPARATOR + stringTrim(value));
        exit;
    end;
    strings[i] := stringTrim(ident) + IDENT_VALUE_SEPARATOR + stringTrim(value);
end;

procedure NormalInitializationFile.eraseSection(const section: String);
var
    i: int;
    j: int;
    k: int;
    t: int_Array1d;
    s: String_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;
    arraycopyStrings(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: String);
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: String_Array1d;
    str: String;
begin
    s := loadStringsFromStream(stream);
    k := length(s);
    for i := k - 1 downto 1 do begin
        str := s[i - 1];
        if stringEndsWith(LINE_ESCAPE, str) then begin
            s[i - 1] := copy(str, 1, length(str) - 1) + stringTrim(s[i]);
            arraycopyStrings(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 := stringTrim(s[i]);
        if stringStartsWith(BRACKET_LEFT, str) and stringEndsWith(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 := stringTrim(s[i]);
        if stringStartsWith(BRACKET_LEFT, str) and stringEndsWith(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: String;
begin
    fn := fileName;
    if length(fn) = 0 then begin
        raise IOException.create(msgFileNameNotSpecified + self.getClass().getCanonicalName());
    end;
    f := FileOutputStream.create(stringToUTF16(fn), false);
    if f.hasOpenError() then begin
        f.free();
        raise IOException.create(msgFileCannotBeWritten + fn);
    end;
    saveToStream(f);
end;

function NormalInitializationFile.getFileName(): String;
begin
    result := fileName;
end;

end.