fpspreadsheet: Read default column width and row height from biff and ooxml files

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3532 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2014-09-09 11:42:20 +00:00
parent b60590d691
commit cfb50bf064
8 changed files with 245 additions and 179 deletions

View File

@ -1066,11 +1066,13 @@ type
FVirtualCell: TCell;
{@@ Stores if the reader is in virtual mode }
FIsVirtualMode: Boolean;
{ Helper methods }
{@@ Removes column records if all of them have the same column width }
procedure FixCols(AWorksheet: TsWorksheet);
{@@ Removes row records if all of them have the same row height }
procedure FixRows(AWorksheet: TsWorksheet);
{ Record reading methods }
{@@ Abstract method for reading a blank cell. Must be overridden by descendent classes. }
procedure ReadBlank(AStream: TStream); virtual; abstract;
@ -6583,7 +6585,7 @@ end;
{@@
Deletes unnecessary column records as they are written by Office applications
when converting a file to another format.
when they convert a file to another format.
@param AWorksheet The columns in this worksheet are processed.
}
@ -6593,20 +6595,10 @@ const
var
c: Cardinal;
w: Single;
cLast: Cardinal;
begin
if AWorksheet.Cols.Count = 0 then
if AWorksheet.Cols.Count <= 1 then
exit;
(* Better to avoid this...
// Delete all column records after the last column containing an existing cell.
cLast := AWorksheet.GetLastOccupiedColIndex;
for c := AWorksheet.Cols.Count-1 downto 0 do
if PCol(AWorksheet.Cols[c])^.Col > cLast then
AWorksheet.RemoveCol(c);
*)
// Check whether all columns have the same column width
w := PCol(AWorksheet.Cols[0])^.Width;
for c := 1 to AWorksheet.Cols.Count-1 do
@ -6617,11 +6609,6 @@ begin
// to the DefaultColWidth and delete all column records.
AWorksheet.DefaultColWidth := w;
AWorksheet.RemoveAllCols;
// To do:
// There's probably more to be done here, such as:
// - if all columns have the same width except for a few use their width as
// DefaultColWidth and delete these records.
end;
{@@
@ -6637,18 +6624,9 @@ var
rLast: Cardinal;
h: Single;
begin
if AWorksheet.Rows.Count = 0 then
if AWorksheet.Rows.Count <= 1 then
exit;
(* Better to avoid this...
// Delete all row records after the last row containing an existing cell.
rLast := AWorksheet.GetLastOccupiedRowIndex;
for r := AWorksheet.Rows.Count-1 downto 0 do
if PRow(AWorksheet.Rows[r])^.Row > rLast then
AWorksheet.RemoveRow(r);
*)
// Check whether all rows have the same height
h := PRow(AWorksheet.Rows[0])^.Height;
for r := 1 to AWorksheet.Rows.Count-1 do
@ -6659,11 +6637,6 @@ begin
// to the DefaultRowHeight and delete all row records.
AWorksheet.DefaultRowHeight := h;
AWorksheet.RemoveAllRows;
// To do:
// There's probably more to be done here, such as:
// - if all rows have the same height except for a few use their height as
// DefaultRowHeight and delete these records.
end;
{@@

View File

@ -957,7 +957,8 @@ begin
numBytes := 2;
Move(FBuffer[FBufferIndex], w, numbytes);
ShowInRow(FCurrRow, FBufferIndex, numBytes, IntToStr(WordLEToN(w)),
w := WordLEToN(w);
ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('%d (%f characters)', [w, w/256]),
'Width of the columns in 1/256 of the width of the zero character, using default font (first FONT record in the file)');
numBytes := 2;

View File

@ -66,6 +66,7 @@ type
out ANumberFormat: TsNumberFormat; out ANumberFormatStr: String); override;
procedure ReadBlank(AStream: TStream); override;
procedure ReadColWidth(AStream: TStream);
procedure ReadDefRowHeight(AStream: TStream);
procedure ReadFont(AStream: TStream);
procedure ReadFontColor(AStream: TStream);
procedure ReadFormat(AStream: TStream); override;
@ -170,8 +171,9 @@ const
INT_EXCEL_ID_BOF = $0009;
{%H-}INT_EXCEL_ID_INDEX = $000B;
INT_EXCEL_ID_FORMAT = $001E;
INT_EXCEL_ID_FORMATCOUNT= $001F;
INT_EXCEL_ID_FORMATCOUNT = $001F;
INT_EXCEL_ID_COLWIDTH = $0024;
INT_EXCEL_ID_DEFROWHEIGHT = 00025;
INT_EXCEL_ID_WINDOW2 = $003E;
INT_EXCEL_ID_XF = $0043;
INT_EXCEL_ID_IXFE = $0044;
@ -423,9 +425,7 @@ var
c, c1, c2: Cardinal;
w: Word;
col: TCol;
sheet: TsWorksheet;
begin
sheet := Workbook.GetFirstWorksheet;
// read column start and end index of column range
c1 := AStream.ReadByte;
c2 := AStream.ReadByte;
@ -433,9 +433,21 @@ begin
w := WordLEToN(AStream.ReadWord);
// calculate width in units of "characters"
col.Width := w / 256;
// assign width to columns
// assign width to columns, but only if different from default column width.
if not SameValue(col.Width, FWorksheet.DefaultColWidth) then
for c := c1 to c2 do
sheet.WriteColInfo(c, col);
FWorksheet.WriteColInfo(c, col);
end;
procedure TsSpreadBIFF2Reader.ReadDefRowHeight(AStream: TStream);
var
hw: word;
h : Single;
begin
hw := WordLEToN(AStream.ReadWord);
h := TwipsToPts(hw and $8000) / FWorkbook.GetDefaultFontSize;
if h > ROW_HEIGHT_CORRECTION then
FWorksheet.DefaultRowHeight := h - ROW_HEIGHT_CORRECTION;
end;
procedure TsSpreadBIFF2Reader.ReadFont(AStream: TStream);
@ -519,7 +531,9 @@ begin
INT_EXCEL_ID_FORMULA : ReadFormula(AStream);
INT_EXCEL_ID_STRING : ReadStringRecord(AStream);
INT_EXCEL_ID_COLWIDTH : ReadColWidth(AStream);
INT_EXCEL_ID_DEFCOLWIDTH : ReadDefColWidth(AStream);
INT_EXCEL_ID_ROW : ReadRowInfo(AStream);
INT_EXCEL_ID_DEFROWHEIGHT: ReadDefRowHeight(AStream);
INT_EXCEL_ID_WINDOW2 : ReadWindow2(AStream);
INT_EXCEL_ID_PANE : ReadPane(AStream);
INT_EXCEL_ID_XF : ReadXF(AStream);

View File

@ -82,13 +82,14 @@ type
FCurrentWorksheet: Integer;
protected
{ Record writing methods }
procedure ReadBoundsheet(AStream: TStream);
procedure ReadFont(const AStream: TStream);
procedure ReadFormat(AStream: TStream); override;
procedure ReadLabel(AStream: TStream); override;
procedure ReadWorkbookGlobals(AStream: TStream; AData: TsWorkbook);
procedure ReadWorksheet(AStream: TStream; AData: TsWorkbook);
procedure ReadBoundsheet(AStream: TStream);
procedure ReadRichString(AStream: TStream);
procedure ReadStandardWidth(AStream: TStream; ASheet: TsWorksheet);
procedure ReadStringRecord(AStream: TStream); override;
procedure ReadXF(AStream: TStream);
public
@ -222,7 +223,7 @@ const
{ BOF record constants }
INT_BOF_BIFF5_VER = $0500;
INT_BOF_WORKBOOK_GLOBALS= $0005;
INT_BOF_WORKBOOK_GLOBALS = $0005;
{%H-}INT_BOF_VB_MODULE = $0006;
INT_BOF_SHEET = $0010;
{%H-}INT_BOF_CHART = $0020;
@ -231,9 +232,10 @@ const
INT_BOF_BUILD_ID = $1FD2;
INT_BOF_BUILD_YEAR = $07CD;
{ FONT record constants }
INT_FONT_WEIGHT_NORMAL = $0190;
{ Record IDs }
INT_EXCEL_ID_STANDARDWIDTH = $0099;
{ FONT record constants }
{%H-}BYTE_ANSILatin1 = $00;
{%H-}BYTE_SYSTEM_DEFAULT = $01;
{%H-}BYTE_SYMBOL = $02;
@ -1168,9 +1170,11 @@ begin
INT_EXCEL_ID_RK : ReadRKValue(AStream); //(RK) This record represents a cell that contains an RK value (encoded integer or floating-point value). If a floating-point value cannot be encoded to an RK value, a NUMBER record will be written. This record replaces the record INTEGER written in BIFF2.
INT_EXCEL_ID_MULRK : ReadMulRKValues(AStream);
INT_EXCEL_ID_COLINFO : ReadColInfo(AStream);
INT_EXCEL_ID_STANDARDWIDTH : ReadStandardWidth(AStream, FWorksheet);
INT_EXCEL_ID_DEFCOLWIDTH : ReadDefColWidth(AStream);
INT_EXCEL_ID_ROW : ReadRowInfo(AStream);
INT_EXCEL_ID_FORMULA : ReadFormula(AStream);
INT_EXCEL_ID_SHAREDFMLA: ReadSharedFormula(AStream);
INT_EXCEL_ID_SHAREDFMLA : ReadSharedFormula(AStream);
INT_EXCEL_ID_STRING : ReadStringRecord(AStream);
INT_EXCEL_ID_WINDOW2 : ReadWindow2(AStream);
INT_EXCEL_ID_PANE : ReadPane(AStream);
@ -1291,6 +1295,20 @@ begin
Workbook.OnReadCellData(Workbook, ARow, ACol, cell);
end;
{ Reads the default column width that is used when a bit in the GCW bit structure
is set for the corresponding column. The GCW is ignored here. The column
width read from the STANDARDWIDTH record overrides the one from the
DEFCOLWIDTH record. }
procedure TsSpreadBIFF5Reader.ReadStandardWidth(AStream: TStream; ASheet: TsWorksheet);
var
w: Word;
begin
// read width in 1/256 of the width of "0" character
w := WordLEToN(AStream.ReadWord);
// calculate width in units of "characters" and use it as DefaultColWidth
ASheet.DefaultColWidth := w / 256;
end;
{ Reads a STRING record which contains the result of string formula. }
procedure TsSpreadBIFF5Reader.ReadStringRecord(AStream: TStream);
var

View File

@ -1435,7 +1435,7 @@ begin
INT_EXCEL_ID_NUMBER : ReadNumber(AStream);
INT_EXCEL_ID_LABEL : ReadLabel(AStream);
INT_EXCEL_ID_FORMULA : ReadFormula(AStream);
INT_EXCEL_ID_SHAREDFMLA: ReadSharedFormula(AStream);
INT_EXCEL_ID_SHAREDFMLA : ReadSharedFormula(AStream);
INT_EXCEL_ID_STRING : ReadStringRecord(AStream);
//(RSTRING) This record stores a formatted text cell (Rich-Text).
// In BIFF8 it is usually replaced by the LABELSST record. Excel still
@ -1448,6 +1448,7 @@ begin
INT_EXCEL_ID_RK : ReadRKValue(AStream);
INT_EXCEL_ID_MULRK : ReadMulRKValues(AStream);
INT_EXCEL_ID_LABELSST : ReadLabelSST(AStream); //BIFF8 only
INT_EXCEL_ID_DEFCOLWIDTH : ReadDefColWidth(AStream);
INT_EXCEL_ID_COLINFO : ReadColInfo(AStream);
INT_EXCEL_ID_ROW : ReadRowInfo(AStream);
INT_EXCEL_ID_WINDOW2 : ReadWindow2(AStream);

View File

@ -25,10 +25,11 @@ const
INT_EXCEL_ID_EOF = $000A;
INT_EXCEL_ID_SELECTION = $001D;
INT_EXCEL_ID_CONTINUE = $003C;
INT_EXCEL_ID_PANE = $0041;
INT_EXCEL_ID_CODEPAGE = $0042;
INT_EXCEL_ID_DATEMODE = $0022;
INT_EXCEL_ID_WINDOW1 = $003D;
INT_EXCEL_ID_PANE = $0041;
INT_EXCEL_ID_CODEPAGE = $0042;
INT_EXCEL_ID_DEFCOLWIDTH = $0055;
{ RECORD IDs which did not change across versions 2, 5, 8}
INT_EXCEL_ID_FORMULA = $0006; // BIFF3: $0206, BIFF4: $0406
@ -46,6 +47,7 @@ const
INT_EXCEL_ID_STRING = $0207; // BIFF2: $0007
INT_EXCEL_ID_ROW = $0208; // BIFF2: $0008
INT_EXCEL_ID_INDEX = $020B; // BIFF2: $000B
INT_EXCEL_ID_DEFROWHEIGHT= $0225; // BIFF2: $0025
INT_EXCEL_ID_WINDOW2 = $023E; // BIFF2: $003E
INT_EXCEL_ID_RK = $027E; // does not exist in BIFF2
INT_EXCEL_ID_STYLE = $0293; // does not exist in BIFF2
@ -82,8 +84,8 @@ const
WORD_CP_1258_Latin1_BIFF2_3 = 32769; // BIFF2-BIFF3
{ DATEMODE record, 5.28 }
DATEMODE_1900_BASE=1; //1/1/1900 minus 1 day in FPC TDateTime
DATEMODE_1904_BASE=1462; //1/1/1904 in FPC TDateTime
DATEMODE_1900_BASE = 1; //1/1/1900 minus 1 day in FPC TDateTime
DATEMODE_1904_BASE = 1462; //1/1/1904 in FPC TDateTime
{ WINDOW1 record constants - BIFF5-BIFF8 }
MASK_WINDOW1_OPTION_WINDOW_HIDDEN = $0001;
@ -245,6 +247,10 @@ type
procedure ReadColInfo(const AStream: TStream);
// Figures out what the base year for dates is for this file
procedure ReadDateMode(AStream: TStream);
// Reads the default column width
procedure ReadDefColWidth(AStream: TStream);
// Reas the default row height
procedure ReadDefRowHeight(AStream: TStream);
// Read FORMAT record (cell formatting)
procedure ReadFormat(AStream: TStream); virtual;
// Read FORMULA record
@ -963,6 +969,8 @@ end;
Valid for BIFF3-BIFF8.
For BIFF2 use the records COLWIDTH and COLUMNDEFAULT. }
procedure TsSpreadBiffReader.ReadColInfo(const AStream: TStream);
const
EPS = 1E-2; // allow for large epsilon because col width calculation is not very well-defined...
var
c, c1, c2: Cardinal;
w: Word;
@ -975,7 +983,8 @@ begin
w := WordLEToN(AStream.ReadWord);
// calculate width in units of "characters"
col.Width := w / 256;
// assign width to columns
// assign width to columns, but only if different from default column width
if not SameValue(col.Width, FWorksheet.DefaultColWidth, EPS) then
for c := c1 to c2 do
FWorksheet.WriteColInfo(c, col);
end;
@ -1002,6 +1011,30 @@ begin
end;
end;
// Reads the default column width
procedure TsSpreadBIFFReader.ReadDefColWidth(AStream: TStream);
begin
// The file contains the column width in characters
FWorksheet.DefaultColWidth := WordLEToN(AStream.ReadWord);
end;
// Reads the default row height
// Valid for BIFF3 - BIFF8 (override for BIFF2)
procedure TsSpreadBIFFReader.ReadDefRowHeight(AStream: TStream);
var
options, hw: Word;
h: Single;
begin
// Options
AStream.ReadWord;
// Height, in Twips (1/20 pt).
hw := WordLEToN(AStream.ReadWord);
h := TwipsToPts(hw) / FWorkbook.GetDefaultFontSize;
if h > ROW_HEIGHT_CORRECTION then
FWorksheet.DefaultRowHeight := h - ROW_HEIGHT_CORRECTION;
end;
// Read the FORMAT record for formatting numerical data
procedure TsSpreadBIFFReader.ReadFormat(AStream: TStream);
begin

View File

@ -80,6 +80,7 @@ type
procedure ReadPalette(ANode: TDOMNode);
procedure ReadRowHeight(ANode: TDOMNode; AWorksheet: TsWorksheet);
procedure ReadSharedStrings(ANode: TDOMNode);
procedure ReadSheetFormatPr(ANode: TDOMNode; AWorksheet: TsWorksheet);
procedure ReadSheetList(ANode: TDOMNode; AList: TStrings);
procedure ReadSheetViews(ANode: TDOMNode; AWorksheet: TsWorksheet);
procedure ReadThemeElements(ANode: TDOMNode);
@ -866,6 +867,8 @@ begin
end;
procedure TsSpreadOOXMLReader.ReadCols(ANode: TDOMNode; AWorksheet: TsWorksheet);
const
EPS = 1e-2;
var
colNode: TDOMNode;
col, col1, col2: Cardinal;
@ -884,12 +887,11 @@ begin
s := GetAttrValue(colNode, 'max');
if s <> '' then col2 := StrToInt(s)-1 else col2 := col1;
s := GetAttrValue(colNode, 'width');
if s <> '' then begin
w := StrToFloat(s, FPointSeparatorSettings);
if (s <> '') and TryStrToFloat(s, w, FPointSeparatorSettings) then
if not SameValue(w, AWorksheet.DefaultColWidth, EPS) then
for col := col1 to col2 do
AWorksheet.WriteColWidth(col, w);
end;
end;
colNode := colNode.NextSibling;
end;
end;
@ -1189,6 +1191,29 @@ begin
end;
end;
procedure TsSpreadOOXMLReader.ReadSheetFormatPr(ANode: TDOMNode;
AWorksheet: TsWorksheet);
var
w, h: Single;
s: String;
begin
if ANode = nil then
exit;
s := GetAttrValue(ANode, 'defaultColWidth'); // is in characters
if (s <> '') and TryStrToFloat(s, w, FPointSeparatorSettings) then
AWorksheet.DefaultColWidth := w;
s := GetAttrValue(ANode, 'defaultRowHeight'); // in in points
if (s <> '') and TryStrToFloat(s, h, FPointSeparatorSettings) then begin
h := h / Workbook.GetDefaultFontSize;
if h > ROW_HEIGHT_CORRECTION then begin
h := h - ROW_HEIGHT_CORRECTION;
AWorksheet.DefaultRowHeight := h;
end;
end;
end;
procedure TsSpreadOOXMLReader.ReadSheetList(ANode: TDOMNode; AList: TStrings);
var
node: TDOMNode;
@ -1340,8 +1365,8 @@ begin
end;
rownode := rownode.NextSibling;
end;
FixRows(AWorksheet);
FixCols(AWorksheet);
FixRows(AWorksheet);
end;
procedure TsSpreadOOXMLReader.ReadFromFile(AFileName: string; AData: TsWorkbook);
@ -1440,6 +1465,7 @@ begin
FWorksheet := AData.AddWorksheet(SheetList[i]);
ReadSheetViews(Doc.DocumentElement.FindNode('sheetViews'), FWorksheet);
ReadSheetFormatPr(Doc.DocumentElement.FindNode('sheetFormatPr'), FWorksheet);
ReadCols(Doc.DocumentElement.FindNode('cols'), FWorksheet);
ReadWorksheet(Doc.DocumentElement.FindNode('sheetData'), FWorksheet);