
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@4236 8e941d3f-bd1b-0410-a28a-d453659cc2b4
1580 lines
46 KiB
ObjectPascal
1580 lines
46 KiB
ObjectPascal
unit fpsNumFormatParser;
|
||
|
||
{$ifdef fpc}
|
||
{$mode objfpc}{$H+}
|
||
{$endif}
|
||
|
||
interface
|
||
|
||
uses
|
||
Classes, SysUtils, fpstypes, fpsNumFormat;
|
||
|
||
|
||
const
|
||
psOK = 0;
|
||
psErrNoValidColorIndex = 1;
|
||
psErrNoValidCompareNumber = 2;
|
||
psErrUnknownInfoInBrackets = 3;
|
||
psErrConditionalFormattingNotSupported = 4;
|
||
psErrNoUsableFormat = 5;
|
||
psErrNoValidNumberFormat = 6;
|
||
psErrNoValidDateTimeFormat = 7;
|
||
psErrQuoteExpected = 8;
|
||
psErrMultipleCurrSymbols = 9;
|
||
psErrMultipleFracSymbols = 10;
|
||
psErrMultipleExpChars = 11;
|
||
psErrGeneralExpected = 12;
|
||
psAmbiguousSymbol = 13;
|
||
|
||
type
|
||
|
||
{ TsNumFormatParser }
|
||
|
||
TsNumFormatParser = class
|
||
private
|
||
FToken: Char;
|
||
FCurrent: PChar;
|
||
FStart: PChar;
|
||
FEnd: PChar;
|
||
FCurrSection: Integer;
|
||
FStatus: Integer;
|
||
function GetCurrencySymbol: String;
|
||
function GetDecimals: byte;
|
||
function GetFracDenominator: Integer;
|
||
function GetFracInt: Integer;
|
||
function GetFracNumerator: Integer;
|
||
function GetFormatString: String;
|
||
function GetNumFormat: TsNumberFormat;
|
||
function GetParsedSectionCount: Integer;
|
||
function GetParsedSections(AIndex: Integer): TsNumFormatSection;
|
||
procedure SetDecimals(AValue: Byte);
|
||
|
||
protected
|
||
FFormatSettings: TFormatSettings;
|
||
FSections: TsNumFormatSections;
|
||
|
||
{ Administration while scanning }
|
||
procedure AddElement(AToken: TsNumFormatToken; AText: String); overload;
|
||
procedure AddElement(AToken: TsNumFormatToken; AIntValue: Integer=0; AText: String = ''); overload;
|
||
procedure AddElement(AToken: TsNumFormatToken; AFloatValue: Double); overload;
|
||
procedure AddSection;
|
||
procedure DeleteElement(ASection, AIndex: Integer);
|
||
procedure InsertElement(ASection, AIndex: Integer; AToken: TsNumFormatToken; AText: String); overload;
|
||
procedure InsertElement(ASection, AIndex: Integer; AToken: TsNumFormatToken; AIntValue: Integer); overload;
|
||
procedure InsertElement(ASection, AIndex: Integer; AToken: TsNumFormatToken; AFloatValue: Double); overload;
|
||
function NextToken: Char;
|
||
function PrevToken: Char;
|
||
|
||
{ Scanning/parsing }
|
||
procedure ScanAMPM;
|
||
procedure ScanAndCount(ATestChar: Char; out ACount: Integer);
|
||
procedure ScanBrackets;
|
||
procedure ScanCondition(AFirstChar: Char);
|
||
procedure ScanCurrSymbol;
|
||
procedure ScanDateTime;
|
||
procedure ScanFormat;
|
||
procedure ScanGeneral;
|
||
procedure ScanNumber;
|
||
procedure ScanQuotedText;
|
||
// Main scanner
|
||
procedure Parse(const AFormatString: String);
|
||
|
||
{ Analysis while scanning }
|
||
procedure AnalyzeColor(AValue: String);
|
||
function AnalyzeCurrency(const AValue: String): Boolean;
|
||
|
||
{ Analysis after scanning }
|
||
// General
|
||
procedure CheckSections;
|
||
procedure CheckSection(ASection: Integer);
|
||
procedure FixMonthMinuteToken(var ASection: TsNumFormatSection);
|
||
// Format string
|
||
function BuildFormatString: String; virtual;
|
||
|
||
public
|
||
constructor Create(const AFormatString: String;
|
||
const AFormatSettings: TFormatSettings);
|
||
destructor Destroy; override;
|
||
procedure ClearAll;
|
||
function GetDateTimeCode(ASection: Integer): String;
|
||
function IsDateTimeFormat: Boolean;
|
||
function IsTimeFormat: Boolean;
|
||
procedure LimitDecimals;
|
||
|
||
property CurrencySymbol: String read GetCurrencySymbol;
|
||
property Decimals: Byte read GetDecimals write SetDecimals;
|
||
property FormatString: String read GetFormatString;
|
||
property FracDenominator: Integer read GetFracDenominator;
|
||
property FracInt: Integer read GetFracInt;
|
||
property FracNumerator: Integer read GetFracNumerator;
|
||
property NumFormat: TsNumberFormat read GetNumFormat;
|
||
property ParsedSectionCount: Integer read GetParsedSectionCount;
|
||
property ParsedSections[AIndex: Integer]: TsNumFormatSection read GetParsedSections;
|
||
property Status: Integer read FStatus;
|
||
end;
|
||
|
||
|
||
function CreateNumFormatParams(ANumFormatStr: String;
|
||
const AFormatSettings: TFormatSettings): TsNumFormatParams;
|
||
|
||
function ParamsOfNumFormatStr(ANumFormatStr: String;
|
||
const AFormatSettings: TFormatSettings; var AResult: TsNumFormatParams): Integer;
|
||
|
||
|
||
implementation
|
||
|
||
uses
|
||
TypInfo, Math, LazUTF8, fpsutils, fpsCurrency;
|
||
|
||
|
||
function CreateNumFormatParams(ANumFormatStr: String;
|
||
const AFormatSettings: TFormatSettings): TsNumFormatParams;
|
||
begin
|
||
Result := TsNumFormatParams.Create;
|
||
ParamsOfNumFormatStr(ANumFormatStr, AFormatSettings, result);
|
||
end;
|
||
|
||
function ParamsOfNumFormatStr(ANumFormatStr: String;
|
||
const AFormatSettings: TFormatSettings; var AResult: TsNumFormatParams): Integer;
|
||
var
|
||
parser: TsNumFormatParser;
|
||
begin
|
||
Assert(AResult <> nil);
|
||
if ANumFormatstr = 'General' then ANumFormatStr := '';
|
||
parser := TsNumFormatParser.Create(ANumFormatStr, AFormatSettings);
|
||
try
|
||
Result := parser.Status;
|
||
AResult.Sections := parser.FSections;
|
||
finally
|
||
parser.Free;
|
||
end;
|
||
end;
|
||
|
||
|
||
{------------------------------------------------------------------------------}
|
||
{ TsNumFormatParser }
|
||
{------------------------------------------------------------------------------}
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Creates a number format parser for analyzing a formatstring that has been
|
||
read from a spreadsheet file.
|
||
|
||
If ALocalized is true then the formatstring contains localized decimal
|
||
separator etc.
|
||
-------------------------------------------------------------------------------}
|
||
constructor TsNumFormatParser.Create(const AFormatString: String;
|
||
const AFormatSettings: TFormatSettings);
|
||
begin
|
||
inherited Create;
|
||
FFormatSettings := AFormatSettings;
|
||
Parse(AFormatString);
|
||
CheckSections;
|
||
if AFormatString = '' then FSections[0].NumFormat := nfGeneral;
|
||
end;
|
||
|
||
destructor TsNumFormatParser.Destroy;
|
||
begin
|
||
FSections := nil;
|
||
inherited Destroy;
|
||
end;
|
||
|
||
procedure TsNumFormatParser.AddElement(AToken: TsNumFormatToken; AText: String);
|
||
var
|
||
n: Integer;
|
||
begin
|
||
n := Length(FSections[FCurrSection].Elements);
|
||
SetLength(FSections[FCurrSection].Elements, n+1);
|
||
FSections[FCurrSection].Elements[n].Token := AToken;
|
||
FSections[FCurrSection].Elements[n].TextValue := AText;
|
||
end;
|
||
|
||
procedure TsNumFormatParser.AddElement(AToken: TsNumFormatToken;
|
||
AIntValue: Integer=0; AText: String = '');
|
||
var
|
||
n: Integer;
|
||
begin
|
||
n := Length(FSections[FCurrSection].Elements);
|
||
SetLength(FSections[FCurrSection].Elements, n+1);
|
||
FSections[FCurrSection].Elements[n].Token := AToken;
|
||
FSections[FCurrSection].Elements[n].IntValue := AIntValue;
|
||
FSections[FCurrSection].Elements[n].TextValue := AText;
|
||
end;
|
||
|
||
procedure TsNumFormatParser.AddElement(AToken: TsNumFormatToken; AFloatValue: Double); overload;
|
||
var
|
||
n: Integer;
|
||
begin
|
||
n := Length(FSections[FCurrSection].Elements);
|
||
SetLength(FSections[FCurrSection].Elements, n+1);
|
||
FSections[FCurrSection].Elements[n].Token := AToken;
|
||
FSections[FCurrSection].Elements[n].FloatValue := AFloatValue;
|
||
end;
|
||
|
||
procedure TsNumFormatParser.AddSection;
|
||
begin
|
||
FCurrSection := Length(FSections);
|
||
SetLength(FSections, FCurrSection + 1);
|
||
with FSections[FCurrSection] do
|
||
SetLength(Elements, 0);
|
||
end;
|
||
|
||
procedure TsNumFormatParser.AnalyzeColor(AValue: String);
|
||
var
|
||
n: Integer;
|
||
begin
|
||
AValue := lowercase(AValue);
|
||
// Colors
|
||
if AValue = 'red' then
|
||
AddElement(nftColor, ord(scRed))
|
||
else
|
||
if AValue = 'black' then
|
||
AddElement(nftColor, ord(scBlack))
|
||
else
|
||
if AValue = 'blue' then
|
||
AddElement(nftColor, ord(scBlue))
|
||
else
|
||
if AValue = 'white' then
|
||
AddElement(nftColor, ord(scWhite))
|
||
else
|
||
if AValue = 'green' then
|
||
AddElement(nftColor, ord(scGreen))
|
||
else
|
||
if AValue = 'cyan' then
|
||
AddElement(nftColor, ord(scCyan))
|
||
else
|
||
if AValue = 'magenta' then
|
||
AddElement(nftColor, ord(scMagenta))
|
||
else
|
||
if copy(AValue, 1, 5) = 'color' then begin
|
||
AValue := copy(AValue, 6, Length(AValue));
|
||
if not TryStrToInt(trim(AValue), n) then begin
|
||
FStatus := psErrNoValidColorIndex;
|
||
exit;
|
||
end;
|
||
AddElement(nftColor, n);
|
||
end else
|
||
FStatus := psErrUnknownInfoInBrackets;
|
||
end;
|
||
|
||
function TsNumFormatParser.AnalyzeCurrency(const AValue: String): Boolean;
|
||
begin
|
||
if (FFormatSettings.CurrencyString = '') then
|
||
Result := false
|
||
else
|
||
Result := CurrencyRegistered(AValue);
|
||
end;
|
||
|
||
{ Creates a formatstring for all sections.
|
||
Note: this implementation is only valid for the fpc and Excel dialects of
|
||
format string. }
|
||
function TsNumFormatParser.BuildFormatString: String;
|
||
var
|
||
i: Integer;
|
||
begin
|
||
if Length(FSections) > 0 then begin
|
||
Result := BuildFormatStringFromSection(FSections[0]);
|
||
for i:=1 to High(FSections) do
|
||
Result := Result + ';' + BuildFormatStringFromSection(FSections[i]);
|
||
end;
|
||
end;
|
||
|
||
procedure TsNumFormatParser.CheckSections;
|
||
var
|
||
i: Integer;
|
||
begin
|
||
for i:=0 to High(FSections) do
|
||
CheckSection(i);
|
||
|
||
if (Length(FSections) > 1) and (FSections[1].NumFormat = nfCurrencyRed) then
|
||
for i:=0 to High(FSections) do
|
||
if FSections[i].NumFormat = nfCurrency then
|
||
FSections[i].NumFormat := nfCurrencyRed;
|
||
end;
|
||
|
||
procedure TsNumFormatParser.CheckSection(ASection: Integer);
|
||
var
|
||
el, i: Integer;
|
||
section: PsNumFormatSection;
|
||
nfs, nfsTest: String;
|
||
nf: TsNumberFormat;
|
||
formats: set of TsNumberFormat;
|
||
isMonthMinute: Boolean;
|
||
begin
|
||
if FStatus <> psOK then
|
||
exit;
|
||
|
||
section := @FSections[ASection];
|
||
section^.Kind := [];
|
||
|
||
if (ASection = 0) and (Length(FSections) = 1) and (Length(section^.Elements) = 1)
|
||
and (section^.Elements[0].Token = nftGeneral)
|
||
then begin
|
||
section^.NumFormat := nfGeneral;
|
||
exit;
|
||
end;
|
||
|
||
|
||
i := 0;
|
||
isMonthMinute := false;
|
||
|
||
for el := 0 to High(section^.Elements) do
|
||
case section^.Elements[el].Token of
|
||
nftZeroDecs:
|
||
section^.Decimals := section^.Elements[el].IntValue;
|
||
nftIntZeroDigit, nftIntOptDigit, nftIntSpaceDigit:
|
||
i := section^.Elements[el].IntValue;
|
||
nftFracNumSpaceDigit, nftFracNumZeroDigit:
|
||
section^.FracNumerator := section^.Elements[el].IntValue;
|
||
nftFracDenomSpaceDigit, nftFracDenomZeroDigit:
|
||
section^.FracDenominator := section^.Elements[el].IntValue;
|
||
nftFracDenom:
|
||
section^.FracDenominator := -section^.Elements[el].IntValue;
|
||
nftPercent:
|
||
section^.Kind := section^.Kind + [nfkPercent];
|
||
nftExpChar:
|
||
if (nfkExp in section^.Kind) then
|
||
FStatus := psErrMultipleExpChars
|
||
else
|
||
section^.Kind := section^.Kind + [nfkExp];
|
||
nftFactor:
|
||
if section^.Elements[el].IntValue <> 0 then
|
||
begin
|
||
section^.Elements[el].FloatValue := IntPower(10, -3*section^.Elements[el].IntValue);
|
||
section^.Factor := section^.Elements[el].FloatValue;
|
||
section^.Kind := section^.Kind + [nfkHasFactor];
|
||
end;
|
||
nftFracSymbol:
|
||
if (nfkFraction in section^.Kind) then
|
||
FStatus := psErrMultipleFracSymbols
|
||
else
|
||
begin
|
||
section^.Kind := section^.Kind + [nfkFraction];
|
||
section^.FracInt := i;
|
||
end;
|
||
nftCurrSymbol:
|
||
begin
|
||
if (nfkCurrency in section^.Kind) then
|
||
FStatus := psErrMultipleCurrSymbols
|
||
else begin
|
||
section^.Kind := section^.Kind + [nfkCurrency];
|
||
section^.CurrencySymbol := section^.Elements[el].TextValue;
|
||
end;
|
||
end;
|
||
nftYear, nftMonth, nftDay:
|
||
section^.Kind := section^.Kind + [nfkDate];
|
||
nftHour, nftMinute, nftSecond, nftMilliseconds:
|
||
begin
|
||
section^.Kind := section^.Kind + [nfkTime];
|
||
if section^.Elements[el].IntValue < 0 then
|
||
section^.Kind := section^.Kind + [nfkTimeInterval];
|
||
end;
|
||
nftMonthMinute:
|
||
isMonthMinute := true;
|
||
nftColor:
|
||
begin
|
||
section^.Kind := section^.Kind + [nfkHasColor];
|
||
section^.Color := section^.Elements[el].IntValue;
|
||
end;
|
||
nftIntTh:
|
||
section^.Kind := section^.Kind + [nfkHasThSep];
|
||
end;
|
||
|
||
if FStatus <> psOK then
|
||
exit;
|
||
|
||
if (section^.Kind * [nfkDate, nfkTime] <> []) and
|
||
(section^.Kind * [nfkPercent, nfkExp, nfkCurrency, nfkFraction] <> []) then
|
||
begin
|
||
FStatus := psErrNoValidDateTimeFormat;
|
||
exit;
|
||
end;
|
||
|
||
section^.NumFormat := nfCustom;
|
||
|
||
if (section^.Kind * [nfkDate, nfkTime] <> []) or isMonthMinute then
|
||
begin
|
||
FixMonthMinuteToken(section^);
|
||
nfs := GetFormatString;
|
||
if (nfkTimeInterval in section^.Kind) then
|
||
section^.NumFormat := nfTimeInterval
|
||
else
|
||
begin
|
||
formats := [nfShortDateTime, nfLongDate, nfShortDate, nfLongTime,
|
||
nfShortTime, nfLongTimeAM, nfShortTimeAM, nfDayMonth, nfMonthYear];
|
||
for nf in formats do
|
||
begin
|
||
nfsTest := BuildDateTimeFormatString(nf, FFormatSettings);
|
||
if Length(nfsTest) = Length(nfs) then
|
||
begin
|
||
if SameText(nfs, nfsTest) then
|
||
begin
|
||
section^.NumFormat := nf;
|
||
break;
|
||
end;
|
||
for i := 1 to Length(nfsTest) do
|
||
case nfsTest[i] of
|
||
'/': if not (nf in [nfLongTimeAM, nfShortTimeAM]) then
|
||
nfsTest[i] := FFormatSettings.DateSeparator;
|
||
':': nfsTest[i] := FFormatSettings.TimeSeparator;
|
||
'n': nfsTest[i] := 'm';
|
||
end;
|
||
if SameText(nfs, nfsTest) then
|
||
begin
|
||
section^.NumFormat := nf;
|
||
break;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end else
|
||
begin
|
||
nfs := GetFormatString;
|
||
nfsTest := BuildFractionFormatString(section^.FracInt > 0, section^.FracNumerator, section^.FracDenominator);
|
||
if sameText(nfs, nfsTest) then
|
||
section^.NumFormat := nfFraction
|
||
else
|
||
begin
|
||
formats := [nfFixed, nfFixedTh, nfPercentage, nfExp];
|
||
for nf in formats do begin
|
||
nfsTest := BuildNumberFormatString(nf, FFormatSettings, section^.Decimals);
|
||
if SameText(nfs, nfsTest) then
|
||
begin
|
||
section^.NumFormat := nf;
|
||
break;
|
||
end;
|
||
end;
|
||
end;
|
||
if (section^.NumFormat = nfCustom) and (nfkCurrency in section^.Kind) then
|
||
begin
|
||
section^.NumFormat := nfCurrency;
|
||
if section^.Color = scRed then
|
||
section^.NumFormat := nfCurrencyRed;
|
||
end;
|
||
|
||
{
|
||
el := 0;
|
||
while el < Length(section^.Elements) do
|
||
begin
|
||
if IsNumberAt(ASection, el, nf, decs, next) then begin
|
||
section^.Decimals := decs;
|
||
if nf = nfFixedTh then begin
|
||
if (nfkCurrency in section^.Kind) then
|
||
section^.NumFormat := nfCurrency
|
||
else
|
||
section^.NumFormat := nfFixedTh
|
||
end else
|
||
begin
|
||
section^.NumFormat := nf;
|
||
if (nfkPercent in section^.Kind) then
|
||
section^.NumFormat := nfPercentage
|
||
else
|
||
if (nfkExp in section^.Kind) then
|
||
section^.NumFormat := nfExp
|
||
else
|
||
if (nfkCurrency in section^.Kind) then
|
||
section^.NumFormat := nfCurrency
|
||
else
|
||
if (nfkFraction in section^.Kind) and (decs = 0) then begin
|
||
f1 := section^.Elements[el].IntValue; // int part or numerator
|
||
el := next;
|
||
while IsTokenAt(nftSpace, ASection, el) or IsTextAt(' ', ASection, el) do
|
||
inc(el);
|
||
if IsTokenAt(nftFracSymbol, ASection, el) then begin
|
||
inc(el);
|
||
while IsTokenAt(nftSpace, ASection, el) or IsTextAt(' ', aSection, el) do
|
||
inc(el);
|
||
if IsNumberAt(ASection, el, nf, decs, next) and (nf in [nfFixed, nfFraction]) and (decs = 0) then
|
||
begin
|
||
section^.FracInt := 0;
|
||
section^.FracNumerator := f1;
|
||
section^.FracDenominator := section^.Elements[el].IntValue;
|
||
section^.NumFormat := nfFraction;
|
||
end;
|
||
end else
|
||
if IsNumberAt(ASection, el, nf, decs, next) and (nf in [nfFixed, nfFraction]) and (decs = 0) then
|
||
begin
|
||
f2 := section^.Elements[el].IntValue;
|
||
el := next;
|
||
while IsTokenAt(nftSpace, ASection, el) or IsTextAt(' ', ASection, el) do
|
||
inc(el);
|
||
if IsTokenAt(nftFracSymbol, ASection, el) then
|
||
begin
|
||
inc(el);
|
||
while IsTokenAt(nftSpace, ASection, el) or IsTextAt(' ', ASection, el) do
|
||
inc(el);
|
||
if IsNumberAt(ASection, el, nf, decs, next) and (nf in [nfFixed, nfFraction]) and (decs=0) then
|
||
begin
|
||
section^.FracInt := f1;
|
||
section^.FracNumerator := f2;
|
||
section^.FracDenominator := section^.Elements[el].IntValue;
|
||
section^.NumFormat := nfFraction;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
break;
|
||
end else
|
||
if IsTokenAt(nftColor, ASection, el) then
|
||
section^.Color := section^.Elements[el].IntValue;
|
||
inc(el);
|
||
end;
|
||
if (section^.NumFormat = nfCurrency) and (section^.Color = scRed) then
|
||
section^.NumFormat := nfCurrencyRed;
|
||
}
|
||
end;
|
||
end;
|
||
|
||
procedure TsNumFormatParser.ClearAll;
|
||
var
|
||
i, j: Integer;
|
||
begin
|
||
for i:=0 to Length(FSections)-1 do begin
|
||
for j:=0 to Length(FSections[i].Elements) do
|
||
if FSections[i].Elements <> nil then
|
||
FSections[i].Elements[j].TextValue := '';
|
||
FSections[i].Elements := nil;
|
||
FSections[i].CurrencySymbol := '';
|
||
end;
|
||
FSections := nil;
|
||
end;
|
||
|
||
procedure TsNumFormatParser.DeleteElement(ASection, AIndex: Integer);
|
||
var
|
||
i, n: Integer;
|
||
begin
|
||
n := Length(FSections[ASection].Elements);
|
||
for i:= AIndex+1 to n-1 do
|
||
FSections[ASection].Elements[i-1] := FSections[ASection].Elements[i];
|
||
SetLength(FSections[ASection].Elements, n-1);
|
||
end;
|
||
|
||
{ Identify the ambiguous "m" token ("month" or "minute") }
|
||
procedure TsNumFormatParser.FixMonthMinuteToken(var ASection: TsNumFormatSection);
|
||
var
|
||
i, j: Integer;
|
||
|
||
// Finds the previous date/time element skipping spaces, date/time sep etc.
|
||
function PrevDateTimeElement(j: Integer): Integer;
|
||
begin
|
||
Result := -1;
|
||
dec(j);
|
||
while (j >= 0) do begin
|
||
with ASection.Elements[j] do
|
||
if Token in [nftYear, nftMonth, nftDay, nftHour, nftMinute, nftSecond] then
|
||
begin
|
||
Result := j;
|
||
exit;
|
||
end;
|
||
dec(j);
|
||
end;
|
||
end;
|
||
|
||
// Finds the next date/time element skipping spaces, date/time sep etc.
|
||
function NextDateTimeElement(j: Integer): Integer;
|
||
begin
|
||
Result := -1;
|
||
inc(j);
|
||
while (j < Length(ASection.Elements)) do begin
|
||
with ASection.Elements[j] do
|
||
if Token in [nftYear, nftMonth, nftDay, nftHour, nftMinute, nftSecond] then
|
||
begin
|
||
Result := j;
|
||
exit;
|
||
end;
|
||
inc(j);
|
||
end;
|
||
end;
|
||
|
||
begin
|
||
for i:=0 to High(ASection.Elements) do
|
||
begin
|
||
// Find index of nftMonthMinute token...
|
||
if ASection.Elements[i].Token = nftMonthMinute then begin
|
||
// ... and, using its neighbors, decide whether it is a month or a minute.
|
||
j := NextDateTimeElement(i);
|
||
if j <> -1 then
|
||
case ASection.Elements[j].Token of
|
||
nftDay, nftYear:
|
||
begin
|
||
ASection.Elements[i].Token := nftMonth;
|
||
Continue;
|
||
end;
|
||
nftSecond:
|
||
begin
|
||
ASection.Elements[i].Token := nftMinute;
|
||
Continue;
|
||
end;
|
||
end;
|
||
j := PrevDateTimeElement(i);
|
||
if j <> -1 then
|
||
case ASection.Elements[j].Token of
|
||
nftDay, nftYear:
|
||
begin
|
||
ASection.Elements[i].Token := nftMonth;
|
||
Continue;
|
||
end;
|
||
nftHour:
|
||
begin
|
||
ASection.Elements[i].Token := nftMinute;
|
||
Continue;
|
||
end;
|
||
end;
|
||
|
||
// If we get here the token is isolated. In this case we assume
|
||
// that it is a month - that's the way Excel does it when reading files
|
||
// (for editing of a worksheet, however, Excel distinguishes between
|
||
// uppercase "M" for "month" and lowercase "m" for "minute".)
|
||
ASection.Elements[i].Token := nftMonth;
|
||
Include(ASection.Kind, nfkDate);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
procedure TsNumFormatParser.InsertElement(ASection, AIndex: Integer;
|
||
AToken: TsNumFormatToken; AText: String);
|
||
var
|
||
i, n: Integer;
|
||
begin
|
||
n := Length(FSections[ASection].Elements);
|
||
SetLength(FSections[ASection].Elements, n+1);
|
||
for i:= n-1 downto AIndex+1 do
|
||
FSections[ASection].Elements[i+1] := FSections[ASection].Elements[i];
|
||
FSections[ASection].Elements[AIndex+1].Token := AToken;
|
||
FSections[ASection].Elements[AIndex+1].TextValue := AText;
|
||
end;
|
||
|
||
procedure TsNumFormatParser.InsertElement(ASection, AIndex: Integer;
|
||
AToken: TsNumFormatToken; AIntValue: Integer);
|
||
var
|
||
i, n: Integer;
|
||
begin
|
||
n := Length(FSections[ASection].Elements);
|
||
SetLength(FSections[ASection].Elements, n+1);
|
||
for i:= n-1 downto AIndex+1 do
|
||
FSections[ASection].Elements[i+1] := FSections[ASection].Elements[i];
|
||
FSections[ASection].Elements[AIndex+1].Token := AToken;
|
||
FSections[ASection].Elements[AIndex+1].IntValue := AIntValue;
|
||
end;
|
||
|
||
procedure TsNumFormatParser.InsertElement(ASection, AIndex: Integer;
|
||
AToken: TsNumFormatToken; AFloatValue: Double);
|
||
var
|
||
i, n: Integer;
|
||
begin
|
||
n := Length(FSections[ASection].Elements);
|
||
SetLength(FSections[ASection].Elements, n+1);
|
||
for i:= n-1 downto AIndex+1 do
|
||
FSections[ASection].Elements[i+1] := FSections[ASection].Elements[i];
|
||
FSections[ASection].Elements[AIndex+1].Token := AToken;
|
||
FSections[ASection].Elements[AIndex+1].FloatValue := AFloatValue;
|
||
end;
|
||
|
||
function TsNumFormatParser.GetFormatString: String;
|
||
begin
|
||
Result := BuildFormatString;
|
||
end;
|
||
|
||
{ Extracts the currency symbol form the formatting sections. It is assumed that
|
||
all two or three sections of the currency/accounting format use the same
|
||
currency symbol, otherwise it would be custom format anyway which ignores
|
||
the currencysymbol value. }
|
||
function TsNumFormatParser.GetCurrencySymbol: String;
|
||
begin
|
||
if Length(FSections) > 0 then
|
||
Result := FSections[0].CurrencySymbol
|
||
else
|
||
Result := '';
|
||
end;
|
||
|
||
{ Creates a string which summarizes the date/time formats in the given section.
|
||
The string contains a 'y' for a nftYear, a 'm' for a nftMonth, a
|
||
'd' for a nftDay, a 'h' for a nftHour, a 'n' for a nftMinute, a 's' for a
|
||
nftSeconds, and a 'z' for a nftMilliseconds token. The order is retained.
|
||
Needed for biff2 }
|
||
function TsNumFormatParser.GetDateTimeCode(ASection: Integer): String;
|
||
var
|
||
i: Integer;
|
||
begin
|
||
Result := '';
|
||
if ASection < Length(FSections) then
|
||
with FSections[ASection] do begin
|
||
i := 0;
|
||
while i < Length(Elements) do begin
|
||
case Elements[i].Token of
|
||
nftYear : Result := Result + 'y';
|
||
nftMonth : Result := Result + 'm';
|
||
nftDay : Result := Result + 'd';
|
||
nftHour : Result := Result + 'h';
|
||
nftMinute : Result := Result + 'n';
|
||
nftSecond : Result := Result + 's';
|
||
nftMilliSeconds: Result := Result + 'z';
|
||
end;
|
||
inc(i);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
{ Extracts the number of decimals from the sections. Since they are needed only
|
||
for default formats having only a single section, only the first section is
|
||
considered. In case of currency/accounting having two or three sections, it is
|
||
assumed that all sections have the same decimals count, otherwise it would not
|
||
be a standard format. }
|
||
function TsNumFormatParser.GetDecimals: Byte;
|
||
begin
|
||
if Length(FSections) > 0 then
|
||
Result := FSections[0].Decimals
|
||
else
|
||
Result := 0;
|
||
end;
|
||
|
||
function TsNumFormatParser.GetFracDenominator: Integer;
|
||
begin
|
||
if Length(FSections) > 0 then
|
||
Result := FSections[0].FracDenominator
|
||
else
|
||
Result := 0;
|
||
end;
|
||
|
||
function TsNumFormatParser.GetFracInt: Integer;
|
||
begin
|
||
if Length(FSections) > 0 then
|
||
Result := FSections[0].FracInt
|
||
else
|
||
Result := 0;
|
||
end;
|
||
|
||
function TsNumFormatParser.GetFracNumerator: Integer;
|
||
begin
|
||
if Length(FSections) > 0 then
|
||
Result := FSections[0].FracNumerator
|
||
else
|
||
Result := 0;
|
||
end;
|
||
|
||
{ Tries to extract a common builtin number format from the sections. If there
|
||
are multiple sections, it is always a custom format, except for Currency and
|
||
Accounting. }
|
||
function TsNumFormatParser.GetNumFormat: TsNumberFormat;
|
||
begin
|
||
if Length(FSections) = 0 then
|
||
result := nfGeneral
|
||
else begin
|
||
Result := FSections[0].NumFormat;
|
||
if (Result = nfCurrency) then begin
|
||
if Length(FSections) = 2 then begin
|
||
Result := FSections[1].NumFormat;
|
||
if FSections[1].CurrencySymbol <> FSections[0].CurrencySymbol then begin
|
||
Result := nfCustom;
|
||
exit;
|
||
end;
|
||
if (FSections[0].NumFormat in [nfCurrency, nfCurrencyRed]) and
|
||
(FSections[1].NumFormat in [nfCurrency, nfCurrencyRed])
|
||
then
|
||
exit;
|
||
end else
|
||
if Length(FSections) = 3 then begin
|
||
Result := FSections[1].NumFormat;
|
||
if (FSections[0].CurrencySymbol <> FSections[1].CurrencySymbol) or
|
||
(FSections[1].CurrencySymbol <> FSections[2].CurrencySymbol)
|
||
then begin
|
||
Result := nfCustom;
|
||
exit;
|
||
end;
|
||
if (FSections[0].NumFormat in [nfCurrency, nfCurrencyRed]) and
|
||
(FSections[1].NumFormat in [nfCurrency, nfCurrencyRed]) and
|
||
(FSections[2].NumFormat in [nfCurrency, nfCurrencyRed])
|
||
then
|
||
exit;
|
||
end;
|
||
Result := nfCustom;
|
||
exit;
|
||
end;
|
||
if Length(FSections) > 1 then
|
||
Result := nfCustom;
|
||
end;
|
||
end;
|
||
|
||
function TsNumFormatParser.GetParsedSectionCount: Integer;
|
||
begin
|
||
Result := Length(FSections);
|
||
end;
|
||
|
||
function TsNumFormatParser.GetParsedSections(AIndex: Integer): TsNumFormatSection;
|
||
begin
|
||
Result := FSections[AIndex];
|
||
end;
|
||
{
|
||
function TsNumFormatParser.GetTokenIntValueAt(AToken: TsNumFormatToken;
|
||
ASection, AIndex: Integer): Integer;
|
||
begin
|
||
if IsTokenAt(AToken, ASection, AIndex) then
|
||
Result := FSections[ASection].Elements[AIndex].IntValue
|
||
else
|
||
Result := -1;
|
||
end;
|
||
}
|
||
{ Returns true if the format elements contain at least one date/time token }
|
||
function TsNumFormatParser.IsDateTimeFormat: Boolean;
|
||
var
|
||
section: TsNumFormatSection;
|
||
begin
|
||
for section in FSections do
|
||
if section.Kind * [nfkDate, nfkTime] <> [] then
|
||
begin
|
||
Result := true;
|
||
exit;
|
||
end;
|
||
Result := false;
|
||
end;
|
||
{
|
||
function TsNumFormatParser.IsNumberAt(ASection, AIndex: Integer;
|
||
out ANumFormat: TsNumberFormat; out ADecimals: Byte;
|
||
out ANextIndex: Integer): Boolean;
|
||
var
|
||
token: TsNumFormatToken;
|
||
begin
|
||
if (ASection > High(FSections)) or (AIndex > High(FSections[ASection].Elements))
|
||
then begin
|
||
Result := false;
|
||
ANextIndex := AIndex;
|
||
exit;
|
||
end;
|
||
|
||
Result := true;
|
||
ANumFormat := nfCustom;
|
||
ADecimals := 0;
|
||
token := FSections[ASection].Elements[AIndex].Token;
|
||
|
||
if token in [nftFracNumOptDigit, nftFracNumZeroDigit, nftFracNumSpaceDigit,
|
||
nftFracDenomOptDigit, nftFracDenomZeroDigit, nftFracDenomSpaceDigit] then
|
||
begin
|
||
ANumFormat := nfFraction;
|
||
ANextIndex := AIndex + 1;
|
||
exit;
|
||
end;
|
||
|
||
if (token = nftIntTh) and (FSections[ASection].Elements[AIndex].IntValue = 1) then // '#,##0'
|
||
ANumFormat := nfFixedTh
|
||
else
|
||
if (token = nftIntZeroDigit) and (FSections[ASection].Elements[AIndex].IntValue = 1) then // '0'
|
||
ANumFormat := nfFixed;
|
||
|
||
if (token in [nftIntTh, nftIntZeroDigit, nftIntOptDigit, nftIntSpaceDigit]) then
|
||
begin
|
||
if IsTokenAt(nftDecSep, ASection, AIndex+1) then
|
||
begin
|
||
if AIndex + 2 < Length(FSections[ASection].Elements) then
|
||
begin
|
||
token := FSections[ASection].Elements[AIndex+2].Token;
|
||
if (token in [nftZeroDecs, nftOptDecs, nftSpaceDecs]) then
|
||
begin
|
||
ANextIndex := AIndex + 3;
|
||
ADecimals := FSections[ASection].Elements[AIndex+2].IntValue;
|
||
if (token <> nftZeroDecs) then
|
||
ANumFormat := nfCustom;
|
||
exit;
|
||
end;
|
||
end;
|
||
end else
|
||
if IsTokenAt(nftSpace, ASection, AIndex+1) then
|
||
begin
|
||
ANumFormat := nfFraction;
|
||
ANextIndex := AIndex + 1;
|
||
exit;
|
||
end else
|
||
begin
|
||
ANextIndex := AIndex + 1;
|
||
exit;
|
||
end;
|
||
end;
|
||
|
||
ANextIndex := AIndex;
|
||
Result := false;
|
||
end;
|
||
|
||
function TsNumFormatParser.IsTextAt(AText: String; ASection, AIndex: Integer): Boolean;
|
||
begin
|
||
Result := IsTokenAt(nftText, ASection, AIndex) and
|
||
(FSections[ASection].Elements[AIndex].TextValue = AText);
|
||
end;
|
||
}
|
||
{ Returns true if the format elements contain only time, no date tokens. }
|
||
function TsNumFormatParser.IsTimeFormat: Boolean;
|
||
var
|
||
section: TsNumFormatSection;
|
||
begin
|
||
for section in FSections do
|
||
if (nfkTime in section.Kind) then
|
||
begin
|
||
Result := true;
|
||
exit;
|
||
end;
|
||
Result := false;
|
||
end;
|
||
{
|
||
function TsNumFormatParser.IsTokenAt(AToken: TsNumFormatToken;
|
||
ASection, AIndex: Integer): Boolean;
|
||
begin
|
||
Result := (ASection < Length(FSections)) and
|
||
(AIndex < Length(FSections[ASection].Elements)) and
|
||
(FSections[ASection].Elements[AIndex].Token = AToken);
|
||
end;
|
||
}
|
||
{ Limits the decimals to 0 or 2, as required by Excel2. }
|
||
procedure TsNumFormatParser.LimitDecimals;
|
||
var
|
||
i, j: Integer;
|
||
begin
|
||
for j:=0 to High(FSections) do
|
||
for i:=0 to High(FSections[j].Elements) do
|
||
if FSections[j].Elements[i].Token = nftZeroDecs then
|
||
if FSections[j].Elements[i].IntValue > 0 then
|
||
FSections[j].Elements[i].IntValue := 2;
|
||
end;
|
||
|
||
function TsNumFormatParser.NextToken: Char;
|
||
begin
|
||
if FCurrent < FEnd then begin
|
||
inc(FCurrent);
|
||
Result := FCurrent^;
|
||
end else
|
||
Result := #0;
|
||
end;
|
||
|
||
function TsNumFormatParser.PrevToken: Char;
|
||
begin
|
||
if FCurrent > nil then begin
|
||
dec(FCurrent);
|
||
Result := FCurrent^;
|
||
end else
|
||
Result := #0;
|
||
end;
|
||
|
||
procedure TsNumFormatParser.Parse(const AFormatString: String);
|
||
begin
|
||
FStatus := psOK;
|
||
|
||
AddSection;
|
||
if (AFormatString = '') then
|
||
begin
|
||
AddElement(nftGeneral);
|
||
exit;
|
||
end;
|
||
|
||
FStart := @AFormatString[1];
|
||
FEnd := FStart + Length(AFormatString);
|
||
FCurrent := FStart;
|
||
FToken := FCurrent^;
|
||
while (FCurrent < FEnd) and (FStatus = psOK) do begin
|
||
case FToken of
|
||
'G','g': ScanGeneral;
|
||
'[': ScanBrackets;
|
||
'"': ScanQuotedText;
|
||
':': AddElement(nftDateTimeSep, ':');
|
||
';': AddSection;
|
||
else ScanFormat;
|
||
end;
|
||
FToken := NextToken;
|
||
end;
|
||
end;
|
||
|
||
{ Scans an AM/PM sequence (or AMPM or A/P).
|
||
At exit, cursor is a next character }
|
||
procedure TsNumFormatParser.ScanAMPM;
|
||
var
|
||
s: String;
|
||
el: Integer;
|
||
begin
|
||
s := '';
|
||
while (FCurrent < FEnd) do begin
|
||
if (FToken in ['A', 'a', 'P', 'p', 'm', 'M', '/']) then
|
||
s := s + FToken
|
||
else
|
||
break;
|
||
FToken := NextToken;
|
||
end;
|
||
if s <> '' then
|
||
begin
|
||
AddElement(nftAMPM, s);
|
||
// Tag the hour element for AM/PM format needed
|
||
el := High(FSections[FCurrSection].Elements)-1;
|
||
for el := High(FSections[FCurrSection].Elements)-1 downto 0 do
|
||
if FSections[FCurrSection].Elements[el].Token = nftHour then
|
||
begin
|
||
FSections[FCurrSection].Elements[el].TextValue := 'AM';
|
||
break;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
{ Counts the number of characters equal to ATestChar. Stops at the next
|
||
different character. This is also where the cursor is at exit. }
|
||
procedure TsNumFormatParser.ScanAndCount(ATestChar: Char; out ACount: Integer);
|
||
begin
|
||
ACount := 0;
|
||
if FToken <> ATestChar then
|
||
exit;
|
||
repeat
|
||
inc(ACount);
|
||
FToken := NextToken;
|
||
until (FToken <> ATestChar) or (FCurrent >= FEnd);
|
||
end;
|
||
|
||
{ Extracts the text between square brackets. This can be
|
||
- a time duration like [hh]
|
||
- a condition, like [>= 2.0]
|
||
- a currency symbol like [$<24>-409]
|
||
- a color like [red] or [color25]
|
||
The procedure is left with the cursor at ']' }
|
||
procedure TsNumFormatParser.ScanBrackets;
|
||
var
|
||
s: String;
|
||
n: Integer;
|
||
prevtok: Char;
|
||
isText: Boolean;
|
||
begin
|
||
s := '';
|
||
isText := false;
|
||
FToken := NextToken; // Cursor was at '['
|
||
while (FCurrent < FEnd) and (FStatus = psOK) do begin
|
||
case FToken of
|
||
'h', 'H', 'm', 'M', 'n', 'N', 's', 'S':
|
||
if isText then
|
||
s := s + FToken
|
||
else
|
||
begin
|
||
prevtok := FToken;
|
||
ScanAndCount(FToken, n);
|
||
if (FToken in [']', #0]) then begin
|
||
case prevtok of
|
||
'h', 'H' : AddElement(nftHour, -n);
|
||
'm', 'M', 'n', 'N': AddElement(nftMinute, -n);
|
||
's', 'S' : AddElement(nftSecond, -n);
|
||
end;
|
||
break;
|
||
end else
|
||
FStatus := psErrUnknownInfoInBrackets;
|
||
end;
|
||
|
||
'<', '>', '=':
|
||
begin
|
||
ScanCondition(FToken);
|
||
if FToken = ']' then
|
||
break
|
||
else
|
||
FStatus := psErrUnknownInfoInBrackets;
|
||
end;
|
||
|
||
'$':
|
||
begin
|
||
ScanCurrSymbol;
|
||
if FToken = ']' then
|
||
break
|
||
else
|
||
FStatus := psErrUnknownInfoInBrackets;
|
||
end;
|
||
|
||
']':
|
||
begin
|
||
AnalyzeColor(s);
|
||
break;
|
||
end;
|
||
|
||
else
|
||
s := s + FToken;
|
||
isText := true;
|
||
end;
|
||
FToken := NextToken;
|
||
end;
|
||
end;
|
||
|
||
{ Scans a condition like [>=2.0]. Starts after the "[" and ends before at "]".
|
||
Returns first character after the number (spaces allowed). }
|
||
procedure TsNumFormatParser.ScanCondition(AFirstChar: Char);
|
||
var
|
||
s: String;
|
||
// op: TsCompareOperation;
|
||
value: Double;
|
||
res: Integer;
|
||
begin
|
||
s := AFirstChar;
|
||
FToken := NextToken;
|
||
if FToken in ['>', '<', '='] then s := s + FToken else FToken := PrevToken;
|
||
{
|
||
if s = '=' then op := coEqual else
|
||
if s = '<>' then op := coNotEqual else
|
||
if s = '<' then op := coLess else
|
||
if s = '>' then op := coGreater else
|
||
if s = '<=' then op := coLessEqual else
|
||
if s = '>=' then op := coGreaterEqual
|
||
else begin
|
||
FStatus := psErrUnknownInfoInBrackets;
|
||
FToken := #0;
|
||
exit;
|
||
end;
|
||
}
|
||
while (FToken = ' ') and (FCurrent < FEnd) do
|
||
FToken := NextToken;
|
||
|
||
if FCurrent >= FEnd then begin
|
||
FStatus := psErrUnknownInfoInBrackets;
|
||
FToken := #0;
|
||
exit;
|
||
end;
|
||
|
||
s := FToken;
|
||
while (FCurrent < FEnd) and (FToken in ['+', '-', '.', '0'..'9']) do begin
|
||
FToken := NextToken;
|
||
s := s + FToken;
|
||
end;
|
||
val(s, value, res);
|
||
if res <> 0 then begin
|
||
FStatus := psErrUnknownInfoInBrackets;
|
||
FToken := #0;
|
||
exit;
|
||
end;
|
||
|
||
while (FCurrent < FEnd) and (FToken = ' ') do
|
||
FToken := NextToken;
|
||
if FToken = ']' then
|
||
AddElement(nftCompareOp, value)
|
||
else begin
|
||
FStatus := psErrUnknownInfoInBrackets;
|
||
FToken := #0;
|
||
end;
|
||
end;
|
||
|
||
{ Scans to end of a symbol like [$EUR-409], starting after the $ and ending at
|
||
the "]".
|
||
After the "$" follows the currency symbol, after the "-" country information }
|
||
procedure TsNumFormatParser.ScanCurrSymbol;
|
||
var
|
||
s: String;
|
||
begin
|
||
s := '';
|
||
FToken := NextToken;
|
||
while (FCurrent < FEnd) and not (FToken in ['-', ']']) do begin
|
||
s := s + FToken;
|
||
FToken := NextToken;
|
||
end;
|
||
if s <> '' then
|
||
AddElement(nftCurrSymbol, s);
|
||
if FToken <> ']' then begin
|
||
FToken := NextToken;
|
||
while (FCurrent < FEnd) and (FToken <> ']') do begin
|
||
s := s + FToken;
|
||
FToken := NextToken;
|
||
end;
|
||
if s <> '' then
|
||
AddElement(nftCountry, s);
|
||
end;
|
||
end;
|
||
|
||
{ Scans a date/time format. Procedure is left with the cursor at the last char
|
||
of the date/time format. }
|
||
procedure TsNumFormatParser.ScanDateTime;
|
||
var
|
||
n: Integer;
|
||
token: Char;
|
||
begin
|
||
while (FCurrent < FEnd) and (FStatus = psOK) do begin
|
||
case FToken of
|
||
'\': // means that the next character is taken literally
|
||
begin
|
||
FToken := NextToken; // skip the "\"...
|
||
AddElement(nftEscaped, FToken);
|
||
FToken := NextToken;
|
||
end;
|
||
'Y', 'y':
|
||
begin
|
||
ScanAndCount(FToken, n);
|
||
AddElement(nftYear, n);
|
||
end;
|
||
'm', 'M', 'n', 'N':
|
||
begin
|
||
token := FToken;
|
||
ScanAndCount(FToken, n);
|
||
AddElement(nftMonthMinute, n, token); // Decide on minute or month later
|
||
end;
|
||
'D', 'd':
|
||
begin
|
||
ScanAndCount(FToken, n);
|
||
AddElement(nftDay, n);
|
||
end;
|
||
'H', 'h':
|
||
begin
|
||
ScanAndCount(FToken, n);
|
||
AddElement(nftHour, n);
|
||
end;
|
||
'S', 's':
|
||
begin
|
||
ScanAndCount(FToken, n);
|
||
AddElement(nftSecond, n);
|
||
end;
|
||
'/', ':':
|
||
begin
|
||
AddElement(nftDateTimeSep, FToken);
|
||
FToken := NextToken;
|
||
end;
|
||
'.':
|
||
begin
|
||
token := NextToken;
|
||
if token in ['z', '0'] then begin
|
||
AddElement(nftDecSep, FToken);
|
||
FToken := NextToken;
|
||
ScanAndCount(FToken, n);
|
||
AddElement(nftMilliseconds, n);
|
||
end else begin
|
||
AddElement(nftDateTimeSep, FToken);
|
||
FToken := token;
|
||
end;
|
||
end;
|
||
'[':
|
||
begin
|
||
ScanBrackets;
|
||
FToken := NextToken;
|
||
end;
|
||
'A', 'a':
|
||
ScanAMPM;
|
||
',', '-':
|
||
begin
|
||
AddElement(nftText, FToken);
|
||
FToken := NextToken;
|
||
end
|
||
else
|
||
// char pointer must be at end of date/time mask.
|
||
FToken := PrevToken;
|
||
Exit;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
procedure TsNumFormatParser.ScanFormat;
|
||
var
|
||
done: Boolean;
|
||
n: Integer;
|
||
uch: Cardinal;
|
||
begin
|
||
done := false;
|
||
while (FCurrent < FEnd) and (FStatus = psOK) and (not done) do begin
|
||
case FToken of
|
||
'\': // Excel: add next character literally
|
||
begin
|
||
FToken := NextToken;
|
||
AddElement(nftText, FToken);
|
||
end;
|
||
'*': // Excel: repeat next character to fill cell. For accounting format.
|
||
begin
|
||
FToken := NextToken;
|
||
AddElement(nftRepeat, FToken);
|
||
end;
|
||
'_': // Excel: Leave width of next character empty
|
||
begin
|
||
FToken := NextToken;
|
||
uch := UTF8CharacterToUnicode(FCurrent, n);
|
||
if n > 1 then
|
||
begin
|
||
AddElement(nftEmptyCharWidth, UnicodeToUTF8(uch));
|
||
inc(FCurrent, n-1);
|
||
FToken := NextToken;
|
||
Continue;
|
||
end else
|
||
AddElement(nftEmptyCharWidth, FToken);
|
||
end;
|
||
'@': // Excel: Indicates text format
|
||
begin
|
||
AddElement(nftTextFormat, FToken);
|
||
end;
|
||
'"':
|
||
ScanQuotedText;
|
||
'(', ')':
|
||
AddElement(nftSignBracket, FToken);
|
||
'0', '#', '?', '.', ',', '-':
|
||
ScanNumber;
|
||
'y', 'Y', 'm', 'M', 'd', 'D', 'h', 'N', 'n', 's':
|
||
ScanDateTime;
|
||
'[':
|
||
ScanBrackets;
|
||
' ':
|
||
AddElement(nftSpace, FToken);
|
||
'A', 'a':
|
||
begin
|
||
ScanAMPM;
|
||
FToken := PrevToken;
|
||
end;
|
||
'G', 'g':
|
||
ScanGeneral;
|
||
';': // End of the section. Important: Cursor must stay on ';'
|
||
begin
|
||
AddSection;
|
||
Exit;
|
||
end;
|
||
else
|
||
uch := UTF8CharacterToUnicode(FCurrent, n);
|
||
if n > 1 then
|
||
begin
|
||
AddElement(nftText, UnicodeToUTF8(uch));
|
||
inc(FCurrent, n-1);
|
||
end else
|
||
AddElement(nftText, FToken);
|
||
end;
|
||
FToken := NextToken;
|
||
end;
|
||
end;
|
||
|
||
{ Scans for the word "General", it may be used like other tokens }
|
||
procedure TsNumFormatParser.ScanGeneral;
|
||
begin
|
||
FStatus := psErrGeneralExpected;
|
||
FToken := NextToken;
|
||
if not (FToken in ['e', 'E']) then exit;
|
||
FToken := NextToken;
|
||
if not (FToken in ['n', 'N']) then exit;
|
||
FToken := NextToken;
|
||
if not (FToken in ['e', 'E']) then exit;
|
||
FToken := NextToken;
|
||
if not (FToken in ['r', 'R']) then exit;
|
||
FToken := NextToken;
|
||
if not (FToken in ['a', 'A']) then exit;
|
||
FToken := NextToken;
|
||
if not (FToken in ['l', 'L']) then exit;
|
||
AddElement(nftGeneral);
|
||
FStatus := psOK;
|
||
end;
|
||
|
||
{ Scans a floating point format. Procedure is left with the cursor at the last
|
||
character of the format. }
|
||
procedure TsNumFormatParser.ScanNumber;
|
||
var
|
||
hasDecSep: Boolean;
|
||
isFrac: Boolean;
|
||
n, m: Integer;
|
||
el: Integer;
|
||
savedCurrent: PChar;
|
||
thSep: Char;
|
||
begin
|
||
hasDecSep := false;
|
||
isFrac := false;
|
||
thSep := ',';
|
||
while (FCurrent < FEnd) and (FStatus = psOK) do begin
|
||
case FToken of
|
||
',': AddElement(nftThSep, ',');
|
||
'.': begin
|
||
AddElement(nftDecSep, '.');
|
||
hasDecSep := true;
|
||
end;
|
||
'#': begin
|
||
ScanAndCount('#', n);
|
||
savedCurrent := FCurrent;
|
||
if not (hasDecSep or isFrac) and (n = 1) and (FToken = thSep) then
|
||
begin
|
||
m := 0;
|
||
FToken := NextToken;
|
||
ScanAndCount('#', n);
|
||
case n of
|
||
0: begin
|
||
ScanAndCount('0', n);
|
||
ScanAndCount(thSep, m);
|
||
FToken := prevToken;
|
||
if n = 3 then
|
||
AddElement(nftIntTh, 3, ',')
|
||
else
|
||
FCurrent := savedCurrent;
|
||
end;
|
||
1: begin
|
||
ScanAndCount('0', n);
|
||
ScanAndCount(thSep, m);
|
||
FToken := prevToken;
|
||
if n = 2 then
|
||
AddElement(nftIntTh, 2, ',')
|
||
else
|
||
FCurrent := savedCurrent;
|
||
end;
|
||
2: begin
|
||
ScanAndCount('0', n);
|
||
ScanAndCount(thSep, m);
|
||
FToken := prevToken;
|
||
if (n = 1) then
|
||
AddElement(nftIntTh, 1, ',')
|
||
else
|
||
FCurrent := savedCurrent;
|
||
end;
|
||
end;
|
||
if m > 0 then
|
||
AddElement(nftFactor, m, thSep);
|
||
end else
|
||
begin
|
||
FToken := PrevToken;
|
||
if isFrac then
|
||
AddElement(nftFracDenomOptDigit, n)
|
||
else
|
||
if hasDecSep then
|
||
AddElement(nftOptDecs, n)
|
||
else
|
||
AddElement(nftIntOptDigit, n);
|
||
end;
|
||
end;
|
||
'0': begin
|
||
ScanAndCount('0', n);
|
||
ScanAndCount(thSep, m);
|
||
FToken := PrevToken;
|
||
if hasDecSep then
|
||
AddElement(nftZeroDecs, n)
|
||
else
|
||
if isFrac then
|
||
AddElement(nftFracDenomZeroDigit, n)
|
||
else
|
||
AddElement(nftIntZeroDigit, n);
|
||
if m > 0 then
|
||
AddElement(nftFactor, m, thSep);
|
||
end;
|
||
'1'..'9':
|
||
begin
|
||
if isFrac then
|
||
begin
|
||
n := 0;
|
||
while (FToken in ['1'..'9','0']) do
|
||
begin
|
||
n := n*10 + StrToInt(FToken);
|
||
FToken := nextToken;
|
||
end;
|
||
AddElement(nftFracDenom, n);
|
||
end else
|
||
AddElement(nftText, FToken);
|
||
end;
|
||
'?': begin
|
||
ScanAndCount('?', n);
|
||
FToken := PrevToken;
|
||
if hasDecSep then
|
||
AddElement(nftSpaceDecs, n)
|
||
else
|
||
if isFrac then
|
||
AddElement(nftFracDenomSpaceDigit, n)
|
||
else
|
||
AddElement(nftIntSpaceDigit, n);
|
||
end;
|
||
'E', 'e':
|
||
begin
|
||
AddElement(nftExpChar, FToken);
|
||
FToken := NextToken;
|
||
if FToken in ['+', '-'] then
|
||
AddElement(nftExpSign, FToken);
|
||
FToken := NextToken;
|
||
if FToken = '0' then begin
|
||
ScanAndCount('0', n);
|
||
FToken := PrevToken;
|
||
AddElement(nftExpDigits, n);
|
||
end;
|
||
end;
|
||
'+', '-':
|
||
AddElement(nftSign, FToken);
|
||
'%': AddElement(nftPercent, FToken);
|
||
'/': begin
|
||
isFrac := true;
|
||
AddElement(nftFracSymbol, FToken);
|
||
// go back and replace correct token for numerator
|
||
el := High(FSections[FCurrSection].Elements);
|
||
while el > 0 do begin
|
||
dec(el);
|
||
case FSections[FCurrSection].Elements[el].Token of
|
||
nftIntOptDigit:
|
||
begin
|
||
FSections[FCurrSection].Elements[el].Token := nftFracNumOptDigit;
|
||
break;
|
||
end;
|
||
nftIntSpaceDigit:
|
||
begin
|
||
FSections[FCurrSection].Elements[el].Token := nftFracNumSpaceDigit;
|
||
break;
|
||
end;
|
||
nftIntZeroDigit:
|
||
begin
|
||
FSections[FCurrSection].Elements[el].Token := nftFracNumZeroDigit;
|
||
break;
|
||
end;
|
||
end;
|
||
end;
|
||
end;
|
||
'G', 'g':
|
||
ScanGeneral;
|
||
else
|
||
FToken := PrevToken;
|
||
Exit;
|
||
end;
|
||
FToken := NextToken;
|
||
end;
|
||
end;
|
||
|
||
{ Scans a text in quotation marks. Tries to interpret the text as a currency
|
||
symbol (--> AnalyzeText).
|
||
The procedure is entered and left with the cursor at a quotation mark. }
|
||
procedure TsNumFormatParser.ScanQuotedText;
|
||
var
|
||
s: String;
|
||
begin
|
||
s := '';
|
||
FToken := NextToken; // Cursor war at '"'
|
||
while (FCurrent < FEnd) and (FStatus = psOK) do begin
|
||
if FToken = '"' then begin
|
||
if AnalyzeCurrency(s) then
|
||
AddElement(nftCurrSymbol, s)
|
||
else
|
||
AddElement(nftText, s);
|
||
exit;
|
||
end else begin
|
||
s := s + FToken;
|
||
FToken := NextToken;
|
||
end;
|
||
end;
|
||
// When the procedure gets here the final quotation mark is missing
|
||
FStatus := psErrQuoteExpected;
|
||
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
|
||
// Don't use "AddElements" because nfCurrency etc have elements after the number.
|
||
InsertElement(j, i, nftDecSep, '.');
|
||
InsertElement(j, i+1, nftZeroDecs, AValue);
|
||
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;
|
||
FSections[j].Elements[i].Token := nftZeroDecs;
|
||
break;
|
||
end else begin
|
||
// No decimals any more: delete decs and decsep elements
|
||
DeleteElement(j, i);
|
||
DeleteElement(j, i-1);
|
||
break;
|
||
end;
|
||
end;
|
||
end;
|
||
dec(i);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
end.
|