diff --git a/components/fpspreadsheet/examples/excel5demo/excel5demo.lpi b/components/fpspreadsheet/examples/excel5demo/excel5demo.lpi index c1f160d4e..c41667f28 100755 --- a/components/fpspreadsheet/examples/excel5demo/excel5demo.lpi +++ b/components/fpspreadsheet/examples/excel5demo/excel5demo.lpi @@ -6,7 +6,7 @@ - + @@ -30,15 +30,15 @@ - + - - + + - + @@ -107,8 +107,8 @@ - - + + @@ -130,8 +130,8 @@ - - + + @@ -140,8 +140,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -158,6 +194,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/fpspreadsheet/fpolestorage.pas b/components/fpspreadsheet/fpolestorage.pas index ce433dffb..79e8908b5 100755 --- a/components/fpspreadsheet/fpolestorage.pas +++ b/components/fpspreadsheet/fpolestorage.pas @@ -13,32 +13,175 @@ unit fpolestorage; interface -uses {$ifdef Windows} + {$define FPOLESTORAGE_USE_COM} +{$endif} + +uses +{$ifdef FPOLESTORAGE_USE_COM} ActiveX, ComObj, {$endif} - Classes, SysUtils; + Classes, SysUtils, + fpsutils; type + { Describes an OLE Document } + + TOLEDocument = record + Sections: array of TMemoryStream; + end; + + { TOLEStorage } TOLEStorage = class private -{$ifdef Windows} +{$ifdef FPOLESTORAGE_USE_COM} FStorage: IStorage; FStream: IStream; {$endif} + { Information filled by the write routines for the helper routines } + FOLEDocument: TOLEDocument; + FNumSectors: Cardinal; + { Helper routines } + procedure WriteOLEHeader(AStream: TStream); + procedure WriteSectorAllocationTable(AStream: TStream); public constructor Create; destructor Destroy; override; - procedure WriteStreamToOLEFile(AFileName: string; AMemStream: TMemoryStream); + procedure WriteOLEFile(AFileName: string; AOLEDocument: TOLEDocument); end; implementation { TOLEStorage } +{ +4.1 Compound Document Header Contents +The header is always located at the beginning of the file, and its size is exactly 512 bytes. This implies that the first +sector (with SecID 0) always starts at file offset 512. +} +procedure TOLEStorage.WriteOLEHeader(AStream: TStream); +var + i: Integer; +begin + { + Contents of the compound document header structure: + Offset Size Contents + 0 8 Compound document file identifier: D0H CFH 11H E0H A1H B1H 1AH E1H + } + AStream.WriteByte($D0); + AStream.WriteByte($CF); + AStream.WriteByte($11); + AStream.WriteByte($E0); + AStream.WriteByte($A1); + AStream.WriteByte($B1); + AStream.WriteByte($1A); + AStream.WriteByte($E1); + + { 8 16 Unique identifier (UID) of this file (not of interest in the following, may be all 0) } + AStream.WriteDWord(0); + AStream.WriteDWord(0); + + { 24 2 Revision number of the file format (most used is 003EH) } + AStream.WriteWord(WordToLE($003E)); + + { 26 2 Version number of the file format (most used is 0003H) } + AStream.WriteWord(WordToLE($0003)); + + { 28 2 Byte order identifier (➜4.2): FEH FFH = Little-Endian + FFH FEH = Big-Endian } + AStream.WriteByte($FE); + AStream.WriteByte($FF); + + { 30 2 Size of a sector in the compound document file (➜3.1) in power-of-two (ssz), real sector + size is sec_size = 2ssz bytes (minimum value is 7 which means 128 bytes, most used + value is 9 which means 512 bytes) } + AStream.WriteWord(WordToLE($0009)); + + { 32 2 Size of a short-sector in the short-stream container stream (➜6.1) in power-of-two (sssz), + real short-sector size is short_sec_size = 2sssz bytes (maximum value is sector size + ssz, see above, most used value is 6 which means 64 bytes) } + AStream.WriteWord(WordToLE($0006)); + + { 34 10 Not used } + AStream.WriteDWord($0); + AStream.WriteDWord($0); + AStream.WriteWord($0); + + { 44 4 Total number of sectors used for the sector allocation table (➜5.2) } + AStream.WriteDWord(DWordToLE(FNumSectors)); + + { 48 4 SecID of first sector of the directory stream (➜7) } + AStream.WriteDWord(DWordToLE($01)); + + { 52 4 Not used } + AStream.WriteDWord($0); + + { 56 4 Minimum size of a standard stream (in bytes, minimum allowed and most used size is 4096 + bytes), streams with an actual size smaller than (and not equal to) this value are stored as + short-streams (➜6) } + AStream.WriteDWord(DWordToLE(4096)); + + { 60 4 SecID of first sector of the short-sector allocation table (➜6.2), or –2 (End Of Chain + SecID, ➜3.1) if not extant } + AStream.WriteDWord(DWordToLE(2)); + + { 64 4 Total number of sectors used for the short-sector allocation table (➜6.2) } + AStream.WriteDWord(DWordToLE(1)); + + { 68 4 SecID of first sector of the master sector allocation table (➜5.1), or –2 (End Of Chain + SecID, ➜3.1) if no additional sectors used } + AStream.WriteDWord(IntegerToLE(-2)); + + { 72 4 Total number of sectors used for the master sector allocation table (➜5.1) } + AStream.WriteDWord(0); + + { 76 436 First part of the master sector allocation table (➜5.1) containing 109 SecIDs } + AStream.WriteDWord(0); + + for i := 1 to 108 do AStream.WriteDWord($FFFFFFFF); +end; + +procedure TOLEStorage.WriteSectorAllocationTable(AStream: TStream); +var + i: Integer; +begin + { Simple copy of an example OLE file + + 00000200H FD FF FF FF FF FF FF FF FE FF FF FF 04 00 00 00 + 00000210H 05 00 00 00 06 00 00 00 07 00 00 00 08 00 00 00 + 00000220H 09 00 00 00 FE FF FF FF 0B 00 00 00 FE FF FF FF + + And from now on only $FFFFFFFF covering $230 to $3FF + for a total of $400 - $230 bytes of $FF } + + AStream.WriteDWord(DWordToLE($FFFFFFFD)); + AStream.WriteDWord($FFFFFFFF); + AStream.WriteDWord(DWordToLE($FFFFFFFE)); + AStream.WriteDWord(DWordToLE($00000004)); + AStream.WriteDWord(DWordToLE($00000005)); + AStream.WriteDWord(DWordToLE($00000006)); + AStream.WriteDWord(DWordToLE($00000007)); + AStream.WriteDWord(DWordToLE($00000008)); + AStream.WriteDWord(DWordToLE($00000009)); + AStream.WriteDWord(DWordToLE($FFFFFFFE)); + AStream.WriteDWord(DWordToLE($0000000B)); + AStream.WriteDWord(DWordToLE($FFFFFFFE)); + + for i := 1 to ($400 - $230) do AStream.WriteByte($FF); + + { + This results in the following SecID array for the SAT: + + Array indexes 0 1 2 3 4 5 6 7 8 9 10 11 12 ... + SecID array –3 –1 –2 4 5 6 7 8 9 –2 11 –2 –1 ... + + As expected, sector 0 is marked with the special SAT SecID (➜3.1). Sector 1 and all sectors starting with sector 12 are + not used (special Free SecID with value –1). } +end; + constructor TOLEStorage.Create; begin inherited Create; @@ -51,11 +194,17 @@ begin inherited Destroy; end; -procedure TOLEStorage.WriteStreamToOLEFile(AFileName: string; AMemStream: TMemoryStream); +procedure TOLEStorage.WriteOLEFile(AFileName: string; AOLEDocument: TOLEDocument); var cbWritten: Cardinal; + AFileStream: TFileStream; + i: Cardinal; begin -{$ifdef Windows} + { Fill information for helper routines } + FOLEDocument := AOLEDocument; + FNumSectors := Length(AOLEDocument.Sections); + +{$ifdef FPOLESTORAGE_USE_COM} { Initialize the Component Object Model (COM) before calling s functions } OleCheck(CoInitialize(nil)); @@ -64,13 +213,24 @@ begin STGM_READWRITE or STGM_FAILIFTHERE or STGM_SHARE_EXCLUSIVE or STGM_DIRECT, 0, FStorage)); - { Create a workbook stream in the storage. A BIFF5 file must - have at least a workbook stream. This stream *must* be named 'Book' } - OleCheck(FStorage.CreateStream('Book', - STGM_READWRITE or STGM_SHARE_EXCLUSIVE or STGM_DIRECT, 0, 0, FStream)); + for i := 0 to FNumSectors do + begin + { Create a workbook stream in the storage. A BIFF5 file must + have at least a workbook stream. This stream *must* be named 'Book' } + OleCheck(FStorage.CreateStream('Book', + STGM_READWRITE or STGM_SHARE_EXCLUSIVE or STGM_DIRECT, 0, 0, FStream)); - { Write all data } - FStream.Write(AMemStream.Memory, AMemStream.Size, @cbWritten); + { Write all data } + FStream.Write(FOLEDocument.Sections[i].Memory, + FOLEDocument.Sections[i].Size, @cbWritten); + end; +{$else} + AFileStream := TFileStream.Create(AFileName, fmOpenWrite or fmCreate); + try + WriteOLEHeader(AFileStream); + finally + AFileStream.Free; + end; {$endif} end; diff --git a/components/fpspreadsheet/laz_fpspreadsheet.lpk b/components/fpspreadsheet/laz_fpspreadsheet.lpk index 15e5eb658..7288ac779 100644 --- a/components/fpspreadsheet/laz_fpspreadsheet.lpk +++ b/components/fpspreadsheet/laz_fpspreadsheet.lpk @@ -14,7 +14,7 @@ - + @@ -47,6 +47,10 @@ + + + + diff --git a/components/fpspreadsheet/reference/compdocfileformat.pdf b/components/fpspreadsheet/reference/compdocfileformat.pdf new file mode 100644 index 000000000..0f177dd2a Binary files /dev/null and b/components/fpspreadsheet/reference/compdocfileformat.pdf differ diff --git a/components/fpspreadsheet/xlsbiff2.pas b/components/fpspreadsheet/xlsbiff2.pas index c158577d6..61a2fd758 100755 --- a/components/fpspreadsheet/xlsbiff2.pas +++ b/components/fpspreadsheet/xlsbiff2.pas @@ -29,7 +29,7 @@ interface uses Classes, SysUtils, - fpspreadsheet; + fpspreadsheet, fpsutils; type @@ -67,23 +67,6 @@ const INT_EXCEL_CHART = $0020; INT_EXCEL_MACRO_SHEET = $0040; -{ - Endianess helper functions - - Excel files are all written with Little Endian byte order, - so it's necessary to swap the data to be able to build a - correct file on big endian systems. -} - -function WordToLE(AValue: Word): Word; -begin - {$IFDEF BIG_ENDIAN} - Result := ((AValue shl 8) and $FF00) or ((AValue shr 8) and $00FF); - {$ELSE} - Result := AValue; - {$ENDIF} -end; - { TsSpreadBIFF2Writer } {******************************************************************* diff --git a/components/fpspreadsheet/xlsbiff5.pas b/components/fpspreadsheet/xlsbiff5.pas index 0a8f75750..2490403d4 100755 --- a/components/fpspreadsheet/xlsbiff5.pas +++ b/components/fpspreadsheet/xlsbiff5.pas @@ -55,7 +55,7 @@ interface uses Classes, SysUtils, fpcanvas, - fpspreadsheet, fpolestorage; + fpspreadsheet, fpolestorage, fpsutils; type @@ -182,23 +182,6 @@ const MASK_XF_VERT_ALIGN = $70; -{ - Endianess helper functions - - Excel files are all written with Little Endian byte order, - so it's necessary to swap the data to be able to build a - correct file on big endian systems. -} - -function WordToLE(AValue: Word): Word; -begin - {$IFDEF BIG_ENDIAN} - Result := ((AValue shl 8) and $FF00) or ((AValue shr 8) and $00FF); - {$ELSE} - Result := AValue; - {$ENDIF} -end; - { Exported functions } @@ -224,16 +207,22 @@ procedure TsSpreadBIFF5Writer.WriteToFile(AFileName: string; AData: TsWorkbook); var MemStream: TMemoryStream; OutputStorage: TOLEStorage; + OLEDocument: TOLEDocument; begin MemStream := TMemoryStream.Create; OutputStorage := TOLEStorage.Create; try WriteToStream(MemStream, AData); - OutputStorage.WriteStreamToOLEFile(AFileName, MemStream); + SetLength(OLEDocument.Sections, 1); + OLEDocument.Sections[0] := MemStream; + + OutputStorage.WriteOLEFile(AFileName, OLEDocument); finally MemStream.Free; OutputStorage.Free; + + SetLength(OLEDocument.Sections, 0); end; end;