You've already forked lazarus-ccr
fpspreadsheet: Improved conversion of numbers to formatted strings, handles more special cases (e.g. integer-only fractions, optional digits, optional space digits).
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@4094 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
@ -1031,6 +1031,7 @@ begin
|
||||
decs := Worksheet.GetDisplayedDecimals(ACell)
|
||||
else
|
||||
decs := FDecimals;
|
||||
|
||||
inc(decs, FDelta);
|
||||
if decs < 0 then decs := 0;
|
||||
Worksheet.WriteDecimals(ACell, decs);
|
||||
|
@ -135,6 +135,7 @@ begin
|
||||
FWorkbook := AWorkbook;
|
||||
Parse(AFormatString);
|
||||
CheckSections;
|
||||
if AFormatString = '' then FSections[0].NumFormat := nfGeneral;
|
||||
end;
|
||||
|
||||
destructor TsNumFormatParser.Destroy;
|
||||
@ -1358,12 +1359,19 @@ end;
|
||||
procedure TsNumFormatParser.SetDecimals(AValue: Byte);
|
||||
var
|
||||
i, j, n: Integer;
|
||||
foundDecs: Boolean;
|
||||
begin
|
||||
foundDecs := false;
|
||||
for j := 0 to High(FSections) do begin
|
||||
n := Length(FSections[j].Elements);
|
||||
i := n-1;
|
||||
while (i > -1) do begin
|
||||
case FSections[j].Elements[i].Token of
|
||||
nftDecSep: // this happens, e.g., for "0.E+00"
|
||||
if (AValue > 0) and not foundDecs then begin
|
||||
InsertElement(j, i, nftZeroDecs, AValue);
|
||||
break;
|
||||
end;
|
||||
nftIntOptDigit, nftIntZeroDigit, nftIntSpaceDigit, nftIntTh:
|
||||
// no decimals so far --> add decimal separator and decimals element
|
||||
if (AValue > 0) then begin
|
||||
@ -1373,6 +1381,8 @@ begin
|
||||
break;
|
||||
end;
|
||||
nftZeroDecs, nftOptDecs, nftSpaceDecs:
|
||||
begin
|
||||
foundDecs := true;
|
||||
if AValue > 0 then begin
|
||||
// decimals are already used, just replace value of decimal places
|
||||
FSections[j].Elements[i].IntValue := AValue;
|
||||
@ -1385,6 +1395,7 @@ begin
|
||||
break;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
dec(i);
|
||||
end;
|
||||
end;
|
||||
|
@ -4424,12 +4424,15 @@ var
|
||||
numFmt: TsNumFormatParams;
|
||||
numFmtStr: String;
|
||||
begin
|
||||
if (ACell = nil) then
|
||||
if (ACell = nil) or (ACell^.ContentType <> cctNumber) then
|
||||
exit;
|
||||
|
||||
fmt := FWorkbook.GetCellFormat(ACell^.FormatIndex);
|
||||
numFmt := FWorkbook.GetNumberFormat(fmt.NumberFormatIndex);
|
||||
numFmtStr := numFmt.NumFormatStr;
|
||||
if numFmt <> nil then
|
||||
numFmtStr := numFmt.NumFormatStr
|
||||
else
|
||||
numFmtStr := '0.00';
|
||||
parser := TsNumFormatParser.Create(Workbook, numFmtStr);
|
||||
try
|
||||
parser.Decimals := ADecimals;
|
||||
|
@ -2400,6 +2400,441 @@ begin
|
||||
end;
|
||||
|
||||
|
||||
type
|
||||
TsNumFormatTokenSet = set of TsNumFormatToken;
|
||||
|
||||
const
|
||||
TERMINATING_TOKENS: TsNumFormatTokenSet =
|
||||
[nftSpace, nftText, nftEscaped, nftPercent, nftCurrSymbol, nftSign, nftSignBracket];
|
||||
INT_TOKENS: TsNumFormatTokenSet =
|
||||
[nftIntOptDigit, nftIntZeroDigit, nftIntSpaceDigit];
|
||||
DECS_TOKENS: TsNumFormatTokenSet =
|
||||
[nftZeroDecs, nftOptDecs, nftSpaceDecs];
|
||||
FRACNUM_TOKENS: TsNumFormatTokenSet =
|
||||
[nftFracNumOptDigit, nftFracNumZeroDigit, nftFracNumSpaceDigit];
|
||||
FRACDENOM_TOKENS: TsNumFormatTokenSet =
|
||||
[nftFracDenomOptDigit, nftFracDenomZeroDigit, nftFracDenomSpaceDigit];
|
||||
EXP_TOKENS: TsNumFormatTokenSet =
|
||||
[nftExpDigits]; // todo: expand by optional digits (0.00E+#)
|
||||
|
||||
{ Checks whether a sequence of format tokens for exponential formatting begins
|
||||
at the specified index in the format elements }
|
||||
function CheckExp(const AElements: TsNumFormatElements; AIndex: Integer): Boolean;
|
||||
var
|
||||
numEl: Integer;
|
||||
i: Integer;
|
||||
begin
|
||||
numEl := Length(AElements);
|
||||
|
||||
Result := (AIndex < numEl) and (AElements[AIndex].Token in INT_TOKENS);
|
||||
if not Result then
|
||||
exit;
|
||||
|
||||
numEl := Length(AElements);
|
||||
i := AIndex + 1;
|
||||
while (i < numEl) and (AElements[i].Token in INT_TOKENS) do inc(i);
|
||||
|
||||
// no decimal places
|
||||
if (i+2 < numEl) and
|
||||
(AElements[i].Token = nftExpChar) and
|
||||
(AElements[i+1].Token = nftExpSign) and
|
||||
(AElements[i+2].Token in EXP_TOKENS)
|
||||
then begin
|
||||
Result := true;
|
||||
exit;
|
||||
end;
|
||||
|
||||
// with decimal places
|
||||
if (i < numEl) and (AElements[i].Token = nftDecSep) //and (AElements[i+1].Token in DECS_TOKENS)
|
||||
then begin
|
||||
inc(i);
|
||||
while (i < numEl) and (AElements[i].Token in DECS_TOKENS) do inc(i);
|
||||
if (i + 2 < numEl) and
|
||||
(AElements[i].Token = nftExpChar) and
|
||||
(AElements[i+1].Token = nftExpSign) and
|
||||
(AElements[i+2].Token in EXP_TOKENS)
|
||||
then begin
|
||||
Result := true;
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
Result := false;
|
||||
end;
|
||||
|
||||
function CheckFraction(const AElements: TsNumFormatElements; AIndex: Integer;
|
||||
out digits: Integer): Boolean;
|
||||
var
|
||||
numEl: Integer;
|
||||
i: Integer;
|
||||
begin
|
||||
digits := 0;
|
||||
numEl := Length(AElements);
|
||||
|
||||
Result := (AIndex < numEl);
|
||||
if not Result then
|
||||
exit;
|
||||
|
||||
i := AIndex;
|
||||
// Check for mixed fraction (integer split off, sample format "# ??/??"
|
||||
if (AElements[i].Token in (INT_TOKENS + [nftIntTh])) then
|
||||
begin
|
||||
inc(i);
|
||||
while (i < numEl) and (AElements[i].Token in (INT_TOKENS + [nftIntTh])) do inc(i);
|
||||
while (i < numEl) and (AElements[i].Token in TERMINATING_TOKENS) do inc(i);
|
||||
end;
|
||||
|
||||
if (i = numEl) or not (AElements[i].Token in FRACNUM_TOKENS) then
|
||||
exit(false);
|
||||
|
||||
// Here follows the ordinary fraction (no integer split off); sample format "??/??"
|
||||
while (i < numEl) and (AElements[i].Token in FRACNUM_TOKENS) do inc(i);
|
||||
while (i < numEl) and (AElements[i].Token in TERMINATING_TOKENS) do inc(i);
|
||||
if (AElements[i].Token <> nftFracSymbol) then
|
||||
exit(False);
|
||||
|
||||
inc(i);
|
||||
while (i < numEl) and (AElements[i].Token in TERMINATING_TOKENS) do inc(i);
|
||||
if not (AElements[i].Token in FRACDENOM_TOKENS) then
|
||||
exit(false);
|
||||
|
||||
while (i < numEL) and (AElements[i].Token in FRACDENOM_TOKENS) do
|
||||
begin
|
||||
case AElements[i].Token of
|
||||
nftFracDenomZeroDigit : inc(digits, AElements[i].IntValue);
|
||||
nftFracDenomSpaceDigit: inc(digits, AElements[i].IntValue);
|
||||
nftFracDenomOptDigit : inc(digits, AElements[i].IntValue);
|
||||
end;
|
||||
inc(i);
|
||||
end;
|
||||
Result := true;
|
||||
end;
|
||||
|
||||
{ Processes a sequence of #, 0, and ? tokens.
|
||||
Adds leading (GrowRight=false) or trailing (GrowRight=true) zeros and/or
|
||||
spaces as specified by the format elements to the number value string. }
|
||||
function ProcessIntegerFormat(AValue: String; AFormatSettings: TFormatSettings;
|
||||
const AElements: TsNumFormatElements; var AIndex: Integer;
|
||||
ATokens: TsNumFormatTokenSet; GrowRight, UseThSep: Boolean): String;
|
||||
const
|
||||
OptTokens = [nftIntOptDigit, nftFracNumOptDigit, nftFracDenomOptDigit, nftOptDecs];
|
||||
ZeroTokens = [nftIntZeroDigit, nftFracNumZeroDigit, nftFracDenomZeroDigit, nftZeroDecs, nftIntTh];
|
||||
SpaceTokens = [nftIntSpaceDigit, nftFracNumSpaceDigit, nftFracDenomSpaceDigit, nftSpaceDecs];
|
||||
AllOptTokens = OptTokens + SpaceTokens;
|
||||
var
|
||||
fs: TFormatSettings absolute AFormatSettings;
|
||||
i, j, L: Integer;
|
||||
numEl: Integer;
|
||||
begin
|
||||
Result := AValue;
|
||||
numEl := Length(AElements);
|
||||
if GrowRight then
|
||||
begin
|
||||
// This branch is intended for decimal places, i.e. there may be trailing zeros.
|
||||
i := AIndex;
|
||||
if (AValue = '0') and (AElements[i].Token in AllOptTokens) then
|
||||
Result := '';
|
||||
// Remove trailing zeros
|
||||
while (Result <> '') and (Result[Length(Result)] = '0')
|
||||
do Delete(Result, Length(Result), 1);
|
||||
// Add trailing zeros or spaces as required by the elements.
|
||||
i := AIndex;
|
||||
L := 0;
|
||||
while (i < numEl) and (AElements[i].Token in ATokens) do
|
||||
begin
|
||||
if AElements[i].Token in ZeroTokens then
|
||||
begin
|
||||
inc(L, AElements[i].IntValue);
|
||||
while Length(Result) < L do Result := Result + '0'
|
||||
end else
|
||||
if AElements[i].Token in SpaceTokens then
|
||||
begin
|
||||
inc(L, AElements[i].IntValue);
|
||||
while Length(Result) < L do Result := Result + ' ';
|
||||
end;
|
||||
inc(i);
|
||||
end;
|
||||
if UseThSep then begin
|
||||
j := 2;
|
||||
while (j < Length(Result)) and (Result[j-1] <> ' ') and (Result[j] <> ' ') do
|
||||
begin
|
||||
Insert(fs.ThousandSeparator, Result, 1);
|
||||
inc(j, 3);
|
||||
end;
|
||||
end;
|
||||
AIndex := i;
|
||||
end else
|
||||
begin
|
||||
// This branch is intended for digits (or integer and numerator parts of fractions)
|
||||
// --> There are no leading zeros.
|
||||
// Find last digit token of the sequence
|
||||
i := AIndex;
|
||||
while (i < numEl) and (AElements[i].Token in ATokens) do
|
||||
inc(i);
|
||||
j := i;
|
||||
dec(i);
|
||||
if (AValue = '0') and (AElements[i].Token in AllOptTokens) and (i = AIndex) then
|
||||
Result := '';
|
||||
// From the end of the sequence, going backward, add leading zeros or spaces
|
||||
// as required by the elements of the format.
|
||||
L := 0;
|
||||
while (i >= AIndex) do begin
|
||||
if AElements[i].Token in ZeroTokens then
|
||||
begin
|
||||
inc(L, AElements[i].IntValue);
|
||||
while Length(Result) < L do Result := '0' + Result;
|
||||
end else
|
||||
if AElements[i].Token in SpaceTokens then
|
||||
begin
|
||||
inc(L, AElements[i].IntValue);
|
||||
while Length(Result) < L do Result := ' ' + Result;
|
||||
end;
|
||||
dec(i);
|
||||
end;
|
||||
AIndex := j;
|
||||
if UseThSep then
|
||||
begin
|
||||
j := Length(Result) - 2;
|
||||
while (j > 1) and (Result[j-1] <> ' ') and (Result[j] <> ' ') do
|
||||
begin
|
||||
Insert(fs.ThousandSeparator, Result, j);
|
||||
dec(j, 3);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
{ Converts the floating point number to an exponential number string according
|
||||
to the format specification in AElements.
|
||||
It must have been verified before, that the elements in fact are valid for
|
||||
an exponential format. }
|
||||
function ProcessExpFormat(AValue: Double; AFormatSettings: TFormatSettings;
|
||||
const AElements: TsNumFormatElements; var AIndex: Integer): String;
|
||||
var
|
||||
fs: TFormatSettings absolute AFormatSettings;
|
||||
expchar: String;
|
||||
expSign: String;
|
||||
se, si, sd: String;
|
||||
decs, expDigits: Integer;
|
||||
intZeroDigits, intOptDigits, intSpaceDigits: Integer;
|
||||
numStr: String;
|
||||
i, id, p: Integer;
|
||||
numEl: Integer;
|
||||
begin
|
||||
Result := '';
|
||||
numEl := Length(AElements);
|
||||
|
||||
// Determine digits of integer part of mantissa
|
||||
intZeroDigits := 0;
|
||||
intOptDigits := 0;
|
||||
intSpaceDigits := 0;
|
||||
i := AIndex;
|
||||
while (AElements[i].Token in INT_TOKENS) do begin
|
||||
case AElements[i].Token of
|
||||
nftIntZeroDigit : inc(intZeroDigits, AElements[i].IntValue);
|
||||
nftIntSpaceDigit: inc(intSpaceDigits, AElements[i].IntValue);
|
||||
nftIntOptDigit : inc(intOptDigits, AElements[i].IntValue);
|
||||
end;
|
||||
inc(i);
|
||||
end;
|
||||
|
||||
// No decimal places
|
||||
if (i + 2 < numEl) and (AElements[i].Token = nftExpChar) then
|
||||
begin
|
||||
expChar := AElements[i].TextValue;
|
||||
expSign := AElements[i+1].TextValue;
|
||||
expDigits := 0;
|
||||
i := i+2;
|
||||
while (i < numEl) and (AElements[i].Token in EXP_TOKENS) do
|
||||
begin
|
||||
inc(expDigits, AElements[i].IntValue); // not exactly what Excel does... Rather exotic case...
|
||||
inc(i);
|
||||
end;
|
||||
numstr := FormatFloat('0'+expChar+expSign+DupeString('0', expDigits), AValue, fs);
|
||||
p := pos('e', Lowercase(numStr));
|
||||
se := copy(numStr, p, Length(numStr)); // exp part of the number string, incl "E"
|
||||
numStr := copy(numstr, 1, p-1); // mantissa of the number string
|
||||
numStr := ProcessIntegerFormat(numStr, fs, AElements, AIndex, INT_TOKENS, false, false);
|
||||
Result := numStr + se;
|
||||
AIndex := i;
|
||||
end
|
||||
else
|
||||
// With decimal places
|
||||
if (i + 1 < numEl) and (AElements[i].Token = nftDecSep) then
|
||||
begin
|
||||
inc(i);
|
||||
id := i; // index of decimal elements
|
||||
decs := 0;
|
||||
while (i < numEl) and (AElements[i].Token in DECS_TOKENS) do
|
||||
begin
|
||||
case AElements[i].Token of
|
||||
nftZeroDecs,
|
||||
nftSpaceDecs: inc(decs, AElements[i].IntValue);
|
||||
end;
|
||||
inc(i);
|
||||
end;
|
||||
expChar := AElements[i].TextValue;
|
||||
expSign := AElements[i+1].TextValue;
|
||||
expDigits := 0;
|
||||
inc(i, 2);
|
||||
while (i < numEl) and (AElements[i].Token in EXP_TOKENS) do
|
||||
begin
|
||||
inc(expDigits, AElements[i].IntValue);
|
||||
inc(i);
|
||||
end;
|
||||
if decs=0 then
|
||||
numstr := FormatFloat('0'+expChar+expSign+DupeString('0', expDigits), AValue, fs)
|
||||
else
|
||||
numStr := FloatToStrF(AValue, ffExponent, decs+1, expDigits, fs);
|
||||
if (abs(AValue) >= 1.0) and (expSign = '-') then
|
||||
Delete(numStr, pos('+', numStr), 1);
|
||||
p := pos('e', Lowercase(numStr));
|
||||
se := copy(numStr, p, Length(numStr)); // exp part of the number string, incl "E"
|
||||
numStr := copy(numStr, 1, p-1); // mantissa of the number string
|
||||
p := pos(fs.DecimalSeparator, numStr);
|
||||
if p = 0 then
|
||||
begin
|
||||
si := numstr;
|
||||
sd := '';
|
||||
end else
|
||||
begin
|
||||
si := ProcessIntegerFormat(copy(numStr, 1, p-1), fs, AElements, AIndex, INT_TOKENS, false, false); // integer part of the mantissa
|
||||
sd := ProcessIntegerFormat(copy(numStr, p+1, Length(numStr)), fs, AElements, id, DECS_TOKENS, true, false); // fractional part of the mantissa
|
||||
end;
|
||||
// Put all parts together...
|
||||
Result := si + fs.DecimalSeparator + sd + se;
|
||||
AIndex := i;
|
||||
end;
|
||||
end;
|
||||
|
||||
function ProcessFracFormat(AValue: Double; const AFormatSettings: TFormatSettings;
|
||||
ADigits: Integer; const AElements: TsNumFormatElements;
|
||||
var AIndex: Integer): String;
|
||||
var
|
||||
fs: TFormatSettings absolute AFormatSettings;
|
||||
frint, frnum, frdenom, maxdenom: Int64;
|
||||
sfrint, sfrnum, sfrdenom: String;
|
||||
sfrsym, sintnumspace, snumsymspace, ssymdenomspace: String;
|
||||
i, numEl: Integer;
|
||||
mixed: Boolean;
|
||||
begin
|
||||
sintnumspace := '';
|
||||
snumsymspace := '';
|
||||
ssymdenomspace := '';
|
||||
sfrsym := '/';
|
||||
maxDenom := Round(IntPower(10, ADigits));
|
||||
numEl := Length(AElements);
|
||||
|
||||
// Split-off integer
|
||||
i := AIndex;
|
||||
if AElements[i].Token in (INT_TOKENS + [nftIntTh]) then begin
|
||||
mixed := true;
|
||||
if (AValue >= 1) then
|
||||
begin
|
||||
frint := trunc(AValue);
|
||||
AValue := frac(AValue);
|
||||
end else
|
||||
frint := 0;
|
||||
FloatToFraction(AValue, 0.1/maxdenom, MaxInt, maxdenom, frnum, frdenom);
|
||||
sfrint := ProcessIntegerFormat(IntToStr(frint), fs, AElements, i,
|
||||
INT_TOKENS, false, false);
|
||||
while (i < numEl) and (AElements[i].Token in TERMINATING_TOKENS) do
|
||||
begin
|
||||
sintnumspace := sintnumspace + AElements[i].TextValue;
|
||||
inc(i);
|
||||
end;
|
||||
end else
|
||||
begin
|
||||
mixed := false;
|
||||
sfrint := '';
|
||||
FloatToFraction(AValue, 0.1/maxdenom, MaxInt, maxdenom, frnum, frdenom);
|
||||
sintnumspace := '';
|
||||
end;
|
||||
|
||||
// "normal" fraction
|
||||
sfrnum := ProcessIntegerFormat(IntToStr(frnum), fs, AElements, i,
|
||||
FRACNUM_TOKENS, false, false);
|
||||
while (i < numEl) and (AElements[i].Token in TERMINATING_TOKENS) do
|
||||
begin
|
||||
snumsymspace := snumsymspace + AElements[i].TextValue;
|
||||
inc(i);
|
||||
end;
|
||||
inc(i); // fraction symbol
|
||||
while (i < numEl) and (AElements[i].Token in TERMINATING_TOKENS) do
|
||||
begin
|
||||
ssymdenomspace := ssymdenomspace + AElements[i].TextValue;
|
||||
inc(i);
|
||||
end;
|
||||
sfrdenom := ProcessIntegerFormat(IntToStr(frdenom), fs, AElements, i,
|
||||
FRACDENOM_TOKENS, false, false);
|
||||
AIndex := i;
|
||||
|
||||
// Special cases
|
||||
if mixed and (frnum = 0) then
|
||||
begin
|
||||
if sfrnum = '' then begin
|
||||
sintnumspace := '';
|
||||
snumsymspace := '';
|
||||
ssymdenomspace := '';
|
||||
sfrdenom := '';
|
||||
sfrsym := '';
|
||||
end else
|
||||
if trim(sfrnum) = '' then begin
|
||||
sfrdenom := DupeString(' ', Length(sfrdenom));
|
||||
sfrsym := ' ';
|
||||
end;
|
||||
end;
|
||||
if sfrint = '' then sintnumspace := '';
|
||||
|
||||
// Compose result string
|
||||
Result := sfrnum + snumsymspace + sfrsym + ssymdenomspace + sfrdenom;
|
||||
if (Trim(Result) = '') and (sfrint = '') then
|
||||
sfrint := '0';
|
||||
if sfrint <> '' then
|
||||
Result := sfrint + sintnumSpace + result;
|
||||
end;
|
||||
|
||||
function ProcessFloatFormat(AValue: Double; AFormatSettings: TFormatSettings;
|
||||
const AElements: TsNumFormatElements; var AIndex: Integer): String;
|
||||
var
|
||||
fs: TFormatSettings absolute AFormatSettings;
|
||||
numEl: Integer;
|
||||
numStr, s: String;
|
||||
p, i: Integer;
|
||||
decs: Integer;
|
||||
useThSep: Boolean;
|
||||
begin
|
||||
Result := '';
|
||||
numEl := Length(AElements);
|
||||
|
||||
// Extract integer part
|
||||
Result := IntToStr(trunc(AValue));
|
||||
useThSep := AElements[AIndex].Token = nftIntTh;
|
||||
Result := ProcessIntegerFormat(Result, fs, AElements, AIndex,
|
||||
(INT_TOKENS + [nftIntTh]), false, UseThSep);
|
||||
|
||||
// Decimals
|
||||
if (AIndex < numEl) and (AElements[AIndex].Token = nftDecSep) then
|
||||
begin
|
||||
inc(AIndex);
|
||||
i := AIndex;
|
||||
// Count decimal digits in format elements
|
||||
decs := 0;
|
||||
while (AIndex < numEl) and (AElements[AIndex].Token in DECS_TOKENS) do begin
|
||||
inc(decs, AElements[AIndex].IntValue);
|
||||
inc(AIndex);
|
||||
end;
|
||||
// Convert value to string
|
||||
numstr := FloatToStrF(AValue, ffFixed, MaxInt, decs, fs);
|
||||
p := Pos(fs.DecimalSeparator, numstr);
|
||||
s := Copy(numstr, p+1, Length(numstr));
|
||||
s := ProcessIntegerFormat(s, fs, AElements, i, DECS_TOKENS, true, false);
|
||||
if s <> '' then
|
||||
Result := Result + fs.DecimalSeparator + s;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
{@@ ----------------------------------------------------------------------------
|
||||
Converts a floating point number to a string as determined by the specified
|
||||
number format parameters
|
||||
@ -2410,115 +2845,11 @@ var
|
||||
fs: TFormatSettings absolute AFormatSettings;
|
||||
sidx: Integer;
|
||||
section: TsNumFormatSection;
|
||||
i, p, q, el, numEl: Integer;
|
||||
i, el, numEl: Integer;
|
||||
isNeg: Boolean;
|
||||
yr, mon, day, hr, min, sec, ms: Word;
|
||||
frInt, frNum, frDenom: Int64;
|
||||
maxNum, maxDenom: Int64;
|
||||
decsZero, decsOpt, decsSpace: Integer;
|
||||
digitsZero, digitsOpt, digitsSpace: Integer;
|
||||
numDigitsZero, numDigitsOpt, numDigitsSpace: Integer;
|
||||
denomDigitsZero, denomDigitsOpt, denomDigitsSpace: Integer;
|
||||
expSign: Char;
|
||||
expDigits: Integer;
|
||||
numStr, s: String;
|
||||
terminatingTokens: set of TsNumFormatToken;
|
||||
intTokens: set of TsNumFormatToken;
|
||||
decsTokens: set of TsNumFormatToken;
|
||||
fracNumTokens: set of TsNumFormatToken;
|
||||
fracDenomTokens: set of TsNumFormatToken;
|
||||
|
||||
function FixIntPart(AValue: Double; AddThousandSeparator: Boolean;
|
||||
AZeroCount, AOptCount, ASpaceCount: Integer): String;
|
||||
var
|
||||
j: Integer;
|
||||
isNeg: Boolean;
|
||||
begin
|
||||
isNeg := AValue < 0;
|
||||
Result := IntToStr(trunc(abs(AValue)));
|
||||
if (AZeroCount = 0) and (ASpaceCount = 0) then
|
||||
begin
|
||||
if Result = '0' then
|
||||
Result := '';
|
||||
end else
|
||||
if (AZeroCount > 0) and (ASpaceCount = 0) then
|
||||
begin
|
||||
while Length(Result) < AZeroCount do
|
||||
Result := '0' + Result;
|
||||
end else
|
||||
if (AZeroCount = 0) and (ASpaceCount > 0) then
|
||||
begin
|
||||
while Length(Result) < AZeroCount do
|
||||
Result := ' ' + Result;
|
||||
end else
|
||||
begin
|
||||
while Length(Result) < AZeroCount do
|
||||
Result := '0' + Result;
|
||||
while Length(Result) < AZeroCount + ASpaceCount do
|
||||
Result := ' ' + Result;
|
||||
end;
|
||||
if AddThousandSeparator then
|
||||
begin
|
||||
j := Length(Result)-2;
|
||||
while (j > 1) do
|
||||
begin
|
||||
Insert(fs.ThousandSeparator, Result, j);
|
||||
dec(j, 3);
|
||||
end;
|
||||
end;
|
||||
if isNeg then
|
||||
Result := '-' + Result;
|
||||
end;
|
||||
|
||||
function FixDecimals(AValue: Double;
|
||||
AZeroCount, AOptCount, ASpaceCount: Integer): String;
|
||||
var
|
||||
j, decs: Integer;
|
||||
begin
|
||||
if AZeroCount + AOptCount + ASpaceCount = 0 then
|
||||
begin
|
||||
Result := ''; // no decimals in this case
|
||||
exit;
|
||||
end;
|
||||
|
||||
Result := FloatToStrF(abs(frac(AValue)), ffFixed, 20, AZeroCount + AOptCount + ASpaceCount, fs);
|
||||
Delete(Result, 1, 2); // Delete '0.' to extract the decimals
|
||||
decs := Length(Result);
|
||||
while decs < AZeroCount do begin
|
||||
Result := Result + '0';
|
||||
inc(decs);
|
||||
end;
|
||||
|
||||
j := Length(Result);
|
||||
while (Result[j] = '0') and (decs > AZeroCount) and (( decsOpt > 0) or (decsSpace > 0)) do
|
||||
begin
|
||||
if decsOpt > 0 then
|
||||
begin
|
||||
Delete(Result, j, 1);
|
||||
dec(decs);
|
||||
dec(decsOpt);
|
||||
end else
|
||||
if decsSpace > 0 then
|
||||
begin
|
||||
Result[j] := ' ';
|
||||
dec(decs);
|
||||
dec(decsOpt);
|
||||
end;
|
||||
dec(j);
|
||||
end;
|
||||
|
||||
if Result <> '' then
|
||||
Result := fs.DecimalSeparator + Result;
|
||||
end;
|
||||
|
||||
procedure InvalidFormat;
|
||||
var
|
||||
fmtStr: String;
|
||||
begin
|
||||
fmtStr := AParams.NumFormatStr;
|
||||
raise Exception.CreateFmt(rsIsNoValidNumberFormatString, [fmtStr]);
|
||||
end;
|
||||
|
||||
s: String;
|
||||
digits: Integer;
|
||||
begin
|
||||
Result := '';
|
||||
if IsNaN(AValue) then
|
||||
@ -2536,18 +2867,10 @@ begin
|
||||
if (AValue = 0) and (Length(AParams.Sections) > 2) then
|
||||
sidx := 2;
|
||||
isNeg := (AValue < 0);
|
||||
if (sidx > 0) and isNeg then
|
||||
AValue := -AValue;
|
||||
AValue := abs(AValue); // section 0 adds the sign back, section 1 has the sign in the elements
|
||||
section := AParams.Sections[sidx];
|
||||
numEl := Length(section.Elements);
|
||||
|
||||
terminatingTokens := [nftSpace, nftText, nftPercent, nftCurrSymbol, nftSignBracket,
|
||||
nftEscaped];
|
||||
intTokens := [nftIntOptDigit, nftIntZeroDigit, nftIntSpaceDigit];
|
||||
decsTokens := [nftZeroDecs, nftOptDecs, nftSpaceDecs];
|
||||
fracNumTokens := [nftFracNumOptDigit, nftFracNumZeroDigit, nftFracNumSpaceDigit];
|
||||
fracDenomTokens := [nftFracDenomOptDigit, nftFracDenomZeroDigit, nftFracDenomSpaceDigit];
|
||||
|
||||
if nfkPercent in section.Kind then
|
||||
AValue := AValue * 100.0;
|
||||
if nfkTime in section.Kind then
|
||||
@ -2557,306 +2880,29 @@ begin
|
||||
|
||||
el := 0;
|
||||
while (el < numEl) do begin
|
||||
// Integer token: can be the start of a number, exp, or mixed fraction format
|
||||
// Cases with thousand separator are handled here as well.
|
||||
if section.Elements[el].Token in (INT_TOKENS + [nftIntTh]) then begin
|
||||
// Check for exponential format
|
||||
if CheckExp(section.Elements, el) then
|
||||
s := ProcessExpFormat(AValue, fs, section.Elements, el)
|
||||
else
|
||||
// Check for fraction format
|
||||
if CheckFraction(section.Elements, el, digits) then
|
||||
s := ProcessFracFormat(AValue, fs, digits, section.Elements, el)
|
||||
// Floating-point or integer
|
||||
else
|
||||
s := ProcessFloatFormat(AValue, fs, section.Elements, el);
|
||||
if (sidx = 0) and isNeg then s := '-' + s;
|
||||
Result := Result + s;
|
||||
Continue;
|
||||
end
|
||||
else
|
||||
case section.Elements[el].Token of
|
||||
nftIntOptDigit,
|
||||
nftIntZeroDigit,
|
||||
nftIntSpaceDigit:
|
||||
begin
|
||||
// Decimals
|
||||
decsZero := 0;
|
||||
decsSpace := 0;
|
||||
decsOpt := 0;
|
||||
// Integer part of number format
|
||||
digitsZero := 0;
|
||||
digitsSpace := 0;
|
||||
digitsOpt := 0;
|
||||
i := el;
|
||||
while (i < numEl) and (section.Elements[i].Token in intTokens) do
|
||||
begin
|
||||
case section.Elements[i].Token of
|
||||
nftIntOptDigit : inc(digitsOpt, section.Elements[i].IntValue);
|
||||
nftIntZeroDigit : inc(digitsZero, section.Elements[i].IntValue);
|
||||
nftIntSpaceDigit: inc(digitsSpace, section.Elements[i].IntValue);
|
||||
end;
|
||||
inc(i);
|
||||
end;
|
||||
|
||||
{ These are the cases that can occur:
|
||||
(1) number w/ decimals ---> end of line
|
||||
(2) number w/ decimals --> space, terminating tokens
|
||||
(3) number w/ decimals --> exponent
|
||||
(4) number w/o decimals --> end of line
|
||||
(5) number w/o decimals --> space, terminating tokens
|
||||
(6) number w/o decimals --> space --> numerator --> '/' --> denominator
|
||||
(7) number w/o decimals --> exponent
|
||||
}
|
||||
// Integer only, followed by end-of-line (case 4)
|
||||
if (i = numEl) or (section.Elements[i].Token in (terminatingTokens - [nftSpace, nftEmptyCharWidth])) then
|
||||
begin
|
||||
Result := Result + FixIntPart(AValue, false, digitsZero, digitsOpt, digitsSpace);
|
||||
el := i;
|
||||
Continue;
|
||||
end;
|
||||
|
||||
if (i < numEl) then
|
||||
begin
|
||||
// Check for Exponent (format '0E+00', case 7)
|
||||
if (section.Elements[i].Token = nftExpChar) then begin
|
||||
inc(i);
|
||||
if (i < numEl) and (section.Elements[i].Token = nftExpSign) then begin
|
||||
expSign := section.Elements[i].TextValue[1];
|
||||
inc(i);
|
||||
if (i < numEl) and (section.Elements[i].Token = nftExpDigits) then
|
||||
expDigits := section.Elements[i].IntValue
|
||||
else
|
||||
InvalidFormat;
|
||||
end else
|
||||
InvalidFormat;
|
||||
numStr := FormatFloat('0E'+expSign+DupeString('0', expDigits), AValue, fs);
|
||||
p := pos('e', Lowercase(numStr));
|
||||
s := copy(numStr, p, Length(numStr)); // E part of the number string
|
||||
numStr := copy(numStr, 1, p-1); // Mantissa of the number string
|
||||
Result := Result
|
||||
+ FixIntPart(StrToFloat(numStr, fs), false, digitsZero, digitsOpt, digitsSpace) + s;
|
||||
el := i;
|
||||
Continue;
|
||||
end;
|
||||
|
||||
// Check for decimal separator
|
||||
if (section.Elements[i].Token = nftDecSep) then
|
||||
begin
|
||||
// Yes, cases (1) or (2) -- get decimal specification
|
||||
decsZero := 0;
|
||||
decsSpace := 0;
|
||||
decsOpt := 0;
|
||||
inc(i);
|
||||
while (i < numEl) and (section.Elements[i].Token in decsTokens) do
|
||||
begin
|
||||
case section.Elements[i].Token of
|
||||
nftZeroDecs : inc(decsZero, section.Elements[i].IntValue);
|
||||
nftOptDecs : inc(decsOpt, section.Elements[i].IntValue);
|
||||
nftSpaceDecs: inc(decsSpace, section.Elements[i].IntValue);
|
||||
end;
|
||||
inc(i);
|
||||
end;
|
||||
|
||||
// Simple decimal number (nfFixed), followed by eol (case 1)
|
||||
if (i = numEl) then
|
||||
begin
|
||||
// Simple decimal number (nfFixed) (case 1)
|
||||
Result := Result
|
||||
+ FixIntPart(AValue, false, digitsZero, digitsOpt, digitsSpace)
|
||||
+ FixDecimals(AValue, decsZero, decsOpt, decsSpace);
|
||||
el := i;
|
||||
Continue;
|
||||
end;
|
||||
|
||||
// Check for exponential format (case 3)
|
||||
if (section.Elements[i].Token = nftExpChar) then
|
||||
begin
|
||||
inc(i);
|
||||
if (i < numEl) and (section.Elements[i].Token = nftExpSign) then begin
|
||||
expSign := section.Elements[i].TextValue[1];
|
||||
inc(i);
|
||||
if (i < numEl) and (section.Elements[i].Token = nftExpDigits) then
|
||||
expDigits := section.Elements[i].IntValue
|
||||
else
|
||||
InvalidFormat;
|
||||
end else
|
||||
InvalidFormat;
|
||||
numStr := FloatToStrF(AValue, ffExponent, decsZero+decsOpt+decsSpace+1, expDigits, fs);
|
||||
if (abs(AValue) >= 1.0) and (expSign = '-') then
|
||||
Delete(numStr, pos('+', numStr), 1);
|
||||
p := pos('e', Lowercase(numStr));
|
||||
s := copy(numStr, p, Length(numStr)); // E part of the number string
|
||||
numStr := copy(numStr, 1, p-1); // Mantissa of the number string
|
||||
q := pos(fs.DecimalSeparator, numStr);
|
||||
Result := Result
|
||||
+ FixIntPart(StrToFloat(numStr, fs), false, digitsZero, digitsOpt, digitsSpace);
|
||||
if q = 0 then
|
||||
Result := Result + s
|
||||
else
|
||||
Result := Result + FixDecimals(StrToFloat(numStr, fs), decsZero, decsOpt, decsSpace) + s;
|
||||
el := i;
|
||||
Continue;
|
||||
end;
|
||||
end;
|
||||
|
||||
// Check for fraction (case 6)
|
||||
if (section.Elements[i].Token = nftSpace) or
|
||||
((section.Elements[i].Token = nftText) and (section.Elements[i].TextValue = ' ')) then
|
||||
begin
|
||||
inc(i);
|
||||
if (i < numEl) and (section.Elements[i].Token in fracNumTokens) then
|
||||
begin
|
||||
// Process numerator
|
||||
numDigitsZero := 0;
|
||||
numDigitsSpace := 0;
|
||||
numDigitsOpt := 0;
|
||||
while (i < numEl) and (section.Elements[i].Token in fracNumTokens) do
|
||||
begin
|
||||
case section.Elements[i].Token of
|
||||
nftFracNumOptDigit : inc(numDigitsOpt, section.Elements[i].IntValue);
|
||||
nftFracNumZeroDigit : inc(numDigitsZero, section.Elements[i].IntValue);
|
||||
nftFracNumSpaceDigit: inc(numDigitsSpace, section.Elements[i].IntValue);
|
||||
end;
|
||||
inc(i);
|
||||
end;
|
||||
// Skip spaces before '/' symbol, find '/'
|
||||
while (i < numEl) and (section.Elements[i].Token <> nftFracSymbol) do
|
||||
inc(i);
|
||||
// Skip spaces after '/' symbol, find denominator
|
||||
while (i < numEl) and not (section.Elements[i].Token in fracDenomTokens) do
|
||||
inc(i);
|
||||
// Process denominator
|
||||
denomDigitsZero := 0;
|
||||
denomDigitsOpt := 0;
|
||||
denomDigitsSpace := 0;
|
||||
while (i < numEl) and (section.Elements[i].Token in fracDenomTokens) do
|
||||
begin
|
||||
case section.Elements[i].Token of
|
||||
nftFracDenomOptDigit : inc(denomDigitsOpt, section.Elements[i].IntValue);
|
||||
nftFracDenomZeroDigit : inc(denomDigitsZero, section.Elements[i].IntValue);
|
||||
nftFracDenomSpaceDigit: inc(denomDigitsSpace, section.Elements[i].IntValue);
|
||||
end;
|
||||
inc(i);
|
||||
end;
|
||||
|
||||
// Calculate fraction
|
||||
maxNum := Round(IntPower(10, numDigitsOpt+numDigitsZero+numDigitsSpace));
|
||||
maxDenom := Round(IntPower(10, denomDigitsOpt+denomDigitsZero+denomDigitsSpace));
|
||||
if (digitsOpt = 0) and (digitsSpace = 0) and (digitsZero = 0) then
|
||||
begin
|
||||
frint := 0;
|
||||
s := '';
|
||||
end else begin
|
||||
frint := trunc(abs(AValue));
|
||||
AValue := frac(abs(AValue));
|
||||
s := IntToStr(frInt);
|
||||
end;
|
||||
FloatToFraction(abs(AValue), 0.1/maxdenom, maxnum, maxdenom, frnum, frdenom);
|
||||
|
||||
if frInt > 0 then
|
||||
Result := Result +
|
||||
FixIntPart(frInt, false, digitsZero, digitsOpt, digitsSpace);
|
||||
Result := Result +
|
||||
' ' +
|
||||
FixIntPart(frnum, false, numDigitsZero, numDigitsOpt, numDigitsSpace) +
|
||||
'/' +
|
||||
FixIntPart(frdenom, false, denomDigitsZero, denomDigitsOpt, denomDigitsSpace);
|
||||
if isNeg then
|
||||
Result := '-' + Result;
|
||||
el := i;
|
||||
Continue;
|
||||
end;
|
||||
end;
|
||||
|
||||
// Simple decimal number (nfFixed), followed by terminating tokens (case 5)
|
||||
if (i < numEl) and (section.Elements[i].Token in terminatingTokens - [nftEmptyCharWidth]) then
|
||||
begin
|
||||
// Simple decimal number (nfFixed) (case 1)
|
||||
Result := Result
|
||||
+ FixIntPart(AValue, false, digitsZero, digitsOpt, digitsSpace)
|
||||
+ FixDecimals(AValue, decsZero, decsOpt, decsSpace);
|
||||
el := i;
|
||||
Continue;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
nftIntTh: // Format with thousand separator
|
||||
begin
|
||||
terminatingTokens := [nftSpace, nftText, nftPercent, nftCurrSymbol,
|
||||
nftSignBracket, nftEscaped];
|
||||
decsTokens := [nftZeroDecs, nftOptDecs, nftSpaceDecs];
|
||||
decsZero := 0;
|
||||
decsSpace := 0;
|
||||
decsOpt := 0;
|
||||
digitsZero := section.Elements[el].IntValue;
|
||||
i := el+1;
|
||||
if (i < numEl) and (section.Elements[i].Token = nftDecSep) then
|
||||
begin
|
||||
inc(i);
|
||||
while (i < numEl) and (section.Elements[i].Token in [nftZeroDecs, nftOptDecs, nftSpaceDecs]) do
|
||||
begin
|
||||
case section.Elements[i].Token of
|
||||
nftZeroDecs : inc(decsZero, section.Elements[i].IntValue);
|
||||
nftOptDecs : inc(decsOpt, section.Elements[i].IntValue);
|
||||
nftSpaceDecs: inc(decsSpace, section.Elements[i].IntValue);
|
||||
end;
|
||||
inc(i);
|
||||
end;
|
||||
end;
|
||||
Result := Result + FixIntPart(AValue, true, digitsZero, 0, 0)
|
||||
+ FixDecimals(AValue, decsZero, DecsOpt, decsSpace);
|
||||
el := i;
|
||||
Continue;
|
||||
end;
|
||||
|
||||
nftFracNumZeroDigit,
|
||||
nftFracNumOptDigit,
|
||||
nftFracNumSpaceDigit:
|
||||
begin
|
||||
// Process numerator
|
||||
numDigitsZero := 0;
|
||||
numDigitsSpace := 0;
|
||||
numDigitsOpt := 0;
|
||||
i := el;
|
||||
while (i < numEl) and (section.Elements[i].Token in fracNumTokens) do
|
||||
begin
|
||||
case section.Elements[i].Token of
|
||||
nftFracNumOptDigit : inc(numDigitsOpt, section.Elements[i].IntValue);
|
||||
nftFracNumZeroDigit : inc(numDigitsZero, section.Elements[i].IntValue);
|
||||
nftFracNumSpaceDigit: inc(numDigitsSpace, section.Elements[i].IntValue);
|
||||
end;
|
||||
inc(i);
|
||||
end;
|
||||
// Skip spaces before '/' symbol, find '/'
|
||||
while (i < numEl) and (section.Elements[i].Token <> nftFracSymbol) do
|
||||
inc(i);
|
||||
// Skip spaces after '/' symbol, find denominator
|
||||
while (i < numEl) and not (section.Elements[i].Token in fracDenomTokens) do
|
||||
inc(i);
|
||||
// Process denominator
|
||||
denomDigitsZero := 0;
|
||||
denomDigitsOpt := 0;
|
||||
denomDigitsSpace := 0;
|
||||
while (i < numEl) and (section.Elements[i].Token in fracDenomTokens) do
|
||||
begin
|
||||
case section.Elements[i].Token of
|
||||
nftFracDenomOptDigit : inc(denomDigitsOpt, section.Elements[i].IntValue);
|
||||
nftFracDenomZeroDigit : inc(denomDigitsZero, section.Elements[i].IntValue);
|
||||
nftFracDenomSpaceDigit: inc(denomDigitsSpace, section.Elements[i].IntValue);
|
||||
end;
|
||||
inc(i);
|
||||
end;
|
||||
|
||||
// Calculate fraction
|
||||
maxNum := Round(IntPower(10, numDigitsOpt+numDigitsZero+numDigitsSpace));
|
||||
maxDenom := Round(IntPower(10, denomDigitsOpt+denomDigitsZero+denomDigitsSpace));
|
||||
FloatToFraction(abs(AValue), 0.1/maxdenom, MaxInt, maxdenom, frnum, frdenom);
|
||||
if isNeg then
|
||||
Result := Result + '-';
|
||||
Result := Result +
|
||||
FixIntPart(frnum, false, numDigitsZero, numDigitsOpt, numDigitsSpace) +
|
||||
'/' +
|
||||
FixIntPart(frdenom, false, denomDigitsZero, denomDigitsOpt, denomDigitsSpace);
|
||||
el := i-1;
|
||||
end;
|
||||
|
||||
nftSpace:
|
||||
Result := Result + ' ';
|
||||
|
||||
nftText:
|
||||
nftSpace, nftText, nftEscaped, nftCurrSymbol,
|
||||
nftSign, nftSignBracket, nftPercent:
|
||||
Result := Result + section.Elements[el].TextValue;
|
||||
|
||||
nftEscaped:
|
||||
begin
|
||||
inc(el);
|
||||
if el < Length(section.Elements) then
|
||||
Result := Result + section.Elements[el].TextValue;
|
||||
end;
|
||||
|
||||
nftEmptyCharWidth:
|
||||
Result := Result + ' ';
|
||||
|
||||
@ -2873,12 +2919,6 @@ begin
|
||||
nftThSep:
|
||||
Result := Result + fs.ThousandSeparator;
|
||||
|
||||
nftSign, nftSignBracket, nftCurrSymbol:
|
||||
Result := Result + section.Elements[el].TextValue;
|
||||
|
||||
nftPercent:
|
||||
Result := Result + '%';
|
||||
|
||||
nftYear:
|
||||
case section.Elements[el].IntValue of
|
||||
1,
|
||||
@ -2963,9 +3003,9 @@ begin
|
||||
end;
|
||||
Result := Result + s;
|
||||
end;
|
||||
end;
|
||||
end; // case
|
||||
inc(el);
|
||||
end;
|
||||
end; // while
|
||||
end;
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user