From c82e915262d060f9c407a579baf1465fb73db5e3 Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Wed, 2 Jul 2014 11:51:59 +0000 Subject: [PATCH] fpspreadsheet: Fix remaining utf8 issues when reading/writing rpn string formulas. Fix BIFFExplorer issues when displaying biff2 formulas. Excel complains about data loss when reading a biff2 file containing a calculated formula having a string result - not fixed yet. git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3266 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- .../examples/excel2demo/excel2write.lpi | 4 - components/fpspreadsheet/fpspreadsheet.pas | 5 +- components/fpspreadsheet/fpsutils.pas | 125 ++++------ .../reference/BIFFExplorer/BIFFExplorer.lpi | 3 +- .../reference/BIFFExplorer/bebiffgrid.pas | 128 ++++++++-- .../reference/BIFFExplorer/bemain.lfm | 2 +- .../reference/BIFFExplorer/bemain.pas | 2 +- .../fpspreadsheet/tests/formulatests.pas | 2 +- .../fpspreadsheet/tests/spreadtestgui.lpi | 2 - .../tests/testcases_calcrpnformula.inc | 14 +- components/fpspreadsheet/xlsbiff2.pas | 112 +-------- components/fpspreadsheet/xlsbiff5.pas | 4 +- components/fpspreadsheet/xlsbiff8.pas | 225 +----------------- components/fpspreadsheet/xlscommon.pas | 82 +++---- 14 files changed, 221 insertions(+), 489 deletions(-) diff --git a/components/fpspreadsheet/examples/excel2demo/excel2write.lpi b/components/fpspreadsheet/examples/excel2demo/excel2write.lpi index 920e71bdc..b796af7d9 100644 --- a/components/fpspreadsheet/examples/excel2demo/excel2write.lpi +++ b/components/fpspreadsheet/examples/excel2demo/excel2write.lpi @@ -39,7 +39,6 @@ - @@ -55,8 +54,5 @@ - - - diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas index ddcf092c1..90a8fc3c2 100755 --- a/components/fpspreadsheet/fpspreadsheet.pas +++ b/components/fpspreadsheet/fpspreadsheet.pas @@ -5154,6 +5154,10 @@ end; procedure TsCustomSpreadWriter.WriteCellCallback(ACell: PCell; AStream: TStream); begin if Length(ACell^.RPNFormulaValue) > 0 then + // A non-calculated RPN formula has ContentType cctUTF8Formula, but after + // calculation it has the content type of the result. Both cases have in + // common that there is a non-vanishing array of rpn tokens which has to + // be written to file. WriteRPNFormula(AStream, ACell^.Row, ACell^.Col, ACell^.RPNFormulaValue, ACell) else case ACell.ContentType of @@ -5162,7 +5166,6 @@ begin cctNumber : WriteNumber(AStream, ACell^.Row, ACell^.Col, ACell^.NumberValue, ACell); cctUTF8String : WriteLabel(AStream, ACell^.Row, ACell^.Col, ACell^.UTF8StringValue, ACell); cctFormula : WriteFormula(AStream, ACell^.Row, ACell^.Col, ACell^.FormulaValue, ACell); -// cctRPNFormula: WriteRPNFormula(AStream, ACell^.Row, ACell^.Col, ACell^.RPNFormulaValue, ACell); end; end; diff --git a/components/fpspreadsheet/fpsutils.pas b/components/fpspreadsheet/fpsutils.pas index 7e4743692..02c57bba8 100644 --- a/components/fpspreadsheet/fpsutils.pas +++ b/components/fpspreadsheet/fpsutils.pas @@ -77,9 +77,6 @@ function GetCellRangeString(ARow1, ACol1, ARow2, ACol2: Cardinal; AFlags: TsRelF function GetErrorValueStr(AErrorValue: TsErrorValue): String; -function UTF8ProperCase(AText: String): String; -function UTF8TextToXMLText(AText: ansistring): ansistring; - function IfThen(ACondition: Boolean; AValue1,AValue2: TsNumberFormat): TsNumberFormat; overload; function IsCurrencyFormat(AFormat: TsNumberFormat): Boolean; @@ -129,6 +126,7 @@ function HTMLLengthStrToPts(AValue: String): Double; function HTMLColorStrToColor(AValue: String): TsColorValue; function ColorToHTMLColorStr(AValue: TsColorValue): String; +function UTF8TextToXMLText(AText: ansistring): ansistring; procedure Unused(const A1); procedure Unused(const A1, A2); @@ -663,85 +661,6 @@ begin end; end; -{@@ - Converts a string to "proper case", i.e. 1st character of each word is - upper-case, other characters are lowercase. - @param S String to be converted - @return String in proper case } -function UTF8ProperCase(AText: String): String; -begin - result := ''; -end; - (* -const - Delims: TSysCharSet = ['0'..'9', ' ', '.', ',', ';', ':', '-', '_', - '<', '>', '|', '''', '#', '*', '+', '~', '!', '"', '§', '$', '%', '&', '/', '(', ')', '=', - '?', '\', '}', ']', '[', '{', '@']; -var - ch: String; - len, i, j: Integer; - res: string; - w: String; - p: Integer; - words: Array of String; -begin - AText := UTF8Lowercase(AText); - - i := 0; - w := 'dummy'; - Result := ''; - while w <> '' do begin - w := ExtractWordPos(i, AText, Delims, p); - Result := UTF8Copy(AText, 1, p-1) + UTF8Uppercase(UTF8Copy(w, 1, 1)) + - UTF8Copy(w, 2, Length(w)-1); - inc(i); - end; -end; - *) - -{@@ - Converts a string encoded in UTF8 to a string usable in XML. For this purpose, - some characters must be translated. - - @param AText input string encoded as UTF8 - @return String usable in XML with some characters replaced by the HTML codes. -} -function UTF8TextToXMLText(AText: ansistring): ansistring; -var - Idx:Integer; - WrkStr, AppoSt:ansistring; -begin - WrkStr:=''; - - for Idx:=1 to Length(AText) do - begin - case AText[Idx] of - '&': begin - AppoSt:=Copy(AText, Idx, 6); - - if (Pos('&', AppoSt) = 1) or - (Pos('<', AppoSt) = 1) or - (Pos('>', AppoSt) = 1) or - (Pos('"', AppoSt) = 1) or - (Pos(''', AppoSt) = 1) then begin - //'&' is the first char of a special chat, it must not be converted - WrkStr:=WrkStr + AText[Idx]; - end else begin - WrkStr:=WrkStr + '&'; - end; - end; - '<': WrkStr:=WrkStr + '<'; - '>': WrkStr:=WrkStr + '>'; - '"': WrkStr:=WrkStr + '"'; - '''':WrkStr:=WrkStr + '''; - else - WrkStr:=WrkStr + AText[Idx]; - end; - end; - - Result:=WrkStr; -end; - {@@ Helper function to reduce typing: "if a conditions is true return the first number format, otherwise return the second format" @@ -1539,6 +1458,48 @@ begin Result := Format('#%.2x%.2x%.2x', [rgb.r, rgb.g, rgb.b]); end; +{@@ + Converts a string encoded in UTF8 to a string usable in XML. For this purpose, + some characters must be translated. + + @param AText input string encoded as UTF8 + @return String usable in XML with some characters replaced by the HTML codes. +} +function UTF8TextToXMLText(AText: ansistring): ansistring; +var + Idx:Integer; + WrkStr, AppoSt:ansistring; +begin + WrkStr:=''; + + for Idx:=1 to Length(AText) do + begin + case AText[Idx] of + '&': begin + AppoSt:=Copy(AText, Idx, 6); + + if (Pos('&', AppoSt) = 1) or + (Pos('<', AppoSt) = 1) or + (Pos('>', AppoSt) = 1) or + (Pos('"', AppoSt) = 1) or + (Pos(''', AppoSt) = 1) then begin + //'&' is the first char of a special chat, it must not be converted + WrkStr:=WrkStr + AText[Idx]; + end else begin + WrkStr:=WrkStr + '&'; + end; + end; + '<': WrkStr:=WrkStr + '<'; + '>': WrkStr:=WrkStr + '>'; + '"': WrkStr:=WrkStr + '"'; + '''':WrkStr:=WrkStr + '''; + else + WrkStr:=WrkStr + AText[Idx]; + end; + end; + + Result:=WrkStr; +end; {******************************************************************************} diff --git a/components/fpspreadsheet/reference/BIFFExplorer/BIFFExplorer.lpi b/components/fpspreadsheet/reference/BIFFExplorer/BIFFExplorer.lpi index a074c35fb..84e573648 100644 --- a/components/fpspreadsheet/reference/BIFFExplorer/BIFFExplorer.lpi +++ b/components/fpspreadsheet/reference/BIFFExplorer/BIFFExplorer.lpi @@ -135,6 +135,7 @@ + @@ -144,6 +145,7 @@ + @@ -160,7 +162,6 @@ - diff --git a/components/fpspreadsheet/reference/BIFFExplorer/bebiffgrid.pas b/components/fpspreadsheet/reference/BIFFExplorer/bebiffgrid.pas index 167dee163..866daa12a 100644 --- a/components/fpspreadsheet/reference/BIFFExplorer/bebiffgrid.pas +++ b/components/fpspreadsheet/reference/BIFFExplorer/bebiffgrid.pas @@ -208,7 +208,7 @@ begin SetLength(sa, ls); ANumBytes := ls*SizeOf(AnsiChar) + ALenBytes; Move(FBuffer[ABufIndex + ALenBytes], sa[1], ls*SizeOf(AnsiChar)); - AString := sa; + AString := AnsiToUTF8(sa); end; end; @@ -1420,9 +1420,76 @@ begin ShowInRow(FCurrROw, FBufferIndex, numBytes, Format('%d ($%.4x)', [w, w]), 'Index of XF record'); end; + // Offset 6: Result of formula numBytes := 8; Move(FBuffer[FBufferIndex], q, numBytes); + if wordarr[3] <> $FFFF then begin + if FCurrRow = Row then begin + FDetails.Add('Formula result:'#13); + FDetails.Add(Format('Bytes 0-7: $%.15x --> IEEE 764 floating-point value, 64-bit double precision'#13+ + ' = %g', [q, dbl])); + end; + ShowInRow(FCurrRow, FBufferIndex, numBytes, FloatToStr(dbl), + 'Result of formula (IEEE 764 floating-point value, 64-bit double precision)'); + end else begin + case bytearr[0] of + 0: begin // String result + if FCurrRow = Row then begin + FDetails.Add('Formula result:'#13); + FDetails.Add('Byte 0 = 0 --> Result is string, follows in STRING record'); + FDetails.Add('Byte 1-5: Not used'); + FDetails.Add('Byte 6&7: $FFFF --> no floating point number'); + end; + ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('$%.16x', [q]), + 'Result is a string, follows in STRING record'); + end; + 1: begin // BOOL result + if FCurrRow = Row then begin + FDetails.Add('Formula result:'#13); + FDetails.Add('Byte 0 = 1 --> Result is BOOLEAN'); + FDetails.Add('Byte 1: Not used'); + if bytearr[2] = 0 + then FDetails.Add('Byte 2 = 0 --> FALSE') + else FDetails.Add('Byte 2 = 1 --> TRUE'); + FDetails.Add('Bytes 3-5: Not used'); + FDetails.Add('Bytes 6&7: $FFFF --> no floating point number'); + end; + ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('$%.16x', [q]), + 'Result is BOOLEAN'); + end; + 2: begin // ERROR result + if FCurrRow = Row then begin + FDetails.Add('Formula result:'#13); + FDetails.Add('Byte 0 = 2 --> Result is an ERROR value'); + FDetails.Add('Byte 1: Not used'); + case bytearr[2] of + $00: FDetails.Add('Byte 2 = $00 --> #NULL! Intersection of two cell ranges is empty'); + $07: FDetails.Add('Byte 2 = $07 --> #DIV/0! Division by zero'); + $0F: FDetails.Add('Byte 2 = $0F --> #VALUE! Wrong type of operand'); + $17: FDetails.Add('Byte 2 = $17 --> #REF! Illegal or deleted cell reference'); + $1D: FDetails.Add('Byte 2 = $1D --> #NAME? Wrong function or range name'); + $24: FDetails.Add('Byte 2 = $24 --> #NUM! Value range overflow'); + $2A: FDetails.Add('Byte 2 = $2A --> #N/A Argument or function not available'); + end; + FDetails.Add('Bytes 6&7: $FFFF --> no floating point number'); + end; + ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('$%.16x', [q]), + 'Result is an ERROR value'); + end; + 3: begin // EMPTY cell + if FCurrRow = Row then begin + FDetails.Add('Formula result:'#13); + FDetails.Add('Byte 0 = 3 --> Result is an empty cell, for example an empty string'); + FDetails.Add('Byte 1-5: Not used'); + FDetails.Add('Bytes 6&7: $FFFF --> no floating point number'); + end; + ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('$%.16x', [q]), + 'Result is an EMPTY cell (empty string)'); + end; + end; + end; + (* if (FFormat > sfExcel2) then begin if wordarr[3] <> $FFFF then begin if FCurrRow = Row then begin @@ -1494,31 +1561,44 @@ begin ShowInRow(FCurrRow, FBufferIndex, numBytes, FloatToStr(dbl), 'Result of formula (IEEE 764 floating-point value, 64-bit double precision)'); end; - + *) // Option flags - numBytes := 2; - Move(FBuffer[FBufferIndex], w, numBytes); - w := WordLEToN(w); - if Row = FCurrRow then begin - FDetails.Add('Option flags:'#13); - if w and $0001 = 0 - then FDetails.Add('Bit $0001 = 0: Don''t recalculate') - else FDetails.Add('Bit $0001 = 1: Recalculate always'); - FDetails.Add('Bit $0002: Reserved - MUST be zero, MUST be ignored'); - if w and $0004 = 0 - then FDetails.Add('Bit $0004 = 0: Cell does NOT have a fill alignment or a center-across-selection alignment.') - else FDetails.Add('Bit $0004 = 1: Cell has either a fill alignment or a center-across-selection alignment.'); - if w and $0008 = 0 - then FDetails.Add('Bit $0008 = 0: Formula is NOT part of a shared formula') - else FDetails.Add('Bit $0008 = 1: Formula is part of a shared formula'); - FDetails.Add('Bit $0010: Reserved - MUST be zero, MUST be ignored'); - if w and $0020 = 0 - then FDetails.Add('Bit $0020 = 0: Formula is NOT excluded from formula error checking') - else FDetails.Add('Bit $0020 = 1: Formula is excluded from formula error checking'); - FDetails.Add('Bits $FC00: Reserved - MUST be zero, MUST be ignored'); + if FFormat = sfExcel2 then begin + numBytes := 1; + b := FBuffer[FBufferIndex]; + if Row = FCurrRow then begin + FDetails.Add('Option flags:'#13); + case b of + 0: FDetails.Add('0 = Do not recalculate'); + 1: FDetails.Add('1 = Recalculate always'); + end; + end; + ShowInRow(FCurrRow, FBufferIndex, numbytes, IntToStr(b), 'Option flags'); + end else begin + numBytes := 2; + Move(FBuffer[FBufferIndex], w, numBytes); + w := WordLEToN(w); + if Row = FCurrRow then begin + FDetails.Add('Option flags:'#13); + if w and $0001 = 0 + then FDetails.Add('Bit $0001 = 0: Do not recalculate') + else FDetails.Add('Bit $0001 = 1: Recalculate always'); + FDetails.Add('Bit $0002: Reserved - MUST be zero, MUST be ignored'); + if w and $0004 = 0 + then FDetails.Add('Bit $0004 = 0: Cell does NOT have a fill alignment or a center-across-selection alignment.') + else FDetails.Add('Bit $0004 = 1: Cell has either a fill alignment or a center-across-selection alignment.'); + if w and $0008 = 0 + then FDetails.Add('Bit $0008 = 0: Formula is NOT part of a shared formula') + else FDetails.Add('Bit $0008 = 1: Formula is part of a shared formula'); + FDetails.Add('Bit $0010: Reserved - MUST be zero, MUST be ignored'); + if w and $0020 = 0 + then FDetails.Add('Bit $0020 = 0: Formula is NOT excluded from formula error checking') + else FDetails.Add('Bit $0020 = 1: Formula is excluded from formula error checking'); + FDetails.Add('Bits $FC00: Reserved - MUST be zero, MUST be ignored'); + end; + ShowInRow(FCurrRow, FBufferIndex, numbytes, Format('$%.4x', [w]), + 'Option flags'); end; - ShowInRow(FCurrRow, FBufferIndex, numbytes, Format('$%.4x', [w]), - 'Option flags'); // Not used if (FFormat >= sfExcel5) then begin diff --git a/components/fpspreadsheet/reference/BIFFExplorer/bemain.lfm b/components/fpspreadsheet/reference/BIFFExplorer/bemain.lfm index 6f2d92657..0311063a6 100644 --- a/components/fpspreadsheet/reference/BIFFExplorer/bemain.lfm +++ b/components/fpspreadsheet/reference/BIFFExplorer/bemain.lfm @@ -12,7 +12,7 @@ object MainForm: TMainForm OnDestroy = FormDestroy OnShow = FormShow ShowHint = True - LCLVersion = '1.2.4.0' + LCLVersion = '1.3' object Splitter1: TSplitter Left = 419 Height = 497 diff --git a/components/fpspreadsheet/reference/BIFFExplorer/bemain.pas b/components/fpspreadsheet/reference/BIFFExplorer/bemain.pas index 056844eb3..fd9d5e161 100644 --- a/components/fpspreadsheet/reference/BIFFExplorer/bemain.pas +++ b/components/fpspreadsheet/reference/BIFFExplorer/bemain.pas @@ -178,7 +178,7 @@ implementation {$R *.lfm} uses - IniFiles, StrUtils, Math, lazutf8, + IniFiles, lazutf8, fpsUtils, beUtils, beBIFFUtils, beAbout; diff --git a/components/fpspreadsheet/tests/formulatests.pas b/components/fpspreadsheet/tests/formulatests.pas index 3c6960639..074cbb849 100644 --- a/components/fpspreadsheet/tests/formulatests.pas +++ b/components/fpspreadsheet/tests/formulatests.pas @@ -161,7 +161,7 @@ begin MyWorkbook := TsWorkbook.Create; MyWorkSheet:= MyWorkBook.AddWorksheet(SHEET); MyWorkSheet.Options := MyWorkSheet.Options + [soCalcBeforeSaving]; - // Calculation of rpn formulas must be activated expicitely! + // Calculation of rpn formulas must be activated explicitly! { Write out test formulas. This include file creates various rpn formulas and stores the expected diff --git a/components/fpspreadsheet/tests/spreadtestgui.lpi b/components/fpspreadsheet/tests/spreadtestgui.lpi index 86230b550..653f2061d 100644 --- a/components/fpspreadsheet/tests/spreadtestgui.lpi +++ b/components/fpspreadsheet/tests/spreadtestgui.lpi @@ -80,7 +80,6 @@ - @@ -89,7 +88,6 @@ - diff --git a/components/fpspreadsheet/tests/testcases_calcrpnformula.inc b/components/fpspreadsheet/tests/testcases_calcrpnformula.inc index eff6a6c9f..08347d9de 100644 --- a/components/fpspreadsheet/tests/testcases_calcrpnformula.inc +++ b/components/fpspreadsheet/tests/testcases_calcrpnformula.inc @@ -116,6 +116,14 @@ SetLength(sollValues, Row+1); sollValues[Row] := CreateNumber(-1); + // String result + inc(Row); + MyWorksheet.WriteUTF8Text(Row, 0, '="Hallo"'); + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('Hallo', nil))); + SetLength(sollValues, Row+1); + sollValues[Row] := CreateString('Hallo'); + // String concatenation inc(Row); MyWorksheet.WriteUTF8Text(Row, 0, '="Hallo"&" world"'); @@ -324,12 +332,9 @@ SetLength(sollValues, Row+1); sollValues[Row] := CreateBool(1<>1); - - {------------------------------------------------------------------------------} { Math } {------------------------------------------------------------------------------} - // ABS inc(Row); MyWorksheet.WriteUTF8Text(Row, 0, '=abs(-1)'); @@ -1506,10 +1511,11 @@ RPNBool(true, RPNNumber(1.0, RPNString('A', - RPNFunc(fekIF,2, nil)))))); // <-- we have 3 parameters, not 2 + RPNFunc(fekIF,2, nil)))))); // <-- but we pushed 3 parameters, not 2 SetLength(sollValues, Row+1); sollValues[Row] := CreateError(errWrongType); { The first idea was that this should report an ArgError, but in fact it is a WrongType error because popping two values from the stack finds a number, but a bool is expected } {$ENDIF} + diff --git a/components/fpspreadsheet/xlsbiff2.pas b/components/fpspreadsheet/xlsbiff2.pas index 8d269701c..167caae60 100755 --- a/components/fpspreadsheet/xlsbiff2.pas +++ b/components/fpspreadsheet/xlsbiff2.pas @@ -682,13 +682,15 @@ var len: Byte; s: ansistring; begin - // The string is a byte-string with 16 bit length + // The string is a byte-string with 8 bit length len := AStream.ReadByte; if len > 0 then begin SetLength(s, Len); AStream.ReadBuffer(s[1], len); if (FIncompleteCell <> nil) and (s <> '') then begin - FIncompleteCell^.UTF8StringValue := s; + // The "IncompleteCell" has been identified in the sheet when reading + // the FORMULA record which precedes the String record. + FIncompleteCell^.UTF8StringValue := AnsiToUTF8(s); FIncompleteCell^.ContentType := cctUTF8String; end; end; @@ -1329,108 +1331,7 @@ begin { Formula data (RPN token array) } WriteRPNTokenArray(AStream, AFormula, RPNLength); -(* -{ Formula } - - { The size of the token array is written later, - because it's necessary to calculate if first, - and this is done at the same time it is written } - TokenArraySizePos := AStream.Position; - AStream.WriteByte(RPNLength); - - { Formula data (RPN token array) } - for i := 0 to Length(AFormula) - 1 do - begin - - { Token identifier } - FormulaKind := FormulaElementKindToExcelTokenID(AFormula[i].ElementKind, ExtraInfo); - AStream.WriteByte(FormulaKind); - Inc(RPNLength); - - { Additional data } - case FormulaKind of - - { binary operation tokens } - - INT_EXCEL_TOKEN_TADD, INT_EXCEL_TOKEN_TSUB, INT_EXCEL_TOKEN_TMUL, - INT_EXCEL_TOKEN_TDIV, INT_EXCEL_TOKEN_TPOWER: begin end; - - INT_EXCEL_TOKEN_TNUM: - begin - AStream.WriteBuffer(AFormula[i].DoubleValue, 8); - Inc(RPNLength, 8); - end; - - INT_EXCEL_TOKEN_TSTR: - begin - s := ansistring(AFormula[i].StringValue); - len := Length(s); - AStream.WriteByte(len); - AStream.WriteBuffer(s[1], len); - Inc(RPNLength, len + 1); - end; - - INT_EXCEL_TOKEN_TBOOL: - begin - AStream.WriteByte(ord(AFormula[i].DoubleValue <> 0.0)); - inc(RPNLength, 1); - end; - - INT_EXCEL_TOKEN_TREFR, INT_EXCEL_TOKEN_TREFV, INT_EXCEL_TOKEN_TREFA: - begin - r := AFormula[i].Row and MASK_EXCEL_ROW; - if (rfRelRow in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_ROW; - if (rfRelCol in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_COL; - AStream.WriteWord(r); - AStream.WriteByte(AFormula[i].Col); - Inc(RPNLength, 3); - end; - - INT_EXCEL_TOKEN_TAREA_R: { fekCellRange } - begin - r := AFormula[i].Row and MASK_EXCEL_ROW; - if (rfRelRow in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_ROW; - if (rfRelCol in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_COL; - AStream.WriteWord(WordToLE(r)); - - r := AFormula[i].Row2 and MASK_EXCEL_ROW; - if (rfRelRow2 in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_ROW; - if (rfRelCol2 in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_COL; - AStream.WriteWord(WordToLE(r)); - - AStream.WriteByte(AFormula[i].Col); - AStream.WriteByte(AFormula[i].Col2); - Inc(RPNLength, 6); - end; - - INT_EXCEL_TOKEN_FUNC_R, INT_EXCEL_TOKEN_FUNC_V, INT_EXCEL_TOKEN_FUNC_A: - begin - AStream.WriteByte(Lo(ExtraInfo)); - Inc(RPNLength, 1); - end; - - INT_EXCEL_TOKEN_FUNCVAR_V: - begin - AStream.WriteByte(AFormula[i].ParamsNum); - AStream.WriteByte(Lo(ExtraInfo)); - // taking only the low-bytes, the high-bytes are needed for compatibility - // with other BIFF formats... - Inc(RPNLength, 2); - end; - - end; - end; - - { Write sizes in the end, after we known them } - FinalPos := AStream.Position; - AStream.position := TokenArraySizePos; - AStream.WriteByte(RPNLength); - AStream.Position := RecordSizePos; - AStream.WriteWord(WordToLE(17 + RPNLength)); - AStream.position := FinalPos; - *) - - { Write sizes in the end, after we known them } + { Finally write sizes after we know them } FinalPos := AStream.Position; AStream.Position := RecordSizePos; AStream.WriteWord(WordToLE(17 + RPNLength)); @@ -1466,7 +1367,8 @@ var s: ansistring; len: Integer; begin - s := AString; +// s := AString; // Why not call UTF8ToAnsi? + s := UTF8ToAnsi(AString); len := Length(s); { BIFF Record header } diff --git a/components/fpspreadsheet/xlsbiff5.pas b/components/fpspreadsheet/xlsbiff5.pas index 71c05f5ab..17adfc5bd 100755 --- a/components/fpspreadsheet/xlsbiff5.pas +++ b/components/fpspreadsheet/xlsbiff5.pas @@ -956,7 +956,7 @@ var s: ansistring; len: Integer; begin - s := AString; + s := UTF8ToAnsi(AString); len := Length(s); { BIFF Record header } @@ -1399,7 +1399,7 @@ begin SetLength(s, Len); AStream.ReadBuffer(s[1], len); if (FIncompleteCell <> nil) and (s <> '') then begin - FIncompleteCell^.UTF8StringValue := s; + FIncompleteCell^.UTF8StringValue := AnsiToUTF8(s); FIncompleteCell^.ContentType := cctUTF8String; end; end; diff --git a/components/fpspreadsheet/xlsbiff8.pas b/components/fpspreadsheet/xlsbiff8.pas index 8ba1499d3..483589bbd 100755 --- a/components/fpspreadsheet/xlsbiff8.pas +++ b/components/fpspreadsheet/xlsbiff8.pas @@ -127,10 +127,6 @@ type AFlags: TsRelFlags): word; override; function WriteRPNCellRangeAddress(AStream: TStream; ARow1, ACol1, ARow2, ACol2: Cardinal; AFlags: TsRelFlags): Word; override; -{ - procedure WriteRPNFormula(AStream: TStream; const ARow, ACol: Cardinal; - const AFormula: TsRPNFormula; ACell: PCell); override; -} function WriteString_8bitLen(AStream: TStream; AString: String): Integer; override; procedure WriteStringRecord(AStream: TStream; AString: string); override; procedure WriteStyle(AStream: TStream); @@ -798,8 +794,7 @@ begin end; { Writes the address of a cell as used in an RPN formula and returns the - number of bytes written. - Valid for BIFF8. } + number of bytes written. } function TsSpreadBIFF8Writer.WriteRPNCellAddress(AStream: TStream; ARow, ACol: Cardinal; AFlags: TsRelFlags): Word; var @@ -814,17 +809,9 @@ begin end; { Writes the address of a cell range as used in an RPN formula and returns the - count of bytes written. - Valid for BIFF2-BIFF5. } + count of bytes written. } function TsSpreadBIFF8Writer.WriteRPNCellRangeAddress(AStream: TStream; ARow1, ACol1, ARow2, ACol2: Cardinal; AFlags: TsRelFlags): Word; -{ Cell range address, BIFF8: - Offset Size Contents - 0 2 Index to first row (0…65535) or offset of first row (method [B], -32768…32767) - 2 2 Index to last row (0…65535) or offset of last row (method [B], -32768…32767) - 4 2 Index to first column or offset of first column, with relative flags (see table above) - 6 2 Index to last column or offset of last column, with relative flags (see table above) -} var c: Cardinal; // column index with encoded relative/absolute address info begin @@ -844,208 +831,6 @@ begin Result := 8; end; - (* -procedure TsSpreadBIFF8Writer.WriteRPNFormula(AStream: TStream; const ARow, - ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell); -var - FormulaResult: double; - FormulaResultWords: array[0..3] of word absolute FormulaResult; - i: Integer; - len: Integer; - RPNLength: Word; - TokenArraySizePos, RecordSizePos, FinalPos: Int64; - TokenID: Word; - lSecondaryID: Word; - c: Cardinal; - wideStr: WideString; -begin - RPNLength := 0; - FormulaResult := 0.0; - case ACell^.ContentType of - cctNumber: - FormulaResult := ACell^.NumberValue; - cctDateTime: - FormulaResult := ACell^.DateTimeValue; - cctUTF8String: - begin - if ACell^.UTF8StringValue = '' then - FormulaResultWords[0] := 3; - FormulaResultWords[3] := $FFFF; - end; - cctBool: - begin - FormulaResultWords[0] := 1; - FormulaResultWords[1] := ord(ACell^.BoolValue); - FormulaResultWords[3] := $FFFF; - end; - cctError: - begin - FormulaResultWords[0] := 2; - case ACell^.ErrorValue of - errEmptyIntersection: FormulaResultWords[1] := ERR_INTERSECTION_EMPTY;// #NULL! - errDivideByZero : FormulaResultWords[1] := ERR_DIVIDE_BY_ZERO; // #DIV/0! - errWrongType : FormulaResultWords[1] := ERR_WRONG_TYPE_OF_OPERAND; // #VALUE! - errIllegalRef : FormulaResultWords[1] := ERR_ILLEGAL_REFERENCE; // #REF! - errWrongName : FormulaResultWords[1] := ERR_WRONG_NAME; // #NAME? - errOverflow : FormulaResultWords[1] := ERR_OVERFLOW; // #NUM! - errArgError : FormulaResultWords[1] := ERR_ARG_ERROR; // #N/A; - end; - FormulaResultWords[3] := $FFFF; - end; - end; - - { BIFF Record header } - AStream.WriteWord(WordToLE(INT_EXCEL_ID_FORMULA)); - RecordSizePos := AStream.Position; - AStream.WriteWord(WordToLE(22 + RPNLength)); - - { BIFF Record data } - AStream.WriteWord(WordToLE(ARow)); - AStream.WriteWord(WordToLE(ACol)); - - { Index to XF record, according to formatting } - //AStream.WriteWord(0); - WriteXFIndex(AStream, ACell); - - { Result of the formula in IEEE 754 floating-point value } - AStream.WriteBuffer(FormulaResult, 8); - - { Options flags } - AStream.WriteWord(WordToLE(MASK_FORMULA_RECALCULATE_ALWAYS)); - - { Not used } - AStream.WriteDWord(0); - - { Formula } - - { The size of the token array is written later, because it's necessary to - calculate if first, and this is done at the same time it is written } - TokenArraySizePos := AStream.Position; - WriteRPNTokenArraySize(AStream, RPNLength); - - WriteRPNTokenArray(AStream, AFormula, RPNLength); - - { Formula data (RPN token array) } - for i := 0 to Length(AFormula) - 1 do - begin - { Token identifier } - TokenID := FormulaElementKindToExcelTokenID(AFormula[i].ElementKind, lSecondaryID); - AStream.WriteByte(TokenID); - Inc(RPNLength); - - { Additional data } - case TokenID of - { Operand Tokens } - INT_EXCEL_TOKEN_TREFR, INT_EXCEL_TOKEN_TREFV, INT_EXCEL_TOKEN_TREFA: { fekCell } - begin - AStream.WriteWord(AFormula[i].Row); - c := AFormula[i].Col and MASK_EXCEL_COL_BITS_BIFF8; - if (rfRelRow in AFormula[i].RelFlags) then c := c or MASK_EXCEL_RELATIVE_ROW; - if (rfRelCol in AFormula[i].RelFlags) then c := c or MASK_EXCEL_RELATIVE_COL; - AStream.WriteWord(c); - Inc(RPNLength, 4); - end; - - INT_EXCEL_TOKEN_TAREA_R: { fekCellRange } - begin - { - Cell range address, BIFF8: - Offset Size Contents - 0 2 Index to first row (0…65535) or offset of first row (method [B], -32768…32767) - 2 2 Index to last row (0…65535) or offset of last row (method [B], -32768…32767) - 4 2 Index to first column or offset of first column, with relative flags (see table above) - 6 2 Index to last column or offset of last column, with relative flags (see table above) - } - AStream.WriteWord(WordToLE(AFormula[i].Row)); - AStream.WriteWord(WordToLE(AFormula[i].Row2)); - c := AFormula[i].Col; - if (rfRelCol in AFormula[i].RelFlags) then c := c or MASK_EXCEL_RELATIVE_COL; - if (rfRelRow in AFormula[i].RelFlags) then c := c or MASK_EXCEL_RELATIVE_ROW; - AStream.WriteWord(WordToLE(c)); - c := AFormula[i].Col2; - if (rfRelCol2 in AFormula[i].RelFlags) then c := c or MASK_EXCEL_RELATIVE_COL; - if (rfRelRow2 in AFormula[i].RelFlags) then c := c or MASK_EXCEL_RELATIVE_ROW; - AStream.WriteWord(WordToLE(c)); - Inc(RPNLength, 8); - end; - - INT_EXCEL_TOKEN_TNUM: { fekNum } - begin - AStream.WriteBuffer(AFormula[i].DoubleValue, 8); - Inc(RPNLength, 8); - end; - - INT_EXCEL_TOKEN_TSTR: { fekString } - begin - // string constant is stored as widestring in BIFF8 - // Writing is done by the virtual method WriteString_8bitLen. - Inc(RPNLength, WriteString_8bitLen(AStream, AFormula[i].StringValue)); - { - wideStr := UTF8Decode(AFormula[i].StringValue); - len := Length(wideStr); - AStream.WriteByte(len); // char count in 1 byte - AStream.WriteByte(1); // Widestring flags, 1=regular unicode LE string - AStream.WriteBuffer(WideStringToLE(wideStr)[1], len * Sizeof(WideChar)); - Inc(RPNLength, 1 + 1 + len*SizeOf(WideChar)); - } - end; - - INT_EXCEL_TOKEN_TBOOL: { fekBool } - begin - AStream.WriteByte(ord(AFormula[i].DoubleValue <> 0.0)); - inc(RPNLength, 1); - end; - - { binary operation tokens } - INT_EXCEL_TOKEN_TADD, INT_EXCEL_TOKEN_TSUB, INT_EXCEL_TOKEN_TMUL, - INT_EXCEL_TOKEN_TDIV, INT_EXCEL_TOKEN_TPOWER: begin end; - - { Other operations } - INT_EXCEL_TOKEN_TATTR: { fekOpSUM } - { 3.10, page 71: e.g. =SUM(1) is represented by token array - tInt(1),tAttrRum - } - begin - // Unary SUM Operation - AStream.WriteByte($10); //tAttrSum token (SUM with one parameter) - AStream.WriteByte(0); // not used - AStream.WriteByte(0); // not used - Inc(RPNLength, 3); - end; - - // Functions with fixed parameter count - INT_EXCEL_TOKEN_FUNC_R, INT_EXCEL_TOKEN_FUNC_V, INT_EXCEL_TOKEN_FUNC_A: - begin - AStream.WriteWord(WordToLE(lSecondaryID)); - Inc(RPNLength, 2); - end; - - // Functions with variable parameter count - INT_EXCEL_TOKEN_FUNCVAR_V: - begin - AStream.WriteByte(AFormula[i].ParamsNum); - AStream.WriteWord(WordToLE(lSecondaryID)); - Inc(RPNLength, 3); - end; - - else - end; - end; - - { Write sizes in the end, after we known them } - FinalPos := AStream.Position; - AStream.position := TokenArraySizePos; - AStream.WriteByte(RPNLength); - AStream.Position := RecordSizePos; - AStream.WriteWord(WordToLE(22 + RPNLength)); - AStream.Position := FinalPos; - - { Write following STRING record if formula result is a non-empty string } - if (ACell^.ContentType = cctUTF8String) and (ACell^.UTF8StringValue <> '') then - WriteStringRecord(AStream, ACell^.UTF8StringValue); -end; -*) - { Helper function for writing a string with 8-bit length. Overridden version for BIFF8. Called for writing rpn formula string tokens. Returns the count of bytes written} @@ -1070,7 +855,7 @@ var wideStr: widestring; len: Integer; begin - wideStr := AString; + wideStr := UTF8Decode(AString); len := Length(wideStr); { BIFF Record header } @@ -1927,7 +1712,7 @@ var s: widestring; begin s := ReadWideString(AStream, true); - Result := s; + Result := UTF8Encode(s); end; procedure TsSpreadBIFF8Reader.ReadStringRecord(AStream: TStream); @@ -1936,7 +1721,7 @@ var begin s := ReadWideString(AStream, false); if (FIncompleteCell <> nil) and (s <> '') then begin - FIncompleteCell^.UTF8StringValue := s; + FIncompleteCell^.UTF8StringValue := UTF8Encode(s); FIncompleteCell^.ContentType := cctUTF8String; end; FIncompleteCell := nil; diff --git a/components/fpspreadsheet/xlscommon.pas b/components/fpspreadsheet/xlscommon.pas index 26bc2917a..a9a062797 100644 --- a/components/fpspreadsheet/xlscommon.pas +++ b/components/fpspreadsheet/xlscommon.pas @@ -1564,7 +1564,7 @@ begin len := AStream.ReadByte; SetLength(s, len); AStream.ReadBuffer(s[1], len); - Result := s; + Result := ansiToUTF8(s); end; { Reads a STRING record. It immediately precedes a FORMULA record which has a @@ -1573,7 +1573,6 @@ end; procedure TsSpreadBIFFReader.ReadStringRecord(AStream: TStream); begin Unused(AStream); - // end; { Reads the WINDOW2 record containing information like "show grid lines", @@ -1991,7 +1990,7 @@ end; function TsSpreadBIFFWriter.WriteRPNCellAddress(AStream: TStream; ARow, ACol: Cardinal; AFlags: TsRelFlags): Word; var - r: Cardinal; // row index containing the relativ/absolute address info + r: Cardinal; // row index containing encoded relativ/absolute address info begin r := ARow and MASK_EXCEL_ROW; if (rfRelRow in AFlags) then r := r or MASK_EXCEL_RELATIVE_ROW; @@ -2091,46 +2090,52 @@ end; { Writes the result of an RPN formula. } procedure TsSpreadBIFFWriter.WriteRPNResult(AStream: TStream; ACell: PCell); var + Data: array[0..3] of word; FormulaResult: double; - FormulaResultWords: array[0..3] of word absolute FormulaResult; begin { Determine encoded result bytes } - FormulaResult := 0.0; + FillChar(Data, SizeOf(Data), 0); case ACell^.ContentType of cctNumber: - FormulaResult := ACell^.NumberValue; + begin + FormulaResult := ACell^.NumberValue; + Move(FormulaResult, Data, 8); + end; cctDateTime: - FormulaResult := ACell^.DateTimeValue; + begin + FormulaResult := ACell^.DateTimeValue; + Move(FormulaResult, Data, 8); + end; cctUTF8String: begin if ACell^.UTF8StringValue = '' then - FormulaResultWords[0] := 3; - FormulaResultWords[3] := $FFFF; + Data[0] := 3; + Data[3] := $FFFF; end; cctBool: begin - FormulaResultWords[0] := 1; - FormulaResultWords[1] := ord(ACell^.BoolValue); - FormulaResultWords[3] := $FFFF; + Data[0] := 1; + Data[1] := ord(ACell^.BoolValue); + Data[3] := $FFFF; end; cctError: begin - FormulaResultWords[0] := 2; + Data[0] := 2; case ACell^.ErrorValue of - errEmptyIntersection: FormulaResultWords[1] := ERR_INTERSECTION_EMPTY;// #NULL! - errDivideByZero : FormulaResultWords[1] := ERR_DIVIDE_BY_ZERO; // #DIV/0! - errWrongType : FormulaResultWords[1] := ERR_WRONG_TYPE_OF_OPERAND; // #VALUE! - errIllegalRef : FormulaResultWords[1] := ERR_ILLEGAL_REFERENCE; // #REF! - errWrongName : FormulaResultWords[1] := ERR_WRONG_NAME; // #NAME? - errOverflow : FormulaResultWords[1] := ERR_OVERFLOW; // #NUM! - errArgError : FormulaResultWords[1] := ERR_ARG_ERROR; // #N/A; + errEmptyIntersection: Data[1] := ERR_INTERSECTION_EMPTY;// #NULL! + errDivideByZero : Data[1] := ERR_DIVIDE_BY_ZERO; // #DIV/0! + errWrongType : Data[1] := ERR_WRONG_TYPE_OF_OPERAND; // #VALUE! + errIllegalRef : Data[1] := ERR_ILLEGAL_REFERENCE; // #REF! + errWrongName : Data[1] := ERR_WRONG_NAME; // #NAME? + errOverflow : Data[1] := ERR_OVERFLOW; // #NUM! + errArgError : Data[1] := ERR_ARG_ERROR; // #N/A; end; - FormulaResultWords[3] := $FFFF; + Data[3] := $FFFF; end; end; { Write result of the formula, encoded above } - AStream.WriteBuffer(FormulaResult, 8); + AStream.WriteBuffer(Data, 8); end; { Writes the token array of the given RPN formula and returns its size } @@ -2203,23 +2208,6 @@ begin inc(RPNLength, 1); end; - { binary operation tokens } - INT_EXCEL_TOKEN_TADD, INT_EXCEL_TOKEN_TSUB, INT_EXCEL_TOKEN_TMUL, - INT_EXCEL_TOKEN_TDIV, INT_EXCEL_TOKEN_TPOWER: - begin - end; - - { Other operations } - INT_EXCEL_TOKEN_TATTR: { fekOpSUM } - { 3.10, page 71: e.g. =SUM(1) is represented by token array tInt(1),tAttrRum } - begin - // Unary SUM Operation - AStream.WriteByte($10); //tAttrSum token (SUM with one parameter) - AStream.WriteByte(0); // not used - AStream.WriteByte(0); // not used - inc(RPNLength, 3); - end; - // Functions with fixed parameter count INT_EXCEL_TOKEN_FUNC_R, INT_EXCEL_TOKEN_FUNC_V, INT_EXCEL_TOKEN_FUNC_A: begin @@ -2234,13 +2222,25 @@ begin n := WriteRPNFunc(AStream, secondaryID); inc(RPNLength, 1 + n); end; + + // Other operations + INT_EXCEL_TOKEN_TATTR: { fekOpSUM } + { 3.10, page 71: e.g. =SUM(1) is represented by token array tInt(1),tAttrRum } + begin + // Unary SUM Operation + AStream.WriteByte($10); //tAttrSum token (SUM with one parameter) + AStream.WriteByte(0); // not used + AStream.WriteByte(0); // not used + inc(RPNLength, 3); + end; + end; // case end; // for // Now update the size of the token array. finalPos := AStream.Position; AStream.Position := TokenArraySizePos; - AStream.WriteByte(RPNLength); + WriteRPNTokenArraySize(AStream, RPNLength); AStream.Position := finalPos; end; @@ -2428,7 +2428,7 @@ end; { Helper function for writing a string with 8-bit length. Here, we implement the version for ansistrings since it is valid for all BIFF versions except BIFF8 - where it has to overridden. Is called for writing a string rpn token. + where it has to be overridden. Is called for writing a string rpn token. Returns the count of bytes written. } function TsSpreadBIFFWriter.WriteString_8bitLen(AStream: TStream; AString: String): Integer;