root/branches/0_1_3/LCLAnIniFile.pas

Revision 111, 49.7 kB (checked in by sagrer, 1 year ago)

Пофиксаны кое-где объявления метода MakNewFile? - чтоб везде со скобками был, иначе перегрузка может поглюкивать.

  • Property svnmailer:content-charset set to cp1251
Line 
1 ////////////////////////////////////////////////////////////
2 //              Класс для работы с *.ini                  //
3 //                        v 1.4                           //
4 //                                                        //
5 //              Copyright (C) 2007 Sagrer                 //
6 //          Распространяется на условиях LGPL 2.1         //
7 //                     LCL-вариант.                       //
8 //                  см. файл lgpl.txt                     //
9 //                                                        //
10 //                  sagrer@yandex.ru                      //
11 ////////////////////////////////////////////////////////////
12
13 //К работе над данным файлом приложили руки, ноги.... короче аффтары:
14 // 1) Sagrer (sagrer@yandex.ru)
15
16 ////////////////////////////////////////////////////////////////////////
17
18 unit LCLAnIniFile;
19
20 {$mode objfpc}{$H+}
21
22 interface
23 uses LCLTextFileInString, SysUtils, ExtraFunctionsLcl, Classes;
24
25 type
26   RIniValueListElement = record             //Структура для значений ini записанных в виде строк в нестандартных секциях.
27     Name : string;
28     Value : string;
29   end;
30  
31   AIniValueListElements = array of RIniValueListElement;   //Тип одномерного массива - соответствует строке IniValueList
32
33   AIniValueList = array of AIniValueListElements;     //Тип двумерного динамического массива для хранения инфы из IniValueList - это содержимое нестандартной секции в которой элементы разделены точкой с запятой, а имена от значений двоеточием, в строке по нескольку элементов.
34  
35   TAnIniFile = class (TObject)              //Самопальный класс для работы со стандартными и не очень (вроде *.iss) ini-файлами.
36     Public
37       //Переменные
38       TextFil1 : TTextFileInString;
39      
40       //Конструкторы-деструкторы...
41       Constructor Create; virtual;
42       Destructor Destroy; override;
43      
44       //Другие методы...
45       Function Load(const FileName : string) : boolean;      //Загрузить файл в объект класса
46       Function Save(const FileName : string) : boolean;      //Сохранить инфу из объекта класса в файл
47       Function SectionExists(const SectName : string) : boolean;    //Существует ли секция
48       Function ElementExists(const SectName, ElemName : string) : boolean;    //Существует ли элемент
49       Function ReadString(const SectName, ElemName : string) : string;  //Прочитать строковое значение
50       Function ReadInteger(const SectName, ElemName : string) : integer;    //Прочитать целочисленное значение
51       Function ReadFloat(const SectName, ElemName : string) : Extended;    //Прочитать дробное значение
52       Function ReadBool(const SectName, ElemName : string) : boolean;     //Прочитать логическое значение
53       Procedure WriteString(const SectName, ElemName, Str : string);     //Записать строковое значение
54       Procedure WriteInteger(const SectName, ElemName : string; Int : integer);   //Записать целочисленное значение
55       Procedure WriteFloat(const SectName, ElemName : string; Flt : Extended);   //Записать дробное значение
56       Procedure WriteBool(const SectName, ElemName : string; Bool : boolean);   //Записать логическое значение
57       Procedure MakNewFile(); virtual;       //Создать "новый файл"
58       Procedure CreateSection(const SectName : string);            //Создать пустую секцию.
59       Procedure RemoveSection(const SectName : string); virtual;   //Полностью удалить секцию.
60       Function ReadRawSection(const SectName : string) : string;   //Прочитать все содержимое секции в одну строку.
61       Procedure WriteRawSection(const SectName, SectContent : string);   virtual; //Перезаписать всё содержимое секции из строки.
62       Procedure StrToIniValueList(const InputStr : string; var IniValueList : AIniValueList);  //Пропарсить строку и закинуть инфу в динамический массив.
63       Function IniValueListToStr(var IniValueList : AIniValueList) : string;   //Сгенерить строку из инфы динамического массива.
64       Procedure ReadIniValueList(const SectName : string; var IniValueList : AIniValueList);  //Прочитать IniValueList из секции.
65       Procedure WriteIniValueList(const SectName : string; var IniValueList : AIniValueList);  //Перезаписать инфу секции из IniValueList.
66       Function CompareIniValueElements(var IniValueList1, IniValueList2 : AIniValueList; const List1Index, List2Index : Integer; const CaseSensitive : boolean; const ExceptValueNames : AnsiString) : Boolean;   //Функция сравнивает 2 строки 2 списков значений, если они идентичны (с учетом условий каш-сенситив и исключенных элементов) - возвращает true, иначе false. Если первое из Except - "all" то все последующие - наоборот те что проверяются, остальные игнорируются.
67       Function CompareIniValueLists(var IniValueList1, IniValueList2 : AIniValueList; const CaseSensitive : boolean; const ExceptValueNames : AnsiString) : Boolean;  //Функция сравнивает 2 списка значений, если списки идентичны (с учетом условий каш-сенситив и исключенных элементов) - возвращает true, иначе false. Если первое из Except - "all" то все последующие - наоборот те что проверяются, остальные игнорируются.
68       Function GetIniValueListValueIndex(var IniValueList : AIniValueList; const ValIndex : Integer; const ValueName : AnsiString) : Integer;   //Вернуть индекс значения с указанным именем в указанной строке списка (элементе).
69       Function GetIniValueListValue(var IniValueList : AIniValueList; const ValIndex : Integer; const ValueName : AnsiString) : AnsiString;   //Функция возвращает содержимое значения с указанным именем в указанной строке списка.
70       Function ChangeIniValueListValue(var IniValueList : AIniValueList; const ValIndex : Integer; const ValueName, ValueValue : AnsiString) : Boolean;   //Функция записывает новое содержимое в значение с указанным именем в указанной строке списка.
71       Function IniValueListValueExists(var IniValueList : AIniValueList; const ValIndex : Integer; const ValueName : AnsiString) : Boolean;   //Если значение существует - возвращает true, иначе false.
72       Function GetIniValueListIndexByValue(var IniValueList : AIniValueList; const ValueName, ValueValue : AnsiString) : Integer;     //Возвращает индекс первого встреченного элемента (сверху) с указанным именем значения и собсно значением. Если ничего такого нема - вернет -1.
73       Function CopyIniValueListElement(var From, Dest : AIniValueList; const FromIndex, DestIndex : Integer) : Boolean;  //Скопировать содержимое одного элемента одного списка в другой элемент другого списка (в теории возможно и в рамкеах одного списка если оба указателя оставить на один и тот же список.).
74       Function CopyIniValueList(var From, Dest : AIniValueList) : Boolean;   //Скопировать содержимое одного списка в другой.
75       Function AddIniValueList(var From, Dest : AIniValueList) : Boolean;   //Скопировать содержимое одного списка в другой, добавив новые данные в конец целевого списка и сохранив все уже там присутствовавшее.
76       Function RemoveIniValueListValue(var IniValueList : AIniValueList; const ValIndex : Integer; const ValueName : AnsiString) : Boolean;     //Удалить значение с указанным именем в указанном элементе в IniValueList. Если значение такое не существовало - вернет false, иначе true.
77       Function RemoveIniValueListElement(var IniValueList : AIniValueList; const ValIndex : Integer) : Boolean;        //Удалить указанный элемент IniValueList. Если элемент не существовал - вернет false, в остальных случаях true.
78     Private
79       //Закрытые методы...
80       Function GoToSection(const SectName : string; TypePos : byte) : boolean;    //Перебросить курсор на ту или иную часть секции
81       Function GoToElement(const SectName, ElemName : string) : boolean;    //Перейти к определенному элементу секции
82       Function NotComment(const Str : string) : boolean;     //Проверялка - является ли строка комментом
83       Function GetElemName(const Str : string) : string;    //Получить имя элемента
84       Function GetElemStr(const Str : string) : string;     //Получить строковое значение элемента
85      
86   end;
87
88 implementation
89
90 /////////////////////////////////////////////
91 //              TAnIniFile                 //
92 /////////////////////////////////////////////
93
94 //-----------------------------------------//
95 //        Конструкторы-деструкторы...      //
96 //-----------------------------------------//
97
98 Constructor TAnIniFile.Create;
99 begin
100   //Конструктор. Во избежание глюков.
101   TextFil1 := TTextFileInString.Create;
102 end;
103
104 Destructor TAnIniFile.Destroy;
105 begin
106   //Типа деструктор.
107   TextFil1.Free;
108   inherited;
109 end;
110
111 //------------------------------------------//
112 //             Другие методы...             //
113 //------------------------------------------//
114
115 Function TAnIniFile.Load(const FileName : string) : boolean;
116 begin
117   //Типа загружальник инишника в файло-строку.
118   Result := TextFil1.Load(FileName);
119 end;
120
121 Function TAnIniFile.Save(const FileName : string) : boolean;
122 begin
123   //Типа савальник файло-строки в файло.
124   TextFil1.Save(FileName);
125   Result := true;
126 end;
127
128 Function TAnIniFile.SectionExists(const SectName : string) : boolean;
129 var
130   TempCur : Longint;
131 begin
132   //Проверяльщик, есть ли нужная секция. Курсор типа не изменится :)
133
134   TempCur := TextFil1.Cursor;
135   Result := GoToSection(SectName,1);
136   TextFil1.Cursor := TempCur;
137
138 end;
139
140 Function TAnIniFile.ElementExists(const SectName, ElemName : string) : boolean;
141 var
142   TempCur : Longint;
143 begin
144   //Проверяльщик, есть ли нужный элемент. Курсор типа не изменится.
145
146   TempCur := TextFil1.Cursor;
147   Result := GoToElement(SectName,ElemName);
148   TextFil1.Cursor := TempCur;
149 end;
150
151 Function TAnIniFile.ReadString(const SectName, ElemName : string) : string;
152 begin
153   //Вернуть значение нужного элемента в виде строки.
154   Result := '';   //Типа по умолчанию пусто :)
155
156   //Выйти на нужный элемент.
157   If GoToElement(SectName,ElemName) = true then begin
158     //Теперь - прочитать что там есть (ессно если элемент нашелся).
159     Result := GetElemStr(TextFil1.ReadStrLn(TextFil1.Cursor));
160   end;
161 end;
162
163 Function TAnIniFile.ReadInteger(const SectName, ElemName : string) : integer;
164 var
165   TempStr : string;
166 begin
167   //Вернуть значение нужного элемента в виде интегера.
168   TempStr := ReadString(SectName,ElemName);
169
170   //Проверить, интегеровое число ли ето.
171   If CheckStrInt(TempStr) = true then begin
172     //Если какое-то число, то вернуть его...
173     Result := StrToInt(TempStr);
174   end
175   else begin
176     //Если там какая-то фигня, то вернуть ноль
177     Result := 0;
178   end;
179 end;
180
181 Function TAnIniFile.ReadFloat(const SectName, ElemName : string) : Extended;
182 var
183   TempStr : string;
184 begin
185   //Вернуть значение нужного элемента в виде флоата.
186   TempStr := ReadString(SectName,ElemName);
187
188   //Проверить, флоатовое число ли ето.
189   If CheckStrFloat(TempStr) = true then begin
190     //Если какое-то число, то вернуть его...
191     Result :=StrToFloat(TempStr);
192   end
193   else begin
194     //Если там какая-то фигня, то вернуть ноль
195     Result := 0;
196   end;
197 end;
198
199 Function TAnIniFile.ReadBool(const SectName, ElemName : string) : boolean;
200 //Вернуть значение нужного элемента в виде буля.
201 begin
202   Result := StrToBool(ReadString(SectName,ElemName));
203 end;
204
205 Procedure TAnIniFile.WriteString(const SectName, ElemName, Str : string);
206 begin
207   //Если элемент есть - переписать его заново, если элемента нет,
208   //то создать его в конце секции. Если нет и секции, то создать
209   //секцию, после чего в конец секции дописать элемент.
210   //Писать элемент ессно в виде строки. Это базовая функа для
211   //остальных писалок. Остальные (типа для интегеров и прочего)
212   //будут работать через нее, просто преобразовывая типы...
213
214   //Итак, в начале - проверить, существует ли элемент.
215   If GoToElement(SectName,ElemName) = true then begin
216     //Типа элемент существует. Просто переписать его заново...
217     TextFil1.ReplaceString(TextFil1.Cursor,ElemName+'='+Str);
218   end
219   else begin
220     //Типа элемента такого нет. Тогда проверить, есть ли секция под этот элемент...
221     If GoToSection(SectName,3) = true then begin
222       //В принципе, ниче делать не надо, курсор стоит в конце секции щас...
223     end
224     else begin
225       //Если, типа и секции такой нету :).
226       //То выставить курсор в самый низ файла
227       //и создать эту секцию...
228
229       If TextFil1.Siz <> 0 then begin
230         //Типа, если вообще файл не пуст.
231         TextFil1.Cursor := TextFil1.Siz+1;    //курсор в конец файлы...
232         If TextFil1.FileString[TextFil1.Cursor-1] = #10 then begin
233           //Если конец файла - пустая новая строка
234           //то сразу вставить имя секции
235           //и еще 1 пустую новую строку, на которую и поставить курсор после всего.
236           //Если же до этой пустой новой строки нету еще одной пустой строки
237           //то вставить еще #13#10 чтоб был промежуток между секциями.
238           If TextFil1.Siz > 3 then begin
239             If TextFil1.FileString[TextFil1.Cursor-3] <> #10 then begin
240               TextFil1.AddStr('');
241             end;
242           end;
243           TextFil1.AddStr('['+SectName+']');
244           TextFil1.Cursor := TextFil1.Siz+1;  //курсор собсно в конец файла.
245         end
246         else begin
247           //Конец файла - не пустая строка (чета есть)
248           //тады - просто тоже самое, что выше, но немного по другому
249           TextFil1.AddStr(#13+#10+#13+#10+'['+SectName+']'); //добавить еще и символ конца строки
250                                     //в конец старой строки, собсно и создав новую строку
251           TextFil1.Cursor := TextFil1.Siz+1;  //курсор собсно в конец файла.
252         end;
253       end
254       else begin
255         //Если же файл пустой, то просто вставить сааамую первую секцию :))
256         TextFil1.AddStr('['+SectName+']');
257         TextFil1.Cursor := TextFil1.Siz+1;  //курсор собсно в конец файла.
258       end;
259     end;
260
261     //В общем так, выше есть несколько веток алгоритма, но в результате должно получится
262     //одно и тоже - курсор стоит на пустой последней строке секции.
263     //Теперь надо просто дописать туды нонвый элемент (т.к. эта ветка для того
264     //случая, что элемента еще нету нифига...
265
266     TextFil1.InsertString(TextFil1.Cursor,ElemName+'='+Str+#13+#10);
267
268   end;
269 end;
270
271 Procedure TAnIniFile.WriteInteger(const SectName, ElemName : string; Int : integer);
272 begin
273   //Юзя запись в строку, записать туда строкой интегер.
274   WriteString(SectName,ElemName,IntToStr(Int));
275 end;
276
277 Procedure TAnIniFile.WriteFloat(const SectName, ElemName : string; Flt : Extended);
278 begin
279   //Юзя запись в строку, записать туда строкой флоат.
280   WriteString(SectName,ElemName,FloatToStr(Flt));
281 end;
282
283 Procedure TAnIniFile.WriteBool(const SectName, ElemName : string; Bool : boolean);
284 var
285   TempInt : Integer;
286 begin
287   //Записать
288   WriteString(SectName,ElemName,BoolToStr(Bool));
289 end;
290
291 Procedure TAnIniFile.MakNewFile();
292 begin
293   //"Создавалка нового файла" ;)
294   //на самом деле - нулит TextFil1
295
296   TextFil1.MakNewFile;
297 end;
298
299 Procedure TAnIniFile.CreateSection(const SectName : string);
300 //Создать пустую секцию.
301 begin
302
303   if SectionExists(SectName) = false then begin
304     //Если такой секции не существует - создаем.
305     //Надо выставить курсор в самый конец файла.
306     If TextFil1.Siz <> 0 then begin
307       //Типа, если вообще файл не пуст.
308       TextFil1.Cursor := TextFil1.Siz+1;    //курсор в конец файлы...
309       If TextFil1.FileString[TextFil1.Cursor-1] = #10 then begin
310         //Если конец файла - пустая новая строка
311         //то сразу вставить имя секции
312         //и еще 1 пустую новую строку, на которую и поставить курсор после всего.
313         //Если же до этой пустой новой строки нету еще одной пустой строки
314         //то вставить еще #13#10 чтоб был промежуток между секциями.
315         If TextFil1.Siz > 3 then begin
316           If TextFil1.FileString[TextFil1.Cursor-3] <> #10 then begin
317             TextFil1.AddStr('');
318           end;
319         end;
320         TextFil1.AddStr('['+SectName+']');
321         TextFil1.Cursor := TextFil1.Siz+1;  //курсор собсно в конец файла.
322       end
323       else begin
324         //Конец файла - не пустая строка (чета есть)
325         //тады - просто тоже самое, что выше, но немного по другому
326         TextFil1.AddStr(#13+#10+#13+#10+'['+SectName+']'); //добавить еще и символ конца строки
327                                   //в конец старой строки, собсно и создав новую строку
328         TextFil1.Cursor := TextFil1.Siz+1;  //курсор собсно в конец файла.
329       end;
330     end
331     else begin
332       //Если же файл пустой, то просто вставить сааамую первую секцию :))
333       TextFil1.AddStr('['+SectName+']');
334       TextFil1.Cursor := TextFil1.Siz+1;  //курсор собсно в конец файла.
335     end;
336   end;
337  
338 end;
339
340 Procedure TAnIniFile.RemoveSection(const SectName : string);
341 //Полностью удалить секцию.
342 var
343   TempCursor1 : LongInt;
344
345 begin
346
347   //Если только эта секция вообще существует....
348   if SectionExists(SectName) = true then begin
349     //Ставим курсор на начало секции...
350     GoToSection(SectName,1);
351     TempCursor1 := TextFil1.Cursor;
352     //Ставим курсор на конец секции...
353     GoToSection(SectName,3);
354     //Теперь удаляем нужное количество символов из строки...
355     Delete(TextFil1.FileString, TempCursor1, TextFil1.Cursor-TempCursor1);
356   end;
357  
358   //И типо полюбому курсор ставим на первый символ.
359   TextFil1.Cursor := 1;
360  
361 end;
362
363 Function TAnIniFile.ReadRawSection(const SectName : string) : string;
364 //Прочитать все содержимое секции в одну строку.
365 var
366   TempCursor1, TempCursor2 : LongInt;
367  
368 begin
369   //По умолчанию возвращается пустая строка...
370   Result := '';
371
372   //Если только эта секция вообще существует....
373   if SectionExists(SectName) = true then begin
374     //Запоминаем исходную позицию курсора...
375     TempCursor2 := TextFil1.Cursor;
376     //Ставим курсор на начало секции...
377     GoToSection(SectName,2);
378     TempCursor1 := TextFil1.Cursor;
379     //Ставим курсор на конец секции...
380     GoToSection(SectName,3);
381     //Теперь копируем инфу в результирующую строку...
382     //если только есть что копировать...
383     if (TextFil1.Cursor-TempCursor1) > 0 then begin
384       Result := Copy(TextFil1.FileString, TempCursor1, TextFil1.Cursor-TempCursor1);
385     end;
386   end;
387  
388 end;
389
390 Procedure TAnIniFile.WriteRawSection(const SectName, SectContent : string);
391 //Перезаписать всё содержимое секции из строки.
392 begin
393
394   //Для начала удаляем всю секцию...
395   RemoveSection(SectName);
396  
397   //Теперь создаем секцию заново...
398   CreateSection(SectName);
399   //И дописываем инфу в конец секции... курсор после CreateSection уже стоит на пустой строке в конце секции...
400   TextFil1.FileString := TextFil1.FileString+SectContent;
401
402 end;
403
404 Procedure TAnIniFile.StrToIniValueList(const InputStr : string; var IniValueList : AIniValueList);
405 //Пропарсить строку и закинуть инфу в динамический массив.
406 var
407   Strings, Elements, I, J, RealStrings, StrsAdded, ElementsAdded : Integer;
408   SearchPChar : PChar;
409   TrimmedStr, CurrStr, ElementStr, ValueStr : string;
410   Done : boolean;
411  
412 begin
413   //Инициализация
414   Done := false;
415   //Для начала - обтримать пробелы и переносы строк...
416   TrimmedStr := TrimEx(InputStr,' '+#13+#10);
417  
418   //Проверяем есть ли что-то в строке...
419   if Length(TrimmedStr) = 0 then begin
420     //Строка пуста. Просто переводим массив в нуль и заканчиваем работу.
421     SetLength(IniValueList,0,0);
422     Done := true;
423   end;
424  
425   //Едем дальше.
426   if Done = false then begin
427
428     //Подсчитать количество строк.
429     Strings := 1;   //Как минимум 1 строка там уже есть.
430     I := 0;   //Начинаем поиск с 1-го символа.
431     repeat
432       I := I+1;
433       if TrimmedStr[I] = #10 then begin
434         Strings := Strings+1;
435       end;
436     until I = Length(TrimmedStr);
437
438     //Посчитали. Выделяем память в динамическом массиве (1-й уровень)...
439     SetLength(IniValueList,Strings);
440    
441     //Терь построчно читаем инфу...
442     //Начальные значения переменных...
443     RealStrings := Strings;
444     StrsAdded := 0;
445     for I := 1 to Strings do begin
446       //Выпарсиваем кусок от строки....
447       CurrStr := {TrimRight(}Parse(TrimmedStr,#13+#10){)};
448       //Подчищаем триманутую строку на всякий случай...
449       TrimExLeft(TrimmedStr,' '+#13+#10);
450      
451       //Строчка есть. Теперь смотрим, не коммент ли она...
452       if NotComment(CurrStr) = true then begin
453         //не коммент - читаем.
454
455         //Подсчитать количество элементов по точкам с запятой.
456         Elements := 1;  //Как минимум 1 штука уже должна быть.
457         J := 0;  //Поиск с 1-го символа.
458         repeat
459           J := J+1;
460           if CurrStr[J] = ';' then begin
461             //Найден разделитель элемента.
462             Elements := Elements+1;
463           end;
464         until J = Length(CurrStr);
465
466         //Выделить место в массиве...
467         SetLength(IniValueList[StrsAdded],Elements);
468        
469         //Пропарсить элементы...
470         ElementsAdded := -1;       //Чтобы начать с 0-го элемента.
471         repeat
472           ElementsAdded := ElementsAdded+1;
473           ElementStr := Trim(Parse(CurrStr,';'));  //Прочитать очередной элемент...
474           IniValueList[StrsAdded,ElementsAdded].Name := TrimRight(Parse(ElementStr,':'));  //Имя элемента
475           IniValueList[StrsAdded,ElementsAdded].Value := TrimLeft(ElementStr);  //Значение элемента
476         until Length(CurrStr) = 0;
477        
478         //Прибавить счетчик прочитанных строк.
479         StrsAdded := StrsAdded+1;
480       end
481       else begin
482         //Коммент - значит эта строка пуста, надо уменьшить размер массива...
483         RealStrings := RealStrings-1;
484         SetLength(IniValueList,RealStrings);
485       end;
486
487     end;
488    
489   end;
490  
491 end;
492
493 Function TAnIniFile.IniValueListToStr(var IniValueList : AIniValueList) : string;
494 //Сгенерить строку из инфы динамического массива.
495 var
496   I, J : Integer;
497 begin
498   //Элементарно пробежаться по массиву и вернуть строку...
499   Result := '';          //Изначально пустая
500  
501   if Length(IniValueList) > 0 then begin
502
503     for I := 0 to Length(IniValueList)-1 do begin
504
505       //Добавить элементы
506       for J := 0 to Length(IniValueList[I])-1 do begin
507         Result := Result+IniValueList[I,J].Name+': '+IniValueList[I,J].Value;
508         if J <> Length(IniValueList[I])-1 then begin
509           //Если элемент не последний - добавить разделитель элементов
510           Result := Result+'; ';
511         end;
512       end;
513      
514       //Если строка не последняя - добавить разделитель строк.
515       if I <> Length(IniValueList)-1 then begin
516         Result := Result+#13+#10;
517       end;
518     end;
519    
520   end;
521  
522   //Собсно - это все %).
523 end;
524
525 Procedure TAnIniFile.ReadIniValueList(const SectName : string; var IniValueList : AIniValueList);
526 //Прочитать IniValueList из секции.
527 begin
528   StrToIniValueList(ReadRawSection(SectName),IniValueList);
529 end;
530
531 Procedure TAnIniFile.WriteIniValueList(const SectName : string; var IniValueList : AIniValueList);
532 //Перезаписать инфу секции из IniValueList.
533 begin
534   WriteRawSection(SectName,IniValueListToStr(IniValueList));
535 end;
536
537 Function TAnIniFile.CompareIniValueElements(var IniValueList1, IniValueList2 : AIniValueList; const List1Index, List2Index : Integer; const CaseSensitive : boolean; const ExceptValueNames : AnsiString) : Boolean;
538 //Функция сравнивает 2 строки 2 списков значений, если они идентичны (с учетом условий каш-сенситив
539 //и исключенных элементов) - возвращает true, иначе false. Если первое из Except - "all" то все
540 //последующие - наоборот те что проверяются, остальные игнорируются.
541 var
542   I, J, I2, J2, ElI : Integer;
543   ExceptsList : TStringList;
544   ExceptAll, IdentElemFinded, CanCheck : Boolean;
545
546 begin
547   //Инициализация.
548   Result := true;   //По умолчанию считаем что значения идентичны.
549   ExceptAll := false;   //По умолчанию это вырублено.
550   ExceptsList := TStringList.Create;
551
552   //Заполняем список ексцептов...
553   ExtractStrings([','],[' '],PChar(ExceptValueNames),ExceptsList);
554   //Проверяем механьызьм екцзепта....
555   if ExceptsList.Count > 0 then begin
556     if LowerCase(ExceptsList.Strings[0]) = 'all' then begin
557       //Ага, есть такое дело.
558       ExceptAll := true;
559       //Удаляем элемент "all".
560       ExceptsList.Delete(0);
561     end;
562   end;
563
564   //Проверяем 1-й список...
565   for I := 0 to Length(IniValueList1[List1Index])-1 do begin
566
567     //Проверяем, проходит ли по екцептам.
568     if ExceptAll = false then begin
569       //Исключение по принципу "разрешено все, кроме..."
570       CanCheck := true;
571       for ElI := 0 to ExceptsList.Count-1 do begin
572         if LowerCase(IniValueList1[List1Index,I].Name) = LowerCase(ExceptsList.Strings[ElI]) then begin
573           //Нашли что элемент исключается.
574           CanCheck := false;
575           Break;
576         end;
577       end;
578     end
579     else begin
580       //Исключение по принципу "запрещено все, кроме..."
581       CanCheck := false;
582       for ElI := 0 to ExceptsList.Count-1 do begin
583         if LowerCase(IniValueList1[List1Index,I].Name) = LowerCase(ExceptsList.Strings[ElI]) then begin
584           //Нашли что элемент _не_ исключается.
585           CanCheck := true;
586           Break;
587         end;
588       end;
589     end;
590
591     //Ок, если значение не попало под исключения - проверяем его... Надо обязательно найти аналогичное значение во втором списке, иначе считаем что списки не идентичны.
592     if CanCheck = true then begin
593       Result := false;   //Задача цикла внизу - вернуть true. Иначе все вылетит с результатом false.
594       for J := 0 to Length(IniValueList2[List2Index])-1 do begin
595         //Сканим %).
596         if LowerCase(IniValueList1[List1Index,I].Name) = LowerCase(IniValueList2[List2Index,J].Name) then begin
597           //Ага, нашли значение с таким же именем. Теперь проверяем собсно значение.
598           //Причем в зависимости от кейс-сенситива.
599           if CaseSensitive = true then begin
600             //Регистр учитывается.
601             if IniValueList1[List1Index,I].Value = IniValueList2[List2Index,J].Value then begin
602               //Ага, идентичны, true.
603               Result := true;
604               //И брякаем - вылетит в прямо на уровень внутри if CanCheck = true then begin
605               Break;
606             end;
607           end
608           else begin
609             //Регистр не учитывается.
610             if LowerCase(IniValueList1[List1Index,I].Value) = LowerCase(IniValueList2[List2Index,J].Value) then begin
611               //Ага, идентичны, true.
612               Result := true;
613               //И брякаем - вылетит в прямо на уровень внутри if CanCheck = true then begin
614               Break;
615             end;
616           end;
617         end;
618       end;
619       //Просканили, смотрим - true оно или false. Если false то имеем бряк, что по факту приведет к завершению функции
620       //и возвращению ей false.
621       if Result = false then begin
622         Break;
623       end;
624     end;
625
626   end;
627
628   //Ок, проверили 1-й список. Если Result все еще true - то проверяем 2-й, аналогично.
629   if Result = true then begin
630     for I := 0 to Length(IniValueList2[List2Index])-1 do begin
631
632       //Проверяем, проходит ли по екцептам.
633       if ExceptAll = false then begin
634         //Исключение по принципу "разрешено все, кроме..."
635         CanCheck := true;
636         for ElI := 0 to ExceptsList.Count-1 do begin
637           if LowerCase(IniValueList2[List2Index,I].Name) = LowerCase(ExceptsList.Strings[ElI]) then begin
638             //Нашли что элемент исключается.
639             CanCheck := false;
640             Break;
641           end;
642         end;
643       end
644       else begin
645         //Исключение по принципу "запрещено все, кроме..."
646         CanCheck := false;
647         for ElI := 0 to ExceptsList.Count-1 do begin
648           if LowerCase(IniValueList2[List2Index,I].Name) = LowerCase(ExceptsList.Strings[ElI]) then begin
649             //Нашли что элемент _не_ исключается.
650             CanCheck := true;
651             Break;
652           end;
653         end;
654       end;
655
656       //Ок, если значение не попало под исключения - проверяем его... Надо обязательно найти аналогичное значение во втором списке, иначе считаем что списки не идентичны.
657       if CanCheck = true then begin
658         Result := false;   //Задача цикла внизу - вернуть true. Иначе все вылетит с результатом false.
659         for J := 0 to Length(IniValueList1[List1Index])-1 do begin
660           //Сканим %).
661           if LowerCase(IniValueList2[List2Index,I].Name) = LowerCase(IniValueList1[List1Index,J].Name) then begin
662             //Ага, нашли значение с таким же именем. Теперь проверяем собсно значение.
663             //Причем в зависимости от кейс-сенситива.
664             if CaseSensitive = true then begin
665               //Регистр учитывается.
666               if IniValueList2[List2Index,I].Value = IniValueList1[List1Index,J].Value then begin
667                 //Ага, идентичны, true.
668                 Result := true;
669                 //И брякаем - вылетит в прямо на уровень внутри if CanCheck = true then begin
670                 Break;
671               end;
672             end
673             else begin
674               //Регистр не учитывается.
675               if LowerCase(IniValueList2[List2Index,I].Value) = LowerCase(IniValueList1[List1Index,J].Value) then begin
676                 //Ага, идентичны, true.
677                 Result := true;
678                 //И брякаем - вылетит в прямо на уровень внутри if CanCheck = true then begin
679                 Break;
680               end;
681             end;
682           end;
683         end;
684         //Просканили, смотрим - true оно или false. Если false то имеем бряк, что по факту приведет к завершению функции
685         //и возвращению ей false.
686         if Result = false then begin
687           Break;
688         end;
689       end;
690
691     end;
692   end;
693
694   //Чистим мусор.
695   ExceptsList.Free;
696 end;
697
698 Function TAnIniFile.CompareIniValueLists(var IniValueList1, IniValueList2 : AIniValueList; const CaseSensitive : boolean; const ExceptValueNames : AnsiString) : Boolean;
699 //Функция сравнивает 2 списка значений, если списки идентичны (с учетом условий каш-сенситив и исключенных
700 //элементов) - возвращает true, иначе false. Если первое из Except - "all" то все последующие - наоборот те
701 //что проверяются, остальные игнорируются.
702 var
703   I, J : Integer;
704   IdentElemFinded : Boolean;
705 begin
706   //Инициализация.
707   Result := true;      //По умолчанию считаем что списки идентичны...
708
709   //Сверяем размер списков.
710   if Length(IniValueList1) <> Length(IniValueList2) then begin
711     Result := false;
712   end;
713
714   if (Result = true) and (Length(IniValueList1) > 0) then begin
715     //Если в списках вообще что-то есть то сверяем их содержимое.
716     for I := 0 to Length(IniValueList1)-1 do begin
717
718       //Ищем во втором списке элемент, идентичный данному...
719       IdentElemFinded := false;
720       for J := 0 to Length(IniValueList2)-1 do begin
721         IdentElemFinded := false;   //Так надо на начало каждой интерации цикла.
722         if CompareIniValueElements(IniValueList1, IniValueList2, I, J, CaseSensitive, ExceptValueNames) = true then begin
723           //Нашли идентичный элемент. Оставляем отметку и брякаем цикл.
724           IdentElemFinded := true;
725           Break;
726         end;
727       end;
728
729       //Смотрим - если идентичный элемент не найден - то Отмечаем false в результате, и опять же брякаем циклу...
730       if IdentElemFinded = false then begin
731         Result := false;
732         Break;
733       end;
734     end;
735   end;
736 end;
737
738 Function TAnIniFile.GetIniValueListValueIndex(var IniValueList : AIniValueList; const ValIndex : Integer; const ValueName : AnsiString) : Integer;
739 //Вернуть индекс значения с указанным именем в указанной строке списка (элементе).
740 var
741   I : Integer;
742   TempValName : AnsiString;
743
744 begin
745   //Инициализация.
746   TempValName := LowerCase(ValueName);
747   Result := -1;
748
749   //Ищем значение и возвращаем результат.
750   for I := 0 to Length(IniValueList[ValIndex])-1 do begin
751     if LowerCase(IniValueList[ValIndex,I].Name) = TempValName then begin
752       Result := I;
753       Break;
754     end;
755   end;
756 end;
757
758 Function TAnIniFile.GetIniValueListValue(var IniValueList : AIniValueList; const ValIndex : Integer; const ValueName : AnsiString) : AnsiString;
759 //Функция возвращает содержимое значения с указанным именем в указанной строке списка.
760 var
761   I : Integer;
762
763 begin
764   //Инициализация.
765   Result := '';
766
767   //Ищем значение и возвращаем результат.
768   I := Self.GetIniValueListValueIndex(IniValueList,ValIndex,ValueName);
769   if I >= 0 then begin
770     Result := IniValueList[ValIndex,I].Value;
771   end;
772 end;
773
774 Function TAnIniFile.ChangeIniValueListValue(var IniValueList : AIniValueList; const ValIndex : Integer; const ValueName, ValueValue : AnsiString) : Boolean;
775 //Функция записывает новое содержимое в значение с указанным именем в указанной строке списка.
776 var
777   I : Integer;
778   TempValName : AnsiString;
779
780 begin
781   //Инициализация.
782   TempValName := LowerCase(ValueName);
783   Result := false;
784
785   //Ищем значение.
786   for I := 0 to Length(IniValueList)-1 do begin
787     if LowerCase(IniValueList[ValIndex,I].Name) = TempValName then begin
788       //Нашли, изменяем содержимое.
789       IniValueList[ValIndex,I].Value := ValueValue;
790       Result := true;
791       Break;
792     end;
793   end;
794
795 end;
796
797 Function TAnIniFile.IniValueListValueExists(var IniValueList : AIniValueList; const ValIndex : Integer; const ValueName : AnsiString) : Boolean;
798 //Если значение существует - возвращает true, иначе false.
799 var
800   I : Integer;
801   TempValName : AnsiString;
802
803 begin
804   //Инициализация.
805   TempValName := LowerCase(ValueName);
806   Result := false;
807
808   //Ищем значение.
809   for I := 0 to Length(IniValueList)-1 do begin
810     if LowerCase(IniValueList[ValIndex,I].Name) = TempValName then begin
811       //Нашли, значит оно есть. Сообщаем об этом.
812       Result := true;
813       Break;
814     end;
815   end;
816 end;
817
818 Function TAnIniFile.GetIniValueListIndexByValue(var IniValueList : AIniValueList; const ValueName, ValueValue : AnsiString) : Integer;
819 //Возвращает индекс первого встреченного элемента (сверху) с указанным именем значения и собсно значением.
820 //Если ничего такого нема - вернет -1.
821 var
822   I, ValueNum : Integer;
823   TempValueStr : AnsiString;
824  
825 begin
826   //Инициализация.
827   Result := -1;
828
829   //Получить сверяемое значение в нижнем регистре.
830   TempValueStr := LowerCase(ValueValue);
831   //Пробежимся по списку в поисках подходящего элемента.
832   for I := 0 to Length(IniValueList)-1 do begin
833     //Смотрим - есть ли тут значение с нужным именем?
834     ValueNum := Self.GetIniValueListValueIndex(IniValueList,I,ValueName);
835     if ValueNum >= 0 then begin
836       //Ага, есть тут такая фиговина. Смотрим - а то ли там собсно значение?
837       if LowerCase(IniValueList[I,ValueNum].Value) = TempValueStr then begin
838         //Вахъ, и в самом деле оно. Поиск завершен, возвращаем результат.
839         Result := I;
840         Break;
841       end;
842     end;
843   end;
844  
845 end;
846
847 Function TAnIniFile.CopyIniValueListElement(var From, Dest : AIniValueList; const FromIndex, DestIndex : Integer) : Boolean;
848 //Скопировать содержимое одного элемента одного списка в другой элемент другого списка (в теории возможно
849 //и в рамкеах одного списка если оба указателя оставить на один и тот же список.).
850 var
851   I : Integer;
852 begin
853   //Инициализация.
854   Result := false;
855
856   //Выставляем размер элемента...
857   SetLength(Dest[DestIndex],Length(From[FromIndex]));
858   //Собсно копируем...
859   for I := 0 to Length(From[FromIndex])-1 do begin
860     Dest[DestIndex,I].Name := From[FromIndex,I].Name;
861     Dest[DestIndex,I].Value := From[FromIndex,I].Value;
862   end;
863   //И вернуть результат.
864   Result := true;
865 end;
866
867 Function TAnIniFile.CopyIniValueList(var From, Dest : AIniValueList) : Boolean;
868 //Скопировать содержимое одного списка в другой.
869 var
870   I : Integer;
871 begin
872   //Инициализация.
873   Result := true;
874
875   //Чистим целевой массив...
876   SetLength(Dest, 0, 0);
877   //Собсно поехали. Выставить размер массива первого уровня...
878   SetLength(Dest, Length(From));
879   //Поехали по этому массиву...
880   for I := 0 to Length(From)-1 do begin
881     //Копируем собсно элемент.
882     if Self.CopyIniValueListElement(From, Dest, I, I) = false then begin
883       //Если при этом была какая-то ошибка - вываливаемся из цикла, возвращая false.
884       Result := false;
885       Break;
886     end;
887   end;
888 end;
889
890 Function TAnIniFile.AddIniValueList(var From, Dest : AIniValueList) : Boolean;
891 //Скопировать содержимое одного списка в другой, добавив новые данные в конец
892 //целевого списка и сохранив все уже там присутствовавшее.
893 var
894   I, OldDestLength : Integer;
895 begin
896   //Инициализация.
897   Result := true;
898
899   //Меняем размер целевого массива...
900   OldDestLength := Length(Dest);
901   SetLength(Dest, Length(Dest)+Length(From));
902   //Поехали по этому массиву...
903   for I := 0 to Length(From)-1 do begin
904     //Копируем собсно элемент.
905     if Self.CopyIniValueListElement(From, Dest, I, OldDestLength+I) = false then begin
906       //Если при этом была какая-то ошибка - вываливаемся из цикла, возвращая false.
907       Result := false;
908       Break;
909     end;
910   end;
911 end;
912
913 Function TAnIniFile.RemoveIniValueListValue(var IniValueList : AIniValueList; const ValIndex : Integer; const ValueName : AnsiString) : Boolean;
914 //Удалить значение с указанным именем в указанном элементе в IniValueList.
915 //Если значение такое не существовало - вернет false, иначе true.
916 var
917   I, ValuesNum : Integer;
918   DeletingValue, DummyValue : RIniValueListElement;
919  
920 begin
921   //Инициализация.
922   Result := false;
923   ValuesNum := Length(IniValueList[ValIndex]);
924
925   //Существует ли нужное значение? И заодно номер его получить.
926   I := Self.GetIniValueListValueIndex(IniValueList,ValIndex,ValueName);
927   if I >= 0 then begin
928     //Итак, значение для удаления найдено.
929     //Если это значение не последнее - то двигаем значения в массиве чтобы таки стало последним.
930     if I <> ValuesNum-1 then begin
931       Move(IniValueList[ValIndex,I],DeletingValue,SizeOf(RIniValueListElement));    //Временно копируем инфу удаляемого значения.
932       Move(IniValueList[ValIndex,I+1],IniValueList[ValIndex,I],SizeOf(RIniValueListElement)*(ValuesNum-(I+1)));    //Переносим инфу других значений.
933       Move(DeletingValue,IniValueList[ValIndex,ValuesNum-1],SizeOf(RIniValueListElement));     //Копируем удаляемое значение в конец массива чтоб его прочистило при смене размера массива (во избежание утечков памяти).
934       Move(DummyValue,DeletingValue,SizeOf(RIniValueListElement));     //Копируем во временное значение инфу значения заглушки - чтобы тупой сборщег мусора не полез по ссылкам и не заховал последнее значение массива раньше или позже (т.е. еще раз на пустое место) правильного времени.
935     end;
936     //Почистить удаляемое значение.
937     IniValueList[ValIndex,ValuesNum-1].Name := '';
938     IniValueList[ValIndex,ValuesNum-1].Value := '';
939     //И почистить массив.
940     ValuesNum := ValuesNum-1;
941     SetLength(IniValueList[ValIndex],ValuesNum);
942     //Done.
943     Result := true;
944   end;
945 end;
946
947 Function TAnIniFile.RemoveIniValueListElement(var IniValueList : AIniValueList; const ValIndex : Integer) : Boolean;
948 //Удалить указанный элемент IniValueList. Если элемент не существовал - вернет false, в остальных случаях true.
949 var
950   I, ValuesNum, ElementsNum : Integer;
951   DeletingElement, DummyElement : AIniValueListElements;
952  
953 begin
954   //Инициализация.
955   Result := false;
956   DeletingElement := nil;      //Чтобы убедиться что в памяти под этот массив ничего не выделено.
957   SetLength(DummyElement,0);
958  
959   if Length(IniValueList) >= ValIndex then begin
960     //т.е. если в массиве есть элемент с таким индексом.
961     //Для начала - очистить содержимое элемента.
962     ValuesNum := Length(IniValueList[ValIndex]);
963     for I := 0 to ValuesNum-1 do begin
964       IniValueList[ValIndex,I].Name := '';
965       IniValueList[ValIndex,I].Value := '';
966     end;
967     SetLength(IniValueList[ValIndex],0);
968     //Теперь надо удалить собсно сам элемент.
969     ElementsNum := Length(IniValueList);
970     //Если это значение не последнее - то двигаем значения в массиве чтобы таки стало последним.
971     if ValIndex <> ElementsNum-1 then begin
972       Move(IniValueList[ValIndex],DeletingElement,SizeOf(AIniValueListElements));    //Временно копируем инфу удаляемого элемента.
973       Move(IniValueList[ValIndex+1],IniValueList[ValIndex],SizeOf(AIniValueListElements)*(ElementsNum-(ValIndex+1)));    //Переносим инфу других элементов.
974       Move(DeletingElement,IniValueList[ElementsNum-1],SizeOf(AIniValueListElements));     //Копируем удаляемый элемент в конец массива чтоб его прочистило при смене размера массива (во избежание утечков памяти).
975       Move(DummyElement,DeletingElement,SizeOf(AIniValueListElements));     //Копируем во временный элемент инфу значение заглушки - чтобы тупой сборщег мусора не полез по ссылкам и не заховал последний элемент массива раньше или позже (т.е. еще раз на пустое место) правильного времени.
976     end;
977     //И почистить массив.
978     ElementsNum := ElementsNum-1;
979     SetLength(IniValueList,ElementsNum);
980   end;
981 end;
982
983 //------------------------------------------//
984 //           Закрытые методы...             //
985 //------------------------------------------//
986
987 Function TAnIniFile.GoToSection(const SectName : string; TypePos : byte) : boolean;
988 var
989   buf, TestStr : string;
990   Finded, TempBool1 : boolean;
991   SectLength, I, SectStartCur, TempCur : integer;
992 begin
993   //В целом - САМАЯ ГЛАВНАЯ ФУНКА ТУТ типа :)
994  
995   //Функа для выхода на нужную секцию, на нужную позицию
996   //Если удастся - вернет тру, если нет - фальс.
997   //Если надо будет выйти в конец секции, а ей не дали
998   //последнего абзаца - создаст.
999   result := false;
1000   Finded := false;
1001   SectLength := Length(SectName)+2;
1002
1003   //В начале - добратся до нужной секции.
1004   //НО - только, если в файл-строке вообще чтото есть :)
1005
1006   If TextFil1.Siz > 0 then begin
1007     TextFil1.Cursor := 1;
1008     repeat
1009       //Прочитать строку (курсор пос