Delphi like Format

If someone need it here is a small class mimic the Delphi Format:

namespace DelphiFormat;

interface
uses RemObjects.Elements.RTL;

type
  Formater = class
  private
    fValues : array of Object;
    fBuffer : String;
    fStart : Integer;
    fPos : Integer;
    fToken : String;
    fPart : String;
    fPartNum : Integer;
    fWidth : Integer;
    fPrec  : Integer;
    fLeft : Boolean;

    method raiseError;
    method fillRules(const value : not nullable String);
    method evalParam(const value : not nullable String) : String;

    method skipUntilParamEnd : Boolean;
    method skipUntilParam : Boolean;
    method Parse : String;

   
    method asUint64(o: Object): UInt64;
    method ToSmallestIntAsString(o : Object) : String;
    method ToSmallestUIntAsString(o : Object) : String;
  protected
    constructor(const value : String; const values : array of Object);
  public
    class method Format(const value : String; const values : array of Object) : String;
  end;

implementation

method Formater.raiseError;
begin
  raise new FormatException;
end;


method Formater.fillRules(const value: not nullable String);
begin
  var lmax := value.Length-1;
  var lpos : Integer := 0;
  loop  begin
    // If it is a Number it can be the paramnum or the Width
    if  Char.IsNumber(value[lpos]) then begin
      var lStart := lpos;
      while  (lpos ≤ lmax) and  Char.IsNumber(value[lpos]) do inc(lpos);
      if lpos <> lStart then begin
        var firstArg := Convert.ToInt32(value.Substring(lStart, (lpos-lStart)));
        if   (lpos ≤ lmax) and (value[lpos] = ':') then begin
          fPartNum := firstArg;
          inc(lpos);
        end
        else
          fWidth := firstArg;
      end;
    end;
    if lpos > lmax then break;
  // Paramnum?
    if value[lpos] = ':' then
    begin
      fPartNum := 0;
      inc(lpos);
    end;
    if lpos > lmax then break;
  // Left?
    if value[lpos] = '-' then begin
      fLeft := True;
      inc(lpos);
    end
    else
      fLeft := False;

    if lpos > lmax then break;

    if value[lpos] = '*' then begin
      fWidth := -2;
      inc(lpos);
    end
    else
      if Char.IsNumber(value[lpos])  then begin
        var lStart := lpos;
        while  (lpos ≤ lmax) and  Char.IsNumber(value[lpos]) do inc(lpos);
        if lpos <> lStart then begin
          fWidth := Convert.ToInt32(value.Substring(lStart, lpos-lStart));
        end;
      end;

    if lpos > lmax then break;
    if value[lpos] = '.' then begin
      inc(lpos);
      if lpos > lmax then break;

      if value[lpos] = '*' then begin
        fPrec := -2;
        inc(lpos);
      end
      else begin
        var lStart := lpos;
        while  (lpos ≤ lmax) and  Char.IsNumber(value[lpos]) do inc(lpos);
        if lpos <> lStart then
          fPrec := Convert.ToInt32(value.Substring(lStart, lpos-lStart));
      end;
    end;
    break;
  end;

end;

method Formater.evalParam(const value: not nullable String): String;
begin
  if value.Length < 2 then raiseError;
  // type of Param
  var temp := value[value.Length-1].ToLower;

  fWidth := -1;
  fPrec := -1;
  if value.Length > 2 then
    fillRules(value.Substring(1, value.Length-2));


   // Now check the Rules for with and prec...
   // They will be set to -2 if we need the param
  if fWidth = -2 then begin
    fWidth := Integer(fValues[fPartNum]);
    inc(fPartNum);
  end;

  if fPrec = -2 then begin
    fPrec := Integer(fValues[fPartNum]);
    inc(fPartNum);
  end;


  case temp of
    'd' : begin
      result :=  ToSmallestIntAsString(fValues[fPartNum]);//.ToString;
    end;
    'u' : begin
      result :=    ToSmallestUIntAsString(fValues[fPartNum]);
    end;
    'x' : begin
      result := Convert.ToHexString(asUint64(fValues[fPartNum]));
    end;
    'p' :  begin
      result := Convert.ToHexString(asUint64(fValues[fPartNum]), sizeOf(NativeInt) * 2);
    end;
    'e', 'f','g','n','m' :
    begin
      var d:= fValues[fPartNum] as Double;
        // Convert.ToDouble(fValues[fPartNum]);
      if fPrec > 0 then
        result :=  Convert.ToString(d, fPrec, Locale.Current)
      else
        result := d.ToString;
    end;
    else result := fValues[fPartNum].ToString;
  end; // Case

  if (fWidth > 0) and (result.Length < fWidth) then
  begin
    if fLeft then
      result := String.Format("{0,-"+fWidth.ToString+"}",result) else
      result := String.Format("{0, "+fWidth.ToString+"}",result);
  end;

end;

method Formater.skipUntilParamEnd: boolean;
begin
  result := false;
  var lPos := fPos;
  inc(fPartNum);
  while fBuffer[lPos] <> #0 do
  begin
    case fBuffer[lPos].ToLower  of
      'a'..'z' : begin
          inc(lPos);
          fPart :=  evalParam(fBuffer.Substring(fPos, lPos-fPos));
          result := true;
          break;
      end;
      else
        inc(lPos);
    end;
  end;
  fPos := lPos;
end;

method Formater.skipUntilParam: Boolean;
begin
  fToken := nil;
  fPart := nil;
  while (fBuffer[fPos] <> #0) do
  begin
    case fBuffer[fPos]  of
      '%' : begin
          if fBuffer[fPos+1] = '%' then
            inc(fPos)
          else
          begin
            fToken := fBuffer.Substring(fStart, fPos-fStart);
            if skipUntilParamEnd then
              exit true
            else
              exit false;
          end;
      end;
      else
        inc(fPos);
    end;
  end;
  // Maybe there is some Text
  if fStart < (fPos) then
  begin
    fToken := fBuffer.Substring(fStart, (fPos) -fStart);
    exit true
  end;
  exit false;
end;

method Formater.Parse: String;
begin
  fStart := 0;
  fPos := 0;
  fPartNum := -1;
  var Builder := new StringBuilder;
  while skipUntilParam do
  begin
    if not String.IsNullOrEmpty(fToken) then
      Builder.Append(fToken);
    if not String.IsNullOrEmpty(fPart) then
      Builder.Append(fPart);
    fStart := fPos;
  end;
  if Builder.Length > 0 then
    result := Builder.ToString
  else result := nil;
end;

constructor Formater(const value: String; const values: array of Object);
begin
  inherited constructor();

  fBuffer := value+#0;
  fValues := values;
end;

class method Formater.Format(const value: String; const values: array of Object): String;
begin
  var Lformater := new Formater(value, values);
  result := Lformater.Parse;
end;

method Formater.asUint64(o: Object): UInt64;
begin
  if o is SByte then exit SByte(o);
  if o is Int16 then exit Int16(o);
  if o is Int32 then exit Int32(o);
  if o is Int64 then exit Int64(o);
  if o is Byte then exit Byte(o);
  if o is UInt16 then exit UInt16(o);
  if o is UInt32 then exit UInt32(o);
  if o is UInt64 then exit UInt64(o);
  raise new ArgumentException('Unknown type for o');

end;

method Formater.ToSmallestIntAsString(o: Object): String;
begin
  if o is SByte then exit SByte(o).ToString;
  if o is Int16 then exit Int16(o).ToString;
  if o is Int32 then exit Int32(o).ToString;
  if o is Int64 then exit Int64(o).ToString;
  if o is Byte then exit Byte(o).ToString;
  if o is UInt16 then exit UInt16(o).ToString;
  if o is UInt32 then exit UInt32(o).ToString;
  if o is UInt64 then exit UInt64(o).ToString;
  raise new ArgumentException('Unknown type for o');
end;

method Formater.ToSmallestUIntAsString(o: Object): String;
begin
  if o = nil then raise new ArgumentNullException('o is null');
  if o is SByte then exit Byte(SByte(o)).ToString;
  if o is Int16 then exit UInt16(Int16(o)).ToString;
  if o is Int32 then exit UInt32(Int32(o)).ToString;
  if o is Int64 then exit UInt64(Int64(o)).ToString;
  if o is Byte then exit Byte(o).ToString;
  if o is UInt16 then exit UInt16(o).ToString;
  if o is UInt32 then exit UInt32(o).ToString;
  if o is UInt64 then exit UInt64(o).ToString;
  raise new ArgumentException('Unknown type for o');
end;

end.

Test:

class method Main(args: array of String): Int32;
     begin
      writeLn(Formater.Format('Integer  %20d',[100]));
      writeLn(Formater.Format('UINT     %20U',[-100]));
      writeLn(Formater.Format('HEX      %20x',[1024]));
      writeLn(Formater.Format('HEX      %20x',[-1]));
      var li : UInt32 := Integer(-1);
  writeLn(Formater.Format('UINT %20U',[li]));
  var li2 : UInt64 := UInt64(li) *3;
  writeLn(Formater.Format('INT64     %20D',[li2]));
  writeLn(Formater.Format('UINT64 Neg %20U',[-li2]));
  writeLn(Formater.Format('This is a String: %1:s',['First', 'Second']));
  writeLn(Formater.Format('This is a String: %:20s und das: %s',['Test','Test2']));
  writeLn(Formater.Format('This is a String: %:-20s und das: %s',['Test','Test2']));
  writeLn(Formater.Format('String: %s Integer: %d',['Test',1]));

  writeLn(Formater.Format('Float: %8.2fT Integer: %d',[9.123456,1]));
  writeLn(Formater.Format('Float: %-*.*g Integer: %d',[8,4,993027.123456,1]));
  writeLn(Formater.Format('Float: %g T',[0.1234567893245767]));
  {$IF NOT ECHOES}
     var i := 200;
     writeLn(Formater.Format('Pointer: %p',[NativeUInt (@i)])); 
  {$ENDIF}
end;

end;

1 Like

Don’t we have that in Delphi RTL? if not, permission to add? @Diego_Navarro

only partially and for sure, it is now public…

1 Like

Thanks, logged as bugs://83032

@FritzW are you on the payroll yet? :slight_smile:

1 Like

I don’t think so… :grin: