
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3699 8e941d3f-bd1b-0410-a28a-d453659cc2b4
1589 lines
48 KiB
ObjectPascal
1589 lines
48 KiB
ObjectPascal
unit fpsNumFormatParser;
|
||
|
||
{$ifdef fpc}
|
||
{$mode delphi}{$H+}
|
||
{$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, LazUTF8, fpsutils, fpsCurrency;
|
||
|
||
|
||
{ 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;
|
||
begin
|
||
if (FWorkbook = nil) or (FWorkbook.FormatSettings.CurrencyString = '') then
|
||
Result := false
|
||
else
|
||
Result := CurrencyRegistered(AValue);
|
||
end;
|
||
|
||
{ Creates a formatstring for all sections.
|
||
Note: this implementation is only valid for the fpc and Excel dialects of
|
||
format string. }
|
||
function TsNumFormatParser.BuildFormatString(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; out 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 Excel2. }
|
||
procedure TsNumFormatParser.LimitDecimals;
|
||
var
|
||
i, j: Integer;
|
||
begin
|
||
for j:=0 to High(FSections) do
|
||
for i:=0 to High(FSections[j].Elements) do
|
||
if FSections[j].Elements[i].Token = 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 [$<24>-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.
|