fpspreadsheet: More logical writing strategy for biff number formats. Still issues with reading.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3072 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2014-05-21 12:12:16 +00:00
parent b9df429d5b
commit a9df011859
6 changed files with 471 additions and 182 deletions

View File

@ -54,7 +54,7 @@ begin
} }
// Write some cells // Write some cells
MyWorksheet.WriteDateTime(0, 20, now, nfShortTime); //1.0);// A1 // MyWorksheet.WriteDateTime(0, 20, now, nfShortTime); //1.0);// A1
MyWorksheet.WriteNumber(0, 0, 1.0, nfFixed, 3);// A1 MyWorksheet.WriteNumber(0, 0, 1.0, nfFixed, 3);// A1
MyWorksheet.WriteNumber(0, 1, 2.0);// B1 MyWorksheet.WriteNumber(0, 1, 2.0);// B1
MyWorksheet.WriteNumber(0, 2, 3.0);// C1 MyWorksheet.WriteNumber(0, 2, 3.0);// C1
@ -209,11 +209,9 @@ begin
MyWorksheet.WriteDateTime(r, 1, now, nfFmtDateTime, 'mm:ss.zzz'); MyWorksheet.WriteDateTime(r, 1, now, nfFmtDateTime, 'mm:ss.zzz');
// Write formatted numbers // Write formatted numbers
number := 12345.67890123456789; // number := 12345.67890123456789;
number := 31415.92;
inc(r, 2); inc(r, 2);
MyWorksheet.WriteUTF8Text(r, 1, '12345.67890123456789');
MyWorksheet.WriteUTF8Text(r, 2, '-12345.67890123456789');
inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfGeneral'); MyWorksheet.WriteUTF8Text(r, 0, 'nfGeneral');
MyWorksheet.WriteNumber(r, 1, number, nfGeneral); MyWorksheet.WriteNumber(r, 1, number, nfGeneral);
MyWorksheet.WriteNumber(r, 2, -number, nfGeneral); MyWorksheet.WriteNumber(r, 2, -number, nfGeneral);
@ -250,6 +248,12 @@ begin
MyWorksheet.WriteNumber(r, 1, number, nfFixedTh, 3); MyWorksheet.WriteNumber(r, 1, number, nfFixedTh, 3);
MyWorksheet.WriteNumber(r, 2, -number, nfFixedTh, 3); MyWorksheet.WriteNumber(r, 2, -number, nfFixedTh, 3);
inc(r,2); inc(r,2);
MyWorksheet.WriteUTF8Text(r, 0, 'nfSci, 0 dec');
MyWorksheet.WriteNumber(r, 1, number, nfSci, 0);
MyWorksheet.WriteNumber(r, 2, -number, nfSci, 0);
MyWorksheet.WriteNumber(r, 3, 1.0/number, nfSci, 0);
MyWorksheet.WriteNumber(r, 4, -1.0/number, nfSci, 0);
inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfSci, 1 dec'); MyWorksheet.WriteUTF8Text(r, 0, 'nfSci, 1 dec');
MyWorksheet.WriteNumber(r, 1, number, nfSci, 1); MyWorksheet.WriteNumber(r, 1, number, nfSci, 1);
MyWorksheet.WriteNumber(r, 2, -number, nfSci, 1); MyWorksheet.WriteNumber(r, 2, -number, nfSci, 1);
@ -268,6 +272,12 @@ begin
MyWorksheet.WriteNumber(r, 3, 1.0/number, nfSci, 3); MyWorksheet.WriteNumber(r, 3, 1.0/number, nfSci, 3);
MyWorksheet.WriteNumber(r, 4, -1.0/number, nfSci, 3); MyWorksheet.WriteNumber(r, 4, -1.0/number, nfSci, 3);
inc(r); inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfExp, 0 dec');
MyWorksheet.WriteNumber(r, 1, number, nfExp, 0);
MyWorksheet.WriteNumber(r, 2, -number, nfExp, 0);
MyWorksheet.WriteNumber(r, 3, 1.0/number, nfExp, 0);
MyWorksheet.WriteNumber(r, 4, -1.0/number, nfExp, 0);
inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfExp, 1 dec'); MyWorksheet.WriteUTF8Text(r, 0, 'nfExp, 1 dec');
MyWorksheet.WriteNumber(r, 1, number, nfExp, 1); MyWorksheet.WriteNumber(r, 1, number, nfExp, 1);
MyWorksheet.WriteNumber(r, 2, -number, nfExp, 1); MyWorksheet.WriteNumber(r, 2, -number, nfExp, 1);
@ -285,7 +295,6 @@ begin
MyWorksheet.WriteNumber(r, 2, -number, nfExp, 3); MyWorksheet.WriteNumber(r, 2, -number, nfExp, 3);
MyWorksheet.WriteNumber(r, 3, 1.0/number, nfExp, 3); MyWorksheet.WriteNumber(r, 3, 1.0/number, nfExp, 3);
MyWorksheet.WriteNumber(r, 4, -1.0/number, nfExp, 3); MyWorksheet.WriteNumber(r, 4, -1.0/number, nfExp, 3);
inc(r,2); inc(r,2);
MyWorksheet.WriteUTF8Text(r, 0, 'nfCurrency, 0 decs'); MyWorksheet.WriteUTF8Text(r, 0, 'nfCurrency, 0 decs');
MyWorksheet.WriteNumber(r, 1, number, nfCurrency, 0, 'USD'); MyWorksheet.WriteNumber(r, 1, number, nfCurrency, 0, 'USD');
@ -319,6 +328,7 @@ begin
MyWorksheet.WriteNumberFormat(r, 1, nfCustom, '"$"#,##0.0_);[Red]("$"#,##0.0)'); MyWorksheet.WriteNumberFormat(r, 1, nfCustom, '"$"#,##0.0_);[Red]("$"#,##0.0)');
MyWorksheet.WriteDateTime(r, 2, -number); MyWorksheet.WriteDateTime(r, 2, -number);
MyWorksheet.WriteNumberFormat(r, 2, nfCustom, '"$"#,##0.0_);[Red]("$"#,##0.0)'); MyWorksheet.WriteNumberFormat(r, 2, nfCustom, '"$"#,##0.0_);[Red]("$"#,##0.0)');
inc(r, 2); inc(r, 2);
number := 1.333333333; number := 1.333333333;
MyWorksheet.WriteUTF8Text(r, 0, 'nfPercentage, 0 decs'); MyWorksheet.WriteUTF8Text(r, 0, 'nfPercentage, 0 decs');
@ -337,14 +347,23 @@ begin
MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval); MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval);
inc(r); inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, h:m:s'); MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, h:m:s');
MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, 'H:M:s'); MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, 'h:m:s');
inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, h:n:s');
MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, 'h:n:s');
inc(r); inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, hh:mm'); MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, hh:mm');
MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, 'hh:mm'); MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, 'hh:mm');
inc(r); inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, hh:nn');
MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, 'hh:nn');
inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, h:m'); MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, h:m');
MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, 'h:m'); MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, 'h:m');
inc(r); inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, h:n');
MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, 'h:n');
inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, h'); MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, h');
MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, 'h'); MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, 'h');
@ -360,7 +379,7 @@ begin
// Set height of rows 0 // Set height of rows 0
MyWorksheet.WriteRowHeight(0, 30); // 30 mm MyWorksheet.WriteRowHeight(0, 30); // 30 mm
(*
// Creates a new worksheet // Creates a new worksheet
MyWorksheet := MyWorkbook.AddWorksheet(Str_Worksheet2); MyWorksheet := MyWorkbook.AddWorksheet(Str_Worksheet2);
@ -371,7 +390,7 @@ begin
MyWorksheet.WriteUTF8Text(0, 3, Str_Fourth); MyWorksheet.WriteUTF8Text(0, 3, Str_Fourth);
MyWorksheet.WriteTextRotation(0, 0, rt90DegreeClockwiseRotation); MyWorksheet.WriteTextRotation(0, 0, rt90DegreeClockwiseRotation);
MyWorksheet.WriteUsedFormatting(0, 1, [uffBold]); MyWorksheet.WriteUsedFormatting(0, 1, [uffBold]);
*)
// Save the spreadsheet to a file // Save the spreadsheet to a file
MyWorkbook.WriteToFile(MyDir + 'test.xls', sfExcel8, true); MyWorkbook.WriteToFile(MyDir + 'test.xls', sfExcel8, true);
MyWorkbook.Free; MyWorkbook.Free;

View File

@ -55,6 +55,7 @@ type
FFormatString: String; FFormatString: String;
FNumFormat: TsNumberFormat; FNumFormat: TsNumberFormat;
FConversionDirection: TsConversionDirection; FConversionDirection: TsConversionDirection;
FIsTime: Boolean;
FStatus: Integer; FStatus: Integer;
function GetFormatString: String; function GetFormatString: String;
function GetParsedSectionCount: Integer; function GetParsedSectionCount: Integer;
@ -78,7 +79,8 @@ type
procedure ScanText; procedure ScanText;
public public
constructor Create(AWorkbook: TsWorkbook; const AFormatString: String; constructor Create(AWorkbook: TsWorkbook;
const AFormatString: String; ANumFormat: TsNumberFormat;
AConversionDirection: TsConversionDirection = cdToFPSpreadsheet); overload; AConversionDirection: TsConversionDirection = cdToFPSpreadsheet); overload;
constructor Create(AWorkbook: TsWorkbook; const AFormatSections: TsNumFormatSections; constructor Create(AWorkbook: TsWorkbook; const AFormatSections: TsNumFormatSections;
AConversionDirection: TsConversionDirection = cdFromFPSpreadsheet); overload; AConversionDirection: TsConversionDirection = cdFromFPSpreadsheet); overload;
@ -96,7 +98,7 @@ type
implementation implementation
uses uses
fpsutils; StrUtils, fpsutils;
const const
COMPARE_STR: array[TsCompareOperation] of string = ( COMPARE_STR: array[TsCompareOperation] of string = (
@ -109,12 +111,14 @@ const
from a spreadsheet file. The conversion, by default, will go FROM the file TO from a spreadsheet file. The conversion, by default, will go FROM the file TO
the fpspreadsheet procedures. } the fpspreadsheet procedures. }
constructor TsNumFormatParser.Create(AWorkbook: TsWorkbook; constructor TsNumFormatParser.Create(AWorkbook: TsWorkbook;
const AFormatString: String; AConversionDirection: TsConversionDirection = cdToFPSpreadsheet); const AFormatString: String; ANumFormat: TsNumberFormat;
AConversionDirection: TsConversionDirection = cdToFPSpreadsheet);
begin begin
inherited Create; inherited Create;
FCreateMethod := 0; FCreateMethod := 0;
FConversionDirection := AConversionDirection; FConversionDirection := AConversionDirection;
FWorkbook := AWorkbook; FWorkbook := AWorkbook;
FNumFormat := ANumFormat;
FFormatSettings := DefaultFormatSettings; FFormatSettings := DefaultFormatSettings;
FFormatSettings.DecimalSeparator := '.'; FFormatSettings.DecimalSeparator := '.';
FFormatSettings.ThousandSeparator := ','; FFormatSettings.ThousandSeparator := ',';
@ -163,6 +167,7 @@ begin
Decimals := 0; Decimals := 0;
NumFormat := nfGeneral; NumFormat := nfGeneral;
end; end;
FIsTime := false;
end; end;
procedure TsNumFormatParser.AnalyzeBracket(const AValue: String); procedure TsNumFormatParser.AnalyzeBracket(const AValue: String);
@ -171,6 +176,23 @@ var
n: Integer; n: Integer;
begin begin
lValue := lowercase(AValue); lValue := lowercase(AValue);
// date/time format for interval
if (lValue = 'h') or (lValue = 'hh') or (lValue = 'm') or (lValue = 'mm') or
(lValue = 's') or (lValue = 'ss') or (lValue = 'n') or (lValue = 'nn')
then begin
FSections[FCurrSection].FormatString := FSections[FCurrSection].FormatString +
'[' + AValue + ']';
FSections[FCurrSection].NumFormat := nfTimeInterval;
FIsTime := true;
end
else
if ((lValue = 'n') or (lValue = 'nn')) and (FConversionDirection = cdFromFPSpreadsheet)
then begin
FSections[FCurrSection].FormatString := FSections[FCurrSection].FormatString +
'[' + DupeString('m', Length(lValue)) + ']';
FIsTime := true;
end
else
// Colors // Colors
if lValue = 'red' then if lValue = 'red' then
FSections[FCurrSection].Color := scRed FSections[FCurrSection].Color := scRed
@ -251,7 +273,7 @@ begin
if FSections[i].FormatString = '' then if FSections[i].FormatString = '' then
FSections[i].NumFormat := nfGeneral; FSections[i].NumFormat := nfGeneral;
if (FSections[i].CurrencySymbol <> '') and (FSections[i].NumFormat = nfFixedTh) then if (FSections[i].CurrencySymbol <> '') {and (FSections[i].NumFormat in [nfFixed, nfFixedTh])} then
FSections[i].NumFormat := nfCurrency; FSections[i].NumFormat := nfCurrency;
if FSections[i].CompareOperation <> coNotUsed then begin if FSections[i].CompareOperation <> coNotUsed then begin
@ -270,7 +292,7 @@ begin
end; end;
nfShortDateTime, nfShortDate, nfShortTime, nfShortTimeAM, nfShortDateTime, nfShortDate, nfShortTime, nfShortTimeAM,
nfLongDate, nfLongTime, nfLongTimeAM, nfFmtDateTime: nfLongDate, nfLongTime, nfLongTimeAM, nfTimeInterval, nfFmtDateTime:
try try
s := FormatDateTimeEx(FSections[i].FormatString, now(), FWorkbook.FormatSettings); s := FormatDateTimeEx(FSections[i].FormatString, now(), FWorkbook.FormatSettings);
except except
@ -280,6 +302,9 @@ begin
end; end;
end; end;
if (ns > 1) and (FNumFormat in [nfCurrencyRed, nfCurrencyDashRed]) then
FSections[1].Color := scRed;
// Extract built-in NumFormat identifier for currency (needs several entries in // Extract built-in NumFormat identifier for currency (needs several entries in
// three sections). // three sections).
if (ns = 3) and if (ns = 3) and
@ -303,6 +328,22 @@ begin
else else
FNumFormat := FSections[0].NumFormat; FNumFormat := FSections[0].NumFormat;
// Add colors to section format strings
if (FConversionDirection = cdFromFPSpreadsheet) then
for i := 0 to High(FSections) do
if FSections[i].Color < 8 then
FSections[i].FormatString := Format('[%s]%s', [
FWorkbook.GetColorName(FSections[i].Color),
FSections[i].FormatString
])
else
if FSections[i].Color <> scNotDefined then
FSections[i].FormatString := Format('[Color%d]%s', [
FSections[i].Color,
FSections[i].FormatString
]);
// Construct total format string
if ns = 2 then if ns = 2 then
FFormatString := Format('%s;%s;%s', [ FFormatString := Format('%s;%s;%s', [
FSections[0].FormatString, FSections[0].FormatString,
@ -408,6 +449,9 @@ var
begin begin
FStatus := psOK; FStatus := psOK;
AddSection; AddSection;
if AFormatString = '' then
exit;
FStart := @AFormatString[1]; FStart := @AFormatString[1];
FEnd := FStart + Length(AFormatString) - 1; FEnd := FStart + Length(AFormatString) - 1;
FCurrent := FStart; FCurrent := FStart;
@ -415,6 +459,7 @@ begin
token := FCurrent^; token := FCurrent^;
case token of case token of
'[': ScanBrackets; '[': ScanBrackets;
':': if FIsTime then AddChar(':');
';': AddSection; ';': AddSection;
else ScanFormat; else ScanFormat;
end; end;
@ -444,6 +489,8 @@ begin
end; end;
end; end;
{ Scans a date/time format. Note: fpc and the Excel-standard have slightly
different formats which are converted here }
procedure TsNumFormatParser.ScanDateTime; procedure TsNumFormatParser.ScanDateTime;
var var
token: Char; token: Char;
@ -452,55 +499,76 @@ var
i: Integer; i: Integer;
nf: TsNumberFormat; nf: TsNumberFormat;
partStr: String; partStr: String;
isTime: Boolean;
isAMPM: Boolean; isAMPM: Boolean;
begin begin
done := false; done := false;
s := ''; s := '';
isTime := false;
isAMPM := false; isAMPM := false;
while (FCurrent <= FEnd) and (FStatus = psOK) and (not done) do begin while (FCurrent <= FEnd) and (FStatus = psOK) and (not done) do begin
token := FCurrent^; token := FCurrent^;
case token of case token of
'\': '\': // means that the next character is taken literally
begin begin
inc(FCurrent); inc(FCurrent); // skip the "\"...
token := FCurrent^; token := FCurrent^; // and take the next character
s := s + token; s := s + token;
end; end;
'Y', 'y': 'Y', 'y':
begin begin
ScanDateTimeParts(token, token, s); ScanDateTimeParts(token, token, s);
isTime := false; FIsTime := false;
end;
'M':
if FConversionDirection = cdToFPSpreadsheet then
ScanDateTimeParts(token, 'm', s)
else begin
if FIsTime then ScanDateTimeParts(token, 'm', s) else ScanDateTimeParts(token, 'M', s);
end;
'm':
if FConversionDirection = cdToFPSpreadsheet then begin
if FIsTime then ScanDateTimeParts(token, 'n', s) else ScanDateTimeParts(token, 'm', s)
end else begin
if FIsTime then ScanDateTimeParts(token, 'm', s) else ScanDateTimeParts(token, 'M', s);
end; end;
'M', 'm':
ScanDateTimeParts(token, token, s);
{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);}
'N', 'n': 'N', 'n':
ScanDateTimeParts(token, 'n', s); // fpc dialect for "minutes" if FConversionDirection = cdToFPSpreadsheet then begin
// "n" is not used by file format --> stop scanning date/time
done := true;
dec(FCurrent);
end else
// "n", in fpc, stands for "minute".
ScanDateTimeParts(token, 'm', s);
'D', 'd': 'D', 'd':
begin begin
ScanDateTimeParts(token, token, s); ScanDateTimeParts(token, token, s);
isTime := false; FIsTime := false;
end; end;
'H', 'h': 'H', 'h':
begin begin
ScanDateTimeParts(token, token, s); ScanDateTimeParts(token, token, s);
isTime := true; FIsTime := true;
end; end;
'S', 's': 'S', 's':
begin begin
ScanDateTimeParts(token, token, s); ScanDateTimeParts(token, token, s);
isTime := true; FIsTime := true;
end; end;
'/', ':', '.', ']', '[', ' ': '/', ':', '.', ']', '[', ' ':
s := s + token; s := s + token;
'0', 'z', 'Z': '0':
ScanDateTimeParts(token, token, s); if FConversionDirection = cdToFPSpreadsheet then
ScanDateTimeParts(token, 'z', s)
else begin
done := true;
dec(FCurrent);
end;
'z', 'Z':
if FConversionDirection = cdToFPSpreadsheet then begin
done := true;
dec(FCurrent);
end else
ScanDateTimeParts(token, '0', s);
'A', 'a': 'A', 'a':
begin begin
ScanAMPM(s); ScanAMPM(s);
@ -530,6 +598,9 @@ begin
else else
if s = StripAMPM(FWorkbook.FormatSettings.ShortTimeFormat) then if s = StripAMPM(FWorkbook.FormatSettings.ShortTimeFormat) then
nf := IfThen(isAMPM, nfShortTimeAM, nfShortTime) nf := IfThen(isAMPM, nfShortTimeAM, nfShortTime)
else
if s[1] = '[' then
nf := nfTimeInterval
else else
nf := nfFmtDateTime; nf := nfFmtDateTime;
@ -590,6 +661,10 @@ begin
inc(FCurrent); inc(FCurrent);
ScanText; ScanText;
end; end;
'(', ')':
begin
AddChar(token);
end;
'0', '#', '.', ',', '-': '0', '#', '.', ',', '-':
ScanNumber; ScanNumber;
'y', 'Y', 'm', 'M', 'd', 'D', 'h', 'N', 'n', 's', '[': 'y', 'Y', 'm', 'M', 'd', 'D', 'h', 'N', 'n', 's', '[':

View File

@ -379,7 +379,8 @@ type
{ Data manipulation methods - For Cells } { Data manipulation methods - For Cells }
procedure CopyCell(AFromRow, AFromCol, AToRow, AToCol: Cardinal; AFromWorksheet: TsWorksheet); procedure CopyCell(AFromRow, AFromCol, AToRow, AToCol: Cardinal; AFromWorksheet: TsWorksheet);
procedure CopyFormat(AFormat: PCell; AToRow, AToCol: Cardinal); procedure CopyFormat(AFormat: PCell; AToRow, AToCol: Cardinal); overload;
procedure CopyFormat(AFromCell, AToCell: PCell); overload;
function FindCell(ARow, ACol: Cardinal): PCell; function FindCell(ARow, ACol: Cardinal): PCell;
function GetCell(ARow, ACol: Cardinal): PCell; function GetCell(ARow, ACol: Cardinal): PCell;
function GetCellCount: Cardinal; function GetCellCount: Cardinal;
@ -561,24 +562,30 @@ type
FFirstFormatIndexInFile: Integer; FFirstFormatIndexInFile: Integer;
FNextFormatIndex: Integer; FNextFormatIndex: Integer;
procedure AddBuiltinFormats; virtual; procedure AddBuiltinFormats; virtual;
procedure Analyze(AFormatIndex: Integer; var AFormatString: String;
var ANumFormat: TsNumberFormat; var ADecimals: byte;
var ACurrencySymbol: String); virtual;
procedure RemoveFormat(AIndex: Integer); procedure RemoveFormat(AIndex: Integer);
public public
constructor Create(AWorkbook: TsWorkbook); constructor Create(AWorkbook: TsWorkbook);
destructor Destroy; override; destructor Destroy; override;
function AddFormat(AFormatCell: PCell): Integer; overload; function AddFormat(AFormatCell: PCell): Integer; overload;
function AddFormat(AFormatIndex: Integer; ANumFormat: TsNumberFormat; function AddFormat(AFormatIndex: Integer; AFormatString: String;
AFormatString: String = ''; ADecimals: Byte = 0; ANumFormat: TsNumberFormat; ADecimals: Byte = 0;
ACurrencySymbol: String = ''): Integer; overload; ACurrencySymbol: String = ''): Integer; overload;
function AddFormat(AFormatString: String; ANumFormat: TsNumberFormat;
ADecimals: Byte = 0; ACurrencySymbol: String = ''): Integer; overload;
procedure AnalyzeAndAdd(AFormatIndex: Integer; AFormatString: String); procedure AnalyzeAndAdd(AFormatIndex: Integer; AFormatString: String);
procedure Clear; procedure Clear;
procedure ConvertAfterReading(AFormatIndex: Integer; var AFormatString: String;
var ANumFormat: TsNumberFormat; var ADecimals: Byte;
var ACurrencySymbol: String); virtual;
procedure ConvertBeforeWriting(var AFormatString: String;
var ANumFormat: TsNumberFormat; var ADecimals: Byte;
var ACurrencySymbol: String); virtual;
procedure Delete(AIndex: Integer); procedure Delete(AIndex: Integer);
function Find(AFormatCell: PCell): integer; overload; function Find(AFormatCell: PCell): integer; overload;
function Find(ANumFormat: TsNumberFormat; AFormatString: String; function Find(ANumFormat: TsNumberFormat; AFormatString: String;
ADecimals: Byte; ACurrencySymbol: String): Integer; overload; ADecimals: Byte; ACurrencySymbol: String): Integer; overload;
function Find(AFormatIndex: Integer): Integer; overload; function Find(AFormatIndex: Integer): Integer; overload;
function Find(AFormatString: String): Integer; overload;
function FormatStringForWriting(AIndex: Integer): String; virtual; function FormatStringForWriting(AIndex: Integer): String; virtual;
procedure Sort; procedure Sort;
@ -731,6 +738,7 @@ procedure RegisterSpreadFormat(
AWriterClass: TsSpreadWriterClass; AWriterClass: TsSpreadWriterClass;
AFormat: TsSpreadsheetFormat); AFormat: TsSpreadsheetFormat);
procedure CopyCellFormat(AFromCell, AToCell: PCell);
function GetFileFormatName(AFormat: TsSpreadsheetFormat): String; function GetFileFormatName(AFormat: TsSpreadsheetFormat): String;
procedure MakeLEPalette(APalette: PsPalette; APaletteSize: Integer); procedure MakeLEPalette(APalette: PsPalette; APaletteSize: Integer);
@ -749,6 +757,7 @@ resourcestring
lpInvalidNumberFormat = 'Trying to use an incompatible number format.'; lpInvalidNumberFormat = 'Trying to use an incompatible number format.';
lpNoValidNumberFormatString = 'No valid number format string.'; lpNoValidNumberFormatString = 'No valid number format string.';
lpNoValidDateTimeFormatString = 'No valid date/time format string.'; lpNoValidDateTimeFormatString = 'No valid date/time format string.';
lpIllegalNumberFormat = 'Illegal number format.';
lpTRUE = 'TRUE'; lpTRUE = 'TRUE';
lpFALSE = 'FALSE'; lpFALSE = 'FALSE';
lpErrEmptyIntersection = '#NULL!'; lpErrEmptyIntersection = '#NULL!';
@ -875,6 +884,28 @@ begin
{$ENDIF} {$ENDIF}
end; end;
{@@
Copies the format of a cell to another one.
}
procedure CopyCellFormat(AFromCell, AToCell: PCell);
begin
Assert(AFromCell <> nil);
Assert(AToCell <> nil);
AToCell^.UsedFormattingFields := AFromCell^.UsedFormattingFields;
AToCell^.BackgroundColor := AFromCell^.BackgroundColor;
AToCell^.Border := AFromCell^.Border;
AToCell^.BorderStyles := AFromCell^.BorderStyles;
AToCell^.FontIndex := AFromCell^.FontIndex;
AToCell^.HorAlignment := AFromCell^.HorAlignment;
AToCell^.VertAlignment := AFromCell^.VertAlignment;
AToCell^.TextRotation := AFromCell^.TextRotation;
AToCell^.NumberFormat := AFromCell^.NumberFormat;
AToCell^.NumberFormatStr := AFromCell^.NumberFormatStr;
AToCell^.Decimals := AFromCell^.Decimals;
AToCell^.CurrencySymbol := AFromCell^.CurrencySymbol;
end;
{ TsWorksheet } { TsWorksheet }
@ -997,29 +1028,19 @@ end;
{@@ {@@
Copies all format parameters from the format cell to another cell. Copies all format parameters from the format cell to another cell.
} }
procedure TsWorksheet.CopyFormat(AFormat: PCell; AToRow, AToCol: Cardinal); procedure TsWorksheet.CopyFormat(AFromCell, AToCell: PCell);
var
cell: PCell;
begin begin
if AFormat = nil then if (AFromCell = nil) or (AToCell = nil) then
exit; exit;
cell := GetCell(AToRow, AToCol); CopyCellFormat(AFromCell, AToCell);
cell^.UsedFormattingFields := AFormat^.UsedFormattingFields; ChangedCell(AToCell^.Row, AToCell^.Col);
cell^.BackgroundColor := AFormat^.BackgroundColor; ChangedFont(AToCell^.Row, AToCell^.Col);
cell^.Border := AFormat^.Border; end;
cell^.BorderStyles := AFormat^.BorderStyles;
cell^.FontIndex := AFormat^.FontIndex;
cell^.HorAlignment := AFormat^.HorAlignment;
cell^.VertAlignment := AFormat^.VertAlignment;
cell^.TextRotation := AFormat^.TextRotation;
cell^.NumberFormat := AFormat^.NumberFormat;
cell^.NumberFormatStr := AFormat^.NumberFormatStr;
cell^.Decimals := AFormat^.Decimals;
cell^.CurrencySymbol := AFormat^.CurrencySymbol;
ChangedCell(AToRow, AToCol); procedure TsWorksheet.CopyFormat(AFormat: PCell; AToRow, AToCol: Cardinal);
ChangedFont(AToRow, AToCol); begin
CopyFormat(AFormat, GetCell(AToRow, AToCol));
end; end;
{@@ {@@
@ -1433,6 +1454,9 @@ begin
if IsDateTimeFormat(AFormat) then if IsDateTimeFormat(AFormat) then
raise Exception.Create(lpInvalidNumberFormat); raise Exception.Create(lpInvalidNumberFormat);
if AFormat = nfCustom then
raise Exception.Create(lpIllegalNumberformat);
if AFormat <> nfGeneral then begin if AFormat <> nfGeneral then begin
Include(ACell^.UsedFormattingFields, uffNumberFormat); Include(ACell^.UsedFormattingFields, uffNumberFormat);
ACell^.NumberFormat := AFormat; ACell^.NumberFormat := AFormat;
@ -1457,7 +1481,7 @@ var
parser: TsNumFormatParser; parser: TsNumFormatParser;
nf: TsNumberFormat; nf: TsNumberFormat;
begin begin
parser := TsNumFormatParser.Create(Workbook, AFormatString, cdToFPSpreadsheet); parser := TsNumFormatParser.Create(Workbook, AFormatString, nfCustom, cdToFPSpreadsheet);
try try
// Format string ok? // Format string ok?
if parser.Status <> psOK then if parser.Status <> psOK then
@ -1533,18 +1557,17 @@ end;
Note: at least Excel xls does not recognize a separate datetime cell type: Note: at least Excel xls does not recognize a separate datetime cell type:
a datetime is stored as a (floating point) Number, and the cell is formatted a datetime is stored as a (floating point) Number, and the cell is formatted
as a date (either built-in or a custom format). as a date (either built-in or a custom format).
Note: custom formats are currently not supported by the writer.
} }
procedure TsWorksheet.WriteDateTime(ARow, ACol: Cardinal; AValue: TDateTime; procedure TsWorksheet.WriteDateTime(ARow, ACol: Cardinal; AValue: TDateTime;
AFormat: TsNumberFormat = nfShortDateTime; AFormatStr: String = ''); AFormat: TsNumberFormat = nfShortDateTime; AFormatStr: String = '');
var var
ACell: PCell; ACell: PCell;
fmt: String; //fmt: String;
parser: TsNumFormatParser; //parser: TsNumFormatParser;
begin begin
if AFormat = nfFmtDateTime then begin if (AFormat in [nfFmtDateTime, nfTimeInterval]) then
AFormatStr := BuildDateTimeFormatString(AFormat, Workbook.FormatSettings, AFormatStr); AFormatStr := BuildDateTimeFormatString(AFormat, Workbook.FormatSettings, AFormatStr);
(*
parser := TsNumFormatParser.Create(Workbook, AFormatStr, cdToFPSpreadsheet); parser := TsNumFormatParser.Create(Workbook, AFormatStr, cdToFPSpreadsheet);
try try
// Check that the format string can be reckognized. // Check that the format string can be reckognized.
@ -1558,6 +1581,7 @@ begin
parser.Free; parser.Free;
end; end;
end; end;
*)
ACell := GetCell(ARow, ACol); ACell := GetCell(ARow, ACol);
ACell^.ContentType := cctDateTime; ACell^.ContentType := cctDateTime;
@ -1579,7 +1603,8 @@ end;
procedure TsWorksheet.WriteDecimals(ACell: PCell; ADecimals: Byte); procedure TsWorksheet.WriteDecimals(ACell: PCell; ADecimals: Byte);
begin begin
if (ACell <> nil) and (ACell^.ContentType = cctNumber) then begin if (ACell <> nil) and (ACell^.ContentType = cctNumber) and (ACell^.NumberFormat <> nfCustom)
then begin
ACell^.Decimals := ADecimals; ACell^.Decimals := ADecimals;
ACell^.NumberFormatStr := BuildNumberFormatString(ACell^.NumberFormat, ACell^.NumberFormatStr := BuildNumberFormatString(ACell^.NumberFormat,
FWorkbook.FormatSettings, ADecimals, ACell^.CurrencySymbol); FWorkbook.FormatSettings, ADecimals, ACell^.CurrencySymbol);
@ -2629,27 +2654,43 @@ end;
{ Adds a new number format data to the list and returns the list index of the { Adds a new number format data to the list and returns the list index of the
new (or present) item. } new (or present) item. }
function TsCustomNumFormatList.AddFormat(AFormatIndex: Integer; ANumFormat: TsNumberFormat; function TsCustomNumFormatList.AddFormat(AFormatIndex: Integer;
AFormatString: String = ''; ADecimals: byte = 0; ACurrencySymbol: String = ''): integer; AFormatString: String; ANumFormat: TsNumberFormat; ADecimals: byte = 0;
ACurrencySymbol: String = ''): integer;
var var
item: TsNumFormatData; item: TsNumFormatData;
begin begin
item := TsNumFormatData.Create; item := TsNumFormatData.Create;
item.Index := AFormatIndex; item.Index := AFormatIndex;
item.NumFormat := ANumFormat; item.NumFormat := ANumFormat;
if IsDateTimeFormat(ANumFormat) then if AFormatString = '' then begin
AFormatString := BuildDateTimeFormatString(ANumFormat, Workbook.FormatSettings, if IsDateTimeFormat(ANumFormat) then
AFormatString) AFormatString := BuildDateTimeFormatString(ANumFormat, Workbook.FormatSettings,
else AFormatString)
if item.NumFormat <> nfCustom then else
AFormatString := BuildNumberFormatString(ANumFormat, Workbook.FormatSettings, if item.NumFormat <> nfCustom then
ADecimals, ACurrencySymbol); AFormatString := BuildNumberFormatString(ANumFormat, Workbook.FormatSettings,
ADecimals, ACurrencySymbol);
end;
item.FormatString := AFormatString; item.FormatString := AFormatString;
item.Decimals := ADecimals; item.Decimals := ADecimals;
item.CurrencySymbol := ACurrencySymbol; item.CurrencySymbol := ACurrencySymbol;
Result := inherited Add(item); Result := inherited Add(item);
end; end;
function TsCustomNumFormatList.AddFormat(AFormatString: String;
ANumFormat: TsNumberFormat; ADecimals: Byte = 0;
ACurrencySymbol: String = ''): Integer;
begin
if AFormatString = '' then begin
Result := 0;
exit;
end;
Result := AddFormat(FNextFormatIndex, AFormatString, ANumFormat, ADecimals,
ACurrencySymbol);
inc(FNextFormatIndex);
end;
function TsCustomNumFormatList.AddFormat(AFormatCell: PCell): Integer; function TsCustomNumFormatList.AddFormat(AFormatCell: PCell): Integer;
var var
item: TsNumFormatData; item: TsNumFormatData;
@ -2661,8 +2702,8 @@ begin
raise Exception.Create('TsCustomNumFormatList: Error in program logics: You must provide built-in formats first.'); raise Exception.Create('TsCustomNumFormatList: Error in program logics: You must provide built-in formats first.');
Result := AddFormat(FNextFormatIndex, Result := AddFormat(FNextFormatIndex,
AFormatCell^.NumberFormat,
AFormatCell^.NumberFormatStr, AFormatCell^.NumberFormatStr,
AFormatCell^.NumberFormat,
AFormatCell^.Decimals, AFormatCell^.Decimals,
AFormatCell^.CurrencySymbol AFormatCell^.CurrencySymbol
); );
@ -2671,25 +2712,24 @@ begin
end; end;
{ Adds the builtin format items to the list. The formats must be specified in { Adds the builtin format items to the list. The formats must be specified in
a way which can be understood by fpc. a way that is compatible with the destination file format. Conversion of the
If fpc and file speak different languages "translation" must be made in formatstrings can be done by calling "ConvertAfterReadung" bzw. "ConvertBeforeWriting".
"Analyze" for reading and "FormatStringForWriting" for writing. "AddBuiltInFormats" must be called before user items are added.
Must be called before user items are added.
Must specify FFirstFormatIndexInFile (BIFF5-8, e.g. doesn't save formats <164) Must specify FFirstFormatIndexInFile (BIFF5-8, e.g. doesn't save formats <164)
and must initialize the index of the first user format (FNextFormatIndex) and must initialize the index of the first user format (FNextFormatIndex)
which is automatically incremented when adding user formats. } which is automatically incremented when adding user formats. }
procedure TsCustomNumFormatList.AddBuiltinFormats; procedure TsCustomNumFormatList.AddBuiltinFormats;
begin begin
// must be overridden // must be overridden - see xlscommon as an example.
end; end;
{ Takes the format string (AFormatString) as it is read from the file and { Takes the format string (AFormatString) as it is read from the file and
extracts the number format type (ANumFormat) and the number of decimals extracts the number format type and the number of decimals out of it for use by
(ADecimals) out of it for use by fpc. fpc. The method also converts the format string to a form that can be used
If the format string cannot be directly handled by fpc it has to be transformed by fpc's FormatDateTime and FormatFloat. This conversion should be done in an
to make it compatible. Can be done in overridden versions which know more overridden method which known more about the details of the spreadsheet file
about the structure of the string in the actual file format. } format. }
procedure TsCustomNumFormatList.Analyze(AFormatIndex: Integer; procedure TsCustomNumFormatList.ConvertAfterReading(AFormatIndex: Integer;
var AFormatString: String; var ANumFormat: TsNumberFormat; var AFormatString: String; var ANumFormat: TsNumberFormat;
var ADecimals: Byte; var ACurrencySymbol: String); var ADecimals: Byte; var ACurrencySymbol: String);
var var
@ -2697,6 +2737,7 @@ var
fmt: String; fmt: String;
lFormatData: TsNumFormatData; lFormatData: TsNumFormatData;
i: Integer; i: Integer;
nf: TsNumberFormat;
begin begin
i := Find(AFormatIndex); i := Find(AFormatIndex);
if i > 0 then begin if i > 0 then begin
@ -2704,16 +2745,42 @@ begin
fmt := lFormatData.FormatString; fmt := lFormatData.FormatString;
end else end else
fmt := AFormatString; fmt := AFormatString;
nf := nfGeneral; // not used here.
parser := TsNumFormatParser.Create(Workbook, fmt, cdToFPSpreadsheet); // Analyzes the format string and tries to convert it to fpSpreadsheet format.
parser := TsNumFormatParser.Create(Workbook, fmt, nf, cdToFPSpreadsheet);
try try
if parser.Status = psOK then begin if parser.Status = psOK then begin
ANumFormat := parser.Builtin_NumFormat; ANumFormat := parser.Builtin_NumFormat;
AFormatString := parser.FormatString; AFormatString := parser.FormatString; // This is the converted string.
{
if not (parser.Builtin_NumFormat in [nfCustom, nfFmtDateTime]) then begin if not (parser.Builtin_NumFormat in [nfCustom, nfFmtDateTime]) then begin
ADecimals := parser.ParsedSections[0].Decimals; ADecimals := parser.ParsedSections[0].Decimals;
ACurrencySymbol := parser.ParsedSections[0].CurrencySymbol; ACurrencySymbol := parser.ParsedSections[0].CurrencySymbol;
end; end;
}
end;
finally
parser.Free;
end;
end;
{ Is called before collection all number formats of the spreadsheet and before
writing to file. Its purpose is to convert the format string as used by fpc
to a format compatible with the spreadsheet file format. }
procedure TsCustomNumFormatList.ConvertBeforeWriting(var AFormatString: String;
var ANumFormat: TsNumberFormat; var ADecimals: Byte; var ACurrencySymbol: String);
var
parser: TsNumFormatParser;
fmt: String;
begin
parser := TsNumFormatParser.Create(Workbook, AFormatString, ANumFormat, cdFromFPSpreadsheet);
try
if parser.Status = psOK then begin
AFormatString := parser.FormatString;
ANumFormat := parser.Builtin_NumFormat;
ADecimals := parser.ParsedSections[0].Decimals;
ACurrencySymbol := parser.ParsedSections[0].CurrencySymbol;
end; end;
finally finally
parser.Free; parser.Free;
@ -2842,11 +2909,11 @@ begin
if Find(AFormatIndex) > -1 then if Find(AFormatIndex) > -1 then
exit; exit;
// Analyze the format string and extract information for internal formatting // Analyze & convert the format string, extract infos for internal formatting
Analyze(AFormatIndex, AFormatString, nf, decs, currsym); ConvertAfterReading(AFormatIndex, AFormatString, nf, decs, currsym);
// Add the new item // Add the new item
AddFormat(AFormatIndex, nf, AFormatString, decs, currSym); AddFormat(AFormatIndex, AFormatString, nf, decs, currSym);
end; end;
{ Clears the list and frees memory occupied by the format items. } { Clears the list and frees memory occupied by the format items. }
@ -2967,6 +3034,20 @@ begin
Result := -1; Result := -1;
end; end;
{ Finds the item with the given format string and returns its index in the
format list. }
function TsCustomNumFormatList.Find(AFormatString: String): integer;
var
item: TsNumFormatData;
begin
for Result := 0 to Count-1 do begin
item := Items[Result];
if item.FormatString = AFormatString then
exit;
end;
Result := -1;
end;
{ Determines the format string to be written into the spreadsheet file. { Determines the format string to be written into the spreadsheet file.
Needs to be overridden if the format strings are different from the fpc Needs to be overridden if the format strings are different from the fpc
convention. } convention. }
@ -3155,7 +3236,7 @@ begin
if (FFormattingStyles[i].Decimals <> AFormat^.Decimals) then Continue; if (FFormattingStyles[i].Decimals <> AFormat^.Decimals) then Continue;
if (FFormattingStyles[i].CurrencySymbol <> AFormat^.CurrencySymbol) then Continue; if (FFormattingStyles[i].CurrencySymbol <> AFormat^.CurrencySymbol) then Continue;
end; end;
nfShortDate, nfLongDate, nfShortDateTime, nfShortTime, nfLongTime, nfShortDateTime, nfShortDate, nfLongDate, nfShortTime, nfLongTime,
nfShortTimeAM, nfLongTimeAM, nfFmtDateTime, nfTimeInterval, nfCustom: nfShortTimeAM, nfLongTimeAM, nfFmtDateTime, nfTimeInterval, nfCustom:
if (FFormattingstyles[i].NumberFormatStr <> AFormat^.NumberFormatStr) then Continue; if (FFormattingstyles[i].NumberFormatStr <> AFormat^.NumberFormatStr) then Continue;
end; end;
@ -3173,6 +3254,10 @@ end;
format of the writer, here is the place to apply replacements. format of the writer, here is the place to apply replacements.
Must be overridden by descendants. See BIFF2 } Must be overridden by descendants. See BIFF2 }
procedure TsCustomSpreadWriter.FixFormat(ACell: PCell); procedure TsCustomSpreadWriter.FixFormat(ACell: PCell);
begin
// to be overridden
end;
(*
var var
isLong, isAMPM, isInterval: Boolean; isLong, isAMPM, isInterval: Boolean;
decs: Byte; decs: Byte;
@ -3182,7 +3267,7 @@ begin
// if IsTimeFormat(ACell^.NumberFormatStr, isLong, isAMPM, isInterval, decs) then // if IsTimeFormat(ACell^.NumberFormatStr, isLong, isAMPM, isInterval, decs) then
ACell^.Decimals := decs; ACell^.Decimals := decs;
end; end;
end; end; *)
{ Each descendent should define its own default formats, if any. { Each descendent should define its own default formats, if any.
Always add the normal, unformatted style first to speed things up. } Always add the normal, unformatted style first to speed things up. }
@ -3210,6 +3295,26 @@ begin
Len := Length(FFormattingStyles); Len := Length(FFormattingStyles);
SetLength(FFormattingStyles, Len+1); SetLength(FFormattingStyles, Len+1);
FFormattingStyles[Len] := ACell^; FFormattingStyles[Len] := ACell^;
// Some built-in number formats do not write the format string to the cell
// But the FormattingStyles need it for comparison later. --> Add the format string.
if IsDateTimeFormat(FFormattingStyles[Len].NumberFormat) then
FFormattingStyles[Len].NumberFormatStr := BuildDateTimeFormatString(
FFormattingStyles[Len].NumberFormat,
Workbook.FormatSettings,
FFormattingStyles[Len].NumberFormatStr
)
else
if FFormattingStyles[Len].NumberFormat <> nfCustom then
FFormattingstyles[Len].NumberFormatStr := BuildNumberFormatString(
FFormattingStyles[Len].NumberFormat,
Workbook.FormatSettings,
FFormattingStyles[Len].Decimals,
FFormattingStyles[Len].CurrencySymbol
);
// We store the index of the XF record that will be assigned to this style in
// the "row" of the style. Will be needed when writing the XF record.
FFormattingStyles[Len].Row := NextXFIndex; FFormattingStyles[Len].Row := NextXFIndex;
Inc(NextXFIndex); Inc(NextXFIndex);
end; end;
@ -3220,12 +3325,24 @@ var
begin begin
SetLength(FFormattingStyles, 0); SetLength(FFormattingStyles, 0);
// Add default styles which are required to be there by the destination file
AddDefaultFormats(); AddDefaultFormats();
// Iterate through all cells and collect the individual styles
for i := 0 to Workbook.GetWorksheetCount - 1 do for i := 0 to Workbook.GetWorksheetCount - 1 do
begin
IterateThroughCells(nil, Workbook.GetWorksheetByIndex(i).Cells, ListAllFormattingStylesCallback); IterateThroughCells(nil, Workbook.GetWorksheetByIndex(i).Cells, ListAllFormattingStylesCallback);
end;
// Convert the numberformats of the collected styles to be compatible with the destination file
for i:=0 to High(FFormattingStyles) do
if (FFormattingStyles[i].NumberFormatStr <> '') and
(FFormattingStyles[i].NumberFormat <> nfCustom) // don't touch custom formatstrings!
then
FNumFormatList.ConvertBeforeWriting(
FFormattingStyles[i].NumberFormatStr,
FFormattingStyles[i].NumberFormat,
FFormattingStyles[i].Decimals,
FFormattingStyles[i].CurrencySymbol
);
end; end;
{@@ {@@
@ -3233,10 +3350,34 @@ end;
it does not yet exist in the list. it does not yet exist in the list.
} }
procedure TsCustomSpreadWriter.ListAllNumFormatsCallback(ACell: PCell; AStream: TStream); procedure TsCustomSpreadWriter.ListAllNumFormatsCallback(ACell: PCell; AStream: TStream);
var
fmt: string;
nf: TsNumberFormat;
decs: Byte;
cs: String;
begin begin
FixFormat(ACell); if ACell^.NumberFormat = nfGeneral then
if FNumFormatList.Find(ACell) = -1 then exit;
FNumFormatList.AddFormat(ACell);
// The builtin format list is in "file syntax", but the format string of the
// cells are in "fpc syntax". Therefore, before seeking, we have to convert
// the format string of the cell to "file syntax".
fmt := ACell^.NumberFormatStr;
nf := ACell^.NumberFormat;
decs := ACell^.Decimals;
cs := ACell^.CurrencySymbol;
if (nf <> nfCustom) then begin
if IsDateTimeFormat(nf) then
fmt := BuildDateTimeFormatString(nf, Workbook.FormatSettings, fmt)
else
fmt := BuildNumberFormatString(nf, Workbook.FormatSettings, decs, cs);
FNumFormatList.ConvertBeforeWriting(fmt, nf, decs, cs);
end;
// Seek the format string in the current number format list.
// If not found add the format to the list.
if FNumFormatList.Find(fmt) = -1 then
FNumFormatList.AddFormat(fmt, nf, decs, cs);
end; end;
{@@ {@@
@ -3265,7 +3406,7 @@ begin
ResPos := -1; ResPos := -1;
SetLength(Result, 0); SetLength(Result, 0);
// The formula needs to start with a = // The formula needs to start with a "=" character.
if AFormula.FormulaStr[1] <> '=' then raise Exception.Create('Formula doesn''t start with ='); if AFormula.FormulaStr[1] <> '=' then raise Exception.Create('Formula doesn''t start with =');
StrPos := 2; StrPos := 2;

View File

@ -84,6 +84,7 @@ function BuildDateTimeFormatString(ANumberFormat: TsNumberFormat;
const AFormatSettings: TFormatSettings; AFormatString: String = ''): String; const AFormatSettings: TFormatSettings; AFormatString: String = ''): String;
function StripAMPM(const ATimeFormatString: String): String; function StripAMPM(const ATimeFormatString: String): String;
function CountDecs(AFormatString: String; ADecChars: TsDecsChars = ['0']): Byte; function CountDecs(AFormatString: String; ADecChars: TsDecsChars = ['0']): Byte;
function AddIntervalBrackets(AFormatString: String): String;
function SciFloat(AValue: Double; ADecimals: Byte): String; function SciFloat(AValue: Double; ADecimals: Byte): String;
//function TimeIntervalToString(AValue: TDateTime; AFormatStr: String): String; //function TimeIntervalToString(AValue: TDateTime; AFormatStr: String): String;
@ -517,7 +518,7 @@ end;
function IsDateTimeFormat(AFormat: TsNumberFormat): Boolean; function IsDateTimeFormat(AFormat: TsNumberFormat): Boolean;
begin begin
Result := AFormat in [nfFmtDateTime, nfShortDateTime, nfShortDate, nfLongDate, Result := AFormat in [nfFmtDateTime, nfShortDateTime, nfShortDate, nfLongDate,
nfShortTime. nfLongTime, nfShortTimeAM, nfLongTimeAM, nfTimeInterval]; nfShortTime, nfLongTime, nfShortTimeAM, nfLongTimeAM, nfTimeInterval];
end; end;
(* (*
{ This simple parsing procedure of the Excel format string checks for a fixed { This simple parsing procedure of the Excel format string checks for a fixed
@ -685,7 +686,7 @@ begin
if ph > 0 then IsSci := true; if ph > 0 then IsSci := true;
end; end;
end; end;
*)
{ IsDateFormat checks if the format string s corresponds to a date format } { IsDateFormat checks if the format string s corresponds to a date format }
function IsDateFormat(s: String; out IsLong: Boolean): Boolean; function IsDateFormat(s: String; out IsLong: Boolean): Boolean;
begin begin
@ -767,6 +768,7 @@ begin
end; end;
end; end;
end; end;
*)
{ Builds a date/time format string from the numberformat code. If the format code { Builds a date/time format string from the numberformat code. If the format code
is nfFmtDateTime the given AFormatString is used. AFormatString can use the is nfFmtDateTime the given AFormatString is used. AFormatString can use the
@ -776,6 +778,7 @@ function BuildDateTimeFormatString(ANumberFormat: TsNumberFormat;
const AFormatSettings: TFormatSettings; AFormatString: String = '') : string; const AFormatSettings: TFormatSettings; AFormatString: String = '') : string;
var var
fmt: String; fmt: String;
am, pm: String;
begin begin
case ANumberFormat of case ANumberFormat of
nfFmtDateTime: nfFmtDateTime:
@ -789,31 +792,38 @@ begin
end; end;
nfShortDateTime: nfShortDateTime:
Result := AFormatSettings.ShortDateFormat + ' ' + FormatSettings.ShortTimeFormat; Result := AFormatSettings.ShortDateFormat + ' ' + FormatSettings.ShortTimeFormat;
// In the DefaultFormatSettings this is: d/m/y hh:nn
nfShortDate: nfShortDate:
Result := AFormatSettings.ShortDateFormat; Result := AFormatSettings.ShortDateFormat; // --> d/m/y
nfLongDate: nfLongDate:
Result := AFormatSettings.LongDateFormat; Result := AFormatSettings.LongDateFormat; // --> dd mm yyyy
nfShortTime: nfShortTime:
Result := StripAMPM(AFormatSettings.ShortTimeFormat); Result := StripAMPM(AFormatSettings.ShortTimeFormat); // --> hh:nn
nfLongTime: nfLongTime:
Result := StripAMPM(AFormatSettings.LongTimeFormat); Result := StripAMPM(AFormatSettings.LongTimeFormat); // --> hh:nn:ss
nfShortTimeAM: nfShortTimeAM:
begin begin // --> hh:nn AM/PM
Result := AFormatSettings.ShortTimeFormat; Result := AFormatSettings.ShortTimeFormat;
if pos('a', lowercase(AFormatSettings.ShortTimeFormat)) = 0 then if (pos('a', lowercase(AFormatSettings.ShortTimeFormat)) = 0) then begin
Result := Format('%s %s/%s', [Result, AFormatSettings.TimeAMString, AFormatSettings.TimePMString]); am := IfThen(AFormatSettings.TimeAMString = '', 'AM', AFormatSettings.TimeAMString);
pm := IfThen(AFormatSettings.TimePMString = '', 'PM', AFormatSettings.TimePMString);
Result := Format('%s %s/%s', [Result, am, pm]);
end;
end; end;
nfLongTimeAM: nfLongTimeAM: // --> hh:nn:ss AM/PM
begin begin
Result := AFormatSettings.LongTimeFormat; Result := AFormatSettings.LongTimeFormat;
if pos('a', lowercase(AFormatSettings.LongTimeFormat)) = 0 then if pos('a', lowercase(AFormatSettings.LongTimeFormat)) = 0 then begin
Result := Format('%s %s/%s', [Result, AFormatSettings.TimeAMString, AFormatSettings.TimePMString]); am := IfThen(AFormatSettings.TimeAMString = '', 'AM', AFormatSettings.TimeAMString);
pm := IfThen(AFormatSettings.TimePMString = '', 'PM', AFormatSettings.TimePMString);
Result := Format('%s %s/%s', [Result, am, pm]);
end;
end; end;
nfTimeInterval: nfTimeInterval: // --> [h]:nn:ss
if AFormatString = '' then if AFormatString = '' then
Result := '[h]:mm:ss' Result := '[h]:mm:ss'
else else
Result := AFormatString; Result := AddIntervalBrackets(AFormatString);
end; end;
end; end;
@ -886,13 +896,16 @@ begin
else Result := Result + ';#,##0' + decs + '-'; else Result := Result + ';#,##0' + decs + '-';
end; end;
end; end;
if ANumberFormat in [nfCurrency, nfCurrencyRed] then begin if ANumberFormat in [nfCurrency, nfCurrencyRed] then
Result := Result +';' + Format(POS_FMT[cf], ['0' + decs, ACurrencySymbol])
{
Result := Result + ';0' + decs; Result := Result + ';0' + decs;
if cf in [2,3] then if cf in [2,3] then
Result := Format('%s "%s"', [Result, ACurrencySymbol]) Result := Format('%s "%s"', [Result, ACurrencySymbol])
else else
Result := Format('%s"%s"', [Result, ACurrencySymbol]); Result := Format('%s"%s"', [Result, ACurrencySymbol]);
end else }
else
Result := Result + ';-'; Result := Result + ';-';
end; end;
end; end;
@ -928,6 +941,26 @@ begin
Result := 0; Result := 0;
end; end;
{ The given format string is assumed to be for time intervals, i.e. its first
time symbol must be enclosed by square brackets. Checks if this is true, and
adds the brackes if not. }
function AddIntervalBrackets(AFormatString: String): String;
var
p: Integer;
s1, s2: String;
begin
if AFormatString[1] = '[' then
Result := AFormatString
else begin
p := pos(':', AFormatString);
if p <> 0 then begin
s1 := copy(AFormatString, 1, p-1);
s2 := copy(AFormatString, p, Length(AFormatString));
Result := Format('[%s]%s', [s1, s2]);
end else
Result := Format('[%s]', [AFormatString]);
end;
end;
{ Formats the number AValue in "scientific" format with the given number of { Formats the number AValue in "scientific" format with the given number of
decimals. "Scientific" is the same as "exponential", but with exponents rounded decimals. "Scientific" is the same as "exponential", but with exponents rounded

View File

@ -44,7 +44,7 @@ type
procedure AddBuiltinFormats; override; procedure AddBuiltinFormats; override;
public public
constructor Create(AWorkbook: TsWorkbook); constructor Create(AWorkbook: TsWorkbook);
function FormatStringForWriting(AIndex: Integer): String; override; // function FormatStringForWriting(AIndex: Integer): String; override;
end; end;
{ TsSpreadBIFF2Reader } { TsSpreadBIFF2Reader }
@ -173,33 +173,34 @@ end;
procedure TsBIFF2NumFormatList.AddBuiltinFormats; procedure TsBIFF2NumFormatList.AddBuiltinFormats;
begin begin
AddFormat( 0, nfGeneral); AddFormat( 0, '', nfGeneral);
AddFormat( 1, nfFixed, '0', 0); AddFormat( 1, '0', nfFixed, 0);
AddFormat( 2, nfFixed, '0.00', 2); AddFormat( 2, '0.00', nfFixed, 2);
AddFormat( 3, nfFixedTh, '#,##0', 0); AddFormat( 3, '#,##0', nfFixedTh, 0);
AddFormat( 4, nfFixedTh, '#,##0.00', 2); AddFormat( 4, '#,##0.00', nfFixedTh, 2);
AddFormat( 5, nfFixedTh, '"$"#,##0_);("$"#,##0)', 0); AddFormat( 5, '"$"#,##0_);("$"#,##0)', nfCurrency, 0);
AddFormat( 6, nfFixedTh, '"$"#,##0_);[Red]("$"#,##0)', 2); AddFormat( 6, '"$"#,##0_);[Red]("$"#,##0)', nfCurrencyRed, 2);
AddFormat( 7, nfFixedTh, '"$"#,##0.00_);("$"#,##0.00)', 0); AddFormat( 7, '"$"#,##0.00_);("$"#,##0.00)', nfCurrency, 0);
AddFormat( 8, nfFixedTh, '"$"#,##0.00_);[Red]("$"#,##0.00)', 2); AddFormat( 8, '"$"#,##0.00_);[Red]("$"#,##0.00)', nfCurrency, 2);
AddFormat( 9, nfPercentage, '0%', 0); AddFormat( 9, '0%', nfPercentage, 0);
AddFormat(10, nfPercentage, '0.00%', 2); AddFormat(10, '0.00%', nfPercentage, 2);
AddFormat(11, nfExp, '0.00E+00', 2); AddFormat(11, '0.00E+00', nfExp, 2);
AddFormat(12, nfShortDate); AddFormat(12, 'M/D/YY', nfShortDate);
AddFormat(13, nfLongDate); AddFormat(13, 'D-MMM-YY', nfLongDate);
AddFormat(14, nfFmtDateTime, 'd-mmm'); AddFormat(14, 'D-MMM', nfFmtDateTime);
AddFormat(15, nfFmtDateTime, 'mmm-yy'); AddFormat(15, 'MMM-YY', nfFmtDateTime);
AddFormat(16, nfShortTimeAM); AddFormat(16, 'h:mm AM/PM', nfShortTimeAM);
AddFormat(17, nfLongTimeAM); AddFormat(17, 'h:mm:ss AM/PM', nfLongTimeAM);
AddFormat(18, nfShortTime); AddFormat(18, 'h:mm', nfShortTime);
AddFormat(19, nfLongTime); AddFormat(19, 'h:mm:ss', nfLongTime);
AddFormat(20, nfShortDateTime); AddFormat(20, 'M/D/YY h:mm', nfShortDateTime);
FFirstFormatIndexInFile := 0; // BIFF2 stores built-in formats to file. FFirstFormatIndexInFile := 0; // BIFF2 stores built-in formats to file.
FNextFormatIndex := 21; // not needed - there are not user-defined formats FNextFormatIndex := 21; // not needed - there are not user-defined formats
end; end;
{ Creates formatting strings that are written into the file. } { Creates formatting strings that are written into the file. }
(*
function TsBIFF2NumFormatList.FormatStringForWriting(AIndex: Integer): String; function TsBIFF2NumFormatList.FormatStringForWriting(AIndex: Integer): String;
var var
ds, ts, cs: string; ds, ts, cs: string;
@ -231,7 +232,7 @@ begin
20: Result := 'm/d/yy h:mm'; 20: Result := 'm/d/yy h:mm';
end; end;
end; end;
*)
{ TsSpreadBIFF2Reader } { TsSpreadBIFF2Reader }

View File

@ -348,11 +348,14 @@ type
TsBIFFNumFormatList = class(TsCustomNumFormatList) TsBIFFNumFormatList = class(TsCustomNumFormatList)
protected protected
procedure AddBuiltinFormats; override; procedure AddBuiltinFormats; override;
procedure Analyze(AFormatIndex: Integer; var AFormatString: String; {
procedure ConvertAfterReading(AFormatIndex: Integer; var AFormatString: String;
var ANumFormat: TsNumberFormat; var ADecimals: Byte; var ANumFormat: TsNumberFormat; var ADecimals: Byte;
var ACurrencySymbol: String); override; var ACurrencySymbol: String); override;
procedure ConvertBeforeWriting(var AFormatString: String); override;
}
public public
function FormatStringForWriting(AIndex: Integer): String; override; // function FormatStringForWriting(AIndex: Integer): String; override;
end; end;
{ TsSpreadBIFFReader } { TsSpreadBIFFReader }
@ -535,50 +538,50 @@ end;
{ TsBIFFNumFormatList } { TsBIFFNumFormatList }
{ These are the built-in number formats as used by fpc. Before writing to file { These are the built-in number formats as expected in the biff spreadsheet file.
some code will be modified to become compatible with Excel In BIFF5+ they are not written to file but they are used for lookup of the
(--> FormatStringForWriting) } number format that Excel used. They have to be converted to fpspreadsheet format. }
procedure TsBIFFNumFormatList.AddBuiltinFormats; procedure TsBIFFNumFormatList.AddBuiltinFormats;
var var
cs: String; cs: String;
begin begin
cs := DefaultFormatSettings.CurrencyString; cs := Workbook.FormatSettings.CurrencyString;
AddFormat( 0, nfGeneral); AddFormat( 0, '', nfGeneral);
AddFormat( 1, nfFixed, '0', 0); AddFormat( 1, '0', nfFixed, 0);
AddFormat( 2, nfFixed, '0.00', 2); AddFormat( 2, '0.00', nfFixed, 2);
AddFormat( 3, nfFixedTh, '#,##0', 0); AddFormat( 3, '#,##0', nfFixedTh, 0);
AddFormat( 4, nfFixedTh, '#,##0.00', 2); AddFormat( 4, '#,##0.00', nfFixedTh, 2);
AddFormat( 5, nfCurrency, '', 0); AddFormat( 5, '"'+cs+'"#,##0_);("'+cs+'"#,##0)', nfCurrency, 0);
AddFormat( 6, nfCurrencyRed, '', 0); // negative numbers in red AddFormat( 6, '"'+cs+'"#,##0_);[Red]("'+cs+'"#,##0)', nfCurrencyRed, 0);
AddFormat( 7, nfCurrency, '', 2); AddFormat( 7, '"'+cs+'"#,##0.00_);("'+cs+'"#,##0.00)', nfCurrency, 2);
AddFormat( 8, nfCurrencyRed, '', 2); AddFormat( 8, '"'+cs+'"#,##0.00_);[Red]("'+cs+'"#,##0.00)', nfCurrencyRed, 2);
AddFormat( 9, nfPercentage, '0%', 0); AddFormat( 9, '0%', nfPercentage, 0);
AddFormat(10, nfPercentage, '0.00%', 2); AddFormat(10, '0.00%', nfPercentage, 2);
AddFormat(11, nfExp, '0.00E+00', 2); AddFormat(11, '0.00E+00', nfExp, 2);
// fraction formats 12 ('# ?/?') and 13 ('# ??/??') not supported // fraction formats 12 ('# ?/?') and 13 ('# ??/??') not supported
AddFormat(14, nfShortDate); AddFormat(14, 'M/D/YY', nfShortDate);
AddFormat(15, nfLongDate); AddFormat(15, 'D-MMM-YY', nfLongDate);
AddFormat(16, nfFmtDateTime, 'd/mmm'); AddFormat(16, 'D-MMM', nfFmtDateTime);
AddFormat(17, nfFmtDateTime, 'mmm/yy'); AddFormat(17, 'MMM-YY', nfFmtDateTime);
AddFormat(18, nfShortTimeAM); AddFormat(18, 'h:mm AM/PM', nfShortTimeAM);
AddFormat(19, nfLongTimeAM); AddFormat(19, 'h:mm:ss AM/PM', nfLongTimeAM);
AddFormat(20, nfShortTime); AddFormat(20, 'h:mm', nfShortTime);
AddFormat(21, nfLongTime); AddFormat(21, 'h:mm:ss', nfLongTime);
AddFormat(22, nfShortDateTime); AddFormat(22, 'M/D/YY h:mm', nfShortDateTime);
// 23..36 not supported // 23..36 not supported
AddFormat(37, nfCurrency, '', 0); AddFormat(37, '_(#,##0_);(#,##0)', nfCurrency, 0);
AddFormat(38, nfCurrencyRed, '', 0); AddFormat(38, '_(#,##0_);[Red](#,##0)', nfCurrencyRed, 0);
AddFormat(39, nfCurrency, '', 2); AddFormat(39, '_(#,##0.00_);(#,##0.00)', nfCurrency, 2);
AddFormat(40, nfCurrencyRed, '', 2); AddFormat(40, '_(#,##0.00_);[Red](#,##0.00)', nfCurrencyRed, 2);
AddFormat(41, nfCurrencyDash, '', 0); AddFormat(41, '_("'+cs+'"* #,##0_);_("'+cs+'"* (#,##0);_("'+cs+'"* "-"_);_(@_)', nfCurrencyDash, 0);
AddFormat(42, nfCurrencyDashRed, '', 0); AddFormat(42, '_(* #,##0_);_(* (#,##0);_(* "-"_);_(@_)', nfCurrencyDash, 0);
AddFormat(43, nfCurrencyDash, '', 2); AddFormat(43, '_("'+cs+'"* #,##0.00_);_("'+cs+'"* (#,##0.00);_("'+cs+'"* "-"??_);_(@_)', nfCurrencyDash, 2);
AddFormat(44, nfCurrencyDashRed, '', 2); AddFormat(44, '_(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(@_)', nfCurrencyDash, 2);
AddFormat(45, nfFmtDateTime, 'nn:ss'); AddFormat(45, 'mm:ss', nfFmtDateTime);
AddFormat(46, nfTimeInterval, '[h]:nn:ss'); AddFormat(46, '[h]:mm:ss', nfTimeInterval);
AddFormat(47, nfFmtDateTime, 'nn:ss.z'); // z will be replaced by 0 later AddFormat(47, 'mm:ss.0', nfFmtDateTime);
AddFormat(48, nfSci, '##0.0E+00', 1); AddFormat(48, '##0.0E+00', nfSci, 1);
// 49 ("Text") not supported // 49 ("Text") not supported
// All indexes from 0 to 163 are reserved for built-in formats. // All indexes from 0 to 163 are reserved for built-in formats.
@ -586,7 +589,7 @@ begin
FFirstFormatIndexInFile := 164; FFirstFormatIndexInFile := 164;
FNextFormatIndex := 164; FNextFormatIndex := 164;
end; end;
(*
{ Considers some Excel specialities for format detection. { Considers some Excel specialities for format detection.
The output values will be passed to fpc. } The output values will be passed to fpc. }
procedure TsBIFFNumFormatList.Analyze(AFormatIndex: Integer; procedure TsBIFFNumFormatList.Analyze(AFormatIndex: Integer;
@ -600,7 +603,7 @@ var
} }
begin begin
(* {
AFormatString := 'hh:mm:ss.0 AM/PM'; //"€" #,##.0;[red]"$" -#,##.000;-'; AFormatString := 'hh:mm:ss.0 AM/PM'; //"€" #,##.0;[red]"$" -#,##.000;-';
parser := TsNumFormatParser.Create(Workbook, AFormatString); parser := TsNumFormatParser.Create(Workbook, AFormatString);
@ -620,7 +623,7 @@ begin
finally finally
parser.Free; parser.Free;
end; end;
*) }
fmt := Lowercase(AFormatString); fmt := Lowercase(AFormatString);
{ Check the built-in formats first: { Check the built-in formats first:
@ -664,7 +667,8 @@ begin
inherited Analyze(AFormatIndex, AFormatString, ANumFormat, ADecimals, ACurrencySymbol); inherited Analyze(AFormatIndex, AFormatString, ANumFormat, ADecimals, ACurrencySymbol);
end; end;
*)
(*
{ Creates formatting strings that are written into the file. } { Creates formatting strings that are written into the file. }
function TsBIFFNumFormatList.FormatStringForWriting(AIndex: Integer): String; function TsBIFFNumFormatList.FormatStringForWriting(AIndex: Integer): String;
var var
@ -712,7 +716,7 @@ begin
end; end;
end; end;
end; end;
*)
{ TsSpreadBIFFReader } { TsSpreadBIFFReader }
@ -2039,6 +2043,7 @@ procedure TsSpreadBIFFWriter.WriteXFIndex(AStream: TStream; ACell: PCell);
var var
lIndex: Integer; lIndex: Integer;
lXFIndex: Word; lXFIndex: Word;
lCell: TCell;
begin begin
// First try the fast methods for default formats // First try the fast methods for default formats
if ACell^.UsedFormattingFields = [] then begin if ACell^.UsedFormattingFields = [] then begin
@ -2047,7 +2052,22 @@ begin
end; end;
// If not, then we need to search in the list of dynamic formats // If not, then we need to search in the list of dynamic formats
lIndex := FindFormattingInList(ACell); // But we have to consider that the number formats of the cell is in fpc syntax,
// but the number format list of the writer is in Excel syntax.
lCell := ACell^;
// CopyCellFormat(ACell, @lCell);
with lCell do begin
if NumberFormat <> nfCustom then begin
if IsDateTimeFormat(NumberFormat) then
NumberFormatStr := BuildDateTimeFormatString(NumberFormat,
Workbook.FormatSettings, NumberFormatStr)
else
NumberFormatStr := BuildNumberFormatString(NumberFormat,
Workbook.FormatSettings, Decimals, CurrencySymbol);
NumFormatList.ConvertBeforeWriting(NumberFormatStr, NumberFormat, Decimals, CurrencyString);
end;
end;
lIndex := FindFormattingInList(@lCell);
// Carefully check the index // Carefully check the index
if (lIndex < 0) or (lIndex > Length(FFormattingStyles)) then if (lIndex < 0) or (lIndex > Length(FFormattingStyles)) then