From a0ee8b893345683dae9bf2396a2ca2b92dca3842 Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Wed, 2 Dec 2015 22:16:55 +0000 Subject: [PATCH] fpspreadsheet: - Move BasicReader/Writer from fpspreadsheet.pas to fpsReaderWriter.pas in order to fix circular unit dependence introduced in prev commit. - Remove usage of temp file in xlsx and ods stream writers (see forum forum.lazarus.freepascal.org/index.php/topic,30606.msg194818.html). git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@4398 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- components/fpspreadsheet/fpsopendocument.pas | 2 +- components/fpspreadsheet/fpspreadsheet.pas | 349 ++++-------------- components/fpspreadsheet/fpsreaderwriter.pas | 129 +++++-- .../fpspreadsheet/fpsregfileformats.pas | 4 +- components/fpspreadsheet/xlsxooxml.pas | 2 +- 5 files changed, 177 insertions(+), 309 deletions(-) diff --git a/components/fpspreadsheet/fpsopendocument.pas b/components/fpspreadsheet/fpsopendocument.pas index 1fecba698..216852e0a 100755 --- a/components/fpspreadsheet/fpsopendocument.pas +++ b/components/fpspreadsheet/fpsopendocument.pas @@ -3703,7 +3703,7 @@ begin { Now compress the files } FZip := TZipper.Create; try - FZip.FileName := '__temp__.tmp'; +// FZip.FileName := '__temp__.tmp'; FZip.Entries.AddFileEntry(FSMeta, OPENDOC_PATH_META); FZip.Entries.AddFileEntry(FSSettings, OPENDOC_PATH_SETTINGS); diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas index 4bf91bcd2..822e52918 100755 --- a/components/fpspreadsheet/fpspreadsheet.pas +++ b/components/fpspreadsheet/fpspreadsheet.pas @@ -40,8 +40,6 @@ type { Forward declarations } TsWorksheet = class; TsWorkbook = class; - TsBasicSpreadReader = class; - TsBasicSpreadWriter = class; {** Type: TRow -- record containing information about a spreadsheet row @@ -637,17 +635,11 @@ type FCellFormatList: TsCellFormatList; { Internal methods } - function CreateSpreadReader(AFormatID: TsSpreadFormatID; - AParams: TsStreamParams = []): TsBasicSpreadReader; - function CreateSpreadWriter(AFormatID: TsSpreadFormatID; - AParams: TsStreamParams = []): TsBasicSpreadWriter; - class function GetFormatFromFileHeader(const AFileName: TFileName; out AFormatID: TsSpreadFormatID): Boolean; overload; class function GetFormatFromFileHeader(AStream: TStream; out AFormatID: TsSpreadFormatID): Boolean; overload; - procedure GetLastRowColIndex(out ALastRow, ALastCol: Cardinal); procedure PrepareBeforeReading; procedure PrepareBeforeSaving; procedure ReCalc; @@ -775,6 +767,7 @@ type procedure EnableNotifications; function NotificationsEnabled: Boolean; procedure UpdateCaches; + procedure GetLastRowColIndex(out ALastRow, ALastCol: Cardinal); { Error messages } procedure AddErrorMsg(const AMsg: String); overload; @@ -819,49 +812,6 @@ type property OnReadCellData: TsWorkbookReadCellDataEvent read FOnReadCellData write FOnReadCellData; end; - { TsBasicSpreadReaderWriter } - TsBasicSpreadReaderWriter = class - protected - {@@ Instance of the workbook which is currently being read or written. } - FWorkbook: TsWorkbook; - {@@ Instance of the worksheet which is currently being read or written. } - FWorksheet: TsWorksheet; - {@@ Limitations for the specific data file format } - FLimitations: TsSpreadsheetFormatLimitations; - public - constructor Create(AWorkbook: TsWorkbook); virtual; // to allow descendents to override it - function Limitations: TsSpreadsheetFormatLimitations; - {@@ Instance of the workbook which is currently being read/written. } - property Workbook: TsWorkbook read FWorkbook; - end; - - { TsBasicSpreadReader } - TsBasicSpreadReader = class(TsBasicSpreadReaderWriter) - public - { General writing methods } - procedure ReadFromFile(AFileName: string; AParams: TsStreamParams = []); virtual; abstract; - procedure ReadFromStream(AStream: TStream; AParams: TsStreamParams = []); virtual; abstract; - procedure ReadFromStrings(AStrings: TStrings; AParams: TsStreamParams = []); virtual; abstract; - end; - - { TsBasicSpreadWriter } - TsBasicSpreadWriter = class(TsBasicSpreadReaderWriter) - public - { Helpers } - procedure CheckLimitations; virtual; - { General writing methods } - procedure WriteToFile(const AFileName: string; - const AOverwriteExisting: Boolean = False; AParams: TsStreamParams = []); virtual; abstract; - procedure WriteToStream(AStream: TStream; AParams: TsStreamParams = []); virtual; abstract; - procedure WriteToStrings(AStrings: TStrings; AParams: TsStreamParams = []); virtual; abstract; - end; - - {@@ TsSpreadReader class reference type } - TsSpreadReaderClass = class of TsBasicSpreadReader; - - {@@ TsSpreadWriter class reference type } - TsSpreadWriterClass = class of TsBasicSpreadWriter; - procedure CopyCellFormat(AFromCell, AToCell: PCell); @@ -870,7 +820,7 @@ implementation uses Math, StrUtils, DateUtils, TypInfo, lazutf8, lazFileUtils, URIParser, fpsStrings, uvirtuallayer_ole, - fpsUtils, fpsHTMLUtils, fpsRegFileFormats, fpsreaderwriter, + fpsUtils, fpsHTMLUtils, fpsRegFileFormats, fpsReaderWriter, fpsCurrency, fpsExprParser, fpsNumFormatParser; (* @@ -906,6 +856,67 @@ const DEF_FONT_AUTOMATIC_COLORVALUE = $000000; *) +{@@ ---------------------------------------------------------------------------- + Convenience method which creates the correct reader object for a given + spreadsheet format. + + @param AWorkbook Workbook to be written + @param AFormatID Identifier of the file format which is assumed when reading + a document into the workbook. An exception is raised when + the document has a different format. + + @param AParams Optional parameters to control stream access. If contains + the element spClipboard the reader knows that access is to + the clipboard, and it can read a special clipboard version + of the data. + + @return An instance of a TsBasicSpreadReader descendent which is able to + read the given file format. +-------------------------------------------------------------------------------} +function CreateSpreadReader(AWorkbook: TsWorkbook; AFormatID: TsSpreadFormatID; + AParams: TsStreamParams = []): TsBasicSpreadReader; +var + readerClass: TsSpreadReaderClass; +begin + Result := nil; + readerClass := GetSpreadReaderClass(AFormatID); + + if readerClass <> nil + then Result := readerClass.Create(AWorkbook); + + if Result = nil then + raise Exception.Create(rsUnsupportedReadFormat); +end; + +{@@ ---------------------------------------------------------------------------- + Convenience method which creates the correct writer object for a given + spreadsheet format. + + @param AWorkbook Workbook to be written + @param AFormatID Identifier of the file format which is used for writing the + workbook + @param AParams Optional parameters to control stream access. If contains + the element spClipboard then the writer can write a + dedicated clipboard version of the stream if required. + @return An instance of a TsBasicSpreadWriter descendant which is able to + write the given file format. +-------------------------------------------------------------------------------} + +function CreateSpreadWriter(AWorkbook: TsWorkbook; AFormatID: TsSpreadFormatID; + AParams: TsStreamParams = []): TsBasicSpreadWriter; +var + writerClass: TsSpreadWriterClass; +begin + Result := nil; + writerClass := GetSpreadWriterClass(AFormatID); + + if writerClass <> nil then + Result := writerClass.Create(AWorkbook); + + if Result = nil then + raise Exception.Create(rsUnsupportedWriteFormat); +end; + {@@ ---------------------------------------------------------------------------- Copies the format of a cell to another one. @@ -6640,65 +6651,6 @@ begin end; end; -{@@ ---------------------------------------------------------------------------- - Convenience method which creates the correct reader object for a given - spreadsheet format. - - @param AFormatID Identifier of the file format which is assumed when reading - a document into the workbook. An exception is raised when - the document has a different format. - - @param AParams Optional parameters to control stream access. If contains - the element spClipboard the reader knows that access is to - the clipboard, and it can read a special clipboard version - of the data. - - @return An instance of a TsCustomSpreadReader descendent which is able to - read the given file format. --------------------------------------------------------------------------------} -function TsWorkbook.CreateSpreadReader(AFormatID: TsSpreadFormatID; - AParams: TsStreamParams = []): TsBasicSpreadReader; -var - readerClass: TsSpreadReaderClass; -begin - Result := nil; - readerClass := GetSpreadReaderClass(AFormatID); - - if readerClass <> nil - then Result := readerClass.Create(self); - - if Result = nil then - raise Exception.Create(rsUnsupportedReadFormat); -end; - -{@@ ---------------------------------------------------------------------------- - Convenience method which creates the correct writer object for a given - spreadsheet format. - - @param AFormatID Identifier of the file format which is used for writing the - workbook - @param AParams Optional parameters to control stream access. If contains - the element spClipboard then the writer can write a - dedicated clipboard version of the stream if required. - - @return An instance of a TsCustomSpreadWriter descendent which is able to - write the given file format. --------------------------------------------------------------------------------} -function TsWorkbook.CreateSpreadWriter(AFormatID: TsSpreadFormatID; - AParams: TsStreamParams): TsBasicSpreadWriter; -var - writerClass: TsSpreadWriterClass; -begin - Result := nil; - writerClass := GetSpreadWriterClass(AFormatID); - - if writerClass <> nil then - Result := writerClass.Create(self); - - if Result = nil then - raise Exception.Create(rsUnsupportedWriteFormat); -end; - {@@ ---------------------------------------------------------------------------- Determines the maximum index of used columns and rows in all sheets of this workbook. Respects VirtualMode. @@ -6761,7 +6713,7 @@ begin if not FileExists(AFileName) then raise Exception.CreateFmt(rsFileNotFound, [AFileName]); - AReader := CreateSpreadReader(AFormatID); + AReader := CreateSpreadReader(self, AFormatID); try FFileName := AFileName; PrepareBeforeReading; @@ -6842,119 +6794,6 @@ begin if not success then raise Exception.CreateFmt(rsNoValidSpreadsheetFile, [AFileName]); end; -(* - - - - on E: Exception do - begin - - while True do - begin - try - ReadFromFile(AFileName, SheetType, AParams); - canLoad := True; - except - on E: Exception do - begin - if SheetType = _sfExcel8 then lException := E; - canLoad := False - end; - end; - if canLoad or (SheetType = _sfExcel2) then Break; - SheetType := Pred(SheetType); - end; - - // A failed attempt to read a file should bring an exception, so re-raise - // the exception if necessary. We re-raise the exception brought by Excel 8, - // since this is the most common format - if (not canLoad) and (lException <> nil) then raise lException; -end -else - ReadFromFile(AFileName, SheetType, AParams); -end else -raise Exception.CreateFmt(rsNoValidSpreadsheetFile, [AFileName]); - - - - - for i:=0 to High(fileformats) do - // .xls files can contain several formats. We look into the header first. - if ext = STR_EXCEL_EXTENSION then - begin - canLoad := GetFormatFromFileHeader(AFileName, SheetType); - if canLoad then begin - // Rearrange the file format list such that the format detected from the - // header is first. Must probably the other file formats are not needed, - // just to make sure... - xlsformats := GetSpreadFormatsFromFileName(AFileName, 'BIFF8'); - SetLength(fileFormats, Length(xlsformats)+1); - fileFormats[0] := sheetType; - n := 1; - for i := 0 to High(xlsformats) do - if xlsformats[i] <> sheetType then - begin - fileFormats[n] := xlsformats[i]; - inc(n); - end; - SetLength(fileformats, n); - end else - begin - // In this case the file format could not be identified from the header. - // But it is possible that this method fails for valid xls files. - // Therefore we open the file explicitly by trial and error using each - // xls reader - see below. We begin with BIFF8 which is most often used. - // Therefore, we read the file format list with BIFF8 at the first item. - fileformats := GetSpreadFormatsFromFileName(AFileName, 'BIFF8'); - SheetType := fileFormats[0]; - canLoad := true; - end; - end else - begin - SheetType := fileFormats[0]; - canLoad := (Length(fileFormats) > 0); - end; - - if not canLoad then - raise Exception.CreateFmt(rsReaderNotFound, [AFileName]); - - // Here is the trial and error loop checking the file formats with the same extension - for sf := 0 to High(fileFormats) do begin - try - ReadFromFile - - if canLoad then - begin - if SheetType = _sfExcel8 then - begin - // Here is the trial-and-error loop checking for the various biff formats. - while True do - begin - try - ReadFromFile(AFileName, SheetType, AParams); - canLoad := True; - except - on E: Exception do - begin - if SheetType = _sfExcel8 then lException := E; - canLoad := False - end; - end; - if canLoad or (SheetType = _sfExcel2) then Break; - SheetType := Pred(SheetType); - end; - - // A failed attempt to read a file should bring an exception, so re-raise - // the exception if necessary. We re-raise the exception brought by Excel 8, - // since this is the most common format - if (not canLoad) and (lException <> nil) then raise lException; - end - else - ReadFromFile(AFileName, SheetType, AParams); - end else - raise Exception.CreateFmt(rsNoValidSpreadsheetFile, [AFileName]); -end; - *) {@@ ---------------------------------------------------------------------------- Reads the document from a file, but ignores the extension. @@ -7008,7 +6847,7 @@ var AReader: TsBasicSpreadReader; ok: Boolean; begin - AReader := CreateSpreadReader(AFormatID); + AReader := CreateSpreadReader(self, AFormatID); try PrepareBeforeReading; FReadWriteFlag := rwfRead; @@ -7082,7 +6921,7 @@ procedure TsWorkbook.WriteToFile(const AFileName: string; var AWriter: TsBasicSpreadWriter; begin - AWriter := CreateSpreadWriter(AFormatID); + AWriter := CreateSpreadWriter(self, AFormatID); try FFileName := AFileName; FFormatID := AFormatID; @@ -7164,7 +7003,7 @@ procedure TsWorkbook.WriteToStream(AStream: TStream; var AWriter: TsBasicSpreadWriter; begin - AWriter := CreateSpreadWriter(AFormatID, AParams); + AWriter := CreateSpreadWriter(self, AFormatID, AParams); try FFormatID := AFormatID; PrepareBeforeSaving; @@ -8437,58 +8276,4 @@ begin end; *) -{------------------------------------------------------------------------------} -{ TsBasicSpreadReaderWriter } -{------------------------------------------------------------------------------} - -{@@ ---------------------------------------------------------------------------- - Constructor of the reader/writer. Has the workbook to be read/written as a - parameter to apply the localization information found in its FormatSettings. - - @param AWorkbook Workbook into which the file is being read or from with the - file is written. This parameter is passed from the workbook - which creates the reader/writer. --------------------------------------------------------------------------------} -constructor TsBasicSpreadReaderWriter.Create(AWorkbook: TsWorkbook); -begin - inherited Create; - FWorkbook := AWorkbook; - { A good starting point valid for many formats ... } - FLimitations.MaxColCount := 256; - FLimitations.MaxRowCount := 65536; - FLimitations.MaxPaletteSize := MaxInt; -end; - -{@@ ---------------------------------------------------------------------------- - Returns a record containing limitations of the specific file format of the - writer. --------------------------------------------------------------------------------} -function TsBasicSpreadReaderWriter.Limitations: TsSpreadsheetFormatLimitations; -begin - Result := FLimitations; -end; - - -{------------------------------------------------------------------------------} -{ TsBasicSpreadWriter } -{------------------------------------------------------------------------------} - -{@@ ---------------------------------------------------------------------------- - Checks limitations of the writer, e.g max row/column count --------------------------------------------------------------------------------} -procedure TsBasicSpreadWriter.CheckLimitations; -var - lastCol, lastRow: Cardinal; -begin - Workbook.GetLastRowColIndex(lastRow, lastCol); - - // Check row count - if lastRow >= FLimitations.MaxRowCount then - Workbook.AddErrorMsg(rsMaxRowsExceeded, [lastRow+1, FLimitations.MaxRowCount]); - - // Check column count - if lastCol >= FLimitations.MaxColCount then - Workbook.AddErrorMsg(rsMaxColsExceeded, [lastCol+1, FLimitations.MaxColCount]); -end; - end. {** End Unit: fpspreadsheet } diff --git a/components/fpspreadsheet/fpsreaderwriter.pas b/components/fpspreadsheet/fpsreaderwriter.pas index 0c80fe13f..a8faea27f 100644 --- a/components/fpspreadsheet/fpsreaderwriter.pas +++ b/components/fpspreadsheet/fpsreaderwriter.pas @@ -23,6 +23,50 @@ uses fpsTypes, fpsClasses, fpSpreadsheet; type + + { TsBasicSpreadReaderWriter } + TsBasicSpreadReaderWriter = class + protected + {@@ Instance of the workbook which is currently being read or written. } + FWorkbook: TsWorkbook; + {@@ Instance of the worksheet which is currently being read or written. } + FWorksheet: TsWorksheet; + {@@ Limitations for the specific data file format } + FLimitations: TsSpreadsheetFormatLimitations; + public + constructor Create(AWorkbook: TsWorkbook); virtual; // to allow descendents to override it + function Limitations: TsSpreadsheetFormatLimitations; + {@@ Instance of the workbook which is currently being read/written. } + property Workbook: TsWorkbook read FWorkbook; + end; + + { TsBasicSpreadReader } + TsBasicSpreadReader = class(TsBasicSpreadReaderWriter) + public + { General writing methods } + procedure ReadFromFile(AFileName: string; AParams: TsStreamParams = []); virtual; abstract; + procedure ReadFromStream(AStream: TStream; AParams: TsStreamParams = []); virtual; abstract; + procedure ReadFromStrings(AStrings: TStrings; AParams: TsStreamParams = []); virtual; abstract; + end; + + { TsBasicSpreadWriter } + TsBasicSpreadWriter = class(TsBasicSpreadReaderWriter) + public + { Helpers } + procedure CheckLimitations; virtual; + { General writing methods } + procedure WriteToFile(const AFileName: string; + const AOverwriteExisting: Boolean = False; AParams: TsStreamParams = []); virtual; abstract; + procedure WriteToStream(AStream: TStream; AParams: TsStreamParams = []); virtual; abstract; + procedure WriteToStrings(AStrings: TStrings; AParams: TsStreamParams = []); virtual; abstract; + end; + + {@@ TsSpreadReader class reference type } + TsSpreadReaderClass = class of TsBasicSpreadReader; + + {@@ TsSpreadWriter class reference type } + TsSpreadWriterClass = class of TsBasicSpreadWriter; + {@@ Custom reader of spreadsheet files. "Custom" means that it provides only the basic functionality. The main implementation is done in derived classes @@ -141,12 +185,67 @@ implementation uses Math, - fpsStrings, fpsUtils, fpsNumFormat, fpsStreams; + fpsStrings, fpsUtils, fpsNumFormat, fpsStreams, fpsRegFileFormats; -{******************************************************************************* -* TsCustomSpreadReader * -*******************************************************************************} +{------------------------------------------------------------------------------} +{ TsBasicSpreadReaderWriter } +{------------------------------------------------------------------------------} + +{@@ ---------------------------------------------------------------------------- + Constructor of the reader/writer. Has the workbook to be read/written as a + parameter to apply the localization information found in its FormatSettings. + + @param AWorkbook Workbook into which the file is being read or from with the + file is written. This parameter is passed from the workbook + which creates the reader/writer. +-------------------------------------------------------------------------------} +constructor TsBasicSpreadReaderWriter.Create(AWorkbook: TsWorkbook); +begin + inherited Create; + FWorkbook := AWorkbook; + { A good starting point valid for many formats ... } + FLimitations.MaxColCount := 256; + FLimitations.MaxRowCount := 65536; + FLimitations.MaxPaletteSize := MaxInt; +end; + +{@@ ---------------------------------------------------------------------------- + Returns a record containing limitations of the specific file format of the + writer. +-------------------------------------------------------------------------------} +function TsBasicSpreadReaderWriter.Limitations: TsSpreadsheetFormatLimitations; +begin + Result := FLimitations; +end; + + +{------------------------------------------------------------------------------} +{ TsBasicSpreadWriter } +{------------------------------------------------------------------------------} + +{@@ ---------------------------------------------------------------------------- + Checks limitations of the writer, e.g max row/column count +-------------------------------------------------------------------------------} +procedure TsBasicSpreadWriter.CheckLimitations; +var + lastCol, lastRow: Cardinal; +begin + Workbook.GetLastRowColIndex(lastRow, lastCol); + + // Check row count + if lastRow >= FLimitations.MaxRowCount then + Workbook.AddErrorMsg(rsMaxRowsExceeded, [lastRow+1, FLimitations.MaxRowCount]); + + // Check column count + if lastCol >= FLimitations.MaxColCount then + Workbook.AddErrorMsg(rsMaxColsExceeded, [lastCol+1, FLimitations.MaxColCount]); +end; + + +{------------------------------------------------------------------------------} +{ TsCustomSpreadReader } +{------------------------------------------------------------------------------} {@@ ---------------------------------------------------------------------------- Constructor of the reader. Has the workbook to be read as a @@ -335,9 +434,9 @@ begin end; -{******************************************************************************* -* TsCustomSpreadWriter * -*******************************************************************************} +{------------------------------------------------------------------------------} +{ TsCustomSpreadWriter } +{------------------------------------------------------------------------------} {@@ ---------------------------------------------------------------------------- Constructor of the writer. Has the workbook to be written as a parameter to @@ -388,23 +487,7 @@ begin exit; Result := -1; end; - (* -{@@ ---------------------------------------------------------------------------- - If a color index is greater then the maximum palette color count this - color is replaced by the closest palette color. - The present implementation does not change the color. Must be overridden by - writers of formats with limited palette sizes. - - @param AColor Color palette index to be checked - @return Closest color to AColor. If AColor belongs to the palette it must - be returned unchanged. --------------------------------------------------------------------------------} -function TsCustomSpreadWriter.FixColor(AColor: TsColor): TsColor; -begin - Result := AColor; -end; - *) {@@ ---------------------------------------------------------------------------- If formatting features of a cell are not supported by the destination file format of the writer, here is the place to apply replacements. diff --git a/components/fpspreadsheet/fpsregfileformats.pas b/components/fpspreadsheet/fpsregfileformats.pas index d4eb6a257..d2b634c87 100644 --- a/components/fpspreadsheet/fpsregfileformats.pas +++ b/components/fpspreadsheet/fpsregfileformats.pas @@ -8,7 +8,7 @@ distribution, for details about the license. USAGE: Each unit implementing a new spreadsheet format must register the - reader/writer ad some specific data by calling "RegisterSpreadFormat". + reader/writer and some specific data by calling "RegisterSpreadFormat". -------------------------------------------------------------------------------} unit fpsRegFileFormats; @@ -17,7 +17,7 @@ unit fpsRegFileFormats; interface uses - Classes, SysUtils, fpstypes, fpspreadsheet; + Classes, SysUtils, fpstypes, fpsReaderWriter; //fpspreadsheet; type TsSpreadFileAccess = (faRead, faWrite); diff --git a/components/fpspreadsheet/xlsxooxml.pas b/components/fpspreadsheet/xlsxooxml.pas index f1db44d48..0b713c44e 100755 --- a/components/fpspreadsheet/xlsxooxml.pas +++ b/components/fpspreadsheet/xlsxooxml.pas @@ -3600,7 +3600,7 @@ begin { Now compress the files } FZip := TZipper.Create; try - FZip.FileName := '__temp__.tmp'; +// FZip.FileName := '__temp__.tmp'; FZip.Entries.AddFileEntry(FSContentTypes, OOXML_PATH_TYPES); FZip.Entries.AddFileEntry(FSRelsRels, OOXML_PATH_RELS_RELS); FZip.Entries.AddFileEntry(FSWorkbookRels, OOXML_PATH_XL_RELS_RELS);