Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Общие вопросы > Чтение параметров из Ini/Inf-файлов


Автор: capricorn 19.4.2008, 14:45
Всем привет!

Вопрос в следующем:
есть INI-файл, нужно прочитать из него параметры, но дело в том, что они указаны следующим образом:

[SECTION 1]
PARAM1 = "123"
PARAM2 = 555

[SECTION 2]
PARAM3
PARAM4
PARAM5


секцию [SECTION 1] прочитать легко (стандартными ср-вами TIniFile), а вот как прочитать параметры из секции [SECTION 2]?

ЗЫ
Пробовал получить все параметры из секции
ReadSection('SECTION 2', myparams)
, но результата не получил.. как быть?

далее...
на http://www.torry.net/quicksearchd.php?String=Ini&Title=Yes нашел прогу, Ini Edit v.2.1, которая читает файлы как раз, как мне нужно, но исходников нет =(, 

Зарарание спасибо!

ЗЫ
подобные файлы - являются файлами INF (в основном), поэтому тут такая заморочка...

Автор: SneG0K 19.4.2008, 15:12
http://forum.vingrad.ru/articles/topic-197956.html

Автор: capricorn 19.4.2008, 15:25
хммм...
там полностью расписана работа с INI-файлами правильной структуры...
у меня же проблема в том, что структура файла немного иная, т.е. есть подобные секции:

[SECTION 2]
PARAM3
PARAM4
PARAM5

стандартные ср-ва Windows (для INI-файлов естественно) не позволяют читать такие секции (я говорю о ф-иях GetPrivateProfileString и GetProfileString, на которых и основан класс TIniFile)...

так вот я и спрашиваю, может есть какие-нить компоненты/либы для работы с подобными (INF) файлами...?

Автор: SneG0K 19.4.2008, 15:39
Берешь TStringList и анализируешь все ручками. Других вариантов не знаю... Либо переделывай структуру файла

Автор: Poseidon 21.4.2008, 08:52
Цитата(capricorn @  19.4.2008,  15:25 Найти цитируемый пост)
стандартные ср-ва Windows (для INI-файлов естественно) не позволяют читать такие секции
ВОТ ключевая фраза! Нельзя (стандартными средствами, разумеется)! Но, учитывая что ini - это обычный текстовик, читай в ручную. Хотя я не понимаю смысла в записях: 
Цитата
Файлы .INI - это текстовые файлы, предназначенные в 16-разрядных Windows 3.x для хранения информации о настройках различных приложений. Информация логически группируется в разделы, каждый из которых начинается оператором заголовка, заключенным в квадратные скобки. Например, [Desktop]. В строках, следующих за заголовком содержится информация, относящаяся к данному разделу, в форме:
<ключ>=<значение>
 У тебя же только названия ключей. Толку от них?

Автор: capricorn 22.4.2008, 10:58
Вобщем выяснилось вот что.
Inf-файлы windows, с которыми и предполагалось работать имеют немного другую структуру, чем Ini-файлы: 

Код

[секция]
строка 1
строка 2
;...
строка n


т.е. строки парсятся уже потом (для получения параметров вида <ключ>=<значение> и т.д)...

далее. ф-ии Windows для работы с INI-файлами не позволяют читать параметры с переносами строк:  smile 

Код

; Этот параметр взят из реального файла
; и демонстрирует многострочные параметры (с переносами)
0x3,"Software\Microsoft\MediaPlayer\Battery\Presets\DrowningFlower","Palette",\
  09,00,09,00,09,01,0c,00,09,03,10,00,09,04,14,00,09,06,17,00,09,07,1b,00,09,\
  09,1f,00,09,0a,22,00,09,0c,26,00,09,0d,2a,00,08,0f,2e,00,08,10,31,00,08,12,\
; тут тоже может быть комментарий
  35,00,08,13,39,00,08,15,3c,00,08,16,40,00,08,18,44,00,08,19,48,00,08,1b,4b
; {вырезано}


Такие параметры так-же могут использоваться в REG-файлах

Поэтому я решил написать модуль для работы с Inf-файлами.
(в нем только самые необходимые функции для чтения/записи строк, парсинг параметров пока во внимание не берется)

Сначала я пытался дописать существующий модуль для работы с Ini-файлами IniFiles32 (от Stephan Schneider), но найдя там более 5 грубых ошибок, решил все-таки писать с нуля...  smile 

вот что из этого получилось:

Код

{----------------------------------------------
InfFiles.pas

Модуль для работы с Inf-файлами Windows

Алексей Куликов (C) 2008 г.

freeware
-----------------------------------------------}

unit InfFiles;

interface

uses Classes, SysUtils;

type

  TInfFile = class (TObject)
    private
      FFileName:    string;
      FFileBuffer:  TStringList;
      procedure LoadFromFile;
      function ParseComments(const S: string): string;
      function IsSection(const S: string): boolean;
      function GetSectionIndex(const Section: string): integer;
      function RemoveStr(const Section: string; Target: string): integer;
    public
      constructor Create(const FileName: string);
      destructor Destroy;
      procedure Save();
      procedure SaveAs(const FileName: string);
      procedure ReadSections(Strings: TStrings);
      procedure ReadStrings(const Section: string; Strings: TStrings);
      procedure WriteString(const Section: string; const Ext: string);
      procedure WriteStrings(const Section: string; Strings: TStrings);
      procedure ReplaceString(const Section: string;
                              const Old: string; const New:string);
      procedure ReplaceStrings(const Section: string;
                               const Old: string; const New: TStrings);
      procedure DeleteString(const Section: string; Target: string);
  end;

implementation

constructor TInfFile.Create(const FileName: string);
begin
  FFileName := FileName;
  FFileBuffer := TStringList.Create;
  if FileExists(FFileName) then
    LoadFromFile;
end;

destructor TInfFile.Destroy;
begin
  FFileBuffer.Free;
  Finalize(FFileName);
end;

{
  Сохраняет изменения в открытом файле
}
procedure TInfFile.Save;
begin
  FFileBuffer.SaveToFile(FFileName);
end;

{
  Сохраняет изменения в назначенном файле
}
procedure TInfFile.SaveAs(const FileName: string);
begin
  FFileBuffer.LoadFromFile(FileName);
end;

procedure TInfFile.LoadFromFile;
begin
  FFileBuffer.LoadFromFile(FFileName);
end;

{
  Функция парсит строку, удаляя комментарии
}
function TInfFile.ParseComments(const S: string): string;
var
  i, len: integer;
  tmp: string;
begin
  len := length(S);
  i := 1;
  while (i < len+1) and (S[i] <> ';') do
  begin
    tmp := tmp + S[i];
    Inc(i);
  end;
  result := tmp;
end;

{
  Функция возвращает true, если S яв-ся секцией
}
function TInfFile.IsSection(const S: string): boolean;
var
  tmp: string;
begin
  Result := false;
  if S <> '' then
  begin
    tmp := Trim(s);
    if (tmp[1] = '[') and (tmp[Length(tmp)] = ']') then
      Result := true;
  end;
end;

{
  Функция возвращает инедекс секции
  причем только первый (если секция разбросана по файлу)
}
function TInfFile.GetSectionIndex(const Section: string): integer;
begin
  Result := FFileBuffer.IndexOf('['+Section+']');
end;

{
  Функция получает список всех секций файла
}
procedure TInfFile.ReadSections(Strings: TStrings);
var
  i: integer;
  Section: string;
  bufcount: integer;
begin
  Strings.BeginUpdate;
  try
    Strings.Clear;
    bufcount := FFileBuffer.Count;
    if bufcount > 0 then
    begin
      i := 0;
      while (i < bufcount - 1 )do
      begin
        Section := Trim(FFileBuffer[i]);
        if Section <> '' then
        if IsSection(Section) then
        begin
          Delete(Section, 1, 1);
          Delete(Section, Length(Section), 1);
          Strings.Add(Section);
        end;
        Inc(i);
      end;
    end;
  finally
    Strings.EndUpdate;
  end;
end;

{
  Ищет строку и удаляет ее (учитывая переносы) и возвращает индекс
  удаленной строки, если строка не найдена, возвращает -1.
  Поиск осуществляется по принципу маски, т.е.
  будет удалена первая найденная строка, начанающаяся с Target
}
function TInfFile.RemoveStr(const Section: string; Target: string): integer;

  function CompareStrMask(const Str: string; const Mask: string): boolean;
  begin
    Result := false;
    if Length(Str) < Length(Mask) then Exit;
    if Copy(UpperCase(Trim(Str)), 1, Length(Mask)) = UpperCase(Mask) then
      Result := true;
  end;

var
  i, j: integer;
  InSection: boolean;
  bufcount: integer;
  FirstDelIndex, removecount: integer; // для перенесенных строк
  Current: string;
  tmp: string;
begin
  Result := -1; // если строка не будет найдена
  bufcount := FFileBuffer.Count;
  if bufcount > 0 then
  begin
    if FFileBuffer.Find(Target, i) then // строка полностью совпадает
    begin
      FFileBuffer.Delete(i);
      Result := i;
      Exit;
    end;
    i := GetSectionIndex(Section); // поиск по маске
    if (i <> -1) then
    begin
      Inc(i);
      InSection := true;
      while (i < BufCount) do
      begin
        Current := ParseComments(FFileBuffer[i]);
        if (Current = '') then
          Inc(i)
        else
        begin
        if IsSection(Current) then
          if Current <> '['+Section+']' then
            InSection := false else
            InSection := true;
          if not InSection then // мы находмися в заданной секции?
          Inc(i) else
          if Current[Length(Current)] = '\' then // строка перенесена
          begin
                FirstDelIndex := i;
                tmp := '';
                removecount := 1;
                while (Current[Length(Current)] = '\') do
                begin
                  Delete(Current, Length(Current), 1);
                  tmp := tmp + Current;
                  Inc(i);
                  Current := ParseComments(Trim(FFileBuffer[i]));
                  if Current = '' then
                  while ((Current = '') and (i < BufCount)) do
                  begin
                    Inc(i);
                    Inc(removecount);
                    Current := ParseComments(Trim(FFileBuffer[i]));
                  end;
                  Inc(removecount);
                end;
                tmp := tmp + Current;
                if CompareStrMask(tmp, Target) then
                begin
                  for j := 1 to removecount do
                    FFileBuffer.Delete(FirstDelIndex);
                  Result := FirstDelIndex; // индекс удаленной строки
                  Exit; // выходим после удаления
                end;
                Inc(i);
          end else
           if CompareStrMask(Current, Target) then
          begin
            FFileBuffer.Delete(i);
            Result := i; // индекс удаленной строки
            Exit; // выходим после удаления
        end else // if CompareStrMask(Current, Target)
          Inc(i);
        end; // if (Current = '')
      end;
    end;// if (i <> -1) // заданная секция отсутствует
  end;
end;

{
 Фукция читает все строки из определенной секции и записывает в буфер Strings
 также производит форматирование строк с переносами
}

procedure TInfFile.ReadStrings(const Section: string; Strings: TStrings);
var
  i: integer;
  InSection: boolean;
  BufCount: integer;
  FirstIndex: integer;
  Current, tmp: string;
begin
  Strings.BeginUpdate;
  try
    Strings.Clear;
    BufCount := FFileBuffer.Count;
    if BufCount > 0 then
    begin
      FirstIndex := GetSectionIndex(Section);
      if FirstIndex <> -1 then
      begin
        i := FirstIndex+1;
        InSection := true;
        while (i < BufCount) do
        begin
          Current := ParseComments(Trim(FFileBuffer[i]));
          if (Current <> '') then
          begin
            if IsSection(Current) then
            begin
              if Current = '['+Section+']' then
                InSection := true else
                InSection := false;
              Inc(i);
            end else // if IsSection(Current)
            if InSection then
            begin
              if Current[Length(Current)] = '\' then
              begin
                tmp := '';
                while (Current[Length(Current)] = '\') do
                begin
                  Delete(Current, Length(Current), 1);
                  tmp := tmp + Current;
                  Inc(i);
                  Current := ParseComments(Trim(FFileBuffer[i]));
                  if Current = '' then
                  while ((Current = '') and (i < BufCount)) do
                  begin
                    Inc(i);
                    Current := ParseComments(Trim(FFileBuffer[i]));
                  end;
                end;
                tmp := tmp + Current;
                Strings.Add(tmp);
                Inc(i);
              end else // if Current[Length(Current)] = '\'
              begin
                Strings.Add(Current);
                Inc(i);
              end;
            end else // if InSection
            Inc(i);
          end else // if (Current <> '')
          Inc(i);
        end; // while (i < BufCount)
      end; // if FirstIndex <> -1
    end; // if BufCount > 0
  finally
  Strings.EndUpdate;
  end;
end;

{
  функция записывает строку в заданную секцию
}
procedure TInfFile.WriteString(const Section: string; const Ext: string);
var
  i: integer;
begin
  i := GetSectionIndex(Section);
  if (i <> -1) then
  begin
    Inc(i);
    FFileBuffer.Insert(i, Ext);
  end else
  begin // если секции не существует, добавляем новую
    FFileBuffer.Add('');
    FFileBuffer.Add('['+Section+']');
    FFileBuffer.Add(Ext);
  end;
end;

{
  Функция записывает строки в заданную секцию
}
procedure TInfFile.WriteStrings(const Section: string; Strings: TStrings);
var
  i, j, count: integer;
begin
  i := GetSectionIndex(Section);
  if (i <> -1) then
  begin
    Inc(i);
    count := Strings.Count;
    if count > 0 then
    for j := 0 to count-1 do
      FFileBuffer.Insert(i, Strings[j]);
  end else
  begin
    FFileBuffer.Add('');
    FFileBuffer.Add('['+Section+']');
    FFileBuffer.AddStrings(Strings);
  end;
end;

{
  Функция заменяет строку в файле (учитывая переносы)
}
procedure TInfFile.ReplaceString(const Section: string; const Old: string; const New: string);
var
  i: integer;
  index: integer;
begin
  i := GetSectionIndex(Section);
  if (i <> -1) then
  begin
    index := RemoveStr(Section, Old);
    if (index <> -1) then
        FFileBuffer.Insert(index, New)
    else
      WriteString(Section, New);
  end else
  begin
    FFileBuffer.Add('');
    FFileBuffer.Add('['+Section+']');
    FFileBuffer.Add(New);
  end;
end;

{
  Удаление строки по маске
  (см. ф-ию RemoveStr)
}
procedure TInfFile.DeleteString(const Section: string; Target: string);
begin
  RemoveStr(Section, Target);
end;

{
  Функция заменяет строку в файле на последовательность строк
  (удаляет строку Old и добавляет новые строки в начало секции)
  (удобно для записи многострочных (с переносами) параметров)
}
procedure TInfFile.ReplaceStrings(const Section: string; const Old: string; const New: TStrings);
var
  i, j: integer;
  index: integer;
begin
  i := GetSectionIndex(Section);
  if (i <> -1) then
  begin
    RemoveStr(Section, Old);
    WriteStrings(Section, New);
  end else
  begin
    FFileBuffer.Add('');
    FFileBuffer.Add('['+Section+']');
    FFileBuffer.AddStrings(New);
  end;
end;

end.


Для описания ф-ий см. комментарии...

Надеюсь, что кому-нить может и пригодится  smile

ЗЫ
Если вы нашли ошибку/баг в моем модуле, пожалуйста, сообщайте...

Автор: vserd 9.10.2008, 17:48
Цитата(capricorn @  22.4.2008,  10:58 Найти цитируемый пост)
ЗЫ
Если вы нашли ошибку/баг в моем модуле, пожалуйста, сообщайте...

Код

{
  Сохраняет изменения в назначенном файле
}
procedure TInfFile.SaveAs(const FileName: string);
begin
  FFileBuffer.LoadFromFile(FileName);
end;

меняем на
Код

procedure TInfFile.SaveAs(const FileName: string);
begin
  FFileBuffer.SavetoFile(FileName);
end;

Автор: MetalFan 9.10.2008, 21:18
вроде как для работы с inf файлами в винде есть ряд функций в http://msdn.microsoft.com/en-us/library/aa376967(VS.85).aspx...
в частности см. http://msdn.microsoft.com/en-us/library/aa376934(VS.85).aspx

Автор: MetalFan 9.10.2008, 21:45
кстати,
Цитата(capricorn @  22.4.2008,  10:58 Найти цитируемый пост)
но найдя там более 5 грубых ошибок

не знаю, что это за модуль такой и что там за ошибки...
но не удержусь прокомментировать некоторые моменты представленного кода
1. 
Цитата(capricorn @  22.4.2008,  10:58 Найти цитируемый пост)

      destructor Destroy;
...
destructor TInfFile.Destroy;
begin
  FFileBuffer.Free;
  Finalize(FFileName);
end;

я конечно понимаю, что класс является наследником TObject, и на некоторые моменты можно положить...
но стоит, имхо, объявить деструктор перекрытым (override) и вызывать из его реализации метод предка (inherited)
так же неясно, зачем строковому полю класса делается Finalize.
2. 
Цитата(capricorn @  22.4.2008,  10:58 Найти цитируемый пост)
{
  Функция парсит строку, удаляя комментарии
}
function TInfFile.ParseComments(const S: string): string;
var
  i, len: integer;
  tmp: string;
begin
  len := length(S);
  i := 1;
  while (i < len+1) and (S[i] <> ';') do
  begin
    tmp := tmp + S[i];
    Inc(i);
  end;
  result := tmp;
end;

медленное и неэффективное решение, достойное студента первого курса в лабе по паскалю. чем функции Pos и Copy не угодили?

3. запись
Код

    if (tmp[1] = '[') and (tmp[Length(tmp)] = ']') then
      Result := true;

логичнее выглядит так:
Код

Result := (tmp[1] = '[') and (tmp[Length(tmp)] = ']');


4. опять лишние телодвижения
Код

          Delete(Section, 1, 1);
          Delete(Section, Length(Section), 1);

чем copy не угодило?

5. функция RemoveStr вообще не доступна моему пониманию... имхо слишком усложненный код.
и т.д....

p.s. без обид  smile 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)