Home > Delphi, Object Pascal > Inifiles with markup support

Inifiles with markup support

INIInifiles are boring, stiff and bordering on jurrasic park technology-wise; Yet most Delphi developers end up with TInifile for both preferences and language medium. Inifiles may be simple, but they are effective and to the point.

But can we improve on the formula? Let’s jump into the nearest Tardis and fly back around 13 years, thats when I wrote this class (and I still use it to this day).

Markup?

The code below let’s you define key/value pairs as normal, but also re-use them as parts of other key/value pairs. This makes it so much easier to write language-files, especially long strings that are 90% identical.

How do i use it?

First, lets look at a small example. Its so simple that it requires only seconds to get it, yet i find it useful in it’s simplicity:

[database]
server1=192.0.2.62
server2=192.0.2.63
server3=192.0.2.64
server4=192.0.2.65
port=143
file="payroll.dat"

Notice how the IP addresse are 90% identical? Well, with my little class you can now do this:

[database]
root=192.0.2.
server1={database:root}.62
server2={database:root}.63
server3={database:root}.64
server4={database:root}.65
port=143
file="payroll.dat"

Now you dont save much space, but you gain the ability to define and re-use values within the same ini-file. And language files can grow large quickly, so the more you can re-use segments and phrases, the better the language file will be.

[DISK]
internal_error=An internal error occured,
file_error=A file error occured,
file_write_error = {DISK:file_error} write operation failed (%s)
file_read_error = {DISK:file_error} read operation failed (%s)
file_lock_error = {DISK:file_error} unable to obtain file-lock (%s)

I also added support for CRLF, for this we use the “|” (pipe) symbol. Its also thread safe, which is a bonus.

Enjoy!

  unit jllanguage;

  interface

  uses
  sysutils, classes, inifiles, syncobjs;

  Const
  CNT_JL_LANGUAGEFILE_MACRO_BEGIN = '{';
  CNT_JL_LANGUAGEFILE_MACRO_END   = '}';
  CNT_JL_LANGUAGEFILE_LINEFEED    = '|';
  CNT_JL_LANGUAGEFILE_NAMESTOP    = ':';

  type

  TJLLanguageFile = Class(TComponent)
  Private
    FLock:      TCriticalSection;
    FLanguage:  TInifile;
    FFilename:  TFileName;
  Protected
    Function    GetActive:Boolean;
    Procedure   SetActive(Value:Boolean);
    Procedure   SetFileName(Value:TFileName);
    Function    IniFormat(IniFile:TCustomIniFile;
                Value:String):String;
  Public
    Function    ReadStr(Section,Ident,DefValue:String):String;
    Function    ReadInt(Section,Ident:String;DefValue:Integer):Integer;
    Function    ReadCurr(Section,Ident:String;DefValue:Currency):Currency;
    Function    FormatLangStr(Value:String):String;
    Function    Open(Filename:String):Boolean;
    Function    Close:Boolean;
  Published
    Property    Filename:TFilename read FFilename write SetFileName;
    Property    Enabled:Boolean read GetActive write SetActive;
    Constructor Create(AOwner:TComponent);override;
    Destructor  Destroy;Override;
  End;

  implementation

  //###########################################################################
  // TJLLanguageFile
  //###########################################################################

  Constructor TJLLanguageFile.Create(AOwner:TComponent);
  Begin
    inherited Create(Aowner);
    FLock:=TCriticalSection.Create;
    FLanguage:=NIL;
  end;

  Destructor TJLLanguageFile.Destroy;
  Begin
    FLock.Enter;
    try
      if FLanguage<>NIl then
      FreeAndNIL(FLanguage);
    finally
      FLock.Leave;
    end;

    FreeAndNIL(FLock);
    inherited;
  end;

  Function TJLLanguageFile.GetActive:Boolean;
  Begin
    FLock.Enter;
    try
      result:=assigned(FLanguage);
    finally
      FLock.Leave;
    end;
  end;

  Procedure TJLLanguageFile.SetActive(Value:Boolean);
  Begin
    If Value<>GetActive then
    Begin
      Close;
      if Value then
      Open(FFilename);
    end;
  end;

  Procedure TJLLanguageFile.SetFileName(Value:TFilename);
  Begin
    if (csDesigning in ComponentState) then
    FFilename:=Value else
    Begin
      If not GetActive then
      FFileName:=Value else
      Raise Exception.Create
      ('Filename can not be changed in active component');
    end;
  end;

  Function TJLLanguageFile.IniFormat(IniFile:TCustomIniFile;
           Value:String):String;
  var
    x,y:    Integer;
    FLen:   Integer;
    FEnd:   Integer;
    FName:  String;
    FSec:   String;
  Begin
    Value:=trim(Value);
    Flen:=Length(Value);
    SetLength(result,0);
    If FLen>0 then
    Begin
      x:=0;
      while x<FLen do       Begin         inc(x);                  if Value[x]=CNT_JL_LANGUAGEFILE_MACRO_BEGIN then         Begin           (* locate end of macro *)           FEnd:=0;           for y:=x+1 to FLen do           Begin             if value[y]=CNT_JL_LANGUAGEFILE_MACRO_END then             Begin               FEnd:=y;               Break;             end;           end;           if FEnd>0 then
          Begin

            FName:=Copy(Value,x+1,(y-x)-1);
            x:=y;

            y:=pos(CNT_JL_LANGUAGEFILE_NAMESTOP,FName);
            if y>0 then
            Begin
              FSec:=Copy(FName,1,y-1);
              Delete(FName,1,y);
              result:=result + IniFile.ReadString(FSec,FName,'');
            end else
            result:=result + CNT_JL_LANGUAGEFILE_MACRO_BEGIN
            + FName + CNT_JL_LANGUAGEFILE_MACRO_END;

          end else
          result:=result + Value[x];
        end else
        result:=result + Value[x];
      end;
    end;
  end;

  Function TJLLanguageFile.Open(Filename:String):Boolean;
  Begin
    result:=FileExists(Filename);
    If result then
    Begin
      FLock.Enter;
      try
        result:=False;

        (* release previous language data *)
        if assigned(Flanguage) then
        FreeAndNil(Flanguage);

        try
          Flanguage:=TIniFile.Create(Filename);
          result:=True;
        except
          on exception do
          exit;
        end;

      finally
        FLock.Leave;
      end;
    end;
  end;

  Function TJLLanguageFile.Close:Boolean;
  Begin
    FLock.Enter;
    try
      result:=assigned(Flanguage);
      If result then
      FreeAndNIL(Flanguage);
    finally
      FLock.Leave;
    end;
  end;

  Function TJLLanguageFile.FormatLangStr(Value:String):String;
  Begin
    FLock.Enter;
    try
      If Flanguage<>NIL then
      Begin
        if pos(CNT_JL_LANGUAGEFILE_MACRO_BEGIN,result)>0 then
        result:=IniFormat(Flanguage,result) else
        result:=Value;
      end else
      result:=Value;
    finally
      FLock.Leave;
    end;
  end;

  Function TJLLanguageFile.ReadStr(Section,Ident,DefValue:String):String;
  Begin
    FLock.Enter;
    try
      If Flanguage<>NIL then
      Begin
        result:=Flanguage.ReadString(Section,Ident,DefValue);

        (* add support for extended formating *)
        if pos(CNT_JL_LANGUAGEFILE_MACRO_BEGIN,result)>0 then
        result:=IniFormat(Flanguage,result);

        if pos(CNT_JL_LANGUAGEFILE_LINEFEED,result)>0 then
        result:=StringReplace(result,CNT_JL_LANGUAGEFILE_LINEFEED,
        #13,[rfReplaceAll]);

      end else
      result:=DefValue;
    finally
      FLock.Leave;
    end;
  end;

  Function TJLLanguageFile.ReadInt(Section,Ident:String;
           DefValue:Integer):Integer;
  Begin
    FLock.Enter;
    try
      If Flanguage<>NIL then
      result:=Flanguage.ReadInteger(Section,Ident,DefValue) else
      result:=DefValue;
    finally
      FLock.Leave;
    end;
  end;

  Function TJLLanguageFile.ReadCurr(Section,Ident:String;
           DefValue:Currency):Currency;
  Begin
    FLock.Enter;
    try
      If Flanguage<>NIL then
      result:=FLanguage.ReadFloat(section,ident,defvalue) else
      result:=DefValue;
    finally
      FLock.Leave;
    end;
  end;

  end.

Advertisements
  1. May 26, 2016 at 7:52 pm

    This looks very similar to something I created (and heavily use) several years ago. See http://zarko-gajic.iz.hr/tliveini-custom-delphi-ini-implementation/

    • Jon Lennart Aasenden
      May 30, 2016 at 5:40 am

      Great minds think alike 🙂

  1. No trackbacks yet.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: