2014-05-19 22:26:42 +00:00
|
|
|
unit fpsNumFormatParser;
|
|
|
|
|
|
|
|
{$ifdef fpc}
|
2015-05-29 22:45:42 +00:00
|
|
|
{$mode objfpc}{$H+}
|
2014-05-19 22:26:42 +00:00
|
|
|
{$endif}
|
|
|
|
|
|
|
|
interface
|
|
|
|
|
|
|
|
uses
|
2015-05-31 20:28:25 +00:00
|
|
|
Classes, SysUtils, fpstypes, fpsNumFormat;
|
2014-05-19 22:26:42 +00:00
|
|
|
|
|
|
|
|
|
|
|
const
|
|
|
|
psOK = 0;
|
|
|
|
psErrNoValidColorIndex = 1;
|
|
|
|
psErrNoValidCompareNumber = 2;
|
|
|
|
psErrUnknownInfoInBrackets = 3;
|
|
|
|
psErrConditionalFormattingNotSupported = 4;
|
|
|
|
psErrNoUsableFormat = 5;
|
|
|
|
psErrNoValidNumberFormat = 6;
|
|
|
|
psErrNoValidDateTimeFormat = 7;
|
2014-06-12 22:20:45 +00:00
|
|
|
psErrQuoteExpected = 8;
|
2015-04-19 22:03:33 +00:00
|
|
|
psErrMultipleCurrSymbols = 9;
|
|
|
|
psErrMultipleFracSymbols = 10;
|
|
|
|
psErrMultipleExpChars = 11;
|
2015-05-29 21:35:07 +00:00
|
|
|
psErrGeneralExpected = 12;
|
|
|
|
psAmbiguousSymbol = 13;
|
2016-03-09 21:17:53 +00:00
|
|
|
psErrNoValidTextFormat = 14;
|
2014-05-19 22:26:42 +00:00
|
|
|
|
|
|
|
type
|
|
|
|
|
2015-04-18 14:58:38 +00:00
|
|
|
{ TsNumFormatParser }
|
2014-05-20 16:13:48 +00:00
|
|
|
|
2014-05-19 22:26:42 +00:00
|
|
|
TsNumFormatParser = class
|
|
|
|
private
|
2014-06-12 22:20:45 +00:00
|
|
|
FToken: Char;
|
2014-05-19 22:26:42 +00:00
|
|
|
FCurrent: PChar;
|
|
|
|
FStart: PChar;
|
|
|
|
FEnd: PChar;
|
|
|
|
FCurrSection: Integer;
|
|
|
|
FStatus: Integer;
|
2014-06-12 22:20:45 +00:00
|
|
|
function GetCurrencySymbol: String;
|
|
|
|
function GetDecimals: byte;
|
2015-03-31 19:01:16 +00:00
|
|
|
function GetFracDenominator: Integer;
|
|
|
|
function GetFracInt: Integer;
|
|
|
|
function GetFracNumerator: Integer;
|
2015-04-19 22:03:33 +00:00
|
|
|
function GetFormatString: String;
|
2014-06-12 22:20:45 +00:00
|
|
|
function GetNumFormat: TsNumberFormat;
|
2014-05-19 22:26:42 +00:00
|
|
|
function GetParsedSectionCount: Integer;
|
|
|
|
function GetParsedSections(AIndex: Integer): TsNumFormatSection;
|
2014-06-12 22:20:45 +00:00
|
|
|
procedure SetDecimals(AValue: Byte);
|
2014-05-19 22:26:42 +00:00
|
|
|
|
|
|
|
protected
|
2015-05-31 16:06:22 +00:00
|
|
|
FFormatSettings: TFormatSettings;
|
2014-06-15 22:27:46 +00:00
|
|
|
FSections: TsNumFormatSections;
|
|
|
|
|
2014-06-12 22:20:45 +00:00
|
|
|
{ Administration while scanning }
|
|
|
|
procedure AddElement(AToken: TsNumFormatToken; AText: String); overload;
|
2015-05-29 22:45:42 +00:00
|
|
|
procedure AddElement(AToken: TsNumFormatToken; AIntValue: Integer=0; AText: String = ''); overload;
|
2014-06-12 22:20:45 +00:00
|
|
|
procedure AddElement(AToken: TsNumFormatToken; AFloatValue: Double); overload;
|
2014-05-19 22:26:42 +00:00
|
|
|
procedure AddSection;
|
2014-06-13 10:03:29 +00:00
|
|
|
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;
|
2014-06-12 22:20:45 +00:00
|
|
|
function NextToken: Char;
|
|
|
|
function PrevToken: Char;
|
|
|
|
|
|
|
|
{ Scanning/parsing }
|
|
|
|
procedure ScanAMPM;
|
|
|
|
procedure ScanAndCount(ATestChar: Char; out ACount: Integer);
|
2014-05-19 22:26:42 +00:00
|
|
|
procedure ScanBrackets;
|
2014-06-12 22:20:45 +00:00
|
|
|
procedure ScanCondition(AFirstChar: Char);
|
|
|
|
procedure ScanCurrSymbol;
|
2014-05-19 22:26:42 +00:00
|
|
|
procedure ScanDateTime;
|
|
|
|
procedure ScanFormat;
|
2015-05-29 21:35:07 +00:00
|
|
|
procedure ScanGeneral;
|
2014-05-19 22:26:42 +00:00
|
|
|
procedure ScanNumber;
|
2014-06-12 22:20:45 +00:00
|
|
|
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);
|
2015-05-29 22:45:42 +00:00
|
|
|
procedure FixMonthMinuteToken(var ASection: TsNumFormatSection);
|
2014-06-12 22:20:45 +00:00
|
|
|
// Format string
|
2015-04-19 22:03:33 +00:00
|
|
|
function BuildFormatString: String; virtual;
|
2015-05-30 22:09:53 +00:00
|
|
|
|
2014-05-19 22:26:42 +00:00
|
|
|
public
|
2015-05-31 16:06:22 +00:00
|
|
|
constructor Create(const AFormatString: String;
|
|
|
|
const AFormatSettings: TFormatSettings);
|
2014-05-19 22:26:42 +00:00
|
|
|
destructor Destroy; override;
|
2014-06-17 09:06:34 +00:00
|
|
|
procedure ClearAll;
|
2014-06-12 22:20:45 +00:00
|
|
|
function GetDateTimeCode(ASection: Integer): String;
|
|
|
|
function IsDateTimeFormat: Boolean;
|
2014-06-18 21:54:29 +00:00
|
|
|
function IsTimeFormat: Boolean;
|
2014-06-13 13:33:02 +00:00
|
|
|
procedure LimitDecimals;
|
2014-06-12 22:20:45 +00:00
|
|
|
|
|
|
|
property CurrencySymbol: String read GetCurrencySymbol;
|
|
|
|
property Decimals: Byte read GetDecimals write SetDecimals;
|
2015-04-19 22:03:33 +00:00
|
|
|
property FormatString: String read GetFormatString;
|
2015-03-31 19:01:16 +00:00
|
|
|
property FracDenominator: Integer read GetFracDenominator;
|
|
|
|
property FracInt: Integer read GetFracInt;
|
|
|
|
property FracNumerator: Integer read GetFracNumerator;
|
2014-06-12 22:20:45 +00:00
|
|
|
property NumFormat: TsNumberFormat read GetNumFormat;
|
2014-05-19 22:26:42 +00:00
|
|
|
property ParsedSectionCount: Integer read GetParsedSectionCount;
|
|
|
|
property ParsedSections[AIndex: Integer]: TsNumFormatSection read GetParsedSections;
|
|
|
|
property Status: Integer read FStatus;
|
|
|
|
end;
|
|
|
|
|
2015-01-17 22:57:23 +00:00
|
|
|
|
2015-05-31 16:06:22 +00:00
|
|
|
function CreateNumFormatParams(ANumFormatStr: String;
|
|
|
|
const AFormatSettings: TFormatSettings): TsNumFormatParams;
|
|
|
|
|
|
|
|
function ParamsOfNumFormatStr(ANumFormatStr: String;
|
|
|
|
const AFormatSettings: TFormatSettings; var AResult: TsNumFormatParams): Integer;
|
2015-04-26 16:24:19 +00:00
|
|
|
|
|
|
|
|
2014-05-19 22:26:42 +00:00
|
|
|
implementation
|
|
|
|
|
|
|
|
uses
|
2015-12-12 10:17:03 +00:00
|
|
|
TypInfo, Math, LazUTF8, fpsCurrency;
|
2014-05-19 22:26:42 +00:00
|
|
|
|
2014-06-12 22:20:45 +00:00
|
|
|
|
2015-05-31 16:06:22 +00:00
|
|
|
function CreateNumFormatParams(ANumFormatStr: String;
|
|
|
|
const AFormatSettings: TFormatSettings): TsNumFormatParams;
|
2015-04-26 16:24:19 +00:00
|
|
|
begin
|
|
|
|
Result := TsNumFormatParams.Create;
|
2015-05-31 16:06:22 +00:00
|
|
|
ParamsOfNumFormatStr(ANumFormatStr, AFormatSettings, result);
|
2015-04-26 16:24:19 +00:00
|
|
|
end;
|
|
|
|
|
2015-05-31 16:06:22 +00:00
|
|
|
function ParamsOfNumFormatStr(ANumFormatStr: String;
|
|
|
|
const AFormatSettings: TFormatSettings; var AResult: TsNumFormatParams): Integer;
|
2015-04-26 16:24:19 +00:00
|
|
|
var
|
|
|
|
parser: TsNumFormatParser;
|
|
|
|
begin
|
|
|
|
Assert(AResult <> nil);
|
|
|
|
if ANumFormatstr = 'General' then ANumFormatStr := '';
|
2015-05-31 16:06:22 +00:00
|
|
|
parser := TsNumFormatParser.Create(ANumFormatStr, AFormatSettings);
|
2015-04-26 16:24:19 +00:00
|
|
|
try
|
|
|
|
Result := parser.Status;
|
|
|
|
AResult.Sections := parser.FSections;
|
|
|
|
finally
|
|
|
|
parser.Free;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
2015-05-31 16:06:22 +00:00
|
|
|
{------------------------------------------------------------------------------}
|
|
|
|
{ TsNumFormatParser }
|
|
|
|
{------------------------------------------------------------------------------}
|
2014-05-19 22:26:42 +00:00
|
|
|
|
2015-05-30 22:09:53 +00:00
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
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.
|
|
|
|
-------------------------------------------------------------------------------}
|
2015-05-31 16:06:22 +00:00
|
|
|
constructor TsNumFormatParser.Create(const AFormatString: String;
|
|
|
|
const AFormatSettings: TFormatSettings);
|
2014-05-21 16:23:38 +00:00
|
|
|
begin
|
|
|
|
inherited Create;
|
2015-05-31 16:06:22 +00:00
|
|
|
FFormatSettings := AFormatSettings;
|
2014-05-21 16:23:38 +00:00
|
|
|
Parse(AFormatString);
|
2015-04-18 14:58:38 +00:00
|
|
|
CheckSections;
|
2015-04-22 18:48:08 +00:00
|
|
|
if AFormatString = '' then FSections[0].NumFormat := nfGeneral;
|
2014-05-21 16:23:38 +00:00
|
|
|
end;
|
2014-05-19 22:26:42 +00:00
|
|
|
|
|
|
|
destructor TsNumFormatParser.Destroy;
|
|
|
|
begin
|
|
|
|
FSections := nil;
|
|
|
|
inherited Destroy;
|
|
|
|
end;
|
|
|
|
|
2014-06-12 22:20:45 +00:00
|
|
|
procedure TsNumFormatParser.AddElement(AToken: TsNumFormatToken; AText: String);
|
|
|
|
var
|
|
|
|
n: Integer;
|
2014-05-19 22:26:42 +00:00
|
|
|
begin
|
2014-06-12 22:20:45 +00:00
|
|
|
n := Length(FSections[FCurrSection].Elements);
|
|
|
|
SetLength(FSections[FCurrSection].Elements, n+1);
|
|
|
|
FSections[FCurrSection].Elements[n].Token := AToken;
|
|
|
|
FSections[FCurrSection].Elements[n].TextValue := AText;
|
|
|
|
end;
|
|
|
|
|
2015-05-29 22:45:42 +00:00
|
|
|
procedure TsNumFormatParser.AddElement(AToken: TsNumFormatToken;
|
|
|
|
AIntValue: Integer=0; AText: String = '');
|
2014-06-12 22:20:45 +00:00
|
|
|
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;
|
2015-05-29 22:45:42 +00:00
|
|
|
FSections[FCurrSection].Elements[n].TextValue := AText;
|
2014-06-12 22:20:45 +00:00
|
|
|
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;
|
2014-05-19 22:26:42 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsNumFormatParser.AddSection;
|
|
|
|
begin
|
|
|
|
FCurrSection := Length(FSections);
|
|
|
|
SetLength(FSections, FCurrSection + 1);
|
2014-06-12 22:20:45 +00:00
|
|
|
with FSections[FCurrSection] do
|
|
|
|
SetLength(Elements, 0);
|
2014-05-19 22:26:42 +00:00
|
|
|
end;
|
|
|
|
|
2014-06-12 22:20:45 +00:00
|
|
|
procedure TsNumFormatParser.AnalyzeColor(AValue: String);
|
2014-05-19 22:26:42 +00:00
|
|
|
var
|
2014-06-12 22:20:45 +00:00
|
|
|
n: Integer;
|
2014-05-19 22:26:42 +00:00
|
|
|
begin
|
2014-06-12 22:20:45 +00:00
|
|
|
AValue := lowercase(AValue);
|
2014-05-19 22:26:42 +00:00
|
|
|
// Colors
|
2014-06-12 22:20:45 +00:00
|
|
|
if AValue = 'red' then
|
|
|
|
AddElement(nftColor, ord(scRed))
|
2014-05-19 22:26:42 +00:00
|
|
|
else
|
2014-06-12 22:20:45 +00:00
|
|
|
if AValue = 'black' then
|
|
|
|
AddElement(nftColor, ord(scBlack))
|
2014-05-19 22:26:42 +00:00
|
|
|
else
|
2014-06-12 22:20:45 +00:00
|
|
|
if AValue = 'blue' then
|
|
|
|
AddElement(nftColor, ord(scBlue))
|
2014-05-19 22:26:42 +00:00
|
|
|
else
|
2014-06-12 22:20:45 +00:00
|
|
|
if AValue = 'white' then
|
|
|
|
AddElement(nftColor, ord(scWhite))
|
2014-05-19 22:26:42 +00:00
|
|
|
else
|
2014-06-12 22:20:45 +00:00
|
|
|
if AValue = 'green' then
|
|
|
|
AddElement(nftColor, ord(scGreen))
|
2014-05-19 22:26:42 +00:00
|
|
|
else
|
2014-06-12 22:20:45 +00:00
|
|
|
if AValue = 'cyan' then
|
|
|
|
AddElement(nftColor, ord(scCyan))
|
2014-05-19 22:26:42 +00:00
|
|
|
else
|
2014-06-12 22:20:45 +00:00
|
|
|
if AValue = 'magenta' then
|
|
|
|
AddElement(nftColor, ord(scMagenta))
|
2014-05-19 22:26:42 +00:00
|
|
|
else
|
2014-06-12 22:20:45 +00:00
|
|
|
if copy(AValue, 1, 5) = 'color' then begin
|
|
|
|
AValue := copy(AValue, 6, Length(AValue));
|
|
|
|
if not TryStrToInt(trim(AValue), n) then begin
|
2014-05-19 22:26:42 +00:00
|
|
|
FStatus := psErrNoValidColorIndex;
|
|
|
|
exit;
|
|
|
|
end;
|
2014-06-12 22:20:45 +00:00
|
|
|
AddElement(nftColor, n);
|
2014-05-19 22:26:42 +00:00
|
|
|
end else
|
|
|
|
FStatus := psErrUnknownInfoInBrackets;
|
|
|
|
end;
|
|
|
|
|
2014-06-12 22:20:45 +00:00
|
|
|
function TsNumFormatParser.AnalyzeCurrency(const AValue: String): Boolean;
|
2014-05-19 22:26:42 +00:00
|
|
|
begin
|
2015-05-31 16:06:22 +00:00
|
|
|
if (FFormatSettings.CurrencyString = '') then
|
2014-06-19 17:56:29 +00:00
|
|
|
Result := false
|
2014-10-29 16:39:49 +00:00
|
|
|
else
|
|
|
|
Result := CurrencyRegistered(AValue);
|
2014-06-12 22:20:45 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
{ Creates a formatstring for all sections.
|
|
|
|
Note: this implementation is only valid for the fpc and Excel dialects of
|
2014-06-15 22:27:46 +00:00
|
|
|
format string. }
|
2015-04-19 22:03:33 +00:00
|
|
|
function TsNumFormatParser.BuildFormatString: String;
|
2014-06-12 22:20:45 +00:00
|
|
|
var
|
|
|
|
i: Integer;
|
|
|
|
begin
|
|
|
|
if Length(FSections) > 0 then begin
|
2015-04-19 22:03:33 +00:00
|
|
|
Result := BuildFormatStringFromSection(FSections[0]);
|
2015-04-18 14:58:38 +00:00
|
|
|
for i:=1 to High(FSections) do
|
2015-04-19 22:03:33 +00:00
|
|
|
Result := Result + ';' + BuildFormatStringFromSection(FSections[i]);
|
2015-04-18 14:58:38 +00:00
|
|
|
end;
|
2014-06-12 22:20:45 +00:00
|
|
|
end;
|
|
|
|
|
2015-04-18 14:58:38 +00:00
|
|
|
procedure TsNumFormatParser.CheckSections;
|
2014-06-12 22:20:45 +00:00
|
|
|
var
|
|
|
|
i: Integer;
|
|
|
|
begin
|
2015-04-18 14:58:38 +00:00
|
|
|
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;
|
2014-06-12 22:20:45 +00:00
|
|
|
|
2015-04-18 14:58:38 +00:00
|
|
|
procedure TsNumFormatParser.CheckSection(ASection: Integer);
|
|
|
|
var
|
2015-05-15 21:14:19 +00:00
|
|
|
el, i: Integer;
|
2015-04-18 14:58:38 +00:00
|
|
|
section: PsNumFormatSection;
|
2015-04-19 22:03:33 +00:00
|
|
|
nfs, nfsTest: String;
|
2015-04-18 14:58:38 +00:00
|
|
|
nf: TsNumberFormat;
|
2015-05-15 21:14:19 +00:00
|
|
|
formats: set of TsNumberFormat;
|
2015-05-29 22:45:42 +00:00
|
|
|
isMonthMinute: Boolean;
|
2015-04-18 14:58:38 +00:00
|
|
|
begin
|
|
|
|
if FStatus <> psOK then
|
2014-06-12 22:20:45 +00:00
|
|
|
exit;
|
2015-04-18 14:58:38 +00:00
|
|
|
|
|
|
|
section := @FSections[ASection];
|
|
|
|
section^.Kind := [];
|
|
|
|
|
2015-05-30 13:32:54 +00:00
|
|
|
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;
|
|
|
|
|
2015-05-19 16:18:01 +00:00
|
|
|
i := 0;
|
2015-05-29 22:45:42 +00:00
|
|
|
isMonthMinute := false;
|
2015-05-19 16:18:01 +00:00
|
|
|
|
2015-04-18 14:58:38 +00:00
|
|
|
for el := 0 to High(section^.Elements) do
|
2016-03-09 21:17:53 +00:00
|
|
|
begin
|
2015-04-18 14:58:38 +00:00
|
|
|
case section^.Elements[el].Token of
|
2015-05-15 21:14:19 +00:00
|
|
|
nftZeroDecs:
|
|
|
|
section^.Decimals := section^.Elements[el].IntValue;
|
2015-05-19 16:18:01 +00:00
|
|
|
nftIntZeroDigit, nftIntOptDigit, nftIntSpaceDigit:
|
|
|
|
i := section^.Elements[el].IntValue;
|
2015-05-15 21:14:19 +00:00
|
|
|
nftFracNumSpaceDigit, nftFracNumZeroDigit:
|
|
|
|
section^.FracNumerator := section^.Elements[el].IntValue;
|
|
|
|
nftFracDenomSpaceDigit, nftFracDenomZeroDigit:
|
|
|
|
section^.FracDenominator := section^.Elements[el].IntValue;
|
2015-05-29 17:30:43 +00:00
|
|
|
nftFracDenom:
|
|
|
|
section^.FracDenominator := -section^.Elements[el].IntValue;
|
2015-04-18 14:58:38 +00:00
|
|
|
nftPercent:
|
|
|
|
section^.Kind := section^.Kind + [nfkPercent];
|
|
|
|
nftExpChar:
|
2015-04-19 22:03:33 +00:00
|
|
|
if (nfkExp in section^.Kind) then
|
|
|
|
FStatus := psErrMultipleExpChars
|
|
|
|
else
|
|
|
|
section^.Kind := section^.Kind + [nfkExp];
|
2015-05-15 17:33:24 +00:00
|
|
|
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;
|
2015-04-08 22:43:57 +00:00
|
|
|
nftFracSymbol:
|
2015-04-19 22:03:33 +00:00
|
|
|
if (nfkFraction in section^.Kind) then
|
|
|
|
FStatus := psErrMultipleFracSymbols
|
|
|
|
else
|
2015-05-19 16:18:01 +00:00
|
|
|
begin
|
2015-04-19 22:03:33 +00:00
|
|
|
section^.Kind := section^.Kind + [nfkFraction];
|
2015-05-19 16:18:01 +00:00
|
|
|
section^.FracInt := i;
|
|
|
|
end;
|
2014-06-12 22:20:45 +00:00
|
|
|
nftCurrSymbol:
|
2015-04-18 14:58:38 +00:00
|
|
|
begin
|
2015-04-19 22:03:33 +00:00
|
|
|
if (nfkCurrency in section^.Kind) then
|
|
|
|
FStatus := psErrMultipleCurrSymbols
|
|
|
|
else begin
|
|
|
|
section^.Kind := section^.Kind + [nfkCurrency];
|
|
|
|
section^.CurrencySymbol := section^.Elements[el].TextValue;
|
|
|
|
end;
|
2014-06-12 22:20:45 +00:00
|
|
|
end;
|
2015-04-18 14:58:38 +00:00
|
|
|
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];
|
2014-06-17 09:06:34 +00:00
|
|
|
end;
|
2015-05-29 22:45:42 +00:00
|
|
|
nftMonthMinute:
|
|
|
|
isMonthMinute := true;
|
2015-04-21 09:35:58 +00:00
|
|
|
nftColor:
|
2015-05-29 21:35:07 +00:00
|
|
|
begin
|
|
|
|
section^.Kind := section^.Kind + [nfkHasColor];
|
|
|
|
section^.Color := section^.Elements[el].IntValue;
|
|
|
|
end;
|
2015-04-26 16:24:19 +00:00
|
|
|
nftIntTh:
|
|
|
|
section^.Kind := section^.Kind + [nfkHasThSep];
|
2016-03-09 21:17:53 +00:00
|
|
|
nftTextFormat:
|
|
|
|
section^.Kind := section^.Kind + [nfkText];
|
2014-06-12 22:20:45 +00:00
|
|
|
end;
|
2016-03-09 21:17:53 +00:00
|
|
|
end; // for
|
2015-04-18 14:58:38 +00:00
|
|
|
|
2015-04-19 22:03:33 +00:00
|
|
|
if FStatus <> psOK then
|
|
|
|
exit;
|
|
|
|
|
2015-04-18 14:58:38 +00:00
|
|
|
if (section^.Kind * [nfkDate, nfkTime] <> []) and
|
|
|
|
(section^.Kind * [nfkPercent, nfkExp, nfkCurrency, nfkFraction] <> []) then
|
|
|
|
begin
|
|
|
|
FStatus := psErrNoValidDateTimeFormat;
|
|
|
|
exit;
|
2014-06-12 22:20:45 +00:00
|
|
|
end;
|
2014-05-19 22:26:42 +00:00
|
|
|
|
2016-03-09 21:17:53 +00:00
|
|
|
if (Length(FSections) = 1) and (section^.Kind = [nfkText]) then begin
|
|
|
|
section^.NumFormat := nfText;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
|
2015-04-18 14:58:38 +00:00
|
|
|
section^.NumFormat := nfCustom;
|
2014-05-19 22:26:42 +00:00
|
|
|
|
2015-05-29 22:45:42 +00:00
|
|
|
if (section^.Kind * [nfkDate, nfkTime] <> []) or isMonthMinute then
|
2015-04-18 14:58:38 +00:00
|
|
|
begin
|
2015-05-29 22:45:42 +00:00
|
|
|
FixMonthMinuteToken(section^);
|
2015-04-19 22:03:33 +00:00
|
|
|
nfs := GetFormatString;
|
2015-04-18 14:58:38 +00:00
|
|
|
if (nfkTimeInterval in section^.Kind) then
|
|
|
|
section^.NumFormat := nfTimeInterval
|
|
|
|
else
|
|
|
|
begin
|
2015-05-15 21:14:19 +00:00
|
|
|
formats := [nfShortDateTime, nfLongDate, nfShortDate, nfLongTime,
|
2015-04-18 14:58:38 +00:00
|
|
|
nfShortTime, nfLongTimeAM, nfShortTimeAM, nfDayMonth, nfMonthYear];
|
2015-05-15 21:14:19 +00:00
|
|
|
for nf in formats do
|
2015-04-19 22:03:33 +00:00
|
|
|
begin
|
2015-05-31 16:06:22 +00:00
|
|
|
nfsTest := BuildDateTimeFormatString(nf, FFormatSettings);
|
2015-04-19 22:03:33 +00:00
|
|
|
if Length(nfsTest) = Length(nfs) then
|
|
|
|
begin
|
2015-05-15 21:14:19 +00:00
|
|
|
if SameText(nfs, nfsTest) then
|
|
|
|
begin
|
|
|
|
section^.NumFormat := nf;
|
|
|
|
break;
|
|
|
|
end;
|
2015-04-19 22:03:33 +00:00
|
|
|
for i := 1 to Length(nfsTest) do
|
|
|
|
case nfsTest[i] of
|
|
|
|
'/': if not (nf in [nfLongTimeAM, nfShortTimeAM]) then
|
2015-05-31 16:06:22 +00:00
|
|
|
nfsTest[i] := FFormatSettings.DateSeparator;
|
|
|
|
':': nfsTest[i] := FFormatSettings.TimeSeparator;
|
2015-04-19 22:03:33 +00:00
|
|
|
'n': nfsTest[i] := 'm';
|
|
|
|
end;
|
|
|
|
if SameText(nfs, nfsTest) then
|
|
|
|
begin
|
|
|
|
section^.NumFormat := nf;
|
|
|
|
break;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
2015-04-18 14:58:38 +00:00
|
|
|
end;
|
|
|
|
end else
|
|
|
|
begin
|
2015-05-15 21:14:19 +00:00
|
|
|
nfs := GetFormatString;
|
2015-05-19 16:18:01 +00:00
|
|
|
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
|
2015-05-31 16:06:22 +00:00
|
|
|
nfsTest := BuildNumberFormatString(nf, FFormatSettings, section^.Decimals);
|
2015-05-19 16:18:01 +00:00
|
|
|
if SameText(nfs, nfsTest) then
|
|
|
|
begin
|
|
|
|
section^.NumFormat := nf;
|
|
|
|
break;
|
|
|
|
end;
|
2015-05-15 21:14:19 +00:00
|
|
|
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;
|
2015-04-18 14:58:38 +00:00
|
|
|
end;
|
2015-04-08 22:43:57 +00:00
|
|
|
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") }
|
2015-05-29 22:45:42 +00:00
|
|
|
procedure TsNumFormatParser.FixMonthMinuteToken(var ASection: TsNumFormatSection);
|
2014-06-12 22:20:45 +00:00
|
|
|
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
|
2015-05-29 22:45:42 +00:00
|
|
|
with ASection.Elements[j] do
|
2014-06-12 22:20:45 +00:00
|
|
|
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);
|
2015-05-29 22:45:42 +00:00
|
|
|
while (j < Length(ASection.Elements)) do begin
|
|
|
|
with ASection.Elements[j] do
|
2014-06-12 22:20:45 +00:00
|
|
|
if Token in [nftYear, nftMonth, nftDay, nftHour, nftMinute, nftSecond] then
|
|
|
|
begin
|
|
|
|
Result := j;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
inc(j);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
begin
|
2015-05-29 22:45:42 +00:00
|
|
|
for i:=0 to High(ASection.Elements) do
|
|
|
|
begin
|
2014-06-12 22:20:45 +00:00
|
|
|
// Find index of nftMonthMinute token...
|
2015-05-29 22:45:42 +00:00
|
|
|
if ASection.Elements[i].Token = nftMonthMinute then begin
|
2014-06-12 22:20:45 +00:00
|
|
|
// ... and, using its neighbors, decide whether it is a month or a minute.
|
|
|
|
j := NextDateTimeElement(i);
|
|
|
|
if j <> -1 then
|
2015-05-29 22:45:42 +00:00
|
|
|
case ASection.Elements[j].Token of
|
2014-06-12 22:20:45 +00:00
|
|
|
nftDay, nftYear:
|
|
|
|
begin
|
2015-05-29 22:45:42 +00:00
|
|
|
ASection.Elements[i].Token := nftMonth;
|
2014-06-12 22:20:45 +00:00
|
|
|
Continue;
|
|
|
|
end;
|
|
|
|
nftSecond:
|
|
|
|
begin
|
2015-05-29 22:45:42 +00:00
|
|
|
ASection.Elements[i].Token := nftMinute;
|
2014-06-12 22:20:45 +00:00
|
|
|
Continue;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
j := PrevDateTimeElement(i);
|
|
|
|
if j <> -1 then
|
2015-05-29 22:45:42 +00:00
|
|
|
case ASection.Elements[j].Token of
|
2014-06-12 22:20:45 +00:00
|
|
|
nftDay, nftYear:
|
|
|
|
begin
|
2015-05-29 22:45:42 +00:00
|
|
|
ASection.Elements[i].Token := nftMonth;
|
2014-06-12 22:20:45 +00:00
|
|
|
Continue;
|
|
|
|
end;
|
|
|
|
nftHour:
|
|
|
|
begin
|
2015-05-29 22:45:42 +00:00
|
|
|
ASection.Elements[i].Token := nftMinute;
|
2014-06-12 22:20:45 +00:00
|
|
|
Continue;
|
|
|
|
end;
|
|
|
|
end;
|
2015-05-29 22:45:42 +00:00
|
|
|
|
2015-05-30 14:01:10 +00:00
|
|
|
// 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".)
|
2015-05-29 22:45:42 +00:00
|
|
|
ASection.Elements[i].Token := nftMonth;
|
|
|
|
Include(ASection.Kind, nfkDate);
|
2014-06-12 22:20:45 +00:00
|
|
|
end;
|
2015-05-29 22:45:42 +00:00
|
|
|
end;
|
2014-06-13 10:03:29 +00:00
|
|
|
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;
|
|
|
|
|
2015-04-19 22:03:33 +00:00
|
|
|
function TsNumFormatParser.GetFormatString: String;
|
2014-05-20 16:13:48 +00:00
|
|
|
begin
|
2015-04-19 22:03:33 +00:00
|
|
|
Result := BuildFormatString;
|
2014-05-20 16:13:48 +00:00
|
|
|
end;
|
|
|
|
|
2014-06-12 22:20:45 +00:00
|
|
|
{ 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;
|
2014-05-20 16:13:48 +00:00
|
|
|
begin
|
2014-06-12 22:20:45 +00:00
|
|
|
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';
|
2014-06-13 13:33:02 +00:00
|
|
|
nftSecond : Result := Result + 's';
|
2014-06-12 22:20:45 +00:00
|
|
|
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;
|
|
|
|
|
2015-03-31 19:01:16 +00:00
|
|
|
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;
|
|
|
|
|
2014-06-12 22:20:45 +00:00
|
|
|
{ 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;
|
2014-06-23 09:15:56 +00:00
|
|
|
if (Result = nfCurrency) then begin
|
2014-06-12 22:20:45 +00:00
|
|
|
if Length(FSections) = 2 then begin
|
2014-06-17 09:06:34 +00:00
|
|
|
Result := FSections[1].NumFormat;
|
2014-06-12 22:20:45 +00:00
|
|
|
if FSections[1].CurrencySymbol <> FSections[0].CurrencySymbol then begin
|
|
|
|
Result := nfCustom;
|
|
|
|
exit;
|
|
|
|
end;
|
2014-06-17 09:06:34 +00:00
|
|
|
if (FSections[0].NumFormat in [nfCurrency, nfCurrencyRed]) and
|
|
|
|
(FSections[1].NumFormat in [nfCurrency, nfCurrencyRed])
|
|
|
|
then
|
2014-06-12 22:20:45 +00:00
|
|
|
exit;
|
|
|
|
end else
|
|
|
|
if Length(FSections) = 3 then begin
|
2014-06-17 09:06:34 +00:00
|
|
|
Result := FSections[1].NumFormat;
|
2014-06-12 22:20:45 +00:00
|
|
|
if (FSections[0].CurrencySymbol <> FSections[1].CurrencySymbol) or
|
|
|
|
(FSections[1].CurrencySymbol <> FSections[2].CurrencySymbol)
|
|
|
|
then begin
|
|
|
|
Result := nfCustom;
|
|
|
|
exit;
|
|
|
|
end;
|
2014-06-17 09:06:34 +00:00
|
|
|
if (FSections[0].NumFormat in [nfCurrency, nfCurrencyRed]) and
|
|
|
|
(FSections[1].NumFormat in [nfCurrency, nfCurrencyRed]) and
|
|
|
|
(FSections[2].NumFormat in [nfCurrency, nfCurrencyRed])
|
2014-06-12 22:20:45 +00:00
|
|
|
then
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
Result := nfCustom;
|
|
|
|
exit;
|
|
|
|
end;
|
2014-06-13 20:32:58 +00:00
|
|
|
if Length(FSections) > 1 then
|
|
|
|
Result := nfCustom;
|
2014-05-20 16:13:48 +00:00
|
|
|
end;
|
2014-05-19 22:26:42 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
function TsNumFormatParser.GetParsedSectionCount: Integer;
|
|
|
|
begin
|
|
|
|
Result := Length(FSections);
|
|
|
|
end;
|
|
|
|
|
|
|
|
function TsNumFormatParser.GetParsedSections(AIndex: Integer): TsNumFormatSection;
|
|
|
|
begin
|
|
|
|
Result := FSections[AIndex];
|
|
|
|
end;
|
2015-05-15 21:14:19 +00:00
|
|
|
{
|
2015-04-08 22:43:57 +00:00
|
|
|
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;
|
2015-05-15 21:14:19 +00:00
|
|
|
}
|
2015-04-18 14:58:38 +00:00
|
|
|
{ Returns true if the format elements contain at least one date/time token }
|
|
|
|
function TsNumFormatParser.IsDateTimeFormat: Boolean;
|
2014-05-19 22:26:42 +00:00
|
|
|
var
|
2015-04-18 14:58:38 +00:00
|
|
|
section: TsNumFormatSection;
|
2014-06-12 22:20:45 +00:00
|
|
|
begin
|
2015-04-18 14:58:38 +00:00
|
|
|
for section in FSections do
|
|
|
|
if section.Kind * [nfkDate, nfkTime] <> [] then
|
|
|
|
begin
|
2014-06-17 09:06:34 +00:00
|
|
|
Result := true;
|
2015-04-18 14:58:38 +00:00
|
|
|
exit;
|
2014-06-17 09:06:34 +00:00
|
|
|
end;
|
2015-04-18 14:58:38 +00:00
|
|
|
Result := false;
|
2014-06-12 22:20:45 +00:00
|
|
|
end;
|
2015-05-15 21:14:19 +00:00
|
|
|
{
|
2015-04-18 14:58:38 +00:00
|
|
|
function TsNumFormatParser.IsNumberAt(ASection, AIndex: Integer;
|
|
|
|
out ANumFormat: TsNumberFormat; out ADecimals: Byte;
|
|
|
|
out ANextIndex: Integer): Boolean;
|
|
|
|
var
|
|
|
|
token: TsNumFormatToken;
|
2014-06-12 22:20:45 +00:00
|
|
|
begin
|
2015-04-18 14:58:38 +00:00
|
|
|
if (ASection > High(FSections)) or (AIndex > High(FSections[ASection].Elements))
|
|
|
|
then begin
|
2014-06-13 20:11:41 +00:00
|
|
|
Result := false;
|
2015-04-18 14:58:38 +00:00
|
|
|
ANextIndex := AIndex;
|
2014-06-13 20:11:41 +00:00
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
|
2014-06-12 22:20:45 +00:00
|
|
|
Result := true;
|
2015-04-18 14:58:38 +00:00
|
|
|
ANumFormat := nfCustom;
|
|
|
|
ADecimals := 0;
|
|
|
|
token := FSections[ASection].Elements[AIndex].Token;
|
2015-04-08 22:43:57 +00:00
|
|
|
|
2015-04-18 14:58:38 +00:00
|
|
|
if token in [nftFracNumOptDigit, nftFracNumZeroDigit, nftFracNumSpaceDigit,
|
|
|
|
nftFracDenomOptDigit, nftFracDenomZeroDigit, nftFracDenomSpaceDigit] then
|
2015-04-08 22:43:57 +00:00
|
|
|
begin
|
2015-04-18 14:58:38 +00:00
|
|
|
ANumFormat := nfFraction;
|
|
|
|
ANextIndex := AIndex + 1;
|
2015-04-08 22:43:57 +00:00
|
|
|
exit;
|
2015-04-18 14:58:38 +00:00
|
|
|
end;
|
2015-04-08 22:43:57 +00:00
|
|
|
|
2015-04-18 14:58:38 +00:00
|
|
|
if (token = nftIntTh) and (FSections[ASection].Elements[AIndex].IntValue = 1) then // '#,##0'
|
|
|
|
ANumFormat := nfFixedTh
|
2015-04-08 22:43:57 +00:00
|
|
|
else
|
2015-04-18 14:58:38 +00:00
|
|
|
if (token = nftIntZeroDigit) and (FSections[ASection].Elements[AIndex].IntValue = 1) then // '0'
|
|
|
|
ANumFormat := nfFixed;
|
2015-04-08 22:43:57 +00:00
|
|
|
|
2015-04-18 14:58:38 +00:00
|
|
|
if (token in [nftIntTh, nftIntZeroDigit, nftIntOptDigit, nftIntSpaceDigit]) then
|
2015-04-08 22:43:57 +00:00
|
|
|
begin
|
2015-04-18 14:58:38 +00:00
|
|
|
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;
|
2015-03-31 19:01:16 +00:00
|
|
|
end;
|
2015-04-18 14:58:38 +00:00
|
|
|
end else
|
|
|
|
if IsTokenAt(nftSpace, ASection, AIndex+1) then
|
|
|
|
begin
|
|
|
|
ANumFormat := nfFraction;
|
|
|
|
ANextIndex := AIndex + 1;
|
|
|
|
exit;
|
|
|
|
end else
|
|
|
|
begin
|
|
|
|
ANextIndex := AIndex + 1;
|
|
|
|
exit;
|
2014-06-12 22:20:45 +00:00
|
|
|
end;
|
|
|
|
end;
|
2015-04-18 14:58:38 +00:00
|
|
|
|
|
|
|
ANextIndex := AIndex;
|
|
|
|
Result := false;
|
2014-06-12 22:20:45 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
function TsNumFormatParser.IsTextAt(AText: String; ASection, AIndex: Integer): Boolean;
|
|
|
|
begin
|
|
|
|
Result := IsTokenAt(nftText, ASection, AIndex) and
|
|
|
|
(FSections[ASection].Elements[AIndex].TextValue = AText);
|
|
|
|
end;
|
2015-05-15 21:14:19 +00:00
|
|
|
}
|
2014-06-18 21:54:29 +00:00
|
|
|
{ Returns true if the format elements contain only time, no date tokens. }
|
|
|
|
function TsNumFormatParser.IsTimeFormat: Boolean;
|
|
|
|
var
|
2015-04-18 14:58:38 +00:00
|
|
|
section: TsNumFormatSection;
|
2014-06-18 21:54:29 +00:00
|
|
|
begin
|
2015-04-18 14:58:38 +00:00
|
|
|
for section in FSections do
|
|
|
|
if (nfkTime in section.Kind) then
|
|
|
|
begin
|
|
|
|
Result := true;
|
|
|
|
exit;
|
|
|
|
end;
|
2014-06-18 21:54:29 +00:00
|
|
|
Result := false;
|
|
|
|
end;
|
2015-05-15 21:14:19 +00:00
|
|
|
{
|
2014-06-12 22:20:45 +00:00
|
|
|
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;
|
2015-05-15 21:14:19 +00:00
|
|
|
}
|
2014-10-29 22:36:03 +00:00
|
|
|
{ Limits the decimals to 0 or 2, as required by Excel2. }
|
2014-06-13 13:33:02 +00:00
|
|
|
procedure TsNumFormatParser.LimitDecimals;
|
|
|
|
var
|
|
|
|
i, j: Integer;
|
|
|
|
begin
|
|
|
|
for j:=0 to High(FSections) do
|
|
|
|
for i:=0 to High(FSections[j].Elements) do
|
2015-04-18 14:58:38 +00:00
|
|
|
if FSections[j].Elements[i].Token = nftZeroDecs then
|
2014-06-13 13:33:02 +00:00
|
|
|
if FSections[j].Elements[i].IntValue > 0 then
|
|
|
|
FSections[j].Elements[i].IntValue := 2;
|
|
|
|
end;
|
|
|
|
|
2014-06-12 22:20:45 +00:00
|
|
|
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);
|
2014-05-19 22:26:42 +00:00
|
|
|
begin
|
|
|
|
FStatus := psOK;
|
2015-04-18 14:58:38 +00:00
|
|
|
|
2014-05-19 22:26:42 +00:00
|
|
|
AddSection;
|
2015-05-29 21:35:07 +00:00
|
|
|
if (AFormatString = '') then
|
|
|
|
begin
|
|
|
|
AddElement(nftGeneral);
|
2014-05-21 12:12:16 +00:00
|
|
|
exit;
|
2015-05-29 21:35:07 +00:00
|
|
|
end;
|
2014-05-21 12:12:16 +00:00
|
|
|
|
2014-05-19 22:26:42 +00:00
|
|
|
FStart := @AFormatString[1];
|
2014-06-12 22:20:45 +00:00
|
|
|
FEnd := FStart + Length(AFormatString);
|
2014-05-19 22:26:42 +00:00
|
|
|
FCurrent := FStart;
|
2014-06-12 22:20:45 +00:00
|
|
|
FToken := FCurrent^;
|
|
|
|
while (FCurrent < FEnd) and (FStatus = psOK) do begin
|
|
|
|
case FToken of
|
2015-05-29 21:35:07 +00:00
|
|
|
'G','g': ScanGeneral;
|
2014-05-19 22:26:42 +00:00
|
|
|
'[': ScanBrackets;
|
2014-06-12 22:20:45 +00:00
|
|
|
'"': ScanQuotedText;
|
|
|
|
':': AddElement(nftDateTimeSep, ':');
|
2014-05-19 22:26:42 +00:00
|
|
|
';': AddSection;
|
|
|
|
else ScanFormat;
|
|
|
|
end;
|
2014-06-12 22:20:45 +00:00
|
|
|
FToken := NextToken;
|
2014-05-19 22:26:42 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2014-06-12 22:20:45 +00:00
|
|
|
{ Scans an AM/PM sequence (or AMPM or A/P).
|
|
|
|
At exit, cursor is a next character }
|
|
|
|
procedure TsNumFormatParser.ScanAMPM;
|
2014-05-19 22:26:42 +00:00
|
|
|
var
|
|
|
|
s: String;
|
2015-04-18 14:58:38 +00:00
|
|
|
el: Integer;
|
2014-05-19 22:26:42 +00:00
|
|
|
begin
|
2014-06-12 22:20:45 +00:00
|
|
|
s := '';
|
|
|
|
while (FCurrent < FEnd) do begin
|
|
|
|
if (FToken in ['A', 'a', 'P', 'p', 'm', 'M', '/']) then
|
|
|
|
s := s + FToken
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
FToken := NextToken;
|
2014-05-19 22:26:42 +00:00
|
|
|
end;
|
2015-04-18 14:58:38 +00:00
|
|
|
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;
|
2014-05-19 22:26:42 +00:00
|
|
|
end;
|
|
|
|
|
2014-06-12 22:20:45 +00:00
|
|
|
{ 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;
|
2015-05-15 17:33:24 +00:00
|
|
|
if FToken <> ATestChar then
|
|
|
|
exit;
|
2014-06-12 22:20:45 +00:00
|
|
|
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 [$�-409]
|
|
|
|
- a color like [red] or [color25]
|
|
|
|
The procedure is left with the cursor at ']' }
|
|
|
|
procedure TsNumFormatParser.ScanBrackets;
|
2014-05-19 22:26:42 +00:00
|
|
|
var
|
|
|
|
s: String;
|
2014-06-12 22:20:45 +00:00
|
|
|
n: Integer;
|
2015-05-29 22:45:42 +00:00
|
|
|
prevtok: Char;
|
2015-04-18 14:58:38 +00:00
|
|
|
isText: Boolean;
|
2014-05-19 22:26:42 +00:00
|
|
|
begin
|
2014-06-19 19:25:40 +00:00
|
|
|
s := '';
|
2015-04-18 14:58:38 +00:00
|
|
|
isText := false;
|
2014-06-12 22:20:45 +00:00
|
|
|
FToken := NextToken; // Cursor was at '['
|
|
|
|
while (FCurrent < FEnd) and (FStatus = psOK) do begin
|
|
|
|
case FToken of
|
|
|
|
'h', 'H', 'm', 'M', 'n', 'N', 's', 'S':
|
2015-04-18 14:58:38 +00:00
|
|
|
if isText then
|
|
|
|
s := s + FToken
|
|
|
|
else
|
2014-05-20 16:13:48 +00:00
|
|
|
begin
|
2015-05-29 22:45:42 +00:00
|
|
|
prevtok := FToken;
|
2014-06-12 22:20:45 +00:00
|
|
|
ScanAndCount(FToken, n);
|
|
|
|
if (FToken in [']', #0]) then begin
|
2015-05-29 22:45:42 +00:00
|
|
|
case prevtok of
|
2014-06-12 22:20:45 +00:00
|
|
|
'h', 'H' : AddElement(nftHour, -n);
|
|
|
|
'm', 'M', 'n', 'N': AddElement(nftMinute, -n);
|
|
|
|
's', 'S' : AddElement(nftSecond, -n);
|
2014-05-22 10:09:35 +00:00
|
|
|
end;
|
2014-06-12 22:20:45 +00:00
|
|
|
break;
|
|
|
|
end else
|
|
|
|
FStatus := psErrUnknownInfoInBrackets;
|
2014-05-20 16:13:48 +00:00
|
|
|
end;
|
2014-06-12 22:20:45 +00:00
|
|
|
|
|
|
|
'<', '>', '=':
|
2014-05-20 16:13:48 +00:00
|
|
|
begin
|
2014-06-12 22:20:45 +00:00
|
|
|
ScanCondition(FToken);
|
|
|
|
if FToken = ']' then
|
|
|
|
break
|
|
|
|
else
|
|
|
|
FStatus := psErrUnknownInfoInBrackets;
|
2014-05-20 16:13:48 +00:00
|
|
|
end;
|
2014-06-12 22:20:45 +00:00
|
|
|
|
|
|
|
'$':
|
2014-05-20 16:13:48 +00:00
|
|
|
begin
|
2014-06-12 22:20:45 +00:00
|
|
|
ScanCurrSymbol;
|
|
|
|
if FToken = ']' then
|
|
|
|
break
|
|
|
|
else
|
|
|
|
FStatus := psErrUnknownInfoInBrackets;
|
2014-05-20 16:13:48 +00:00
|
|
|
end;
|
2014-06-12 22:20:45 +00:00
|
|
|
|
|
|
|
']':
|
2014-05-20 16:13:48 +00:00
|
|
|
begin
|
2014-06-12 22:20:45 +00:00
|
|
|
AnalyzeColor(s);
|
|
|
|
break;
|
2014-05-20 16:13:48 +00:00
|
|
|
end;
|
2014-06-12 22:20:45 +00:00
|
|
|
|
2014-05-20 16:13:48 +00:00
|
|
|
else
|
2014-06-12 22:20:45 +00:00
|
|
|
s := s + FToken;
|
2015-04-18 14:58:38 +00:00
|
|
|
isText := true;
|
2014-05-19 22:26:42 +00:00
|
|
|
end;
|
2014-06-12 22:20:45 +00:00
|
|
|
FToken := NextToken;
|
2014-05-19 22:26:42 +00:00
|
|
|
end;
|
2014-06-12 22:20:45 +00:00
|
|
|
end;
|
2014-05-19 22:26:42 +00:00
|
|
|
|
2014-06-12 22:20:45 +00:00
|
|
|
{ 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;
|
2014-10-03 22:02:09 +00:00
|
|
|
// op: TsCompareOperation;
|
2014-06-12 22:20:45 +00:00
|
|
|
value: Double;
|
|
|
|
res: Integer;
|
|
|
|
begin
|
|
|
|
s := AFirstChar;
|
|
|
|
FToken := NextToken;
|
|
|
|
if FToken in ['>', '<', '='] then s := s + FToken else FToken := PrevToken;
|
2014-10-03 22:02:09 +00:00
|
|
|
{
|
2014-06-12 22:20:45 +00:00
|
|
|
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;
|
2014-10-03 22:02:09 +00:00
|
|
|
}
|
2014-06-12 22:20:45 +00:00
|
|
|
while (FToken = ' ') and (FCurrent < FEnd) do
|
|
|
|
FToken := NextToken;
|
2014-05-20 16:13:48 +00:00
|
|
|
|
2014-06-12 22:20:45 +00:00
|
|
|
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;
|
2014-05-19 22:26:42 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2014-06-12 22:20:45 +00:00
|
|
|
{ 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;
|
2014-05-19 22:26:42 +00:00
|
|
|
var
|
2014-06-12 22:20:45 +00:00
|
|
|
s: String;
|
2014-05-19 22:26:42 +00:00
|
|
|
begin
|
2014-06-12 22:20:45 +00:00
|
|
|
s := '';
|
|
|
|
FToken := NextToken;
|
|
|
|
while (FCurrent < FEnd) and not (FToken in ['-', ']']) do begin
|
|
|
|
s := s + FToken;
|
|
|
|
FToken := NextToken;
|
|
|
|
end;
|
2015-04-18 14:58:38 +00:00
|
|
|
if s <> '' then
|
|
|
|
AddElement(nftCurrSymbol, s);
|
2014-06-12 22:20:45 +00:00
|
|
|
if FToken <> ']' then begin
|
|
|
|
FToken := NextToken;
|
|
|
|
while (FCurrent < FEnd) and (FToken <> ']') do begin
|
|
|
|
s := s + FToken;
|
|
|
|
FToken := NextToken;
|
2014-05-19 22:26:42 +00:00
|
|
|
end;
|
2015-04-18 14:58:38 +00:00
|
|
|
if s <> '' then
|
|
|
|
AddElement(nftCountry, s);
|
2014-05-19 22:26:42 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2014-06-12 22:20:45 +00:00
|
|
|
{ Scans a date/time format. Procedure is left with the cursor at the last char
|
|
|
|
of the date/time format. }
|
|
|
|
procedure TsNumFormatParser.ScanDateTime;
|
2014-05-19 22:26:42 +00:00
|
|
|
var
|
2014-06-12 22:20:45 +00:00
|
|
|
n: Integer;
|
2014-05-19 22:26:42 +00:00
|
|
|
token: Char;
|
|
|
|
begin
|
2014-06-12 22:20:45 +00:00
|
|
|
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
|
2015-05-29 22:45:42 +00:00
|
|
|
token := FToken;
|
2014-06-12 22:20:45 +00:00
|
|
|
ScanAndCount(FToken, n);
|
2015-05-29 22:45:42 +00:00
|
|
|
AddElement(nftMonthMinute, n, token); // Decide on minute or month later
|
2014-06-12 22:20:45 +00:00
|
|
|
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;
|
2014-06-19 17:56:29 +00:00
|
|
|
',', '-':
|
|
|
|
begin
|
2015-04-18 14:58:38 +00:00
|
|
|
AddElement(nftText, FToken);
|
2014-06-19 17:56:29 +00:00
|
|
|
FToken := NextToken;
|
|
|
|
end
|
2014-06-12 22:20:45 +00:00
|
|
|
else
|
|
|
|
// char pointer must be at end of date/time mask.
|
|
|
|
FToken := PrevToken;
|
|
|
|
Exit;
|
2014-05-19 22:26:42 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsNumFormatParser.ScanFormat;
|
|
|
|
var
|
|
|
|
done: Boolean;
|
2015-07-26 12:40:51 +00:00
|
|
|
n: Integer;
|
|
|
|
uch: Cardinal;
|
2014-05-19 22:26:42 +00:00
|
|
|
begin
|
|
|
|
done := false;
|
2014-06-12 22:20:45 +00:00
|
|
|
while (FCurrent < FEnd) and (FStatus = psOK) and (not done) do begin
|
|
|
|
case FToken of
|
|
|
|
'\': // Excel: add next character literally
|
2014-05-22 21:54:24 +00:00
|
|
|
begin
|
2014-06-12 22:20:45 +00:00
|
|
|
FToken := NextToken;
|
|
|
|
AddElement(nftText, FToken);
|
2014-05-22 21:54:24 +00:00
|
|
|
end;
|
2014-06-12 22:20:45 +00:00
|
|
|
'*': // Excel: repeat next character to fill cell. For accounting format.
|
2014-05-20 16:13:48 +00:00
|
|
|
begin
|
2014-06-12 22:20:45 +00:00
|
|
|
FToken := NextToken;
|
|
|
|
AddElement(nftRepeat, FToken);
|
2014-05-20 16:13:48 +00:00
|
|
|
end;
|
2014-06-12 22:20:45 +00:00
|
|
|
'_': // Excel: Leave width of next character empty
|
|
|
|
begin
|
|
|
|
FToken := NextToken;
|
2015-07-26 12:40:51 +00:00
|
|
|
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);
|
2014-06-12 22:20:45 +00:00
|
|
|
end;
|
|
|
|
'@': // Excel: Indicates text format
|
2014-05-21 12:12:16 +00:00
|
|
|
begin
|
2014-06-12 22:20:45 +00:00
|
|
|
AddElement(nftTextFormat, FToken);
|
2014-05-21 12:12:16 +00:00
|
|
|
end;
|
2014-06-12 22:20:45 +00:00
|
|
|
'"':
|
|
|
|
ScanQuotedText;
|
|
|
|
'(', ')':
|
|
|
|
AddElement(nftSignBracket, FToken);
|
2015-03-31 19:01:16 +00:00
|
|
|
'0', '#', '?', '.', ',', '-':
|
2014-05-20 16:13:48 +00:00
|
|
|
ScanNumber;
|
2014-06-05 19:57:00 +00:00
|
|
|
'y', 'Y', 'm', 'M', 'd', 'D', 'h', 'N', 'n', 's':
|
2014-05-20 16:13:48 +00:00
|
|
|
ScanDateTime;
|
2014-06-05 19:57:00 +00:00
|
|
|
'[':
|
2014-06-12 22:20:45 +00:00
|
|
|
ScanBrackets;
|
|
|
|
' ':
|
|
|
|
AddElement(nftSpace, FToken);
|
|
|
|
'A', 'a':
|
2014-06-05 19:57:00 +00:00
|
|
|
begin
|
2014-06-12 22:20:45 +00:00
|
|
|
ScanAMPM;
|
|
|
|
FToken := PrevToken;
|
2014-06-05 19:57:00 +00:00
|
|
|
end;
|
2015-05-29 21:35:07 +00:00
|
|
|
'G', 'g':
|
|
|
|
ScanGeneral;
|
2014-06-12 22:20:45 +00:00
|
|
|
';': // End of the section. Important: Cursor must stay on ';'
|
2014-05-20 16:13:48 +00:00
|
|
|
begin
|
2014-06-12 22:20:45 +00:00
|
|
|
AddSection;
|
|
|
|
Exit;
|
2014-05-20 16:13:48 +00:00
|
|
|
end;
|
2015-04-18 14:58:38 +00:00
|
|
|
else
|
2015-07-26 12:40:51 +00:00
|
|
|
uch := UTF8CharacterToUnicode(FCurrent, n);
|
|
|
|
if n > 1 then
|
|
|
|
begin
|
|
|
|
AddElement(nftText, UnicodeToUTF8(uch));
|
|
|
|
inc(FCurrent, n-1);
|
|
|
|
end else
|
|
|
|
AddElement(nftText, FToken);
|
2014-05-19 22:26:42 +00:00
|
|
|
end;
|
2014-06-12 22:20:45 +00:00
|
|
|
FToken := NextToken;
|
2014-05-19 22:26:42 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2015-05-29 21:35:07 +00:00
|
|
|
{ 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;
|
|
|
|
|
2014-06-12 22:20:45 +00:00
|
|
|
{ Scans a floating point format. Procedure is left with the cursor at the last
|
|
|
|
character of the format. }
|
2014-05-19 22:26:42 +00:00
|
|
|
procedure TsNumFormatParser.ScanNumber;
|
|
|
|
var
|
2014-06-12 22:20:45 +00:00
|
|
|
hasDecSep: Boolean;
|
2015-04-08 22:43:57 +00:00
|
|
|
isFrac: Boolean;
|
2015-05-15 17:33:24 +00:00
|
|
|
n, m: Integer;
|
2015-04-18 14:58:38 +00:00
|
|
|
el: Integer;
|
|
|
|
savedCurrent: PChar;
|
2015-05-30 22:09:53 +00:00
|
|
|
thSep: Char;
|
2014-05-19 22:26:42 +00:00
|
|
|
begin
|
2014-06-12 22:20:45 +00:00
|
|
|
hasDecSep := false;
|
2015-04-08 22:43:57 +00:00
|
|
|
isFrac := false;
|
2015-05-30 22:09:53 +00:00
|
|
|
thSep := ',';
|
2014-06-12 22:20:45 +00:00
|
|
|
while (FCurrent < FEnd) and (FStatus = psOK) do begin
|
|
|
|
case FToken of
|
2014-06-19 19:25:40 +00:00
|
|
|
',': AddElement(nftThSep, ',');
|
2014-05-19 22:26:42 +00:00
|
|
|
'.': begin
|
2014-06-12 22:20:45 +00:00
|
|
|
AddElement(nftDecSep, '.');
|
|
|
|
hasDecSep := true;
|
2014-05-19 22:26:42 +00:00
|
|
|
end;
|
2015-04-18 14:58:38 +00:00
|
|
|
'#': begin
|
|
|
|
ScanAndCount('#', n);
|
|
|
|
savedCurrent := FCurrent;
|
2015-05-30 22:09:53 +00:00
|
|
|
if not (hasDecSep or isFrac) and (n = 1) and (FToken = thSep) then
|
2015-04-18 14:58:38 +00:00
|
|
|
begin
|
2015-05-15 17:33:24 +00:00
|
|
|
m := 0;
|
2015-04-18 14:58:38 +00:00
|
|
|
FToken := NextToken;
|
|
|
|
ScanAndCount('#', n);
|
|
|
|
case n of
|
|
|
|
0: begin
|
|
|
|
ScanAndCount('0', n);
|
2015-05-30 22:09:53 +00:00
|
|
|
ScanAndCount(thSep, m);
|
2015-04-18 14:58:38 +00:00
|
|
|
FToken := prevToken;
|
|
|
|
if n = 3 then
|
2015-05-30 22:09:53 +00:00
|
|
|
AddElement(nftIntTh, 3, ',')
|
2015-04-18 14:58:38 +00:00
|
|
|
else
|
|
|
|
FCurrent := savedCurrent;
|
|
|
|
end;
|
|
|
|
1: begin
|
|
|
|
ScanAndCount('0', n);
|
2015-05-30 22:09:53 +00:00
|
|
|
ScanAndCount(thSep, m);
|
2015-04-18 14:58:38 +00:00
|
|
|
FToken := prevToken;
|
|
|
|
if n = 2 then
|
2015-05-30 22:09:53 +00:00
|
|
|
AddElement(nftIntTh, 2, ',')
|
2015-04-18 14:58:38 +00:00
|
|
|
else
|
|
|
|
FCurrent := savedCurrent;
|
|
|
|
end;
|
|
|
|
2: begin
|
|
|
|
ScanAndCount('0', n);
|
2015-05-30 22:09:53 +00:00
|
|
|
ScanAndCount(thSep, m);
|
2015-04-18 14:58:38 +00:00
|
|
|
FToken := prevToken;
|
|
|
|
if (n = 1) then
|
2015-05-30 22:09:53 +00:00
|
|
|
AddElement(nftIntTh, 1, ',')
|
2015-04-18 14:58:38 +00:00
|
|
|
else
|
|
|
|
FCurrent := savedCurrent;
|
|
|
|
end;
|
|
|
|
end;
|
2015-05-15 17:33:24 +00:00
|
|
|
if m > 0 then
|
2015-05-30 22:09:53 +00:00
|
|
|
AddElement(nftFactor, m, thSep);
|
2015-04-18 14:58:38 +00:00
|
|
|
end else
|
|
|
|
begin
|
|
|
|
FToken := PrevToken;
|
|
|
|
if isFrac then
|
|
|
|
AddElement(nftFracDenomOptDigit, n)
|
|
|
|
else
|
|
|
|
if hasDecSep then
|
|
|
|
AddElement(nftOptDecs, n)
|
|
|
|
else
|
|
|
|
AddElement(nftIntOptDigit, n);
|
|
|
|
end;
|
|
|
|
end;
|
2015-04-08 22:43:57 +00:00
|
|
|
'0': begin
|
2014-06-12 22:20:45 +00:00
|
|
|
ScanAndCount('0', n);
|
2015-05-30 22:09:53 +00:00
|
|
|
ScanAndCount(thSep, m);
|
2014-06-12 22:20:45 +00:00
|
|
|
FToken := PrevToken;
|
2015-04-08 22:43:57 +00:00
|
|
|
if hasDecSep then
|
2015-04-18 14:58:38 +00:00
|
|
|
AddElement(nftZeroDecs, n)
|
2015-04-08 22:43:57 +00:00
|
|
|
else
|
|
|
|
if isFrac then
|
|
|
|
AddElement(nftFracDenomZeroDigit, n)
|
|
|
|
else
|
2015-04-18 14:58:38 +00:00
|
|
|
AddElement(nftIntZeroDigit, n);
|
2015-05-15 17:33:24 +00:00
|
|
|
if m > 0 then
|
2015-05-30 22:09:53 +00:00
|
|
|
AddElement(nftFactor, m, thSep);
|
2015-04-18 14:58:38 +00:00
|
|
|
end;
|
2015-04-29 16:23:07 +00:00
|
|
|
'1'..'9':
|
|
|
|
begin
|
|
|
|
if isFrac then
|
|
|
|
begin
|
|
|
|
n := 0;
|
2015-05-30 22:09:53 +00:00
|
|
|
while (FToken in ['1'..'9','0']) do
|
2015-04-29 16:23:07 +00:00
|
|
|
begin
|
|
|
|
n := n*10 + StrToInt(FToken);
|
|
|
|
FToken := nextToken;
|
|
|
|
end;
|
|
|
|
AddElement(nftFracDenom, n);
|
|
|
|
end else
|
|
|
|
AddElement(nftText, FToken);
|
|
|
|
end;
|
2015-04-18 14:58:38 +00:00
|
|
|
'?': begin
|
|
|
|
ScanAndCount('?', n);
|
|
|
|
FToken := PrevToken;
|
|
|
|
if hasDecSep then
|
|
|
|
AddElement(nftSpaceDecs, n)
|
|
|
|
else
|
|
|
|
if isFrac then
|
|
|
|
AddElement(nftFracDenomSpaceDigit, n)
|
|
|
|
else
|
|
|
|
AddElement(nftIntSpaceDigit, n);
|
2015-04-08 22:43:57 +00:00
|
|
|
end;
|
2014-05-19 22:26:42 +00:00
|
|
|
'E', 'e':
|
|
|
|
begin
|
2014-06-12 22:20:45 +00:00
|
|
|
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;
|
2014-05-19 22:26:42 +00:00
|
|
|
end;
|
|
|
|
'+', '-':
|
2014-06-12 22:20:45 +00:00
|
|
|
AddElement(nftSign, FToken);
|
|
|
|
'%': AddElement(nftPercent, FToken);
|
2015-04-08 22:43:57 +00:00
|
|
|
'/': begin
|
|
|
|
isFrac := true;
|
|
|
|
AddElement(nftFracSymbol, FToken);
|
2015-04-18 14:58:38 +00:00
|
|
|
// 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;
|
2015-04-08 22:43:57 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
2015-05-29 21:35:07 +00:00
|
|
|
'G', 'g':
|
|
|
|
ScanGeneral;
|
2014-06-12 22:20:45 +00:00
|
|
|
else
|
|
|
|
FToken := PrevToken;
|
|
|
|
Exit;
|
2014-05-19 22:26:42 +00:00
|
|
|
end;
|
2014-06-12 22:20:45 +00:00
|
|
|
FToken := NextToken;
|
|
|
|
end;
|
2014-05-19 22:26:42 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
{ Scans a text in quotation marks. Tries to interpret the text as a currency
|
2014-06-12 22:20:45 +00:00
|
|
|
symbol (--> AnalyzeText).
|
|
|
|
The procedure is entered and left with the cursor at a quotation mark. }
|
|
|
|
procedure TsNumFormatParser.ScanQuotedText;
|
2014-05-19 22:26:42 +00:00
|
|
|
var
|
|
|
|
s: String;
|
|
|
|
begin
|
|
|
|
s := '';
|
2014-06-12 22:20:45 +00:00
|
|
|
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;
|
2014-05-19 22:26:42 +00:00
|
|
|
end else begin
|
2014-06-12 22:20:45 +00:00
|
|
|
s := s + FToken;
|
|
|
|
FToken := NextToken;
|
2014-05-19 22:26:42 +00:00
|
|
|
end;
|
|
|
|
end;
|
2014-06-12 22:20:45 +00:00
|
|
|
// When the procedure gets here the final quotation mark is missing
|
|
|
|
FStatus := psErrQuoteExpected;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsNumFormatParser.SetDecimals(AValue: Byte);
|
|
|
|
var
|
2014-06-13 07:59:42 +00:00
|
|
|
i, j, n: Integer;
|
2015-04-22 18:48:08 +00:00
|
|
|
foundDecs: Boolean;
|
2014-06-12 22:20:45 +00:00
|
|
|
begin
|
2015-04-22 18:48:08 +00:00
|
|
|
foundDecs := false;
|
2014-06-13 07:59:42 +00:00
|
|
|
for j := 0 to High(FSections) do begin
|
|
|
|
n := Length(FSections[j].Elements);
|
2014-06-13 10:03:29 +00:00
|
|
|
i := n-1;
|
|
|
|
while (i > -1) do begin
|
2014-06-13 07:59:42 +00:00
|
|
|
case FSections[j].Elements[i].Token of
|
2015-04-22 18:48:08 +00:00
|
|
|
nftDecSep: // this happens, e.g., for "0.E+00"
|
|
|
|
if (AValue > 0) and not foundDecs then begin
|
|
|
|
InsertElement(j, i, nftZeroDecs, AValue);
|
|
|
|
break;
|
|
|
|
end;
|
2015-04-18 14:58:38 +00:00
|
|
|
nftIntOptDigit, nftIntZeroDigit, nftIntSpaceDigit, nftIntTh:
|
2014-06-13 07:59:42 +00:00
|
|
|
// no decimals so far --> add decimal separator and decimals element
|
2014-06-13 10:03:29 +00:00
|
|
|
if (AValue > 0) then begin
|
|
|
|
// Don't use "AddElements" because nfCurrency etc have elements after the number.
|
|
|
|
InsertElement(j, i, nftDecSep, '.');
|
2015-04-18 14:58:38 +00:00
|
|
|
InsertElement(j, i+1, nftZeroDecs, AValue);
|
2014-06-13 10:03:29 +00:00
|
|
|
break;
|
2014-06-13 07:59:42 +00:00
|
|
|
end;
|
2015-04-18 14:58:38 +00:00
|
|
|
nftZeroDecs, nftOptDecs, nftSpaceDecs:
|
2015-04-22 18:48:08 +00:00
|
|
|
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;
|
2014-06-13 07:59:42 +00:00
|
|
|
end;
|
|
|
|
end;
|
2014-06-13 10:03:29 +00:00
|
|
|
dec(i);
|
2014-06-13 07:59:42 +00:00
|
|
|
end;
|
|
|
|
end;
|
2014-05-19 22:26:42 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
end.
|