root/trunk/LCLAnIniFile.pas

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