Ini文件 - 如何读取多行?

8

我知道Ini文件通常只用于单行信息,不用说我正在尝试读写多行信息到Ini文件中——但并没有太大的成功(好像我总是走弯路!)

假设我的Ini文件在保存时看起来像这样:

[richardmarx] 
Filenames=hazard
children of the night
right here waiting

假设Ini文件是动态生成的(即richardmarx和Filenames未知,但唯一性 - 它们可以是任何内容)。
我将如何读取Ini文件?
在这个例子中,我如何将richardmarx放入TEdit中,并将与richardmarx部分相关联的Filenames放入备忘录中?
非常感谢。

Ini文件对于许多事情来说都很好,但我认为多行文本已经超出了它的能力范围。我更喜欢使用更结构化的格式,比如XML、YAML或JSON。 - David Heffernan
1
可能是重复的问题:Delphi:如何在ini文件中使用换行符? - manlio
读取INI文件到StringList的问题 - 空格问题 - Gabriel
8个回答

14

首先不要将多行字符串存储到 INI 文件中。 像 @RobertFrank 建议的那样转义换行符。我不会使用星号,因为它是一个有效的文本字符。 我会改用类似这样的东西:

[richardmarx] 
Filenames=hazard%nchildren of the night%nright here waiting

您可以读取字符串并将%n序列替换为sLineBreak全局变量的值。如果您需要存储实际的%字符,请将其转义为%%,例如:

[sales] 
value=Sale! 50%% off%nat Macy's

14
为什么这个被踩了?人们不解释地乱踩真的很烦人。 - Remy Lebeau
为什么需要转义任何内容?我的编辑显示您将获得Filenames=Value1#13Value2#13Value3,那么#13不会起到相同的作用吗? - Ken White
1
+1,我已经使用这种技术很长时间了。对于数字使用逗号分隔符,对于字符串使用竖线分隔符。 - LU RD
1
@KenWhite:手动解析仍需要区分单个多行INI值的换行符和分隔不同INI值的换行符。编码换行符使手动解析在这方面更容易。 - Remy Lebeau
4
如果你手动解析文件,你需要手动区分换行符代表什么,以及它是作为值的一部分还是作为分隔符。如果你使用标准API(TIniFile/TMemIniFile或直接的Win32配置文件API),值中的换行符必须被编码。这就是我所说的全部内容。 - Remy Lebeau
显示剩余6条评论

6

你没有使用有效的 .ini 格式,所以这不会很容易。如果你使用一个格式正确的 .ini 文件,那么就会简单得多。

一个有效的 ini 文件的格式如下:

[section]
akey=value
bkey=value
ckey=value

下面是从ini文件中读取多行的示例代码。虽然它使用了一个TListBox而不是TEdit,但应该足以让您开始。
下面的代码也可以处理格式不正确的文件,但您可能需要更改ListBox1Click事件中的代码,使用ReadSectionValues来手动解析每个项目并在显示之前进行一些操作;在这种情况下,请在事件处理程序中创建另一个TStringList并传递它,而不是Memo1.Lines。
对于格式正确的ini文件,您可以使用TIniFile.ReadSection或TMemIniFile.ReadSections将所有部分加载到TStrings派生类中,然后使用ReadSection(SectionName)获取每个部分的值。
以下是一个示例-请将此ini文件保存在某个地方(我已经使用了d:\temp\sample.ini:)。
[A Section]
Item1=Item A1
Item2=Item A2
Item3=Item A3
Item4=Item A4

[B Section]
Item1=Item B1
Item2=Item B2
Item3=Item B3
Item4=Item B4

[C Section]
Item1=Item C1
Item2=Item C2
Item3=Item C3
Item4=Item C4

这里是表单代码的示例:
unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, IniFiles;

type
  TForm2 = class(TForm)
    ListBox1: TListBox;
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure ListBox1Click(Sender: TObject);
  private
    { Private declarations }
    FIni: TMemIniFile;
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

const
  IniName = 'd:\Temp\Sample.ini';


procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  FIni.Free;
end;

procedure TForm2.FormCreate(Sender: TObject);
begin
  FIni := TMemIniFile.Create(IniName);
  Memo1.Lines.Clear;
  FIni.ReadSections(ListBox1.Items);
end;

procedure TForm2.ListBox1Click(Sender: TObject);
var
  Section: string;
begin
  if ListBox1.ItemIndex > -1 then
  begin
    Section := ListBox1.Items[ListBox1.ItemIndex];
    FIni.ReadSection(Section, Memo1.Lines);
  end;
end;

end.

ListBox中点击每个部分名称会显示该部分中的项目,如下所示:

Application Screen Capture

编辑:好吧。我很好奇你在问题中发布的ini文件内容是如何工作的。

所以我做了以下更改:

  • 将您的示例ini内容逐字复制并粘贴为新部分,添加到上面创建的Sample.ini文件的末尾。

运行代码,然后单击新的richardmarx项。这是我得到的结果:

Sample image 2

显然,那不起作用。所以我进行了以下额外更改:

  • ListBox1Click事件更改为使用FIni.ReadSectionValues而不是ReadSection
  • 运行修改后的应用程序,单击C Section项以查看其显示方式,然后单击新的richardmarx项以查看其显示方式。结果如下:

Image with C Section selectedImage with richardmarx selected


想知道当部分中有更多具有多行的键或仅有多个键之一具有多行(并且该键是第一个、最后一个或位于中间某处)时会发生什么。 - Marjan Venema
当然。但是使用格式错误的ini文件的人最好自己解决这个问题,不是吗? :) 我已经给了一个良好的开端 - 剩下的就交给@Craig了。 - Ken White

4

预处理.ini文件!将]和[之间的所有换行符更改为某个在文件名中永远不会出现的字符(如星号)。然后使用TInifile访问刚刚预处理的文件,在检索字符串后将星号更改回换行符。(使用StringReplace

如果一个部分中有多个标识符,则比这更复杂。在这种情况下,您可以使用等号作为标志,表示前面的换行符不应被删除。也许您可以从末尾向前读取文件。

您甚至可以创建一个TIniFile的派生类,为您执行星号到换行符的更改。

不,这当然不是一种优雅的解决方案。但是,如果你被卡住了,有时候像这样的蛮力方法是有效的!这里的其他解决方案可能更好,但我想分享这个,以防它给您提供了一个思考方向...


@DavidHeffernan,如果有人在记事本中打开文件,chr(0)可能很难可视化。chr(9)(= tab)可能是更好的选择。 - LU RD
@LURD 这个答案建议使用进程内部的特殊字符,而不是磁盘。 - David Heffernan
@DavidHeffernan,太晚看到了。不过,在字符串中放置chr(0)仍然会让我感到毛骨悚然 :) - LU RD

2
这段代码展示了如何使用INI文件编写和读取多行文本。
procedure TForm1.SaveButtonClick(Sender: TObject);
begin
  IniFile.WriteString('Name', 'FirtName', FirstName.Text);
  IniFile.WriteString('Name', 'LastName', LastName.Text);
  IniFile.WriteInteger('Alpha Blend', 'Alpha Blend Value', Form1.AlphaBlendValue);
//Here start save Memo Lines
  LinesCount := AboutMemo.Lines.Count;
  IniFile.WriteInteger('About', 'Lines Count', LinesCount);
  for I := 0 to LinesCount-1 do
  IniFile.WriteString('About', 'About'+IntToStr(I), AboutMemo.Lines[i]);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin;
  GetCurrentDir;
  IniFile := TIniFile.Create(ChangeFileExt(Application.ExeName, '.ini'));
  FirstName.Text := IniFile.ReadString('Name', 'FirstName', '');
  LastName.Text := IniFile.ReadString('Name', 'LastName', '');
  Form1.AlphaBlendValue := IniFile.ReadInteger('Alpha Blend', 'Alpha Blend Value', 255);
//Here Start Read Memo Lins From INI File
  LinesCount := IniFile.ReadInteger('About', 'Lines Count', 0);
  for I := 0 to LinesCount-1 do
  AboutMemo.Lines.Insert(I, IniFile.ReadString('About', 'About'+IntToStr(I), ''));
end;
end.

2
使用任何TStrings派生对象,您只需使用CommaText属性将所有行作为单个字符串读取和写入即可。
MyStrings.CommaText := IniFile.ReadString('Section', 'Ident');

并且
IniFile.WriteString('Section', 'Ident', MyStrings.CommaText);

CommaText 足够智能,可以自动将包含逗号的行用引号括起来处理。


我回顾了原始问题的示例数据,但我没有看到任何逗号。你看到了我没看到的东西吗? - Ken White

1

我想要类似的东西,将自动邮件的正文包含在ini文件中。
但我也希望正文中有空行。这是ini文件的一部分

[EmailBody]
This is the weekly Survey Status notification email.
.
It lists Survey Reports that may need some attention.
.
The purpose of this information is to improve conduct-of-operations in our tracking, 
recordkeeping, and maintenance of Survey Reports.  This report will also enable 
us to identify any changes made to electronic Surveys following the normal QA review.
.
Please review your information weekly and determine if you need to take corrective action.

这是我最终做的事情

var
  Config: TMemIniFile;
  sl: TStringList;
...
  sl := TStringList.Create;
  Config.ReadSectionValues('EmailBody', sl);
  for i := 0 to sl.Count - 1 do
    if sl[i] = '.' then
      sl[i] := '';

1

@Ken White

FIni.ReadSectionValues 获取相同键之后的完整字符串,例如 "key=value"。

.ini 文件中如下:

[B Section]
Item1=Item B1
Item B2
Item B3
Item4=Item B4

之后

FIni.ReadSectionValues('B Section',Memo1.Lines);

Memo1.Lines 将会是 {'Item1=Item B1', 'Item4=Item B4'}


很遗憾,我们无法直接读取值,没有任何键。 但实际上,这并不太困难:

procedure TForm2.FormCreate(Sender: TObject);
var i:longint;
strtemp: TStringList;
begin
  FIni := TMemIniFile.Create(IniName);
  Memo1.Lines.Clear;
  Fini.ReadSection('B Section',strtemp);
for i:=0 to strtemp.Count-1 do
  Memo1.Items.Add(Fini.ReadString('B Section',strtemp[i],''));
end;

Memo1.Lines 将是 {'Item B1', 'Item B4'}

Upper 方法将读取部分中的所有值,而不需要键。 如何读取非键值 - 我不知道(如果在 .ini 中可能的话)。 作为巨大非格式化 .ini 的解决方案,只需对所有字符串进行编号 - 像文本文件一样读取文件,找到所需的部分并添加相同的计数索引作为键,示意图如下:

FileString[i]:=inttostr(i)+'='+FileString[i];

如果需要混合格式,则需要更复杂的解析器。
附言:抱歉我的英语不太好。

0

您可以使用ReadSections方法获取ini文件中的所有部分名称。

也许您可以使用TMemIniFileReadSectionValues方法来读取“多行值”(即将整个部分读入stringlist)。如果这不起作用,那么也许您可以使用GetStrings来获取ini文件的内容,并“半手动”解析它 - 我记得它会返回一个stringlist,其中包含每个项对象都持有另一个stringlist,其中包含部分数据。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接