diff --git a/components/fpspreadsheet/source/common/fpsnumformat.pas b/components/fpspreadsheet/source/common/fpsnumformat.pas
index 020beb095..3222168f1 100644
--- a/components/fpspreadsheet/source/common/fpsnumformat.pas
+++ b/components/fpspreadsheet/source/common/fpsnumformat.pas
@@ -132,6 +132,8 @@ type
NumFormat: TsNumberFormat;
{@@ Number of decimal places used by the format string }
Decimals: Byte;
+ {@@ Minimum number of digits before the decimal separator }
+ MinIntDigits: Byte;
{@@ Factor by which a number will be multiplied before converting to string }
Factor: Double;
{@@ Digits to be used for the integer part of a fraction }
@@ -310,7 +312,8 @@ function BuildDateTimeFormatString(ANumberFormat: TsNumberFormat;
function BuildFractionFormatString(AMixedFraction: Boolean;
ANumeratorDigits, ADenominatorDigits: Integer): String;
function BuildNumberFormatString(ANumberFormat: TsNumberFormat;
- const AFormatSettings: TFormatSettings; ADecimals: Integer = -1): String;
+ const AFormatSettings: TFormatSettings; ADecimals: Integer = -1;
+ AMinIntDigits: Integer = 1): String;
function BuildFormatStringFromSection(const ASection: TsNumFormatSection): String;
@@ -1401,39 +1404,53 @@ end;
value of the FormatSettings is used. In case of a
fraction format "ADecimals" refers to the maximum count
digits of the denominator.
+ @param AMinIntDigits minimum count of integer digits, i.e. count of '0' in
+ the format string before the decimal separator
@return String of formatting codes
@example ANumberFormat = nfFixedTh, ADecimals = 2 --> '#,##0.00'
-------------------------------------------------------------------------------}
function BuildNumberFormatString(ANumberFormat: TsNumberFormat;
- const AFormatSettings: TFormatSettings; ADecimals: Integer = -1): String;
+ const AFormatSettings: TFormatSettings; ADecimals: Integer = -1;
+ AMinIntDigits: Integer = 1): String;
var
- decs: String;
+ decdigits: String;
+ intdigits: String;
begin
Result := '';
+ if AMinIntDigits > 0 then
+ intdigits := DupeString('0', AMinIntDigits)
+ else
+ intdigits := '#';
if ADecimals = -1 then
ADecimals := AFormatSettings.CurrencyDecimals;
- decs := DupeString('0', ADecimals);
- if ADecimals > 0 then decs := '.' + decs;
+ if ADecimals > 0 then
+ decdigits := '.' + DupeString('0', ADecimals)
+ else
+ decdigits := '';
case ANumberFormat of
nfText:
Result := '@';
nfFixed:
- Result := '0' + decs;
+ Result := intdigits + decdigits;
nfFixedTh:
- Result := '#,##0' + decs;
+ begin
+ while Length(IntDigits) < 4 do intDigits := '#' + intdigits;
+ System.Insert(',', intdigits, Length(intdigits)-2);
+ Result := intdigits + decdigits;
+ end;
nfExp:
- Result := '0' + decs + 'E+00';
+ Result := intdigits + decdigits + 'E+00';
nfPercentage:
- Result := '0' + decs + '%';
+ Result := intdigits + decdigits + '%';
nfFraction:
if ADecimals = 0 then // "ADecimals" has a different meaning here...
Result := '# ??/??' // This is the default fraction format
else
begin
- decs := DupeString('?', ADecimals);
- Result := '# ' + decs + '/' + decs;
+ decdigits := DupeString('?', ADecimals);
+ Result := '# ' + decdigits + '/' + decdigits;
end;
nfCurrency, nfCurrencyRed:
Result := BuildCurrencyFormatString(ANumberFormat, AFormatSettings,
@@ -2834,7 +2851,12 @@ begin
case section^.Elements[el].Token of
nftZeroDecs:
section^.Decimals := section^.Elements[el].IntValue;
- nftIntZeroDigit, nftIntOptDigit, nftIntSpaceDigit:
+ nftIntZeroDigit:
+ begin
+ section^.MinIntDigits := section^.Elements[el].IntValue;
+ i := section^.Elements[el].IntValue;
+ end;
+ nftIntOptDigit, nftIntSpaceDigit:
i := section^.Elements[el].IntValue;
nftFracNumSpaceDigit, nftFracNumZeroDigit:
section^.FracNumerator := section^.Elements[el].IntValue;
diff --git a/components/fpspreadsheet/source/common/fpsopendocument.pas b/components/fpspreadsheet/source/common/fpsopendocument.pas
index 810bbde89..fbb7013fe 100644
--- a/components/fpspreadsheet/source/common/fpsopendocument.pas
+++ b/components/fpspreadsheet/source/common/fpsopendocument.pas
@@ -853,7 +853,8 @@ begin
if (el+3 < nel) and (Elements[el+1].Token = nftExpChar) then
begin
Result := Result + ''
+ else
+ *)
// Standard integer
if (el = nel-1) or (Elements[el+1].Token <> nftDecSep) then
begin
Result := Result + ' 0) then
Result := Result + Format(' number:display-factor="%.0f"', [1.0/Factor]);
@@ -2962,13 +2972,13 @@ procedure TsSpreadOpenDocReader.ReadNumFormats(AStylesNode: TDOMNode);
nodeName: String;
nf: TsNumberFormat;
nfs: String;
- decs: Byte;
- sint: String;
s: String;
f: Double;
fracInt, fracNum, fracDenom: Integer;
grouping: Boolean;
nex: Integer;
+ nint: Integer;
+ ndecs: Integer;
cs: String;
color: TsColor;
hasColor: Boolean;
@@ -2987,9 +2997,8 @@ procedure TsSpreadOpenDocReader.ReadNumFormats(AStylesNode: TDOMNode);
end else
if nodeName = 'number:number' then
begin
- sint := GetAttrValue(node, 'number:min-integer-digits');
- if sint = '' then sint := '1';
-
+ s := GetAttrValue(node, 'number:min-integer-digits');
+ if s <> '' then nint := StrToInt(s) else nint := 0;
s := GetAttrValue(node, 'number:decimal-places');
if s = '' then
s := GetAttrValue(node, 'decimal-places');
@@ -2999,12 +3008,12 @@ procedure TsSpreadOpenDocReader.ReadNumFormats(AStylesNode: TDOMNode);
nfs := nfs + 'General';
end else
begin
- decs := StrToInt(s);
+ ndecs := StrToInt(s);
grouping := GetAttrValue(node, 'number:grouping') = 'true';
s := GetAttrValue(node, 'number:display-factor');
if s <> '' then f := StrToFloat(s, FPointSeparatorSettings) else f := 1.0;
nf := IfThen(grouping, nfFixedTh, nfFixed);
- nfs := nfs + BuildNumberFormatString(nf, Workbook.FormatSettings, decs); //, StrToInt(sint));
+ nfs := nfs + BuildNumberFormatString(nf, Workbook.FormatSettings, ndecs, nint);
if f <> 1.0 then begin
nf := nfCustom;
while (f > 1.0) do
@@ -3031,11 +3040,13 @@ procedure TsSpreadOpenDocReader.ReadNumFormats(AStylesNode: TDOMNode);
if nodeName = 'number:scientific-number' then
begin
nf := nfExp;
+ s := GetAttrValue(node, 'number:min-integer-digits');
+ if s <> '' then nint := StrToInt(s) else nint := 0;
s := GetAttrValue(node, 'number:decimal-places');
- if s <> '' then decs := StrToInt(s) else decs := 0;
+ if s <> '' then ndecs := StrToInt(s) else ndecs := 0;
s := GetAttrValue(node, 'number:min-exponent-digits');
if s <> '' then nex := StrToInt(s) else nex := 1;
- nfs := nfs + BuildNumberFormatString(nfFixed, Workbook.FormatSettings, decs);
+ nfs := nfs + BuildNumberFormatString(nfFixed, Workbook.FormatSettings, ndecs, nint);
nfs := nfs + 'E+' + DupeString('0', nex);
end else
if nodeName = 'number:currency-symbol' then
diff --git a/components/fpspreadsheet/source/common/fpspreadsheet.pas b/components/fpspreadsheet/source/common/fpspreadsheet.pas
index a6e32baeb..531401bf4 100644
--- a/components/fpspreadsheet/source/common/fpspreadsheet.pas
+++ b/components/fpspreadsheet/source/common/fpspreadsheet.pas
@@ -256,9 +256,11 @@ type
function WriteNumber(ARow, ACol: Cardinal; ANumber: double): PCell; overload;
procedure WriteNumber(ACell: PCell; ANumber: Double); overload;
function WriteNumber(ARow, ACol: Cardinal; ANumber: double;
- ANumFormat: TsNumberFormat; ADecimals: Byte = 2): PCell; overload;
+ ANumFormat: TsNumberFormat; ADecimals: Byte = 2;
+ AMinIntDigits: Integer = 1): PCell; overload;
procedure WriteNumber(ACell: PCell; ANumber: Double;
- ANumFormat: TsNumberFormat; ADecimals: Byte = 2); overload;
+ ANumFormat: TsNumberFormat; ADecimals: Byte = 2;
+ AMinIntDigits: Integer = 1); overload;
function WriteNumber(ARow, ACol: Cardinal; ANumber: double;
ANumFormat: TsNumberFormat; ANumFormatString: String): PCell; overload;
procedure WriteNumber(ACell: PCell; ANumber: Double;
@@ -4789,34 +4791,39 @@ end;
{@@ ----------------------------------------------------------------------------
Writes a floating-point number to a cell
- @param ARow Cell row index
- @param ACol Cell column index
- @param ANumber Number to be written
- @param ANumFormat Identifier for a built-in number format, e.g. nfFixed (optional)
- @param ADecimals Number of decimal places used for formatting (optional)
+ @param ARow Cell row index
+ @param ACol Cell column index
+ @param ANumber Number to be written
+ @param ANumFormat Identifier for a built-in number format,
+ e.g. nfFixed (optional)
+ @param ADecimals Number of decimal places used for formatting (optional)
+ @param AMinIntDigits Minimum count of digits before the decimal separator
@return Pointer to cell created or used
@see TsNumberFormat
-------------------------------------------------------------------------------}
function TsWorksheet.WriteNumber(ARow, ACol: Cardinal; ANumber: double;
- ANumFormat: TsNumberFormat; ADecimals: Byte = 2): PCell;
+ ANumFormat: TsNumberFormat; ADecimals: Byte = 2;
+ AMinIntDigits: Integer = 1): PCell;
begin
Result := GetCell(ARow, ACol);
- WriteNumber(Result, ANumber, ANumFormat, ADecimals);
+ WriteNumber(Result, ANumber, ANumFormat, ADecimals, AMinIntDigits);
end;
{@@ ----------------------------------------------------------------------------
Writes a floating-point number to a cell
- @param ACell Pointer to the cell
- @param ANumber Number to be written
- @param ANumFormat Identifier for a built-in number format, e.g. nfFixed
- @param ADecimals Optional number of decimal places used for formatting
- If ANumFormat is nfFraction the ADecimals defines the
- digits of Numerator and denominator.
+ @param ACell Pointer to the cell
+ @param ANumber Number to be written
+ @param ANumFormat Identifier for a built-in number format, e.g. nfFixed
+ @param ADecimals Optional number of decimal places used for formatting
+ If ANumFormat is nfFraction the ADecimals defines the
+ digits of Numerator and denominator.
+ @param AMinIntDigits Minimum count of digits before the decimal separator
@see TsNumberFormat
-------------------------------------------------------------------------------}
procedure TsWorksheet.WriteNumber(ACell: PCell; ANumber: Double;
- ANumFormat: TsNumberFormat; ADecimals: Byte = 2);
+ ANumFormat: TsNumberFormat; ADecimals: Byte = 2;
+ AMinIntDigits: Integer = 1);
var
fmt: TsCellFormat;
nfs: String;
@@ -4837,7 +4844,7 @@ begin
if ADecimals = 0 then ADecimals := 1;
nfs := '# ' + DupeString('?', ADecimals) + '/' + DupeString('?', ADecimals);
end else
- nfs := BuildNumberFormatString(fmt.NumberFormat, Workbook.FormatSettings, ADecimals);
+ nfs := BuildNumberFormatString(fmt.NumberFormat, Workbook.FormatSettings, ADecimals, AMinIntDigits);
fmt.NumberFormatIndex := Workbook.AddNumberFormat(nfs);
end else begin
Exclude(fmt.UsedFormattingFields, uffNumberFormat);
diff --git a/components/fpspreadsheet/tests/formattests.pas b/components/fpspreadsheet/tests/formattests.pas
index 83fcf9369..5262e392a 100644
--- a/components/fpspreadsheet/tests/formattests.pas
+++ b/components/fpspreadsheet/tests/formattests.pas
@@ -75,6 +75,8 @@ type
procedure TestWriteRead_MergedCells(AFormat: TsSpreadsheetFormat);
// Many XF records
procedure TestWriteRead_ManyXF(AFormat: TsSpreadsheetFormat);
+ // Format strings
+ procedure TestWriteRead_FormatStrings(AFormat: TsSpreadsheetFormat);
published
// Writes out numbers & reads back.
@@ -89,6 +91,7 @@ type
procedure TestWriteRead_BIFF2_MergedCells;
procedure TestWriteRead_BIFF2_NumberFormats;
procedure TestWriteRead_BIFF2_ManyXFRecords;
+ procedure TestWriteRead_BIFF2_FormatStrings;
// These features are not supported by Excel2 --> no test cases required!
// - Background
// - BorderStyle
@@ -107,6 +110,7 @@ type
procedure TestWriteRead_BIFF5_NumberFormats;
procedure TestWriteRead_BIFF5_TextRotation;
procedure TestWriteRead_BIFF5_WordWrap;
+ procedure TestWriteRead_BIFF5_FormatStrings;
{ BIFF8 Tests }
procedure TestWriteRead_BIFF8_Alignment;
@@ -120,6 +124,7 @@ type
procedure TestWriteRead_BIFF8_NumberFormats;
procedure TestWriteRead_BIFF8_TextRotation;
procedure TestWriteRead_BIFF8_WordWrap;
+ procedure TestWriteRead_BIFF8_FormatStrings;
{ ODS Tests }
procedure TestWriteRead_ODS_Alignment;
@@ -133,6 +138,7 @@ type
procedure TestWriteRead_ODS_NumberFormats;
procedure TestWriteRead_ODS_TextRotation;
procedure TestWriteRead_ODS_WordWrap;
+ procedure TestWriteRead_ODS_FormatStrings;
{ OOXML Tests }
procedure TestWriteRead_OOXML_Alignment;
@@ -146,6 +152,7 @@ type
procedure TestWriteRead_OOXML_NumberFormats;
procedure TestWriteRead_OOXML_TextRotation;
procedure TestWriteRead_OOXML_WordWrap;
+ procedure TestWriteRead_OOXML_FormatStrings;
{ CSV Tests }
procedure TestWriteRead_CSV_DateTimeFormats;
@@ -1677,6 +1684,84 @@ begin
TestWriteRead_ManyXF(sfExcel2);
end;
+
+procedure TSpreadWriteReadFormatTests.TestWriteRead_FormatStrings(
+ AFormat: TsSpreadsheetFormat);
+const
+ FormatStrings: Array[0..5] of string = (
+ '#', '#.000', '#.000E+00', '0', '0.000', '0.000E+00');
+ Numbers: array[0..4] of double = (
+ 0, 1.23456789, -1.23456789, 1234.56789, -1234.56789);
+var
+ MyWorkBook: TsWorkbook;
+ MyWorkSheet: TsWorksheet;
+ sollStr: String;
+ currStr: String;
+ currVal: Double;
+ r, c: Cardinal;
+ TempFile: String;
+begin
+ MyWorkbook := TsWorkbook.Create;
+ try
+ MyWorkSheet := MyWorkBook.AddWorksheet('Sheet');
+ for r := 0 to High(Numbers) do
+ for c := 0 to High(FormatStrings) do
+ MyWorksheet.WriteNumber(r, c, Numbers[r], nfCustom, FormatStrings[c]);
+ TempFile := NewTempFile;
+ MyWorkBook.WriteToFile(TempFile, AFormat, true);
+ finally
+ MyWorkbook.Free;
+ end;
+
+ MyWorkbook := TsWorkbook.Create;
+ try
+ MyWorkbook.ReadFromFile(TempFile, AFormat);
+ MyWorksheet := MyWorkbook.GetWorksheetByName('Sheet');
+ CheckEquals(High(Numbers), MyWorksheet.GetLastRowIndex, 'Row count mismatch');
+ CheckEquals(High(FormatStrings), MyWorksheet.GetLastColIndex, 'Col count mismatch');
+ for r := 0 to MyWorksheet.GetLastRowIndex do
+ for c := 0 to MyWorksheet.GetLastColIndex do begin
+ currStr := MyWorksheet.ReadAsText(r, c);
+ currVal := MyWorksheet.ReadAsNumber(r, c);
+ sollStr := FormatFloat(FormatStrings[c], currVal);
+ // Quick & dirty fix for FPC's issue with #.00E+00 showing a leading zero
+ if (sollStr <> '') and (sollStr[1] = '0') and
+ (pos('#.', FormatStrings[c]) = 1) and (pos('E', FormatStrings[c]) > 0)
+ then
+ Delete(sollStr, 1, 1);
+ CheckEquals(sollStr, currStr, Format('Formatted cell mismatch, FormatStr "%s", Cell %s', [
+ FormatStrings[c], GetCellString(r, c)]));
+ end;
+ finally
+ MyWorkbook.Free;
+ end;
+end;
+
+procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF2_FormatStrings;
+begin
+ TestWriteRead_FormatStrings(sfExcel2);
+end;
+
+procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF5_FormatStrings;
+begin
+ TestWriteRead_FormatStrings(sfExcel5);
+end;
+
+procedure TSpreadWriteReadFormatTests.TestWriteRead_BIFF8_FormatStrings;
+begin
+ TestWriteRead_FormatStrings(sfExcel8);
+end;
+
+procedure TSpreadWriteReadFormatTests.TestWriteRead_OOXML_FormatStrings;
+begin
+ TestWriteRead_FormatStrings(sfOOXML);
+end;
+
+procedure TSpreadWriteReadFormatTests.TestWriteRead_ODS_FormatStrings;
+begin
+ TestWriteRead_FormatStrings(sfOpenDocument);
+end;
+
initialization
RegisterTest(TSpreadWriteReadFormatTests);
InitSollFmtData;
diff --git a/components/fpspreadsheet/tests/numformatparsertests.pas b/components/fpspreadsheet/tests/numformatparsertests.pas
index 63d06e5a9..53b712918 100644
--- a/components/fpspreadsheet/tests/numformatparsertests.pas
+++ b/components/fpspreadsheet/tests/numformatparsertests.pas
@@ -35,7 +35,7 @@ type
var
ParserTestData: Array[0..13] of TParserTestData;
- RoundingTestData: Array[0..62] of TRoundingTestData = (
+ RoundingTestData: Array[0..65] of TRoundingTestData = (
// 0
(FormatString: '0'; Number: 1.2; SollString: '1'),
(FormatString: '0'; Number: 1.9; SollString: '2'),
@@ -109,7 +109,12 @@ var
(FormatString: '0.0##'; Number: 1.21; SollString: '1.21'),
(FormatString: '0.0##'; Number: 1.212; SollString: '1.212'),
(FormatString: '0.0##'; Number: 1.2134; SollString: '1.213'),
- (FormatString: '0.0##'; Number: 1.2135; SollString: '1.214')
+ (FormatString: '0.0##'; Number: 1.2135; SollString: '1.214'),
+
+ // 63
+ (FormatString: '#'; Number: 0; SollString: ''),
+ (FormatString: '#'; Number: 1.2; SollString: '1'),
+ (FormatString: '#'; Number: -1.2; SollString: '-1')
);
diff --git a/components/fpspreadsheet/tests/spreadtestgui.lpi b/components/fpspreadsheet/tests/spreadtestgui.lpi
index 05b79648b..6bf720977 100644
--- a/components/fpspreadsheet/tests/spreadtestgui.lpi
+++ b/components/fpspreadsheet/tests/spreadtestgui.lpi
@@ -1,7 +1,7 @@
-
+
@@ -19,9 +19,10 @@
-
-
-
+
+
+
+