//////////////////////////////////////////////////////////// // Класс для работы с *.ini // // v 1.4 // // // // Copyright (C) 2007 Sagrer // // Распространяется на условиях LGPL 2.1 // // LCL-вариант. // // см. файл lgpl.txt // // // // sagrer@yandex.ru // //////////////////////////////////////////////////////////// //К работе над данным файлом приложили руки, ноги.... короче аффтары: // 1) Sagrer (sagrer@yandex.ru) //////////////////////////////////////////////////////////////////////// unit LCLAnIniFile; {$mode objfpc}{$H+} interface uses LCLTextFileInString, SysUtils, ExtraFunctionsLcl, Classes; type RIniValueListElement = record //Структура для значений ini записанных в виде строк в нестандартных секциях. Name : string; Value : string; end; AIniValueListElements = array of RIniValueListElement; //Тип одномерного массива - соответствует строке IniValueList AIniValueList = array of AIniValueListElements; //Тип двумерного динамического массива для хранения инфы из IniValueList - это содержимое нестандартной секции в которой элементы разделены точкой с запятой, а имена от значений двоеточием, в строке по нескольку элементов. TAnIniFile = class (TObject) //Самопальный класс для работы со стандартными и не очень (вроде *.iss) ini-файлами. Public //Переменные TextFil1 : TTextFileInString; //Конструкторы-деструкторы... Constructor Create; virtual; Destructor Destroy; override; //Другие методы... Function Load(const FileName : string) : boolean; //Загрузить файл в объект класса Function Save(const FileName : string) : boolean; //Сохранить инфу из объекта класса в файл Function SectionExists(const SectName : string) : boolean; //Существует ли секция Function ElementExists(const SectName, ElemName : string) : boolean; //Существует ли элемент Function ReadString(const SectName, ElemName : string) : string; //Прочитать строковое значение Function ReadInteger(const SectName, ElemName : string) : integer; //Прочитать целочисленное значение Function ReadFloat(const SectName, ElemName : string) : Extended; //Прочитать дробное значение Function ReadBool(const SectName, ElemName : string) : boolean; //Прочитать логическое значение Procedure WriteString(const SectName, ElemName, Str : string); //Записать строковое значение Procedure WriteInteger(const SectName, ElemName : string; Int : integer); //Записать целочисленное значение Procedure WriteFloat(const SectName, ElemName : string; Flt : Extended); //Записать дробное значение Procedure WriteBool(const SectName, ElemName : string; Bool : boolean); //Записать логическое значение Procedure MakNewFile(); virtual; //Создать "новый файл" Procedure CreateSection(const SectName : string); //Создать пустую секцию. Procedure RemoveSection(const SectName : string); virtual; //Полностью удалить секцию. Function ReadRawSection(const SectName : string) : string; //Прочитать все содержимое секции в одну строку. Procedure WriteRawSection(const SectName, SectContent : string); virtual; //Перезаписать всё содержимое секции из строки. Procedure StrToIniValueList(const InputStr : string; var IniValueList : AIniValueList); //Пропарсить строку и закинуть инфу в динамический массив. Function IniValueListToStr(var IniValueList : AIniValueList) : string; //Сгенерить строку из инфы динамического массива. Procedure ReadIniValueList(const SectName : string; var IniValueList : AIniValueList); //Прочитать IniValueList из секции. Procedure WriteIniValueList(const SectName : string; var IniValueList : AIniValueList); //Перезаписать инфу секции из IniValueList. Function CompareIniValueElements(var IniValueList1, IniValueList2 : AIniValueList; const List1Index, List2Index : Integer; const CaseSensitive : boolean; const ExceptValueNames : AnsiString) : Boolean; //Функция сравнивает 2 строки 2 списков значений, если они идентичны (с учетом условий каш-сенситив и исключенных элементов) - возвращает true, иначе false. Если первое из Except - "all" то все последующие - наоборот те что проверяются, остальные игнорируются. Function CompareIniValueLists(var IniValueList1, IniValueList2 : AIniValueList; const CaseSensitive : boolean; const ExceptValueNames : AnsiString) : Boolean; //Функция сравнивает 2 списка значений, если списки идентичны (с учетом условий каш-сенситив и исключенных элементов) - возвращает true, иначе false. Если первое из Except - "all" то все последующие - наоборот те что проверяются, остальные игнорируются. Function GetIniValueListValueIndex(var IniValueList : AIniValueList; const ValIndex : Integer; const ValueName : AnsiString) : Integer; //Вернуть индекс значения с указанным именем в указанной строке списка (элементе). Function GetIniValueListValue(var IniValueList : AIniValueList; const ValIndex : Integer; const ValueName : AnsiString) : AnsiString; //Функция возвращает содержимое значения с указанным именем в указанной строке списка. Function ChangeIniValueListValue(var IniValueList : AIniValueList; const ValIndex : Integer; const ValueName, ValueValue : AnsiString) : Boolean; //Функция записывает новое содержимое в значение с указанным именем в указанной строке списка. Function IniValueListValueExists(var IniValueList : AIniValueList; const ValIndex : Integer; const ValueName : AnsiString) : Boolean; //Если значение существует - возвращает true, иначе false. Function GetIniValueListIndexByValue(var IniValueList : AIniValueList; const ValueName, ValueValue : AnsiString) : Integer; //Возвращает индекс первого встреченного элемента (сверху) с указанным именем значения и собсно значением. Если ничего такого нема - вернет -1. Function CopyIniValueListElement(var From, Dest : AIniValueList; const FromIndex, DestIndex : Integer) : Boolean; //Скопировать содержимое одного элемента одного списка в другой элемент другого списка (в теории возможно и в рамкеах одного списка если оба указателя оставить на один и тот же список.). Function CopyIniValueList(var From, Dest : AIniValueList) : Boolean; //Скопировать содержимое одного списка в другой. Function AddIniValueList(var From, Dest : AIniValueList) : Boolean; //Скопировать содержимое одного списка в другой, добавив новые данные в конец целевого списка и сохранив все уже там присутствовавшее. Function RemoveIniValueListValue(var IniValueList : AIniValueList; const ValIndex : Integer; const ValueName : AnsiString) : Boolean; //Удалить значение с указанным именем в указанном элементе в IniValueList. Если значение такое не существовало - вернет false, иначе true. Function RemoveIniValueListElement(var IniValueList : AIniValueList; const ValIndex : Integer) : Boolean; //Удалить указанный элемент IniValueList. Если элемент не существовал - вернет false, в остальных случаях true. Private //Закрытые методы... Function GoToSection(const SectName : string; TypePos : byte) : boolean; //Перебросить курсор на ту или иную часть секции Function GoToElement(const SectName, ElemName : string) : boolean; //Перейти к определенному элементу секции Function NotComment(const Str : string) : boolean; //Проверялка - является ли строка комментом Function GetElemName(const Str : string) : string; //Получить имя элемента Function GetElemStr(const Str : string) : string; //Получить строковое значение элемента end; implementation ///////////////////////////////////////////// // TAnIniFile // ///////////////////////////////////////////// //-----------------------------------------// // Конструкторы-деструкторы... // //-----------------------------------------// Constructor TAnIniFile.Create; begin //Конструктор. Во избежание глюков. TextFil1 := TTextFileInString.Create; end; Destructor TAnIniFile.Destroy; begin //Типа деструктор. TextFil1.Free; inherited; end; //------------------------------------------// // Другие методы... // //------------------------------------------// Function TAnIniFile.Load(const FileName : string) : boolean; begin //Типа загружальник инишника в файло-строку. Result := TextFil1.Load(FileName); end; Function TAnIniFile.Save(const FileName : string) : boolean; begin //Типа савальник файло-строки в файло. TextFil1.Save(FileName); Result := true; end; Function TAnIniFile.SectionExists(const SectName : string) : boolean; var TempCur : Longint; begin //Проверяльщик, есть ли нужная секция. Курсор типа не изменится :) TempCur := TextFil1.Cursor; Result := GoToSection(SectName,1); TextFil1.Cursor := TempCur; end; Function TAnIniFile.ElementExists(const SectName, ElemName : string) : boolean; var TempCur : Longint; begin //Проверяльщик, есть ли нужный элемент. Курсор типа не изменится. TempCur := TextFil1.Cursor; Result := GoToElement(SectName,ElemName); TextFil1.Cursor := TempCur; end; Function TAnIniFile.ReadString(const SectName, ElemName : string) : string; begin //Вернуть значение нужного элемента в виде строки. Result := ''; //Типа по умолчанию пусто :) //Выйти на нужный элемент. If GoToElement(SectName,ElemName) = true then begin //Теперь - прочитать что там есть (ессно если элемент нашелся). Result := GetElemStr(TextFil1.ReadStrLn(TextFil1.Cursor)); end; end; Function TAnIniFile.ReadInteger(const SectName, ElemName : string) : integer; var TempStr : string; begin //Вернуть значение нужного элемента в виде интегера. TempStr := ReadString(SectName,ElemName); //Проверить, интегеровое число ли ето. If CheckStrInt(TempStr) = true then begin //Если какое-то число, то вернуть его... Result := StrToInt(TempStr); end else begin //Если там какая-то фигня, то вернуть ноль Result := 0; end; end; Function TAnIniFile.ReadFloat(const SectName, ElemName : string) : Extended; var TempStr : string; begin //Вернуть значение нужного элемента в виде флоата. TempStr := ReadString(SectName,ElemName); //Проверить, флоатовое число ли ето. If CheckStrFloat(TempStr) = true then begin //Если какое-то число, то вернуть его... Result :=StrToFloat(TempStr); end else begin //Если там какая-то фигня, то вернуть ноль Result := 0; end; end; Function TAnIniFile.ReadBool(const SectName, ElemName : string) : boolean; //Вернуть значение нужного элемента в виде буля. begin Result := StrToBool(ReadString(SectName,ElemName)); end; Procedure TAnIniFile.WriteString(const SectName, ElemName, Str : string); begin //Если элемент есть - переписать его заново, если элемента нет, //то создать его в конце секции. Если нет и секции, то создать //секцию, после чего в конец секции дописать элемент. //Писать элемент ессно в виде строки. Это базовая функа для //остальных писалок. Остальные (типа для интегеров и прочего) //будут работать через нее, просто преобразовывая типы... //Итак, в начале - проверить, существует ли элемент. If GoToElement(SectName,ElemName) = true then begin //Типа элемент существует. Просто переписать его заново... TextFil1.ReplaceString(TextFil1.Cursor,ElemName+'='+Str); end else begin //Типа элемента такого нет. Тогда проверить, есть ли секция под этот элемент... If GoToSection(SectName,3) = true then begin //В принципе, ниче делать не надо, курсор стоит в конце секции щас... end else begin //Если, типа и секции такой нету :). //То выставить курсор в самый низ файла //и создать эту секцию... If TextFil1.Siz <> 0 then begin //Типа, если вообще файл не пуст. TextFil1.Cursor := TextFil1.Siz+1; //курсор в конец файлы... If TextFil1.FileString[TextFil1.Cursor-1] = #10 then begin //Если конец файла - пустая новая строка //то сразу вставить имя секции //и еще 1 пустую новую строку, на которую и поставить курсор после всего. //Если же до этой пустой новой строки нету еще одной пустой строки //то вставить еще #13#10 чтоб был промежуток между секциями. If TextFil1.Siz > 3 then begin If TextFil1.FileString[TextFil1.Cursor-3] <> #10 then begin TextFil1.AddStr(''); end; end; TextFil1.AddStr('['+SectName+']'); TextFil1.Cursor := TextFil1.Siz+1; //курсор собсно в конец файла. end else begin //Конец файла - не пустая строка (чета есть) //тады - просто тоже самое, что выше, но немного по другому TextFil1.AddStr(#13+#10+#13+#10+'['+SectName+']'); //добавить еще и символ конца строки //в конец старой строки, собсно и создав новую строку TextFil1.Cursor := TextFil1.Siz+1; //курсор собсно в конец файла. end; end else begin //Если же файл пустой, то просто вставить сааамую первую секцию :)) TextFil1.AddStr('['+SectName+']'); TextFil1.Cursor := TextFil1.Siz+1; //курсор собсно в конец файла. end; end; //В общем так, выше есть несколько веток алгоритма, но в результате должно получится //одно и тоже - курсор стоит на пустой последней строке секции. //Теперь надо просто дописать туды нонвый элемент (т.к. эта ветка для того //случая, что элемента еще нету нифига... TextFil1.InsertString(TextFil1.Cursor,ElemName+'='+Str+#13+#10); end; end; Procedure TAnIniFile.WriteInteger(const SectName, ElemName : string; Int : integer); begin //Юзя запись в строку, записать туда строкой интегер. WriteString(SectName,ElemName,IntToStr(Int)); end; Procedure TAnIniFile.WriteFloat(const SectName, ElemName : string; Flt : Extended); begin //Юзя запись в строку, записать туда строкой флоат. WriteString(SectName,ElemName,FloatToStr(Flt)); end; Procedure TAnIniFile.WriteBool(const SectName, ElemName : string; Bool : boolean); var TempInt : Integer; begin //Записать WriteString(SectName,ElemName,BoolToStr(Bool)); end; Procedure TAnIniFile.MakNewFile(); begin //"Создавалка нового файла" ;) //на самом деле - нулит TextFil1 TextFil1.MakNewFile; end; Procedure TAnIniFile.CreateSection(const SectName : string); //Создать пустую секцию. begin if SectionExists(SectName) = false then begin //Если такой секции не существует - создаем. //Надо выставить курсор в самый конец файла. If TextFil1.Siz <> 0 then begin //Типа, если вообще файл не пуст. TextFil1.Cursor := TextFil1.Siz+1; //курсор в конец файлы... If TextFil1.FileString[TextFil1.Cursor-1] = #10 then begin //Если конец файла - пустая новая строка //то сразу вставить имя секции //и еще 1 пустую новую строку, на которую и поставить курсор после всего. //Если же до этой пустой новой строки нету еще одной пустой строки //то вставить еще #13#10 чтоб был промежуток между секциями. If TextFil1.Siz > 3 then begin If TextFil1.FileString[TextFil1.Cursor-3] <> #10 then begin TextFil1.AddStr(''); end; end; TextFil1.AddStr('['+SectName+']'); TextFil1.Cursor := TextFil1.Siz+1; //курсор собсно в конец файла. end else begin //Конец файла - не пустая строка (чета есть) //тады - просто тоже самое, что выше, но немного по другому TextFil1.AddStr(#13+#10+#13+#10+'['+SectName+']'); //добавить еще и символ конца строки //в конец старой строки, собсно и создав новую строку TextFil1.Cursor := TextFil1.Siz+1; //курсор собсно в конец файла. end; end else begin //Если же файл пустой, то просто вставить сааамую первую секцию :)) TextFil1.AddStr('['+SectName+']'); TextFil1.Cursor := TextFil1.Siz+1; //курсор собсно в конец файла. end; end; end; Procedure TAnIniFile.RemoveSection(const SectName : string); //Полностью удалить секцию. var TempCursor1 : LongInt; begin //Если только эта секция вообще существует.... if SectionExists(SectName) = true then begin //Ставим курсор на начало секции... GoToSection(SectName,1); TempCursor1 := TextFil1.Cursor; //Ставим курсор на конец секции... GoToSection(SectName,3); //Теперь удаляем нужное количество символов из строки... Delete(TextFil1.FileString, TempCursor1, TextFil1.Cursor-TempCursor1); end; //И типо полюбому курсор ставим на первый символ. TextFil1.Cursor := 1; end; Function TAnIniFile.ReadRawSection(const SectName : string) : string; //Прочитать все содержимое секции в одну строку. var TempCursor1, TempCursor2 : LongInt; begin //По умолчанию возвращается пустая строка... Result := ''; //Если только эта секция вообще существует.... if SectionExists(SectName) = true then begin //Запоминаем исходную позицию курсора... TempCursor2 := TextFil1.Cursor; //Ставим курсор на начало секции... GoToSection(SectName,2); TempCursor1 := TextFil1.Cursor; //Ставим курсор на конец секции... GoToSection(SectName,3); //Теперь копируем инфу в результирующую строку... //если только есть что копировать... if (TextFil1.Cursor-TempCursor1) > 0 then begin Result := Copy(TextFil1.FileString, TempCursor1, TextFil1.Cursor-TempCursor1); end; end; end; Procedure TAnIniFile.WriteRawSection(const SectName, SectContent : string); //Перезаписать всё содержимое секции из строки. begin //Для начала удаляем всю секцию... RemoveSection(SectName); //Теперь создаем секцию заново... CreateSection(SectName); //И дописываем инфу в конец секции... курсор после CreateSection уже стоит на пустой строке в конце секции... TextFil1.FileString := TextFil1.FileString+SectContent; end; Procedure TAnIniFile.StrToIniValueList(const InputStr : string; var IniValueList : AIniValueList); //Пропарсить строку и закинуть инфу в динамический массив. var Strings, Elements, I, J, RealStrings, StrsAdded, ElementsAdded : Integer; SearchPChar : PChar; TrimmedStr, CurrStr, ElementStr, ValueStr : string; Done : boolean; begin //Инициализация Done := false; //Для начала - обтримать пробелы и переносы строк... TrimmedStr := TrimEx(InputStr,' '+#13+#10); //Проверяем есть ли что-то в строке... if Length(TrimmedStr) = 0 then begin //Строка пуста. Просто переводим массив в нуль и заканчиваем работу. SetLength(IniValueList,0,0); Done := true; end; //Едем дальше. if Done = false then begin //Подсчитать количество строк. Strings := 1; //Как минимум 1 строка там уже есть. I := 0; //Начинаем поиск с 1-го символа. repeat I := I+1; if TrimmedStr[I] = #10 then begin Strings := Strings+1; end; until I = Length(TrimmedStr); //Посчитали. Выделяем память в динамическом массиве (1-й уровень)... SetLength(IniValueList,Strings); //Терь построчно читаем инфу... //Начальные значения переменных... RealStrings := Strings; StrsAdded := 0; for I := 1 to Strings do begin //Выпарсиваем кусок от строки.... CurrStr := {TrimRight(}Parse(TrimmedStr,#13+#10){)}; //Подчищаем триманутую строку на всякий случай... TrimExLeft(TrimmedStr,' '+#13+#10); //Строчка есть. Теперь смотрим, не коммент ли она... if NotComment(CurrStr) = true then begin //не коммент - читаем. //Подсчитать количество элементов по точкам с запятой. Elements := 1; //Как минимум 1 штука уже должна быть. J := 0; //Поиск с 1-го символа. repeat J := J+1; if CurrStr[J] = ';' then begin //Найден разделитель элемента. Elements := Elements+1; end; until J = Length(CurrStr); //Выделить место в массиве... SetLength(IniValueList[StrsAdded],Elements); //Пропарсить элементы... ElementsAdded := -1; //Чтобы начать с 0-го элемента. repeat ElementsAdded := ElementsAdded+1; ElementStr := Trim(Parse(CurrStr,';')); //Прочитать очередной элемент... IniValueList[StrsAdded,ElementsAdded].Name := TrimRight(Parse(ElementStr,':')); //Имя элемента IniValueList[StrsAdded,ElementsAdded].Value := TrimLeft(ElementStr); //Значение элемента until Length(CurrStr) = 0; //Прибавить счетчик прочитанных строк. StrsAdded := StrsAdded+1; end else begin //Коммент - значит эта строка пуста, надо уменьшить размер массива... RealStrings := RealStrings-1; SetLength(IniValueList,RealStrings); end; end; end; end; Function TAnIniFile.IniValueListToStr(var IniValueList : AIniValueList) : string; //Сгенерить строку из инфы динамического массива. var I, J : Integer; begin //Элементарно пробежаться по массиву и вернуть строку... Result := ''; //Изначально пустая if Length(IniValueList) > 0 then begin for I := 0 to Length(IniValueList)-1 do begin //Добавить элементы for J := 0 to Length(IniValueList[I])-1 do begin Result := Result+IniValueList[I,J].Name+': '+IniValueList[I,J].Value; if J <> Length(IniValueList[I])-1 then begin //Если элемент не последний - добавить разделитель элементов Result := Result+'; '; end; end; //Если строка не последняя - добавить разделитель строк. if I <> Length(IniValueList)-1 then begin Result := Result+#13+#10; end; end; end; //Собсно - это все %). end; Procedure TAnIniFile.ReadIniValueList(const SectName : string; var IniValueList : AIniValueList); //Прочитать IniValueList из секции. begin StrToIniValueList(ReadRawSection(SectName),IniValueList); end; Procedure TAnIniFile.WriteIniValueList(const SectName : string; var IniValueList : AIniValueList); //Перезаписать инфу секции из IniValueList. begin WriteRawSection(SectName,IniValueListToStr(IniValueList)); end; Function TAnIniFile.CompareIniValueElements(var IniValueList1, IniValueList2 : AIniValueList; const List1Index, List2Index : Integer; const CaseSensitive : boolean; const ExceptValueNames : AnsiString) : Boolean; //Функция сравнивает 2 строки 2 списков значений, если они идентичны (с учетом условий каш-сенситив //и исключенных элементов) - возвращает true, иначе false. Если первое из Except - "all" то все //последующие - наоборот те что проверяются, остальные игнорируются. var I, J, I2, J2, ElI : Integer; ExceptsList : TStringList; ExceptAll, IdentElemFinded, CanCheck : Boolean; begin //Инициализация. Result := true; //По умолчанию считаем что значения идентичны. ExceptAll := false; //По умолчанию это вырублено. ExceptsList := TStringList.Create; //Заполняем список ексцептов... ExtractStrings([','],[' '],PChar(ExceptValueNames),ExceptsList); //Проверяем механьызьм екцзепта.... if ExceptsList.Count > 0 then begin if LowerCase(ExceptsList.Strings[0]) = 'all' then begin //Ага, есть такое дело. ExceptAll := true; //Удаляем элемент "all". ExceptsList.Delete(0); end; end; //Проверяем 1-й список... for I := 0 to Length(IniValueList1[List1Index])-1 do begin //Проверяем, проходит ли по екцептам. if ExceptAll = false then begin //Исключение по принципу "разрешено все, кроме..." CanCheck := true; for ElI := 0 to ExceptsList.Count-1 do begin if LowerCase(IniValueList1[List1Index,I].Name) = LowerCase(ExceptsList.Strings[ElI]) then begin //Нашли что элемент исключается. CanCheck := false; Break; end; end; end else begin //Исключение по принципу "запрещено все, кроме..." CanCheck := false; for ElI := 0 to ExceptsList.Count-1 do begin if LowerCase(IniValueList1[List1Index,I].Name) = LowerCase(ExceptsList.Strings[ElI]) then begin //Нашли что элемент _не_ исключается. CanCheck := true; Break; end; end; end; //Ок, если значение не попало под исключения - проверяем его... Надо обязательно найти аналогичное значение во втором списке, иначе считаем что списки не идентичны. if CanCheck = true then begin Result := false; //Задача цикла внизу - вернуть true. Иначе все вылетит с результатом false. for J := 0 to Length(IniValueList2[List2Index])-1 do begin //Сканим %). if LowerCase(IniValueList1[List1Index,I].Name) = LowerCase(IniValueList2[List2Index,J].Name) then begin //Ага, нашли значение с таким же именем. Теперь проверяем собсно значение. //Причем в зависимости от кейс-сенситива. if CaseSensitive = true then begin //Регистр учитывается. if IniValueList1[List1Index,I].Value = IniValueList2[List2Index,J].Value then begin //Ага, идентичны, true. Result := true; //И брякаем - вылетит в прямо на уровень внутри if CanCheck = true then begin Break; end; end else begin //Регистр не учитывается. if LowerCase(IniValueList1[List1Index,I].Value) = LowerCase(IniValueList2[List2Index,J].Value) then begin //Ага, идентичны, true. Result := true; //И брякаем - вылетит в прямо на уровень внутри if CanCheck = true then begin Break; end; end; end; end; //Просканили, смотрим - true оно или false. Если false то имеем бряк, что по факту приведет к завершению функции //и возвращению ей false. if Result = false then begin Break; end; end; end; //Ок, проверили 1-й список. Если Result все еще true - то проверяем 2-й, аналогично. if Result = true then begin for I := 0 to Length(IniValueList2[List2Index])-1 do begin //Проверяем, проходит ли по екцептам. if ExceptAll = false then begin //Исключение по принципу "разрешено все, кроме..." CanCheck := true; for ElI := 0 to ExceptsList.Count-1 do begin if LowerCase(IniValueList2[List2Index,I].Name) = LowerCase(ExceptsList.Strings[ElI]) then begin //Нашли что элемент исключается. CanCheck := false; Break; end; end; end else begin //Исключение по принципу "запрещено все, кроме..." CanCheck := false; for ElI := 0 to ExceptsList.Count-1 do begin if LowerCase(IniValueList2[List2Index,I].Name) = LowerCase(ExceptsList.Strings[ElI]) then begin //Нашли что элемент _не_ исключается. CanCheck := true; Break; end; end; end; //Ок, если значение не попало под исключения - проверяем его... Надо обязательно найти аналогичное значение во втором списке, иначе считаем что списки не идентичны. if CanCheck = true then begin Result := false; //Задача цикла внизу - вернуть true. Иначе все вылетит с результатом false. for J := 0 to Length(IniValueList1[List1Index])-1 do begin //Сканим %). if LowerCase(IniValueList2[List2Index,I].Name) = LowerCase(IniValueList1[List1Index,J].Name) then begin //Ага, нашли значение с таким же именем. Теперь проверяем собсно значение. //Причем в зависимости от кейс-сенситива. if CaseSensitive = true then begin //Регистр учитывается. if IniValueList2[List2Index,I].Value = IniValueList1[List1Index,J].Value then begin //Ага, идентичны, true. Result := true; //И брякаем - вылетит в прямо на уровень внутри if CanCheck = true then begin Break; end; end else begin //Регистр не учитывается. if LowerCase(IniValueList2[List2Index,I].Value) = LowerCase(IniValueList1[List1Index,J].Value) then begin //Ага, идентичны, true. Result := true; //И брякаем - вылетит в прямо на уровень внутри if CanCheck = true then begin Break; end; end; end; end; //Просканили, смотрим - true оно или false. Если false то имеем бряк, что по факту приведет к завершению функции //и возвращению ей false. if Result = false then begin Break; end; end; end; end; //Чистим мусор. ExceptsList.Free; end; Function TAnIniFile.CompareIniValueLists(var IniValueList1, IniValueList2 : AIniValueList; const CaseSensitive : boolean; const ExceptValueNames : AnsiString) : Boolean; //Функция сравнивает 2 списка значений, если списки идентичны (с учетом условий каш-сенситив и исключенных //элементов) - возвращает true, иначе false. Если первое из Except - "all" то все последующие - наоборот те //что проверяются, остальные игнорируются. var I, J : Integer; IdentElemFinded : Boolean; begin //Инициализация. Result := true; //По умолчанию считаем что списки идентичны... //Сверяем размер списков. if Length(IniValueList1) <> Length(IniValueList2) then begin Result := false; end; if (Result = true) and (Length(IniValueList1) > 0) then begin //Если в списках вообще что-то есть то сверяем их содержимое. for I := 0 to Length(IniValueList1)-1 do begin //Ищем во втором списке элемент, идентичный данному... IdentElemFinded := false; for J := 0 to Length(IniValueList2)-1 do begin IdentElemFinded := false; //Так надо на начало каждой интерации цикла. if CompareIniValueElements(IniValueList1, IniValueList2, I, J, CaseSensitive, ExceptValueNames) = true then begin //Нашли идентичный элемент. Оставляем отметку и брякаем цикл. IdentElemFinded := true; Break; end; end; //Смотрим - если идентичный элемент не найден - то Отмечаем false в результате, и опять же брякаем циклу... if IdentElemFinded = false then begin Result := false; Break; end; end; end; end; Function TAnIniFile.GetIniValueListValueIndex(var IniValueList : AIniValueList; const ValIndex : Integer; const ValueName : AnsiString) : Integer; //Вернуть индекс значения с указанным именем в указанной строке списка (элементе). var I : Integer; TempValName : AnsiString; begin //Инициализация. TempValName := LowerCase(ValueName); Result := -1; //Ищем значение и возвращаем результат. for I := 0 to Length(IniValueList[ValIndex])-1 do begin if LowerCase(IniValueList[ValIndex,I].Name) = TempValName then begin Result := I; Break; end; end; end; Function TAnIniFile.GetIniValueListValue(var IniValueList : AIniValueList; const ValIndex : Integer; const ValueName : AnsiString) : AnsiString; //Функция возвращает содержимое значения с указанным именем в указанной строке списка. var I : Integer; begin //Инициализация. Result := ''; //Ищем значение и возвращаем результат. I := Self.GetIniValueListValueIndex(IniValueList,ValIndex,ValueName); if I >= 0 then begin Result := IniValueList[ValIndex,I].Value; end; end; Function TAnIniFile.ChangeIniValueListValue(var IniValueList : AIniValueList; const ValIndex : Integer; const ValueName, ValueValue : AnsiString) : Boolean; //Функция записывает новое содержимое в значение с указанным именем в указанной строке списка. var I : Integer; TempValName : AnsiString; begin //Инициализация. TempValName := LowerCase(ValueName); Result := false; //Ищем значение. for I := 0 to Length(IniValueList)-1 do begin if LowerCase(IniValueList[ValIndex,I].Name) = TempValName then begin //Нашли, изменяем содержимое. IniValueList[ValIndex,I].Value := ValueValue; Result := true; Break; end; end; end; Function TAnIniFile.IniValueListValueExists(var IniValueList : AIniValueList; const ValIndex : Integer; const ValueName : AnsiString) : Boolean; //Если значение существует - возвращает true, иначе false. var I : Integer; TempValName : AnsiString; begin //Инициализация. TempValName := LowerCase(ValueName); Result := false; //Ищем значение. for I := 0 to Length(IniValueList)-1 do begin if LowerCase(IniValueList[ValIndex,I].Name) = TempValName then begin //Нашли, значит оно есть. Сообщаем об этом. Result := true; Break; end; end; end; Function TAnIniFile.GetIniValueListIndexByValue(var IniValueList : AIniValueList; const ValueName, ValueValue : AnsiString) : Integer; //Возвращает индекс первого встреченного элемента (сверху) с указанным именем значения и собсно значением. //Если ничего такого нема - вернет -1. var I, ValueNum : Integer; TempValueStr : AnsiString; begin //Инициализация. Result := -1; //Получить сверяемое значение в нижнем регистре. TempValueStr := LowerCase(ValueValue); //Пробежимся по списку в поисках подходящего элемента. for I := 0 to Length(IniValueList)-1 do begin //Смотрим - есть ли тут значение с нужным именем? ValueNum := Self.GetIniValueListValueIndex(IniValueList,I,ValueName); if ValueNum >= 0 then begin //Ага, есть тут такая фиговина. Смотрим - а то ли там собсно значение? if LowerCase(IniValueList[I,ValueNum].Value) = TempValueStr then begin //Вахъ, и в самом деле оно. Поиск завершен, возвращаем результат. Result := I; Break; end; end; end; end; Function TAnIniFile.CopyIniValueListElement(var From, Dest : AIniValueList; const FromIndex, DestIndex : Integer) : Boolean; //Скопировать содержимое одного элемента одного списка в другой элемент другого списка (в теории возможно //и в рамкеах одного списка если оба указателя оставить на один и тот же список.). var I : Integer; begin //Инициализация. Result := false; //Выставляем размер элемента... SetLength(Dest[DestIndex],Length(From[FromIndex])); //Собсно копируем... for I := 0 to Length(From[FromIndex])-1 do begin Dest[DestIndex,I].Name := From[FromIndex,I].Name; Dest[DestIndex,I].Value := From[FromIndex,I].Value; end; //И вернуть результат. Result := true; end; Function TAnIniFile.CopyIniValueList(var From, Dest : AIniValueList) : Boolean; //Скопировать содержимое одного списка в другой. var I : Integer; begin //Инициализация. Result := true; //Чистим целевой массив... SetLength(Dest, 0, 0); //Собсно поехали. Выставить размер массива первого уровня... SetLength(Dest, Length(From)); //Поехали по этому массиву... for I := 0 to Length(From)-1 do begin //Копируем собсно элемент. if Self.CopyIniValueListElement(From, Dest, I, I) = false then begin //Если при этом была какая-то ошибка - вываливаемся из цикла, возвращая false. Result := false; Break; end; end; end; Function TAnIniFile.AddIniValueList(var From, Dest : AIniValueList) : Boolean; //Скопировать содержимое одного списка в другой, добавив новые данные в конец //целевого списка и сохранив все уже там присутствовавшее. var I, OldDestLength : Integer; begin //Инициализация. Result := true; //Меняем размер целевого массива... OldDestLength := Length(Dest); SetLength(Dest, Length(Dest)+Length(From)); //Поехали по этому массиву... for I := 0 to Length(From)-1 do begin //Копируем собсно элемент. if Self.CopyIniValueListElement(From, Dest, I, OldDestLength+I) = false then begin //Если при этом была какая-то ошибка - вываливаемся из цикла, возвращая false. Result := false; Break; end; end; end; Function TAnIniFile.RemoveIniValueListValue(var IniValueList : AIniValueList; const ValIndex : Integer; const ValueName : AnsiString) : Boolean; //Удалить значение с указанным именем в указанном элементе в IniValueList. //Если значение такое не существовало - вернет false, иначе true. var I, ValuesNum : Integer; DeletingValue, DummyValue : RIniValueListElement; begin //Инициализация. Result := false; ValuesNum := Length(IniValueList[ValIndex]); //Существует ли нужное значение? И заодно номер его получить. I := Self.GetIniValueListValueIndex(IniValueList,ValIndex,ValueName); if I >= 0 then begin //Итак, значение для удаления найдено. //Если это значение не последнее - то двигаем значения в массиве чтобы таки стало последним. if I <> ValuesNum-1 then begin Move(IniValueList[ValIndex,I],DeletingValue,SizeOf(RIniValueListElement)); //Временно копируем инфу удаляемого значения. Move(IniValueList[ValIndex,I+1],IniValueList[ValIndex,I],SizeOf(RIniValueListElement)*(ValuesNum-(I+1))); //Переносим инфу других значений. Move(DeletingValue,IniValueList[ValIndex,ValuesNum-1],SizeOf(RIniValueListElement)); //Копируем удаляемое значение в конец массива чтоб его прочистило при смене размера массива (во избежание утечков памяти). Move(DummyValue,DeletingValue,SizeOf(RIniValueListElement)); //Копируем во временное значение инфу значения заглушки - чтобы тупой сборщег мусора не полез по ссылкам и не заховал последнее значение массива раньше или позже (т.е. еще раз на пустое место) правильного времени. end; //Почистить удаляемое значение. IniValueList[ValIndex,ValuesNum-1].Name := ''; IniValueList[ValIndex,ValuesNum-1].Value := ''; //И почистить массив. ValuesNum := ValuesNum-1; SetLength(IniValueList[ValIndex],ValuesNum); //Done. Result := true; end; end; Function TAnIniFile.RemoveIniValueListElement(var IniValueList : AIniValueList; const ValIndex : Integer) : Boolean; //Удалить указанный элемент IniValueList. Если элемент не существовал - вернет false, в остальных случаях true. var I, ValuesNum, ElementsNum : Integer; DeletingElement, DummyElement : AIniValueListElements; begin //Инициализация. Result := false; DeletingElement := nil; //Чтобы убедиться что в памяти под этот массив ничего не выделено. SetLength(DummyElement,0); if Length(IniValueList) >= ValIndex then begin //т.е. если в массиве есть элемент с таким индексом. //Для начала - очистить содержимое элемента. ValuesNum := Length(IniValueList[ValIndex]); for I := 0 to ValuesNum-1 do begin IniValueList[ValIndex,I].Name := ''; IniValueList[ValIndex,I].Value := ''; end; SetLength(IniValueList[ValIndex],0); //Теперь надо удалить собсно сам элемент. ElementsNum := Length(IniValueList); //Если это значение не последнее - то двигаем значения в массиве чтобы таки стало последним. if ValIndex <> ElementsNum-1 then begin Move(IniValueList[ValIndex],DeletingElement,SizeOf(AIniValueListElements)); //Временно копируем инфу удаляемого элемента. Move(IniValueList[ValIndex+1],IniValueList[ValIndex],SizeOf(AIniValueListElements)*(ElementsNum-(ValIndex+1))); //Переносим инфу других элементов. Move(DeletingElement,IniValueList[ElementsNum-1],SizeOf(AIniValueListElements)); //Копируем удаляемый элемент в конец массива чтоб его прочистило при смене размера массива (во избежание утечков памяти). Move(DummyElement,DeletingElement,SizeOf(AIniValueListElements)); //Копируем во временный элемент инфу значение заглушки - чтобы тупой сборщег мусора не полез по ссылкам и не заховал последний элемент массива раньше или позже (т.е. еще раз на пустое место) правильного времени. end; //И почистить массив. ElementsNum := ElementsNum-1; SetLength(IniValueList,ElementsNum); end; end; //------------------------------------------// // Закрытые методы... // //------------------------------------------// Function TAnIniFile.GoToSection(const SectName : string; TypePos : byte) : boolean; var buf, TestStr : string; Finded, TempBool1 : boolean; SectLength, I, SectStartCur, TempCur : integer; begin //В целом - САМАЯ ГЛАВНАЯ ФУНКА ТУТ типа :) //Функа для выхода на нужную секцию, на нужную позицию //Если удастся - вернет тру, если нет - фальс. //Если надо будет выйти в конец секции, а ей не дали //последнего абзаца - создаст. result := false; Finded := false; SectLength := Length(SectName)+2; //В начале - добратся до нужной секции. //НО - только, если в файл-строке вообще чтото есть :) If TextFil1.Siz > 0 then begin TextFil1.Cursor := 1; repeat //Прочитать строку (курсор поставить на конец считанной строки при этом). SectStartCur := TextFil1.Cursor; //Запомнить позицию курсора на начало строки buf := TextFil1.CurReadStr; //Теперь посмотреть что там прочиталось... If (Length(buf) > 0) and (NotComment(buf) = true) then begin //Проверка - на случай пустой строки If buf[1] = '[' then begin //типа начало какой-то секции. //Проверить длину (не меньше секции, которую ищем) If Length(buf) >= SectLength then begin //Получить строку из буфа типа TestStr := ''; For I := 1 to SectLength do begin TestStr := TestStr+buf[I]; end; //Теперь - сравнить секцию поиска и тест-строку. If UpperCase('['+SectName+']') = UpperCase(TestStr) then begin //Типа все прально. Секция эта. Finded := true; end; end; end; end; If (TextFil1.Cursor <> Length(TextFil1.FileString)+1) and ((Length(TextFil1.FileString)+1) - (TextFil1.Cursor) >=2) then begin //Если курсор еще не в конце, и есть еще 2 байта, то... TextFil1.Cursor := TextFil1.Cursor+2; end; until (Finded = true) or (TextFil1.Eof); //Теперь собсно - выйти на нужную позицию курсором, и //вернуть булёвый результат функи (ессно, только если секция была //найдена). If Finded = true then begin // В зависимости от TypePos // 1 - на начало названия секции // 2 - на первый элемент секции // 3 - в конец секции (обычно для добавки чегото там) If TypePos = 1 then begin //Выйти на заголовок секции TextFil1.Cursor := SectStartCur; Result := true; end; If TypePos = 2 then begin //Выйти на начало секции TextFil1.Cursor := SectStartCur; TextFil1.CurReadLn; //Автоматом выйдет на первую строку. //и если еще не конец файла, и "первая строка" - не конец секции //то оки типа. if TextFil1.Eof = false then begin If TextFil1.FileString[TextFil1.Cursor] <> '[' then begin Result := true; end else begin //А если это уже другая секция, то добавить строчку куды надо :) TextFil1.Cursor := SectStartCur; TextFil1.CurReadStr; //Выйти на конец строки-заголовка секции TextFil1.InsertString(TextFil1.Cursor,#13#10); TextFil1.Cursor := TextFil1.Cursor+2; end; end; end; If TypePos = 3 then begin //Выйти на начало секции TextFil1.Cursor := SectStartCur; TempCur := TextFil1.Cursor; //TextFil1.CurReadLn; //Выйти на начало секции типа. //Просканировать до начала следующей секции или конца файла. repeat TempBool1 := false; //TempCur := SectStartCur; //Начальный временный курсор - первый символ //старой секции If TextFil1.Eof <> true then begin TempCur := TextFil1.Cursor; //Запомнить позицию "предыдущей" строки TextFil1.CurReadLn; //Выйти на след. строку. buf := TextFil1.ReadStrLn(TextFil1.Cursor); //прочитать 1 раз типа... If Length(buf) > 0 then begin //Проверка - на случай пустой строки. //т.к. без проверки длины буфа, проверять что в нем есть //нельзя. (это в антиле будет проверятся темпбул потом). If buf[1] = '[' then begin TempBool1 := true; //типа нашли секцию //TempCur := end; end; end; until (TempBool1 = true) or (TextFil1.Eof = true); //Теперь, если курсор стоит на "еофе" If TextFil1.Eof = true then begin //Проверить, лежит ли курсор на начале новой строки. if TextFil1.FileString[TextFil1.Cursor-1] = #10 then begin //Да, все прально. result := true; end else begin //Значит стоит в конце другой строки. //Добавить еще 1 строку. TextFil1.FileString := TextFil1.FileString+#13+#10; TextFil1.Cursor := TextFil1.Cursor+2; result := true; end; end else begin //Типа была найдена секция еще одна. TextFil1.Cursor := TempCur; //выйти на начало строки, предыдущей для строки новой секции buf := TextFil1.CurReadStr; //Выйти на конец строки + прочитать ее. //Теперь проверить - пуста ли считанная строка. //Если окажется, что нет, значит надо добавить еще 1 строку сюда. //Если нет - то типа строка уже готова. If Length(buf) > 0 then begin //надо "вставить" еще 1 строку в конец этой. TextFil1.InsertString(TextFil1.Cursor,#13+#10); TextFil1.Cursor := TextFil1.Cursor+2; //Поправить курсор. result := true; end else begin //В принципе, курсор уже там где надо. Типа делать ниче не надо :) result := true; //только резальт правильного типа вернуть... end; end; end; end; end else begin //Типа, если файл-строка пуста. //то типа фальса по любому возвращается, делать тут ниче не надо. end; end; Function TAnIniFile.GoToElement(const SectName, ElemName : string) : boolean; var Finished : boolean; TempCur, TempPos : Longint; Buf : string; begin //Выйти на начало нужного элемента. //Выставить переменные... Finished := false; Result := false; TempCur := TextFil1.Cursor; //Запомнить на случай отката курсора, если //элемент не будет найден. //Сначала выйти на начало секции If GoToSection(SectName,2) = true then begin //Если удачно, то начать искать элемент. repeat If TextFil1.Eof = false then begin TempPos := TextFil1.Cursor; //Запомнить, где былО начало строки. buf := TextFil1.CurReadLn; if length(buf) > 0 then begin //Если типа строка не пуста. If buf[1] <> '[' then begin //Если типа это не след. секция еще :) If GetElemName(Buf) = ElemName then begin //Типа все прально, типа нашли :) TextFil1.Cursor := TempPos; //Вернуть курсор в начало этой строки Result := true; Finished := true; //Типа нашли и закончили. end; end else begin //Не блин, это уже другая секция. Типа финиш. Finished := true; end; end; end else begin //Типа конец файла, нифига не найдено. Finished := true; end; until Finished = true; end; If Result = false then begin //Если нифига не найдено то откат. TextFil1.Cursor := TempCur; end; end; Function TAnIniFile.NotComment(const Str : string) : boolean; //Проверялка - является ли строка комментом begin //Принцип действия прост. Ищем маркеры коммента в начале строки, если есть - false, если нет - true. Result := true; //По умолчанию строка - не коммент. if Length(Str) = 0 then begin Result := false; //Если строка пуста - считаем что коммент. end else if Length(Str) > 0 then begin //Проверяем односимвольные маркеры... if Str[1] = ';' then begin Result := false; end else if Str[1] = '#' then begin Result := false; end else if Length(Str) > 1 then begin //Двухсимвольные маркеры... if Str[1]+Str[2] = '//' then begin Result := false; end; end; end; end; Function TAnIniFile.GetElemName(const Str : string) : string; var TempStr : string; begin //Функа-получалка имени элемента (того, что до знака равенства). //При этом с проверкой, элемент ли это, или комментарий, //т.к. имя элемента не может быть пустым, и возврат //пустой строки будет означать, что это вообще не элемент. Result := ''; //По умолчанию возвращается пустая строка. If NotComment(Str) = true then begin //Типа если это не комментарий. //то парснуть строку - получить имя типа :) TempStr := Str; Result := Parse(TempStr,'='); end; end; Function TAnIniFile.GetElemStr(const Str : string) : string; var TempStr : string; begin //Функа-получалка содержимого элемента (в виде текста). //Из строки выпарсывается имя вместе со знаком равенства, //и потом эта строка уже возвращается (остается как раз содержимое). TempStr := Str; Parse(TempStr,'='); Result := TempStr; end; end.