diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas index 74dc2a86d..b9b9fffb9 100755 --- a/components/fpspreadsheet/fpspreadsheet.pas +++ b/components/fpspreadsheet/fpspreadsheet.pas @@ -795,8 +795,10 @@ type FOnWriteCellData: TsWorkbookWriteCellDataEvent; FOnReadCellData: TsWorkbookReadCellDataEvent; FFileName: String; + FLog: TStringList; { Setter/Getter } + function GetErrorMsg: String; procedure SetVirtualColCount(AValue: Cardinal); procedure SetVirtualRowCount(AValue: Cardinal); @@ -865,6 +867,11 @@ type procedure UsePalette(APalette: PsPalette; APaletteCount: Word; ABigEndian: Boolean = false); + { Error messages } + procedure AddErrorMsg(const AMsg: String); overload; + procedure AddErrorMsg(const AMsg: String; const Args: array of const); overload; + procedure ClearErrorList; + {@@ The default column width given in "character units" (width of the character "0" in the default font) } property DefaultColWidth: Single read FDefaultColWidth; @@ -874,6 +881,8 @@ type {@@ This property is only used for formats which don't support unicode and support a single encoding for the whole document, like Excel 2 to 5 } property Encoding: TsEncoding read FEncoding write FEncoding; + {@@ Retrieves error messages collected during reading/writing } + property ErrorMsg: String read GetErrorMsg; {@@ Filename of the saved workbook } property FileName: String read FFileName; {@@ Identifies the file format which was detected when reading the file } @@ -970,9 +979,6 @@ type {@@ Common ancestor of the spreadsheet reader and writer classes providing shared data and methods. } TsCustomSpreadReaderWriter = class - private - FLog: TStringList; - function GetErrorMsg: String; protected {@@ Instance of the workbook which is currently being read. } FWorkbook: TsWorkbook; @@ -982,9 +988,6 @@ type {@@ List of number formats found in the file } FNumFormatList: TsCustomNumFormatList; procedure CreateNumFormatList; virtual; - protected - procedure AddToLog(const AMsg: String); overload; - procedure AddToLog(const AMsg: String; const Args: array of const); overload; public constructor Create(AWorkbook: TsWorkbook); virtual; // to allow descendents to override it destructor Destroy; override; @@ -992,8 +995,6 @@ type property Workbook: TsWorkbook read FWorkbook; {@@ List of number formats found in the workbook. } property NumFormatList: TsCustomNumFormatList read FNumFormatList; - {@@ Retrieve error messages logged during writing} - property ErrorMsg: String read GetErrorMsg; end; { TsCustomSpreadReader } @@ -2130,8 +2131,11 @@ begin // Store the result FFirstColIndex := Result; end - else + else begin Result := FFirstColIndex; + if Result = $FFFFFFFF then + Result := GetFirstColIndex(true); + end; end; {@@ @@ -2255,8 +2259,11 @@ begin // Store result FFirstRowIndex := Result; end - else - Result := FFirstRowIndex + else begin + Result := FFirstRowIndex; + if Result = $FFFFFFFF then + Result := GetFirstRowIndex(true); + end; end; {@@ @@ -4717,6 +4724,7 @@ constructor TsWorkbook.Create; begin inherited Create; FWorksheets := TFPList.Create; + FLog := TStringList.Create; FFormat := sfExcel8; FDefaultColWidth := 12; FDefaultRowHeight := 1; @@ -4739,6 +4747,7 @@ begin FWorksheets.Free; FFontList.Free; + FLog.Free; inherited Destroy; end; @@ -5389,6 +5398,43 @@ begin FPalette[Result] := AColorValue; end; +{@@ + Adds a (simple) error message to an internal list + + @param AMsg Error text to be stored in the list +} +procedure TsWorkbook.AddErrorMsg(const AMsg: String); +begin + FLog.Add(AMsg); +end; + +{@@ + Adds an error message composed by means of format codes to an internal list + + @param AMsg Error text to be stored in the list + @param Args Array of arguments to be used by the Format() function +} +procedure TsWorkbook.AddErrorMsg(const AMsg: String; const Args: Array of const); +begin + FLog.Add(Format(AMsg, Args)); +end; + +{@@ + Clears the internal list with error messages +} +procedure TsWorkbook.ClearErrorList; +begin + FLog.Clear; +end; + +{@@ + Getter to retrieve the error messages collected during reading/writing +} +function TsWorkbook.GetErrorMsg: String; +begin + Result := FLog.Text; +end; + {@@ Finds the palette color index which points to a color that is closest to a given color. "Close" means here smallest length of the rgb-difference vector. @@ -5970,7 +6016,6 @@ constructor TsCustomSpreadReaderWriter.Create(AWorkbook: TsWorkbook); begin inherited Create; FWorkbook := AWorkbook; - FLog := TStringList.Create; CreateNumFormatList; end; @@ -5980,27 +6025,9 @@ end; destructor TsCustomSpreadReaderWriter.Destroy; begin FNumFormatList.Free; - FLog.Free; inherited Destroy; end; -{@@ - Adds an (simple) error message to the log list -} -procedure TsCustomSpreadReaderWriter.AddToLog(const AMsg: String); -begin - FLog.Add(AMsg); -end; - -{@@ - Adds an error message to the log list by using the Format function -} -procedure TsCustomSpreadReaderWriter.AddToLog(const AMsg: String; - const Args: array of const); -begin - FLog.Add(Format(AMsg, Args)); -end; - {@@ Creates an instance of the number format list which contains prototypes of all number formats found in the workbook (when writing) or in the file (when @@ -6013,12 +6040,6 @@ begin // nothing to do here end; -{@@ Getter method to retrieve the error messages collected during reading/writing } -function TsCustomSpreadReaderWriter.GetErrorMsg: String; -begin - Result := FLog.Text; -end; - { TsCustomSpreadReader } @@ -6133,7 +6154,7 @@ begin inherited Create(AWorkbook); { A good starting point valid for many formats... } FLimitations.MaxCols := 256; - FLimitations.MaxRows := 65536; + FLimitations.MaxRows := 65536; end; {@@ @@ -6267,9 +6288,9 @@ var begin Workbook.GetLastRowColIndex(lastRow, lastCol); if lastRow >= FLimitations.MaxRows then - AddToLog(lpMaxRowsExceeded, [lastRow+1, FLimitations.MaxRows]); + Workbook.AddErrorMsg(lpMaxRowsExceeded, [lastRow+1, FLimitations.MaxRows]); if lastCol >= FLimitations.MaxCols then - AddToLog(lpMaxColsExceeded, [lastCol+1, FLimitations.MaxCols]); + Workbook.AddErrorMsg(lpMaxColsExceeded, [lastCol+1, FLimitations.MaxCols]); end; diff --git a/components/fpspreadsheet/fpsutils.pas b/components/fpspreadsheet/fpsutils.pas index fa757d486..f95ea42a9 100644 --- a/components/fpspreadsheet/fpsutils.pas +++ b/components/fpspreadsheet/fpsutils.pas @@ -75,7 +75,7 @@ function ParseCellColString(const AStr: string; out AResult: Cardinal): Boolean; function GetColString(AColIndex: Integer): String; -function GetCellString(ARow,ACol: Cardinal; AFlags: TsRelFlags): String; +function GetCellString(ARow,ACol: Cardinal; AFlags: TsRelFlags = [rfRelRow, rfRelCol]): String; function GetCellRangeString(ARow1, ACol1, ARow2, ACol2: Cardinal; AFlags: TsRelFlags): String; function GetErrorValueStr(AErrorValue: TsErrorValue): String; @@ -610,13 +610,15 @@ const @param ARowIndex Zero-based row index @param AColIndex Zero-based column index - @param AFlags A set containing an entry for column and row if these - addresses are relative. + @param AFlags An optional set containing an entry for column and row + if these addresses are relative. By default, relative + addresses are assumed. @return Excel type of cell address containing $ characters for absolute address parts. @example ARowIndex = 0, AColIndex = 0, AFlags = [rfRelRow] --> $A1 } -function GetCellString(ARow, ACol: Cardinal; AFlags: TsRelFlags): String; +function GetCellString(ARow, ACol: Cardinal; + AFlags: TsRelFlags = [rfRelRow, rfRelCol]): String; begin Result := Format('%s%s%s%d', [ RELCHAR[rfRelCol in AFlags], GetColString(ACol), diff --git a/components/fpspreadsheet/tests/internaltests.pas b/components/fpspreadsheet/tests/internaltests.pas index b0d20edeb..fdec62d7f 100644 --- a/components/fpspreadsheet/tests/internaltests.pas +++ b/components/fpspreadsheet/tests/internaltests.pas @@ -1,5 +1,7 @@ unit internaltests; +{$DEFINE SKIP_TestWriteErrorMessages_ODS} + { Other units test file read/write capability. This unit tests functions, procedures and properties that fpspreadsheet provides. } @@ -34,6 +36,7 @@ type // Set up expected values: procedure SetUp; override; procedure TearDown; override; + procedure TestWriteErrorMessages(AFormat: TsSpreadsheetFormat); procedure TestVirtualMode(AFormat: TsSpreadsheetFormat; ABufStreamMode: Boolean); published @@ -50,10 +53,18 @@ type procedure OverwriteExistingFile; // Write out date cell and try to read as UTF8; verify if contents the same procedure ReadDateAsUTF8; + // Test buffered stream procedure TestReadBufStream; procedure TestWriteBufStream; + // Tests collection of error messages during writing + procedure TestWriteErrorMessages_BIFF2; + procedure TestWriteErrorMessages_BIFF5; + procedure TestWriteErrorMessages_BIFF8; + procedure TestWriteErrorMessages_ODS; + procedure TestWriteErrorMessages_OOXML; + // Virtual mode tests for all file formats procedure TestVirtualMode_BIFF2; procedure TestVirtualMode_BIFF5; @@ -71,7 +82,7 @@ type implementation uses - numberstests, stringtests; + StrUtils, numberstests, stringtests; const InternalSheet = 'Internal'; //worksheet name @@ -82,11 +93,14 @@ var MyWorkbook: TsWorkbook; begin MyWorkbook := TsWorkbook.Create; - MyWorkSheet:=MyWorkBook.AddWorksheet(InternalSheet); - MyWorkSheet:=nil; - MyWorkSheet:=MyWorkBook.GetWorksheetByIndex(0); - CheckFalse((MyWorksheet=nil),'GetWorksheetByIndex should return a valid index'); - MyWorkbook.Free; + try + MyWorkSheet:=MyWorkBook.AddWorksheet(InternalSheet); + MyWorkSheet:=nil; + MyWorkSheet:=MyWorkBook.GetWorksheetByIndex(0); + CheckFalse((MyWorksheet=nil),'GetWorksheetByIndex should return a valid index'); + finally + MyWorkbook.Free; + end; end; procedure TSpreadInternalTests.GetSheetByName; @@ -97,13 +111,16 @@ var MyWorkbook: TsWorkbook; begin MyWorkbook := TsWorkbook.Create; - MyWorkSheet:=MyWorkBook.AddWorksheet(InternalSheet); - MyWorkSheet:=MyWorkBook.AddWorksheet(AnotherSheet); - MyWorkSheet:=nil; - MyWorkSheet:=MyWorkBook.GetWorksheetByName(InternalSheet); - CheckFalse((MyWorksheet=nil),'GetWorksheetByName should return a valid index'); - CheckEquals(MyWorksheet.Name,InternalSheet,'GetWorksheetByName should return correct name.'); - MyWorkbook.Free; + try + MyWorkSheet:=MyWorkBook.AddWorksheet(InternalSheet); + MyWorkSheet:=MyWorkBook.AddWorksheet(AnotherSheet); + MyWorkSheet:=nil; + MyWorkSheet:=MyWorkBook.GetWorksheetByName(InternalSheet); + CheckFalse((MyWorksheet=nil),'GetWorksheetByName should return a valid index'); + CheckEquals(MyWorksheet.Name,InternalSheet,'GetWorksheetByName should return correct name.'); + finally + MyWorkbook.Free; + end; end; procedure TSpreadInternalTests.OverwriteExistingFile; @@ -156,22 +173,26 @@ begin Row:=0; Column:=0; TestDT:=EncodeDate(1969,7,21)+EncodeTime(2,56,0,0); + MyWorkbook:=TsWorkbook.Create; - MyWorkSheet:=MyWorkBook.AddWorksheet(InternalSheet); - MyWorkSheet.WriteDateTime(Row,Column,TestDT); //write date + try + MyWorkSheet:=MyWorkBook.AddWorksheet(InternalSheet); + MyWorkSheet.WriteDateTime(Row,Column,TestDT); //write date - // Reading as date/time should just work - if not(MyWorksheet.ReadAsDateTime(Row,Column,ActualDT)) then - Fail('Could not read date time for cell '+CellNotation(MyWorkSheet,Row,Column)); - CheckEquals(TestDT,ActualDT,'Test date/time value mismatch ' - +'cell '+CellNotation(MyWorkSheet,Row,Column)); + // Reading as date/time should just work + if not(MyWorksheet.ReadAsDateTime(Row,Column,ActualDT)) then + Fail('Could not read date time for cell '+CellNotation(MyWorkSheet,Row,Column)); + CheckEquals(TestDT,ActualDT,'Test date/time value mismatch ' + +'cell '+CellNotation(MyWorkSheet,Row,Column)); - //Check reading as string, convert to date & compare - ActualDTString:=MyWorkSheet.ReadAsUTF8Text(Row,Column); - ActualDT:=StrToDateTimeDef(ActualDTString,EncodeDate(1906,1,1)); - CheckEquals(TestDT,ActualDT,'Date/time mismatch using ReadAsUTF8Text'); + //Check reading as string, convert to date & compare + ActualDTString:=MyWorkSheet.ReadAsUTF8Text(Row,Column); + ActualDT:=StrToDateTimeDef(ActualDTString,EncodeDate(1906,1,1)); + CheckEquals(TestDT,ActualDT,'Date/time mismatch using ReadAsUTF8Text'); - MyWorkbook.Free; + finally + MyWorkbook.Free; + end; end; procedure TSpreadInternalTests.TestWriteBufStream; @@ -255,6 +276,122 @@ begin end; end; +procedure TSpreadInternalTests.TestWriteErrorMessages(AFormat: TsSpreadsheetFormat); +type + TTestFormat = (sfExcel2, sfExcel5, sfExcel8, sfOOXML, sfOpenDocument); +const + MAX_ROW_COUNT: array[TTestFormat] of Cardinal = (65536, 65536, 65536, 1048576, 1048576); + MAX_COL_COUNT: array[TTestFormat] of Cardinal = (256, 256, 256, 16384, 1024); + MAX_CELL_LEN: array[TTestFormat] of Cardinal = (255, 255, 32767, cardinal(-1), Cardinal(-1)); +var + MyWorkbook: TsWorkbook; + MyWorksheet: TsWorksheet; + row, col: Cardinal; + row1, row2: Cardinal; + col1, col2: Cardinal; + s: String; + TempFile: String; + ErrList: TStringList; +begin + ErrList := TStringList.Create; + try + // Test 1: Too many rows + MyWorkbook := TsWorkbook.Create; + try + MyWorkSheet:= MyWorkBook.AddWorksheet('Test'); + row1 := MAX_ROW_COUNT[TTestFormat(AFormat)] - 5; + row2 := MAX_ROW_COUNT[TTestFormat(AFormat)] + 5; + for row :=row1 to row2 do begin + MyWorksheet.WriteBlank(row, 0); + MyWorksheet.WriteNumber(row, 1, 1.0); + MyWorksheet.WriteUTF8Text(row, 2, 'A'); + MyWorksheet.WriteRPNFormula(row, 3, CreateRPNFormula( + RPNCellValue('A1', nil))); + end; + TempFile:=NewTempFile; + MyWorkBook.WriteToFile(TempFile, AFormat, true); + ErrList.Text := MyWorkbook.ErrorMsg; + CheckEquals(1, ErrList.Count, 'Error count mismatch in test 1'); + finally + MyWorkbook.Free; + DeleteFile(TempFile); + end; + + // Test 2: Too many columns + MyWorkbook := TsWorkbook.Create; + try + MyWorkSheet:= MyWorkBook.AddWorksheet('Test'); + col1 := MAX_COL_COUNT[TTestFormat(AFormat)] - 5; + col2 := MAX_COL_COUNT[TTestFormat(AFormat)] + 5; + for col := col1 to col2 do begin + MyWorksheet.WriteBlank(row, 0); + MyWorksheet.WriteNumber(row, 1, 1.0); + MyWorksheet.WriteUTF8Text(row, 2, 'A'); + MyWorksheet.WriteRPNFormula(row, 3, CreateRPNFormula( + RPNCellValue('A1', nil))); + end; + TempFile:=NewTempFile; + MyWorkBook.WriteToFile(TempFile, AFormat, true); + ErrList.Text := MyWorkbook.ErrorMsg; + CheckEquals(1, ErrList.Count, 'Error count mismatch in test 2'); + finally + MyWorkbook.Free; + DeleteFile(TempFile); + end; + + // Test 3: Too long cell label + if MAX_CELL_LEN[TTestFormat(AFormat)] <> Cardinal(-1) then begin + s := DupeString('A', MAX_CELL_LEN[TTestFormat(AFormat)] + 10); + MyWorkbook := TsWorkbook.Create; + try + MyWorkSheet:= MyWorkBook.AddWorksheet('Test'); + MyWorksheet.WriteUTF8Text(0, 0, s); + TempFile:=NewTempFile; + MyWorkBook.WriteToFile(TempFile, AFormat, true); + ErrList.Text := MyWorkbook.ErrorMsg; + CheckEquals(1, ErrList.Count, 'Error count mismatch in test 3'); + finally + MyWorkbook.Free; + DeleteFile(TempFile); + end; + end; + + finally + ErrList.Free; + end; +end; + +procedure TSpreadInternalTests.TestWriteErrorMessages_BIFF2; +begin + TestWriteErrorMessages(sfExcel2); +end; + +procedure TSpreadInternalTests.TestWriteErrorMessages_BIFF5; +begin + TestWriteErrorMessages(sfExcel5); +end; + +procedure TSpreadInternalTests.TestWriteErrorMessages_BIFF8; +begin + TestWriteErrorMessages(sfExcel8); +end; + +procedure TSpreadInternalTests.TestWriteErrorMessages_ODS; +begin + {$IFDEF SKIP_TestWriteErrorMessages_ODS} + //Ignore(TestWriteErrorMessages(sfOpenDocument)); + // How to do that? + {$ELSE} + TestWriteErrorMessages(sfOpenDocument); + {$ENDIF} +end; + +procedure TSpreadInternalTests.TestWriteErrorMessages_OOXML; +begin + TestWriteErrorMessages(sfOOXML); +end; + + procedure TSpreadInternalTests.TestReadBufStream; const BUF_SIZE = 1024; diff --git a/components/fpspreadsheet/tests/spreadtestgui.lpi b/components/fpspreadsheet/tests/spreadtestgui.lpi index 9ab1b5d1d..bd26fe4db 100644 --- a/components/fpspreadsheet/tests/spreadtestgui.lpi +++ b/components/fpspreadsheet/tests/spreadtestgui.lpi @@ -48,12 +48,10 @@ - - @@ -63,17 +61,14 @@ - - - @@ -91,7 +86,6 @@ - @@ -100,17 +94,14 @@ - - - diff --git a/components/fpspreadsheet/xlsbiff2.pas b/components/fpspreadsheet/xlsbiff2.pas index 8e68ea21b..e857e1afb 100755 --- a/components/fpspreadsheet/xlsbiff2.pas +++ b/components/fpspreadsheet/xlsbiff2.pas @@ -604,7 +604,7 @@ begin XF := rec.Attrib1 and $3F; { String with 8-bit size } - L := rec.TextLen; + L := rec.TextLen; SetLength(AValue, L); AStream.ReadBuffer(AValue[1], L); @@ -1502,6 +1502,9 @@ var s: ansistring; xf: Word; begin + if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then + exit; + RPNLength := 0; FormulaResult := 0.0; @@ -1605,6 +1608,9 @@ var xf: Word; rec: TBlankRecord; begin + if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then + exit; + xf := FindXFIndex(ACell); if xf >= 63 then WriteIXFE(AStream, xf); @@ -1661,6 +1667,9 @@ var var xf: Word; begin + if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then + exit; + if AValue = '' then Exit; // Writing an empty text doesn't work AnsiText := UTF8ToISO_8859_1(AValue); @@ -1672,6 +1681,11 @@ begin // with the problem or purposefully ignore it. TextTooLong:=true; AnsiText := Copy(AnsiText, 1, MAXBYTES); + Workbook.AddErrorMsg( + 'Text value exceeds %d character limit in cell %s. ' + + 'Text has been truncated.', [ + MAXBYTES, GetCellString(ARow, ACol) + ]); end; L := Length(AnsiText); @@ -1700,32 +1714,6 @@ begin { Write out } AStream.WriteBuffer(buf[0], SizeOf(Rec) + SizeOf(ansiChar)*L); - - - (* - { BIFF Record header } - AStream.WriteWord(WordToLE(INT_EXCEL_ID_LABEL)); - AStream.WriteWord(WordToLE(8 + L)); - - { BIFF Record data } - AStream.WriteWord(WordToLE(ARow)); - AStream.WriteWord(WordToLE(ACol)); - - { BIFF2 Attributes } - WriteCellFormatting(AStream, ACell, xf); - - { String with 8-bit size } - AStream.WriteByte(L); - AStream.WriteBuffer(AnsiText[1], L); - *) - - { - //todo: keep a log of errors and show with an exception after writing file or something. - We can't just do the following - if TextTooLong then - Raise Exception.CreateFmt('Text value exceeds %d character limit in cell [%d,%d]. Text has been truncated.',[MaxBytes,ARow,ACol]); - because the file wouldn't be written. - } end; {******************************************************************* @@ -1742,24 +1730,12 @@ var xf: Word; rec: TBIFF2NumberRecord; begin + if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then + exit; + xf := FindXFIndex(ACell); if xf >= 63 then WriteIXFE(AStream, xf); - (* - { BIFF Record header } - AStream.WriteWord(WordToLE(INT_EXCEL_ID_NUMBER)); - AStream.WriteWord(WordToLE(15)); - - { BIFF Record data } - AStream.WriteWord(WordToLE(ARow)); - AStream.WriteWord(WordToLE(ACol)); - - { BIFF2 Attributes } - WriteCellFormatting(AStream, ACell, xf); - - { IEE 754 floating-point value } - AStream.WriteBuffer(AValue, 8); - *) { BIFF record header } rec.RecordID := WordToLE(INT_EXCEL_ID_NUMBER); @@ -1787,6 +1763,11 @@ var w: Word; h: Single; begin + if (ARowIndex >= FLimitations.MaxRows) or (AFirstColIndex >= FLimitations.MaxCols) + or (ALastColIndex >= FLimitations.MaxCols) + then + exit; + Unused(ASheet); containsXF := false; diff --git a/components/fpspreadsheet/xlsbiff5.pas b/components/fpspreadsheet/xlsbiff5.pas index 55b5cd3cb..03ab855f2 100755 --- a/components/fpspreadsheet/xlsbiff5.pas +++ b/components/fpspreadsheet/xlsbiff5.pas @@ -951,6 +951,9 @@ var rec: TBIFF5LabelRecord; buf: array of byte; begin + if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then + exit; + case WorkBookEncoding of seLatin2: AnsiValue := UTF8ToCP1250(AValue); seCyrillic: AnsiValue := UTF8ToCP1251(AValue); @@ -977,6 +980,11 @@ begin // with the problem or purposefully ignore it. TextTooLong := true; AnsiValue := Copy(AnsiValue, 1, MAXBYTES); + Workbook.AddErrorMsg( + 'Text value exceeds %d character limit in cell %s. ' + + 'Text has been truncated.', [ + MAXBYTES, GetCellString(ARow, ACol) + ]); end; L := Length(AnsiValue); @@ -1004,30 +1012,6 @@ begin { Clean up } SetLength(buf, 0); - - (* - { BIFF Record header } - AStream.WriteWord(WordToLE(INT_EXCEL_ID_LABEL)); - AStream.WriteWord(WordToLE(8 + L)); - - { BIFF Record data } - AStream.WriteWord(WordToLE(ARow)); - AStream.WriteWord(WordToLE(ACol)); - - { Index to XF record } - WriteXFIndex(AStream, ACell); - - { Byte String with 16-bit size } - AStream.WriteWord(WordToLE(L)); - AStream.WriteBuffer(AnsiValue[1], L); - *) - { - //todo: keep a log of errors and show with an exception after writing file or something. - We can't just do the following - if TextTooLong then - Raise Exception.CreateFmt('Text value exceeds %d character limit in cell [%d,%d]. Text has been truncated.',[MaxBytes,ARow,ACol]); - because the file wouldn't be written. - } end; { Writes an Excel 5 STRING record which immediately follows a FORMULA record diff --git a/components/fpspreadsheet/xlsbiff8.pas b/components/fpspreadsheet/xlsbiff8.pas index c9599e6bd..07484e6a7 100755 --- a/components/fpspreadsheet/xlsbiff8.pas +++ b/components/fpspreadsheet/xlsbiff8.pas @@ -788,7 +788,11 @@ procedure TsSpreadBIFF8Writer.WriteFormula(AStream: TStream; const ARow, RPNLength: Word; TokenArraySizePos, RecordSizePos, FinalPos: Int64;} begin -(* RPNLength := 0; +(* + if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then + exit; + + RPNLength := 0; FormulaResult := 0.0; { BIFF Record header } @@ -992,6 +996,9 @@ var rec: TBIFF8LabelRecord; buf: array of byte; begin + if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then + exit; + WideValue := UTF8Decode(AValue); //to UTF16 if WideValue = '' then begin // Badly formatted UTF8String (maybe ANSI?) @@ -1007,6 +1014,11 @@ begin // with the problem or purposefully ignore it. TextTooLong := true; SetLength(WideValue, MaxBytes); //may corrupt the string (e.g. in surrogate pairs), but... too bad. + Workbook.AddErrorMsg( + 'Text value exceeds %d character limit in cell %s. ' + + 'Text has been truncated.', [ + MAXBYTES, GetCellString(ARow, ACol) + ]); end; L := Length(WideValue); @@ -1037,14 +1049,6 @@ begin { Clean up } SetLength(buf, 0); - - { - //todo: keep a log of errors and show with an exception after writing file or something. - We can't just do the following - if TextTooLong then - Raise Exception.CreateFmt('Text value exceeds %d character limit in cell [%d,%d]. Text has been truncated.',[MaxBytes,ARow,ACol]); - because the file wouldn't be written. - } end; {******************************************************************* diff --git a/components/fpspreadsheet/xlscommon.pas b/components/fpspreadsheet/xlscommon.pas index db7d4b595..527854484 100644 --- a/components/fpspreadsheet/xlscommon.pas +++ b/components/fpspreadsheet/xlscommon.pas @@ -1852,6 +1852,9 @@ procedure TsSpreadBIFFWriter.WriteBlank(AStream: TStream; var rec: TBIFF58BlankRecord; begin + if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then + exit; + { BIFF record header } rec.RecordID := WordToLE(INT_EXCEL_ID_BLANK); rec.RecordSize := WordToLE(6); @@ -1922,6 +1925,9 @@ var w: Integer; begin if Assigned(ACol) then begin + if (ACol^.Col >= FLimitations.MaxCols) then + exit; + { BIFF record header } rec.RecordID := WordToLE(INT_EXCEL_ID_COLINFO); rec.RecordSize := WordToLE(12); @@ -2035,6 +2041,9 @@ procedure TsSpreadBIFFWriter.WriteNumber(AStream: TStream; var rec: TBIFF58NumberRecord; begin + if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then + exit; + { BIFF Record header } rec.RecordID := WordToLE(INT_EXCEL_ID_NUMBER); rec.RecordSize := WordToLE(14); @@ -2260,6 +2269,9 @@ var RPNLength: Word; RecordSizePos, FinalPos: Int64; begin + if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then + exit; + { BIFF Record header } AStream.WriteWord(WordToLE(INT_EXCEL_ID_FORMULA)); RecordSizePos := AStream.Position; @@ -2482,6 +2494,12 @@ var rowheight: Word; h: Single; begin + if (ARowIndex >= FLimitations.MaxRows) or + (AFirstColIndex >= FLimitations.MaxCols) or + (ALastColIndex >= FLimitations.MaxCols) + then + exit; + // Check for additional space above/below row spaceabove := false; spacebelow := false; diff --git a/components/fpspreadsheet/xlsxooxml.pas b/components/fpspreadsheet/xlsxooxml.pas index fd6b10a8b..2aa118f1c 100755 --- a/components/fpspreadsheet/xlsxooxml.pas +++ b/components/fpspreadsheet/xlsxooxml.pas @@ -1842,7 +1842,8 @@ end; procedure TsSpreadOOXMLWriter.WriteSheetData(AStream: TStream; AWorksheet: TsWorksheet); var - r, c, c1, c2: Cardinal; + r, r1, r2: Cardinal; + c, c1, c2: Cardinal; row: PRow; value: Variant; lCell: TCell; @@ -1907,7 +1908,11 @@ begin c1 := AWorksheet.GetFirstColIndex; c2 := AWorksheet.GetLastColIndex; if (c1 = $FFFFFFFF) and (c2 = 0) then c1 := 0; // avoid arithmetic overflow in case of empty worksheet - for r := 0 to AWorksheet.GetLastRowIndex do begin + r1 := AWorksheet.GetFirstRowIndex; + r2 := AWorksheet.GetlastRowIndex; + if (r1 = $FFFFFFFF) and (r2 = 0) then r1 := 0; // avoid arithmetic overflow in case of empty worksheet +// for r := 0 to AWorksheet.GetLastRowIndex do begin + for r := r1 to r2 do begin // If the row has a custom height add this value to the specification row := AWorksheet.FindRow(r); if row <> nil then