diff --git a/.gitattributes b/.gitattributes index 265152dac5..761cffe3e8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1988,6 +1988,7 @@ packages/fcl-base/examples/testcont.pp svneol=native#text/plain packages/fcl-base/examples/testexprpars.pp svneol=native#text/plain packages/fcl-base/examples/testez.pp svneol=native#text/plain packages/fcl-base/examples/testhres.pp svneol=native#text/plain +packages/fcl-base/examples/testini.pp svneol=native#text/plain packages/fcl-base/examples/testipc_client.pp svneol=native#text/plain packages/fcl-base/examples/testipc_server.pp svneol=native#text/plain packages/fcl-base/examples/testmime.pp svneol=native#text/plain diff --git a/packages/fcl-base/examples/README.txt b/packages/fcl-base/examples/README.txt index b90db1da32..2580e9e675 100644 --- a/packages/fcl-base/examples/README.txt +++ b/packages/fcl-base/examples/README.txt @@ -73,3 +73,4 @@ poolmm2.pp Test for pooledmm (nonfree) (VS) testweb.pp Test for fpcgi (MVC) daemon.pp Test for daemonapp (MVC) testtimer.pp Test for TFPTimer (MVC) +testini.pp Test/Demo for inifiles, ReadSectionValues. \ No newline at end of file diff --git a/packages/fcl-base/examples/testini.pp b/packages/fcl-base/examples/testini.pp new file mode 100644 index 0000000000..e3c8268013 --- /dev/null +++ b/packages/fcl-base/examples/testini.pp @@ -0,0 +1,61 @@ +program testini; + +{$mode objfpc}{$H+} + +uses + inifiles, classes; + +var + i: Integer; + ini: TMemIniFile; + lines: TStrings; + +begin + lines:=TStringList.Create(); + try + lines.Add('[main]'); + lines.Add('key_a=1'); + lines.Add(';comment'); + lines.Add('key_b =2'); + lines.Add('not_valid'); + lines.Add('key_c= 3'); + lines.Add('key_d="3"'); + WriteLn('ini file source:'); + for i:=0 to lines.Count-1 do + WriteLn(' ', lines[i]); + ini:=TMemIniFile.Create(''); + try + ini.options:=ini.options+[ifoStripQuotes]; + ini.SetStrings(lines); + lines.Clear(); + ini.ReadSectionValues('main', lines,[]); + WriteLn('ReadSectionValues (no options):'); + for i:=0 to lines.Count-1 do + WriteLn(' ', lines[i]); + lines.Clear(); + ini.ReadSectionValues('main', lines,[svoIncludeComments]); + WriteLn('ReadSectionValues (with comments, no invalid):'); + for i:=0 to lines.Count-1 do + WriteLn(' ', lines[i]); + lines.Clear(); + ini.ReadSectionValues('main', lines,[svoIncludeInvalid]); + WriteLn('ReadSectionValues (without comments, with invalid):'); + for i:=0 to lines.Count-1 do + WriteLn(' ', lines[i]); + lines.Clear(); + ini.ReadSectionValues('main', lines,[svoIncludeComments,svoIncludeInvalid]); + WriteLn('ReadSectionValues (with comments, with invalid):'); + for i:=0 to lines.Count-1 do + WriteLn(' ', lines[i]); + Lines.Clear; + ini.ReadSectionValues('main', lines,[svoIncludeQuotes]); + WriteLn('ReadSectionValues (with quotes):'); + for i:=0 to lines.Count-1 do + WriteLn(' ', lines[i]); + finally + ini.Free(); + end; + finally + lines.Free(); + end +end. diff --git a/packages/fcl-base/src/inifiles.pp b/packages/fcl-base/src/inifiles.pp index 1b37e3828a..8c44c4c68c 100644 --- a/packages/fcl-base/src/inifiles.pp +++ b/packages/fcl-base/src/inifiles.pp @@ -134,19 +134,31 @@ type property Items[Index: integer]: TIniFileSection read GetItem; default; end; + TIniFileOption = (ifoStripComments, // Strip comments when reading file + ifoStripInvalid, // Strip invalid lines when reading file. + ifoEscapeLineFeeds, // Escape linefeeds when reading file. + ifoCaseSensitive, // Use Case sensitive section/key names + ifoStripQuotes, // Strip quotes when reading string values. + ifoFormatSettingsActive); // Use format settings when writing date/float etc. + TIniFileOptions = Set of TIniFileOption; + + TSectionValuesOption = (svoIncludeComments,svoIncludeInvalid, svoIncludeQuotes); + TSectionValuesOptions = set of TSectionValuesOption; + { TCustomIniFile } TCustomIniFile = class Private FFileName: string; + FOptions: TIniFileOptions; FSectionList: TIniFileSectionList; - FEscapeLineFeeds: boolean; - FCaseSensitive : Boolean; - FStripQuotes : Boolean; - FFormatSettingsActive: Boolean; + function GetOption(AIndex: TIniFileOption): Boolean; + procedure SetOption(AIndex: TIniFileOption; AValue: Boolean); + procedure SetOptions(AValue: TIniFileOptions); public FormatSettings: TFormatSettings; - constructor Create(const AFileName: string; AEscapeLineFeeds : Boolean = False); virtual; + constructor Create(const AFileName: string; AOptions : TIniFileOptions = []); virtual; + constructor Create(const AFileName: string; AEscapeLineFeeds : Boolean); virtual; destructor Destroy; override; function SectionExists(const Section: string): Boolean; virtual; function ReadString(const Section, Ident, Default: string): string; virtual; abstract; @@ -169,16 +181,18 @@ type procedure WriteBinaryStream(const Section, Name: string; Value: TStream); virtual; procedure ReadSection(const Section: string; Strings: TStrings); virtual; abstract; procedure ReadSections(Strings: TStrings); virtual; abstract; - procedure ReadSectionValues(const Section: string; Strings: TStrings); virtual; abstract; + procedure ReadSectionValues(const Section: string; Strings: TStrings; Options : TSectionValuesOptions); virtual; overload; + procedure ReadSectionValues(const Section: string; Strings: TStrings); virtual;overload; procedure EraseSection(const Section: string); virtual; abstract; procedure DeleteKey(const Section, Ident: String); virtual; abstract; procedure UpdateFile; virtual; abstract; function ValueExists(const Section, Ident: string): Boolean; virtual; property FileName: string read FFileName; - property EscapeLineFeeds: boolean read FEscapeLineFeeds; - Property CaseSensitive : Boolean Read FCaseSensitive Write FCaseSensitive; - Property StripQuotes : Boolean Read FStripQuotes Write FStripQuotes; - Property FormatSettingsActive: Boolean Read FFormatSettingsActive write FFormatSettingsActive; + Property Options : TIniFileOptions Read FOptions Write SetOptions; + property EscapeLineFeeds: boolean index ifoEscapeLineFeeds Read GetOption ;deprecated 'Use options instead'; + Property CaseSensitive : Boolean index ifoCaseSensitive Read GetOption Write SetOption; deprecated 'Use options instead'; + Property StripQuotes : Boolean index ifoStripQuotes Read GetOption Write SetOption; deprecated 'Use options instead'; + Property FormatSettingsActive : Boolean index ifoFormatSettingsActive Read GetOption Write SetOption;deprecated 'Use options instead'; end; { TIniFile } @@ -197,15 +211,16 @@ type procedure MaybeUpdateFile; property Dirty : Boolean Read FDirty; public - constructor Create(const AFileName: string; AEscapeLineFeeds : Boolean = False); overload; override; - constructor Create(AStream: TStream; AEscapeLineFeeds : Boolean = False); overload; + constructor Create(const AFileName: string; AOptions : TIniFileoptions = []); overload; override; + constructor Create(AStream: TStream; AOptions : TIniFileoptions = []); overload; + constructor Create(AStream: TStream; AEscapeLineFeeds : Boolean); overload; deprecated 'Use Options argument instead'; destructor Destroy; override; function ReadString(const Section, Ident, Default: string): string; override; procedure WriteString(const Section, Ident, Value: String); override; procedure ReadSection(const Section: string; Strings: TStrings); override; procedure ReadSectionRaw(const Section: string; Strings: TStrings); procedure ReadSections(Strings: TStrings); override; - procedure ReadSectionValues(const Section: string; Strings: TStrings); override; + procedure ReadSectionValues(const Section: string; Strings: TStrings; AOptions : TSectionValuesOptions = []); overload; override; procedure EraseSection(const Section: string); override; procedure DeleteKey(const Section, Ident: String); override; procedure UpdateFile; override; @@ -510,11 +525,34 @@ end; { TCustomIniFile } -constructor TCustomIniFile.Create(const AFileName: string; AEscapeLineFeeds : Boolean = False); + +function TCustomIniFile.GetOption(AIndex: TIniFileOption): Boolean; +begin + Result:=AIndex in FOptions; +end; + + +procedure TCustomIniFile.SetOption(AIndex: TIniFileOption; AValue: Boolean); +begin + if AIndex in [ifoStripComments,ifoStripInvalid] then + Raise Exception.Create('Flags ifoStripComments or ifoStripInvalid must be set/unset in the constructor'); + if AValue then + Include(FOptions,AIndex) + else + Exclude(FOptions,AIndex) +end; + +procedure TCustomIniFile.SetOptions(AValue: TIniFileOptions); +begin + if FOptions=AValue then Exit; + FOptions:=AValue; +end; + +constructor TCustomIniFile.Create(const AFileName: string; AOptions : TIniFileOptions = []); begin FFileName := AFileName; FSectionList := TIniFileSectionList.Create; - FEscapeLineFeeds := AEscapeLineFeeds; + FOptions:=AOptions; FormatSettings := DefaultFormatSettings; with FormatSettings do begin DecimalSeparator := '.'; @@ -528,6 +566,15 @@ begin end; end; +constructor TCustomIniFile.Create(const AFileName: string; + AEscapeLineFeeds: Boolean); +begin + if AEscapeLineFeeds then + Create(AFileName,[ifoEscapeLineFeeds]) + else + Create(AFileName,[]) +end; + destructor TCustomIniFile.Destroy; begin FSectionList.Free; @@ -584,7 +631,7 @@ end; function TCustomIniFile.ReadDate(const Section, Ident: string; Default: TDateTime): TDateTime; begin - if FFormatSettingsActive then begin + if FormatSettingsActive then begin if not TryStrToDate(ReadString(Section, Ident, ''), Result, FormatSettings) then Result := Default; end else @@ -594,7 +641,7 @@ end; function TCustomIniFile.ReadDateTime(const Section, Ident: string; Default: TDateTime): TDateTime; begin - if FFormatSettingsActive then begin + if FormatSettingsActive then begin if not TryStrToDateTime(ReadString(Section, Ident, ''), Result, FormatSettings) then Result := Default; end else @@ -604,7 +651,7 @@ end; function TCustomIniFile.ReadFloat(const Section, Ident: string; Default: Double): Double; begin - if FFormatSettingsActive then + if FormatSettingsActive then Result:=StrToFloatDef(ReadString(Section, Ident, ''),Default, FormatSettings) else Result:=StrToFloatDef(ReadString(Section, Ident, ''),Default); @@ -613,7 +660,7 @@ end; function TCustomIniFile.ReadTime(const Section, Ident: string; Default: TDateTime): TDateTime; begin - if FFormatSettingsActive then + if FormatSettingsActive then Result := StrToTimeDef(ReadString(Section, Ident, ''),Default, FormatSettings.TimeSeparator) else Result := StrToTimeDef(ReadString(Section, Ident, ''),Default); @@ -621,7 +668,7 @@ end; procedure TCustomIniFile.WriteDate(const Section, Ident: string; Value: TDateTime); begin - if FFormatSettingsActive then + if FormatSettingsActive then WriteString(Section, Ident, DateToStr(Value, FormatSettings)) else WriteString(Section, Ident, DateToStr(Value)); @@ -629,7 +676,7 @@ end; procedure TCustomIniFile.WriteDateTime(const Section, Ident: string; Value: TDateTime); begin - if FFormatSettingsActive then + if FormatSettingsActive then WriteString(Section, Ident, DateTimeToStr(Value, FormatSettings)) else WriteString(Section, Ident, DateTimeToStr(Value)); @@ -637,7 +684,7 @@ end; procedure TCustomIniFile.WriteFloat(const Section, Ident: string; Value: Double); begin - if FFormatSettingsActive then + if FormatSettingsActive then WriteString(Section, Ident, FloatToStr(Value, FormatSettings)) else WriteString(Section, Ident, FloatToStr(Value)); @@ -645,7 +692,7 @@ end; procedure TCustomIniFile.WriteTime(const Section, Ident: string; Value: TDateTime); begin - if FFormatSettingsActive then + if FormatSettingsActive then WriteString(Section, Ident, TimeToStr(Value, FormatSettings)) else WriteString(Section, Ident, TimeToStr(Value)); @@ -698,7 +745,8 @@ begin end; end; -procedure TCustomInifile.WriteBinaryStream(const Section, Name: string; Value: TStream); +procedure TCustomIniFile.WriteBinaryStream(const Section, Name: string; + Value: TStream); Var @@ -733,16 +781,54 @@ begin end; end; +procedure TCustomIniFile.ReadSectionValues(const Section: string; Strings: TStrings; Options: TSectionValuesOptions); + +type + TOldSectionValues = Procedure (const Section: string; Strings: TStrings) of object; + +var + CurrSV, + TCustomSV: TOldSectionValues; + CurrClass : TClass; + +begin + if (Options<>[]) then + Raise Exception.Create('Options not supported, options must be empty'); + // Redirect calls to old implementation, if it is overridden. + CurrSV:=nil; + CurrClass:=Classtype; + while (CurrClass<>nil) and (CurrClass<>TCustomIniFile) do + CurrClass:=CurrClass.Classparent; + if CurrClass<>nil then + begin + CurrSV:=@Self.ReadSectionValues; + TCustomSV:=@TCustomIniFile(@CurrClass).ReadSectionValues; + if TMethod(TCustomSV).Code=TMethod(CurrSV).Code then + CurrSV:=nil; + end; + if Assigned(CurrSV) then + ReadSectionValues(Section,Strings) + else + Raise Exception.Create('ReadSectionValues not overridden'); +end; + +procedure TCustomIniFile.ReadSectionValues(const Section: string; + Strings: TStrings); +begin + ReadSectionValues(Section,Strings,[]); +end; + { TIniFile } -constructor TIniFile.Create(const AFileName: string; AEscapeLineFeeds : Boolean = False); + +constructor TIniFile.Create(const AFileName: string; AOptions : TIniFileOptions = []); var slLines: TStringList; begin FBOM := ''; If Not (self is TMemIniFile) then StripQuotes:=True; - inherited Create(AFileName,AEscapeLineFeeds); + inherited Create(AFileName,AOptions); FStream := nil; slLines := TStringList.Create; try @@ -757,12 +843,23 @@ begin end; end; -constructor TIniFile.Create(AStream: TStream; AEscapeLineFeeds : Boolean = False); +constructor TIniFile.Create(AStream: TStream; AEscapeLineFeeds : Boolean); + +begin + if AEscapeLineFeeds then + Create(AStream,[ifoEscapeLineFeeds]) + else + Create(AStream,[]); +end; + +constructor TIniFile.Create(AStream: TStream; AOptions : TIniFileOptions = []); + var slLines: TStringList; + begin FBOM := ''; - inherited Create('',AEscapeLineFeeds); + inherited Create('',AOptions); FStream := AStream; slLines := TStringList.Create; try @@ -818,10 +915,13 @@ var end; end; +Var + addKey : Boolean; + begin oSection := nil; FSectionList.Clear; - if FEscapeLineFeeds then + if EscapeLineFeeds then RemoveBackslashes; if (AStrings.Count > 0) and (copy(AStrings.Strings[0],1,Length(Utf8Bom)) = Utf8Bom) then begin @@ -832,37 +932,51 @@ begin sLine := Trim(AStrings[i]); if sLine > '' then begin - if IsComment(sLine) and (oSection = nil) then begin + if IsComment(sLine) and (oSection = nil) then + begin // comment at the beginning of the ini file - oSection := TIniFileSection.Create(sLine); - FSectionList.Add(oSection); + if Not (ifoStripComments in Options) then + begin + oSection := TIniFileSection.Create(sLine); + FSectionList.Add(oSection); + end; continue; - end; - if (Copy(sLine, 1, 1) = Brackets[0]) and (Copy(sLine, length(sLine), 1) = Brackets[1]) then begin + end; + if (Copy(sLine, 1, 1) = Brackets[0]) and (Copy(sLine, length(sLine), 1) = Brackets[1]) then + begin // regular section oSection := TIniFileSection.Create(Copy(sLine, 2, Length(sLine) - 2)); FSectionList.Add(oSection); - end else if oSection <> nil then begin - if IsComment(sLine) then begin + end + else if oSection <> nil then + begin + if IsComment(sLine) then + begin + AddKey:=Not (ifoStripComments in Options); // comment within a section sIdent := sLine; sValue := ''; - end else begin + end + else + begin // regular key j:=Pos(Separator, sLine); if j=0 then begin - sIdent:=''; - sValue:=sLine + AddKey:=Not (ifoStripInvalid in Options); + sIdent:=''; + sValue:=sLine end else begin - sIdent:=Trim(Copy(sLine, 1, j - 1)); - sValue:=Trim(Copy(sLine, j + 1, Length(sLine) - j)); + AddKey:=True; + sIdent:=Trim(Copy(sLine, 1, j - 1)); + sValue:=Trim(Copy(sLine, j + 1, Length(sLine) - j)); end; end; - oSection.KeyList.Add(TIniFileKey.Create(sIdent, sValue)); - end; + if AddKey then + oSection.KeyList.Add(TIniFileKey.Create(sIdent, sValue)); + end; end; end; end; @@ -982,31 +1096,49 @@ begin end; end; -procedure TIniFile.ReadSectionValues(const Section: string; Strings: TStrings); +procedure TIniFile.ReadSectionValues(const Section: string; Strings: TStrings; AOptions : TSectionValuesOptions = []); var oSection: TIniFileSection; s: string; i,J: integer; + KeyIsComment,IncludeComments,IncludeInvalid,DoStripQuotes : boolean; + K : TIniFileKey; + begin + IncludeComments:=(svoIncludeComments in AOptions) Or (ifoStripComments in Options); + IncludeInvalid:=(svoIncludeInvalid in AOptions) Or (ifoStripInvalid in Options); + DoStripQuotes:=StripQuotes and Not (svoIncludeQuotes in AOptions); Strings.BeginUpdate; try Strings.Clear; oSection := FSectionList.SectionByName(Section,CaseSensitive); - if oSection <> nil then with oSection.KeyList do - for i := 0 to Count-1 do begin - s := Items[i].Value; - If StripQuotes then + if oSection = nil then + Exit; + for i := 0 to oSection.KeyList.Count-1 do + begin + K:=oSection.KeyList.Items[i]; + if IncludeInvalid or (K.Ident<>'') then begin - J:=Length(s); - // Joost, 2-jan-2007: The check (J>1) is there for the case that - // the value consist of a single double-quote character. (see - // mantis bug 6555) - If (J>1) and ((s[1] in ['"','''']) and (s[J]=s[1])) then - s:=Copy(s,2,J-2); + s := K.Value; + KeyIsComment:=IsComment(K.Ident); + if IncludeComments Or Not KeyIsComment then + begin + If DoStripQuotes then + begin + J:=Length(s); + // Joost, 2-jan-2007: The check (J>1) is there for the case that + // the value consist of a single double-quote character. (see + // mantis bug 6555) + If (J>1) and ((s[1] in ['"','''']) and (s[J]=s[1])) then + s:=Copy(s,2,J-2); + end; + if KeyIsComment then + S:=K.Ident + else if k.ident<>'' then + s:=K.Ident+Separator+s; + Strings.Add(s); + end; end; - if Items[i].Ident<>'' then - s:=Items[i].Ident+Separator+s; - Strings.Add(s); end; finally Strings.EndUpdate;