From 00ed56d1ef602f15bbc701755a44705c02b82d1f Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Tue, 20 May 2014 16:13:48 +0000 Subject: [PATCH] fpspreadsheet: Integrate number format parser into fpspreadsheet. Some regressions in format detection... git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3068 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- .../examples/excel8demo/excel8write.lpr | 10 +- .../examples/fpsgrid/fpsgrid.lpi | 91 +++-- .../fpspreadsheet/fpsnumformatparser.pas | 325 ++++++++++++------ components/fpspreadsheet/fpsopendocument.pas | 4 +- components/fpspreadsheet/fpspreadsheet.pas | 204 +++++++---- components/fpspreadsheet/fpsutils.pas | 121 ++++++- components/fpspreadsheet/wikitable.pas | 2 +- components/fpspreadsheet/xlsbiff2.pas | 10 +- components/fpspreadsheet/xlscommon.pas | 69 ++-- components/fpspreadsheet/xlsxooxml.pas | 2 +- 10 files changed, 574 insertions(+), 264 deletions(-) diff --git a/components/fpspreadsheet/examples/excel8demo/excel8write.lpr b/components/fpspreadsheet/examples/excel8demo/excel8write.lpr index 5517d29d0..1bc27b1ed 100644 --- a/components/fpspreadsheet/examples/excel8demo/excel8write.lpr +++ b/components/fpspreadsheet/examples/excel8demo/excel8write.lpr @@ -29,7 +29,8 @@ var number: Double; lCell: PCell; lCol: TCol; - i, r: Integer; + i: Integer; + r: Integer = 10; begin MyDir := ExtractFilePath(ParamStr(0)); @@ -53,7 +54,7 @@ begin } // Write some cells -// MyWorksheet.WriteNumber(0, 0, 1.0);// A1 + 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 @@ -206,10 +207,6 @@ begin inc(r); MyWorksheet.WriteUTF8Text(r, 0, 'nfFmtDateTime, mm:ss.zzz'); MyWorksheet.WriteDateTime(r, 1, now, nfFmtDateTime, 'mm:ss.zzz'); - // NOTE: The upper option "MSZ" = "mm:ss.z" should result only in 1 decimal. - // This is true for writing, but in reading always 3 decimals are displayed. - // This is due to fpc's SysUtile.FormatDateTime which does not distinguish - // both cases. // Write formatted numbers number := 12345.67890123456789; @@ -252,7 +249,6 @@ begin MyWorksheet.WriteUTF8Text(r, 0, 'nfFixedTh, 3 decs'); MyWorksheet.WriteNumber(r, 1, number, nfFixedTh, 3); MyWorksheet.WriteNumber(r, 2, -number, nfFixedTh, 3); - inc(r,2); MyWorksheet.WriteUTF8Text(r, 0, 'nfSci, 1 dec'); MyWorksheet.WriteNumber(r, 1, number, nfSci, 1); diff --git a/components/fpspreadsheet/examples/fpsgrid/fpsgrid.lpi b/components/fpspreadsheet/examples/fpsgrid/fpsgrid.lpi index 009e9d077..cd91bb6de 100644 --- a/components/fpspreadsheet/examples/fpsgrid/fpsgrid.lpi +++ b/components/fpspreadsheet/examples/fpsgrid/fpsgrid.lpi @@ -137,8 +137,7 @@ - - + @@ -148,7 +147,7 @@ - + @@ -286,10 +285,11 @@ + - - + + @@ -577,143 +577,139 @@ - - - - - + - + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - + + - + - - + + - + - + - - + + @@ -743,15 +739,6 @@ - - - - - - - - - diff --git a/components/fpspreadsheet/fpsnumformatparser.pas b/components/fpspreadsheet/fpsnumformatparser.pas index 8233215f1..6238615be 100644 --- a/components/fpspreadsheet/fpsnumformatparser.pas +++ b/components/fpspreadsheet/fpsnumformatparser.pas @@ -27,6 +27,8 @@ type coEqual, coNotEqual, coLess, coGreater, coLessEqual, coGreaterEqual ); + TsConversionDirection = (cdToFPSpreadsheet, cdFromFPSpreadsheet); + TsNumFormatSection = record FormatString: String; CompareOperation: TsCompareOperation; @@ -38,17 +40,23 @@ type NumFormat: TsNumberFormat; end; + TsNumFormatSections = array of TsNumFormatSection; + TsNumFormatParser = class private + FCreateMethod: Byte; FWorkbook: TsWorkbook; FCurrent: PChar; FStart: PChar; FEnd: PChar; FCurrSection: Integer; - FSections: array of TsNumFormatSection; + FSections: TsNumFormatSections; FFormatSettings: TFormatSettings; FFormatString: String; + FNumFormat: TsNumberFormat; + FConversionDirection: TsConversionDirection; FStatus: Integer; + function GetFormatString: String; function GetParsedSectionCount: Integer; function GetParsedSections(AIndex: Integer): TsNumFormatSection; @@ -58,6 +66,8 @@ type procedure AnalyzeBracket(const AValue: String); procedure AnalyzeText(const AValue: String); procedure CheckSections; + function CreateFormatStringFromSection(ASection: Integer): String; virtual; + function CreateFormatStringFromSections: String; procedure Parse(const AFormatString: String); procedure ScanAMPM(var s: String); procedure ScanBrackets; @@ -68,9 +78,16 @@ type procedure ScanText; public - constructor Create(AWorkbook: TsWorkbook; const AFormatString: String); + constructor Create(AWorkbook: TsWorkbook; const AFormatString: String; + AConversionDirection: TsConversionDirection = cdToFPSpreadsheet); overload; + constructor Create(AWorkbook: TsWorkbook; const AFormatSections: TsNumFormatSections; + AConversionDirection: TsConversionDirection = cdFromFPSpreadsheet); overload; destructor Destroy; override; - property FormatString: String read FFormatString; + procedure CopySections(const FromSections: TsNumFormatSections; + var ToSections: TsNumFormatSections); + procedure CopySectionsTo(var ADestination: TsNumFormatSections); + property Builtin_NumFormat: TsNumberFormat read FNumFormat; + property FormatString: String read GetFormatString; property ParsedSectionCount: Integer read GetParsedSectionCount; property ParsedSections[AIndex: Integer]: TsNumFormatSection read GetParsedSections; property Status: Integer read FStatus; @@ -88,10 +105,15 @@ const { TsNumFormatParser } +{ Creates a number format parser for analyzing a formatstring that has been read + from a spreadsheet file. The conversion, by default, will go FROM the file TO + the fpspreadsheet procedures. } constructor TsNumFormatParser.Create(AWorkbook: TsWorkbook; - const AFormatString: String); + const AFormatString: String; AConversionDirection: TsConversionDirection = cdToFPSpreadsheet); begin inherited Create; + FCreateMethod := 0; + FConversionDirection := AConversionDirection; FWorkbook := AWorkbook; FFormatSettings := DefaultFormatSettings; FFormatSettings.DecimalSeparator := '.'; @@ -99,6 +121,22 @@ begin Parse(AFormatString); end; +{ Creates a number format parser to create a format string from the individual + format sections given in "AFormatSections". It is assumed by default that the + format string will be written to file. Therefore, it can contain features of + the destination file format and, in general, will not work if called by + fpspreadsheet. } +constructor TsNumFormatParser.Create(AWorkbook: TsWorkbook; + const AFormatSections: TsNumFormatSections; + AConversionDirection: TsConversionDirection = cdFromFPSpreadsheet); +begin + inherited Create; + FCreateMethod := 1; + FConversionDirection := AConversionDirection; + FWorkbook := AWorkbook; + CopySections(AFormatSections, FSections); +end; + destructor TsNumFormatParser.Destroy; begin FSections := nil; @@ -119,7 +157,7 @@ begin FormatString := ''; CompareOperation := coNotUsed; CompareValue := 0.0; - Color := scBlack; + Color := scNotDefined; CountryCode := ''; CurrencySymbol := ''; Decimals := 0; @@ -221,6 +259,7 @@ begin exit; end; + // Check format strings case FSections[i].NumFormat of nfGeneral, nfFixed, nfFixedTh, nfPercentage, nfExp, nfSci, nfCurrency: try @@ -241,6 +280,29 @@ begin end; end; + // Extract built-in NumFormat identifier for currency (needs several entries in + // three sections). + if (ns = 3) and + (FSections[0].NumFormat = nfCurrency) and + (FSections[1].NumFormat = nfCurrency) and + (FSections[2].NumFormat = nfCurrency) + then begin + if ((FSections[2].FormatString = '-') or (FSections[2].FormatString = '"-"')) then begin + if (FSections[1].Color = scRed) then + FNumFormat := nfCurrencyDashRed + else + FNumFormat := nfCurrencyDash; + end else begin + if (FSections[1].Color = scRed) then + FNumFormat := nfCurrencyRed; + end; + end else + // If there are other multi-section formatstrings they must be a custom format + if (ns > 1) then + FNumFormat := nfCustom + else + FNumFormat := FSections[0].NumFormat; + if ns = 2 then FFormatString := Format('%s;%s;%s', [ FSections[0].FormatString, @@ -256,33 +318,79 @@ begin FStatus := psErrNoUsableFormat; end; -{ -function TsNumFormatParser.GetNumFormat: TsNumberFormat; +procedure TsNumFormatParser.CopySections( + const FromSections: TsNumFormatSections; var ToSections: TsNumformatSections); var i: Integer; begin - if FStatus <> psOK then - Result := nfGeneral - else - if (FSections[0].NumFormat = nfCurrency) and (FSections[1].NumFormat = nfCurrency) and - (FSections[2].NumFormat = nfCurrency) - then begin - if (FSections[1].Color = scNotDefined) then begin - if (FSections[2].FormatString = '-') then - Result := nfCurrencyDash - else - Result := nfCurrency; - end else - if FSections[1].Color = scRed then begin - if (FSections[2].Formatstring = '-') then - Result := nfCurrencyDashRed - else - Result := nfCurrencyRed; - end; - end else - Result := FSections[0].NumFormat; + SetLength(ToSections, Length(FromSections)); + for i:= 0 to High(FromSections) do begin + ToSections[i].FormatString := FromSections[i].FormatString; + ToSections[i].CompareOperation := FromSections[i].CompareOperation; + ToSections[i].CompareValue := FromSections[i].CompareValue; + ToSections[i].Color := FromSections[i].Color; + ToSections[i].CurrencySymbol := FromSections[i].CurrencySymbol; + ToSections[i].Decimals := FromSections[i].Decimals; + ToSections[i].NumFormat := FromSections[i].NumFormat; + end; +end; + +procedure TsNumFormatParser.CopySectionsTo(var ADestination: TsNumFormatSections); +begin + CopySections(FSections, ADestination); +end; + +function TsNumFormatParser.CreateFormatStringFromSections: String; +var + i: Integer; +begin + if Length(FSections) = 0 then + Result := '' + else begin + Result := CreateFormatStringFromSection(0); + for i:=1 to High(FSections) do + Result := Result + ';' + CreateFormatStringFromSection(i); + end; +end; + +function TsNumFormatParser.CreateFormatStringFromSection(ASection: Integer): String; +begin + with FSections[ASection] do + if (NumFormat = nfFmtDateTime) or (NumFormat = nfCustom) then begin + Result := FormatString; + exit; + end; + + Result := BuildNumberFormatString(FSections[ASection].NumFormat, + FWorkbook.FormatSettings, + FSections[ASection].Decimals, + FSections[ASection].CurrencySymbol + ); + if FConversionDirection = cdFromFPSpreadsheet then begin + // This is typical of Excel, but is valid for all others as well. + // Override if you need to change + if FSections[ASection].Color < 8 then + Result := Format('[%s]%s', [FWorkbook.GetColorName(FSections[ASection].Color), Result]) + else + if FSections[ASection].Color < scNotDefined then + Result := Format('[Color%d]%s', [FSections[ASection].Color, Result]); + + if FSections[ASection].CompareOperation <> coNotUsed then + Result := Format('[%s%g]%s', [ + COMPARE_STR[FSections[ASection].CompareOperation], + FSections[ASection].CompareValue, + Result + ]); + end; +end; + +function TsNumFormatParser.GetFormatString: String; +begin + case FCreateMethod of + 0: Result := FFormatString; + 1: Result := CreateFormatStringFromSections; + end; end; -} function TsNumFormatParser.GetParsedSectionCount: Integer; begin @@ -355,74 +463,77 @@ begin while (FCurrent <= FEnd) and (FStatus = psOK) and (not done) do begin token := FCurrent^; case token of - '\' : begin - inc(FCurrent); - token := FCurrent^; - s := s + token; - end; - 'Y', 'y' : begin - ScanDateTimeParts(token, token, s); - isTime := false; - end; - 'M', 'm' : if isTime then // help fpc to separate "month" and "minute" - ScanDateTimeParts(token, 'n', s) - else // both "month" and "minute" work in fpc to some degree - ScanDateTimeParts(token, token, s); - 'D', 'd' : begin - ScanDateTimeParts(token, token, s); - isTime := false; - end; - 'H', 'h' : begin - ScanDateTimeParts(token, token, s); - isTime := true; - end; - 'S', 's' : begin - ScanDateTimeParts(token, token, s); - isTime := true; - end; - '/', ':', '.', ']', '[', ' ' - : s := s + token; - '0' : ScanDateTimeParts(token, 'z', s); - 'A', 'a' : begin - ScanAMPM(s); - isAMPM := true; - end; - else begin - done := true; - dec(FCurrent); - // char pointer must be at end of date/time mask. - end; + '\': + begin + inc(FCurrent); + token := FCurrent^; + s := s + token; + end; + 'Y', 'y': + begin + ScanDateTimeParts(token, token, s); + isTime := false; + end; + 'M', 'm': + ScanDateTimeParts(token, token, s); + {if isTime then // help fpc to separate "month" and "minute" + ScanDateTimeParts(token, 'n', s) + else // both "month" and "minute" work in fpc to some degree + ScanDateTimeParts(token, token, s);} + 'N', 'n': + ScanDateTimeParts(token, 'n', s); // fpc dialect for "minutes" + 'D', 'd': + begin + ScanDateTimeParts(token, token, s); + isTime := false; + end; + 'H', 'h': + begin + ScanDateTimeParts(token, token, s); + isTime := true; + end; + 'S', 's': + begin + ScanDateTimeParts(token, token, s); + isTime := true; + end; + '/', ':', '.', ']', '[', ' ': + s := s + token; + '0', 'z', 'Z': + ScanDateTimeParts(token, token, s); + 'A', 'a': + begin + ScanAMPM(s); + isAMPM := true; + end; + else + begin + done := true; + dec(FCurrent); + // char pointer must be at end of date/time mask. + end; end; if not done then inc(FCurrent); end; FSections[FCurrSection].FormatString := FSections[FCurrSection].FormatString + s; s := FSections[FCurrSection].FormatString; + if s <> '' then begin + if s = FWorkbook.FormatSettings.LongDateFormat then + nf := nfLongDate + else + if s = FWorkbook.FormatSettings.ShortDateFormat then + nf := nfShortDate + else + if s = StripAMPM(FWorkbook.FormatSettings.LongTimeFormat) then + nf := IfThen(isAMPM, nfLongTimeAM, nfLongTime) + else + if s = StripAMPM(FWorkbook.FormatSettings.ShortTimeFormat) then + nf := IfThen(isAMPM, nfShortTimeAM, nfShortTime) + else + nf := nfFmtDateTime; - // Check format - try - if s <> '' then begin - FormatDateTime(s, now); - // !!!! MODIFY TO USE EXTENDED SYNTAX !!!!! - - if s = FWorkbook.FormatSettings.LongDateFormat then - nf := nfLongDate - else - if s = FWorkbook.FormatSettings.ShortDateFormat then - nf := nfShortDate - else - if s = FWorkbook.FormatSettings.LongTimeFormat then - nf := nfLongTime - else - if s = FWorkbook.FormatSettings.ShortTimeFormat then - nf := nfShortTime - else - nf := nfFmtDateTime; - FSections[FCurrSection].NumFormat := nf; - end; - - except - FStatus := psErrNoValidDateTimeFormat; + FSections[FCurrSection].NumFormat := nf; end; end; @@ -470,21 +581,27 @@ begin token := FCurrent^; case token of // Strip Excel's formatting symbols - '\', '*' : ; - '_' : inc(FCurrent); - '"' : begin - inc(FCurrent); - ScanText; - end; - '0', '#', '.', ',', '-': ScanNumber; - 'y', 'Y', 'm', 'M', - 'd', 'D', 'h', 's', '[': ScanDateTime; - ' ' : AddChar(token); - ';' : begin - done := true; - dec(FCurrent); - // Cursor must stay on the ";" - end; + '\', '*': + ; + '_': + inc(FCurrent); + '"': + begin + inc(FCurrent); + ScanText; + end; + '0', '#', '.', ',', '-': + ScanNumber; + 'y', 'Y', 'm', 'M', 'd', 'D', 'h', 'N', 'n', 's', '[': + ScanDateTime; + ' ': + AddChar(token); + ';': + begin + done := true; + dec(FCurrent); + // Cursor must stay on the ";" + end; end; if not done then inc(FCurrent); end; diff --git a/components/fpspreadsheet/fpsopendocument.pas b/components/fpspreadsheet/fpsopendocument.pas index 94d193dd7..8c62fd5a2 100755 --- a/components/fpspreadsheet/fpsopendocument.pas +++ b/components/fpspreadsheet/fpsopendocument.pas @@ -184,7 +184,7 @@ end; procedure TsSpreadOpenDocReader.CreateNumFormatList; begin FreeAndNil(FNumFormatList); - FNumFormatList := TsSpreadOpenDocNumFormatList.Create; + FNumFormatList := TsSpreadOpenDocNumFormatList.Create(Workbook); end; function TsSpreadOpenDocReader.GetAttrValue(ANode : TDOMNode; AAttrName : string) : string; @@ -471,7 +471,7 @@ end; procedure TsSpreadOpenDocWriter.CreateNumFormatList; begin FreeAndNil(FNumFormatList); - FNumFormatList := TsSpreadOpenDocNumFormatList.Create; + FNumFormatList := TsSpreadOpenDocNumFormatList.Create(Workbook); end; procedure TsSpreadOpenDocWriter.WriteMimetype; diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas index 810b3896c..1f902bd1f 100755 --- a/components/fpspreadsheet/fpspreadsheet.pas +++ b/components/fpspreadsheet/fpspreadsheet.pas @@ -362,16 +362,21 @@ type FOnChangeCell: TsCellEvent; FOnChangeFont: TsCellEvent; procedure RemoveCallback(data, arg: pointer); + protected procedure ChangedCell(ARow, ACol: Cardinal); procedure ChangedFont(ARow, ACol: Cardinal); + public Name: string; + { Base methods } constructor Create; destructor Destroy; override; + { Utils } class function CellPosToText(ARow, ACol: Cardinal): string; + { Data manipulation methods - For Cells } procedure CopyCell(AFromRow, AFromCol, AToRow, AToCol: Cardinal; AFromWorksheet: TsWorksheet); procedure CopyFormat(AFormat: PCell; AToRow, AToCol: Cardinal); @@ -390,35 +395,25 @@ type function ReadUsedFormatting(ARow, ACol: Cardinal): TsUsedFormattingFields; function ReadBackgroundColor(ARow, ACol: Cardinal): TsColor; procedure RemoveAllCells; + { Writing of values } - procedure WriteUTF8Text(ARow, ACol: Cardinal; AText: ansistring); + procedure WriteBlank(ARow, ACol: Cardinal); + procedure WriteBoolValue(ARow, ACol: Cardinal; AValue: Boolean); + procedure WriteDateTime(ARow, ACol: Cardinal; AValue: TDateTime; + AFormat: TsNumberFormat = nfShortDateTime; AFormatStr: String = ''); + procedure WriteErrorValue(ARow, ACol: Cardinal; AValue: TErrorValue); + procedure WriteFormula(ARow, ACol: Cardinal; AFormula: TsFormula); procedure WriteNumber(ARow, ACol: Cardinal; ANumber: double; AFormat: TsNumberFormat = nfGeneral; ADecimals: Byte = 2; ACurrencySymbol: String = ''); overload; procedure WriteNumber(ARow, ACol: Cardinal; ANumber: double; AFormatString: String); overload; - procedure WriteBlank(ARow, ACol: Cardinal); - procedure WriteBoolValue(ARow, ACol: Cardinal; AValue: Boolean); - procedure WriteDateTime(ARow, ACol: Cardinal; AValue: TDateTime; - AFormat: TsNumberFormat = nfShortDateTime; AFormatStr: String = ''); - procedure WriteDecimals(ARow, ACol: Cardinal; ADecimals: byte); overload; - procedure WriteDecimals(ACell: PCell; ADecimals: Byte); overload; - procedure WriteErrorValue(ARow, ACol: Cardinal; AValue: TErrorValue); - procedure WriteFormula(ARow, ACol: Cardinal; AFormula: TsFormula); procedure WriteRPNFormula(ARow, ACol: Cardinal; AFormula: TsRPNFormula); + procedure WriteUTF8Text(ARow, ACol: Cardinal; AText: ansistring); + { Writing of cell attributes } - procedure WriteNumberFormat(ARow, ACol: Cardinal; ANumberFormat: TsNumberFormat; - const AFormatString: String = ''); - function WriteFont(ARow, ACol: Cardinal; const AFontName: String; - AFontSize: Single; AFontStyle: TsFontStyles; AFontColor: TsColor): Integer; overload; - procedure WriteFont(ARow, ACol: Cardinal; AFontIndex: Integer); overload; - function WriteFontColor(ARow, ACol: Cardinal; AFontColor: TsColor): Integer; - function WriteFontName(ARow, ACol: Cardinal; AFontName: String): Integer; - function WriteFontSize(ARow, ACol: Cardinal; ASize: Single): Integer; - function WriteFontStyle(ARow, ACol: Cardinal; AStyle: TsFontStyles): Integer; - procedure WriteTextRotation(ARow, ACol: Cardinal; ARotation: TsTextRotation); - procedure WriteUsedFormatting(ARow, ACol: Cardinal; AUsedFormatting: TsUsedFormattingFields); procedure WriteBackgroundColor(ARow, ACol: Cardinal; AColor: TsColor); + procedure WriteBorderColor(ARow, ACol: Cardinal; ABorder: TsCellBorder; AColor: TsColor); procedure WriteBorderLineStyle(ARow, ACol: Cardinal; ABorder: TsCellBorder; ALineStyle: TsLineStyle); @@ -428,9 +423,31 @@ type procedure WriteBorderStyle(ARow, ACol: Cardinal; ABorder: TsCellBorder; ALineStyle: TsLineStyle; AColor: TsColor); overload; procedure WriteBorderStyles(ARow, ACol: Cardinal; const AStyles: TsCellBorderStyles); + + procedure WriteDecimals(ARow, ACol: Cardinal; ADecimals: byte); overload; + procedure WriteDecimals(ACell: PCell; ADecimals: Byte); overload; + + function WriteFont(ARow, ACol: Cardinal; const AFontName: String; + AFontSize: Single; AFontStyle: TsFontStyles; AFontColor: TsColor): Integer; overload; + procedure WriteFont(ARow, ACol: Cardinal; AFontIndex: Integer); overload; + function WriteFontColor(ARow, ACol: Cardinal; AFontColor: TsColor): Integer; + function WriteFontName(ARow, ACol: Cardinal; AFontName: String): Integer; + function WriteFontSize(ARow, ACol: Cardinal; ASize: Single): Integer; + function WriteFontStyle(ARow, ACol: Cardinal; AStyle: TsFontStyles): Integer; + procedure WriteHorAlignment(ARow, ACol: Cardinal; AValue: TsHorAlignment); + + procedure WriteNumberFormat(ARow, ACol: Cardinal; ANumberFormat: TsNumberFormat; + const AFormatString: String = ''); + + procedure WriteTextRotation(ARow, ACol: Cardinal; ARotation: TsTextRotation); + + procedure WriteUsedFormatting(ARow, ACol: Cardinal; AUsedFormatting: TsUsedFormattingFields); + procedure WriteVertAlignment(ARow, ACol: Cardinal; AValue: TsVertAlignment); + procedure WriteWordwrap(ARow, ACol: Cardinal; AValue: boolean); + { Data manipulation methods - For Rows and Cols } function FindRow(ARow: Cardinal): PRow; function FindCol(ACol: Cardinal): PCol; @@ -442,11 +459,13 @@ type procedure WriteRowHeight(ARow: Cardinal; AHeight: Single); procedure WriteColInfo(ACol: Cardinal; AData: TCol); procedure WriteColWidth(ACol: Cardinal; AWidth: Single); + { Properties } property Cells: TAVLTree read FCells; property Cols: TIndexedAVLTree read FCols; property Rows: TIndexedAVLTree read FRows; property Workbook: TsWorkbook read FWorkbook; + // These are properties to interface to fpspreadsheetgrid. property Options: TsSheetOptions read FOptions write FOptions; property LeftPaneWidth: Integer read FLeftPaneWidth write FLeftPaneWidth; @@ -547,7 +566,7 @@ type var ACurrencySymbol: String); virtual; procedure RemoveFormat(AIndex: Integer); public - constructor Create; + constructor Create(AWorkbook: TsWorkbook); destructor Destroy; override; function AddFormat(AFormatCell: PCell): Integer; overload; function AddFormat(AFormatIndex: Integer; ANumFormat: TsNumberFormat; @@ -718,7 +737,7 @@ procedure MakeLEPalette(APalette: PsPalette; APaletteSize: Integer); implementation uses - Math, StrUtils, fpsutils; + Math, StrUtils, fpsUtils, fpsNumFormatParser; { Translatable strings } resourcestring @@ -727,6 +746,9 @@ resourcestring lpNoValidSpreadsheetFile = '"%s" is not a valid spreadsheet file'; lpUnknownSpreadsheetFormat = 'unknown format'; lpInvalidFontIndex = 'Invalid font index'; + lpInvalidNumberFormat = 'Trying to use an incompatible number format.'; + lpNoValidNumberFormatString = 'No valid number format string.'; + lpNoValidDateTimeFormatString = 'No valid date/time format string.'; lpTRUE = 'TRUE'; lpFALSE = 'FALSE'; lpErrEmptyIntersection = '#NULL!'; @@ -1407,14 +1429,19 @@ begin ACell^.ContentType := cctNumber; ACell^.NumberValue := ANumber; ACell^.Decimals := ADecimals; + + if IsDateTimeFormat(AFormat) then + raise Exception.Create(lpInvalidNumberFormat); + if AFormat <> nfGeneral then begin Include(ACell^.UsedFormattingFields, uffNumberFormat); ACell^.NumberFormat := AFormat; ACell^.Decimals := ADecimals; ACell^.CurrencySymbol := ACurrencySymbol; - ACell^.NumberFormatStr := BuildNumFormatString(ACell^.NumberFormat, + ACell^.NumberFormatStr := BuildNumberFormatString(ACell^.NumberFormat, Workbook.FormatSettings, ADecimals, ACurrencySymbol); end; + ChangedCell(ARow, ACol); end; @@ -1427,13 +1454,32 @@ procedure TsWorksheet.WriteNumber(ARow, ACol: Cardinal; ANumber: Double; AFormatString: String); var ACell: PCell; + parser: TsNumFormatParser; + nf: TsNumberFormat; begin + parser := TsNumFormatParser.Create(Workbook, AFormatString, cdToFPSpreadsheet); + try + // Format string ok? + if parser.Status <> psOK then + raise Exception.Create(lpNoValidNumberFormatString); + if IsDateTimeFormat(parser.Builtin_NumFormat) + then raise Exception.Create(lpInvalidNumberFormat); + // If format string matches a built-in format use its format identifier, + // All this is considered when calling Builtin_NumFormat of the parser. + nf := parser.Builtin_NumFormat; + finally + parser.Free; + end; + ACell := GetCell(ARow, ACol); Include(ACell^.UsedFormattingFields, uffNumberFormat); ACell^.ContentType := cctNumber; ACell^.NumberValue := ANumber; - ACell^.NumberFormat := nfCustom; + ACell^.NumberFormat := nf; ACell^.NumberFormatStr := AFormatString; + ACell^.Decimals := 0; + ACell^.CurrencySymbol := ''; + ChangedCell(ARow, ACol); end; @@ -1482,6 +1528,7 @@ end; 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: a datetime is stored as a (floating point) Number, and the cell is formatted @@ -1494,9 +1541,25 @@ procedure TsWorksheet.WriteDateTime(ARow, ACol: Cardinal; AValue: TDateTime; var ACell: PCell; fmt: String; + parser: TsNumFormatParser; begin - ACell := GetCell(ARow, ACol); + if AFormat = nfFmtDateTime then begin + 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; ACell^.DateTimeValue := AValue; // Date/time is actually a number field in Excel. @@ -1504,34 +1567,8 @@ begin // The user can choose another date format if he wants to Include(ACell^.UsedFormattingFields, uffNumberFormat); ACell^.NumberFormat := AFormat; - case AFormat of - nfShortDateTime: - ACell^.NumberFormatStr := FormatSettings.ShortDateFormat + ' ' + FormatSettings.ShortTimeFormat; - nfShortDate: - ACell^.NumberFormatStr := FormatSettings.ShortDateFormat; - nfLongDate: - ACell^.NumberFormatStr := 'dd/mmm/yyyy'; - nfShortTime: - ACell^.NumberFormatStr := 't'; - nfLongTime: - ACell^.NumberFormatStr := 'tt'; - nfShortTimeAM: - ACell^.NumberFormatStr := 'hh:nn AM/PM'; - nfLongTimeAM: - ACell^.NumberFormatStr := 'hh:nn:ss AM/PM'; - nfFmtDateTime: - begin - fmt := lowercase(AFormatStr); - if fmt = 'dm' then ACell^.NumberFormatStr := 'd/mmm' - else if fmt = 'my' then ACell^.NumberFormatStr := 'mmm/yy' - else if fmt = 'ms' then ACell^.NumberFormatStr := 'nn:ss' - else if fmt = 'msz' then ACell^.NumberFormatStr := 'nn:ss.z' - else ACell^.NumberFormatStr := AFormatStr; - end; - nfTimeInterval: - if AFormatStr = '' then ACell^.NumberFormatStr := '[h]:nn:ss' - else ACell^.NumberFormatStr := AFormatStr; - end; + ACell^.NumberFormatStr := AFormatStr; + ChangedCell(ARow, ACol); end; @@ -1544,7 +1581,7 @@ procedure TsWorksheet.WriteDecimals(ACell: PCell; ADecimals: Byte); begin if (ACell <> nil) and (ACell^.ContentType = cctNumber) then begin ACell^.Decimals := ADecimals; - ACell^.NumberFormatStr := BuildNumFormatString(ACell^.NumberFormat, + ACell^.NumberFormatStr := BuildNumberFormatString(ACell^.NumberFormat, FWorkbook.FormatSettings, ADecimals, ACell^.CurrencySymbol); ChangedCell(ACell^.Row, ACell^.Col); end; @@ -1603,7 +1640,7 @@ begin Include(ACell^.UsedFormattingFields, uffNumberFormat); ACell^.NumberFormat := ANumberFormat; if (AFormatString = '') then - ACell^.NumberFormatStr := BuildNumFormatString(ANumberFormat, + ACell^.NumberFormatStr := BuildNumberFormatString(ANumberFormat, Workbook.FormatSettings, ACell^.Decimals, ACell^.CurrencySymbol) else ACell^.NumberFormatStr := AFormatString; @@ -2577,9 +2614,10 @@ end; { TsCustomNumFormatList } -constructor TsCustomNumFormatList.Create; +constructor TsCustomNumFormatList.Create(AWorkbook: TsWorkbook); begin inherited Create; + FWorkbook := AWorkbook; AddBuiltinFormats; end; @@ -2599,6 +2637,13 @@ begin item := TsNumFormatData.Create; item.Index := AFormatIndex; item.NumFormat := ANumFormat; + if IsDateTimeFormat(ANumFormat) then + AFormatString := BuildDateTimeFormatString(ANumFormat, Workbook.FormatSettings, + AFormatString) + else + if item.NumFormat <> nfCustom then + AFormatString := BuildNumberFormatString(ANumFormat, Workbook.FormatSettings, + ADecimals, ACurrencySymbol); item.FormatString := AFormatString; item.Decimals := ADecimals; item.CurrencySymbol := ACurrencySymbol; @@ -2644,6 +2689,38 @@ end; If the format string cannot be directly handled by fpc it has to be transformed to make it compatible. Can be done in overridden versions which know more about the structure of the string in the actual file format. } +procedure TsCustomNumFormatList.Analyze(AFormatIndex: Integer; + var AFormatString: String; var ANumFormat: TsNumberFormat; + var ADecimals: Byte; var ACurrencySymbol: String); +var + parser: TsNumFormatParser; + fmt: String; + lFormatData: TsNumFormatData; + i: Integer; +begin + i := Find(AFormatIndex); + if i > 0 then begin + lFormatData := Items[i]; + fmt := lFormatData.FormatString; + end else + fmt := AFormatString; + + parser := TsNumFormatParser.Create(Workbook, fmt, cdToFPSpreadsheet); + try + if parser.Status = psOK then begin + ANumFormat := parser.Builtin_NumFormat; + AFormatString := parser.FormatString; + if not (parser.Builtin_NumFormat in [nfCustom, nfFmtDateTime]) then begin + ADecimals := parser.ParsedSections[0].Decimals; + ACurrencySymbol := parser.ParsedSections[0].CurrencySymbol; + end; + end; + finally + parser.Free; + end; +end; + +(* procedure TsCustomNumFormatList.Analyze(AFormatIndex: Integer; var AFormatString: String; var ANumFormat: TsNumberFormat; var ADecimals: Byte; var ACurrencySymbol: String); @@ -2749,6 +2826,7 @@ begin 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 @@ -2816,8 +2894,8 @@ begin item := Items[Result]; if (item <> nil) and (item.NumFormat = ANumFormat) then exit; - end - else + end; + if (ANumFormat = nfFmtDateTime) then begin fmt := lowercase(AFormatString); for Result := 0 to Count-1 do begin @@ -2847,9 +2925,10 @@ begin if fmt = lowercase(item.FormatString) then exit; end; - end else + end; + // Check only the format string for nfCustom. - if (ANumFormat = nfCustom) then begin + if (ANumFormat = nfCustom) then for Result := 0 to Count-1 do begin item := Items[Result]; if (item <> nil) @@ -2858,7 +2937,7 @@ begin then exit; end; - end else + // The other formats can carry additional information for Result := 0 to Count-1 do begin item := Items[Result]; @@ -3099,7 +3178,8 @@ var decs: Byte; begin if ACell^.NumberFormat = nfFmtDateTime then begin - if IsTimeFormat(ACell^.NumberFormatStr, isLong, isAMPM, isInterval, decs) then + decs := CountDecs(ACell^.NumberFormatStr, ['0', 'z', 'Z']); +// if IsTimeFormat(ACell^.NumberFormatStr, isLong, isAMPM, isInterval, decs) then ACell^.Decimals := decs; end; end; diff --git a/components/fpspreadsheet/fpsutils.pas b/components/fpspreadsheet/fpsutils.pas index 0728410b2..eb167f45e 100644 --- a/components/fpspreadsheet/fpsutils.pas +++ b/components/fpspreadsheet/fpsutils.pas @@ -17,6 +17,7 @@ uses // Exported types type TsSelectionDirection = (fpsVerticalSelection, fpsHorizontalSelection); + TsDecsChars = set of char; const // Date formatting string for unambiguous date/time display as strings @@ -60,19 +61,29 @@ function UTF8TextToXMLText(AText: ansistring): ansistring; function TwipsToMillimeters(AValue: Integer): Single; function MillimetersToTwips(AValue: Single): Integer; +function IfThen(ACondition: Boolean; AValue1,AValue2: TsNumberFormat): TsNumberFormat; overload; + +function IsDateTimeFormat(AFormat: TsNumberFormat): Boolean; + (* function IsCurrencyFormat(s: String; out Decimals: Byte; out CurrSymbol: String; out IsCurrencyRedFmt, IsCurrencyDashFmt: Boolean): Boolean; function IsExpNumberFormat(s: String; out Decimals: Byte; out IsSci: Boolean): Boolean; function IsFixedNumberFormat(s: String; out Decimals: Byte): Boolean; function IsPercentNumberFormat(s: String; out Decimals: Byte): Boolean; function IsThousandSepNumberFormat(s: String; out Decimals: Byte): Boolean; -function IsDateFormat(s: String; out IsLong: Boolean): Boolean; -function IsTimeFormat(s: String; out isLong, isAMPM, isInterval: Boolean; - out SecDecimals: Byte): Boolean; -function BuildNumFormatString(ANumberFormat: TsNumberFormat; +function IsDateFormat(s: String; out IsLong: Boolean): Boolean; +{function IsTimeFormat(s: String; out isLong, isAMPM, isInterval: Boolean; + out SecDecimals: Byte): Boolean; +*) + +function BuildNumberFormatString(ANumberFormat: TsNumberFormat; const AFormatSettings: TFormatSettings; ADecimals: Integer = -1; ACurrencySymbol: String = '?'): String; +function BuildDateTimeFormatString(ANumberFormat: TsNumberFormat; + const AFormatSettings: TFormatSettings; AFormatString: String = ''): String; +function StripAMPM(const ATimeFormatString: String): String; +function CountDecs(AFormatString: String; ADecChars: TsDecsChars = ['0']): Byte; function SciFloat(AValue: Double; ADecimals: Byte): String; //function TimeIntervalToString(AValue: TDateTime; AFormatStr: String): String; @@ -494,8 +505,21 @@ begin end; +{ Returns either AValue1 or AValue2, depending on the condition. + For reduciton of typing... } +function IfThen(ACondition: Boolean; AValue1, AValue2: TsNumberFormat): TsNumberFormat; +begin + if ACondition then Result := AValue1 else Result := AValue2; +end; + { Format checking procedures } +function IsDateTimeFormat(AFormat: TsNumberFormat): Boolean; +begin + Result := AFormat in [nfFmtDateTime, nfShortDateTime, nfShortDate, nfLongDate, + nfShortTime. nfLongTime, nfShortTimeAM, nfLongTimeAM, nfTimeInterval]; +end; + (* { This simple parsing procedure of the Excel format string checks for a fixed float format s, i.e. s can be '0', '0.00', '000', '0,000', and returns the number of decimals, i.e. number of zeros behind the decimal point } @@ -661,7 +685,7 @@ begin if ph > 0 then IsSci := true; end; end; - + *) { IsDateFormat checks if the format string s corresponds to a date format } function IsDateFormat(s: String; out IsLong: Boolean): Boolean; begin @@ -744,9 +768,58 @@ begin end; end; -{ Builds a number format string from the numberformat code and the count of - decimals. } -function BuildNumFormatString(ANumberFormat: TsNumberFormat; +{ 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 + abbreviations "dm" (for "d/mmm"), "my" (for "mmm/yy"), "ms" (for "mm:ss") + and "msz" (for "mm:ss.z"). } +function BuildDateTimeFormatString(ANumberFormat: TsNumberFormat; + const AFormatSettings: TFormatSettings; AFormatString: String = '') : string; +var + fmt: String; +begin + case ANumberFormat of + nfFmtDateTime: + begin + fmt := lowercase(AFormatString); + if (fmt = 'dm') then Result := 'd/mmm' + else if (fmt = 'my') then Result := 'mmm/yy' + else if (fmt = 'ms') then Result := 'nn:ss' + else if (fmt = 'msz') then Result := 'nn:ss.z' + else Result := AFormatString; + end; + nfShortDateTime: + Result := AFormatSettings.ShortDateFormat + ' ' + FormatSettings.ShortTimeFormat; + nfShortDate: + Result := AFormatSettings.ShortDateFormat; + nfLongDate: + Result := AFormatSettings.LongDateFormat; + nfShortTime: + Result := StripAMPM(AFormatSettings.ShortTimeFormat); + nfLongTime: + Result := StripAMPM(AFormatSettings.LongTimeFormat); + nfShortTimeAM: + begin + Result := AFormatSettings.ShortTimeFormat; + if pos('a', lowercase(AFormatSettings.ShortTimeFormat)) = 0 then + Result := Format('%s %s/%s', [Result, AFormatSettings.TimeAMString, AFormatSettings.TimePMString]); + end; + nfLongTimeAM: + begin + Result := AFormatSettings.LongTimeFormat; + if pos('a', lowercase(AFormatSettings.LongTimeFormat)) = 0 then + Result := Format('%s %s/%s', [Result, AFormatSettings.TimeAMString, AFormatSettings.TimePMString]); + end; + nfTimeInterval: + if AFormatString = '' then + Result := '[h]:mm:ss' + else + Result := AFormatString; + end; +end; + +{ Builds a number format string from the numberformat code, the count of + decimals, and the currencysymbol (if not empty). } +function BuildNumberFormatString(ANumberFormat: TsNumberFormat; const AFormatSettings: TFormatSettings; ADecimals: Integer = -1; ACurrencySymbol: String = '?'): String; const @@ -778,6 +851,7 @@ var decs: String; cf, ncf: Byte; begin + Result := ''; cf := AFormatSettings.CurrencyFormat; ncf := AFormatSettings.NegCurrFormat; if ADecimals = -1 then ADecimals := AFormatSettings.CurrencyDecimals; @@ -824,6 +898,37 @@ begin end; end; +function StripAMPM(const ATimeFormatString: String): String; +var + i: Integer; +begin + Result := ''; + i := 1; + while i <= Length(ATimeFormatString) do begin + if ATimeFormatString[i] in ['a', 'A'] then begin + inc(i); + while (i <= Length(ATimeFormatString)) and (ATimeFormatString[i] in ['p', 'P', 'm', 'M', '/']) do + inc(i); + end else + Result := Result + ATimeFormatString[i]; + inc(i); + end; +end; + +function CountDecs(AFormatString: String; ADecChars: TsDecsChars = ['0']): Byte; +var + i: Integer; +begin + Result := 0; + for i:=Length(AFormatString) downto 1 do begin + if AFormatString[i] in ADecChars then inc(Result); + if AFormatString[i] = '.' then exit; + end; + // Comes to this point when there is no decimal separtor. + Result := 0; +end; + + { Formats the number AValue in "scientific" format with the given number of decimals. "Scientific" is the same as "exponential", but with exponents rounded to multiples of 3 (like for "kilo" - "Mega" - "Giga" etc.). } diff --git a/components/fpspreadsheet/wikitable.pas b/components/fpspreadsheet/wikitable.pas index ca991ef86..3670ea216 100644 --- a/components/fpspreadsheet/wikitable.pas +++ b/components/fpspreadsheet/wikitable.pas @@ -352,7 +352,7 @@ end; procedure TsWikiTableWriter.CreateNumFormatList; begin FreeAndNil(FNumFormatList); - FNumFormatList := TsWikiTableNumFormatList.Create; + FNumFormatList := TsWikiTableNumFormatList.Create(Workbook); end; procedure TsWikiTableWriter.WriteToStrings(AStrings: TStrings); diff --git a/components/fpspreadsheet/xlsbiff2.pas b/components/fpspreadsheet/xlsbiff2.pas index 34cea77b0..996e07994 100755 --- a/components/fpspreadsheet/xlsbiff2.pas +++ b/components/fpspreadsheet/xlsbiff2.pas @@ -43,7 +43,7 @@ type protected procedure AddBuiltinFormats; override; public - constructor Create; + constructor Create(AWorkbook: TsWorkbook); function FormatStringForWriting(AIndex: Integer): String; override; end; @@ -166,9 +166,9 @@ const { TsBIFF2NumFormatList } -constructor TsBIFF2NumFormatList.Create; +constructor TsBIFF2NumFormatList.Create(AWorkbook: TsWorkbook); begin - inherited Create; + inherited Create(AWorkbook); end; procedure TsBIFF2NumFormatList.AddBuiltinFormats; @@ -283,7 +283,7 @@ end; procedure TsSpreadBIFF2Reader.CreateNumFormatList; begin FreeAndNil(FNumFormatList); - FNumFormatList := TsBIFF2NumFormatList.Create; + FNumFormatList := TsBIFF2NumFormatList.Create(Workbook); end; { Extracts the number format data from an XF record indexed by AXFIndex. @@ -701,7 +701,7 @@ end; procedure TsSpreadBIFF2Writer.CreateNumFormatList; begin FreeAndNil(FNumFormatList); - FNumFormatList := TsBIFF2NumFormatList.Create; + FNumFormatList := TsBIFF2NumFormatList.Create(Workbook); end; function TsSpreadBIFF2Writer.FindXFIndex(ACell: PCell): Word; diff --git a/components/fpspreadsheet/xlscommon.pas b/components/fpspreadsheet/xlscommon.pas index 867512616..cf0cc9e5f 100644 --- a/components/fpspreadsheet/xlscommon.pas +++ b/components/fpspreadsheet/xlscommon.pas @@ -593,13 +593,15 @@ procedure TsBIFFNumFormatList.Analyze(AFormatIndex: Integer; var AFormatString: String; var ANumFormat: TsNumberFormat; var ADecimals: Byte; var ACurrencySymbol: String); var - parser: TsNumFormatParser; fmt: String; +{ + parser: TsNumFormatParser; + sections: TsNumFormatSections; +} begin - { - AFormatString := 'hh:mm AM/PM'; //"€" #,##.0;[red]"$" -#,##.000;-'; - +(* + AFormatString := 'hh:mm:ss.0 AM/PM'; //"€" #,##.0;[red]"$" -#,##.000;-'; parser := TsNumFormatParser.Create(Workbook, AFormatString); try @@ -607,10 +609,18 @@ begin 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: @@ -660,6 +670,18 @@ 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]; @@ -670,15 +692,19 @@ begin 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'; - // The minutes in short time formats are coded by "n" in fpc, but Excel wants "m". - if Result[i] in ['n', 'N'] then Result[i] := 'm'; end; + FixN(Result); end; nfTimeInterval: - // 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); + 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); @@ -765,7 +791,7 @@ end; procedure TsSpreadBIFFReader.CreateNumFormatList; begin FreeAndNil(FNumFormatList); - FNumFormatList := TsBIFFNumFormatList.Create; + FNumFormatList := TsBIFFNumFormatList.Create(Workbook); end; { Extracts a number out of an RK value. @@ -812,9 +838,11 @@ procedure TsSpreadBIFFReader.ExtractNumberFormat(AXFIndex: WORD; decs: Byte; i: Integer; begin - if IsTimeFormat(ANumberFormatStr, isLong, isAMPM, isInterval, decs) + decs := CountDecs(ANumberFormatStr, ['0', 'z', 'Z']); +{ if IsTimeFormat(ANumberFormatStr, isLong, isAMPM, isInterval, decs) and (decs > 0) - then + then } + if decs > 0 then for i:= Length(ANumberFormatStr) downto 1 do case ANumberFormatStr[i] of '0': ANumberFormatStr[i] := 'z'; @@ -1361,7 +1389,7 @@ end; procedure TsSpreadBIFFWriter.CreateNumFormatList; begin FreeAndNil(FNumFormatList); - FNumFormatList := TsBIFFNumFormatList.Create; + FNumFormatList := TsBIFFNumFormatList.Create(Workbook); end; function TsSpreadBIFFWriter.FormulaElementKindToExcelTokenID( @@ -1679,19 +1707,16 @@ end; procedure TsSpreadBIFFWriter.WriteFormats(AStream: TStream); var i: Integer; - item: TsNumFormatData; - begin ListAllNumFormats; - - item := NumFormatList[20]; - i := NumFormatList.Find(NumFormatList.FirstFormatIndexInFile); if i > -1 then while i < NumFormatList.Count do begin - if NumFormatList[i] <> nil then - WriteFormat(AStream, NumFormatList[i], i); + item := NumFormatList[i]; + if item <> nil then begin + WriteFormat(AStream, item, i); + end; inc(i); end; end; diff --git a/components/fpspreadsheet/xlsxooxml.pas b/components/fpspreadsheet/xlsxooxml.pas index 9d455d412..bc278df87 100755 --- a/components/fpspreadsheet/xlsxooxml.pas +++ b/components/fpspreadsheet/xlsxooxml.pas @@ -393,7 +393,7 @@ end; procedure TsSpreadOOXMLWriter.CreateNumFormatList; begin FreeAndNil(FNumFormatList); - FNumFormatList := TsOOXMLNumFormatList.Create; + FNumFormatList := TsOOXMLNumFormatList.Create(Workbook); end; {