2 {*****************************************************************************}
4 { Tnt Delphi Unicode Controls }
5 { http://www.tntware.com/delphicontrols/unicode/ }
8 { Copyright (c) 2002-2007, Troy Wolbrink (troy.wolbrink@tntware.com) }
10 {*****************************************************************************}
12 unit TntFormatStrUtils
;
14 {$INCLUDE TntCompilers.inc}
18 // this unit provides functions to work with format strings
23 function GetCanonicalFormatStr(const _FormatString
: WideString
): WideString
;
24 {$IFNDEF COMPILER_9_UP}
25 function ReplaceFloatingArgumentsInFormatString(const _FormatString
: WideString
;
26 const Args
: array of const
27 {$IFDEF COMPILER_7_UP}; FormatSettings
: PFormatSettings
{$ENDIF}): WideString
;
29 procedure CompareFormatStrings(FormatStr1
, FormatStr2
: WideString
);
30 function FormatStringsAreCompatible(FormatStr1
, FormatStr2
: WideString
): Boolean;
33 EFormatSpecError
= class(ETntGeneralError
);
38 SysUtils
, Math
, TntClasses
;
41 SInvalidFormatSpecifier
= 'Invalid Format Specifier: %s';
42 SMismatchedArgumentTypes
= 'Argument types for index %d do not match. (%s <> %s)';
43 SMismatchedArgumentCounts
= 'Number of format specifiers do not match.';
46 TFormatSpecifierType
= (fstInteger
, fstFloating
, fstPointer
, fstString
);
48 function GetFormatSpecifierType(const FormatSpecifier
: WideString
): TFormatSpecifierType
;
52 LastChar
:= TntWideLastChar(FormatSpecifier
);
54 'd', 'D', 'u', 'U', 'x', 'X':
56 'e', 'E', 'f', 'F', 'g', 'G', 'n', 'N', 'm', 'M':
57 result
:= fstFloating
;
63 raise ETntInternalError
.CreateFmt('Internal Error: Unexpected format type (%s)', [LastChar
]);
68 TFormatStrParser
= class(TObject
)
70 ParsedString
: TBufferedWideString
;
71 PFormatString
: PWideChar
;
73 ExplicitCount
: Integer;
74 ImplicitCount
: Integer;
75 procedure RaiseInvalidFormatSpecifier
;
76 function ParseChar(c
: WideChar
): Boolean;
77 procedure ForceParseChar(c
: WideChar
);
78 function ParseDigit
: Boolean;
79 function ParseInteger
: Boolean;
80 procedure ForceParseType
;
81 function PeekDigit
: Boolean;
82 function PeekIndexSpecifier(out Index
: Integer): Boolean;
84 constructor Create(const _FormatString
: WideString
);
85 destructor Destroy
; override;
86 function ParseFormatSpecifier
: Boolean;
89 constructor TFormatStrParser
.Create(const _FormatString
: WideString
);
92 PFormatString
:= PWideChar(_FormatString
);
96 ParsedString
:= TBufferedWideString
.Create
;
99 destructor TFormatStrParser
.Destroy
;
101 FreeAndNil(ParsedString
);
105 procedure TFormatStrParser
.RaiseInvalidFormatSpecifier
;
107 raise EFormatSpecError
.CreateFmt(SInvalidFormatSpecifier
, [ParsedString
.Value
+ PFormatString
]);
110 function TFormatStrParser
.ParseChar(c
: WideChar
): Boolean;
113 if PFormatString
^ = c
then begin
115 ParsedString
.AddChar(c
);
120 procedure TFormatStrParser
.ForceParseChar(c
: WideChar
);
122 if not ParseChar(c
) then
123 RaiseInvalidFormatSpecifier
;
126 function TFormatStrParser
.PeekDigit
: Boolean;
129 if (PFormatString
^ <> #0)
130 and (PFormatString
^ >= '0')
131 and (PFormatString
^ <= '9') then
135 function TFormatStrParser
.ParseDigit
: Boolean;
138 if PeekDigit
then begin
140 ForceParseChar(PFormatString
^);
144 function TFormatStrParser
.ParseInteger
: Boolean;
151 While ParseDigit
do begin
154 result
:= (digitcount
> 0);
155 if digitcount
> MAX_INT_DIGITS
then
156 RaiseInvalidFormatSpecifier
;
159 procedure TFormatStrParser
.ForceParseType
;
161 if PFormatString
^ = #0 then
162 RaiseInvalidFormatSpecifier
;
164 case PFormatString
^ of
165 'd', 'u', 'x', 'e', 'f', 'g', 'n', 'm', 'p', 's',
166 'D', 'U', 'X', 'E', 'F', 'G', 'N', 'M', 'P', 'S':
171 RaiseInvalidFormatSpecifier
;
173 ForceParseChar(PFormatString
^);
176 function TFormatStrParser
.PeekIndexSpecifier(out Index
: Integer): Boolean;
178 SaveParsedString
: WideString
;
179 SaveFormatString
: PWideChar
;
181 SaveParsedString
:= ParsedString
.Value
;
182 SaveFormatString
:= PFormatString
;
187 if ParseInteger
then begin
188 Index
:= StrToInt(ParsedString
.Value
);
189 if ParseChar(':') then
194 ParsedString
.AddString(SaveParsedString
);
195 PFormatString
:= SaveFormatString
;
199 function TFormatStrParser
.ParseFormatSpecifier
: Boolean;
201 ExplicitIndex
: Integer;
204 // Parse entire format specifier
206 if (PFormatString
^ <> #0)
207 and (not ParseChar(' '))
208 and (not ParseChar('%')) then begin
209 if PeekIndexSpecifier(ExplicitIndex
) then begin
211 LastIndex
:= Max(LastIndex
, ExplicitIndex
);
215 ParsedString
.AddString(IntToStr(LastIndex
));
216 ParsedString
.AddChar(':');
218 if ParseChar('*') then
223 end else if ParseInteger
then
226 if ParseChar('*') then begin
231 if ParseChar('.') then begin
232 if not ParseChar('*') then
240 //-----------------------------------
242 function GetCanonicalFormatStr(const _FormatString
: WideString
): WideString
;
246 with TFormatStrParser
.Create(_FormatString
) do
248 // loop until no more '%'
249 PosSpec
:= Pos('%', PFormatString
);
250 While PosSpec
<> 0 do begin
252 // delete everything up until '%'
253 ParsedString
.AddBuffer(PFormatString
, PosSpec
- 1);
254 Inc(PFormatString
, PosSpec
- 1);
255 // parse format specifier
256 ParseFormatSpecifier
;
258 PosSpec
:= Pos('%', PFormatString
);
261 if ((ExplicitCount
= 0) and (ImplicitCount
= 1)) {simple expression}
262 or ((ExplicitCount
> 0) and (ImplicitCount
= 0)) {nothing converted} then
263 result
:= _FormatString
{original}
265 result
:= ParsedString
.Value
+ PFormatString
;
271 {$IFNDEF COMPILER_9_UP}
272 function ReplaceFloatingArgumentsInFormatString(const _FormatString
: WideString
;
273 const Args
: array of const
274 {$IFDEF COMPILER_7_UP}; FormatSettings
: PFormatSettings
{$ENDIF}): WideString
;
275 { This function replaces floating point format specifiers with their actual formatted values.
276 It also adds index specifiers so that the other format specifiers don't lose their place.
277 The reason for this is that WideFormat doesn't correctly format floating point specifiers.
280 Parser
: TFormatStrParser
;
282 Output
: TBufferedWideString
;
284 Output
:= TBufferedWideString
.Create
;
286 Parser
:= TFormatStrParser
.Create(_FormatString
);
289 // loop until no more '%'
290 PosSpec
:= Pos('%', PFormatString
);
291 While PosSpec
<> 0 do begin
293 // delete everything up until '%'
294 Output
.AddBuffer(PFormatString
, PosSpec
- 1);
295 Inc(PFormatString
, PosSpec
- 1);
296 // parse format specifier
298 if (not ParseFormatSpecifier
)
299 or (GetFormatSpecifierType(ParsedString
.Value
) <> fstFloating
) then
300 Output
.AddBuffer(ParsedString
.BuffPtr
, MaxInt
)
301 {$IFDEF COMPILER_7_UP}
302 else if Assigned(FormatSettings
) then
303 Output
.AddString(Format
{TNT-ALLOW Format}(ParsedString
.Value
, Args
, FormatSettings
^))
306 Output
.AddString(Format
{TNT-ALLOW Format}(ParsedString
.Value
, Args
));
308 PosSpec
:= Pos('%', PFormatString
);
311 Output
.AddString(PFormatString
);
315 Result
:= Output
.Value
;
322 procedure GetFormatArgs(const _FormatString
: WideString
; FormatArgs
: TTntStrings
);
326 with TFormatStrParser
.Create(_FormatString
) do
329 // loop until no more '%'
330 PosSpec
:= Pos('%', PFormatString
);
331 While PosSpec
<> 0 do begin
333 // delete everything up until '%'
334 Inc(PFormatString
, PosSpec
- 1);
335 // add format specifier to list
337 if ParseFormatSpecifier
then
338 FormatArgs
.Add(ParsedString
.Value
);
340 PosSpec
:= Pos('%', PFormatString
);
348 function GetExplicitIndex(const FormatSpecifier
: WideString
): Integer;
350 IndexStr
: WideString
;
354 PosColon
:= Pos(':', FormatSpecifier
);
355 if PosColon
<> 0 then begin
356 IndexStr
:= Copy(FormatSpecifier
, 2, PosColon
- 2);
357 result
:= StrToInt(IndexStr
);
361 function GetMaxIndex(FormatArgs
: TTntStrings
): Integer;
364 RunningIndex
: Integer;
365 ExplicitIndex
: Integer;
369 for i
:= 0 to FormatArgs
.Count
- 1 do begin
370 ExplicitIndex
:= GetExplicitIndex(FormatArgs
[i
]);
371 if ExplicitIndex
<> -1 then
372 RunningIndex
:= ExplicitIndex
375 result
:= Max(result
, RunningIndex
);
379 procedure UpdateTypeList(FormatArgs
, TypeList
: TTntStrings
);
383 SpecType
: TFormatSpecifierType
;
384 ExplicitIndex
: Integer;
386 RunningIndex
: Integer;
388 // set count of TypeList to accomodate maximum index
389 MaxIndex
:= GetMaxIndex(FormatArgs
);
391 for i
:= 0 to MaxIndex
do
396 for i
:= 0 to FormatArgs
.Count
- 1 do begin
398 ExplicitIndex
:= GetExplicitIndex(f
);
399 SpecType
:= GetFormatSpecifierType(f
);
401 // determine running arg index
402 if ExplicitIndex
<> -1 then
403 RunningIndex
:= ExplicitIndex
407 if TypeList
[RunningIndex
] <> '' then begin
408 // already exists in list, check for compatibility
409 if TypeList
.Objects
[RunningIndex
] <> TObject(SpecType
) then
410 raise EFormatSpecError
.CreateFmt(SMismatchedArgumentTypes
,
411 [RunningIndex
, TypeList
[RunningIndex
], f
]);
413 // not in list so update it
414 TypeList
[RunningIndex
] := f
;
415 TypeList
.Objects
[RunningIndex
] := TObject(SpecType
);
420 procedure CompareFormatStrings(FormatStr1
, FormatStr2
: WideString
);
422 ArgList1
: TTntStringList
;
423 ArgList2
: TTntStringList
;
424 TypeList1
: TTntStringList
;
425 TypeList2
: TTntStringList
;
433 ArgList1
:= TTntStringList
.Create
;
434 ArgList2
:= TTntStringList
.Create
;
435 TypeList1
:= TTntStringList
.Create
;
436 TypeList2
:= TTntStringList
.Create
;
438 GetFormatArgs(FormatStr1
, ArgList1
);
439 UpdateTypeList(ArgList1
, TypeList1
);
441 GetFormatArgs(FormatStr2
, ArgList2
);
442 UpdateTypeList(ArgList2
, TypeList2
);
444 if TypeList1
.Count
<> TypeList2
.Count
then
445 raise EFormatSpecError
.Create(SMismatchedArgumentCounts
+ CRLF
+ CRLF
+ '> ' + FormatStr1
+ CRLF
+ '> ' + FormatStr2
);
447 for i
:= 0 to TypeList1
.Count
- 1 do begin
448 if TypeList1
.Objects
[i
] <> TypeList2
.Objects
[i
] then begin
449 raise EFormatSpecError
.CreateFmt(SMismatchedArgumentTypes
,
450 [i
, TypeList1
[i
], TypeList2
[i
]]);
462 function FormatStringsAreCompatible(FormatStr1
, FormatStr2
: WideString
): Boolean;
464 ArgList1
: TTntStringList
;
465 ArgList2
: TTntStringList
;
466 TypeList1
: TTntStringList
;
467 TypeList2
: TTntStringList
;
475 ArgList1
:= TTntStringList
.Create
;
476 ArgList2
:= TTntStringList
.Create
;
477 TypeList1
:= TTntStringList
.Create
;
478 TypeList2
:= TTntStringList
.Create
;
480 GetFormatArgs(FormatStr1
, ArgList1
);
481 UpdateTypeList(ArgList1
, TypeList1
);
483 GetFormatArgs(FormatStr2
, ArgList2
);
484 UpdateTypeList(ArgList2
, TypeList2
);
486 Result
:= (TypeList1
.Count
= TypeList2
.Count
);
488 for i
:= 0 to TypeList1
.Count
- 1 do begin
489 if TypeList1
.Objects
[i
] <> TypeList2
.Objects
[i
] then begin