
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3061 8e941d3f-bd1b-0410-a28a-d453659cc2b4
599 lines
15 KiB
ObjectPascal
599 lines
15 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;
|
||
|
||
{ TsNumFormatParser }
|
||
|
||
type
|
||
TsCompareOperation = (coNotUsed,
|
||
coEqual, coNotEqual, coLess, coGreater, coLessEqual, coGreaterEqual
|
||
);
|
||
|
||
TsNumFormatSection = record
|
||
FormatString: String;
|
||
CompareOperation: TsCompareOperation;
|
||
CompareValue: Double;
|
||
Color: TsColor;
|
||
CountryCode: String;
|
||
CurrencySymbol: String;
|
||
Decimals: Byte;
|
||
NumFormat: TsNumberFormat;
|
||
end;
|
||
|
||
TsNumFormatParser = class
|
||
private
|
||
FWorkbook: TsWorkbook;
|
||
FCurrent: PChar;
|
||
FStart: PChar;
|
||
FEnd: PChar;
|
||
FCurrSection: Integer;
|
||
FSections: array of TsNumFormatSection;
|
||
FFormatSettings: TFormatSettings;
|
||
FFormatString: String;
|
||
FStatus: Integer;
|
||
function GetParsedSectionCount: Integer;
|
||
function GetParsedSections(AIndex: Integer): TsNumFormatSection;
|
||
|
||
protected
|
||
procedure AddChar(AChar: Char);
|
||
procedure AddSection;
|
||
procedure AnalyzeBracket(const AValue: String);
|
||
procedure AnalyzeText(const AValue: String);
|
||
procedure CheckSections;
|
||
procedure Parse(const AFormatString: String);
|
||
procedure ScanAMPM(var s: String);
|
||
procedure ScanBrackets;
|
||
procedure ScanDateTime;
|
||
procedure ScanDateTimeParts(TestToken, Replacement: Char; var s: String);
|
||
procedure ScanFormat;
|
||
procedure ScanNumber;
|
||
procedure ScanText;
|
||
|
||
public
|
||
constructor Create(AWorkbook: TsWorkbook; const AFormatString: String);
|
||
destructor Destroy; override;
|
||
property FormatString: String read FFormatString;
|
||
property ParsedSectionCount: Integer read GetParsedSectionCount;
|
||
property ParsedSections[AIndex: Integer]: TsNumFormatSection read GetParsedSections;
|
||
property Status: Integer read FStatus;
|
||
end;
|
||
|
||
implementation
|
||
|
||
uses
|
||
fpsutils;
|
||
|
||
const
|
||
COMPARE_STR: array[TsCompareOperation] of string = (
|
||
'', '=', '<>', '<', '>', '<=', '>'
|
||
);
|
||
|
||
{ TsNumFormatParser }
|
||
|
||
constructor TsNumFormatParser.Create(AWorkbook: TsWorkbook;
|
||
const AFormatString: String);
|
||
begin
|
||
inherited Create;
|
||
FWorkbook := AWorkbook;
|
||
FFormatSettings := DefaultFormatSettings;
|
||
FFormatSettings.DecimalSeparator := '.';
|
||
FFormatSettings.ThousandSeparator := ',';
|
||
Parse(AFormatString);
|
||
end;
|
||
|
||
destructor TsNumFormatParser.Destroy;
|
||
begin
|
||
FSections := nil;
|
||
inherited Destroy;
|
||
end;
|
||
|
||
procedure TsNumFormatParser.AddChar(AChar: Char);
|
||
begin
|
||
with FSections[FCurrSection] do
|
||
FormatString := FormatString + AChar;
|
||
end;
|
||
|
||
procedure TsNumFormatParser.AddSection;
|
||
begin
|
||
FCurrSection := Length(FSections);
|
||
SetLength(FSections, FCurrSection + 1);
|
||
with FSections[FCurrSection] do begin
|
||
FormatString := '';
|
||
CompareOperation := coNotUsed;
|
||
CompareValue := 0.0;
|
||
Color := scBlack;
|
||
CountryCode := '';
|
||
CurrencySymbol := '';
|
||
Decimals := 0;
|
||
NumFormat := nfGeneral;
|
||
end;
|
||
end;
|
||
|
||
procedure TsNumFormatParser.AnalyzeBracket(const AValue: String);
|
||
var
|
||
lValue: String;
|
||
n: Integer;
|
||
begin
|
||
lValue := lowercase(AValue);
|
||
// Colors
|
||
if lValue = 'red' then
|
||
FSections[FCurrSection].Color := scRed
|
||
else
|
||
if lValue = 'black' then
|
||
FSections[FCurrSection].Color := scBlack
|
||
else
|
||
if lValue = 'blue' then
|
||
FSections[FCurrSection].Color := scBlue
|
||
else
|
||
if lValue = 'white' then
|
||
FSections[FCurrSection].Color := scWhite
|
||
else
|
||
if lValue = 'green' then
|
||
FSections[FCurrSection].Color := scGreen
|
||
else
|
||
if lValue = 'cyan' then
|
||
FSections[FCurrSection].Color := scCyan
|
||
else
|
||
if lValue = 'magenta' then
|
||
FSections[FCurrSection].Color := scMagenta
|
||
else
|
||
if copy(lValue, 1, 5) = 'color' then begin
|
||
lValue := copy(lValue, 6, Length(lValue));
|
||
if not TryStrToInt(trim(lValue), n) then begin
|
||
FStatus := psErrNoValidColorIndex;
|
||
exit;
|
||
end;
|
||
FSections[FCurrSection].Color := n;
|
||
end
|
||
else
|
||
// Conditions
|
||
if lValue[1] in ['=', '<', '>'] then begin
|
||
n := 1;
|
||
case lValue[1] of
|
||
'=': FSections[FCurrSection].CompareOperation := coEqual;
|
||
'<': case lValue[2] of
|
||
'>': begin FSections[FCurrSection].CompareOperation := coNotEqual; inc(n); end;
|
||
'=': begin FSections[FCurrSection].CompareOperation := coLessEqual; inc(n); end;
|
||
else FSections[FCurrSection].CompareOperation := coLess;
|
||
end;
|
||
'>': case lValue[2] of
|
||
'=': begin FSections[FCurrSection].CompareOperation := coGreaterEqual; inc(n); end;
|
||
else FSections[FCurrSection].CompareOperation := coGreater;
|
||
end;
|
||
end;
|
||
Delete(lValue, 1, n);
|
||
if not TryStrToFloat(trim(lValue), FSections[FCurrSection].CompareValue) then
|
||
FStatus := psErrNoValidCompareNumber;
|
||
end else
|
||
// Locale information
|
||
if lValue[1] = '$' then begin
|
||
FSections[FCurrSection].CountryCode := Copy(AValue, 2, Length(AValue));
|
||
end else
|
||
FStatus := psErrUnknownInfoInBrackets;
|
||
end;
|
||
|
||
procedure TsNumFormatParser.AnalyzeText(const AValue: String);
|
||
var
|
||
uValue: String;
|
||
begin
|
||
uValue := Uppercase(AValue);
|
||
if (uValue = '$') or (uValue = 'USD') or (uValue = '<27>') or (uValue = 'EUR') or
|
||
(uValue = '<27>') or (uValue = 'GBP') or (uValue = '<27>') or (uValue = 'JPY')
|
||
then
|
||
FSections[FCurrSection].CurrencySymbol := AValue;
|
||
end;
|
||
|
||
procedure TsNumFormatParser.CheckSections;
|
||
var
|
||
i: Integer;
|
||
ns: Integer;
|
||
s: String;
|
||
begin
|
||
ns := Length(FSections);
|
||
|
||
for i:=0 to ns-1 do begin
|
||
if FSections[i].FormatString = '' then
|
||
FSections[i].NumFormat := nfGeneral;
|
||
|
||
if (FSections[i].CurrencySymbol <> '') and (FSections[i].NumFormat = nfFixedTh) then
|
||
FSections[i].NumFormat := nfCurrency;
|
||
|
||
if FSections[i].CompareOperation <> coNotUsed then begin
|
||
FStatus := psErrConditionalFormattingNotSupported;
|
||
exit;
|
||
end;
|
||
|
||
case FSections[i].NumFormat of
|
||
nfGeneral, nfFixed, nfFixedTh, nfPercentage, nfExp, nfSci, nfCurrency:
|
||
try
|
||
s := FormatFloat(FSections[i].FormatString, 1.0, FWorkBook.FormatSettings);
|
||
except
|
||
FStatus := psErrNoValidNumberFormat;
|
||
exit;
|
||
end;
|
||
|
||
nfShortDateTime, nfShortDate, nfShortTime, nfShortTimeAM,
|
||
nfLongDate, nfLongTime, nfLongTimeAM, nfFmtDateTime:
|
||
try
|
||
s := FormatDateTimeEx(FSections[i].FormatString, now(), FWorkbook.FormatSettings);
|
||
except
|
||
FStatus := psErrNoValidDateTimeFormat;
|
||
exit;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
if ns = 2 then
|
||
FFormatString := Format('%s;%s;%s', [
|
||
FSections[0].FormatString,
|
||
FSections[1].FormatString,
|
||
FSections[0].FormatString // make sure that fpc understands the "zero"
|
||
])
|
||
else
|
||
if ns > 0 then begin
|
||
FFormatString := FSections[0].FormatString;
|
||
for i:=1 to ns-1 do
|
||
FFormatString := Format('%s;%s', [FFormatString, FSections[i].FormatString]);
|
||
end else
|
||
FStatus := psErrNoUsableFormat;
|
||
end;
|
||
|
||
{
|
||
function TsNumFormatParser.GetNumFormat: TsNumberFormat;
|
||
var
|
||
i: Integer;
|
||
begin
|
||
if FStatus <> psOK then
|
||
Result := nfGeneral
|
||
else
|
||
if (FSections[0].NumFormat = nfCurrency) and (FSections[1].NumFormat = nfCurrency) and
|
||
(FSections[2].NumFormat = nfCurrency)
|
||
then begin
|
||
if (FSections[1].Color = scNotDefined) then begin
|
||
if (FSections[2].FormatString = '-') then
|
||
Result := nfCurrencyDash
|
||
else
|
||
Result := nfCurrency;
|
||
end else
|
||
if FSections[1].Color = scRed then begin
|
||
if (FSections[2].Formatstring = '-') then
|
||
Result := nfCurrencyDashRed
|
||
else
|
||
Result := nfCurrencyRed;
|
||
end;
|
||
end else
|
||
Result := FSections[0].NumFormat;
|
||
end;
|
||
}
|
||
|
||
function TsNumFormatParser.GetParsedSectionCount: Integer;
|
||
begin
|
||
Result := Length(FSections);
|
||
end;
|
||
|
||
function TsNumFormatParser.GetParsedSections(AIndex: Integer): TsNumFormatSection;
|
||
begin
|
||
Result := FSections[AIndex];
|
||
end;
|
||
|
||
procedure TsNumFormatParser.Parse(const AFormatString: String);
|
||
var
|
||
token: Char;
|
||
begin
|
||
FStatus := psOK;
|
||
AddSection;
|
||
FStart := @AFormatString[1];
|
||
FEnd := FStart + Length(AFormatString) - 1;
|
||
FCurrent := FStart;
|
||
while (FCurrent <= FEnd) and (FStatus = psOK) do begin
|
||
token := FCurrent^;
|
||
case token of
|
||
'[': ScanBrackets;
|
||
';': AddSection;
|
||
else ScanFormat;
|
||
end;
|
||
inc(FCurrent);
|
||
end;
|
||
CheckSections;
|
||
end;
|
||
|
||
{ Extracts the text between square brackets --> AnalyzeBracket }
|
||
procedure TsNumFormatParser.ScanBrackets;
|
||
var
|
||
s: String;
|
||
token: Char;
|
||
begin
|
||
inc(FCurrent); // cursor stands at '['
|
||
while (FCurrent <= FEnd) and (FStatus = psOK) do begin
|
||
token := FCurrent^;
|
||
case token of
|
||
']': begin
|
||
AnalyzeBracket(s);
|
||
break;
|
||
end;
|
||
else
|
||
s := s + token;
|
||
end;
|
||
inc(FCurrent);
|
||
end;
|
||
end;
|
||
|
||
procedure TsNumFormatParser.ScanDateTime;
|
||
var
|
||
token: Char;
|
||
done: Boolean;
|
||
s: String;
|
||
i: Integer;
|
||
nf: TsNumberFormat;
|
||
partStr: String;
|
||
isTime: Boolean;
|
||
isAMPM: Boolean;
|
||
begin
|
||
done := false;
|
||
s := '';
|
||
isTime := false;
|
||
isAMPM := false;
|
||
|
||
while (FCurrent <= FEnd) and (FStatus = psOK) and (not done) do begin
|
||
token := FCurrent^;
|
||
case token of
|
||
'\' : begin
|
||
inc(FCurrent);
|
||
token := FCurrent^;
|
||
s := s + token;
|
||
end;
|
||
'Y', 'y' : begin
|
||
ScanDateTimeParts(token, token, s);
|
||
isTime := false;
|
||
end;
|
||
'M', 'm' : if isTime then // help fpc to separate "month" and "minute"
|
||
ScanDateTimeParts(token, 'n', s)
|
||
else // both "month" and "minute" work in fpc to some degree
|
||
ScanDateTimeParts(token, token, s);
|
||
'D', 'd' : begin
|
||
ScanDateTimeParts(token, token, s);
|
||
isTime := false;
|
||
end;
|
||
'H', 'h' : begin
|
||
ScanDateTimeParts(token, token, s);
|
||
isTime := true;
|
||
end;
|
||
'S', 's' : begin
|
||
ScanDateTimeParts(token, token, s);
|
||
isTime := true;
|
||
end;
|
||
'/', ':', '.', ']', '[', ' '
|
||
: s := s + token;
|
||
'0' : ScanDateTimeParts(token, 'z', s);
|
||
'A', 'a' : begin
|
||
ScanAMPM(s);
|
||
isAMPM := true;
|
||
end;
|
||
else begin
|
||
done := true;
|
||
dec(FCurrent);
|
||
// char pointer must be at end of date/time mask.
|
||
end;
|
||
end;
|
||
if not done then inc(FCurrent);
|
||
end;
|
||
|
||
FSections[FCurrSection].FormatString := FSections[FCurrSection].FormatString + s;
|
||
s := FSections[FCurrSection].FormatString;
|
||
|
||
// Check format
|
||
try
|
||
if s <> '' then begin
|
||
FormatDateTime(s, now);
|
||
// !!!! MODIFY TO USE EXTENDED SYNTAX !!!!!
|
||
|
||
if s = FWorkbook.FormatSettings.LongDateFormat then
|
||
nf := nfLongDate
|
||
else
|
||
if s = FWorkbook.FormatSettings.ShortDateFormat then
|
||
nf := nfShortDate
|
||
else
|
||
if s = FWorkbook.FormatSettings.LongTimeFormat then
|
||
nf := nfLongTime
|
||
else
|
||
if s = FWorkbook.FormatSettings.ShortTimeFormat then
|
||
nf := nfShortTime
|
||
else
|
||
nf := nfFmtDateTime;
|
||
FSections[FCurrSection].NumFormat := nf;
|
||
end;
|
||
|
||
except
|
||
FStatus := psErrNoValidDateTimeFormat;
|
||
end;
|
||
end;
|
||
|
||
procedure TsNumFormatParser.ScanAMPM(var s: String);
|
||
var
|
||
token: Char;
|
||
begin
|
||
while (FCurrent <= FEnd) do begin
|
||
token := FCurrent^;
|
||
if token in ['A', 'a', 'P', 'p', 'm', 'M', '/'] then
|
||
s := s + token
|
||
else begin
|
||
dec(FCurrent);
|
||
exit;
|
||
end;
|
||
inc(FCurrent);
|
||
end;
|
||
end;
|
||
|
||
procedure TsNumFormatParser.ScanDateTimeParts(TestToken, Replacement: Char;
|
||
var s: String);
|
||
var
|
||
token: Char;
|
||
begin
|
||
s := s + Replacement;
|
||
while (FCurrent <= FEnd) do begin
|
||
inc(FCurrent);
|
||
token := FCurrent^;
|
||
if token = TestToken then
|
||
s := s + Replacement
|
||
else begin
|
||
dec(FCurrent);
|
||
break;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
procedure TsNumFormatParser.ScanFormat;
|
||
var
|
||
token: Char;
|
||
done: Boolean;
|
||
begin
|
||
done := false;
|
||
while (FCurrent <= FEnd) and (FStatus = psOK) and (not done) do begin
|
||
token := FCurrent^;
|
||
case token of
|
||
// Strip Excel's formatting symbols
|
||
'\', '*' : ;
|
||
'_' : inc(FCurrent);
|
||
'"' : begin
|
||
inc(FCurrent);
|
||
ScanText;
|
||
end;
|
||
'0', '#', '.', ',', '-': ScanNumber;
|
||
'y', 'Y', 'm', 'M',
|
||
'd', 'D', 'h', 's', '[': ScanDateTime;
|
||
' ' : AddChar(token);
|
||
';' : begin
|
||
done := true;
|
||
dec(FCurrent);
|
||
// Cursor must stay on the ";"
|
||
end;
|
||
end;
|
||
if not done then inc(FCurrent);
|
||
end;
|
||
end;
|
||
|
||
procedure TsNumFormatParser.ScanNumber;
|
||
var
|
||
token: Char;
|
||
done: Boolean;
|
||
countdecs: Boolean;
|
||
s: String;
|
||
hasThSep: Boolean;
|
||
isExp: Boolean;
|
||
isSci: Boolean;
|
||
hasHash: Boolean;
|
||
hasPerc: Boolean;
|
||
nf: TsNumberFormat;
|
||
begin
|
||
countdecs := false;
|
||
done := false;
|
||
hasThSep := false;
|
||
hasHash := false;
|
||
hasPerc := false;
|
||
isExp := false;
|
||
isSci := false;
|
||
s := '';
|
||
while (FCurrent <= FEnd) and (FStatus = psOK) and (not done) do begin
|
||
token := FCurrent^;
|
||
case token of
|
||
',': begin
|
||
hasThSep := true;
|
||
s := s + token;
|
||
end;
|
||
'.': begin
|
||
countdecs := true;
|
||
FSections[FCurrSection].Decimals := 0;
|
||
s := s + token;
|
||
end;
|
||
'0': begin
|
||
if countdecs then inc(FSections[FCurrSection].Decimals);
|
||
s := s + token;
|
||
end;
|
||
'E', 'e':
|
||
begin
|
||
if hasHash and countdecs then isSci := true else isExp := true;
|
||
countdecs := false;
|
||
s := s + token;
|
||
end;
|
||
'+', '-':
|
||
s := s + token;
|
||
'#': begin
|
||
hasHash := true;
|
||
countdecs := false;
|
||
s := s + token;
|
||
end;
|
||
'%': begin
|
||
hasPerc := true;
|
||
s := s + token;
|
||
end;
|
||
else begin
|
||
done := true;
|
||
dec(FCurrent);
|
||
end;
|
||
end;
|
||
if not done then
|
||
inc(FCurrent);
|
||
end;
|
||
|
||
if s <> '' then begin
|
||
if isExp then
|
||
nf := nfExp
|
||
else if isSci then
|
||
nf := nfSci
|
||
else if hasPerc then
|
||
nf := nfPercentage
|
||
else if hasThSep then
|
||
nf := nfFixedTh
|
||
else
|
||
nf := nfFixed;
|
||
end else
|
||
nf := nfGeneral;
|
||
|
||
FSections[FCurrSection].NumFormat := nf;
|
||
FSections[FCurrSection].FormatString := FSections[FCurrSection].FormatString + s;
|
||
end;
|
||
|
||
{ Scans a text in quotation marks. Tries to interpret the text as a currency
|
||
symbol (--> AnalyzeText) }
|
||
procedure TsNumFormatParser.ScanText;
|
||
var
|
||
token: Char;
|
||
done: Boolean;
|
||
s: String;
|
||
begin
|
||
done := false;
|
||
s := '';
|
||
while (FCurrent <= FEnd) and (FStatus = psOK) and not done do begin
|
||
token := FCurrent^;
|
||
if token = '"' then begin
|
||
done := true;
|
||
AnalyzeText(s);
|
||
end else begin
|
||
s := s + token;
|
||
inc(FCurrent);
|
||
end;
|
||
end;
|
||
FSections[FCurrSection].FormatString := Format('%s"%s"',
|
||
[FSections[FCurrSection].FormatString, s]);
|
||
end;
|
||
|
||
end.
|