You've already forked lazarus-ccr
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3386 8e941d3f-bd1b-0410-a28a-d453659cc2b4
1596 lines
48 KiB
ObjectPascal
1596 lines
48 KiB
ObjectPascal
unit fpsNumFormatParser;
|
|
|
|
{$ifdef fpc}
|
|
{$mode delphi}
|
|
{$endif}
|
|
|
|
interface
|
|
|
|
uses
|
|
SysUtils, fpspreadsheet;
|
|
|
|
|
|
const
|
|
psOK = 0;
|
|
psErrNoValidColorIndex = 1;
|
|
psErrNoValidCompareNumber = 2;
|
|
psErrUnknownInfoInBrackets = 3;
|
|
psErrConditionalFormattingNotSupported = 4;
|
|
psErrNoUsableFormat = 5;
|
|
psErrNoValidNumberFormat = 6;
|
|
psErrNoValidDateTimeFormat = 7;
|
|
psErrQuoteExpected = 8;
|
|
psAmbiguousSymbol = 9;
|
|
|
|
{ TsNumFormatParser }
|
|
|
|
type
|
|
TsNumFormatDialect = (nfdDefault, nfdExcel);
|
|
// nfdDefault is the dialect used by fpc,
|
|
// nfdExcel is the dialect used by Excel
|
|
|
|
TsNumFormatToken = (nftText, nftThSep, nftDecSep,
|
|
nftYear, nftMonth, nftDay, nftHour, nftMinute, nftSecond, nftMilliseconds,
|
|
nftAMPM, nftMonthMinute, nftDateTimeSep,
|
|
nftSign, nftSignBracket,
|
|
nftDigit, nftOptDigit, nftDecs, nftOptDec,
|
|
nftExpChar, nftExpSign, nftExpDigits,
|
|
nftPercent,
|
|
nftCurrSymbol, nftCountry,
|
|
nftColor, nftCompareOp, nftCompareValue,
|
|
nftSpace, nftEscaped,
|
|
nftRepeat, nftEmptyCharWidth,
|
|
nftTextFormat);
|
|
|
|
TsNumFormatElement = record
|
|
Token: TsNumFormatToken;
|
|
IntValue: Integer;
|
|
FloatValue: Double;
|
|
TextValue: String;
|
|
end;
|
|
|
|
TsNumFormatElements = array of TsNumFormatElement;
|
|
|
|
TsNumFormatSection = record
|
|
Elements: TsNumFormatElements;
|
|
NumFormat: TsNumberFormat;
|
|
Decimals: Byte;
|
|
CurrencySymbol: String;
|
|
Color: TsColor;
|
|
end;
|
|
|
|
TsNumFormatSections = array of TsNumFormatSection;
|
|
|
|
TsNumFormatParser = class
|
|
private
|
|
FCreateMethod: Byte;
|
|
FToken: Char;
|
|
FCurrent: PChar;
|
|
FStart: PChar;
|
|
FEnd: PChar;
|
|
FCurrSection: Integer;
|
|
FHasRedSection: Boolean;
|
|
FStatus: Integer;
|
|
function GetCurrencySymbol: String;
|
|
function GetDecimals: byte;
|
|
function GetFormatString(ADialect: TsNumFormatDialect): String;
|
|
function GetNumFormat: TsNumberFormat;
|
|
function GetParsedSectionCount: Integer;
|
|
function GetParsedSections(AIndex: Integer): TsNumFormatSection;
|
|
procedure SetDecimals(AValue: Byte);
|
|
|
|
protected
|
|
FWorkbook: TsWorkbook;
|
|
FSections: TsNumFormatSections;
|
|
|
|
{ Administration while scanning }
|
|
procedure AddElement(AToken: TsNumFormatToken; AText: String); overload;
|
|
procedure AddElement(AToken: TsNumFormatToken; AIntValue: Integer); 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 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);
|
|
// Format string
|
|
function BuildFormatString(ADialect: TsNumFormatDialect): String; virtual;
|
|
function BuildFormatStringFromSection(ASection: Integer;
|
|
ADialect: TsNumFormatDialect): String; virtual;
|
|
// NumberFormat
|
|
procedure EvalNumFormatOfSection(ASection: Integer; out ANumFormat: TsNumberFormat;
|
|
out ADecimals: byte; out ACurrencySymbol: String; out AColor: TsColor);
|
|
function IsCurrencyAt(ASection: Integer; out ANumFormat: TsNumberFormat;
|
|
out ADecimals: byte; out ACurrencySymbol: String; out AColor: TsColor): Boolean;
|
|
function IsDateAt(ASection,AIndex: Integer; out ANumberFormat: TsNumberFormat;
|
|
var ANextIndex: Integer): Boolean;
|
|
function IsNumberAt(ASection,AIndex: Integer; out ANumberFormat: TsNumberFormat;
|
|
out ADecimals: Byte; out ANextIndex: Integer): Boolean;
|
|
function IsTextAt(AText: string; ASection, AIndex: Integer): Boolean;
|
|
function IsTimeAt(ASection,AIndex: Integer; out ANumberFormat: TsNumberFormat;
|
|
out ANextIndex: Integer): Boolean;
|
|
function IsTokenAt(AToken: TsNumFormatToken; ASection,AIndex: Integer): Boolean;
|
|
|
|
public
|
|
constructor Create(AWorkbook: TsWorkbook; const AFormatString: String;
|
|
const ANumFormat: TsNumberFormat = nfGeneral);
|
|
destructor Destroy; override;
|
|
procedure ClearAll;
|
|
function GetDateTimeCode(ASection: Integer): String;
|
|
function IsDateTimeFormat: Boolean;
|
|
function IsTimeFormat: Boolean;
|
|
procedure LimitDecimals;
|
|
procedure Localize;
|
|
|
|
property CurrencySymbol: String read GetCurrencySymbol;
|
|
property Decimals: Byte read GetDecimals write SetDecimals;
|
|
property FormatString[ADialect: TsNumFormatDialect]: String read GetFormatString;
|
|
property NumFormat: TsNumberFormat read GetNumFormat;
|
|
property ParsedSectionCount: Integer read GetParsedSectionCount;
|
|
property ParsedSections[AIndex: Integer]: TsNumFormatSection read GetParsedSections;
|
|
property Status: Integer read FStatus;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
TypInfo, StrUtils, fpsutils;
|
|
|
|
|
|
{ TsNumFormatParser }
|
|
|
|
{ Creates a number format parser for analyzing a formatstring that has been read
|
|
from a spreadsheet file.
|
|
In case of "red" number formats we also have to specify the number format
|
|
because the format string might not contain the color information, and we
|
|
extract it from the NumFormat in this case. }
|
|
constructor TsNumFormatParser.Create(AWorkbook: TsWorkbook;
|
|
const AFormatString: String; const ANumFormat: TsNumberFormat = nfGeneral);
|
|
begin
|
|
inherited Create;
|
|
FCreateMethod := 0;
|
|
FWorkbook := AWorkbook;
|
|
FHasRedSection := (ANumFormat = nfCurrencyRed);
|
|
Parse(AFormatString);
|
|
end;
|
|
|
|
destructor TsNumFormatParser.Destroy;
|
|
begin
|
|
FSections := nil;
|
|
// ClearAll;
|
|
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);
|
|
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;
|
|
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;
|
|
var
|
|
uValue: String;
|
|
begin
|
|
if (FWorkbook = nil) or (FWorkbook.FormatSettings.CurrencyString = '') then
|
|
Result := false
|
|
else begin
|
|
uValue := Uppercase(AValue);
|
|
Result := (uValue = Uppercase(AnsiToUTF8(FWorkbook.FormatSettings.CurrencyString))) or
|
|
(uValue = '$') or (uValue = 'USD') or
|
|
(uValue = '�') or (uValue = 'EUR') or
|
|
(uValue = '�') or (uValue = 'GBP') or
|
|
(uValue = '�') or (uValue = 'JPY');
|
|
end;
|
|
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(ADialect: TsNumFormatDialect): String;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if Length(FSections) > 0 then begin
|
|
Result := BuildFormatStringFromSection(0, ADialect);
|
|
for i := 1 to High(FSections) do
|
|
Result := Result + ';' + BuildFormatStringFromSection(i, ADialect);
|
|
end else
|
|
Result := '';
|
|
end;
|
|
|
|
{ Creates a format string for the given section. This implementation covers
|
|
the formatstring dialects of fpc (nfdDefault) and Excel (nfdExcel). }
|
|
function TsNumFormatParser.BuildFormatStringFromSection(ASection: Integer;
|
|
ADialect: TsNumFormatDialect): String;
|
|
var
|
|
element: TsNumFormatElement;
|
|
i: Integer;
|
|
begin
|
|
Result := '';
|
|
|
|
if (ASection < 0) and (ASection >= GetParsedSectionCount) then
|
|
exit;
|
|
for i := 0 to High(FSections[ASection].Elements) do begin
|
|
element := FSections[ASection].Elements[i];
|
|
case element.Token of
|
|
nftText:
|
|
if element.TextValue <> '' then result := Result + '"' + element.TextValue + '"';
|
|
nftThSep, nftDecSep:
|
|
Result := Result + element.TextValue;
|
|
nftDigit:
|
|
Result := Result + '0';
|
|
nftOptDigit, nftOptDec:
|
|
Result := Result + '#';
|
|
nftYear:
|
|
Result := Result + DupeString(IfThen(ADialect = nfdExcel, 'Y', 'y'), element.IntValue);
|
|
nftMonth:
|
|
Result := Result + DupeString(IfThen(ADialect = nfdExcel, 'M', 'm'), element.IntValue);
|
|
nftDay:
|
|
Result := Result + DupeString(IfThen(ADialect = nfdExcel, 'D', 'd'), element.IntValue);
|
|
nftHour:
|
|
if element.IntValue < 0
|
|
then Result := Result + '[' + DupeString('h', -element.IntValue) + ']'
|
|
else Result := Result + DupeString('h', element.IntValue);
|
|
nftMinute:
|
|
if element.IntValue < 0
|
|
then Result := result + '[' + DupeString(IfThen(ADialect = nfdExcel, 'm', 'n'), -element.IntValue) + ']'
|
|
else Result := Result + DupeString(IfThen(ADialect = nfdExcel, 'm', 'n'), element.IntValue);
|
|
nftSecond:
|
|
if element.IntValue < 0
|
|
then Result := Result + '[' + DupeString('s', -element.IntValue) + ']'
|
|
else Result := Result + DupeString('s', element.IntValue);
|
|
nftDecs, nftExpDigits, nftMilliseconds:
|
|
Result := Result + Dupestring('0', element.IntValue);
|
|
nftSpace, nftSign, nftSignBracket, nftExpChar, nftExpSign, nftPercent,
|
|
nftAMPM, nftDateTimeSep:
|
|
if element.TextValue <> '' then Result := Result + element.TextValue;
|
|
nftCurrSymbol:
|
|
if element.TextValue <> '' then begin
|
|
if ADialect = nfdExcel then
|
|
Result := Result + '[$' + element.TextValue + ']'
|
|
else
|
|
Result := Result + '"' + element.TextValue + '"';
|
|
end;
|
|
nftEscaped:
|
|
if element.TextValue <> '' then begin
|
|
if ADialect = nfdExcel then
|
|
Result := Result + '\' + element.TextValue
|
|
else
|
|
Result := Result + element.TextValue;
|
|
end;
|
|
nftTextFormat:
|
|
if element.TextValue <> '' then
|
|
if ADialect = nfdExcel then Result := Result + element.TextValue;
|
|
nftRepeat:
|
|
if element.TextValue <> '' then Result := Result + '*' + element.TextValue;
|
|
nftColor:
|
|
if ADialect = nfdExcel then begin
|
|
case element.IntValue of
|
|
scBlack : Result := '[black]';
|
|
scWhite : Result := '[white]';
|
|
scRed : Result := '[red]';
|
|
scBlue : Result := '[blue]';
|
|
scGreen : Result := '[green]';
|
|
scYellow : Result := '[yellow]';
|
|
scMagenta: Result := '[magenta]';
|
|
scCyan : Result := '[cyan]';
|
|
else Result := Format('[Color%d]', [element.IntValue]);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TsNumFormatParser.CheckSections;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i:=0 to Length(FSections)-1 do
|
|
CheckSection(i);
|
|
end;
|
|
|
|
procedure TsNumFormatParser.CheckSection(ASection: Integer);
|
|
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 FSections[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(FSections[ASection].Elements)) do begin
|
|
with FSections[ASection].Elements[j] do
|
|
if Token in [nftYear, nftMonth, nftDay, nftHour, nftMinute, nftSecond] then
|
|
begin
|
|
Result := j;
|
|
exit;
|
|
end;
|
|
inc(j);
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
// Fix the ambiguous "m":
|
|
for i:=0 to High(FSections[ASection].Elements) do
|
|
// Find index of nftMonthMinute token...
|
|
if FSections[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 FSections[ASection].Elements[j].Token of
|
|
nftDay, nftYear:
|
|
begin
|
|
FSections[ASection].Elements[i].Token := nftMonth;
|
|
Continue;
|
|
end;
|
|
nftSecond:
|
|
begin
|
|
FSections[ASection].Elements[i].Token := nftMinute;
|
|
Continue;
|
|
end;
|
|
end;
|
|
j := PrevDateTimeElement(i);
|
|
if j <> -1 then
|
|
case FSections[ASection].Elements[j].Token of
|
|
nftDay, nftYear:
|
|
begin
|
|
FSections[ASection].Elements[i].Token := nftMonth;
|
|
Continue;
|
|
end;
|
|
nftHour:
|
|
begin
|
|
FSections[ASection].Elements[i].Token := nftMinute;
|
|
Continue;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
EvalNumFormatOfSection(ASection,
|
|
FSections[ASection].NumFormat,
|
|
FSections[ASection].Decimals,
|
|
FSections[ASection].CurrencySymbol,
|
|
FSections[ASection].Color
|
|
);
|
|
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;
|
|
|
|
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(ADialect: TsNumFormatDialect): String;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
Result := '';
|
|
if Length(FSections) > 0 then begin
|
|
Result := BuildFormatStringFromSection(0, ADialect);
|
|
for i:=1 to High(FSections) do
|
|
Result := Result + ';' + BuildFormatStringFromSection(i, ADialect);
|
|
end;
|
|
end;
|
|
|
|
procedure TsNumFormatParser.EvalNumFormatOfSection(ASection: Integer;
|
|
out ANumFormat: TsNumberFormat; out ADecimals: byte; out ACurrencySymbol: String;
|
|
out AColor: TsColor);
|
|
var
|
|
nf: TsNumberFormat;
|
|
next: Integer = 0;
|
|
begin
|
|
ANumFormat := nfCustom;
|
|
ADecimals := 0;
|
|
ACurrencySymbol := '';
|
|
AColor := scNotDefined;
|
|
|
|
with FSections[ASection] do begin
|
|
if Length(Elements) = 0 then begin
|
|
ANumFormat := nfGeneral;
|
|
exit;
|
|
end;
|
|
|
|
// Look for number formats
|
|
if IsNumberAt(ASection, 0, ANumFormat, ADecimals, next) then begin
|
|
// nfFixed, nfFixedTh
|
|
if next = Length(Elements) then
|
|
exit;
|
|
// nfPercentage
|
|
if IsTokenAt(nftPercent, ASection, next) and (next+1 = Length(Elements))
|
|
then begin
|
|
ANumFormat := nfPercentage;
|
|
exit;
|
|
end;
|
|
// nfExp
|
|
if IsTokenAt(nftExpChar, ASection, next) then begin
|
|
if IsTokenAt(nftExpSign, ASection, next+1) and IsTokenAt(nftExpDigits, ASection, next+2) and
|
|
(next+3 = Length(Elements))
|
|
then begin
|
|
if ANumFormat = nfFixed then
|
|
ANumFormat := nfExp;
|
|
exit;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// Currency?
|
|
if IsCurrencyAt(ASection, ANumFormat, ADecimals, ACurrencySymbol, AColor)
|
|
then exit;
|
|
|
|
// Look for date formats
|
|
if IsDateAt(ASection, 0, ANumFormat, next) then begin
|
|
if (next = Length(Elements)) then
|
|
exit;
|
|
if IsTextAt(' ', ASection, next) and IsTimeAt(ASection, next+1, nf, next) and
|
|
(next = Length(Elements))
|
|
then begin
|
|
if (ANumFormat = nfShortDate) and (nf = nfShortTime) then
|
|
ANumFormat := nfShortDateTime;
|
|
end;
|
|
exit;
|
|
end;
|
|
|
|
// Look for time formats
|
|
if IsTimeAt(ASection, 0, ANumFormat, next) then
|
|
if next = Length(Elements) then
|
|
exit;
|
|
end;
|
|
|
|
// What is left must be a custom format.
|
|
ANumFormat := nfCustom;
|
|
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;
|
|
|
|
{ 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;
|
|
|
|
{ Checks if a currency-type of format string begins at index AIndex, and returns
|
|
the numberformat code, the count of decimals, the currency sambol, and the
|
|
color.
|
|
Note that the check is not very exact, but should cover most cases. }
|
|
function TsNumFormatParser.IsCurrencyAt(ASection: Integer;
|
|
out ANumFormat: TsNumberFormat; out ADecimals: byte;
|
|
out ACurrencySymbol: String; out AColor: TsColor): Boolean;
|
|
var
|
|
hasCurrSymbol: Boolean;
|
|
hasColor: Boolean;
|
|
el: Integer;
|
|
begin
|
|
Result := false;
|
|
|
|
ANumFormat := nfCustom;
|
|
ACurrencySymbol := '';
|
|
ADecimals := 0;
|
|
AColor := scNotDefined;
|
|
hasColor := false;
|
|
hasCurrSymbol := false;
|
|
|
|
// Looking for the currency symbol: it is the unique identifier of the
|
|
// currency format.
|
|
for el := 0 to High(FSections[ASection].Elements) do
|
|
if FSections[ASection].Elements[el].Token = nftCurrSymbol then begin
|
|
Result := true;
|
|
break;
|
|
end;
|
|
|
|
if not Result then
|
|
exit;
|
|
|
|
{ When the format string comes from fpc it does not contain a color token.
|
|
Color would be lost when saving. Therefore, we take the color from the
|
|
knowledge of the NumFormat passed on creation: nfCurrencyRed has color red
|
|
in the second section! }
|
|
if (ASection = 1) and FHasRedSection then
|
|
AColor := scRed;
|
|
|
|
// Now that we know that it is a currency format analyze the elements again
|
|
// and determine color, decimals and currency symbol.
|
|
el := 0;
|
|
while (el < Length(FSections[ASection].Elements)) do begin
|
|
case FSections[ASection].Elements[el].Token of
|
|
nftColor:
|
|
begin
|
|
AColor := FSections[ASection].Elements[el].IntValue;
|
|
hasColor := true;
|
|
end;
|
|
nftRepeat:
|
|
;
|
|
nftCurrSymbol:
|
|
begin
|
|
ACurrencySymbol := FSections[ASection].Elements[el].TextValue;
|
|
hasCurrSymbol := true;
|
|
end;
|
|
nftOptDigit:
|
|
if IsNumberAt(ASection, el, ANumFormat, ADecimals, el) then
|
|
dec(el)
|
|
else begin
|
|
Result := false;
|
|
exit;
|
|
end;
|
|
nftDigit:
|
|
if IsNumberAt(ASection, el, ANumFormat, ADecimals, el) then
|
|
dec(el)
|
|
else begin
|
|
Result := false;
|
|
exit;
|
|
end;
|
|
end;
|
|
inc(el);
|
|
end;
|
|
|
|
if (ASection = 1) and FHasRedSection and not hasColor then
|
|
InsertElement(ASection, 0, nftColor, scRed);
|
|
|
|
Result := hasCurrSymbol and ((ANumFormat = nfFixedTh) or (ASection = 2));
|
|
if Result then begin
|
|
if AColor = scNotDefined then ANumFormat := nfCurrency else
|
|
if AColor = scRed then ANumFormat := nfCurrencyRed;
|
|
end else
|
|
ANumFormat := nfCustom;
|
|
end;
|
|
|
|
function TsNumFormatParser.IsDateAt(ASection,AIndex: Integer;
|
|
out ANumberFormat: TsNumberFormat; var ANextIndex: Integer): Boolean;
|
|
|
|
function CheckFormat(AFmtStr: String; var idx: Integer): Boolean;
|
|
var
|
|
i: Integer;
|
|
s: String;
|
|
begin
|
|
Result := false;
|
|
idx := AIndex;
|
|
i := 1;
|
|
while (i < Length(AFmtStr)) and (idx < Length(FSections[ASection].Elements)) do begin
|
|
case AFmtStr[i] of
|
|
'y', 'Y':
|
|
begin
|
|
if not IsTokenAt(nftYear, ASection, idx) then Exit;
|
|
inc(idx);
|
|
inc(i);
|
|
while (i < Length(AFmtStr)) and (AFmtStr[i] in ['y', 'Y']) do inc(i);
|
|
end;
|
|
'm', 'M':
|
|
begin
|
|
if not IsTokenAt(nftMonth, ASection, idx) then Exit;
|
|
inc(idx);
|
|
inc(i);
|
|
while (i < Length(AFmtStr)) and (AFmtStr[i] in ['m', 'M']) do inc(i);
|
|
end;
|
|
'd', 'D':
|
|
begin
|
|
if not IsTokenAt(nftDay, ASection, idx) then exit;
|
|
inc(idx);
|
|
inc(i);
|
|
while (i < Length(AFmtStr)) and (AFmtStr[i] in ['d', 'D']) do inc(i);
|
|
end;
|
|
'/':
|
|
begin
|
|
if not IsTokenAt(nftDateTimeSep, ASection, idx) then exit;
|
|
s := FSections[ASection].Elements[idx].TextValue;
|
|
if not ((s = '/') or (s = FWorkbook.FormatSettings.DateSeparator)) then
|
|
exit;
|
|
inc(idx);
|
|
inc(i);
|
|
end;
|
|
else
|
|
begin
|
|
if not (IsTokenAt(nftDateTimeSep, ASection, idx) and
|
|
(FSections[ASection].Elements[idx].textValue = AFmtStr[i]))
|
|
then
|
|
exit;
|
|
inc(idx);
|
|
inc(i);
|
|
end;
|
|
end;
|
|
end; // while ...
|
|
Result := true;
|
|
ANextIndex := idx;
|
|
end;
|
|
|
|
begin
|
|
if FWorkbook = nil then begin
|
|
Result := false;
|
|
exit;
|
|
end;
|
|
|
|
// The default format nfShortDate is defined by the ShortDateFormat of the
|
|
// Workbook's FormatSettings. Check whether the current format string matches.
|
|
// But watch out for different date separators!
|
|
if CheckFormat(FWorkbook.FormatSettings.ShortDateFormat, ANextIndex) then begin
|
|
Result := true;
|
|
ANumberFormat := nfShortDate;
|
|
end else
|
|
// dto. with the LongDateFormat
|
|
if CheckFormat(FWorkbook.FormatSettings.LongDateFormat, ANextIndex) then begin
|
|
Result := true;
|
|
ANumberFormat := nfLongDate;
|
|
end else
|
|
Result := false;
|
|
end;
|
|
|
|
{ Returns true if the format elements contain at least one date/time token }
|
|
function TsNumFormatParser.IsDateTimeFormat: Boolean;
|
|
var
|
|
section: Integer;
|
|
elem: Integer;
|
|
begin
|
|
Result := true;
|
|
for section := 0 to High(FSections) do
|
|
for elem := 0 to High(FSections[section].Elements) do
|
|
if FSections[section].Elements[elem].Token in [nftYear, nftMonth, nftDay,
|
|
nftHour, nftMinute, nftSecond]
|
|
then
|
|
exit;
|
|
Result := false;
|
|
end;
|
|
|
|
{ Checks whether the format tokens beginning at AIndex for ASection represent
|
|
at standard number format, like nfFixed, nfPercentage etc.
|
|
Returns TRUE if it does. }
|
|
function TsNumFormatParser.IsNumberAt(ASection,AIndex: Integer;
|
|
out ANumberFormat: TsNumberFormat; out ADecimals: Byte;
|
|
out ANextIndex: Integer): Boolean;
|
|
begin
|
|
Result := false;
|
|
ANumberFormat := nfGeneral;
|
|
ADecimals := 0;
|
|
ANextIndex := MaxInt;
|
|
// Let's look for digit tokens ('0') first
|
|
if IsTokenAt(nftDigit, ASection, AIndex) then begin // '0'
|
|
if IsTokenAt(nftDecSep, ASection, AIndex+1) and // '.'
|
|
IsTokenAt(nftDecs, ASection, AIndex+2) // count of decimals
|
|
then begin
|
|
// This is the case with decimal separator, like "0.000"
|
|
Result := true;
|
|
ANumberFormat := nfFixed;
|
|
ADecimals := FSections[ASection].Elements[AIndex+2].IntValue;
|
|
ANextIndex := AIndex+3;
|
|
end else
|
|
if not IsTokenAt(nftDecSep, ASection, AIndex+1) then begin
|
|
// and this is the (only) case without decimal separator ("0")
|
|
Result := true;
|
|
ANumberFormat := nfFixed;
|
|
ADecimals := 0;
|
|
ANextIndex := AIndex+1;
|
|
end;
|
|
end else
|
|
// Now look also for optional digits ('#')
|
|
if IsTokenAt(nftOptDigit, ASection, AIndex) and // '#'
|
|
IsTokenAt(nftThSep, ASection, AIndex+1) and // ','
|
|
IsTokenAt(nftOptDigit, ASection, AIndex+2) and // '#'
|
|
IsTokenAt(nftOptDigit, ASection, Aindex+3) and // '#'
|
|
IsTokenAt(nftDigit, ASection, AIndex+4) // '0'
|
|
then begin
|
|
if IsTokenAt(nftDecSep, ASection, AIndex+5) and // '.'
|
|
IsTokenAt(nftDecs, ASection, AIndex+6) // count of decimals
|
|
then begin
|
|
// This is the case with decimal separator, like "#,##0.000"
|
|
Result := true;
|
|
ANumberFormat := nfFixedTh;
|
|
ADecimals := FSections[ASection].Elements[AIndex+6].IntValue;
|
|
ANextIndex := AIndex+7;
|
|
end else
|
|
if not IsTokenAt(nftDecSep, ASection, AIndex+5) then begin
|
|
// and this is without decimal separator, "#,##0"
|
|
result := true;
|
|
ANumberFormat := nfFixedTh;
|
|
ADecimals := 0;
|
|
ANextIndex := AIndex + 5;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TsNumFormatParser.IsTextAt(AText: String; ASection, AIndex: Integer): Boolean;
|
|
begin
|
|
Result := IsTokenAt(nftText, ASection, AIndex) and
|
|
(FSections[ASection].Elements[AIndex].TextValue = AText);
|
|
end;
|
|
|
|
function TsNumFormatParser.IsTimeAt(ASection,AIndex: Integer;
|
|
out ANumberFormat: TsNumberFormat; out ANextIndex: Integer): Boolean;
|
|
|
|
function CheckFormat(AFmtStr: String; var idx: Integer;
|
|
out AMPM, IsInterval: boolean): Boolean;
|
|
var
|
|
i: Integer;
|
|
s: String;
|
|
begin
|
|
Result := false;
|
|
AMPM := false;
|
|
IsInterval := false;
|
|
idx := AIndex;
|
|
i := 1;
|
|
while (i < Length(AFmtStr)) and (idx < Length(FSections[ASection].Elements)) do begin
|
|
case AFmtStr[i] of
|
|
'h', 'H':
|
|
begin
|
|
if not IsTokenAt(nftHour, ASection, idx) then Exit;
|
|
if FSections[ASection].Elements[idx].IntValue < 0 then isInterval := true;
|
|
inc(idx);
|
|
inc(i);
|
|
while (i < Length(AFmtStr)) and (AFmtStr[i] in ['h', 'H']) do inc(i);
|
|
end;
|
|
'm', 'M', 'n', 'N':
|
|
begin
|
|
if not IsTokenAt(nftMinute, ASection, idx) then Exit;
|
|
if FSections[ASection].Elements[idx].IntValue < 0 then isInterval := true;
|
|
inc(idx);
|
|
inc(i);
|
|
while (i < Length(AFmtStr)) and (AFmtStr[i] in ['m', 'M', 'n', 'N']) do inc(i);
|
|
end;
|
|
's', 'S':
|
|
begin
|
|
if not IsTokenAt(nftSecond, ASection, idx) then exit;
|
|
if FSections[ASection].Elements[idx].IntValue < 0 then isInterval := true;
|
|
inc(idx);
|
|
inc(i);
|
|
while (i < Length(AFmtStr)) and (AFmtStr[i] in ['s', 'S']) do inc(i);
|
|
end;
|
|
':':
|
|
begin
|
|
if not IsTokenAt(nftDateTimeSep, ASection, idx) then exit;
|
|
s := FSections[ASection].Elements[idx].TextValue;
|
|
if not ((s = ':') or (s = FWorkbook.FormatSettings.DateSeparator)) then
|
|
exit;
|
|
inc(idx);
|
|
inc(i);
|
|
end;
|
|
' ':
|
|
if (i+1 <= Length(AFmtStr)) and (AFmtStr[i+1] in ['a', 'A']) then begin
|
|
inc(idx);
|
|
inc(i);
|
|
end else
|
|
exit;
|
|
'a', 'A':
|
|
begin
|
|
if not IsTokenAt(nftAMPM, ASection, idx) then exit;
|
|
inc(idx);
|
|
inc(i);
|
|
while (i < Length(AFmtStr)) and (AFmtStr[i] in ['m','M','/','p','P']) do inc(i);
|
|
AMPM := true;
|
|
end;
|
|
'[':
|
|
begin
|
|
if not IsTokenAt(nftHour, ASection, idx+1) then exit;
|
|
if IsTextAt(']', ASection, idx+2) then begin
|
|
inc(i, 3);
|
|
inc(idx, 3);
|
|
IsInterval := true;
|
|
end else
|
|
if IsTokenAt(nftHour, ASection, idx+2) and IsTextAt(']', ASection, idx+3) then begin
|
|
inc(i, 4);
|
|
inc(idx, 4);
|
|
isInterval := true;
|
|
end else
|
|
exit;
|
|
end
|
|
else
|
|
exit;
|
|
end;
|
|
end;
|
|
Result := true;
|
|
end;
|
|
|
|
var
|
|
AMPM, isInterval: Boolean;
|
|
i: Integer;
|
|
fmt: String;
|
|
begin
|
|
if FWorkbook = nil then begin
|
|
Result := false;
|
|
exit;
|
|
end;
|
|
|
|
Result := true;
|
|
fmt := AddAMPM(FWorkbook.FormatSettings.LongTimeFormat, FWorkbook.FormatSettings);
|
|
if CheckFormat(fmt, ANextIndex, AMPM, isInterval) then begin
|
|
ANumberFormat := IfThen(AMPM, nfLongTimeAM, IfThen(isInterval, nfTimeInterval, nfLongTime));
|
|
exit;
|
|
end;
|
|
fmt := FWorkbook.FormatSettings.LongTimeFormat;
|
|
if CheckFormat(fmt, ANextIndex, AMPM, isInterval) then begin
|
|
ANumberFormat := IfThen(AMPM, nfLongTimeAM, IfThen(isInterval, nfTimeInterval, nfLongTime));
|
|
exit;
|
|
end;
|
|
fmt := AddAMPM(FWorkbook.FormatSettings.ShortTimeFormat, FWorkbook.FormatSettings);
|
|
if CheckFormat(fmt, ANextIndex, AMPM, isInterval) then begin
|
|
ANumberFormat := IfThen(AMPM, nfShortTimeAM, nfShortTime);
|
|
exit;
|
|
end;
|
|
fmt := AddAMPM(FWorkbook.FormatSettings.ShortTimeFormat, FWorkbook.FormatSettings);
|
|
if CheckFormat(fmt, ANextIndex, AMPM, isInterval) then begin
|
|
ANumberFormat := IfThen(AMPM, nfShortTimeAM, nfShortTime);
|
|
exit;
|
|
end;
|
|
|
|
for i:=0 to High(FSections[ASection].Elements) do
|
|
if (FSections[ASection].Elements[i].Token in [nftHour, nftMinute, nftSecond]) and
|
|
(FSections[ASection].Elements[i].IntValue < 0)
|
|
then begin
|
|
ANumberFormat := nfTimeInterval;
|
|
exit;
|
|
end;
|
|
|
|
Result := false;
|
|
end;
|
|
|
|
{ Returns true if the format elements contain only time, no date tokens. }
|
|
function TsNumFormatParser.IsTimeFormat: Boolean;
|
|
var
|
|
section: Integer;
|
|
elem: Integer;
|
|
begin
|
|
Result := false;
|
|
for section := 0 to High(FSections) do
|
|
for elem := 0 to High(FSections[section].Elements) do
|
|
if FSections[section].Elements[elem].Token in [nftHour, nftMinute, nftSecond]
|
|
then begin
|
|
Result := true;
|
|
end else
|
|
if FSections[section].Elements[elem].Token in
|
|
[nftYear, nftMonth, nftDay, nftExpChar, nftCurrSymbol]
|
|
then begin
|
|
Result := false;
|
|
exit;
|
|
end;
|
|
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 Excel }
|
|
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 = nftDecs then
|
|
if FSections[j].Elements[i].IntValue > 0 then
|
|
FSections[j].Elements[i].IntValue := 2;
|
|
end;
|
|
|
|
{ Localizes the thousand- and decimal separator symbols by replacing them with
|
|
the FormatSettings value of the workbook. A recreated format string will be
|
|
localized as required by Excel2 }
|
|
procedure TsNumFormatParser.Localize;
|
|
var
|
|
i, j: Integer;
|
|
fs: TFormatSettings;
|
|
txt: String;
|
|
begin
|
|
fs := FWorkbook.FormatSettings;
|
|
for j:=0 to High(FSections) do
|
|
for i:=0 to High(FSections[j].Elements) do begin
|
|
txt := FSections[j].Elements[i].TextValue;
|
|
case FSections[j].Elements[i].Token of
|
|
nftThSep : txt := fs.ThousandSeparator;
|
|
nftDecSep : txt := fs.DecimalSeparator;
|
|
nftCurrSymbol: txt := UTF8ToAnsi(txt);
|
|
end;
|
|
FSections[j].Elements[i].TextValue := txt;
|
|
end;
|
|
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 = '') or (lowercase(AFormatString) = 'general') then
|
|
exit;
|
|
|
|
FStart := @AFormatString[1];
|
|
FEnd := FStart + Length(AFormatString);
|
|
FCurrent := FStart;
|
|
FToken := FCurrent^;
|
|
while (FCurrent < FEnd) and (FStatus = psOK) do begin
|
|
case FToken of
|
|
'[': ScanBrackets;
|
|
'"': ScanQuotedText;
|
|
':': AddElement(nftDateTimeSep, ':');
|
|
';': AddSection;
|
|
else ScanFormat;
|
|
end;
|
|
FToken := NextToken;
|
|
end;
|
|
|
|
CheckSections;
|
|
end;
|
|
|
|
{ Scans an AM/PM sequence (or AMPM or A/P).
|
|
At exit, cursor is a next character }
|
|
procedure TsNumFormatParser.ScanAMPM;
|
|
var
|
|
s: String;
|
|
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;
|
|
AddElement(nftAMPM, s);
|
|
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;
|
|
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;
|
|
var
|
|
s: String;
|
|
n: Integer;
|
|
prevtoken: Char;
|
|
begin
|
|
s := '';
|
|
FToken := NextToken; // Cursor was at '['
|
|
while (FCurrent < FEnd) and (FStatus = psOK) do begin
|
|
case FToken of
|
|
'h', 'H', 'm', 'M', 'n', 'N', 's', 'S':
|
|
begin
|
|
prevtoken := FToken;
|
|
ScanAndCount(FToken, n);
|
|
if (FToken in [']', #0]) then begin
|
|
case prevtoken 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;
|
|
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;
|
|
AddElement(nftCurrSymbol, s);
|
|
if FToken <> ']' then begin
|
|
FToken := NextToken;
|
|
while (FCurrent < FEnd) and (FToken <> ']') do begin
|
|
s := s + FToken;
|
|
FToken := NextToken;
|
|
end;
|
|
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
|
|
ScanAndCount(FToken, n);
|
|
AddElement(nftMonthMinute, n); // 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;
|
|
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;
|
|
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;
|
|
';': // End of the section. Important: Cursor must stay on ';'
|
|
begin
|
|
AddSection;
|
|
Exit;
|
|
end;
|
|
end;
|
|
FToken := NextToken;
|
|
end;
|
|
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;
|
|
n: Integer;
|
|
begin
|
|
hasDecSep := false;
|
|
while (FCurrent < FEnd) and (FStatus = psOK) do begin
|
|
case FToken of
|
|
',': AddElement(nftThSep, ',');
|
|
'.': begin
|
|
AddElement(nftDecSep, '.');
|
|
hasDecSep := true;
|
|
end;
|
|
'0': if hasDecSep then begin
|
|
ScanAndCount('0', n);
|
|
FToken := PrevToken;
|
|
AddElement(nftDecs, n);
|
|
end else
|
|
AddElement(nftDigit, '0');
|
|
'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(nftOptDigit, FToken);
|
|
'%': AddElement(nftPercent, FToken);
|
|
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;
|
|
begin
|
|
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
|
|
nftDigit:
|
|
// 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, nftDecs, AValue);
|
|
break;
|
|
end;
|
|
nftDecs:
|
|
if AValue > 0 then begin
|
|
// decimals are already used, just replace value of decimal places
|
|
FSections[j].Elements[i].IntValue := AValue;
|
|
break;
|
|
end else begin
|
|
// No decimals any more: delete decs and decsep elements
|
|
DeleteElement(j, i);
|
|
DeleteElement(j, i-1);
|
|
break;
|
|
end;
|
|
end;
|
|
dec(i);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
end.
|