fpspreadsheet: Bug fixes for biff2 reading/writing for number formats

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3075 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2014-05-21 23:01:07 +00:00
parent cd13fe49a0
commit fb92d5a906
8 changed files with 241 additions and 528 deletions

View File

@ -43,7 +43,7 @@ begin
MyWorksheet.LeftPaneWidth := 1;
MyWorksheet.TopPaneHeight := 3;
}
(*
// Write some number cells
MyWorksheet.WriteNumber(0, 0, 1.0);
MyWorksheet.WriteUsedFormatting(0, 0, [uffBold, uffNumberFormat]);
@ -82,7 +82,7 @@ begin
MyWorksheet.WriteUTF8Text(1, 1, 'Second');
MyWorksheet.WriteUTF8Text(1, 2, 'Third');
MyWorksheet.WriteUTF8Text(1, 3, 'Fourth');
*)
// Write current date/time
MyWorksheet.WriteDateTime(2, 0, now);
@ -267,7 +267,6 @@ begin
MyWorksheet.WriteFontColor(r, 3, scGray);
MyWorksheet.WriteNumber(r, 4, -1.0/number, nfExp, 3);
MyWorksheet.WriteFontColor(r, 4, scGray);
inc(r,2);
MyWorksheet.WriteUTF8Text(r, 0, 'nfCurrency, 0 decs');
MyWorksheet.WriteNumber(r, 1, number, nfCurrency, 0, '$');
@ -288,7 +287,6 @@ begin
MyWorksheet.WriteNumber(r, 1, number, nfCurrencyDashRed, 0, 'USD');
MyWorksheet.WriteNumber(r, 2, -number, nfCurrencyDashRed, 0, 'USD');
MyWorksheet.WriteNumber(r, 3, 0.0, nfCurrencyDashRed, 0, 'USD');
inc(r, 2);
MyWorksheet.WriteUTF8Text(r, 0, 'nfCustom, "$"#,##0_);("$"#,##0)');
MyWorksheet.WriteNumber(r, 1, number);

View File

@ -55,7 +55,6 @@ begin
}
// Write some cells
// MyWorksheet.WriteDateTime(0, 20, now, nfShortTime); //1.0);// A1
MyWorksheet.WriteNumber(0, 0, 1.0, nfFixed, 3);// A1
MyWorksheet.WriteNumber(0, 1, 2.0);// B1
MyWorksheet.WriteNumber(0, 2, 3.0);// C1
@ -133,7 +132,7 @@ begin
MyWorksheet.WriteFont(8, 3, 'Courier New', 12, [fssUnderline], scBlue);
MyWorksheet.WriteBackgroundColor(8, 3, scYellow);
(*
{
// Uncomment this to test large XLS files
for i := 50 to 1000 do
begin
@ -142,7 +141,7 @@ begin
// MyWorksheet.WriteUTF8Text(i, 2, ParamStr(0));
MyWorksheet.WriteUTF8Text(i, 3, ParamStr(0));
end;
*)
}
// Write the formula E1 = A1 + B1
SetLength(MyRPNFormula, 3);

View File

@ -550,11 +550,17 @@ begin
if FIsTime then ScanDateTimeParts(token, 'm', s) else ScanDateTimeParts(token, 'M', s);
end;
'm':
if FConversionDirection = cdToFPSpreadsheet then
ScanDateTimeParts(token, 'n', s)
else
ScanDateTimeParts(token, 'm', s);
{
if FConversionDirection = cdToFPSpreadsheet then begin
if FIsTime then ScanDateTimeParts(token, 'n', s) else ScanDateTimeParts(token, 'm', s)
end else begin
if FIsTime then ScanDateTimeParts(token, 'm', s) else ScanDateTimeParts(token, 'M', s);
end;
}
'N', 'n':
if FConversionDirection = cdToFPSpreadsheet then begin
// "n" is not used by file format --> stop scanning date/time

View File

@ -581,11 +581,11 @@ type
var ANumFormat: TsNumberFormat; var ADecimals: Byte;
var ACurrencySymbol: String); virtual;
procedure Delete(AIndex: Integer);
function Find(AFormatCell: PCell): integer; overload;
function Find(ANumFormat: TsNumberFormat; AFormatString: String;
ADecimals: Byte; ACurrencySymbol: String): Integer; overload;
function Find(AFormatIndex: Integer): Integer; overload;
function Find(AFormatString: String): Integer; overload;
function FindFormatOf(AFormatCell: PCell): integer; virtual;
function FormatStringForWriting(AIndex: Integer): String; virtual;
procedure Sort;
@ -642,7 +642,7 @@ type
function FindFormattingInList(AFormat: PCell): Integer;
procedure FixFormat(ACell: PCell); virtual;
procedure ListAllFormattingStylesCallback(ACell: PCell; AStream: TStream);
procedure ListAllFormattingStyles;
procedure ListAllFormattingStyles; virtual;
procedure ListAllNumFormatsCallback(ACell: PCell; AStream: TStream);
procedure ListAllNumFormats; virtual;
{ Helpers for writing }
@ -999,11 +999,6 @@ procedure TsWorksheet.CopyCell(AFromRow, AFromCol, AToRow, AToCol: Cardinal;
AFromWorksheet: TsWorksheet);
var
lSrcCell, lDestCell: PCell;
{
lCurStr: String;
lCurUsedFormatting: TsUsedFormattingFields;
lCurColor: TsColor;
}
begin
lSrcCell := AFromWorksheet.FindCell(AFromRow, AFromCol);
lDestCell := GetCell(AToRow, AToCol);
@ -1564,26 +1559,9 @@ procedure TsWorksheet.WriteDateTime(ARow, ACol: Cardinal; AValue: TDateTime;
AFormat: TsNumberFormat = nfShortDateTime; AFormatStr: String = '');
var
ACell: PCell;
//fmt: String;
//parser: TsNumFormatParser;
begin
if (AFormat in [nfFmtDateTime, nfTimeInterval]) then
AFormatStr := BuildDateTimeFormatString(AFormat, Workbook.FormatSettings, AFormatStr);
(*
parser := TsNumFormatParser.Create(Workbook, AFormatStr, cdToFPSpreadsheet);
try
// Check that the format string can be reckognized.
if parser.Status <> psOK then
raise Exception.Create(lpNoValidNumberFormatString);
// Check that the format string belongs to date/time values
if not (IsDateTimeFormat(parser.Builtin_NumFormat) or (parser.Builtin_NumFormat = nfFmtDateTime))
then raise Exception.Create(lpNoValidDateTimeFormatString);
AFormatStr := parser.FormatString;
finally
parser.Free;
end;
end;
*)
ACell := GetCell(ARow, ACol);
ACell^.ContentType := cctDateTime;
@ -2268,9 +2246,7 @@ end;
It is added to the end of the list of worksheets
@param AName The name of the new worksheet
@return The instace of the newly created worksheet
@see TsWorkbook
}
function TsWorkbook.AddWorksheet(AName: string): TsWorksheet;
@ -2790,114 +2766,6 @@ begin
end;
end;
(*
procedure TsCustomNumFormatList.Analyze(AFormatIndex: Integer;
var AFormatString: String; var ANumFormat: TsNumberFormat;
var ADecimals: Byte; var ACurrencySymbol: String);
const
SHORT_LONG_DATE: array[boolean] of TsNumberFormat = (
nfShortDate, nfLongDate
);
AMPM_SHORT_LONG_TIME: array[boolean, boolean] of TsNumberFormat = (
(nfShortTime, nfLongTime),
(nfShortTimeAM, nfLongTimeAM)
);
EXP_SCI: array[boolean] of TsNumberFormat = (
nfExp, nfSci
);
CURR_FMT: array[boolean, boolean] of TsNumberFormat = (
(nfCurrency, nfCurrencyDash),
(nfCurrencyRed, nfCurrencyDashRed)
);
var
decs: Word;
isAMPM: Boolean;
isLongTime: Boolean;
isLongDate: Boolean;
isInterval: Boolean;
isSci: Boolean;
isTime, isDate: Boolean;
isCurrRed, isCurrDash: Boolean;
lFormatData: TsNumFormatData;
i: Integer;
fmt: String;
begin
{ Check the built-in formats first }
i := Find(AFormatIndex);
if i > 0 then begin
lFormatData := Items[i];
case lFormatData.NumFormat of
nfFixed, nfFixedTh, nfPercentage, nfExp, nfSci:
begin
ANumFormat := lFormatData.NumFormat;
AFormatString := lFormatData.FormatString;
ADecimals := lFormatData.Decimals;
exit;
end;
nfShortDateTime, nfShortDate, nfLongDate,
nfShortTime, nfLongTime, nfShortTimeAM, nfLongTimeAM, nfTimeInterval:
begin
ANumFormat := lFormatData.NumFormat;
AFormatString := lFormatData.FormatString;
ADecimals := 0;
exit;
end;
nfFmtDateTime:
begin
ANumFormat := lFormatData.NumFormat;
AFormatString := lFormatData.FormatString;
IsTimeFormat(AFormatString, isLongTime, isAMPM, isInterval, ADecimals);
exit;
end;
nfCurrency, nfCurrencyRed, nfCurrencyDash, nfCurrencyDashRed:
begin
ANumFormat := lFormatData.NumFormat;
AFormatString := lFormatData.FormatString;
ADecimals := lFormatData.Decimals;
ACurrencySymbol := lFormatData.CurrencySymbol;
end;
end;
end;
{ Then check the non-standard formats. There is a chance that they may not be
reckognized correctly... }
ANumFormat := nfGeneral;
if IsPercentNumberFormat(AFormatString, ADecimals) then
ANumFormat := nfPercentage
else
if IsExpNumberFormat(AFormatstring, ADecimals, isSci) then
ANumFormat := EXP_SCI[isSci]
else
if IsThousandSepNumberFormat(AFormatString, ADecimals) then
ANumFormat := nfFixedTh
else
if IsFixedNumberFormat(AFormatString, ADecimals) then
ANumFormat := nfFixed
else
if IsCurrencyFormat(AFormatString, ADecimals, ACurrencySymbol, isCurrRed, isCurrDash) then
ANumFormat := CURR_FMT[isCurrRed, isCurrDash]
else begin
isTime := IsTimeFormat(AFormatString, isLongTime, isAMPM, isInterval, ADecimals);
isDate := IsDateFormat(AFormatString, isLongDate);
if isInterval then
ANumFormat := nfTimeInterval
else
if isDate and isTime then
ANumFormat := nfShortDateTime
else if isDate then
ANumFormat := SHORT_LONG_DATE[isLongDate]
else if isTime then begin
if (ADecimals > 0) and (not isAMPM) then
ANumFormat := nfFmtDateTime
else
ANumFormat := AMPM_SHORT_LONG_TIME[isAMPM, isLongTime]
end
else if AFormatString <> '' then
ANumFormat := nfCustom;
end;
end;
*)
{ Called from the reader when a format item has been read from the file.
Determines the numFormat type, format string etc and stores the format in the
list. If necessary, the format string has to be made compatible with fpc
@ -2936,17 +2804,6 @@ begin
Delete(AIndex);
end;
{ Determines whether the format attributed to the given cell is already
contained in the list and returns its list index. }
function TsCustomNumFormatList.Find(AFormatCell: PCell): integer;
begin
if AFormatCell = nil then
Result := -1
else
Result := Find(AFormatCell^.NumberFormat, AFormatCell^.NumberFormatStr,
AFormatCell^.Decimals, AFormatCell^.CurrencySymbol);
end;
{ Seeks a format item with the given properties and returns its list index,
or -1 if not found. }
function TsCustomNumFormatList.Find(ANumFormat: TsNumberFormat;
@ -2956,17 +2813,6 @@ var
fmt: String;
itemfmt: String;
begin
(*
// These are pre-defined formats - no need to check format string & decimals
if ANumFormat in [ nfGeneral, nfShortDateTime, nfShortDate, nfLongDate,
nfShortTime, nfLongTime, nfShortTimeAM, nfLongTimeAM ]
then
for Result := 0 to Count-1 do begin
item := Items[Result];
if (item <> nil) and (item.NumFormat = ANumFormat) then
exit;
end;
*)
if (ANumFormat = nfFmtDateTime) then begin
fmt := lowercase(AFormatString);
for Result := Count-1 downto 0 do begin
@ -3054,6 +2900,17 @@ begin
Result := -1;
end;
{ Determines whether the format attributed to the given cell is already
contained in the list and returns its list index. }
function TsCustomNumFormatList.FindFormatOf(AFormatCell: PCell): integer;
begin
if AFormatCell = nil then
Result := -1
else
Result := Find(AFormatCell^.NumberFormat, AFormatCell^.NumberFormatStr,
AFormatCell^.Decimals, AFormatCell^.CurrencySymbol);
end;
{ Determines the format string to be written into the spreadsheet file.
Needs to be overridden if the format strings are different from the fpc
convention. }
@ -3263,17 +3120,6 @@ procedure TsCustomSpreadWriter.FixFormat(ACell: PCell);
begin
// to be overridden
end;
(*
var
isLong, isAMPM, isInterval: Boolean;
decs: Byte;
begin
if ACell^.NumberFormat = nfFmtDateTime then begin
decs := CountDecs(ACell^.NumberFormatStr, ['0', 'z', 'Z']);
// if IsTimeFormat(ACell^.NumberFormatStr, isLong, isAMPM, isInterval, decs) then
ACell^.Decimals := decs;
end;
end; *)
{ Each descendent should define its own default formats, if any.
Always add the normal, unformatted style first to speed things up. }
@ -3442,42 +3288,7 @@ begin
Inc(StrPos);
end;
end;
(*
function TsCustomSpreadWriter.FPSColorToHexString(AColor: TsColor; ARGBColor: TFPColor): string;
{ We use RGB bytes here, but please note that these are physically written
to XLS file as ABGR (where A is 0) }
begin
case AColor of
scBlack: Result := '000000';
scWhite: Result := 'FFFFFF';
scRed: Result := 'FF0000';
scGREEN: Result := '00FF00';
scBLUE: Result := '0000FF';
scYELLOW: Result := 'FFFF00';
scMAGENTA: Result := 'FF00FF';
scCYAN: Result := '00FFFF';
scDarkRed: Result := '800000';
scDarkGreen:Result := '008000';
scDarkBlue: Result := '000080';
scOLIVE: Result := '808000';
scPURPLE: Result := '800080';
scTEAL: Result := '008080';
scSilver: Result := 'C0C0C0';
scGrey: Result := '808080';
//
scGrey10pct:Result := 'E6E6E6';
scGrey20pct:Result := 'CCCCCC';
scOrange: Result := 'FFA500';
scDarkBrown:Result := 'A0522D';
scBrown: Result := 'CD853F';
scBeige: Result := 'F5F5DC';
scWheat: Result := 'F5DEB3';
//
scRGBCOLOR: Result := Format('%x%x%x', [ARGBColor.Red div $100, ARGBColor.Green div $100, ARGBColor.Blue div $100]);
end;
end;
*)
{@@
Helper function for the spreadsheet writers.
@ -3812,3 +3623,32 @@ finalization
end.
{ Strategy for handling of number formats:
Problem:
For number formats, fpspreadsheet uses a syntax which is slightly different from
the syntax that Excel uses in the xls files. Moreover, the file syntax can be
different from file type to file type (biff2, for example, allows only a few
predefined formats, while the number of allowed formats is unlimited (?) for
biff8.
Number format handling in fpspreadsheet is implemented with the following
concept in mind:
- Formats written into TsWorksheet cells always follow the fpspreadsheet syntax.
The exception is for the format nfCustom for which the format strings are
left untouched.
- For writing, the writer creates a TsNumFormatList which stores all formats
in file syntax.
- The built-in formats of the file types are coded in the file syntax.
- The method "ConvertBeforeWriting" converts the cell formats from the
fpspreadsheet to the file syntax.
- For reading, the reader creates another TsNumFormatList.
- The built-in formats of the file types are coded again in file syntax.
- The formats read from the file are added in file syntax.
- After reading, the formats are converted to fpspreadsheet syntax
("ConvertAfterReading").
}

View File

@ -42,9 +42,12 @@ type
TsBIFF2NumFormatList = class(TsCustomNumFormatList)
protected
procedure AddBuiltinFormats; override;
procedure ConvertBeforeWriting(var AFormatString: String;
var ANumFormat: TsNumberFormat; var ADecimals: Byte; var ACurrencySymbol: String); override;
function FindFormatOf(AFormatCell: PCell): Integer; override;
public
constructor Create(AWorkbook: TsWorkbook);
// function FormatStringForWriting(AIndex: Integer): String; override;
function FormatStringForWriting(AIndex: Integer): String; override;
end;
{ TsSpreadBIFF2Reader }
@ -82,7 +85,7 @@ type
TsSpreadBIFF2Writer = class(TsSpreadBIFFWriter)
private
function FindXFIndex(ACell: PCell): Word;
function FindXFIndex(ACell: PCell): Word;
{ Record writing methods }
procedure WriteBOF(AStream: TStream);
procedure WriteCellFormatting(AStream: TStream; ACell: PCell; XFIndex: Word);
@ -100,7 +103,7 @@ type
procedure WriteXFRecords(AStream: TStream);
protected
procedure CreateNumFormatList; override;
procedure FixFormat(ACell: PCell); override;
procedure ListAllFormattingStyles; override;
procedure ListAllNumFormats; override;
procedure WriteBlank(AStream: TStream; const ARow, ACol: Cardinal; ACell: PCell); override;
procedure WriteFormat(AStream: TStream; AFormatData: TsNumFormatData;
@ -172,19 +175,24 @@ begin
end;
procedure TsBIFF2NumFormatList.AddBuiltinFormats;
var
ds, ts, cs: string;
begin
ds := DefaultFormatSettings.DecimalSeparator;
ts := DefaultFormatSettings.ThousandSeparator;
cs := DefaultFormatSettings.CurrencyString;
AddFormat( 0, '', nfGeneral);
AddFormat( 1, '0', nfFixed, 0);
AddFormat( 2, '0.00', nfFixed, 2);
AddFormat( 3, '#,##0', nfFixedTh, 0);
AddFormat( 4, '#,##0.00', nfFixedTh, 2);
AddFormat( 5, '"$"#,##0_);("$"#,##0)', nfCurrency, 0);
AddFormat( 6, '"$"#,##0_);[Red]("$"#,##0)', nfCurrencyRed, 2);
AddFormat( 7, '"$"#,##0.00_);("$"#,##0.00)', nfCurrency, 0);
AddFormat( 8, '"$"#,##0.00_);[Red]("$"#,##0.00)', nfCurrency, 2);
AddFormat( 2, '0'+ds+'00', nfFixed, 2); // 0.00
AddFormat( 3, '#'+ts+'##0', nfFixedTh, 0); // #,##0
AddFormat( 4, '#'+ts+'##0'+ds+'00', nfFixedTh, 2); // #,##0.00
AddFormat( 5, UTF8ToAnsi('"'+cs+'"#'+ts+'##0_);("'+cs+'"#'+ts+'##0)'), nfCurrency, 0);
AddFormat( 6, UTF8ToAnsi('"'+cs+'"#'+ts+'##0_);[Red]("'+cs+'"#'+ts+'##0)'), nfCurrencyRed, 2);
AddFormat( 7, UTF8ToAnsi('"'+cs+'"#'+ts+'##0'+ds+'00_);("'+cs+'"#'+ts+'##0'+ds+'00)'), nfCurrency, 0);
AddFormat( 8, UTF8ToAnsi('"'+cs+'"#'+ts+'##0'+ds+'00_);[Red]("'+cs+'"#'+ts+'##0'+ds+'00)'), nfCurrency, 2);
AddFormat( 9, '0%', nfPercentage, 0);
AddFormat(10, '0.00%', nfPercentage, 2);
AddFormat(11, '0.00E+00', nfExp, 2);
AddFormat(10, '0'+ds+'00%', nfPercentage, 2);
AddFormat(11, '0'+ds+'00E+00', nfExp, 2);
AddFormat(12, 'M/D/YY', nfShortDate);
AddFormat(13, 'D-MMM-YY', nfLongDate);
AddFormat(14, 'D-MMM', nfFmtDateTime);
@ -199,40 +207,126 @@ begin
FNextFormatIndex := 21; // not needed - there are not user-defined formats
end;
{ Creates formatting strings that are written into the file. }
(*
function TsBIFF2NumFormatList.FormatStringForWriting(AIndex: Integer): String;
procedure TsBIFF2NumFormatList.ConvertBeforeWriting(var AFormatString: String;
var ANumFormat: TsNumberFormat; var ADecimals: Byte; var ACurrencySymbol: String);
var
ds, ts, cs: string;
fmt: String;
begin
ds := DefaultFormatSettings.DecimalSeparator;
ts := DefaultFormatSettings.ThousandSeparator;
cs := DefaultFormatSettings.CurrencyString;
case AIndex of
0: Result := 'General';
1: Result := '0';
2: Result := '0' + ds + '00'; // 0.00
3: Result := '#' + ts + '#0'; // #,##0
4: Result := '#' + ts + '#0' + ds + '0'; // #,##0.00
5: Result := UTF8ToAnsi(Format('"%s"#%s##0_);("%s"#%s##0)', [cs, ts, cs, ts]));
6: Result := UTF8ToAnsi(Format('"%s"#%s##0_);[Red]("%s"#%s##0)', [cs, ts, cs, ts]));
7: Result := UTF8ToAnsi(Format('"%s"#%s##0%s00_);("%s"#%s##0%s00)', [cs, ts, ds, cs, ts, ds]));
8: Result := UTF8ToAnsi(Format('"%s"#%s##0%s00_);[Red]("%s"#%s##0%s00)', [cs, ts, ds, cs, ts, ds]));
9: Result := '0%';
10: Result := '0' + ds + '00%'; // 0.00%
11: Result := '0' + ds + '00E+00'; // 0.00E+00
12: Result := 'm/d/yy';
13: Result := 'd-mmm-yy';
14: Result := 'd-mmm';
15: Result := 'mmm-yy';
16: Result := 'h:mm AM/PM';
17: Result := 'h:mm:ss AM/PM';
18: Result := 'h:mm';
19: Result := 'h:mm:ss';
20: Result := 'm/d/yy h:mm';
case ANumFormat of
nfGeneral:
;
nfFixed, nfFixedTh, nfPercentage, nfExp,
nfCurrency, nfCurrencyRed, nfCurrencyDash, nfCurrencyDashRed:
if ADecimals > 0 then ADecimals := 2;
nfSci:
begin
if ADecimals > 0 then ADecimals := 2;
ANumFormat := nfExp;
end;
nfFmtDateTime:
begin
fmt := lowercase(AFormatString);
if (fmt = 'd-mm') or (fmt = 'd/mm') or
(fmt = 'dd-mm') or (fmt = 'dd/mm') or
(fmt = 'dd-mmm') or (fmt = 'dd/mmm')
then
AFormatString := 'D-MMM'
else
if (fmt = 'm-yy') or (fmt = 'm/yy') or
(fmt = 'mm-yy') or (fmt = 'mm/yy') or
(fmt = 'mmm-yy') or (fmt = 'mmm/yy') or
(fmt = 'm-yyyy') or (fmt = 'm/yyyy') or
(fmt = 'mm-yyyy') or (fmt = 'mm/yyyy') or
(fmt = 'mmm-yyyy') or (fmt = 'mmm-yyyy')
then
AFormatString := 'MMM-YY'
else
if (copy(fmt, 1, 5) = 'nn:ss') or (copy(fmt, 1, 5) = 'mm:ss') or
(copy(fmt, 1, 4) = 'n:ss') or (copy(fmt, 1, 4) = 'm:ss')
then
ANumFormat := nfLongTime
else
ANumFormat := nfShortDateTime;
end;
nfCustom, nfTimeInterval:
begin
ANumFormat := nfGeneral;
AFormatString := '';
ADecimals := 0;
end;
end;
end;
*)
function TsBIFF2NumFormatList.FindFormatOf(AFormatCell: PCell): Integer;
var
fmt: String;
begin
case AFormatCell^.NumberFormat of
nfGeneral : Result := 0;
nfFixed : Result := IfThen(AFormatCell^.Decimals = 0, 1, 2);
nfFixedTh : Result := IfThen(AFormatCell^.Decimals = 0, 3, 4);
nfCurrency,
nfCurrencyDash : Result := IfThen(AFormatCell^.Decimals = 0, 5, 7);
nfCurrencyRed,
nfCurrencyDashRed: Result := IfThen(AFormatCell^.Decimals = 0, 6, 8);
nfPercentage : Result := IfThen(AFormatCell^.Decimals = 0, 9, 10);
nfExp, nfSci : Result := 11;
nfShortDate : Result := 12;
nfLongDate : Result := 13;
nfShortTimeAM : Result := 16;
nfLongTimeAM : Result := 17;
nfShortTime : Result := 18;
nfLongTime : Result := 19;
nfShortDateTime : Result := 20;
nfFmtDateTime : begin
fmt := lowercase(AFormatCell^.NumberFormatStr);
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;
nfCustom,
nfTimeInterval : Result := 0;
end;
end;
{ Creates formatting strings that are written into the file. These are the
strings in the format list. The only exception is the nfGeneral entry which
is written as "General". }
function TsBIFF2NumFormatList.FormatStringForWriting(AIndex: Integer): String;
begin
Result := inherited FormatStringForWriting(AIndex);
if Result = '' then
Result := 'General';
end;
{ TsSpreadBIFF2Reader }
@ -707,57 +801,62 @@ end;
function TsSpreadBIFF2Writer.FindXFIndex(ACell: PCell): Word;
var
i: Integer;
lIndex: Integer;
lCell: TCell;
begin
// First try the fast methods for default formats
if ACell^.UsedFormattingFields = [] then
Result := 15
else begin
// If not, then we need to search in the list of dynamic formats
i := FindFormattingInList(ACell);
// But we have to consider that the number formats of the cell is in fpc syntax,
// but the number format list of the writer is in Excel syntax.
// And for BIFF2, there is only a limited number of formats.
lCell := ACell^;
with lCell do begin
if IsDateTimeFormat(NumberFormat) then
NumberFormatStr := BuildDateTimeFormatString(NumberFormat,
Workbook.FormatSettings, NumberFormatStr)
else
NumberFormatStr := BuildNumberFormatString(NumberFormat,
Workbook.FormatSettings, Decimals, CurrencySymbol);
NumFormatList.ConvertBeforeWriting(NumberFormatStr, NumberFormat, Decimals, CurrencyString);
end;
lIndex := FindFormattingInList(@lCell);
// Carefully check the index
if (i < 0) or (i > Length(FFormattingStyles)) then
if (lIndex < 0) or (lIndex > Length(FFormattingStyles)) then
raise Exception.Create('[TsSpreadBIFF2Writer.WriteXFIndex] Invalid Index, this should not happen!');
Result := FFormattingStyles[i].Row;
Result := FFormattingStyles[lIndex].Row;
end;
end;
procedure TsSpreadBIFF2Writer.FixFormat(ACell: PCell);
procedure TsSpreadBIFF2Writer.ListAllFormattingStyles;
var
j: Integer;
i: Integer;
begin
case ACell.NumberFormat of
nfExp:
if ACell.Decimals <> 2 then begin
ACell.Decimals := 2;
ACell.CurrencySymbol := '';
ACell.NumberFormatStr := '0.00E+00';
end;
nfSci:
begin
ACell.NumberFormat := nfExp;
ACell.NumberFormatStr := '0.00E+00';
ACell.Decimals := 2;
ACell.CurrencySymbol := '';
end;
nfFmtDateTime:
begin
j := NumFormatList.Find(ACell);
if j = -1 then ACell.NumberFormat := nfLongTime;
end;
nfCustom:
ACell.NumberFormat := nfGeneral;
end;
inherited ListAllFormattingStyles;
for i:=0 to High(FFormattingStyles) do
FNumFormatList.ConvertBeforeWriting(
FFormattingStyles[i].NumberFormatStr,
FFormattingStyles[i].NumberFormat,
FFormattingStyles[i].Decimals,
FFormattingStyles[i].CurrencySymbol
);
end;
{ Builds up the list of number formats to be written to the biff2 file.
Unlike biff5+ no formats are added here because biff2 supports only 21
standard formats; these formats have been added by the NumFormatList's
AddBuiltInFormats. }
procedure TsSpreadBIFF2Writer.ListAllNumFormats;
begin
// Nothing to do. All formats have already been added by the NumFormatList.
// Nothing to do here.
end;
{
Attaches cell formatting data for the given cell to the current record.
Is called from all writing methods of cell contents.
}
{ Attaches cell formatting data for the given cell to the current record.
Is called from all writing methods of cell contents. }
procedure TsSpreadBIFF2Writer.WriteCellFormatting(AStream: TStream; ACell: PCell;
XFIndex: Word);
var
@ -1002,17 +1101,9 @@ begin
// Now apply the modifications.
if uffNumberFormat in FFormattingStyles[i].UsedFormattingFields then begin
j := NumFormatList.Find(@FFormattingStyles[i]);
if j > -1 then begin
j := NumFormatList.FindFormatOf(@FFormattingStyles[i]);
if j > -1 then
lFormatIndex := NumFormatList[j].Index;
{
// BIFF2 can only handle the 21 built-in formats. Here we find replacements
// for the others.
case NumFormatList[j].NumFormat of
nfSci : lFormatIndex := 11; // Exp
nfFmtDateTime : if lFormatIndex > 20 then lFormatIndex := 19;
end;}
end;
end;
if uffBorder in FFormattingStyles[i].UsedFormattingFields then
@ -1162,26 +1253,6 @@ begin
AStream.WriteByte(len); // AnsiString, char count in 1 byte
AStream.WriteBuffer(s[1], len); // String data
end;
(*
procedure TsSpreadBIFF2Writer.WriteFormat(AStream: TStream; AFormatCode: String);
var
len: Integer;
s: AnsiString;
begin
if AFormatCode = '' then
exit;
s := AFormatCode;
len := Length(s);
{ BIFF record header }
AStream.WriteWord(WordToLE(INT_EXCEL_ID_FORMAT));
AStream.WriteWord(WordToLE(len + 1));
{ Write format string }
AStream.WriteByte(len);
AStream.WriteBuffer(s[1], len);
end; *)
procedure TsSpreadBIFF2Writer.WriteFormatCount(AStream: TStream);
begin
@ -1189,83 +1260,6 @@ begin
AStream.WriteWord(WordToLE(2));
AStream.WriteWord(WordToLE(21)); // there are 21 built-in formats
end;
(*
procedure TsSpreadBIFF2Writer.WriteFormats(AStream: TStream);
var
i: Integer;
begin
WriteFormatCount(AStream);
for i:=0 to High(NUMFORMAT_BIFF2) do
WriteFormat(AStream, NUMFORMAT_BIFF2[i]);
end;*)
(*
var
ds, ts: Char; //decimal separator, thousand separator
begin
ds := DefaultFormatSettings.DecimalSeparator;
ts := DefaultFormatSettings.ThousandSeparator;
{ 0} WriteFormat(AStream, 'General'); // 0
{ 1} WriteFormat(AStream, '0');
{ 2} WriteFormat(AStream, '0'+ds+'00'); // 0.00
{ 3} WriteFormat(AStream, '#'+ts+'##0'); // #,##0
{ 4} WriteFormat(AStream, '#'+ts+'##0'+ds+'00'); // #,##0.00
{ 5} WriteFormat(AStream, '"$"#'+ts+'##0_);("$"#'+ts+'##0)');
{ 6} WriteFormat(AStream, '"$"#'+ts+'##0_);[Red]("$"#'+ts+'##0)');
{ 7} WriteFormat(AStream, '"$"#'+ts+'##0'+ds+'00_);("$"#'+ts+'##0'+ds+'00)');
{ 8} WriteFormat(AStream, '"$"#'+ts+'##0'+ds+'00_);[Red]("$"#'+ts+'##0'+ds+'00)');
{ 9} WriteFormat(AStream, '0%');
{10} WriteFormat(AStream, '0'+ds+'00%'); // 0.00%
{11} WriteFormat(AStream, '0'+ds+'00E+00'); // 0.00E+00
{12} WriteFormat(AStream, 'm/d/yy');
{13} WriteFormat(AStream, 'd-mmm-yy');
{14} WriteFormat(AStream, 'd-mmm');
{15} WriteFormat(AStream, 'mmm-yy');
{16} WriteFormat(AStream, 'h:mm AM/PM');
{17} WriteFormat(AStream, 'h:mm:ss AM/PM');
{18} WriteFormat(AStream, 'h:mm');
{19} WriteFormat(AStream, 'h:mm:ss');
{20} WriteFormat(AStream, 'm/d/yy h:mm');
{ # TODO: locale support
0 => 'GENERAL',
1 => '0',
2 => '0.00',
3 => '#,##0',
4 => '#,##0.00',
5 => '"$"#,##0_);("$"#,##0)',
6 => '"$"#,##0_);[Red]("$"#,##0)',
7 => '"$"#,##0.00_);("$"#,##0.00)',
8 => '"$"#,##0.00_);[Red]("$"#,##0.00)',
9 => '0%',
10 => '0.00%',
11 => '0.00E+00',
12 => '# ?/?',
13 => '# ??/??',
14 => 'M/D/YY',
15 => 'D-MMM-YY',
16 => 'D-MMM',
17 => 'MMM-YY',
18 => 'h:mm AM/PM',
19 => 'h:mm:ss AM/PM',
20 => 'h:mm',
21 => 'h:mm:ss',
22 => 'M/D/YY h:mm',
37 => '_(#,##0_);(#,##0)',
38 => '_(#,##0_);[Red](#,##0)',
39 => '_(#,##0.00_);(#,##0.00)',
40 => '_(#,##0.00_);[Red](#,##0.00)',
41 => '_("$"* #,##0_);_("$"* (#,##0);_("$"* "-"_);_(@_)',
42 => '_(* #,##0_);_(* (#,##0);_(* "-"_);_(@_)',
43 => '_("$"* #,##0.00_);_("$"* (#,##0.00);_("$"* "-"??_);_(@_)',
44 => '_(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(@_)',
45 => 'mm:ss',
46 => '[h]:mm:ss',
47 => 'mm:ss.0',
48 => '##0.0E+0',
49 => '@',
}
end;
*)
{
Writes an Excel 2 FORMULA record

View File

@ -1119,7 +1119,7 @@ begin
// Now apply the modifications.
if uffNumberFormat in FFormattingStyles[i].UsedFormattingFields then begin
j := NumFormatList.Find(@FFormattingStyles[i]);
j := NumFormatList.FindFormatOf(@FFormattingStyles[i]);
if j > -1 then
lFormatIndex := NumFormatList[j].Index;
end;

View File

@ -307,7 +307,7 @@ begin
// Now apply the modifications.
if uffNumberFormat in FFormattingStyles[i].UsedFormattingFields then begin
j := NumFormatList.Find(@FFormattingStyles[i]);
j := NumFormatList.FindFormatOf(@FFormattingStyles[i]);
if j > -1 then
lFormatIndex := NumFormatList[j].Index;
end;

View File

@ -348,14 +348,7 @@ type
TsBIFFNumFormatList = class(TsCustomNumFormatList)
protected
procedure AddBuiltinFormats; override;
{
procedure ConvertAfterReading(AFormatIndex: Integer; var AFormatString: String;
var ANumFormat: TsNumberFormat; var ADecimals: Byte;
var ACurrencySymbol: String); override;
procedure ConvertBeforeWriting(var AFormatString: String); override;
}
public
// function FormatStringForWriting(AIndex: Integer): String; override;
end;
{ TsSpreadBIFFReader }
@ -589,138 +582,13 @@ begin
FFirstFormatIndexInFile := 164;
FNextFormatIndex := 164;
end;
(*
{ Considers some Excel specialities for format detection.
The output values will be passed to fpc. }
procedure TsBIFFNumFormatList.Analyze(AFormatIndex: Integer;
var AFormatString: String; var ANumFormat: TsNumberFormat;
var ADecimals: Byte; var ACurrencySymbol: String);
var
fmt: String;
{
parser: TsNumFormatParser;
sections: TsNumFormatSections;
}
begin
{
AFormatString := 'hh:mm:ss.0 AM/PM'; //"€" #,##.0;[red]"$" -#,##.000;-';
parser := TsNumFormatParser.Create(Workbook, AFormatString);
try
fmt := parser.FormatString;
ANumFormat := parser.ParsedSections[0].NumFormat;
ADecimals := parser.ParsedSections[0].Decimals;
ACurrencySymbol := parser.ParsedSections[0].CurrencySymbol;
parser.CopySectionsTo(sections);
finally
parser.Free;
end;
parser := TsNumFormatParser.Create(Workbook, sections);
try
fmt := parser.FormatString;
finally
parser.Free;
end;
}
fmt := Lowercase(AFormatString);
{ Check the built-in formats first:
The prefix "[$-F400]" before the formatting string means that the system's
long time format string is used. }
if (pos('[$-F400]', AFormatString) = 1) then begin
ANumFormat := nfLongTime;
AFormatString := ''; // will be replaced by system's format setting
ADecimals := 0;
exit;
end;
{ Excel often has the locale ID [$-409] (for Germany) in front of the format
string. We currently ignore this because it confuses fpc. }
if (pos('[$', fmt) = 1) then begin
if (pos('h:mm:ss\', fmt) > 0) then begin
// long time format
if (pos('am/pm', fmt) > 0) or (pos('a/p',fmt) > 0) then begin
ANumFormat := nfLongTimeAM;
AFormatString := '';
end else begin
ANumFormat := nfLongTime;
AFormatString := '';
end;
ADecimals := 0;
exit;
end;
if (pos('h:mm\', fmt) > 0) then begin
if (pos('am/pm', fmt) > 0) or (pos ('a/p', fmt) > 0) then begin
ANumFormat := nfShortTimeAM;
AFormatString := '';
end else begin
ANumFormat := nfShortTime;
AFormatString := '';
end;
ADecimals := 0;
exit;
end;
// TO DO: Analyze currency
end;
inherited Analyze(AFormatIndex, AFormatString, ANumFormat, ADecimals, ACurrencySymbol);
end;
*)
(*
{ Creates formatting strings that are written into the file. }
function TsBIFFNumFormatList.FormatStringForWriting(AIndex: Integer): String;
var
item: TsNumFormatData;
i: Integer;
procedure FixN(var s: String);
// The minutes in short time formats are coded by "n" in fpc, but Excel wants "m".
var
i: Integer;
begin
for i:=1 to Length(s) do
case s[i] of
'n', 'N': s[i] := 'm'; // no "M" which will be interpreted as "Month"
end;
end;
begin
Result := inherited FormatStringForWriting(AIndex);
item := Items[AIndex];
case item.NumFormat of
nfFmtDateTime:
begin
Result := lowercase(item.FormatString);
for i:=1 to Length(Result) do begin
// The milliseccond format contains the symbol "z" in fpc, but Excel wants "0"
if Result[i] in ['z', 'Z'] then Result[i] := '0';
end;
FixN(Result);
end;
nfTimeInterval:
begin
// Time interval format string could still be without square brackets
// if added by user.
// We check here for safety and add the brackets if not there.
MakeTimeIntervalMask(item.FormatString, Result);
FixN(Result);
end;
nfShortTime, nfShortTimeAM, nfLongTime, nfLongTimeAM:
FixN(Result);
nfCurrencyRed, nfCurrencyDashRed:
begin
i := Pos(';', item.FormatString);
Result := Copy(item.FormatString, 1, i) + '[RED]'+ Copy(item.FormatString, i+1, Length(item.FormatString));
end;
end;
end;
*)
{ TsSpreadBIFFReader }
constructor TsSpreadBIFFReader.Create(AWorkbook: TsWorkbook);
var
i: Integer;
begin
inherited Create(AWorkbook);
FXFList := TFPList.Create;
@ -793,9 +661,18 @@ end;
formats.
Valid for BIFF5.BIFF8. Needs to be overridden for BIFF2. }
procedure TsSpreadBIFFReader.CreateNumFormatList;
var
i: Integer;
item: TsNumFormatData;
begin
FreeAndNil(FNumFormatList);
FNumFormatList := TsBIFFNumFormatList.Create(Workbook);
// Convert builtin formats to fps syntax
for i:=0 to FNumFormatList.Count-1 do begin
item := FNumFormatList[i];
FNumFormatList.ConvertAfterReading(item.Index, item.FormatString,
item.NumFormat, item.Decimals, item.CurrencySymbol);
end;
end;
{ Extracts a number out of an RK value.
@ -1332,7 +1209,6 @@ begin
end;
{ TsSpreadBIFFWriter }
constructor TsSpreadBIFFWriter.Create(AWorkbook: TsWorkbook);