diff --git a/components/fpspreadsheet/examples/excel2demo/excel2write.lpr b/components/fpspreadsheet/examples/excel2demo/excel2write.lpr
index 432d4c874..c11655985 100644
--- a/components/fpspreadsheet/examples/excel2demo/excel2write.lpr
+++ b/components/fpspreadsheet/examples/excel2demo/excel2write.lpr
@@ -17,6 +17,9 @@ var
MyWorksheet: TsWorksheet;
MyRPNFormula: TsRPNFormula;
MyDir: string;
+ number: Double;
+ lCol: TCol;
+ lRow: TRow;
begin
// Open the output file
MyDir := ExtractFilePath(ParamStr(0));
@@ -30,8 +33,7 @@ begin
// Write some number cells
MyWorksheet.WriteNumber(0, 0, 1.0);
- MyWorksheet.WriteUsedFormatting(0, 0, [uffBold]);
-
+ MyWorksheet.WriteUsedFormatting(0, 0, [uffBold, uffNumberFormat]);
MyWorksheet.WriteNumber(0, 1, 2.0);
MyWorksheet.WriteNumber(0, 2, 3.0);
MyWorksheet.WriteNumber(0, 3, 4.0);
@@ -95,6 +97,74 @@ begin
MyWorksheet.WriteNumber(6, 3, 2017);
MyWorksheet.WriteFont(6, 3, 'Arial', 18, [fssBold], scBlue);
+ // Write current date/time to cells B11:B16
+ MyWorksheet.WriteUTF8Text(10, 0, 'nfShortDate');
+ MyWorksheet.WriteDateTime(10, 1, now, nfShortDate);
+ MyWorksheet.WriteUTF8Text(11, 0, 'nfShortTime');
+ MyWorksheet.WriteDateTime(11, 1, now, nfShortTime);
+ MyWorksheet.WriteUTF8Text(12, 0, 'nfLongTime');
+ MyWorksheet.WriteDateTime(12, 1, now, nfLongTime);
+ MyWorksheet.WriteUTF8Text(13, 0, 'nfShortDateTime');
+ MyWorksheet.WriteDateTime(13, 1, now, nfShortDateTime);
+ MyWorksheet.WriteUTF8Text(14, 0, 'nfFmtDateTime, DM');
+ MyWorksheet.WriteDateTime(14, 1, now, nfFmtDateTime, 'DM');
+ MyWorksheet.WriteUTF8Text(15, 0, 'nfFmtDateTime, MY');
+ MyWorksheet.WriteDateTime(15, 1, now, nfFmtDateTime, 'MY');
+ MyWorksheet.WriteUTF8Text(16, 0, 'nfShortTimeAM');
+ MyWorksheet.WriteDateTime(16, 1, now, nfShortTimeAM);
+ MyWorksheet.WriteUTF8Text(17, 0, 'nfLongTimeAM');
+ MyWorksheet.WriteDateTime(17, 1, now, nfLongTimeAM);
+ MyWorksheet.WriteUTF8Text(18, 0, 'nfFmtDateTime, MS');
+ MyWorksheet.WriteDateTime(18, 1, now, nfFmtDateTime, 'MS');
+ MyWorksheet.WriteUTF8Text(19, 0, 'nfFmtDateTime, MSZ');
+ MyWorksheet.WriteDateTime(19, 1, now, nfFmtDateTime, 'MSZ');
+
+ // Write formatted numbers
+ number := 12345.67890123456789;
+ MyWorksheet.WriteUTF8Text(24, 1, '12345.67890123456789');
+ MyWorksheet.WriteUTF8Text(24, 2, '-12345.67890123456789');
+ MyWorksheet.WriteUTF8Text(25, 0, 'nfFixed, 0 decs');
+ MyWorksheet.WriteNumber(25, 1, number, nfFixed, 0);
+ MyWorksheet.WriteNumber(25, 2, -number, nfFixed, 0);
+ MyWorksheet.WriteUTF8Text(26, 0, 'nfFixed, 2 decs');
+ MyWorksheet.WriteNumber(26, 1, number, nfFixed, 2);
+ MyWorksheet.WriteNumber(26, 2, -number, nfFixed, 2);
+ MyWorksheet.WriteUTF8Text(27, 0, 'nfFixedTh, 0 decs');
+ MyWorksheet.WriteNumber(27, 1, number, nfFixedTh, 0);
+ MyWorksheet.WriteNumber(27, 2, -number, nfFixedTh, 0);
+ MyWorksheet.WriteUTF8Text(28, 0, 'nfFixedTh, 2 decs');
+ MyWorksheet.WriteNumber(28, 1, number, nfFixedTh, 2);
+ MyWorksheet.WriteNumber(28, 2, -number, nfFixedTh, 2);
+ MyWorksheet.WriteUTF8Text(29, 0, 'nfSci, 1 dec');
+ MyWorksheet.WriteNumber(29, 1, number, nfSci);
+ MyWorksheet.WriteNumber(29, 2, -number, nfSci);
+ MyWorksheet.WriteNumber(29, 3, 1.0/number, nfSci);
+ MyWorksheet.WriteNumber(29, 4, -1.0/number, nfSci);
+ MyWorksheet.WriteUTF8Text(30, 0, 'nfExp, 2 decs');
+ MyWorksheet.WriteNumber(30, 1, number, nfExp, 2);
+ MyWorksheet.WriteNumber(30, 2, -number, nfExp, 2);
+ MyWorksheet.WriteNumber(30, 3, 1.0/number, nfExp, 2);
+ MyWorksheet.WriteNumber(30, 4, -1.0/number, nfExp, 2);
+
+ number := 1.333333333;
+ MyWorksheet.WriteUTF8Text(35, 0, 'nfPercentage, 0 decs');
+ MyWorksheet.WriteNumber(35, 1, number, nfPercentage, 0);
+ MyWorksheet.WriteUTF8Text(36, 0, 'nfPercentage, 2 decs');
+ MyWorksheet.WriteNumber(36, 1, number, nfPercentage, 2);
+ MyWorksheet.WriteUTF8Text(37, 0, 'nfTimeInterval');
+ MyWorksheet.WriteDateTime(37, 1, number, nfTimeInterval);
+
+ // Set width of columns 0 and 1
+ MyWorksheet.WriteColWidth(0, 40);
+ lCol.Width := 35;
+ MyWorksheet.WriteColInfo(1, lCol);
+
+ // Set height of rows 5 and 6
+ lRow.Height := 10;
+ MyWorksheet.WriteRowInfo(5, lRow);
+ lRow.Height := 5;
+ MyWorksheet.WriteRowInfo(6, lRow);
+
// Save the spreadsheet to a file
MyWorkbook.WriteToFile(MyDir + 'test' + STR_EXCEL_EXTENSION, sfExcel2, true);
MyWorkbook.Free;
diff --git a/components/fpspreadsheet/examples/fpsgrid/fpsgrid.lpi b/components/fpspreadsheet/examples/fpsgrid/fpsgrid.lpi
index 224f22a88..10b127a17 100644
--- a/components/fpspreadsheet/examples/fpsgrid/fpsgrid.lpi
+++ b/components/fpspreadsheet/examples/fpsgrid/fpsgrid.lpi
@@ -108,7 +108,7 @@
-
+
@@ -117,7 +117,7 @@
-
+
@@ -131,21 +131,19 @@
-
+
+
-
-
+
+
-
-
-
@@ -264,11 +262,10 @@
-
-
-
+
+
@@ -292,8 +289,8 @@
-
-
+
+
@@ -302,8 +299,8 @@
-
-
+
+
@@ -312,9 +309,12 @@
-
-
+
+
+
+
+
@@ -348,127 +348,137 @@
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
diff --git a/components/fpspreadsheet/reference/BIFFExplorer/bebiffgrid.pas b/components/fpspreadsheet/reference/BIFFExplorer/bebiffgrid.pas
index be046aa96..8c6f37a3f 100644
--- a/components/fpspreadsheet/reference/BIFFExplorer/bebiffgrid.pas
+++ b/components/fpspreadsheet/reference/BIFFExplorer/bebiffgrid.pas
@@ -48,6 +48,7 @@ type
procedure ShowFont;
procedure ShowFooter;
procedure ShowFormat;
+ procedure ShowFormatCount;
procedure ShowFormula;
procedure ShowHeader;
procedure ShowHideObj;
@@ -268,6 +269,8 @@ begin
ShowSelection;
$001E, $041E:
ShowFormat;
+ $001F:
+ ShowFormatCount;
$0022:
ShowDateMode;
$0024:
@@ -1219,6 +1222,21 @@ begin
end;
+procedure TBIFFGrid.ShowFormatCount;
+var
+ numBytes: Integer;
+ w: Word;
+begin
+ if FFormat = sfExcel2 then begin
+ RowCount := 1 + FixedRows;
+ numBytes := 2;
+ Move(FBuffer[FBufferIndex], w, numBytes);
+ ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(WordLEToN(w)),
+ 'Number of FORMAT records');
+ end;
+end;
+
+
procedure TBIFFGrid.ShowFormula;
const
ABS_REL: array[boolean] of string = ('abs', 'rel');
diff --git a/components/fpspreadsheet/reference/BIFFExplorer/bemain.pas b/components/fpspreadsheet/reference/BIFFExplorer/bemain.pas
index 4387b5a9c..6eed35229 100644
--- a/components/fpspreadsheet/reference/BIFFExplorer/bemain.pas
+++ b/components/fpspreadsheet/reference/BIFFExplorer/bemain.pas
@@ -595,11 +595,6 @@ begin
OnRecentFile := @MRUMenuManagerRecentFile;
end;
- FXFIndex := -1;
- FFontIndex := -1;
- FFormatIndex := -1;
- FRowIndex := -1;
-
HexGrid.ColWidths[HexGrid.ColCount-1] := 5;
HexGrid.DefaultRowHeight := HexGrid.Canvas.TextHeight('Tg') + 4;
AlphaGrid.DefaultRowHeight := HexGrid.DefaultRowHeight;
@@ -1059,6 +1054,10 @@ begin
Screen.Cursor := crHourGlass;
BiffTree.Clear;
parentnode := nil;
+ FXFIndex := -1;
+ FFontIndex := -1;
+ FFormatIndex := -1;
+ FRowIndex := -1;
AStream.Position := 0;
while AStream.Position < AStream.Size do begin
p := AStream.Position;
diff --git a/components/fpspreadsheet/tests/datetests.pas b/components/fpspreadsheet/tests/datetests.pas
index b73c591d7..a5586e8c6 100644
--- a/components/fpspreadsheet/tests/datetests.pas
+++ b/components/fpspreadsheet/tests/datetests.pas
@@ -208,6 +208,7 @@ type
// One cell per test so some tests can fail and those further below may still work
procedure TestWriteReadDates(AFormat: TsSpreadsheetFormat);
published
+ procedure TestWriteReadDates_BIFF2;
procedure TestWriteReadDates_BIFF5;
procedure TestWriteReadDates_BIFF8;
end;
@@ -333,6 +334,11 @@ begin
DeleteFile(TempFile);
end;
+procedure TSpreadWriteReadDateTests.TestWriteReadDates_BIFF2;
+begin
+ TestWriteReadDates(sfExcel2);
+end;
+
procedure TSpreadWriteReadDateTests.TestWriteReadDates_BIFF5;
begin
TestWriteReadDates(sfExcel5);
diff --git a/components/fpspreadsheet/tests/numberstests.pas b/components/fpspreadsheet/tests/numberstests.pas
index fce9d4eb8..de650c57e 100644
--- a/components/fpspreadsheet/tests/numberstests.pas
+++ b/components/fpspreadsheet/tests/numberstests.pas
@@ -95,6 +95,7 @@ type
// One cell per test so some tests can fail and those further below may still work
procedure TestWriteReadNumbers(AFormat: TsSpreadsheetFormat);
published
+ procedure TestWriteReadNumbers_BIFF2;
procedure TestWriteReadNumbers_BIFF5;
procedure TestWriteReadNumbers_BIFF8;
end;
@@ -200,6 +201,11 @@ begin
DeleteFile(TempFile);
end;
+procedure TSpreadWriteReadNumberTests.TestWriteReadNumbers_BIFF2;
+begin
+ TestWriteReadNumbers(sfExcel2);
+end;
+
procedure TSpreadWriteReadNumberTests.TestWriteReadNumbers_BIFF5;
begin
TestWriteReadNumbers(sfExcel5);
diff --git a/components/fpspreadsheet/xlsbiff2.pas b/components/fpspreadsheet/xlsbiff2.pas
index 83444c47a..fe2d071d2 100755
--- a/components/fpspreadsheet/xlsbiff2.pas
+++ b/components/fpspreadsheet/xlsbiff2.pas
@@ -48,6 +48,9 @@ type
procedure ReadRowInfo(AStream: TStream);
protected
procedure ApplyCellFormatting(ARow, ACol: Cardinal; XFIndex: Word); override;
+ procedure ExtractNumberFormat(AXFIndex: WORD;
+ out ANumberFormat: TsNumberFormat; out ADecimals: Word;
+ out ANumberFormatStr: String); override;
procedure ReadBlank(AStream: TStream); override;
procedure ReadColWidth(AStream: TStream);
procedure ReadFont(AStream: TStream);
@@ -57,7 +60,6 @@ type
procedure ReadLabel(AStream: TStream); override;
procedure ReadNumber(AStream: TStream); override;
procedure ReadRowColXF(AStream: TStream; out ARow, ACol: Cardinal; out AXF: Word); override;
-
procedure ReadXF(AStream: TStream);
public
{ General reading methods }
@@ -77,6 +79,9 @@ type
procedure WriteEOF(AStream: TStream);
procedure WriteFont(AStream: TStream; AFontIndex: Integer);
procedure WriteFonts(AStream: TStream);
+ procedure WriteFormat(AStream: TStream; AFormatCode: String);
+ procedure WriteFormatCount(AStream: TStream);
+ procedure WriteFormats(AStream: TStream);
procedure WriteIXFE(AStream: TStream; XFIndex: Word);
procedure WriteXF(AStream: TStream; AFontIndex, AFormatIndex: byte;
ABorders: TsCellBorders = []; AHorAlign: TsHorAlignment = haLeft;
@@ -95,7 +100,7 @@ type
end;
var
- // the palette of the default BIFF2 colors as "big-endian color" values
+ { the palette of the default BIFF2 colors as "big-endian color" values }
PALETTE_BIFF2: array[$0..$07] of TsColorValue = (
$000000, // $00: black
$FFFFFF, // $01: white
@@ -107,6 +112,36 @@ var
$00FFFF // $07: cyan
);
+ { These are the built-in number formats of BIFF 2. They are not stored in
+ the file. Note that, compared to the BUFF5+ built-in formats, two formats
+ are missing and the indexes are offset by 2 after #11.
+ It seems that BIFF2 can handle only these 21 formats. The other formats
+ available in fpspreadsheet are mapped to these 21 formats such that least
+ destruction is made. }
+ NUMFORMAT_BIFF2: array[0..20] of string = (
+ 'General', // 0
+ '0',
+ '0.00',
+ '#,##0',
+ '#,##0.00',
+ '"$"#,##0_);("$"#,##0)', // 5
+ '"$"#,##0_);[Red]("$"#,##0)',
+ '"$"#,##0.00_);("$"#,##0.00)',
+ '"$"#,##0.00_);[Red]("$"#,##0.00)',
+ '0%',
+ '0.00%', // 10
+ '0.00E+00',
+ 'M/D/YY',
+ 'D-MMM-YY',
+ 'D-MMM',
+ 'MMM-YY', // 15
+ 'h:mm AM/PM',
+ 'h:mm:ss AM/PM',
+ 'h:mm',
+ 'h:mm:ss',
+ 'M/D/YY h:mm' // 20
+ );
+
implementation
const
@@ -120,6 +155,7 @@ const
INT_EXCEL_ID_BOF = $0009;
INT_EXCEL_ID_EOF = $000A;
INT_EXCEL_ID_FORMAT = $001E;
+ INT_EXCEL_ID_FORMATCOUNT= $001F;
INT_EXCEL_ID_COLWIDTH = $0024;
INT_EXCEL_ID_XF = $0043;
INT_EXCEL_ID_IXFE = $0044;
@@ -135,6 +171,34 @@ const
INT_EXCEL_CHART = $0020;
INT_EXCEL_MACRO_SHEET = $0040;
+ { FORMAT record constants for BIFF2 }
+ // Subset of the built-in formats for US Excel,
+ // including those needed for date/time output
+ FORMAT_GENERAL = 0; //general/default format
+ FORMAT_FIXED_0_DECIMALS = 1; //fixed, 0 decimals
+ FORMAT_FIXED_2_DECIMALS = 2; //fixed, 2 decimals
+ FORMAT_FIXED_THOUSANDS_0_DECIMALS = 3; //fixed, w/ thousand separator, 0 decs
+ FORMAT_FIXED_THOUSANDS_2_DECIMALS = 4; //fixed, w/ thousand separator, 2 decs
+ FORMAT_CURRENCY_0_DECIMALS = 5; //currency (with currency symbol), 0 decs
+ FORMAT_CURRENCY_2_DECIMALS = 7; //currency (with currency symbol), 2 decs
+ FORMAT_PERCENT_0_DECIMALS = 9; //percent, 0 decimals
+ FORMAT_PERCENT_2_DECIMALS = 10; //percent, 2 decimals
+ FORMAT_EXP_2_DECIMALS = 11; //exponent, 2 decimals
+ FORMAT_SCI_1_DECIMAL = 11; //scientific, 1 decimal -- not present in BIFF2 -- mapped to EXP_2_DECIMALS
+ FORMAT_SHORT_DATE = 12; //short date
+ FORMAT_DATE_DM = 14; //date D-MMM
+ FORMAT_DATE_MY = 15; //date MMM-YYYY
+ FORMAT_SHORT_TIME_AM = 16; //short time H:MM with AM
+ FORMAT_LONG_TIME_AM = 17; //long time H:MM:SS with AM
+ FORMAT_SHORT_TIME = 18; //short time H:MM
+ FORMAT_LONG_TIME = 19; //long time H:MM:SS
+ FORMAT_SHORT_DATETIME = 20; //short date+time
+ { The next three formats are not available in BIFF2. They should not be used
+ when a file is to be saved in BIFF2. If it IS saved as BIFF2 the formats
+ are mapped to FORMAT_LONG_TIME}
+ FORMAT_TIME_MS = 19; //time MM:SS
+ FORMAT_TIME_MSZ = 19; //time MM:SS.0
+ FORMAT_TIME_INTERVAL = 19; //time [hh]:mm:ss, hh can be >24
{ TsSpreadBIFF2Reader }
@@ -181,6 +245,81 @@ begin
end;
end;
+{ Extracts the number format data from an XF record indexed by AXFIndex.
+ Note that BIFF2 supports only 21 formats. }
+procedure TsSpreadBIFF2Reader.ExtractNumberFormat(AXFIndex: WORD;
+ out ANumberFormat: TsNumberFormat; out ADecimals: Word;
+ out ANumberFormatStr: String);
+const
+ NOT_USED = nfGeneral;
+ fmts: array[1..20] of TsNumberFormat = (
+ nfFixed, nfFixed, nfFixedTh, nfFixedTh, nfFixedTh, // 1..5
+ nfFixedTh, nfFixedTh, nfFixedTh, nfPercentage, nfPercentage, // 6..10
+ nfExp, nfShortDate, nfShortDate, nfFmtDateTime, nfFmtDateTime, // 11..15
+ nfShortTimeAM, nfLongTimeAM, nfShortTime, nfLongTime, nfShortDateTime// 16..20
+ );
+ decs: array[1..20] of word = (
+ 0, 2, 0, 2, 0, 0, 2, 2, 0, 2, // 1..10
+ 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 11..20
+ );
+var
+ lFormatData: TFormatListData;
+ lXFData: TXFListData;
+ isAMPM: Boolean;
+ isLongTime: Boolean;
+ isMilliSec: Boolean;
+ t,d: Boolean;
+begin
+ ANumberFormat := nfGeneral;
+ ANumberFormatStr := '';
+ ADecimals := 0;
+
+ lFormatData := FindFormatDataForCell(AXFIndex);
+ if lFormatData = nil then begin
+ // no custom format, so first test for default formats
+ lXFData := TXFListData (FXFList.Items[AXFIndex]);
+ if (lXFData.FormatIndex > 0) and (lXFData.FormatIndex <= 20) then begin
+ ANumberFormat := fmts[lXFData.FormatIndex];
+ ADecimals := decs[lXFData.FormatIndex];
+ end;
+ end else
+ // The next is copied from xlscommon - I think it's not necessary here
+ if IsPercentNumberFormat(lFormatData.FormatString, ADecimals) then
+ ANumberFormat := nfPercentage
+ else
+ if IsExpNumberFormat(lFormatData.Formatstring, ADecimals) then
+ ANumberFormat := nfExp
+ else
+ if IsThousandSepNumberFormat(lFormatData.FormatString, ADecimals) then
+ ANumberFormat := nfFixedTh
+ else
+ if IsFixedNumberFormat(lFormatData.FormatString, ADecimals) then
+ ANumberFormat := nfFixed
+ else begin
+ t := IsTimeFormat(lFormatData.FormatString, isLongTime, isAMPM, isMilliSec);
+ d := IsDateFormat(lFormatData.FormatString);
+ if d and t then
+ ANumberFormat := nfShortDateTime
+ else
+ if d then
+ ANumberFormat := nfShortDate
+ else
+ if t then begin
+ if isAMPM then begin
+ if isLongTime then
+ ANumberFormat := nfLongTimeAM
+ else
+ ANumberFormat := nfShortTimeAM;
+ end else begin
+ if isLongTime then
+ ANumberFormat := nfLongTime
+ else
+ ANumberFormat := nfShortTime;
+ end;
+ end;
+ end;
+end;
+
procedure TsSpreadBIFF2Reader.ReadBlank(AStream: TStream);
var
ARow, ACol: Cardinal;
@@ -341,16 +480,24 @@ procedure TsSpreadBIFF2Reader.ReadNumber(AStream: TStream);
var
ARow, ACol: Cardinal;
XF: Word;
- AValue: Double;
+ value: Double;
+ dt: TDateTime;
+ nf: TsNumberFormat;
+ nd: Word;
+ nfs: String;
begin
{ BIFF Record row/column/style }
ReadRowColXF(AStream, ARow, ACol, XF);
{ IEE 754 floating-point value }
- AStream.ReadBuffer(AValue, 8);
+ AStream.ReadBuffer(value, 8);
- { Save the data }
- FWorksheet.WriteNumber(ARow, ACol, AValue);
+ {Find out what cell type, set content type and value}
+ ExtractNumberFormat(XF, nf, nd, nfs);
+ if IsDateTime(value, nf, dt) then
+ FWorksheet.WriteDateTime(ARow, ACol, dt, nf, nfs)
+ else
+ FWorksheet.WriteNumber(ARow, ACol, value, nf, nd);
{ Apply formatting to cell }
ApplyCellFormatting(ARow, ACol, XF);
@@ -414,31 +561,29 @@ begin
end;
procedure TsSpreadBIFF2Reader.ReadXF(AStream: TStream);
-{
-Offset Size Contents
- 0 1 Index to FONT record (➜5.45)
- 1 1 Not used
- 2 1 Number format and cell flags:
- Bit Mask Contents
- 5-0 3FH Index to FORMAT record (➜5.49)
- 6 40H 1 = Cell is locked
- 7 80H 1 = Formula is hidden
- 3 1 Horizontal alignment, border style, and background:
- Bit Mask Contents
- 2-0 07H XF_HOR_ALIGN – Horizontal alignment
- 0 General, 1 Left, 2 Centred, 3 Right, 4 Filled
- 3 08H 1 = Cell has left black border
- 4 10H 1 = Cell has right black border
- 5 20H 1 = Cell has top black border
- 6 40H 1 = Cell has bottom black border
- 7 80H 1 = Cell has shaded background
-}
+{ Offset Size Contents
+ 0 1 Index to FONT record (➜5.45)
+ 1 1 Not used
+ 2 1 Number format and cell flags:
+ Bit Mask Contents
+ 5-0 3FH Index to FORMAT record (➜5.49)
+ 6 40H 1 = Cell is locked
+ 7 80H 1 = Formula is hidden
+ 3 1 Horizontal alignment, border style, and background:
+ Bit Mask Contents
+ 2-0 07H XF_HOR_ALIGN – Horizontal alignment
+ 0 General, 1 Left, 2 Center, 3 Right, 4 Filled
+ 3 08H 1 = Cell has left black border
+ 4 10H 1 = Cell has right black border
+ 5 20H 1 = Cell has top black border
+ 6 40H 1 = Cell has bottom black border
+ 7 80H 1 = Cell has shaded background }
type
- TXFRecord = packed record // see p. 224
- FontIndex: byte; // Offset 0, Size 1
- NotUsed: byte; // Offset 1, Size 1
- NumFormat_Flags: byte; // Offset 2, Size 1
- HorAlign_Border_BackGround: Byte; // Offset 3, Size 1
+ TXFRecord = packed record
+ FontIndex: byte;
+ NotUsed: byte;
+ NumFormat_Flags: byte;
+ HorAlign_Border_BackGround: Byte;
end;
var
lData: TXFListData;
@@ -453,7 +598,7 @@ begin
lData.FontIndex := xf.FontIndex;
// Format index
- lData.FormatIndex := xf.NumFormat_Flags and $07;
+ lData.FormatIndex := xf.NumFormat_Flags and $3F;
// Horizontal alignment
b := xf.HorAlign_Border_Background and MASK_XF_HOR_ALIGN;
@@ -490,6 +635,8 @@ begin
// Add the decoded data to the list
FXFList.Add(lData);
+
+ ldata := TXFListData(FXFList.Items[FXFList.Count-1]);
end;
@@ -635,6 +782,7 @@ begin
WriteBOF(AStream);
WriteFonts(AStream);
+ WriteFormats(AStream);
WriteXFRecords(AStream);
WriteColWidths(AStream);
WriteCellsToStream(AStream, Workbook.GetFirstWorksheet.Cells);
@@ -658,7 +806,7 @@ begin
{ not used }
AStream.WriteByte(0);
- { number format and cell flags }
+ { Number format index and cell flags }
b := AFormatIndex and $3F;
AStream.WriteByte(b);
@@ -689,7 +837,7 @@ begin
lFormatIndex := 0; //General format (one of the built-in number formats)
lBorders := [];
lHorAlign := FFormattingStyles[i].HorAlignment;
-(*
+
// Now apply the modifications.
if uffNumberFormat in FFormattingStyles[i].UsedFormattingFields then
case FFormattingStyles[i].NumberFormat of
@@ -740,16 +888,18 @@ begin
if (fmt = 'my') or (fmt = 'mmm-yy') or (fmt = 'mmm yy') or (fmt = 'mmm/yy') then
lFormatIndex := FORMAT_DATE_MY
else
+ { Because of limitations of BIFF2 the next two formats are mapped
+ to the same format index! }
if (fmt = 'ms') or (fmt = 'nn:ss') or (fmt = 'mm:ss') then
lFormatIndex := FORMAT_TIME_MS
else
if (fmt = 'msz') or (fmt = 'nn:ss.zzz') or (fmt = 'mm:ss.zzz') or (fmt = 'mm:ss.0') or (fmt = 'mm:ss.z') or (fmt = 'nn:ss.z') then
- lFormatIndex := FORMAT_TIME_MSZ
+ lFormatIndex := FORMAT_TIME_MSZ;
end;
nfTimeInterval:
lFormatIndex := FORMAT_TIME_INTERVAL;
end;
- *)
+
if uffBorder in FFormattingStyles[i].UsedFormattingFields then
lBorders := FFormattingStyles[i].Border;
@@ -880,6 +1030,110 @@ begin
WriteFont(AStream, i);
end;
+procedure TsSpreadBIFF2Writer.WriteFormat(AStream: TStream; AFormatCode: String);
+var
+ len: Integer;
+ s: AnsiString;
+begin
+ if AFormatCode = '' then
+ exit;
+
+ s := AFormatCode;
+ len := Length(s);
+
+ { BIFF record header }
+ AStream.WriteWord(WordToLE(INT_EXCEL_ID_FORMAT));
+ AStream.WriteWord(WordToLE(len + 1));
+
+ { Write format string }
+ AStream.WriteByte(len);
+ AStream.WriteBuffer(s[1], len);
+end;
+
+procedure TsSpreadBIFF2Writer.WriteFormatCount(AStream: TStream);
+begin
+ AStream.WriteWord(WordToLE(INT_EXCEL_ID_FORMATCOUNT));
+ AStream.WriteWord(WordToLE(2));
+ AStream.WriteWord(WordToLE(High(NUMFORMAT_BIFF2)+1));
+end;
+
+procedure TsSpreadBIFF2Writer.WriteFormats(AStream: TStream);
+var
+ i: Integer;
+begin
+ WriteFormatCount(AStream);
+ for i:=0 to High(NUMFORMAT_BIFF2) do
+ WriteFormat(AStream, NUMFORMAT_BIFF2[i]);
+end;
+(*
+var
+ ds, ts: Char; //decimal separator, thousand separator
+begin
+ ds := DefaultFormatSettings.DecimalSeparator;
+ ts := DefaultFormatSettings.ThousandSeparator;
+ { 0} WriteFormat(AStream, 'General'); // 0
+ { 1} WriteFormat(AStream, '0');
+ { 2} WriteFormat(AStream, '0'+ds+'00'); // 0.00
+ { 3} WriteFormat(AStream, '#'+ts+'##0'); // #,##0
+ { 4} WriteFormat(AStream, '#'+ts+'##0'+ds+'00'); // #,##0.00
+ { 5} WriteFormat(AStream, '"$"#'+ts+'##0_);("$"#'+ts+'##0)');
+ { 6} WriteFormat(AStream, '"$"#'+ts+'##0_);[Red]("$"#'+ts+'##0)');
+ { 7} WriteFormat(AStream, '"$"#'+ts+'##0'+ds+'00_);("$"#'+ts+'##0'+ds+'00)');
+ { 8} WriteFormat(AStream, '"$"#'+ts+'##0'+ds+'00_);[Red]("$"#'+ts+'##0'+ds+'00)');
+ { 9} WriteFormat(AStream, '0%');
+ {10} WriteFormat(AStream, '0'+ds+'00%'); // 0.00%
+ {11} WriteFormat(AStream, '0'+ds+'00E+00'); // 0.00E+00
+ {12} WriteFormat(AStream, 'm/d/yy');
+ {13} WriteFormat(AStream, 'd-mmm-yy');
+ {14} WriteFormat(AStream, 'd-mmm');
+ {15} WriteFormat(AStream, 'mmm-yy');
+ {16} WriteFormat(AStream, 'h:mm AM/PM');
+ {17} WriteFormat(AStream, 'h:mm:ss AM/PM');
+ {18} WriteFormat(AStream, 'h:mm');
+ {19} WriteFormat(AStream, 'h:mm:ss');
+ {20} WriteFormat(AStream, 'm/d/yy h:mm');
+
+ { # TODO: locale support
+ 0 => 'GENERAL',
+ 1 => '0',
+ 2 => '0.00',
+ 3 => '#,##0',
+ 4 => '#,##0.00',
+ 5 => '"$"#,##0_);("$"#,##0)',
+ 6 => '"$"#,##0_);[Red]("$"#,##0)',
+ 7 => '"$"#,##0.00_);("$"#,##0.00)',
+ 8 => '"$"#,##0.00_);[Red]("$"#,##0.00)',
+ 9 => '0%',
+ 10 => '0.00%',
+ 11 => '0.00E+00',
+ 12 => '# ?/?',
+ 13 => '# ??/??',
+ 14 => 'M/D/YY',
+ 15 => 'D-MMM-YY',
+ 16 => 'D-MMM',
+ 17 => 'MMM-YY',
+ 18 => 'h:mm AM/PM',
+ 19 => 'h:mm:ss AM/PM',
+ 20 => 'h:mm',
+ 21 => 'h:mm:ss',
+ 22 => 'M/D/YY h:mm',
+ 37 => '_(#,##0_);(#,##0)',
+ 38 => '_(#,##0_);[Red](#,##0)',
+ 39 => '_(#,##0.00_);(#,##0.00)',
+ 40 => '_(#,##0.00_);[Red](#,##0.00)',
+ 41 => '_("$"* #,##0_);_("$"* (#,##0);_("$"* "-"_);_(@_)',
+ 42 => '_(* #,##0_);_(* (#,##0);_(* "-"_);_(@_)',
+ 43 => '_("$"* #,##0.00_);_("$"* (#,##0.00);_("$"* "-"??_);_(@_)',
+ 44 => '_(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(@_)',
+ 45 => 'mm:ss',
+ 46 => '[h]:mm:ss',
+ 47 => 'mm:ss.0',
+ 48 => '##0.0E+0',
+ 49 => '@',
+ }
+end;
+ *)
+
{
Writes an Excel 2 FORMULA record
diff --git a/components/fpspreadsheet/xlscommon.pas b/components/fpspreadsheet/xlscommon.pas
index d80993d57..7045b860a 100644
--- a/components/fpspreadsheet/xlscommon.pas
+++ b/components/fpspreadsheet/xlscommon.pas
@@ -207,7 +207,7 @@ const
DATEMODE_1900_BASE=1; //1/1/1900 minus 1 day in FPC TDateTime
DATEMODE_1904_BASE=1462; //1/1/1904 in FPC TDateTime
- { FORMAT record constants }
+ { FORMAT record constants for BIFF5-BIFF8}
// Subset of the built-in formats for US Excel,
// including those needed for date/time output
FORMAT_GENERAL = 0; //general/default format
@@ -330,7 +330,7 @@ type
// Returns the numberformat for a given XF record
procedure ExtractNumberFormat(AXFIndex: WORD;
out ANumberFormat: TsNumberFormat; out ADecimals: Word;
- out ANumberFormatStr: String);
+ out ANumberFormatStr: String); virtual;
// Finds format record for XF record pointed to by cell
// Will not return info for built-in formats
function FindFormatDataForCell(const AXFIndex: Integer): TFormatListData;
@@ -382,8 +382,10 @@ type
const AValue: TDateTime; ACell: PCell); override;
// Writes out a PALETTE record containing all colors defined in the workbook
procedure WritePalette(AStream: TStream);
+
public
constructor Create(AWorkbook: TsWorkbook); override;
+ destructor Destroy; override;
end;
function IsExpNumberFormat(s: String; out Decimals: Word): Boolean;
@@ -470,7 +472,7 @@ begin
inherited Destroy;
end;
-{ Applies the XF formatting given by the given index to the specified cell }
+{ Applies the XF formatting referred to by XFIndex to the specified cell }
procedure TsSpreadBIFFReader.ApplyCellFormatting(ARow, ACol: Cardinal;
XFIndex: Word);
var
@@ -521,6 +523,8 @@ begin
end;
end;
+{ Extracts number format data from an XF record index by AXFIndex.
+ Valid for BIFF5-BIFF8. Needs to be overridden for BIFF2 }
procedure TsSpreadBIFFReader.ExtractNumberFormat(AXFIndex: WORD;
out ANumberFormat: TsNumberFormat; out ADecimals: Word;
out ANumberFormatStr: String);
@@ -568,7 +572,7 @@ begin
if lFormatData = nil then begin
// no custom format, so first test for default formats
- lXFData := TXFListData (FXFList.Items[AXFIndex]);
+ lXFData := TXFListData(FXFList.Items[AXFIndex]);
case lXFData.FormatIndex of
FORMAT_DATE_DM:
begin ANumberFormat := nfFmtDateTime; ANumberFormatStr := 'DM'; end;
@@ -716,6 +720,9 @@ begin
end;
end;
+{ Read column info (column width) from the stream.
+ Valid for BIFF3-BIFF8.
+ For BIFF2 use the records COLWIDTH and COLUMNDEFAULT. }
procedure TsSpreadBiffReader.ReadColInfo(const AStream: TStream);
var
c, c1, c2: Cardinal;
@@ -768,8 +775,8 @@ procedure TsSpreadBIFFReader.ReadNumber(AStream: TStream);
var
ARow, ACol: Cardinal;
XF: WORD;
- AValue: Double;
- lDateTime: TDateTime;
+ value: Double;
+ dt: TDateTime;
nf: TsNumberFormat;
nd: word;
nfs: String;
@@ -777,14 +784,14 @@ begin
ReadRowColXF(AStream,ARow,ACol,XF);
{ IEE 754 floating-point value }
- AStream.ReadBuffer(AValue, 8);
+ AStream.ReadBuffer(value, 8);
{Find out what cell type, set content type and value}
ExtractNumberFormat(XF, nf, nd, nfs);
- if IsDateTime(AValue, nf, lDateTime) then
- FWorksheet.WriteDateTime(ARow, ACol, lDateTime, nf, nfs)
+ if IsDateTime(value, nf, dt) then
+ FWorksheet.WriteDateTime(ARow, ACol, dt, nf, nfs)
else
- FWorksheet.WriteNumber(ARow, ACol, AValue, nf, nd);
+ FWorksheet.WriteNumber(ARow, ACol, value, nf, nd);
{ Add attributes to cell }
ApplyCellFormatting(ARow, ACol, XF);
@@ -853,6 +860,11 @@ begin
FDateMode := dm1900;
end;
+destructor TsSpreadBIFFWriter.Destroy;
+begin
+ inherited Destroy;
+end;
+
function TsSpreadBIFFWriter.FormulaElementKindToExcelTokenID(
AElementKind: TFEKind; out ASecondaryID: Word): Word;
const
@@ -1126,7 +1138,7 @@ procedure TsSpreadBIFFWriter.WriteDateTime(AStream: TStream; const ARow,
var
ExcelDateSerial: double;
begin
- ExcelDateSerial := ConvertDateTimeToExcelDateTime(AValue,FDateMode);
+ ExcelDateSerial := ConvertDateTimeToExcelDateTime(AValue, FDateMode);
// fpspreadsheet must already have set formatting to a date/datetime format, so
// this will get written out as a pointer to the relevant XF record.
// In the end, dates in xls are just numbers with a format. Pass it on to WriteNumber: