fpspreadsheet: Major reconstruction of numberformat parser to facilitate creation of xml formats for ods. Some minor regressions in unit tests and fpsgrid demo to be fixed. Removed elements "Decimals" and "CurrencySymbol" from TCell (this information is taken from the format string now). Removed the built-in format nfFmtDateTime (makes life easier, use nfCustom instead).

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3156 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2014-06-12 22:20:45 +00:00
parent 885faa4ed6
commit a4ee00f870
15 changed files with 2035 additions and 1151 deletions

View File

@ -64,9 +64,6 @@
</Debugging> </Debugging>
</Linking> </Linking>
<Other> <Other>
<CompilerMessages>
<UseMsgFile Value="True"/>
</CompilerMessages>
<CompilerPath Value="$(CompPath)"/> <CompilerPath Value="$(CompPath)"/>
</Other> </Other>
</CompilerOptions> </CompilerOptions>

View File

@ -53,9 +53,9 @@ begin
{ non-frozen panes not working, at the moment. Requires SELECTION records? { non-frozen panes not working, at the moment. Requires SELECTION records?
MyWorksheet.LeftPaneWidth := 20*72*2; // 72 pt = inch --> 2 inches = 5 cm MyWorksheet.LeftPaneWidth := 20*72*2; // 72 pt = inch --> 2 inches = 5 cm
} }
// Write some cells // Write some cells
MyWorksheet.WriteNumber(0, 0, 1.0, nfFixed, 3);// A1 MyWorksheet.WriteNumber(0, 0, 0.0, nfSci, 1);
(*
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
MyWorksheet.WriteNumber(0, 3, 4.0);// D1 MyWorksheet.WriteNumber(0, 3, 4.0);// D1
@ -131,7 +131,7 @@ begin
MyWorksheet.WriteUTF8Text(8, 3, 'Colors...'); MyWorksheet.WriteUTF8Text(8, 3, 'Colors...');
MyWorksheet.WriteFont(8, 3, 'Courier New', 12, [fssUnderline], scBlue); MyWorksheet.WriteFont(8, 3, 'Courier New', 12, [fssUnderline], scBlue);
MyWorksheet.WriteBackgroundColor(8, 3, scYellow); MyWorksheet.WriteBackgroundColor(8, 3, scYellow);
{}
{ {
// Uncomment this to test large XLS files // Uncomment this to test large XLS files
for i := 50 to 1000 do for i := 50 to 1000 do
@ -174,6 +174,9 @@ begin
MyWorksheet.WriteUTF8Text(r, 0, 'Writing current date/time:'); MyWorksheet.WriteUTF8Text(r, 0, 'Writing current date/time:');
inc(r, 2); inc(r, 2);
// Write current date/time to cells B11:B16 // Write current date/time to cells B11:B16
MyWorksheet.WriteUTF8Text(r, 0, '(default format)');
MyWorksheet.WriteDateTime(r, 1, now);
inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfShortDate'); MyWorksheet.WriteUTF8Text(r, 0, 'nfShortDate');
MyWorksheet.WriteDateTime(r, 1, now, nfShortDate); MyWorksheet.WriteDateTime(r, 1, now, nfShortDate);
inc(r); inc(r);
@ -189,11 +192,11 @@ begin
MyWorksheet.WriteUTF8Text(r, 0, 'nfShortDateTime'); MyWorksheet.WriteUTF8Text(r, 0, 'nfShortDateTime');
MyWorksheet.WriteDateTime(r, 1, now, nfShortDateTime); MyWorksheet.WriteDateTime(r, 1, now, nfShortDateTime);
inc(r); inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfFmtDateTime, DM'); MyWorksheet.WriteUTF8Text(r, 0, 'nfCustom, ''dd/mmm''');
MyWorksheet.WriteDateTime(r, 1, now, nfFmtDateTime, 'DM'); MyWorksheet.WriteDateTime(r, 1, now, nfCustom, 'dd/mmm');
inc(r); inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfFmtDateTime, MY'); MyWorksheet.WriteUTF8Text(r, 0, 'nfCustom, ''MMM/YY''');
MyWorksheet.WriteDateTime(r, 1, now, nfFmtDateTime, 'MY'); MyWorksheet.WriteDateTime(r, 1, now, nfCustom, 'mmm/yy');
inc(r); inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfShortTimeAM'); MyWorksheet.WriteUTF8Text(r, 0, 'nfShortTimeAM');
MyWorksheet.WriteDateTime(r, 1, now, nfShortTimeAM); MyWorksheet.WriteDateTime(r, 1, now, nfShortTimeAM);
@ -201,14 +204,14 @@ begin
MyWorksheet.WriteUTF8Text(r, 0, 'nfLongTimeAM'); MyWorksheet.WriteUTF8Text(r, 0, 'nfLongTimeAM');
MyWorksheet.WriteDateTime(r, 1, now, nfLongTimeAM); MyWorksheet.WriteDateTime(r, 1, now, nfLongTimeAM);
inc(r); inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfFmtDateTime, MS'); MyWorksheet.WriteUTF8Text(r, 0, 'nfCustom, ''mm:ss''');
MyWorksheet.WriteDateTime(r, 1, now, nfFmtDateTime, 'MS'); MyWorksheet.WriteDateTime(r, 1, now, nfCustom, 'mm:ss');
inc(r); inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfFmtDateTime, MSZ'); MyWorksheet.WriteUTF8Text(r, 0, 'nfCustom, ''mm:ss.z''');
MyWorksheet.WriteDateTime(r, 1, now, nfFmtDateTime, 'MSZ'); MyWorksheet.WriteDateTime(r, 1, now, nfCustom, 'mm:ss.z');
inc(r); inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfFmtDateTime, mm:ss.zzz'); MyWorksheet.WriteUTF8Text(r, 0, 'nfCustom, ''mm:ss.zzz''');
MyWorksheet.WriteDateTime(r, 1, now, nfFmtDateTime, 'mm:ss.zzz'); MyWorksheet.WriteDateTime(r, 1, now, nfCustom, 'mm:ss.zzz');
// Write formatted numbers // Write formatted numbers
s := '31415.9265359'; s := '31415.9265359';
@ -301,26 +304,28 @@ begin
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.WriteCurrency(r, 1, number, nfCurrency, 0, 'USD');
MyWorksheet.WriteNumber(r, 2, -number, nfCurrency, 0, 'USD'); MyWorksheet.WriteCurrency(r, 2, -number, nfCurrency, 0, 'USD');
MyWorksheet.WriteNumber(r, 3, 0.0, nfCurrency, 0, 'USD'); MyWorksheet.WriteCurrency(r, 3, 0.0, nfCurrency, 0, 'USD');
inc(r); inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfCurrencyRed, 0 decs'); MyWorksheet.WriteUTF8Text(r, 0, 'nfCurrencyRed, 0 decs');
MyWorksheet.WriteNumber(r, 1, number, nfCurrencyRed, 0, 'USD'); MyWorksheet.WriteCurrency(r, 1, number, nfCurrencyRed, 0, 'USD');
MyWorksheet.WriteNumber(r, 2, -number, nfCurrencyRed, 0, 'USD'); MyWorksheet.WriteCurrency(r, 2, -number, nfCurrencyRed, 0, 'USD');
MyWorksheet.WriteNumber(r, 3, 0.0, nfCurrencyRed, 0, 'USD'); MyWorksheet.WriteCurrency(r, 3, 0.0, nfCurrencyRed, 0, 'USD');
inc(r); inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfAccounting, 0 decs'); MyWorksheet.WriteUTF8Text(r, 0, 'nfAccounting, 0 decs');
MyWorksheet.WriteNumber(r, 1, number, nfAccounting, 0, 'USD'); MyWorksheet.WriteCurrency(r, 1, number, nfAccounting, 0, 'USD');
MyWorksheet.WriteNumber(r, 2, -number, nfAccounting, 0, 'USD'); MyWorksheet.WriteCurrency(r, 2, -number, nfAccounting, 0, 'USD');
MyWorksheet.WriteNumber(r, 3, 0.0, nfAccounting, 0, 'USD'); MyWorksheet.WriteCurrency(r, 3, 0.0, nfAccounting, 0, 'USD');
inc(r); inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfAccountingRed, 0 decs'); MyWorksheet.WriteUTF8Text(r, 0, 'nfAccountingRed, 0 decs');
MyWorksheet.WriteNumber(r, 1, -number, nfAccountingRed, 0, 'USD'); MyWorksheet.WriteCurrency(r, 1, -number, nfAccountingRed, 0, 'USD');
MyWorksheet.WriteNumber(r, 2, number, nfAccountingRed, 0, 'USD'); MyWorksheet.WriteCurrency(r, 2, number, nfAccountingRed, 0, 'USD');
MyWorksheet.WriteNumber(r, 3, 0.0, nfAccountingRed, 0, 'USD'); MyWorksheet.WriteCurrency(r, 3, 0.0, nfAccountingRed, 0, 'USD');
{
inc(r,2); inc(r,2);
MyWorksheet.WriteUTF8Text(r, 0, 'nfCustom, "EUR "#,##0_);("EUR "#,##0)'); MyWorksheet.WriteUTF8Text(r, 0, 'nfCustom, "EUR "#,##0_);("EUR "#,##0)');
MyWorksheet.WriteNumber(r, 1, number); MyWorksheet.WriteNumber(r, 1, number);
@ -333,7 +338,7 @@ begin
MyWorksheet.WriteNumberFormat(r, 1, nfCustom, '"$"#,##0.0_);[Red]("$"#,##0.0)'); MyWorksheet.WriteNumberFormat(r, 1, nfCustom, '"$"#,##0.0_);[Red]("$"#,##0.0)');
MyWorksheet.WriteNumber(r, 2, -number); MyWorksheet.WriteNumber(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');
@ -348,29 +353,44 @@ begin
MyWorksheet.WriteUTF8Text(r, 0, 'nfPercentage, 3 decs'); MyWorksheet.WriteUTF8Text(r, 0, 'nfPercentage, 3 decs');
MyWorksheet.WriteNumber(r, 1, number, nfPercentage, 3); MyWorksheet.WriteNumber(r, 1, number, nfPercentage, 3);
inc(r); inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, hh:mm:ss'); MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval Default=[h]:mm:ss');
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); inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, h:n:s'); MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, [h]:m:s');
MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, 'h:n:s'); MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, '[h]:m:s');
inc(r); inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, hh:mm'); MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, [h]:n:s');
MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, 'hh:mm'); MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, '[h]:n:s');
inc(r); inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, hh:nn'); MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, [hh]:mm');
MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, 'hh:nn'); MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, '[hh]:mm');
inc(r); inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, h:m'); MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, [hh]:nn');
MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, 'h:m'); MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, '[hh]:nn');
inc(r); inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, h:n'); MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval [h]:m');
MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, 'h:n'); MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, '[h]:m');
inc(r); inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, h'); MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, [h]:n');
MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, 'h'); MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, '[h]:n');
inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, [h]');
MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, '[h]');
inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, [m]:s');
MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, '[m]:s');
inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, m:s');
MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, 'm:s');
inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, [mm]:ss');
MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, '[mm]:ss');
inc(r);
MyWorksheet.WriteUTF8Text(r, 0, 'nfTimeInterval, [ss]');
MyWorksheet.WriteDateTime(r, 1, number, nfTimeInterval, '[ss]');
// Set width of columns 0, 1 and 5 // Set width of columns 0, 1 and 5
MyWorksheet.WriteColWidth(0, 30); MyWorksheet.WriteColWidth(0, 30);
@ -395,7 +415,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

@ -4,7 +4,7 @@ object Form1: TForm1
Top = 248 Top = 248
Width = 884 Width = 884
Caption = 'fpsGrid' Caption = 'fpsGrid'
ClientHeight = 624 ClientHeight = 629
ClientWidth = 884 ClientWidth = 884
Menu = MainMenu Menu = MainMenu
OnActivate = FormActivate OnActivate = FormActivate
@ -14,7 +14,7 @@ object Form1: TForm1
object Panel1: TPanel object Panel1: TPanel
Left = 0 Left = 0
Height = 85 Height = 85
Top = 539 Top = 544
Width = 884 Width = 884
Align = alBottom Align = alBottom
BevelOuter = bvNone BevelOuter = bvNone
@ -23,9 +23,9 @@ object Form1: TForm1
TabOrder = 0 TabOrder = 0
object CbShowHeaders: TCheckBox object CbShowHeaders: TCheckBox
Left = 8 Left = 8
Height = 24 Height = 19
Top = 8 Top = 8
Width = 116 Width = 93
Caption = 'Show headers' Caption = 'Show headers'
Checked = True Checked = True
OnClick = CbShowHeadersClick OnClick = CbShowHeadersClick
@ -34,9 +34,9 @@ object Form1: TForm1
end end
object CbShowGridLines: TCheckBox object CbShowGridLines: TCheckBox
Left = 8 Left = 8
Height = 24 Height = 19
Top = 32 Top = 32
Width = 125 Width = 100
Caption = 'Show grid lines' Caption = 'Show grid lines'
Checked = True Checked = True
OnClick = CbShowGridLinesClick OnClick = CbShowGridLinesClick
@ -45,7 +45,7 @@ object Form1: TForm1
end end
object EdFrozenCols: TSpinEdit object EdFrozenCols: TSpinEdit
Left = 389 Left = 389
Height = 28 Height = 23
Top = 8 Top = 8
Width = 52 Width = 52
OnChange = EdFrozenColsChange OnChange = EdFrozenColsChange
@ -53,7 +53,7 @@ object Form1: TForm1
end end
object EdFrozenRows: TSpinEdit object EdFrozenRows: TSpinEdit
Left = 389 Left = 389
Height = 28 Height = 23
Top = 39 Top = 39
Width = 52 Width = 52
OnChange = EdFrozenRowsChange OnChange = EdFrozenRowsChange
@ -61,37 +61,37 @@ object Form1: TForm1
end end
object Label1: TLabel object Label1: TLabel
Left = 304 Left = 304
Height = 20 Height = 15
Top = 13 Top = 13
Width = 77 Width = 62
Caption = 'Frozen cols:' Caption = 'Frozen cols:'
FocusControl = EdFrozenCols FocusControl = EdFrozenCols
ParentColor = False ParentColor = False
end end
object Label2: TLabel object Label2: TLabel
Left = 304 Left = 304
Height = 20 Height = 15
Top = 40 Top = 40
Width = 82 Width = 66
Caption = 'Frozen rows:' Caption = 'Frozen rows:'
FocusControl = EdFrozenRows FocusControl = EdFrozenRows
ParentColor = False ParentColor = False
end end
object CbReadFormulas: TCheckBox object CbReadFormulas: TCheckBox
Left = 8 Left = 8
Height = 24 Height = 19
Top = 56 Top = 56
Width = 120 Width = 96
Caption = 'Read formulas' Caption = 'Read formulas'
OnChange = CbReadFormulasChange OnChange = CbReadFormulasChange
TabOrder = 4 TabOrder = 4
end end
object CbHeaderStyle: TComboBox object CbHeaderStyle: TComboBox
Left = 152 Left = 152
Height = 28 Height = 23
Top = 8 Top = 8
Width = 116 Width = 116
ItemHeight = 20 ItemHeight = 15
ItemIndex = 2 ItemIndex = 2
Items.Strings = ( Items.Strings = (
'Lazarus' 'Lazarus'
@ -106,7 +106,7 @@ object Form1: TForm1
end end
object PageControl1: TPageControl object PageControl1: TPageControl
Left = 0 Left = 0
Height = 460 Height = 465
Top = 79 Top = 79
Width = 884 Width = 884
ActivePage = TabSheet1 ActivePage = TabSheet1
@ -116,11 +116,11 @@ object Form1: TForm1
OnChange = PageControl1Change OnChange = PageControl1Change
object TabSheet1: TTabSheet object TabSheet1: TTabSheet
Caption = 'Sheet1' Caption = 'Sheet1'
ClientHeight = 427 ClientHeight = 437
ClientWidth = 876 ClientWidth = 876
object WorksheetGrid: TsWorksheetGrid object WorksheetGrid: TsWorksheetGrid
Left = 0 Left = 0
Height = 427 Height = 437
Top = 0 Top = 0
Width = 876 Width = 876
FrozenCols = 0 FrozenCols = 0
@ -136,7 +136,7 @@ object Form1: TForm1
TitleStyle = tsNative TitleStyle = tsNative
OnSelection = WorksheetGridSelection OnSelection = WorksheetGridSelection
ColWidths = ( ColWidths = (
56 42
64 64
64 64
64 64
@ -244,19 +244,19 @@ object Form1: TForm1
end end
object FontComboBox: TComboBox object FontComboBox: TComboBox
Left = 52 Left = 52
Height = 28 Height = 23
Top = 2 Top = 2
Width = 127 Width = 127
ItemHeight = 20 ItemHeight = 15
OnSelect = FontComboBoxSelect OnSelect = FontComboBoxSelect
TabOrder = 0 TabOrder = 0
end end
object FontSizeComboBox: TComboBox object FontSizeComboBox: TComboBox
Left = 179 Left = 179
Height = 28 Height = 23
Top = 2 Top = 2
Width = 48 Width = 48
ItemHeight = 20 ItemHeight = 15
Items.Strings = ( Items.Strings = (
'8' '8'
'9' '9'
@ -2455,70 +2455,70 @@ object Form1: TForm1
OnExecute = AcNumFormatExecute OnExecute = AcNumFormatExecute
end end
object AcNFFmtDateTimeDM: TAction object AcNFFmtDateTimeDM: TAction
Tag = 1111 Tag = 1181
Category = 'Format' Category = 'Format'
AutoCheck = True AutoCheck = True
Caption = 'Day + month' Caption = 'Day + month'
OnExecute = AcNumFormatExecute OnExecute = AcNumFormatExecute
end end
object AcNFFmtDateTimeMY: TAction object AcNFFmtDateTimeMY: TAction
Tag = 1112 Tag = 1182
Category = 'Format' Category = 'Format'
AutoCheck = True AutoCheck = True
Caption = 'Month + year' Caption = 'Month + year'
OnExecute = AcNumFormatExecute OnExecute = AcNumFormatExecute
end end
object AcNFLongDate: TAction object AcNFLongDate: TAction
Tag = 1130 Tag = 1120
Category = 'Format' Category = 'Format'
AutoCheck = True AutoCheck = True
Caption = 'Long date' Caption = 'Long date'
OnExecute = AcNumFormatExecute OnExecute = AcNumFormatExecute
end end
object AcNFShortTime: TAction object AcNFShortTime: TAction
Tag = 1140 Tag = 1130
Category = 'Format' Category = 'Format'
AutoCheck = True AutoCheck = True
Caption = 'Short time' Caption = 'Short time'
OnExecute = AcNumFormatExecute OnExecute = AcNumFormatExecute
end end
object AcNFLongTime: TAction object AcNFLongTime: TAction
Tag = 1150 Tag = 1140
Category = 'Format' Category = 'Format'
AutoCheck = True AutoCheck = True
Caption = 'Long time' Caption = 'Long time'
OnExecute = AcNumFormatExecute OnExecute = AcNumFormatExecute
end end
object AcNFShortTimeAM: TAction object AcNFShortTimeAM: TAction
Tag = 1160 Tag = 1150
Category = 'Format' Category = 'Format'
AutoCheck = True AutoCheck = True
Caption = 'Short time AM/PM' Caption = 'Short time AM/PM'
OnExecute = AcNumFormatExecute OnExecute = AcNumFormatExecute
end end
object AcNFLongTimeAM: TAction object AcNFLongTimeAM: TAction
Tag = 1170 Tag = 1160
Category = 'Format' Category = 'Format'
AutoCheck = True AutoCheck = True
Caption = 'Long time AM/PM' Caption = 'Long time AM/PM'
OnExecute = AcNumFormatExecute OnExecute = AcNumFormatExecute
end end
object AcNFFmtDateTimeMS: TAction object AcNFFmtDateTimeMS: TAction
Tag = 1113 Tag = 1183
Category = 'Format' Category = 'Format'
AutoCheck = True AutoCheck = True
Caption = 'Minutes + seconds' Caption = 'Minutes + seconds'
OnExecute = AcNumFormatExecute OnExecute = AcNumFormatExecute
end end
object AcNFFmtDateTimeMSZ: TAction object AcNFFmtDateTimeMSZ: TAction
Tag = 1114 Tag = 1184
Category = 'Format' Category = 'Format'
AutoCheck = True AutoCheck = True
Caption = 'Minutes + seconds + milliseconds' Caption = 'Minutes + seconds + milliseconds'
OnExecute = AcNumFormatExecute OnExecute = AcNumFormatExecute
end end
object AcNFTimeInterval: TAction object AcNFTimeInterval: TAction
Tag = 1180 Tag = 1170
Category = 'Format' Category = 'Format'
AutoCheck = True AutoCheck = True
Caption = 'Time interval' Caption = 'Time interval'

View File

@ -271,7 +271,7 @@ var
implementation implementation
uses uses
fpcanvas, fpsutils; fpcanvas, fpsutils, fpsnumformatparser;
const const
HORALIGN_TAG = 100; HORALIGN_TAG = 100;
@ -455,17 +455,25 @@ procedure TForm1.AcIncDecDecimalsExecute(Sender: TObject);
var var
cell: PCell; cell: PCell;
decs: Byte; decs: Byte;
parser: TsNumFormatParser;
begin begin
with WorksheetGrid do begin with WorksheetGrid do begin
if Workbook = nil then if Workbook = nil then
exit; exit;
cell := Worksheet.FindCell(GetWorksheetRow(Row), GetWorksheetCol(Col)); cell := Worksheet.FindCell(GetWorksheetRow(Row), GetWorksheetCol(Col));
if (cell <> nil) then begin if (cell <> nil) then begin
decs := cell^.Decimals; parser := TsNumFormatParser.Create(Workbook, cell^.NumberFormatStr);
try
decs := parser.Decimals;
if (Sender = AcIncDecimals) then if (Sender = AcIncDecimals) then
Worksheet.WriteDecimals(cell, decs+1); Parser.Decimals := decs+1;
if (Sender = AcDecDecimals) and (decs > 0) then if (Sender = AcDecDecimals) and (decs > 0) then
Worksheet.WriteDecimals(cell, decs-1); Parser.Decimals := decs-1;
cell^.NumberFormatStr := parser.FormatString[nfdDefault];
finally
parser.Free;
end;
Invalidate;
end; end;
end; end;
end; end;
@ -477,23 +485,20 @@ end;
procedure TForm1.AcNumFormatExecute(Sender: TObject); procedure TForm1.AcNumFormatExecute(Sender: TObject);
const const
DATETIME_FMT: array[0..4] of string = ('', 'dm', 'my', 'ms', 'msz'); DATETIME_CUSTOM: array[0..4] of string = ('', 'dm', 'my', 'ms', 'msz');
var var
nf: TsNumberFormat; nf: TsNumberFormat;
c, r: Cardinal; c, r: Cardinal;
cell: PCell; cell: PCell;
fmt: String; fmt: String;
begin begin
if WorksheetGrid.Worksheet = nil then
exit;
if TAction(Sender).Checked then if TAction(Sender).Checked then
nf := TsNumberFormat((TAction(Sender).Tag - NUMFMT_TAG) div 10) nf := TsNumberFormat((TAction(Sender).Tag - NUMFMT_TAG) div 10)
else else
nf := nfGeneral; nf := nfGeneral;
if nf = nfFmtDateTime then if nf = nfCustom then
fmt := DATETIME_FMT[TAction(Sender).Tag mod 10] fmt := DATETIME_CUSTOM[TAction(Sender).Tag mod 10]
else else
fmt := ''; fmt := '';
@ -508,17 +513,22 @@ begin
Worksheet.WriteDateTime(cell, cell^.DateTimeValue, nf, fmt) Worksheet.WriteDateTime(cell, cell^.DateTimeValue, nf, fmt)
else else
Worksheet.WriteDateTime(cell, cell^.NumberValue, nf, fmt); Worksheet.WriteDateTime(cell, cell^.NumberValue, nf, fmt);
end else
if IsCurrencyFormat(nf) then begin
if IsDateTimeFormat(cell^.NumberFormat) then
Worksheet.WriteCurrency(cell, cell^.DateTimeValue, nf, fmt)
else
Worksheet.WriteCurrency(cell, cell^.Numbervalue, nf, fmt);
end else begin end else begin
if IsDateTimeFormat(cell^.NumberFormat) then if IsDateTimeFormat(cell^.NumberFormat) then
Worksheet.WriteNumber(cell, cell^.DateTimeValue, nf, cell^.Decimals, cell^.CurrencySymbol) Worksheet.WriteNumber(cell, cell^.DateTimeValue, nf, fmt)
else else
Worksheet.WriteNumber(cell, cell^.NumberValue, nf, cell^.Decimals, cell^.CurrencySymbol); Worksheet.WriteNumber(cell, cell^.NumberValue, nf, fmt)
end; end;
else else
Worksheet.WriteNumberformat(cell, nf, fmt); Worksheet.WriteNumberformat(cell, nf, fmt);
end; end;
end; end;
UpdateNumFormatActions; UpdateNumFormatActions;
end; end;
@ -872,14 +882,12 @@ begin
t := ac.Tag; t := ac.Tag;
if (ac.Tag >= NUMFMT_TAG) and (ac.Tag < NUMFMT_TAG + 200) then begin if (ac.Tag >= NUMFMT_TAG) and (ac.Tag < NUMFMT_TAG + 200) then begin
found := ((ac.Tag - NUMFMT_TAG) div 10 = ord(nf)); found := ((ac.Tag - NUMFMT_TAG) div 10 = ord(nf));
if (nf = nfFmtDateTime) then if nf = nfCustom then
case (ac.Tag - NUMFMT_TAG) mod 10 of case (ac.Tag - NUMFMT_TAG) mod 10 of
1: found := pos('d/m', cell^.NumberFormatStr) > 0; 1: found := cell^.NumberFormatStr = 'dd/mmm';
2: found := pos('m/y', cell^.NumberFormatStr) > 0; 2: found := cell^.NumberFormatStr = 'mmm/yy';
3: found := (pos('n:s', cell^.NumberFormatStr) > 0) 3: found := cell^.NumberFormatStr = 'nn:ss';
and (pos ('.z', cell^.NumberFormatStr) = 0); 4: found := cell^.NumberFormatStr = 'nn:ss.z';
4: found := (pos('n:s', cell^.NumberFormatStr) > 0)
and (pos ('.z', cell^.NumberFormatStr) > 0);
end; end;
ac.Checked := found; ac.Checked := found;
end; end;

File diff suppressed because it is too large Load Diff

View File

@ -452,8 +452,6 @@ begin
Include(ACell^.UsedFormattingFields, uffNumberFormat); Include(ACell^.UsedFormattingFields, uffNumberFormat);
ACell^.NumberFormat := numFmtData.NumFormat; ACell^.NumberFormat := numFmtData.NumFormat;
ACell^.NumberFormatStr := numFmtData.FormatString; ACell^.NumberFormatStr := numFmtData.FormatString;
ACell^.Decimals := numFmtData.Decimals;
ACell^.CurrencySymbol := numFmtData.CurrencySymbol;
end; end;
end; end;
end; end;
@ -1074,7 +1072,7 @@ procedure TsSpreadOpenDocReader.ReadNumFormats(AStylesNode: TDOMNode);
if negfmt <> '' then AFormatStr := AFormatStr + ';' + negfmt; if negfmt <> '' then AFormatStr := AFormatStr + ';' + negfmt;
if zerofmt <> '' then AFormatStr := AFormatStr + ';' + zerofmt; if zerofmt <> '' then AFormatStr := AFormatStr + ';' + zerofmt;
if ANumFormat <> nfFmtDateTime then // if ANumFormat <> nfFmtDateTime then
ANumFormat := nfCustom; ANumFormat := nfCustom;
end; end;
@ -1141,7 +1139,7 @@ procedure TsSpreadOpenDocReader.ReadNumFormats(AStylesNode: TDOMNode);
else if ANumFormatNode.NodeName = 'number:currency-style' then else if ANumFormatNode.NodeName = 'number:currency-style' then
nf := nfCurrency; nf := nfCurrency;
NumFormatList.AddFormat(ANumFormatName, fmt, nf, decs, cs); NumFormatList.AddFormat(ANumFormatName, fmt, nf);
end; end;
procedure ReadDateTimeStyle(ANumFormatNode: TDOMNode; ANumFormatName: String); procedure ReadDateTimeStyle(ANumFormatNode: TDOMNode; ANumFormatName: String);
@ -1233,7 +1231,8 @@ procedure TsSpreadOpenDocReader.ReadNumFormats(AStylesNode: TDOMNode);
node := node.NextSibling; node := node.NextSibling;
end; end;
nf := IfThen(isInterval, nfTimeInterval, nfFmtDateTime); // nf := IfThen(isInterval, nfTimeInterval, nfFmtDateTime);
nf := IfThen(isInterval, nfTimeInterval, nfCustom);
node := ANumFormatNode.FindNode('style:map'); node := ANumFormatNode.FindNode('style:map');
if node <> nil then if node <> nil then
ReadStyleMap(node, nf, fmt); ReadStyleMap(node, nf, fmt);
@ -1271,9 +1270,11 @@ procedure TsSpreadOpenDocReader.ReadNumFormats(AStylesNode: TDOMNode);
node := ANumFormatNode.FindNode('style:map'); node := ANumFormatNode.FindNode('style:map');
if node <> nil then if node <> nil then
ReadStyleMap(node, nf, fmt); ReadStyleMap(node, nf, fmt);
{
if IsDateTimeFormat(fmt) then if IsDateTimeFormat(fmt) then
nf := nfFmtDateTime nf := nfFmtDateTime
else else
}
nf := nfCustom; nf := nfCustom;
NumFormatList.AddFormat(ANumFormatName, fmt, nf); NumFormatList.AddFormat(ANumFormatName, fmt, nf);

View File

@ -174,11 +174,46 @@ type
// currency // currency
nfCurrency, nfCurrencyRed, nfAccounting, nfAccountingRed, nfCurrency, nfCurrencyRed, nfAccounting, nfAccountingRed,
// dates and times // dates and times
nfShortDateTime, nfFmtDateTime, nfShortDate, nfLongDate, nfShortTime, nfLongTime, nfShortDateTime, {nfFmtDateTime, }nfShortDate, nfLongDate, nfShortTime, nfLongTime,
nfShortTimeAM, nfLongTimeAM, nfTimeInterval, nfShortTimeAM, nfLongTimeAM, nfTimeInterval,
// other (format string goes directly into the file) // other (format string goes directly into the file)
nfCustom); nfCustom);
const
{ @@ Codes for curreny format according to FormatSettings.CurrencyFormat:
"C" = currency symbol, "V" = currency value, "S" = space character
For the negative value formats, we use also:
"B" = bracket, "M" = Minus
The order of these characters represents the order of these items.
Example: 1000 dollars --> "$1000" for pCV, or "1000 $" for pVsC
-1000 dollars --> "($1000)" for nbCVb, or "-$ 1000" for nMCSV
Assignment taken from "sysstr.inc" }
pcfDefault = -1; // use value from Worksheet.FormatSettings.CurrencyFormat
pcfCV = 0; // $1000
pcfVC = 1; // 1000$
pcfCSV = 2; // $ 1000
pcfVSC = 3; // 1000 $
ncfDefault = -1; // use value from Worksheet.FormatSettings.NegCurrFormat
ncfBCVB = 0; // ($1000)
ncfMCV = 1; // -$1000
ncfCMV = 2; // $-1000
ncfCVM = 3; // $1000-
ncfBVCB = 4; // (1000$)
ccfMVC = 5; // -1000$
ncfVMC = 6; // 1000-$
ncfVCM = 7; // 1000$-
ncfMVSC = 8; // -1000 $
ncfMCSV = 9; // -$ 1000
ncfVSCM = 10; // 1000 $-
ncfCSVM = 11; // $ 1000-
ncfCSMV = 12; // $ -1000
ncfVMSC = 13; // 1000- $
ncfBCSVB = 14; // ($ 1000)
ncfBVSCB = 15; // (1000 $)
type
{@@ Text rotation formatting. The text is rotated relative to the standard {@@ Text rotation formatting. The text is rotated relative to the standard
orientation, which is from left to right horizontal: orientation, which is from left to right horizontal:
---> --->
@ -328,8 +363,6 @@ type
BackgroundColor: TsColor; BackgroundColor: TsColor;
NumberFormat: TsNumberFormat; NumberFormat: TsNumberFormat;
NumberFormatStr: String; NumberFormatStr: String;
Decimals: Byte;
CurrencySymbol: String;
RGBBackgroundColor: TFPColor; // only valid if BackgroundColor=scRGBCOLOR RGBBackgroundColor: TFPColor; // only valid if BackgroundColor=scRGBCOLOR
end; end;
@ -396,22 +429,9 @@ type
{ Utils } { Utils }
class function CellPosToText(ARow, ACol: Cardinal): string; class function CellPosToText(ARow, ACol: Cardinal): string;
procedure RemoveAllCells;
{ Data manipulation methods - For Cells } { Reading of values }
procedure CopyCell(AFromRow, AFromCol, AToRow, AToCol: Cardinal; AFromWorksheet: TsWorksheet);
procedure CopyFormat(AFormat: PCell; AToRow, AToCol: Cardinal); overload;
procedure CopyFormat(AFromCell, AToCell: PCell); overload;
function FindCell(ARow, ACol: Cardinal): PCell;
function GetCell(ARow, ACol: Cardinal): PCell;
function GetCellCount: Cardinal;
function GetFirstCell(): PCell;
function GetNextCell(): PCell;
function GetFirstCellOfRow(ARow: Cardinal): PCell;
function GetLastCellOfRow(ARow: Cardinal): PCell;
function GetLastColIndex: Cardinal;
function GetLastColNumber: Cardinal; deprecated 'Use GetLastColIndex';
function GetLastRowIndex: Cardinal;
function GetLastRowNumber: Cardinal; deprecated 'Use GetLastRowIndex';
function ReadAsUTF8Text(ARow, ACol: Cardinal): ansistring; overload; function ReadAsUTF8Text(ARow, ACol: Cardinal): ansistring; overload;
function ReadAsUTF8Text(ACell: PCell): ansistring; overload; function ReadAsUTF8Text(ACell: PCell): ansistring; overload;
function ReadAsNumber(ARow, ACol: Cardinal): Double; function ReadAsNumber(ARow, ACol: Cardinal): Double;
@ -421,25 +441,46 @@ type
function ReadUsedFormatting(ARow, ACol: Cardinal): TsUsedFormattingFields; function ReadUsedFormatting(ARow, ACol: Cardinal): TsUsedFormattingFields;
function ReadBackgroundColor(ARow, ACol: Cardinal): TsColor; function ReadBackgroundColor(ARow, ACol: Cardinal): TsColor;
procedure RemoveAllCells;
{ Writing of values } { Writing of values }
procedure WriteBlank(ARow, ACol: Cardinal); procedure WriteBlank(ARow, ACol: Cardinal);
procedure WriteBoolValue(ARow, ACol: Cardinal; AValue: Boolean); procedure WriteBoolValue(ARow, ACol: Cardinal; AValue: Boolean);
procedure WriteCurrency(ARow, ACol: Cardinal; AValue: Double;
AFormat: TsNumberFormat = nfCurrency; ADecimals: Integer = 2;
ACurrencySymbol: String = '?'; APosCurrFormat: Integer = -1;
ANegCurrFormat: Integer = -1); overload;
procedure WriteCurrency(ACell: PCell; AValue: Double;
AFormat: TsNumberFormat = nfCurrency; ADecimals: Integer = -1;
ACurrencySymbol: String = '?'; APosCurrFormat: Integer = -1;
ANegCurrFormat: Integer = -1); overload;
procedure WriteCurrency(ARow, ACol: Cardinal; AValue: Double;
AFormat: TsNumberFormat; AFormatString: String); overload;
procedure WriteCurrency(ACell: PCell; AValue: Double;
AFormat: TsNumberFormat; AFormatString: String); overload;
procedure WriteDateTime(ARow, ACol: Cardinal; AValue: TDateTime; procedure WriteDateTime(ARow, ACol: Cardinal; AValue: TDateTime;
AFormat: TsNumberFormat = nfGeneral; AFormatStr: String = ''); overload; AFormat: TsNumberFormat = nfShortDateTime; AFormatStr: String = ''); overload;
procedure WriteDateTime(ACell: PCell; AValue: TDateTime; procedure WriteDateTime(ACell: PCell; AValue: TDateTime;
AFormat: TsNumberFormat = nfGeneral; AFormatStr: String = ''); overload; AFormat: TsNumberFormat = nfShortDateTime; AFormatStr: String = ''); overload;
procedure WriteDateTime(ARow, ACol: Cardinal; AValue: TDateTime;
AFormatStr: String); overload;
procedure WriteDateTime(ACell: PCell; AValue: TDateTime;
AFormatStr: String); overload;
procedure WriteErrorValue(ARow, ACol: Cardinal; AValue: TsErrorValue); overload; procedure WriteErrorValue(ARow, ACol: Cardinal; AValue: TsErrorValue); overload;
procedure WriteErrorValue(ACell: PCell; AValue: TsErrorValue); overload; procedure WriteErrorValue(ACell: PCell; AValue: TsErrorValue); overload;
procedure WriteFormula(ARow, ACol: Cardinal; AFormula: TsFormula); procedure WriteFormula(ARow, ACol: Cardinal; AFormula: TsFormula);
procedure WriteNumber(ARow, ACol: Cardinal; ANumber: double; procedure WriteNumber(ARow, ACol: Cardinal; ANumber: double;
AFormat: TsNumberFormat = nfGeneral; ADecimals: Byte = 2; AFormat: TsNumberFormat = nfGeneral; ADecimals: Byte = 2;
ACurrencySymbol: String = ''); overload; ACurrencySymbol: String = ''); overload;
procedure WriteNumber(ACell: PCell; ANumber: Double; AFormat: TsNumberFormat = nfGeneral; procedure WriteNumber(ACell: PCell; ANumber: Double; AFormat: TsNumberFormat = nfGeneral;
ADecimals: Byte = 2; ACurrencySymbol: String = ''); overload; ADecimals: Byte = 2; ACurrencySymbol: String = ''); overload;
procedure WriteNumber(ARow, ACol: Cardinal; ANumber: double; procedure WriteNumber(ARow, ACol: Cardinal; ANumber: double;
AFormatString: String); overload; AFormat: TsNumberFormat; AFormatString: String); overload;
procedure WriteNumber(ACell: PCell; ANumber: Double;
AFormat: TsNumberFormat; AFormatString: String); overload;
procedure WriteRPNFormula(ARow, ACol: Cardinal; AFormula: TsRPNFormula); procedure WriteRPNFormula(ARow, ACol: Cardinal; AFormula: TsRPNFormula);
procedure WriteUTF8Text(ARow, ACol: Cardinal; AText: ansistring); overload; procedure WriteUTF8Text(ARow, ACol: Cardinal; AText: ansistring); overload;
procedure WriteUTF8Text(ACell: PCell; AText: ansistring); overload; procedure WriteUTF8Text(ACell: PCell; AText: ansistring); overload;
@ -483,6 +524,22 @@ type
procedure WriteWordwrap(ARow, ACol: Cardinal; AValue: boolean); procedure WriteWordwrap(ARow, ACol: Cardinal; AValue: boolean);
{ Data manipulation methods - For Cells }
procedure CopyCell(AFromRow, AFromCol, AToRow, AToCol: Cardinal; AFromWorksheet: TsWorksheet);
procedure CopyFormat(AFormat: PCell; AToRow, AToCol: Cardinal); overload;
procedure CopyFormat(AFromCell, AToCell: PCell); overload;
function FindCell(ARow, ACol: Cardinal): PCell;
function GetCell(ARow, ACol: Cardinal): PCell;
function GetCellCount: Cardinal;
function GetFirstCell(): PCell;
function GetNextCell(): PCell;
function GetFirstCellOfRow(ARow: Cardinal): PCell;
function GetLastCellOfRow(ARow: Cardinal): PCell;
function GetLastColIndex: Cardinal;
function GetLastColNumber: Cardinal; deprecated 'Use GetLastColIndex';
function GetLastRowIndex: Cardinal;
function GetLastRowNumber: Cardinal; deprecated 'Use GetLastRowIndex';
{ Data manipulation methods - For Rows and Cols } { Data manipulation methods - For Rows and Cols }
function FindRow(ARow: Cardinal): PRow; function FindRow(ARow: Cardinal): PRow;
function FindCol(ACol: Cardinal): PCol; function FindCol(ACol: Cardinal): PCol;
@ -506,7 +563,7 @@ type
property Rows: TIndexedAVLTree read FRows; property Rows: TIndexedAVLTree read FRows;
property Workbook: TsWorkbook read FWorkbook; property Workbook: TsWorkbook read FWorkbook;
// These are properties to interface to fpspreadsheetgrid. // These are properties to interface to TsWorksheetGrid
property Options: TsSheetOptions read FOptions write FOptions; property Options: TsSheetOptions read FOptions write FOptions;
property LeftPaneWidth: Integer read FLeftPaneWidth write FLeftPaneWidth; property LeftPaneWidth: Integer read FLeftPaneWidth write FLeftPaneWidth;
property TopPaneHeight: Integer read FTopPaneHeight write FTopPaneHeight; property TopPaneHeight: Integer read FTopPaneHeight write FTopPaneHeight;
@ -608,8 +665,6 @@ type
Index: Integer; Index: Integer;
Name: String; Name: String;
NumFormat: TsNumberFormat; NumFormat: TsNumberFormat;
Decimals: Byte;
CurrencySymbol: String;
FormatString: string; FormatString: string;
end; end;
@ -630,27 +685,21 @@ type
destructor Destroy; override; destructor Destroy; override;
function AddFormat(AFormatCell: PCell): Integer; overload; function AddFormat(AFormatCell: PCell): Integer; overload;
function AddFormat(AFormatIndex: Integer; AFormatName, AFormatString: String; function AddFormat(AFormatIndex: Integer; AFormatName, AFormatString: String;
ANumFormat: TsNumberFormat; ADecimals: Byte = 0; ANumFormat: TsNumberFormat): Integer; overload;
ACurrencySymbol: String = ''): Integer; overload;
function AddFormat(AFormatIndex: Integer; AFormatString: String; function AddFormat(AFormatIndex: Integer; AFormatString: String;
ANumFormat: TsNumberFormat; ADecimals: Byte = 0; ANumFormat: TsNumberFormat): Integer; overload;
ACurrencySymbol: String = ''): Integer; overload;
function AddFormat(AFormatName, AFormatString: String; function AddFormat(AFormatName, AFormatString: String;
ANumFormat: TsNumberFormat; ADecimals: Byte = 0; ANumFormat: TsNumberFormat): Integer; overload;
ACurrencySymbol: String = ''): Integer; overload; function AddFormat(AFormatString: String; ANumFormat: TsNumberFormat): 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; procedure ConvertAfterReading(AFormatIndex: Integer; var AFormatString: String;
var ANumFormat: TsNumberFormat; var ADecimals: Byte; var ANumFormat: TsNumberFormat); virtual;
var ACurrencySymbol: String); virtual;
procedure ConvertBeforeWriting(var AFormatString: String; procedure ConvertBeforeWriting(var AFormatString: String;
var ANumFormat: TsNumberFormat; var ADecimals: Byte; var ANumFormat: TsNumberFormat; var ADecimals: Byte;
var ACurrencySymbol: String); virtual; var ACurrencySymbol: String); virtual;
procedure Delete(AIndex: Integer); procedure Delete(AIndex: Integer);
function Find(ANumFormat: TsNumberFormat; AFormatString: String; function Find(ANumFormat: TsNumberFormat; AFormatString: String): Integer; overload;
ADecimals: Byte; ACurrencySymbol: String): Integer; overload;
function Find(AFormatString: String): Integer; overload; function Find(AFormatString: String): Integer; overload;
function FindByIndex(AFormatIndex: Integer): Integer; function FindByIndex(AFormatIndex: Integer): Integer;
function FindByName(AFormatName: String): Integer; function FindByName(AFormatName: String): Integer;
@ -829,6 +878,7 @@ resourcestring
lpUnknownSpreadsheetFormat = 'unknown format'; lpUnknownSpreadsheetFormat = 'unknown format';
lpInvalidFontIndex = 'Invalid font index'; lpInvalidFontIndex = 'Invalid font index';
lpInvalidNumberFormat = 'Trying to use an incompatible number format.'; lpInvalidNumberFormat = 'Trying to use an incompatible number format.';
lpInvalidDateTimeFormat = 'Trying to use an incompatible date/time 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.';
lpNoValidCellAddress = '"%s" is not a valid cell address.'; lpNoValidCellAddress = '"%s" is not a valid cell address.';
@ -1129,8 +1179,6 @@ begin
AToCell^.TextRotation := AFromCell^.TextRotation; AToCell^.TextRotation := AFromCell^.TextRotation;
AToCell^.NumberFormat := AFromCell^.NumberFormat; AToCell^.NumberFormat := AFromCell^.NumberFormat;
AToCell^.NumberFormatStr := AFromCell^.NumberFormatStr; AToCell^.NumberFormatStr := AFromCell^.NumberFormatStr;
AToCell^.Decimals := AFromCell^.Decimals;
AToCell^.CurrencySymbol := AFromCell^.CurrencySymbol;
end; end;
@ -1321,7 +1369,6 @@ begin
Result^.Col := ACol; Result^.Col := ACol;
Result^.ContentType := cctEmpty; Result^.ContentType := cctEmpty;
Result^.BorderStyles := DEFAULT_BORDERSTYLES; Result^.BorderStyles := DEFAULT_BORDERSTYLES;
Result^.CurrencySymbol := '?';
Cells.Add(Result); Cells.Add(Result);
end; end;
@ -1503,7 +1550,7 @@ end;
function TsWorksheet.ReadAsUTF8Text(ACell: PCell): ansistring; function TsWorksheet.ReadAsUTF8Text(ACell: PCell): ansistring;
function FloatToStrNoNaN(const Value: Double; function FloatToStrNoNaN(const Value: Double;
ANumberFormat: TsNumberFormat; ANumberFormatStr: string; ADecimals: byte): ansistring; ANumberFormat: TsNumberFormat; ANumberFormatStr: string): ansistring;
var var
fs: TFormatSettings; fs: TFormatSettings;
left, right: String; left, right: String;
@ -1513,12 +1560,12 @@ function TsWorksheet.ReadAsUTF8Text(ACell: PCell): ansistring;
if IsNan(Value) then if IsNan(Value) then
Result := '' Result := ''
else else
if ANumberFormat = nfSci then
Result := SciFloat(Value, ADecimals)
else
if (ANumberFormat = nfGeneral) or (ANumberFormatStr = '') then if (ANumberFormat = nfGeneral) or (ANumberFormatStr = '') then
Result := FloatToStr(Value, fs) Result := FloatToStr(Value, fs)
else else
if ANumberFormat = nfSci then
Result := SciFloat(Value, CountDecs(ANumberFormatStr, ['0']), fs)
else
if (ANumberFormat = nfPercentage) then if (ANumberFormat = nfPercentage) then
Result := FormatFloat(ANumberFormatStr, Value*100, fs) Result := FormatFloat(ANumberFormatStr, Value*100, fs)
else else
@ -1533,7 +1580,7 @@ function TsWorksheet.ReadAsUTF8Text(ACell: PCell): ansistring;
end; end;
function DateTimeToStrNoNaN(const Value: Double; function DateTimeToStrNoNaN(const Value: Double;
ANumberFormat: TsNumberFormat; ANumberFormatStr: String; ADecimals: Word): ansistring; ANumberFormat: TsNumberFormat; ANumberFormatStr: String): ansistring;
var var
fmtp, fmtn, fmt0: String; fmtp, fmtn, fmt0: String;
begin begin
@ -1564,11 +1611,11 @@ begin
with ACell^ do with ACell^ do
case ContentType of case ContentType of
cctNumber: cctNumber:
Result := FloatToStrNoNaN(NumberValue, NumberFormat, NumberFormatStr, Decimals); Result := FloatToStrNoNaN(NumberValue, NumberFormat, NumberFormatStr);
cctUTF8String: cctUTF8String:
Result := UTF8StringValue; Result := UTF8StringValue;
cctDateTime: cctDateTime:
Result := DateTimeToStrNoNaN(DateTimeValue, NumberFormat, NumberFormatStr, Decimals); Result := DateTimeToStrNoNaN(DateTimeValue, NumberFormat, NumberFormatStr);
cctBool: cctBool:
Result := IfThen(BoolValue, lpTRUE, lpFALSE); Result := IfThen(BoolValue, lpTRUE, lpFALSE);
cctError: cctError:
@ -1845,33 +1892,22 @@ begin
WriteNumber(GetCell(ARow, ACol), ANumber, AFormat, ADecimals, ACurrencySymbol); WriteNumber(GetCell(ARow, ACol), ANumber, AFormat, ADecimals, ACurrencySymbol);
end; end;
procedure TsWorksheet.WriteNumber(ACell: PCell; ANumber: Double; procedure TsWorksheet.WriteNumber(ACell: PCell; ANumber: Double;
AFormat: TsNumberFormat = nfGeneral; ADecimals: Byte = 2; AFormat: TsNumberFormat = nfGeneral; ADecimals: Byte = 2;
ACurrencySymbol: String = ''); ACurrencySymbol: String = '');
var
fs: TFormatSettings;
begin begin
if IsDateTimeFormat(AFormat) or IsCurrencyFormat(AFormat) then
raise Exception.Create(lpInvalidNumberFormat);
if ACell <> nil then begin if ACell <> nil then begin
ACell^.ContentType := cctNumber; ACell^.ContentType := cctNumber;
ACell^.NumberValue := ANumber; ACell^.NumberValue := ANumber;
ACell^.Decimals := ADecimals;
if IsDateTimeFormat(AFormat) then
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;
ACell^.Decimals := ADecimals;
ACell^.CurrencySymbol := ACurrencySymbol;
ACell^.NumberFormatStr := BuildNumberFormatString(ACell^.NumberFormat, ACell^.NumberFormatStr := BuildNumberFormatString(ACell^.NumberFormat,
Workbook.FormatSettings, ADecimals, ACurrencySymbol); Workbook.FormatSettings, ADecimals);
end else begin end else begin
Exclude(ACell^.UsedFormattingFields, uffNumberFormat); Exclude(ACell^.UsedFormattingFields, uffNumberFormat);
ACell^.NumberFormat := nfGeneral; ACell^.NumberFormat := nfGeneral;
@ -1888,36 +1924,41 @@ end;
NOTE that fpspreadsheet may not be able to detect the formatting when reading NOTE that fpspreadsheet may not be able to detect the formatting when reading
the file. } the file. }
procedure TsWorksheet.WriteNumber(ARow, ACol: Cardinal; ANumber: Double; procedure TsWorksheet.WriteNumber(ARow, ACol: Cardinal; ANumber: Double;
AFormatString: String); AFormat: TsNumberFormat; AFormatString: String);
var var
ACell: PCell; ACell: PCell;
parser: TsNumFormatParser;
nf: TsNumberFormat;
begin begin
parser := TsNumFormatParser.Create(Workbook, AFormatString, nfCustom, cdToFPSpreadsheet); WriteNumber(GetCell(ARow, ACol), ANumber, AFormat, AFormatString);
end;
procedure TsWorksheet.WriteNumber(ACell: PCell; ANumber: Double;
AFormat: TsNumberFormat; AFormatString: String);
var
parser: TsNumFormatParser;
begin
if ACell <> nil then begin
parser := TsNumFormatParser.Create(Workbook, AFormatString);
try try
// Format string ok? // Format string ok?
if parser.Status <> psOK then if parser.Status <> psOK then
raise Exception.Create(lpNoValidNumberFormatString); raise Exception.Create(lpNoValidNumberFormatString);
if IsDateTimeFormat(parser.Builtin_NumFormat) // Make sure that we do not write a date/time value here
if parser.IsDateTimeFormat
then raise Exception.Create(lpInvalidNumberFormat); then raise Exception.Create(lpInvalidNumberFormat);
// If format string matches a built-in format use its format identifier, // If format string matches a built-in format use its format identifier,
// All this is considered when calling Builtin_NumFormat of the parser. // All this is considered when calling Builtin_NumFormat of the parser.
nf := parser.Builtin_NumFormat;
finally finally
parser.Free; parser.Free;
end; end;
ACell := GetCell(ARow, ACol);
Include(ACell^.UsedFormattingFields, uffNumberFormat); Include(ACell^.UsedFormattingFields, uffNumberFormat);
ACell^.ContentType := cctNumber; ACell^.ContentType := cctNumber;
ACell^.NumberValue := ANumber; ACell^.NumberValue := ANumber;
ACell^.NumberFormat := nf; ACell^.NumberFormat := AFormat; //nfCustom;
ACell^.NumberFormatStr := AFormatString; ACell^.NumberFormatStr := AFormatString;
ACell^.Decimals := 0;
ACell^.CurrencySymbol := '';
ChangedCell(ARow, ACol); ChangedCell(ACell^.Row, ACell^.Col);
end;
end; end;
{@@ {@@
@ -1954,6 +1995,72 @@ begin
ChangedCell(ARow, ACol); ChangedCell(ARow, ACol);
end; end;
{@@
Writes a currency value to a given cell. Its number format can be provided
optionally by specifying these parameters:
- ADecimals: number of decimals
- APosCurrFormat: code specifying the order of value, currency symbol and spaces
(see pcfXXXX constants above)
- ANegCurrFormat: code specifying the order of value, currency symbol, spaces
and how negative values are shown (see ncfXXXX constants above)
- ACurrencySymbol: the string to be shown as currency, such as '$', or 'EUR'
}
procedure TsWorksheet.WriteCurrency(ARow, ACol: Cardinal; AValue: Double;
AFormat: TsNumberFormat = nfCurrency; ADecimals: Integer = 2;
ACurrencySymbol: String = '?'; APosCurrFormat: Integer = -1;
ANegCurrFormat: Integer = -1);
begin
WriteCurrency(GetCell(ARow, ACol), AValue, AFormat, ADecimals, ACurrencySymbol,
APosCurrFormat, ANegCurrFormat);
end;
procedure TsWorksheet.WriteCurrency(ACell: PCell; AValue: Double;
AFormat: TsNumberFormat = nfCurrency; ADecimals: Integer = -1;
ACurrencySymbol: String = '?'; APosCurrFormat: Integer = -1;
ANegCurrFormat: Integer = -1);
var
fmt: String;
begin
if ADecimals = -1 then
ADecimals := Workbook.FormatSettings.CurrencyDecimals;
if APosCurrFormat = -1 then
APosCurrFormat := Workbook.FormatSettings.CurrencyFormat;
if ANegCurrFormat = -1 then
ANegCurrFormat := Workbook.FormatSettings.NegCurrFormat;
if ACurrencySymbol = '?' then
ACurrencySymbol := Workbook.FormatSettings.CurrencyString;
fmt := BuildCurrencyFormatString(
Workbook.FormatSettings,
ADecimals,
APosCurrFormat, ANegCurrFormat,
AFormat in [nfCurrencyRed, nfAccountingRed],
AFormat in [nfAccounting, nfAccountingRed],
ACurrencySymbol);
WriteCurrency(ACell, AValue, AFormat, fmt);
end;
procedure TsWorksheet.WriteCurrency(ARow, ACol: Cardinal; AValue: Double;
AFormat: TsNumberFormat; AFormatString: String);
begin
WriteCurrency(GetCell(ARow, ACol), AValue, AFormat, AFormatString);
end;
procedure TsWorksheet.WriteCurrency(ACell: PCell; AValue: Double;
AFormat: TsNumberFormat; AFormatString: String);
begin
if (ACell <> nil) and IsCurrencyFormat(AFormat) then begin
Include(ACell^.UsedFormattingFields, uffNumberFormat);
ACell^.ContentType := cctNumber;
ACell^.NumberValue := AValue;
ACell^.NumberFormat := AFormat;
ACell^.NumberFormatStr := AFormatString;
ChangedCell(ACell^.Row, ACell^.Col);
end;
end;
{@@ {@@
Writes a date/time value to a determined cell Writes a date/time value to a determined cell
@ -1962,24 +2069,23 @@ end;
@param AValue The date/time/datetime to be written @param AValue The date/time/datetime to be written
@param AFormat The format specifier, e.g. nfShortDate (optional) @param AFormat The format specifier, e.g. nfShortDate (optional)
If not specified format is not changed. If not specified format is not changed.
@param AFormatStr Format string, used only for nfFmtDateTime. @param AFormatStr Format string, used only for nfCustom or nfTimeInterval.
Must follow the rules for "FormatDateTime", or use
"dm" as abbreviation for "d/mmm", "my" for "mmm/yy",
"ms" for "nn:ss", "msz" for "nn:ss.z" (optional)
or use any other free format (at your own risk...)
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).
} }
procedure TsWorksheet.WriteDateTime(ARow, ACol: Cardinal; AValue: TDateTime; procedure TsWorksheet.WriteDateTime(ARow, ACol: Cardinal; AValue: TDateTime;
AFormat: TsNumberFormat = nfGeneral; AFormatStr: String = ''); AFormat: TsNumberFormat = nfShortDateTime; AFormatStr: String = '');
begin begin
WriteDateTime(GetCell(ARow, ACol), AValue, AFormat, AFormatStr); WriteDateTime(GetCell(ARow, ACol), AValue, AFormat, AFormatStr);
end; end;
procedure TsWorksheet.WriteDateTime(ACell: PCell; AValue: TDateTime; procedure TsWorksheet.WriteDateTime(ACell: PCell; AValue: TDateTime;
AFormat: TsNumberFormat = nfGeneral; AFormatStr: String = ''); AFormat: TsNumberFormat = nfShortDateTime; AFormatStr: String = '');
var
parser: TsNumFormatParser;
nf: TsNumberFormat;
begin begin
if ACell <> nil then begin if ACell <> nil then begin
ACell^.ContentType := cctDateTime; ACell^.ContentType := cctDateTime;
@ -1988,10 +2094,28 @@ begin
// Date/time is actually a number field in Excel. // Date/time is actually a number field in Excel.
// To make sure it gets saved correctly, set a date format (instead of General). // To make sure it gets saved correctly, set a date format (instead of General).
// The user can choose another date format if he wants to // The user can choose another date format if he wants to
if IsDateTimeFormat(AFormat) then
if (AFormat in [nfFmtDateTime, nfTimeInterval]) then if AFormatStr = '' then
AFormatStr := BuildDateTimeFormatString(AFormat, Workbook.FormatSettings, AFormatStr); AFormatStr := BuildDateTimeFormatString(AFormat, Workbook.FormatSettings, AFormatStr);
// Check whether the formatstring is for date/times.
if AFormatStr <> '' then begin
parser := TsNumFormatParser.Create(Workbook, AFormatStr);
try
// Format string ok?
if parser.Status <> psOK then
raise Exception.Create(lpNoValidNumberFormatString);
// Make sure that we do not use a number format for date/times values.
if not parser.IsDateTimeFormat
then raise Exception.Create(lpInvalidDateTimeFormat);
// Avoid possible duplication of standard formats
if AFormat = nfCustom then
AFormat := parser.NumFormat;
finally
parser.Free;
end;
end;
Include(ACell^.UsedFormattingFields, uffNumberFormat); Include(ACell^.UsedFormattingFields, uffNumberFormat);
ACell^.NumberFormat := AFormat; ACell^.NumberFormat := AFormat;
ACell^.NumberFormatStr := AFormatStr; ACell^.NumberFormatStr := AFormatStr;
@ -1999,24 +2123,42 @@ begin
end; end;
end; end;
procedure TsWorksheet.WriteDateTime(ARow, ACol: Cardinal; AValue: TDateTime;
AFormatStr: String);
begin
WriteDateTime(GetCell(ARow, ACol), AValue, AFormatStr);
end;
procedure TsWorksheet.WriteDateTime(ACell: PCell; AValue: TDateTime;
AFormatStr: String);
begin
WriteDateTime(ACell, AValue, nfCustom, AFormatStr);
end;
procedure TsWorksheet.WriteDecimals(ARow, ACol: Cardinal; ADecimals: Byte); procedure TsWorksheet.WriteDecimals(ARow, ACol: Cardinal; ADecimals: Byte);
begin begin
WriteDecimals(FindCell(ARow, ACol), ADecimals); WriteDecimals(FindCell(ARow, ACol), ADecimals);
end; end;
procedure TsWorksheet.WriteDecimals(ACell: PCell; ADecimals: Byte); procedure TsWorksheet.WriteDecimals(ACell: PCell; ADecimals: Byte);
var
parser: TsNumFormatParser;
begin begin
if (ACell <> nil) and (ACell^.ContentType = cctNumber) and (ACell^.NumberFormat <> nfCustom) if (ACell <> nil) and (ACell^.ContentType = cctNumber) and (ACell^.NumberFormat <> nfCustom)
then begin then begin
ACell^.Decimals := ADecimals; parser := TsNumFormatParser.Create(Workbook, ACell^.NumberFormatStr);
ACell^.NumberFormatStr := BuildNumberFormatString(ACell^.NumberFormat, try
FWorkbook.FormatSettings, ADecimals, ACell^.CurrencySymbol); parser.Decimals := ADecimals;
ACell^.NumberFormatStr := parser.FormatString[nfdDefault];
finally
parser.Free;
end;
ChangedCell(ACell^.Row, ACell^.Col); ChangedCell(ACell^.Row, ACell^.Col);
end; end;
end; end;
{@@ {@@
Writes a cell with an error. Writes a cell with an error value.
@param ARow The row of the cell @param ARow The row of the cell
@param ACol The column of the cell @param ACol The column of the cell
@ -2037,7 +2179,7 @@ begin
end; end;
{@@ {@@
Writes a formula to a determined cell Writes a formula to a given cell
@param ARow The row of the cell @param ARow The row of the cell
@param ACol The column of the cell @param ACol The column of the cell
@ -2080,8 +2222,7 @@ begin
Include(ACell^.UsedFormattingFields, uffNumberFormat); Include(ACell^.UsedFormattingFields, uffNumberFormat);
ACell^.NumberFormat := ANumberFormat; ACell^.NumberFormat := ANumberFormat;
if (AFormatString = '') then if (AFormatString = '') then
ACell^.NumberFormatStr := BuildNumberFormatString(ANumberFormat, ACell^.NumberFormatStr := BuildNumberFormatString(ANumberFormat, Workbook.FormatSettings)
Workbook.FormatSettings, ACell^.Decimals, ACell^.CurrencySymbol)
else else
ACell^.NumberFormatStr := AFormatString; ACell^.NumberFormatStr := AFormatString;
ChangedCell(ACell^.Row, ACell^.Col); ChangedCell(ACell^.Row, ACell^.Col);
@ -2521,6 +2662,8 @@ begin
FDefaultColWidth := 12; FDefaultColWidth := 12;
FDefaultRowHeight := 1; FDefaultRowHeight := 1;
FormatSettings := DefaultFormatSettings; FormatSettings := DefaultFormatSettings;
FormatSettings.ShortDateFormat := MakeShortDateFormat(FormatSettings.ShortDateFormat);
FormatSettings.LongDateFormat := MakeLongDateFormat(FormatSettings.ShortDateFormat);
FFontList := TFPList.Create; FFontList := TFPList.Create;
SetDefaultFont('Arial', 10.0); SetDefaultFont('Arial', 10.0);
InitFonts; InitFonts;
@ -3194,8 +3337,7 @@ 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 item. } new item. }
function TsCustomNumFormatList.AddFormat(AFormatIndex: Integer; function TsCustomNumFormatList.AddFormat(AFormatIndex: Integer;
AFormatName, AFormatString: String; ANumFormat: TsNumberFormat; AFormatName, AFormatString: String; ANumFormat: TsNumberFormat): Integer;
ADecimals: Byte = 0; ACurrencySymbol: String = ''): Integer;
var var
item: TsNumFormatData; item: TsNumFormatData;
begin begin
@ -3203,45 +3345,31 @@ begin
item.Index := AFormatIndex; item.Index := AFormatIndex;
item.Name := AFormatName; item.Name := AFormatName;
item.NumFormat := ANumFormat; item.NumFormat := ANumFormat;
if AFormatString = '' then begin
if IsDateTimeFormat(ANumFormat) then
AFormatString := BuildDateTimeFormatString(ANumFormat, Workbook.FormatSettings,
AFormatString)
else
AFormatString := BuildNumberFormatString(ANumFormat, Workbook.FormatSettings,
ADecimals, ACurrencySymbol);
end;
item.FormatString := AFormatString; item.FormatString := AFormatString;
item.Decimals := ADecimals;
item.CurrencySymbol := ACurrencySymbol;
Result := inherited Add(item); Result := inherited Add(item);
end; end;
function TsCustomNumFormatList.AddFormat(AFormatIndex: Integer; function TsCustomNumFormatList.AddFormat(AFormatIndex: Integer;
AFormatString: String; ANumFormat: TsNumberFormat; ADecimals: byte = 0; AFormatString: String; ANumFormat: TsNumberFormat): integer;
ACurrencySymbol: String = ''): integer;
begin begin
Result := AddFormat(AFormatIndex, '', AFormatString, ANumFormat, ADecimals, ACurrencySymbol); Result := AddFormat(AFormatIndex, '', AFormatString, ANumFormat);
end; end;
function TsCustomNumFormatList.AddFormat(AFormatName, AFormatString: String; function TsCustomNumFormatList.AddFormat(AFormatName, AFormatString: String;
ANumFormat: TsNumberFormat; ADecimals: Byte = 0; ANumFormat: TsNumberFormat): Integer;
ACurrencySymbol: String = ''): Integer;
begin begin
if (AFormatString = '') and (ANumFormat <> nfGeneral) then begin if (AFormatString = '') and (ANumFormat <> nfGeneral) then begin
Result := 0; Result := 0;
exit; exit;
end; end;
Result := AddFormat(FNextFormatIndex, AFormatName, AFormatString, ANumFormat, Result := AddFormat(FNextFormatIndex, AFormatName, AFormatString, ANumFormat);
ADecimals, ACurrencySymbol);
inc(FNextFormatIndex); inc(FNextFormatIndex);
end; end;
function TsCustomNumFormatList.AddFormat(AFormatString: String; function TsCustomNumFormatList.AddFormat(AFormatString: String;
ANumFormat: TsNumberFormat; ADecimals: Byte = 0; ANumFormat: TsNumberFormat): Integer;
ACurrencySymbol: String = ''): Integer;
begin begin
Result := AddFormat('', AFormatString, ANumFormat, ADecimals, ACurrencySymbol); Result := AddFormat('', AFormatString, ANumFormat);
end; end;
function TsCustomNumFormatList.AddFormat(AFormatCell: PCell): Integer; function TsCustomNumFormatList.AddFormat(AFormatCell: PCell): Integer;
@ -3256,9 +3384,7 @@ begin
Result := AddFormat(FNextFormatIndex, Result := AddFormat(FNextFormatIndex,
AFormatCell^.NumberFormatStr, AFormatCell^.NumberFormatStr,
AFormatCell^.NumberFormat, AFormatCell^.NumberFormat
AFormatCell^.Decimals,
AFormatCell^.CurrencySymbol
); );
inc(FNextFormatIndex); inc(FNextFormatIndex);
@ -3283,8 +3409,7 @@ end;
overridden method which known more about the details of the spreadsheet file overridden method which known more about the details of the spreadsheet file
format. } format. }
procedure TsCustomNumFormatList.ConvertAfterReading(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 var
parser: TsNumFormatParser; parser: TsNumFormatParser;
fmt: String; fmt: String;
@ -3302,44 +3427,27 @@ begin
nf := nfGeneral; nf := nfGeneral;
// Analyzes the format string and tries to convert it to fpSpreadsheet format. // Analyzes the format string and tries to convert it to fpSpreadsheet format.
parser := TsNumFormatParser.Create(Workbook, fmt, nf, cdToFPSpreadsheet); 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.NumFormat;
AFormatString := parser.FormatString; // This is the converted string. AFormatString := parser.FormatString[nfdDefault];
if ANumFormat <> nfCustom then begin
ADecimals := parser.ParsedSections[0].Decimals;
ACurrencySymbol := parser.ParsedSections[0].CurrencySymbol;
end else begin end else begin
ADecimals := 0; // Show an error here?
ACurrencySymbol := '';
end;
end; end;
finally finally
parser.Free; parser.Free;
end; end;
end; end;
{ Is called before collection all number formats of the spreadsheet and before { Is called before collecting all number formats of the spreadsheet and before
writing to file. Its purpose is to convert the format string as used by fpc writing to file. Its purpose is to convert the format string as used by fpc
to a format compatible with the spreadsheet file format. } to a format compatible with the spreadsheet file format.
Nothing is changed here. The method needs to be overridden. }
procedure TsCustomNumFormatList.ConvertBeforeWriting(var AFormatString: String; procedure TsCustomNumFormatList.ConvertBeforeWriting(var AFormatString: String;
var ANumFormat: TsNumberFormat; var ADecimals: Byte; var ACurrencySymbol: String); var ANumFormat: TsNumberFormat; var ADecimals: Byte; var ACurrencySymbol: String);
var
parser: TsNumFormatParser;
fmt: String;
begin begin
parser := TsNumFormatParser.Create(Workbook, AFormatString, ANumFormat, cdFromFPSpreadsheet); // nothing to do here. But see, e.g., xlscommon.TsBIFFNumFormatList
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;
finally
parser.Free;
end;
end; end;
{ Called from the reader when a format item has been read from the file. { Called from the reader when a format item has been read from the file.
@ -3357,10 +3465,10 @@ begin
exit; exit;
// Analyze & convert the format string, extract infos for internal formatting // Analyze & convert the format string, extract infos for internal formatting
ConvertAfterReading(AFormatIndex, AFormatString, nf, decs, currsym); ConvertAfterReading(AFormatIndex, AFormatString, nf);
// Add the new item // Add the new item
AddFormat(AFormatIndex, AFormatString, nf, decs, currSym); AddFormat(AFormatIndex, AFormatString, nf);
end; end;
{ Clears the list and frees memory occupied by the format items. } { Clears the list and frees memory occupied by the format items. }
@ -3383,65 +3491,14 @@ end;
{ Seeks a format item with the given properties and returns its list index, { Seeks a format item with the given properties and returns its list index,
or -1 if not found. } or -1 if not found. }
function TsCustomNumFormatList.Find(ANumFormat: TsNumberFormat; function TsCustomNumFormatList.Find(ANumFormat: TsNumberFormat;
AFormatString: String; ADecimals: Byte; ACurrencySymbol: String): Integer; AFormatString: String): Integer;
var var
item: TsNumFormatData; item: TsNumFormatData;
fmt: String;
itemfmt: String;
begin begin
if (ANumFormat = nfFmtDateTime) then begin
fmt := lowercase(AFormatString);
for Result := Count-1 downto 0 do begin for Result := Count-1 downto 0 do begin
item := Items[Result]; item := Items[Result];
if (item <> nil) and (item.NumFormat = nfFmtDateTime) then begin if (item <> nil) and (item.NumFormat = ANumFormat) and (item.FormatString = AFormatString)
itemfmt := lowercase(item.FormatString); then exit;
if ((itemfmt = 'dm') or (itemfmt = 'd-mmm') or (itemfmt = 'd mmm') or (itemfmt = 'd. mmm') or (itemfmt ='d/mmm'))
and ((fmt = 'dm') or (fmt = 'd-mmm') or (fmt = 'd mmm') or (fmt = 'd. mmm') or (fmt = 'd/mmm'))
then
exit;
if ((itemfmt = 'my') or (itemfmt = 'mmm-yy') or (itemfmt = 'mmm yyy') or (itemfmt = 'mmm/yy'))
and ((fmt = 'my') or (fmt = 'mmm-yy') or (fmt = 'mmm yy') or (fmt = 'mmm/yy'))
then
exit;
if ((itemfmt = 'ms') or (itemfmt = 'nn:ss') or (itemfmt = 'mm:ss'))
and ((fmt = 'ms') or (fmt = 'nn:ss') or (fmt = 'mm:ss'))
then
exit;
if ((itemfmt = 'msz') or (itemfmt = 'mm:ss.z') or (itemfmt = 'mm:ss.0'))
and ((fmt = 'msz') or (fmt = 'mm:ss.z') or (fmt = 'mm:ss.0'))
then
exit;
end;
end;
for Result := 0 to Count-1 do begin
item := Items[Result];
if fmt = lowercase(item.FormatString) then
exit;
end;
end;
// Check only the format string for nfCustom.
if (ANumFormat = nfCustom) then
for Result := Count-1 downto 0 do begin
item := Items[Result];
if (item <> nil)
and (item.NumFormat = ANumFormat)
and (item.FormatString = AFormatString)
then
exit;
end;
// The other formats can carry additional information
for Result := Count-1 downto 0 do begin
item := Items[Result];
if (item <> nil)
and (item.NumFormat = ANumFormat)
and (item.FormatString = AFormatString)
and (item.Decimals = ADecimals)
and (not (item.NumFormat in [nfCurrency, nfCurrencyRed, nfAccounting, nfAccountingRed])
or (item.CurrencySymbol = ACurrencySymbol))
then
exit;
end; end;
Result := -1; Result := -1;
end; end;
@ -3499,19 +3556,25 @@ begin
if AFormatCell = nil then if AFormatCell = nil then
Result := -1 Result := -1
else else
Result := Find(AFormatCell^.NumberFormat, AFormatCell^.NumberFormatStr, Result := Find(AFormatCell^.NumberFormat, AFormatCell^.NumberFormatStr);
AFormatCell^.Decimals, AFormatCell^.CurrencySymbol);
end; end;
{ Determines the format string to be written into the spreadsheet file. { Determines the format string to be written into the spreadsheet file. Calls
Needs to be overridden if the format strings are different from the fpc ConvertBeforeWriting in order to convert the fpc format strings to the dialect
convention. } used in the file. }
function TsCustomNumFormatList.FormatStringForWriting(AIndex: Integer): String; function TsCustomNumFormatList.FormatStringForWriting(AIndex: Integer): String;
var var
item: TsNumFormatdata; item: TsNumFormatdata;
nf: TsNumberFormat;
decs: Byte;
cs: String;
begin begin
item := Items[AIndex]; item := Items[AIndex];
if item <> nil then Result := item.FormatString else Result := ''; if item <> nil then begin
Result := item.FormatString;
ConvertBeforeWriting(Result, nf, decs, cs);
end else
Result := '';
end; end;
function TsCustomNumFormatList.GetItem(AIndex: Integer): TsNumFormatData; function TsCustomNumFormatList.GetItem(AIndex: Integer): TsNumFormatData;
@ -3641,14 +3704,14 @@ end;
} }
function TsCustomSpreadWriter.FindFormattingInList(AFormat: PCell): Integer; function TsCustomSpreadWriter.FindFormattingInList(AFormat: PCell): Integer;
var var
i: Integer; i, n: Integer;
b: TsCellBorder; b: TsCellBorder;
equ: Boolean; equ: Boolean;
begin begin
Result := -1; Result := -1;
for i := Length(FFormattingStyles) - 1 downto 0 do n := Length(FFormattingStyles);
begin for i := n - 1 downto 0 do begin
if (FFormattingStyles[i].UsedFormattingFields <> AFormat^.UsedFormattingFields) then Continue; if (FFormattingStyles[i].UsedFormattingFields <> AFormat^.UsedFormattingFields) then Continue;
if uffHorAlign in AFormat^.UsedFormattingFields then if uffHorAlign in AFormat^.UsedFormattingFields then
@ -3683,18 +3746,7 @@ begin
if uffNumberFormat in AFormat^.UsedFormattingFields then begin if uffNumberFormat in AFormat^.UsedFormattingFields then begin
if (FFormattingStyles[i].NumberFormat <> AFormat^.NumberFormat) then Continue; if (FFormattingStyles[i].NumberFormat <> AFormat^.NumberFormat) then Continue;
case AFormat^.NumberFormat of if (FFormattingStyles[i].NumberFormatStr <> AFormat^.NumberFormatStr) then Continue;
nfFixed, nfFixedTh, nfPercentage, nfExp, nfSci:
if (FFormattingStyles[i].Decimals <> AFormat^.Decimals) then Continue;
nfCurrency, nfCurrencyRed, nfAccounting, nfAccountingRed:
begin
if (FFormattingStyles[i].Decimals <> AFormat^.Decimals) then Continue;
if (FFormattingStyles[i].CurrencySymbol <> AFormat^.CurrencySymbol) then Continue;
end;
nfShortDateTime, nfShortDate, nfLongDate, nfShortTime, nfLongTime,
nfShortTimeAM, nfLongTimeAM, nfFmtDateTime, nfTimeInterval, nfCustom:
if (FFormattingstyles[i].NumberFormatStr <> AFormat^.NumberFormatStr) then Continue;
end;
end; end;
if uffFont in AFormat^.UsedFormattingFields then if uffFont in AFormat^.UsedFormattingFields then
@ -3740,23 +3792,6 @@ begin
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 // 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. // the "row" of the style. Will be needed when writing the XF record.
FFormattingStyles[Len].Row := NextXFIndex; FFormattingStyles[Len].Row := NextXFIndex;
@ -3776,6 +3811,7 @@ begin
for i := 0 to Workbook.GetWorksheetCount - 1 do for i := 0 to Workbook.GetWorksheetCount - 1 do
IterateThroughCells(nil, Workbook.GetWorksheetByIndex(i).Cells, ListAllFormattingStylesCallback); IterateThroughCells(nil, Workbook.GetWorksheetByIndex(i).Cells, ListAllFormattingStylesCallback);
(*
// Convert the numberformats of the collected styles to be compatible with the destination file // Convert the numberformats of the collected styles to be compatible with the destination file
for i:=0 to High(FFormattingStyles) do for i:=0 to High(FFormattingStyles) do
if (FFormattingStyles[i].NumberFormatStr <> '') and if (FFormattingStyles[i].NumberFormatStr <> '') and
@ -3787,6 +3823,7 @@ begin
FFormattingStyles[i].Decimals, FFormattingStyles[i].Decimals,
FFormattingStyles[i].CurrencySymbol FFormattingStyles[i].CurrencySymbol
); );
*)
end; end;
{@@ {@@
@ -3797,37 +3834,25 @@ procedure TsCustomSpreadWriter.ListAllNumFormatsCallback(ACell: PCell; AStream:
var var
fmt: string; fmt: string;
nf: TsNumberFormat; nf: TsNumberFormat;
decs: Byte;
cs: String;
begin begin
if ACell^.NumberFormat = nfGeneral then if ACell^.NumberFormat = nfGeneral then
exit; exit;
// The builtin format list is in "file syntax", but the format string of the // The builtin format list is in fpc dialect.
// cells are in "fpc syntax". Therefore, before seeking, we have to convert
// the format string of the cell to "file syntax".
fmt := ACell^.NumberFormatStr; fmt := ACell^.NumberFormatStr;
nf := ACell^.NumberFormat; 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. // Seek the format string in the current number format list.
// If not found add the format to the list. // If not found add the format to the list.
if FNumFormatList.Find(fmt) = -1 then if FNumFormatList.Find(nf, fmt) = -1 then
FNumFormatList.AddFormat(fmt, nf, decs, cs); FNumFormatList.AddFormat(fmt, nf);
end; end;
{@@ {@@
Iterats through all cells and collects the number formats in Iterates through all cells and collects the number formats in
FNumFormatList (without duplicates). FNumFormatList (without duplicates).
The index of the list item is needed for the field FormatIndex of the XF record. } The index of the list item is needed for the field FormatIndex of the XF record.
At the time when the method is called the formats are still in fpc dialect. }
procedure TsCustomSpreadWriter.ListAllNumFormats; procedure TsCustomSpreadWriter.ListAllNumFormats;
var var
i: Integer; i: Integer;

View File

@ -70,22 +70,23 @@ function IfThen(ACondition: Boolean; AValue1,AValue2: TsNumberFormat): TsNumberF
function IsCurrencyFormat(AFormat: TsNumberFormat): Boolean; function IsCurrencyFormat(AFormat: TsNumberFormat): Boolean;
function IsDateTimeFormat(AFormat: TsNumberFormat): Boolean; overload; function IsDateTimeFormat(AFormat: TsNumberFormat): Boolean; overload;
function IsDateTimeFormat(AFormatStr: String): Boolean; overload; //function IsDateTimeFormat(AFormatStr: String): Boolean; overload;
function BuildNumberFormatString(ANumberFormat: TsNumberFormat; function BuildCurrencyFormatString(const AFormatSettings: TFormatSettings;
const AFormatSettings: TFormatSettings; ADecimals: Integer = -1; ADecimals, APosCurrFormat, ANegCurrFormat: Integer;
ACurrencySymbol: String = '?'): String; ANegativeValuesRed, AAccountingStyle: Boolean; ACurrencySymbol: String = '?'): String;
function BuildDateTimeFormatString(ANumberFormat: TsNumberFormat; function BuildDateTimeFormatString(ANumberFormat: TsNumberFormat;
const AFormatSettings: TFormatSettings; AFormatString: String = ''): String; const AFormatSettings: TFormatSettings; AFormatString: String = ''): String;
function BuildCurrencyFormatString(const AFormatSettings: TFormatSettings; function BuildNumberFormatString(ANumberFormat: TsNumberFormat;
ADecimals: Integer; ANegativeValuesRed: Boolean; AAccountingStyle: Boolean; const AFormatSettings: TFormatSettings; ADecimals: Integer = -1): String;
ACurrencySymbol: String = '?'): String;
function AddAMPM(const ATimeFormatString: String; function AddAMPM(const ATimeFormatString: String;
const AFormatSettings: TFormatSettings): String; const AFormatSettings: TFormatSettings): 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 AddIntervalBrackets(AFormatString: String): String;
function MakeLongDateFormat(AShortDateFormat: String): String;
function MakeShortDateFormat(AShortDateFormat: String): String;
function SpecialDateTimeFormat(ACode: String; function SpecialDateTimeFormat(ACode: String;
const AFormatSettings: TFormatSettings; ForWriting: Boolean): String; const AFormatSettings: TFormatSettings; ForWriting: Boolean): String;
function SplitAccountingFormatString(const AFormatString: String; ASection: ShortInt; function SplitAccountingFormatString(const AFormatString: String; ASection: ShortInt;
@ -93,7 +94,8 @@ function SplitAccountingFormatString(const AFormatString: String; ASection: Shor
procedure SplitFormatString(const AFormatString: String; out APositivePart, procedure SplitFormatString(const AFormatString: String; out APositivePart,
ANegativePart, AZeroPart: String); ANegativePart, AZeroPart: String);
function SciFloat(AValue: Double; ADecimals: Byte): String; function SciFloat(AValue: Double; ADecimals: Byte): String; overload;
function SciFloat(AValue: Double; ADecimals: Byte; AFormatSettings: TFormatSettings): String; overload;
//function TimeIntervalToString(AValue: TDateTime; AFormatStr: String): String; //function TimeIntervalToString(AValue: TDateTime; AFormatStr: String): String;
procedure MakeTimeIntervalMask(Src: String; var Dest: String); procedure MakeTimeIntervalMask(Src: String; var Dest: String);
@ -563,53 +565,10 @@ end;
{ Checks whether the given number format code is for date/times. } { Checks whether the given number format code is for date/times. }
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;
function IsDateTimeFormat(AFormatStr: string): Boolean;
var
P, PStart, PEnd: PChar;
token: Char;
begin
if AFormatStr = '' then
Result := false
else begin
PStart := PChar(@AFormatStr[1]);
PEnd := PStart + Length(AFormatStr);
P := PStart;
while P < PEnd do begin
token := P^;
case token of // Skip quoted text
'"': begin
inc(P);
token := P^;
while (P < PEnd) and (token <> '"') do begin
inc(P);
token := P^;
end;
end;
{
'[': begin
inc(P);
token := P^;
while (P < PEnd) and (token <> ']') do begin
inc(P);
token := P^;
end;
end;
}
'y', 'Y', 'm', 'M', 'd', 'D', 'h', 'H', 'n', 'N', 's', 'S', ':':
begin
Result := true;
exit;
end;
end;
inc(P);
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
abbreviations "dm" (for "d/mmm"), "my" (for "mmm/yy"), "ms" (for "mm:ss") abbreviations "dm" (for "d/mmm"), "my" (for "mmm/yy"), "ms" (for "mm:ss")
@ -620,8 +579,10 @@ var
fmt: String; fmt: String;
begin begin
case ANumberFormat of case ANumberFormat of
{
nfFmtDateTime: nfFmtDateTime:
Result := SpecialDateTimeFormat(lowercase(AFormatString), AFormatSettings, false); Result := SpecialDateTimeFormat(lowercase(AFormatString), AFormatSettings, false);
}
nfShortDateTime: nfShortDateTime:
Result := AFormatSettings.ShortDateFormat + ' ' + AFormatSettings.ShortTimeFormat; Result := AFormatSettings.ShortDateFormat + ' ' + AFormatSettings.ShortTimeFormat;
// In the DefaultFormatSettings this is: d/m/y hh:nn // In the DefaultFormatSettings this is: d/m/y hh:nn
@ -664,41 +625,44 @@ end;
This code has to be removed by StripAccountingSymbols before applying to This code has to be removed by StripAccountingSymbols before applying to
FormatFloat. } FormatFloat. }
function BuildCurrencyFormatString(const AFormatSettings: TFormatSettings; function BuildCurrencyFormatString(const AFormatSettings: TFormatSettings;
ADecimals: Integer; ANegativeValuesRed: Boolean; AAccountingStyle: Boolean; ADecimals, APosCurrFormat, ANegCurrFormat: Integer; ANegativeValuesRed: Boolean;
ACurrencySymbol: String = '?'): String; AAccountingStyle: Boolean; ACurrencySymbol: String = '?'): String;
const const
POS_FMT: array[0..3, boolean] of string = ( //0: value, 1: currency symbol POS_FMT: array[0..3, boolean] of string = (
// Parameter 0 is "value", parameter 1 is "currency symbol"
// AccountingStyle = false --> 1st column, true --> 2nd column
('"%1:s"%0:s', '"%1:s"* %0:s'), // 0: $1 ('"%1:s"%0:s', '"%1:s"* %0:s'), // 0: $1
('%0:s"%1:s"', '%0:s* "%1:s"'), // 1: 1$ ('%0:s"%1:s"', '%0:s "%1:s"'), // 1: 1$
('"%1:s" %0:s', '"%1:s"* %0:s'), // 2: $ 1 ('"%1:s" %0:s', '"%1:s"* %0:s'), // 2: $ 1
('%0:s "%1:s"', '%0:s* "%1:s"') // 3: 1 $ ('%0:s "%1:s"', '%0:s "%1:s"') // 3: 1 $
); );
NEG_FMT: array[0..15, boolean] of string = ( NEG_FMT: array[0..15, boolean] of string = (
('("%1:s"%0:s)', '"%1:s"* (%0:s)'), // 0: ($1) ('("%1:s"%0:s)', '"%1:s"* (%0:s)'), // 0: ($1)
('-"%1:s"%0:s', '"%1:s"* -%0:s'), // 1: -$1 ('-"%1:s"%0:s', '-* "%1:s" %0:s'), // 1: -$1
('"%1:s"-%0:s', '"%1:s"* -%0:s'), // 2: $-1 ('"%1:s"-%0:s', '"%1:s"* -%0:s'), // 2: $-1
('"%1:s"%0:s-', '"%1:s"* %0:s-'), // 3: $1- ('"%1:s"%0:s-', '"%1:s"%0:s-'), // 3: $1-
('(%0:s"%1:s")', '(%0:s)"%1:s"'), // 4: (1$) ('(%0:s"%1:s")', '(%0:s)%1:s"'), // 4: (1$)
('-%0:s"%1:s"', '-%0:s"%1:s"'), // 5: -1$ ('-%0:s"%1:s"', '-* %0:s"%1:s"'), // 5: -1$
('%0:s-"%1:s"', '%0:s-"%1:s"'), // 6: 1-$ ('%0:s-"%1:s"', '%0:s-"%1:s"'), // 6: 1-$
('%0:s"%1:s"-', '%0:s-"%1:s"'), // 7: 1$- ('%0:s"%1:s"-', '%0:s-"%1:s"'), // 7: 1$-
('-%0:s "%1:s"', '-%0:s"%1:s"'), // 8: -1 $ ('-%0:s "%1:s"', '-* %0:s"%1:s"'), // 8: -1 $
('-"%1:s" %0:s', '"%1:s"* -%0:s'), // 9: -$ 1 ('-"%1:s" %0:s', '-* "%1:s" -%0:s'), // 9: -$ 1
('%0:s "%1:s"-', '%0:s- "%1:s"'), // 10: 1 $- ('%0:s "%1:s"-', '%0:s- "%1:s"'), // 10: 1 $-
('"%1:s" %0:s-', '"%1:s"* %0:s-'), // 11: $ 1- ('"%1:s" %0:s-', '"%1:s"* %0:s-'), // 11: $ 1-
('"%1:s" -%0:s', '"%1:s"* -%0:s'), // 12: $ -1 ('"%1:s" -%0:s', '"%1:s"* -%0:s'), // 12: $ -1
('%0:s- "%1:s"', '%0:s- "%1:s"'), // 13: 1- $ ('%0:s- "%1:s"', '%0:s- "%1:s"'), // 13: 1- $
('("%1:s" %0:s)', '"%1:s"* (%0:s)'), // 14: ($ 1) ('("%1:s" %0:s)', '"%1:s"* (%0:s)'), // 14: ($ 1)
('(%0:s "%1:s")', '(%0:s) "%1:s"') // 15: (1 $) ('(%0:s "%1:s")', '(%0:s "%1:s")') // 15: (1 $)
); );
var var
decs: String; decs: String;
cf, ncf: Byte; cf, ncf: Byte;
p, n: String; p, n: String;
begin begin
cf := AFormatSettings.CurrencyFormat; cf := IfThen(APosCurrFormat < 0, AFormatSettings.CurrencyFormat, APosCurrFormat);
ncf := AFormatSettings.NegCurrFormat; ncf := IfThen(ANegCurrFormat < 0, AFormatSettings.NegCurrFormat, ANegCurrFormat);
if ADecimals < 0 then ADecimals := AFormatSettings.CurrencyDecimals; if ADecimals < 0 then
ADecimals := AFormatSettings.CurrencyDecimals;
if ACurrencySymbol = '?' then if ACurrencySymbol = '?' then
ACurrencySymbol := AnsiToUTF8(AFormatSettings.CurrencyString); ACurrencySymbol := AnsiToUTF8(AFormatSettings.CurrencyString);
decs := DupeString('0', ADecimals); decs := DupeString('0', ADecimals);
@ -717,7 +681,7 @@ begin
if ACurrencySymbol <> '' then begin if ACurrencySymbol <> '' then begin
Result := Format(p, ['#,##0' + decs, ACurrencySymbol]) + ';' Result := Format(p, ['#,##0' + decs, ACurrencySymbol]) + ';'
+ Format(n, ['#,##0' + decs, ACurrencySymbol]) + ';' + IfThen(ANegativeValuesRed, '[red]', '') + Format(n, ['#,##0' + decs, ACurrencySymbol]) + ';'
+ Format(p, [IfThen(AAccountingStyle, '-', '0'+decs), ACurrencySymbol]); + Format(p, [IfThen(AAccountingStyle, '-', '0'+decs), ACurrencySymbol]);
end end
else begin else begin
@ -731,21 +695,16 @@ begin
end; end;
end; end;
{ Builds a number format string from the numberformat code, the count of { Builds a number format string from the number format code, the count of
decimals, and the currencysymbol (if not empty). } decimals, and the currencysymbol (if not empty). }
function BuildNumberFormatString(ANumberFormat: TsNumberFormat; function BuildNumberFormatString(ANumberFormat: TsNumberFormat;
const AFormatSettings: TFormatSettings; ADecimals: Integer = -1; const AFormatSettings: TFormatSettings; ADecimals: Integer = -1): String;
ACurrencySymbol: String = '?'): String;
var var
decs: String; decs: String;
cf, ncf: Byte;
begin begin
Result := ''; Result := '';
cf := AFormatSettings.CurrencyFormat; if ADecimals = -1 then
ncf := AFormatSettings.NegCurrFormat; ADecimals := AFormatSettings.CurrencyDecimals;
if ADecimals = -1 then ADecimals := AFormatSettings.CurrencyDecimals;
if ACurrencySymbol = '?' then
ACurrencySymbol := AnsiToUTF8(AFormatSettings.CurrencyString);
decs := DupeString('0', ADecimals); decs := DupeString('0', ADecimals);
if ADecimals > 0 then decs := '.' + decs; if ADecimals > 0 then decs := '.' + decs;
case ANumberFormat of case ANumberFormat of
@ -760,13 +719,12 @@ begin
nfPercentage: nfPercentage:
Result := '0' + decs + '%'; Result := '0' + decs + '%';
nfCurrency, nfCurrencyRed, nfAccounting, nfAccountingRed: nfCurrency, nfCurrencyRed, nfAccounting, nfAccountingRed:
Result := BuildCurrencyFormatString( raise Exception.Create('BuildNumberFormatString: Use BuildCurrencyFormatString '+
AFormatSettings, 'to create a format string for currency values.');
ADecimals, nfShortDateTime, nfShortDate, nfLongDate, nfShortTime, nfLongTime,
ANumberFormat in [nfCurrencyRed, nfAccountingRed], nfShortTimeAM, nfLongTimeAM, nfTimeInterval:
ANumberFormat in [nfAccounting, nfAccountingRed], raise Exception.Create('BuildNumberFormatString: Use BuildDateTimeFormatSstring '+
ACurrencySymbol 'to create a format string for date/time values.');
);
end; end;
end; end;
@ -802,12 +760,18 @@ var
i: Integer; i: Integer;
begin begin
Result := 0; Result := 0;
for i:=Length(AFormatString) downto 1 do begin i := 1;
if AFormatString[i] in ADecChars then inc(Result); while (i <= Length(AFormatString)) do begin
if AFormatString[i] = '.' then exit; if AFormatString[i] = '.' then begin
inc(i);
while (i <= Length(AFormatString)) and (AFormatString[i] in ADecChars) do begin
inc(i);
inc(Result);
end;
exit;
end else
inc(i);
end; end;
// Comes to this point when there is no decimal separtor.
Result := 0;
end; end;
{ The given format string is assumed to be for time intervals, i.e. its first { The given format string is assumed to be for time intervals, i.e. its first
@ -831,6 +795,64 @@ begin
end; end;
end; end;
{ Creates a long date format string out of a short one. Retains the order of
year-month-day and the separators, but uses 4 digits for year and 3 digits of m }
function MakeLongDateFormat(AShortDateFormat: String): String;
var
i: Integer;
begin
Result := '';
i := 1;
while i < Length(AShortDateFormat) do begin
case AShortDateFormat[i] of
'y', 'Y':
begin
Result := Result + DupeString(AShortDateFormat[i], 4);
while (i < Length(AShortDateFormat)) and (AShortDateFormat[i] in ['y','Y']) do
inc(i);
end;
'm', 'M':
begin
result := Result + DupeString(AShortDateFormat[i], 3);
while (i < Length(AShortDateFormat)) and (AShortDateFormat[i] in ['m','M']) do
inc(i);
end;
else
Result := Result + AShortDateFormat[i];
inc(i);
end;
end;
end;
{ Modifies the short date format such that it has a two-digit year and a two-digit
month. Retains the order of year-month-day and the separators. }
function MakeShortDateFormat(AShortDateFormat: String): String;
var
i: Integer;
begin
Result := '';
i := 1;
while i < Length(AShortDateFormat) do begin
case AShortDateFormat[i] of
'y', 'Y':
begin
Result := Result + DupeString(AShortDateFormat[i], 2);
while (i < Length(AShortDateFormat)) and (AShortDateFormat[i] in ['y','Y']) do
inc(i);
end;
'm', 'M':
begin
result := Result + DupeString(AShortDateFormat[i], 2);
while (i < Length(AShortDateFormat)) and (AShortDateFormat[i] in ['m','M']) do
inc(i);
end;
else
Result := Result + AShortDateFormat[i];
inc(i);
end;
end;
end;
{ Creates the formatstrings for the date/time codes "dm", "my", "ms" and "msz" { Creates the formatstrings for the date/time codes "dm", "my", "ms" and "msz"
out of the formatsettings. } out of the formatsettings. }
function SpecialDateTimeFormat(ACode: String; function SpecialDateTimeFormat(ACode: String;
@ -991,23 +1013,30 @@ 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
to multiples of 3 (like for "kilo" - "Mega" - "Giga" etc.). } to multiples of 3 (like for "kilo" - "Mega" - "Giga" etc.). }
function SciFloat(AValue: Double; ADecimals: Byte): String; function SciFloat(AValue: Double; ADecimals: Byte;
AFormatSettings: TFormatSettings): String;
var var
m: Double; m: Double;
ex: Integer; ex: Integer;
begin begin
if AValue = 0 then if AValue = 0 then
Result := '0.0' Result := Format('%0.*fE+0', [ADecimals, 0.0], AFormatSettings)
// Excel shows "000.0E+0", but I think the "0.0E+0" shown here is better.
else begin else begin
ex := floor(log10(abs(AValue))); // exponent ex := floor(log10(abs(AValue))); // exponent
// round exponent to multiples of 3 // round exponent to multiples of 3
ex := (ex div 3) * 3; ex := (ex div 3) * 3;
if ex < 0 then dec(ex, 3); if ex < 0 then dec(ex, 3);
m := AValue * Power(10, -ex); // mantisse m := AValue * Power(10, -ex); // mantisse
Result := Format('%.*fE%d', [ADecimals, m, ex]); Result := Format('%.*fE+%d', [ADecimals, m, ex], AFormatSettings);
end; end;
end; end;
function SciFloat(AValue: Double; ADecimals: Byte): String;
begin
Result := SciFloat(AValue, ADecimals, DefaultFormatSettings);
end;
{ Creates a "time interval" format string having the first code identifier { Creates a "time interval" format string having the first code identifier
in square brackets. } in square brackets. }
procedure MakeTimeIntervalMask(Src: String; var Dest: String); procedure MakeTimeIntervalMask(Src: String; var Dest: String);

View File

@ -250,27 +250,27 @@ begin
SollDates[14]:=SollDates[1]; // #1 formatted as nfLongTime SollDates[14]:=SollDates[1]; // #1 formatted as nfLongTime
SollDates[15]:=SollDates[1]; // #1 formatted as nfShortTimeAM SollDates[15]:=SollDates[1]; // #1 formatted as nfShortTimeAM
SollDates[16]:=SollDates[1]; // #1 formatted as nfLongTimeAM SollDates[16]:=SollDates[1]; // #1 formatted as nfLongTimeAM
SollDates[17]:=SollDates[1]; // #1 formatted as nfFmtDateTime dm SollDates[17]:=SollDates[1]; // #1 formatted as nfCustom dd/mmm
SollDates[18]:=SollDates[1]; // #1 formatted as nfFmtDateTime my SollDates[18]:=SollDates[1]; // #1 formatted as nfCustom mmm/yy
SollDates[19]:=SollDates[1]; // #1 formatted as nfFmtDateTime ms SollDates[19]:=SollDates[1]; // #1 formatted as nfCustom mm:ss
SollDates[20]:=SollDates[5]; // #5 formatted as nfShortDateTime SollDates[20]:=SollDates[5]; // #5 formatted as nfShortDateTime
SollDates[21]:=SollDates[5]; // #5 formatted as nfShortTime SollDates[21]:=SollDates[5]; // #5 formatted as nfShortTime
SollDates[22]:=SollDates[5]; // #5 formatted as nfLongTime SollDates[22]:=SollDates[5]; // #5 formatted as nfLongTime
SollDates[23]:=SollDates[5]; // #5 formatted as nfShortTimeAM SollDates[23]:=SollDates[5]; // #5 formatted as nfShortTimeAM
SollDates[24]:=SollDates[5]; // #5 formatted as nfLongTimeAM SollDates[24]:=SollDates[5]; // #5 formatted as nfLongTimeAM
SollDates[25]:=SollDates[5]; // #5 formatted as nfFmtDateTime dm SollDates[25]:=SollDates[5]; // #5 formatted as nfCustom dd:mmm
SollDates[26]:=SollDates[5]; // #5 formatted as nfFmtDateTime my SollDates[26]:=SollDates[5]; // #5 formatted as nfCustom mmm:yy
SollDates[27]:=SollDates[5]; // #5 formatted as nfFmtDateTime ms SollDates[27]:=SollDates[5]; // #5 formatted as nfCustom mm:ss
SollDates[28]:=SollDates[11]; // #11 formatted as nfShortDateTime SollDates[28]:=SollDates[11]; // #11 formatted as nfShortDateTime
SollDates[29]:=SollDates[11]; // #11 formatted as nfShortTime SollDates[29]:=SollDates[11]; // #11 formatted as nfShortTime
SollDates[30]:=SollDates[11]; // #11 formatted as nfLongTime SollDates[30]:=SollDates[11]; // #11 formatted as nfLongTime
SollDates[31]:=SollDates[11]; // #11 formatted as nfShortTimeAM SollDates[31]:=SollDates[11]; // #11 formatted as nfShortTimeAM
SollDates[32]:=SollDates[11]; // #11 formatted as nfLongTimeAM SollDates[32]:=SollDates[11]; // #11 formatted as nfLongTimeAM
SollDates[33]:=SollDates[11]; // #11 formatted as nfFmtDateTime dm SollDates[33]:=SollDates[11]; // #11 formatted as nfCustom dd/mmm
SollDates[34]:=SollDates[11]; // #11 formatted as nfFmtDateTime my SollDates[34]:=SollDates[11]; // #11 formatted as nfCustom mmm/yy
SollDates[35]:=SollDates[11]; // #11 formatted as nfFmtDateTime ms SollDates[35]:=SollDates[11]; // #11 formatted as nfCustom mmm:ss
SollDates[36]:=EncodeTime(3,45,12,0); // formatted as nfTimeDuration SollDates[36]:=EncodeTime(3,45,12,0); // formatted as nfTimeDuration
SollDates[37]:=EncodeTime(3,45,12,0) + 1 // formatted as nfTimeDuration SollDates[37]:=EncodeTime(3,45,12,0) + 1 // formatted as nfTimeDuration

View File

@ -137,10 +137,15 @@ procedure InitSollFmtData;
var var
i: Integer; i: Integer;
fs: TFormatSettings; fs: TFormatSettings;
myworkbook: TsWorkbook;
begin begin
// Set up norm - MUST match spreadsheet cells exactly // Set up norm - MUST match spreadsheet cells exactly
fs := DefaultFormatSettings; // The workbook uses a slightly modified copy of the DefaultFormatSettings
// We create a copy here in order to better define the predicted strings.
myWorkbook := TsWorkbook.Create;
fs := MyWorkbook.FormatSettings;
myWorkbook.Free;
// Numbers // Numbers
SollNumbers[0] := 0.0; SollNumbers[0] := 0.0;
@ -162,15 +167,15 @@ begin
SollNumberFormats[8] := nfSci; SollNumberDecimals[8] := 1; SollNumberFormats[8] := nfSci; SollNumberDecimals[8] := 1;
for i:=Low(SollNumbers) to High(SollNumbers) do begin for i:=Low(SollNumbers) to High(SollNumbers) do begin
SollNumberStrings[i, 0] := FloatToStr(SollNumbers[i]); SollNumberStrings[i, 0] := FloatToStr(SollNumbers[i], fs);
SollNumberStrings[i, 1] := FormatFloat('0', SollNumbers[i]); SollNumberStrings[i, 1] := FormatFloat('0', SollNumbers[i], fs);
SollNumberStrings[i, 2] := FormatFloat('0.00', SollNumbers[i]); SollNumberStrings[i, 2] := FormatFloat('0.00', SollNumbers[i], fs);
SollNumberStrings[i, 3] := FormatFloat('#,##0', SollNumbers[i]); SollNumberStrings[i, 3] := FormatFloat('#,##0', SollNumbers[i], fs);
SollNumberStrings[i, 4] := FormatFloat('#,##0.00', SollNumbers[i]); SollNumberStrings[i, 4] := FormatFloat('#,##0.00', SollNumbers[i], fs);
SollNumberStrings[i, 5] := FormatFloat('0.00E+00', SollNumbers[i]); SollNumberStrings[i, 5] := FormatFloat('0.00E+00', SollNumbers[i], fs);
SollNumberStrings[i, 6] := FormatFloat('0', SollNumbers[i]*100) + '%'; SollNumberStrings[i, 6] := FormatFloat('0', SollNumbers[i]*100, fs) + '%';
SollNumberStrings[i, 7] := FormatFloat('0.00', SollNumbers[i]*100) + '%'; SollNumberStrings[i, 7] := FormatFloat('0.00', SollNumbers[i]*100, fs) + '%';
SollNumberStrings[i, 8] := SciFloat(SollNumbers[i], 1); SollNumberStrings[i, 8] := SciFloat(SollNumbers[i], 1, fs);
end; end;
// Date/time values // Date/time values
@ -186,22 +191,22 @@ begin
SollDateTimeFormats[3] := nfLongTime; SollDateTimeFormatStrings[3] := ''; SollDateTimeFormats[3] := nfLongTime; SollDateTimeFormatStrings[3] := '';
SollDateTimeFormats[4] := nfShortTimeAM; SollDateTimeFormatStrings[4] := ''; SollDateTimeFormats[4] := nfShortTimeAM; SollDateTimeFormatStrings[4] := '';
SollDateTimeFormats[5] := nfLongTimeAM; SollDateTimeFormatStrings[5] := ''; SollDateTimeFormats[5] := nfLongTimeAM; SollDateTimeFormatStrings[5] := '';
SollDateTimeFormats[6] := nfFmtDateTime; SollDateTimeFormatStrings[6] := 'dm'; SollDateTimeFormats[6] := nfCustom; SollDateTimeFormatStrings[6] := 'dd/mmm';
SolLDateTimeFormats[7] := nfFmtDateTime; SollDateTimeFormatStrings[7] := 'my'; SolLDateTimeFormats[7] := nfCustom; SollDateTimeFormatStrings[7] := 'mmm/yy';
SollDateTimeFormats[8] := nfFmtDateTime; SollDateTimeFormatStrings[8] := 'ms'; SollDateTimeFormats[8] := nfCustom; SollDateTimeFormatStrings[8] := 'nn:ss';
SollDateTimeFormats[9] := nfTimeInterval; SollDateTimeFormatStrings[9] := ''; SollDateTimeFormats[9] := nfTimeInterval; SollDateTimeFormatStrings[9] := '';
for i:=Low(SollDateTimes) to High(SollDateTimes) do begin for i:=Low(SollDateTimes) to High(SollDateTimes) do begin
SollDateTimeStrings[i, 0] := DateToStr(SollDateTimes[i]) + ' ' + FormatDateTime('t', SollDateTimes[i]); SollDateTimeStrings[i, 0] := DateToStr(SollDateTimes[i], fs) + ' ' + FormatDateTime('t', SollDateTimes[i], fs);
SollDateTimeStrings[i, 1] := DateToStr(SollDateTimes[i]); SollDateTimeStrings[i, 1] := DateToStr(SollDateTimes[i], fs);
SollDateTimeStrings[i, 2] := FormatDateTime(fs.ShortTimeFormat, SollDateTimes[i]); SollDateTimeStrings[i, 2] := FormatDateTime(fs.ShortTimeFormat, SollDateTimes[i], fs);
SolLDateTimeStrings[i, 3] := FormatDateTime(fs.LongTimeFormat, SollDateTimes[i]); SolLDateTimeStrings[i, 3] := FormatDateTime(fs.LongTimeFormat, SollDateTimes[i], fs);
SollDateTimeStrings[i, 4] := FormatDateTime(fs.ShortTimeFormat + ' am/pm', SollDateTimes[i]); // dont't use "t" - it does the hours wrong SollDateTimeStrings[i, 4] := FormatDateTime(fs.ShortTimeFormat + ' am/pm', SollDateTimes[i], fs); // dont't use "t" - it does the hours wrong
SollDateTimeStrings[i, 5] := FormatDateTime(fs.LongTimeFormat + ' am/pm', SollDateTimes[i]); SollDateTimeStrings[i, 5] := FormatDateTime(fs.LongTimeFormat + ' am/pm', SollDateTimes[i], fs);
SollDateTimeStrings[i, 6] := FormatDateTime(SpecialDateTimeFormat('dm', fs, false), SollDateTimes[i]); SollDateTimeStrings[i, 6] := FormatDateTime(SpecialDateTimeFormat('dm', fs, false), SollDateTimes[i], fs);
SollDateTimeStrings[i, 7] := FormatDateTime(SpecialDateTimeFormat('my', fs, false), SollDateTimes[i]); SollDateTimeStrings[i, 7] := FormatDateTime(SpecialDateTimeFormat('my', fs, false), SollDateTimes[i], fs);
SollDateTimeStrings[i, 8] := FormatDateTime(SpecialDateTimeFormat('ms', fs, false), SollDateTimes[i]); SollDateTimeStrings[i, 8] := FormatDateTime(SpecialDateTimeFormat('ms', fs, false), SollDateTimes[i], fs);
SollDateTimeStrings[i, 9] := FormatDateTime('[h]:mm:ss', SollDateTimes[i], [fdoInterval]); SollDateTimeStrings[i, 9] := FormatDateTime('[h]:mm:ss', SollDateTimes[i], fs, [fdoInterval]);
end; end;
// Column width // Column width
@ -345,7 +350,7 @@ begin
MyWorksheet := MyWorkbook.AddWorksheet(FmtDateTimesSheet); MyWorksheet := MyWorkbook.AddWorksheet(FmtDateTimesSheet);
for Row := Low(SollDateTimes) to High(SollDateTimes) do for Row := Low(SollDateTimes) to High(SollDateTimes) do
for Col := Low(SollDateTimeFormats) to High(SollDateTimeFormats) do begin for Col := Low(SollDateTimeFormats) to High(SollDateTimeFormats) do begin
if (AFormat = sfExcel2) and (SollDateTimeFormats[Col] in [nfFmtDateTime, nfTimeInterval]) then if (AFormat = sfExcel2) and (SollDateTimeFormats[Col] in [nfCustom, nfTimeInterval]) then
Continue; // The formats nfFmtDateTime and nfTimeInterval are not supported by BIFF2 Continue; // The formats nfFmtDateTime and nfTimeInterval are not supported by BIFF2
MyWorksheet.WriteDateTime(Row, Col, SollDateTimes[Row], SollDateTimeFormats[Col], SollDateTimeFormatStrings[Col]); MyWorksheet.WriteDateTime(Row, Col, SollDateTimes[Row], SollDateTimeFormats[Col], SollDateTimeFormatStrings[Col]);
ActualString := MyWorksheet.ReadAsUTF8Text(Row, Col); ActualString := MyWorksheet.ReadAsUTF8Text(Row, Col);
@ -369,7 +374,7 @@ begin
fail('Error in test code. Failed to get named worksheet'); fail('Error in test code. Failed to get named worksheet');
for Row := Low(SollDateTimes) to High(SollDateTimes) do for Row := Low(SollDateTimes) to High(SollDateTimes) do
for Col := Low(SollDateTimeFormats) to High(SollDateTimeFormats) do begin for Col := Low(SollDateTimeFormats) to High(SollDateTimeFormats) do begin
if (AFormat = sfExcel2) and (SollDateTimeFormats[Col] in [nfFmtDateTime, nfTimeInterval]) then if (AFormat = sfExcel2) and (SollDateTimeFormats[Col] in [nfCustom, nfTimeInterval]) then
Continue; // The formats nfFmtDateTime and nfTimeInterval are not supported by BIFF2 Continue; // The formats nfFmtDateTime and nfTimeInterval are not supported by BIFF2
ActualString := MyWorksheet.ReadAsUTF8Text(Row,Col); ActualString := MyWorksheet.ReadAsUTF8Text(Row,Col);
CheckEquals( CheckEquals(

View File

@ -46,6 +46,7 @@ implementation
uses uses
TypInfo; TypInfo;
{ The test will use Excel strings and convert them to fpc dialect }
procedure InitParserTestData; procedure InitParserTestData;
begin begin
// Tests with 1 format section only // Tests with 1 format section only
@ -146,14 +147,16 @@ var
i: Integer; i: Integer;
parser: TsNumFormatParser; parser: TsNumFormatParser;
MyWorkbook: TsWorkbook; MyWorkbook: TsWorkbook;
actual: String;
begin begin
MyWorkbook := TsWorkbook.Create; // needed to provide the FormatSettings for the parser MyWorkbook := TsWorkbook.Create; // needed to provide the FormatSettings for the parser
try try
for i:=0 to 5 do begin for i:=0 to 5 do begin
parser := TsNumFormatParser.Create(MyWorkbook, ParserTestData[i].FormatString, cdToFPSpreadsheet); parser := TsNumFormatParser.Create(MyWorkbook, ParserTestData[i].FormatString);
try try
CheckEquals(ParserTestData[i].SollFormatString, parser.FormatString, actual := parser.FormatString[nfdDefault];
'Test format string ' + ParserTestData[i].FormatString + ' construction mismatch'); CheckEquals(ParserTestData[i].SollFormatString, actual,
'Test format string ' + ParserTestData[i].SollFormatString + ' construction mismatch');
CheckEquals(ord(ParserTestData[i].SollNumFormat), ord(parser.ParsedSections[0].NumFormat), CheckEquals(ord(ParserTestData[i].SollNumFormat), ord(parser.ParsedSections[0].NumFormat),
'Test format (' + GetEnumName(TypeInfo(TsNumberFormat), integer(ParserTestData[i].SollNumFormat)) + 'Test format (' + GetEnumName(TypeInfo(TsNumberFormat), integer(ParserTestData[i].SollNumFormat)) +
') detection mismatch'); ') detection mismatch');

View File

@ -48,9 +48,6 @@
</Debugging> </Debugging>
</Linking> </Linking>
<Other> <Other>
<CompilerMessages>
<MsgFileName Value=""/>
</CompilerMessages>
<CompilerPath Value="$(CompPath)"/> <CompilerPath Value="$(CompPath)"/>
</Other> </Other>
</CompilerOptions> </CompilerOptions>
@ -173,9 +170,6 @@
</Optimizations> </Optimizations>
</CodeGeneration> </CodeGeneration>
<Other> <Other>
<CompilerMessages>
<MsgFileName Value=""/>
</CompilerMessages>
<CompilerPath Value="$(CompPath)"/> <CompilerPath Value="$(CompPath)"/>
</Other> </Other>
</CompilerOptions> </CompilerOptions>

View File

@ -61,8 +61,7 @@ type
procedure ApplyCellFormatting(ARow, ACol: Cardinal; XFIndex: Word); override; procedure ApplyCellFormatting(ARow, ACol: Cardinal; XFIndex: Word); override;
procedure CreateNumFormatList; override; procedure CreateNumFormatList; override;
procedure ExtractNumberFormat(AXFIndex: WORD; procedure ExtractNumberFormat(AXFIndex: WORD;
out ANumberFormat: TsNumberFormat; out ADecimals: Byte; out ANumberFormat: TsNumberFormat; out ANumberFormatStr: String); override;
out ACurrencySymbol: String; out ANumberFormatStr: String); override;
procedure ReadBlank(AStream: TStream); override; procedure ReadBlank(AStream: TStream); override;
procedure ReadColWidth(AStream: TStream); procedure ReadColWidth(AStream: TStream);
procedure ReadFont(AStream: TStream); procedure ReadFont(AStream: TStream);
@ -139,7 +138,7 @@ var
implementation implementation
uses uses
Math; Math, fpsNumFormatParser;
const const
{ Excel record IDs } { Excel record IDs }
@ -181,21 +180,23 @@ begin
ts := fs.ThousandSeparator; ts := fs.ThousandSeparator;
cs := fs.CurrencyString; cs := fs.CurrencyString;
AddFormat( 0, '', nfGeneral); AddFormat( 0, '', nfGeneral);
AddFormat( 1, '0', nfFixed, 0); AddFormat( 1, '0', nfFixed);
AddFormat( 2, '0'+ds+'00', nfFixed, 2); // 0.00 AddFormat( 2, '0'+ds+'00', nfFixed); // 0.00
AddFormat( 3, '#'+ts+'##0', nfFixedTh, 0); // #,##0 AddFormat( 3, '#'+ts+'##0', nfFixedTh); // #,##0
AddFormat( 4, '#'+ts+'##0'+ds+'00', nfFixedTh, 2); // #,##0.00 AddFormat( 4, '#'+ts+'##0'+ds+'00', nfFixedTh); // #,##0.00
AddFormat( 5, UTF8ToAnsi('"'+cs+'"#'+ts+'##0_);("'+cs+'"#'+ts+'##0)'), nfCurrency, 0); AddFormat( 5, UTF8ToAnsi('"'+cs+'"#'+ts+'##0_);("'+cs+'"#'+ts+'##0)'), nfCurrency);
AddFormat( 6, UTF8ToAnsi('"'+cs+'"#'+ts+'##0_);[Red]("'+cs+'"#'+ts+'##0)'), nfCurrencyRed, 2); AddFormat( 6, UTF8ToAnsi('"'+cs+'"#'+ts+'##0_);[Red]("'+cs+'"#'+ts+'##0)'), nfCurrencyRed);
AddFormat( 7, UTF8ToAnsi('"'+cs+'"#'+ts+'##0'+ds+'00_);("'+cs+'"#'+ts+'##0'+ds+'00)'), nfCurrency, 0); AddFormat( 7, UTF8ToAnsi('"'+cs+'"#'+ts+'##0'+ds+'00_);("'+cs+'"#'+ts+'##0'+ds+'00)'), nfCurrency);
AddFormat( 8, UTF8ToAnsi('"'+cs+'"#'+ts+'##0'+ds+'00_);[Red]("'+cs+'"#'+ts+'##0'+ds+'00)'), nfCurrency, 2); AddFormat( 8, UTF8ToAnsi('"'+cs+'"#'+ts+'##0'+ds+'00_);[Red]("'+cs+'"#'+ts+'##0'+ds+'00)'), nfCurrency);
AddFormat( 9, '0%', nfPercentage, 0); AddFormat( 9, '0%', nfPercentage);
AddFormat(10, '0'+ds+'00%', nfPercentage, 2); AddFormat(10, '0'+ds+'00%', nfPercentage);
AddFormat(11, '0'+ds+'00E+00', nfExp, 2); AddFormat(11, '0'+ds+'00E+00', nfExp);
AddFormat(12, fs.ShortDateFormat, nfShortDate); AddFormat(12, fs.ShortDateFormat, nfShortDate);
AddFormat(13, fs.LongDateFormat, nfLongDate); AddFormat(13, fs.LongDateFormat, nfLongDate);
AddFormat(14, SpecialDateTimeFormat('dm', fs, true), nfFmtDateTime); AddFormat(14, 'd/mmm', nfCustom);
AddFormat(15, SpecialDateTimeFormat('my', fs, true), nfFmtDateTime); AddFormat(15, 'mmm/yy', nfCustom);
//AddFormat(14, SpecialDateTimeFormat('dm', fs, true), nfFmtDateTime);
//AddFormat(15, SpecialDateTimeFormat('my', fs, true), nfFmtDateTime);
AddFormat(16, AddAMPM(fs.ShortTimeFormat, fs), nfShortTimeAM); AddFormat(16, AddAMPM(fs.ShortTimeFormat, fs), nfShortTimeAM);
AddFormat(17, AddAMPM(fs.LongTimeFormat, fs), nfLongTimeAM); AddFormat(17, AddAMPM(fs.LongTimeFormat, fs), nfLongTimeAM);
AddFormat(18, fs.ShortTimeFormat, nfShortTime); AddFormat(18, fs.ShortTimeFormat, nfShortTime);
@ -223,6 +224,7 @@ begin
if ADecimals > 0 then ADecimals := 2; if ADecimals > 0 then ADecimals := 2;
ANumFormat := nfExp; ANumFormat := nfExp;
end; end;
{
nfFmtDateTime: nfFmtDateTime:
begin begin
fmt := lowercase(AFormatString); fmt := lowercase(AFormatString);
@ -248,6 +250,7 @@ begin
else else
ANumFormat := nfShortDateTime; ANumFormat := nfShortDateTime;
end; end;
}
nfCustom, nfTimeInterval: nfCustom, nfTimeInterval:
begin begin
ANumFormat := nfGeneral; ANumFormat := nfGeneral;
@ -261,18 +264,28 @@ end;
function TsBIFF2NumFormatList.FindFormatOf(AFormatCell: PCell): Integer; function TsBIFF2NumFormatList.FindFormatOf(AFormatCell: PCell): Integer;
var var
fmt: String; fmt: String;
parser: TsNumFormatParser;
decs: Integer;
dt: string;
begin begin
parser := TsNumFormatParser.Create(Workbook, AFormatCell^.NumberFormatStr);
try
decs := parser.Decimals;
dt := parser.GetDateTimeCode(0);
finally
parser.Free;
end;
case AFormatCell^.NumberFormat of case AFormatCell^.NumberFormat of
nfGeneral, nfGeneral,
nfCustom,
nfTimeInterval : Result := 0; nfTimeInterval : Result := 0;
nfFixed : Result := IfThen(AFormatCell^.Decimals = 0, 1, 2); nfFixed : Result := IfThen(decs = 0, 1, 2);
nfFixedTh : Result := IfThen(AFormatCell^.Decimals = 0, 3, 4); nfFixedTh : Result := IfThen(decs = 0, 3, 4);
nfCurrency, nfCurrency,
nfAccounting : Result := IfThen(AFormatCell^.Decimals = 0, 5, 7); nfAccounting : Result := IfThen(decs = 0, 5, 7);
nfCurrencyRed, nfCurrencyRed,
nfAccountingRed : Result := IfThen(AFormatCell^.Decimals = 0, 6, 8); nfAccountingRed : Result := IfThen(decs = 0, 6, 8);
nfPercentage : Result := IfThen(AFormatCell^.Decimals = 0, 9, 10); nfPercentage : Result := IfThen(decs = 0, 9, 10);
nfExp, nfSci : Result := 11; nfExp, nfSci : Result := 11;
nfShortDate : Result := 12; nfShortDate : Result := 12;
nfLongDate : Result := 13; nfLongDate : Result := 13;
@ -281,38 +294,8 @@ begin
nfShortTime : Result := 18; nfShortTime : Result := 18;
nfLongTime : Result := 19; nfLongTime : Result := 19;
nfShortDateTime : Result := 20; nfShortDateTime : Result := 20;
nfFmtDateTime : begin nfCustom : if dt = 'dm' then Result := 14 else
fmt := lowercase(AFormatCell^.NumberFormatStr); if dt = 'my' then Result := 15;
if (fmt = 'd-mmm') or (fmt = 'd/mmm') or
(fmt = 'd-mm') or (fmt = 'd/mm') or
(fmt = 'dd-mm') or (fmt = 'dd/mm') or
(fmt = 'dd-mmm') or (fmt = 'dd/mmm')
then
Result := 14
else
if (fmt = 'mmm-yy') or (fmt = 'mmm/yy') or
(fmt = 'mm-yy') or (fmt = 'mm/yy') or
(fmt = 'm-yy') or (fmt = 'm/y') or
(fmt = 'mmm-yyyy') or (fmt = 'mmm/yyyy') or
(fmt = 'mm-yyyy') or (fmt = 'mm/yyyy') or
(fmt = 'm-yyyy') or (fmt = 'm/yyyy')
then
Result := 15
else
if (fmt = 'nn:ss') or (fmt = 'mm:ss') or
(fmt = 'n:ss') or (fmt = 'm:ss')
then
Result := 19
else
if (fmt = 'nn:ss.z') or (fmt = 'mm:ss.z') or
(fmt = 'n:ss.z') or (fmt = 'm:ss.z') or
(fmt = 'nn:ss.zzz') or (fmt = 'mm:ss.zzz') or
(fmt = 'n:ss.zzz') or (fmt = 'm:ss.zzz')
then
Result := 19
else
Result := 20;
end;
end; end;
end; end;
@ -383,8 +366,7 @@ end;
{ Extracts the number format data from an XF record indexed by AXFIndex. { Extracts the number format data from an XF record indexed by AXFIndex.
Note that BIFF2 supports only 21 formats. } Note that BIFF2 supports only 21 formats. }
procedure TsSpreadBIFF2Reader.ExtractNumberFormat(AXFIndex: WORD; procedure TsSpreadBIFF2Reader.ExtractNumberFormat(AXFIndex: WORD;
out ANumberFormat: TsNumberFormat; out ADecimals: Byte; out ANumberFormat: TsNumberFormat; out ANumberFormatStr: String);
out ACurrencySymbol: String; out ANumberFormatStr: String);
var var
lNumFormatData: TsNumFormatData; lNumFormatData: TsNumFormatData;
begin begin
@ -392,13 +374,9 @@ begin
if lNumFormatData <> nil then begin if lNumFormatData <> nil then begin
ANumberFormat := lNumFormatData.NumFormat; ANumberFormat := lNumFormatData.NumFormat;
ANumberFormatStr := lNumFormatData.FormatString; ANumberFormatStr := lNumFormatData.FormatString;
ADecimals := lNumFormatData.Decimals;
ACurrencySymbol := lNumFormatData.CurrencySymbol;
end else begin end else begin
ANumberFormat := nfGeneral; ANumberFormat := nfGeneral;
ANumberFormatStr := ''; ANumberFormatStr := '';
ADecimals := 0;
ACurrencySymbol := '';
end; end;
end; end;
@ -575,11 +553,11 @@ begin
Move(Data[0], formulaResult, SizeOf(Data)); Move(Data[0], formulaResult, SizeOf(Data));
{Find out what cell type, set content type and value} {Find out what cell type, set content type and value}
ExtractNumberFormat(XF, nf, nd, ncs, nfs); ExtractNumberFormat(XF, nf, nfs);
if IsDateTime(formulaResult, nf, dt) then if IsDateTime(formulaResult, nf, nfs, dt) then
FWorksheet.WriteDateTime(ARow, ACol, dt, nf, nfs) FWorksheet.WriteDateTime(ARow, ACol, dt, nf, nfs)
else else
FWorksheet.WriteNumber(ARow, ACol, formulaResult, nf, nd, ncs); FWorksheet.WriteNumber(ARow, ACol, formulaResult, nf, nfs);
end; end;
{ Formula token array } { Formula token array }
@ -645,11 +623,11 @@ begin
AStream.ReadBuffer(value, 8); AStream.ReadBuffer(value, 8);
{Find out what cell type, set content type and value} {Find out what cell type, set content type and value}
ExtractNumberFormat(XF, nf, nd, ncs, nfs); ExtractNumberFormat(XF, nf, nfs);
if IsDateTime(value, nf, dt) then if IsDateTime(value, nf, nfs, dt) then
FWorksheet.WriteDateTime(ARow, ACol, dt, nf, nfs) FWorksheet.WriteDateTime(ARow, ACol, dt, nf, nfs)
else else
FWorksheet.WriteNumber(ARow, ACol, value, nf, nd, ncs); FWorksheet.WriteNumber(ARow, ACol, value, nf, nfs);
{ Apply formatting to cell } { Apply formatting to cell }
ApplyCellFormatting(ARow, ACol, XF); ApplyCellFormatting(ARow, ACol, XF);
@ -902,6 +880,7 @@ begin
// but the number format list of the writer is in Excel syntax. // but the number format list of the writer is in Excel syntax.
// And for BIFF2, there is only a limited number of formats. // And for BIFF2, there is only a limited number of formats.
lCell := ACell^; lCell := ACell^;
{
with lCell do begin with lCell do begin
if IsDateTimeFormat(NumberFormat) then if IsDateTimeFormat(NumberFormat) then
NumberFormatStr := BuildDateTimeFormatString(NumberFormat, NumberFormatStr := BuildDateTimeFormatString(NumberFormat,
@ -911,6 +890,7 @@ begin
Workbook.FormatSettings, Decimals, CurrencySymbol); Workbook.FormatSettings, Decimals, CurrencySymbol);
NumFormatList.ConvertBeforeWriting(NumberFormatStr, NumberFormat, Decimals, CurrencyString); NumFormatList.ConvertBeforeWriting(NumberFormatStr, NumberFormat, Decimals, CurrencyString);
end; end;
}
lIndex := FindFormattingInList(@lCell); lIndex := FindFormattingInList(@lCell);
// Carefully check the index // Carefully check the index
@ -925,14 +905,13 @@ var
i: Integer; i: Integer;
begin begin
inherited ListAllFormattingStyles; inherited ListAllFormattingStyles;
{
for i:=0 to High(FFormattingStyles) do for i:=0 to High(FFormattingStyles) do
FNumFormatList.ConvertBeforeWriting( FNumFormatList.ConvertBeforeWriting(
FFormattingStyles[i].NumberFormatStr, FFormattingStyles[i].NumberFormatStr,
FFormattingStyles[i].NumberFormat, FFormattingStyles[i].NumberFormat
FFormattingStyles[i].Decimals,
FFormattingStyles[i].CurrencySymbol
); );
}
end; end;
{ Builds up the list of number formats to be written to the biff2 file. { Builds up the list of number formats to be written to the biff2 file.

View File

@ -310,6 +310,8 @@ begin
// Now apply the modifications. // Now apply the modifications.
if uffNumberFormat in FFormattingStyles[i].UsedFormattingFields then begin if uffNumberFormat in FFormattingStyles[i].UsedFormattingFields then begin
// The number formats in the FormattingStyles are still in fpc dialect
// They will be converted to Excel syntax immediately before writing.
j := NumFormatList.FindFormatOf(@FFormattingStyles[i]); j := NumFormatList.FindFormatOf(@FFormattingStyles[i]);
if j > -1 then if j > -1 then
lFormatIndex := NumFormatList[j].Index; lFormatIndex := NumFormatList[j].Index;

View File

@ -357,6 +357,9 @@ type
TsBIFFNumFormatList = class(TsCustomNumFormatList) TsBIFFNumFormatList = class(TsCustomNumFormatList)
protected protected
procedure AddBuiltinFormats; override; procedure AddBuiltinFormats; override;
procedure ConvertBeforeWriting(var AFormatString: String;
var ANumFormat: TsNumberFormat; var ADecimals: Byte;
var ACurrencySymbol: String); override;
public public
end; end;
@ -375,13 +378,15 @@ type
function DecodeRKValue(const ARK: DWORD): Double; function DecodeRKValue(const ARK: DWORD): Double;
// Returns the numberformat for a given XF record // Returns the numberformat for a given XF record
procedure ExtractNumberFormat(AXFIndex: WORD; procedure ExtractNumberFormat(AXFIndex: WORD;
out ANumberFormat: TsNumberFormat; out ADecimals: Byte; out ANumberFormat: TsNumberFormat; //out ADecimals: Byte;
out ACurrencySymbol: String; out ANumberFormatStr: String); virtual; //out ACurrencySymbol: String;
out ANumberFormatStr: String); virtual;
// Finds format record for XF record pointed to by cell // Finds format record for XF record pointed to by cell
// Will not return info for built-in formats // Will not return info for built-in formats
function FindNumFormatDataForCell(const AXFIndex: Integer): TsNumFormatData; function FindNumFormatDataForCell(const AXFIndex: Integer): TsNumFormatData;
// Tries to find if a number cell is actually a date/datetime/time cell and retrieves the value // Tries to find if a number cell is actually a date/datetime/time cell and retrieves the value
function IsDateTime(Number: Double; ANumberFormat: TsNumberFormat; var ADateTime: TDateTime): Boolean; function IsDateTime(Number: Double; ANumberFormat: TsNumberFormat;
ANumberFormatStr: String; var ADateTime: TDateTime): Boolean;
// Here we can add reading of records which didn't change across BIFF5-8 versions // Here we can add reading of records which didn't change across BIFF5-8 versions
procedure ReadCodePage(AStream: TStream); procedure ReadCodePage(AStream: TStream);
// Read column info // Read column info
@ -708,48 +713,50 @@ end;
{ These are the built-in number formats as expected in the biff spreadsheet file. { These are the built-in number formats as expected in the biff spreadsheet file.
In BIFF5+ they are not written to file but they are used for lookup of the In BIFF5+ they are not written to file but they are used for lookup of the
number format that Excel used. They have to be converted to fpspreadsheet format. } number format that Excel used. They are specified here in fpc dialect. }
procedure TsBIFFNumFormatList.AddBuiltinFormats; procedure TsBIFFNumFormatList.AddBuiltinFormats;
var var
fs: TFormatSettings;
cs: String; cs: String;
begin begin
fs := Workbook.FormatSettings;
cs := AnsiToUTF8(Workbook.FormatSettings.CurrencyString); cs := AnsiToUTF8(Workbook.FormatSettings.CurrencyString);
AddFormat( 0, '', nfGeneral); AddFormat( 0, '', nfGeneral);
AddFormat( 1, '0', nfFixed, 0); AddFormat( 1, '0', nfFixed);
AddFormat( 2, '0.00', nfFixed, 2); AddFormat( 2, '0.00', nfFixed);
AddFormat( 3, '#,##0', nfFixedTh, 0); AddFormat( 3, '#,##0', nfFixedTh);
AddFormat( 4, '#,##0.00', nfFixedTh, 2); AddFormat( 4, '#,##0.00', nfFixedTh);
AddFormat( 5, '"'+cs+'"#,##0_);("'+cs+'"#,##0)', nfCurrency, 0); AddFormat( 5, '"'+cs+'"#,##0_);("'+cs+'"#,##0)', nfCurrency);
AddFormat( 6, '"'+cs+'"#,##0_);[Red]("'+cs+'"#,##0)', nfCurrencyRed, 0); AddFormat( 6, '"'+cs+'"#,##0_);[Red]("'+cs+'"#,##0)', nfCurrencyRed);
AddFormat( 7, '"'+cs+'"#,##0.00_);("'+cs+'"#,##0.00)', nfCurrency, 2); AddFormat( 7, '"'+cs+'"#,##0.00_);("'+cs+'"#,##0.00)', nfCurrency);
AddFormat( 8, '"'+cs+'"#,##0.00_);[Red]("'+cs+'"#,##0.00)', nfCurrencyRed, 2); AddFormat( 8, '"'+cs+'"#,##0.00_);[Red]("'+cs+'"#,##0.00)', nfCurrencyRed);
AddFormat( 9, '0%', nfPercentage, 0); AddFormat( 9, '0%', nfPercentage);
AddFormat(10, '0.00%', nfPercentage, 2); AddFormat(10, '0.00%', nfPercentage);
AddFormat(11, '0.00E+00', nfExp, 2); AddFormat(11, '0.00E+00', nfExp);
// fraction formats 12 ('# ?/?') and 13 ('# ??/??') not supported // fraction formats 12 ('# ?/?') and 13 ('# ??/??') not supported
AddFormat(14, 'M/D/YY', nfShortDate); AddFormat(14, fs.ShortDateFormat, nfShortDate); // 'M/D/YY'
AddFormat(15, 'D-MMM-YY', nfLongDate); AddFormat(15, fs.LongDateFormat, nfLongDate); // 'D-MMM-YY'
AddFormat(16, 'D-MMM', nfFmtDateTime); AddFormat(16, 'd/mmm', nfCustom); // 'D-MMM'
AddFormat(17, 'MMM-YY', nfFmtDateTime); AddFormat(17, 'mmm/yy', nfCustom); // 'MMM-YY'
AddFormat(18, 'h:mm AM/PM', nfShortTimeAM); AddFormat(18, AddAMPM(fs.ShortTimeFormat, fs), nfShortTimeAM); // 'h:mm AM/PM'
AddFormat(19, 'h:mm:ss AM/PM', nfLongTimeAM); AddFormat(19, AddAMPM(fs.LongTimeFormat, fs), nfLongTimeAM); // 'h:mm:ss AM/PM'
AddFormat(20, 'h:mm', nfShortTime); AddFormat(20, fs.ShortTimeFormat, nfShortTime); // 'h:mm'
AddFormat(21, 'h:mm:ss', nfLongTime); AddFormat(21, fs.LongTimeFormat, nfLongTime); // 'h:mm:ss'
AddFormat(22, 'M/D/YY h:mm', nfShortDateTime); AddFormat(22, fs.ShortDateFormat + ' ' + fs.ShortTimeFormat, nfShortDateTime); // 'M/D/YY h:mm' (localized)
// 23..36 not supported // 23..36 not supported
AddFormat(37, '_(#,##0_);(#,##0)', nfCurrency, 0); AddFormat(37, '_(#,##0_);(#,##0)', nfCurrency);
AddFormat(38, '_(#,##0_);[Red](#,##0)', nfCurrencyRed, 0); AddFormat(38, '_(#,##0_);[Red](#,##0)', nfCurrencyRed);
AddFormat(39, '_(#,##0.00_);(#,##0.00)', nfCurrency, 2); AddFormat(39, '_(#,##0.00_);(#,##0.00)', nfCurrency);
AddFormat(40, '_(#,##0.00_);[Red](#,##0.00)', nfCurrencyRed, 2); AddFormat(40, '_(#,##0.00_);[Red](#,##0.00)', nfCurrencyRed);
AddFormat(41, '_("'+cs+'"* #,##0_);_("'+cs+'"* (#,##0);_("'+cs+'"* "-"_);_(@_)', nfAccounting, 0); AddFormat(41, '_("'+cs+'"* #,##0_);_("'+cs+'"* (#,##0);_("'+cs+'"* "-"_);_(@_)', nfAccounting);
AddFormat(42, '_(* #,##0_);_(* (#,##0);_(* "-"_);_(@_)', nfAccounting, 0); AddFormat(42, '_(* #,##0_);_(* (#,##0);_(* "-"_);_(@_)', nfAccounting);
AddFormat(43, '_("'+cs+'"* #,##0.00_);_("'+cs+'"* (#,##0.00);_("'+cs+'"* "-"??_);_(@_)', nfAccounting, 2); AddFormat(43, '_("'+cs+'"* #,##0.00_);_("'+cs+'"* (#,##0.00);_("'+cs+'"* "-"??_);_(@_)', nfAccounting);
AddFormat(44, '_(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(@_)', nfAccounting, 2); AddFormat(44, '_(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(@_)', nfAccounting);
AddFormat(45, 'mm:ss', nfFmtDateTime); AddFormat(45, 'nn:ss', nfCustom);
AddFormat(46, '[h]:mm:ss', nfTimeInterval); AddFormat(46, '[h]:nn:ss', nfTimeInterval);
AddFormat(47, 'mm:ss.0', nfFmtDateTime); AddFormat(47, 'nn:ss.z', nfCustom);
AddFormat(48, '##0.0E+00', nfSci, 1); AddFormat(48, '##0.0E+00', nfSci);
// 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.
@ -758,6 +765,26 @@ begin
FNextFormatIndex := 164; FNextFormatIndex := 164;
end; end;
procedure TsBIFFNumFormatList.ConvertBeforeWriting(var AFormatString: String;
var ANumFormat: TsNumberFormat; var ADecimals: Byte; var ACurrencySymbol: String);
var
parser: TsNumFormatParser;
fmt: String;
begin
parser := TsNumFormatParser.Create(Workbook, AFormatString);
try
if parser.Status = psOK then begin
// We convert the fpc format string to Excel dialect
AFormatString := parser.FormatString[nfdExcel];
ANumFormat := parser.NumFormat;
ADecimals := parser.Decimals;
ACurrencySymbol := parser.CurrencySymbol;
end;
finally
parser.Free;
end;
end;
{ TsSpreadBIFFReader } { TsSpreadBIFFReader }
@ -840,12 +867,14 @@ var
begin begin
FreeAndNil(FNumFormatList); FreeAndNil(FNumFormatList);
FNumFormatList := TsBIFFNumFormatList.Create(Workbook); FNumFormatList := TsBIFFNumFormatList.Create(Workbook);
(*
// Convert builtin formats to fps syntax // Convert builtin formats to fps syntax
for i:=0 to FNumFormatList.Count-1 do begin for i:=0 to FNumFormatList.Count-1 do begin
item := FNumFormatList[i]; item := FNumFormatList[i];
FNumFormatList.ConvertAfterReading(item.Index, item.FormatString, FNumFormatList.ConvertAfterReading(item.Index, item.FormatString,
item.NumFormat, item.Decimals, item.CurrencySymbol); item.NumFormat, item.Decimals, item.CurrencySymbol);
end; end;
*)
end; end;
{ Extracts a number out of an RK value. { Extracts a number out of an RK value.
@ -883,8 +912,9 @@ end;
{ Extracts number format data from an XF record index by AXFIndex. { Extracts number format data from an XF record index by AXFIndex.
Valid for BIFF5-BIFF8. Needs to be overridden for BIFF2 } Valid for BIFF5-BIFF8. Needs to be overridden for BIFF2 }
procedure TsSpreadBIFFReader.ExtractNumberFormat(AXFIndex: WORD; procedure TsSpreadBIFFReader.ExtractNumberFormat(AXFIndex: WORD;
out ANumberFormat: TsNumberFormat; out ADecimals: Byte; out ANumberFormat: TsNumberFormat; //out ADecimals: Byte;
out ACurrencySymbol: String; out ANumberFormatStr: String); //out ACurrencySymbol: String;
out ANumberFormatStr: String);
var var
lNumFormatData: TsNumFormatData; lNumFormatData: TsNumFormatData;
begin begin
@ -892,13 +922,13 @@ begin
if lNumFormatData <> nil then begin if lNumFormatData <> nil then begin
ANumberFormat := lNumFormatData.NumFormat; ANumberFormat := lNumFormatData.NumFormat;
ANumberFormatStr := lNumFormatData.FormatString; ANumberFormatStr := lNumFormatData.FormatString;
ADecimals := lNumFormatData.Decimals; // ADecimals := lNumFormatData.Decimals;
ACurrencySymbol := lNumFormatData.CurrencySymbol; // ACurrencySymbol := lNumFormatData.CurrencySymbol;
end else begin end else begin
ANumberFormat := nfGeneral; ANumberFormat := nfGeneral;
ANumberFormatStr := ''; ANumberFormatStr := '';
ADecimals := 0; // ADecimals := 0;
ACurrencySymbol := ''; // ACurrencySymbol := '';
end; end;
end; end;
@ -918,22 +948,30 @@ end;
{ Convert the number to a date/time and return that if it is } { Convert the number to a date/time and return that if it is }
function TsSpreadBIFFReader.IsDateTime(Number: Double; function TsSpreadBIFFReader.IsDateTime(Number: Double;
ANumberFormat: TsNumberFormat; var ADateTime: TDateTime): boolean; ANumberFormat: TsNumberFormat; ANumberFormatStr: String;
var ADateTime: TDateTime): boolean;
var
parser: TsNumFormatParser;
begin begin
Result := true;
if ANumberFormat in [ if ANumberFormat in [
nfShortDateTime, nfFmtDateTime, nfShortDate, nfLongDate, nfShortDateTime, {nfFmtDateTime, }nfShortDate, nfLongDate,
nfShortTime, nfLongTime, nfShortTimeAM, nfLongTimeAM] then nfShortTime, nfLongTime, nfShortTimeAM, nfLongTimeAM]
begin then
ADateTime := ConvertExcelDateTimeToDateTime(Number, FDateMode); ADateTime := ConvertExcelDateTimeToDateTime(Number, FDateMode)
Result := true; else
end else if ANumberFormat = nfTimeInterval then
if ANumberFormat = nfTimeInterval then begin ADateTime := Number
ADateTime := Number; else begin
Result := true; parser := TsNumFormatParser.Create(Workbook, ANumberFormatStr);
end else try
begin if (parser.Status = psOK) and parser.IsDateTimeFormat then
ADateTime := 0; ADateTime := ConvertExcelDateTimeToDateTime(Number, FDateMode)
else
Result := false; Result := false;
finally
parser.Free;
end;
end; end;
end; end;
@ -1108,11 +1146,11 @@ begin
Move(Data[0], ResultFormula, SizeOf(Data)); Move(Data[0], ResultFormula, SizeOf(Data));
{Find out what cell type, set content type and value} {Find out what cell type, set content type and value}
ExtractNumberFormat(XF, nf, nd, ncs, nfs); ExtractNumberFormat(XF, nf, nfs);
if IsDateTime(ResultFormula, nf, dt) then if IsDateTime(ResultFormula, nf, nfs, dt) then
FWorksheet.WriteDateTime(ARow, ACol, dt, nf, nfs) FWorksheet.WriteDateTime(ARow, ACol, dt, nf, nfs)
else else
FWorksheet.WriteNumber(ARow, ACol, ResultFormula, nf, nd, ncs); FWorksheet.WriteNumber(ARow, ACol, ResultFormula, nf, nfs); //, nd, ncs);
end; end;
{ Formula token array } { Formula token array }
@ -1174,11 +1212,11 @@ begin
RK := DWordLEtoN(AStream.ReadDWord); RK := DWordLEtoN(AStream.ReadDWord);
lNumber := DecodeRKValue(RK); lNumber := DecodeRKValue(RK);
{Find out what cell type, set contenttype and value} {Find out what cell type, set contenttype and value}
ExtractNumberFormat(XF, nf, nd, ncs, nfs); ExtractNumberFormat(XF, nf, nfs);
if IsDateTime(lNumber, nf, lDateTime) then if IsDateTime(lNumber, nf, nfs, lDateTime) then
FWorksheet.WriteDateTime(ARow, fc, lDateTime, nf, nfs) FWorksheet.WriteDateTime(ARow, fc, lDateTime, nf, nfs)
else else
FWorksheet.WriteNumber(ARow, fc, lNumber, nf, nd, ncs); FWorksheet.WriteNumber(ARow, fc, lNumber, nf, nfs);
inc(fc); inc(fc);
dec(pending, SizeOf(XF) + SizeOf(RK)); dec(pending, SizeOf(XF) + SizeOf(RK));
end; end;
@ -1210,13 +1248,11 @@ begin
AStream.ReadBuffer(value, 8); AStream.ReadBuffer(value, 8);
{Find out what cell type, set content type and value} {Find out what cell type, set content type and value}
ExtractNumberFormat(XF, nf, nd, ncs, nfs); ExtractNumberFormat(XF, nf, nfs);
if IsDateTime(value, nf, dt) then if IsDateTime(value, nf, nfs, dt) then
FWorksheet.WriteDateTime(ARow, ACol, dt) //, nf, nfs) FWorksheet.WriteDateTime(ARow, ACol, dt, nf, nfs)
else else
//if nf <> nfCustom then // why was this here? FWorksheet.WriteNumber(ARow, ACol, value, nf, nfs);
FWorksheet.WriteNumber(ARow, ACol, value, nf, nd, ncs);
FWorksheet.WriteNumberFormat(ARow, ACol, nf, nfs); // override built-in format string
{ Add attributes to cell } { Add attributes to cell }
ApplyCellFormatting(ARow, ACol, XF); ApplyCellFormatting(ARow, ACol, XF);
@ -1297,17 +1333,17 @@ begin
Number := DecodeRKValue(RK); Number := DecodeRKValue(RK);
{Find out what cell type, set contenttype and value} {Find out what cell type, set contenttype and value}
ExtractNumberFormat(XF, nf, nd, ncs, nfs); ExtractNumberFormat(XF, nf, nfs);
if IsDateTime(Number, nf, lDateTime) then if IsDateTime(Number, nf, nfs, lDateTime) then
FWorksheet.WriteDateTime(ARow, ACol, lDateTime, nf, nfs) FWorksheet.WriteDateTime(ARow, ACol, lDateTime, nf, nfs)
else else
FWorksheet.WriteNumber(ARow, ACol, Number, nf, nd, ncs); FWorksheet.WriteNumber(ARow, ACol, Number, nf, nfs);
{Add attributes} {Add attributes}
ApplyCellFormatting(ARow, ACol, XF); ApplyCellFormatting(ARow, ACol, XF);
end; end;
// Read the part of the ROW record that is common to all BIFF versions // Read the part of the ROW record that is common to BIFF3-8 versions
procedure TsSpreadBIFFReader.ReadRowInfo(AStream: TStream); procedure TsSpreadBIFFReader.ReadRowInfo(AStream: TStream);
type type
TRowRecord = packed record TRowRecord = packed record
@ -1325,6 +1361,21 @@ var
h: word; h: word;
begin begin
AStream.ReadBuffer(rowrec, SizeOf(TRowRecord)); AStream.ReadBuffer(rowrec, SizeOf(TRowRecord));
// if bit 6 is set in the flags row height does not match the font size.
// Only for this case we create a row record for fpspreadsheet
if rowrec.Flags and $00000040 <> 0 then begin
lRow := FWorksheet.GetRow(WordLEToN(rowrec.RowIndex));
// row height is encoded into the 15 lower bits in units "twips" (1/20 pt)
// we need it in "lines", i.e. we divide the points by the point size of the default font
h := WordLEToN(rowrec.Height) and $7FFF;
lRow^.Height := TwipsToPts(h) / FWorkbook.GetDefaultFontSize;
if lRow^.Height > ROW_HEIGHT_CORRECTION then
lRow^.Height := lRow^.Height - ROW_HEIGHT_CORRECTION
else
lRow^.Height := 0;
end;
{
h := WordLEToN(rowrec.Height); h := WordLEToN(rowrec.Height);
if h and $8000 = 0 then begin // if this bit were set, rowheight would be default if h and $8000 = 0 then begin // if this bit were set, rowheight would be default
lRow := FWorksheet.GetRow(WordLEToN(rowrec.RowIndex)); lRow := FWorksheet.GetRow(WordLEToN(rowrec.RowIndex));
@ -1336,6 +1387,7 @@ begin
else else
lRow^.Height := 0; lRow^.Height := 0;
end; end;
}
end; end;
{ Reads the cell address used in an RPN formula element. Evaluates the corresponding { Reads the cell address used in an RPN formula element. Evaluates the corresponding
@ -2118,6 +2170,7 @@ begin
// But we have to consider that the number formats of the cell is in fpc syntax, // 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. // but the number format list of the writer is in Excel syntax.
lCell := ACell^; lCell := ACell^;
(*
with lCell do begin with lCell do begin
if NumberFormat <> nfCustom then begin if NumberFormat <> nfCustom then begin
if IsDateTimeFormat(NumberFormat) then if IsDateTimeFormat(NumberFormat) then
@ -2126,9 +2179,10 @@ begin
else else
NumberFormatStr := BuildNumberFormatString(NumberFormat, NumberFormatStr := BuildNumberFormatString(NumberFormat,
Workbook.FormatSettings, Decimals, CurrencySymbol); Workbook.FormatSettings, Decimals, CurrencySymbol);
NumFormatList.ConvertBeforeWriting(NumberFormatStr, NumberFormat, Decimals, CurrencyString); //NumFormatList.ConvertBeforeWriting(NumberFormatStr, NumberFormat, Decimals, CurrencyString);
end; end;
end; end;
*)
lIndex := FindFormattingInList(@lCell); lIndex := FindFormattingInList(@lCell);
// Carefully check the index // Carefully check the index