diff --git a/components/fpspreadsheet/examples/other/test_write_formula.lpi b/components/fpspreadsheet/examples/other/test_write_formula.lpi new file mode 100644 index 000000000..a42f8266e --- /dev/null +++ b/components/fpspreadsheet/examples/other/test_write_formula.lpi @@ -0,0 +1,215 @@ + + + + + + + + + + + + <ResourceType Value="res"/> + <UseXPManifest Value="True"/> + <Icon Value="0"/> + <ActiveWindowIndexAtStart Value="0"/> + </General> + <i18n> + <EnableI18N LFM="False"/> + </i18n> + <VersionInfo> + <StringTable ProductVersion=""/> + </VersionInfo> + <BuildModes Count="1" Active="Default"> + <Item1 Name="Default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + <IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/> + <ExcludeFileFilter Value="*.(bak|ppu|o|so);*~;backup"/> + </PublishOptions> + <RunParams> + <local> + <FormatVersion Value="1"/> + <LaunchingApplication PathPlusParams="/usr/bin/xterm -T 'Lazarus Run Output' -e $(LazarusDir)/tools/runwait.sh $(TargetCmdLine)"/> + </local> + </RunParams> + <RequiredPackages Count="1"> + <Item1> + <PackageName Value="laz_fpspreadsheet"/> + </Item1> + </RequiredPackages> + <Units Count="4"> + <Unit0> + <Filename Value="test_write_formula.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="test_write_formula"/> + <IsVisibleTab Value="True"/> + <EditorIndex Value="0"/> + <WindowIndex Value="0"/> + <TopLine Value="23"/> + <CursorPos X="42" Y="45"/> + <UsageCount Value="20"/> + <Loaded Value="True"/> + </Unit0> + <Unit1> + <Filename Value="../../fpspreadsheet.pas"/> + <UnitName Value="fpspreadsheet"/> + <EditorIndex Value="3"/> + <WindowIndex Value="0"/> + <TopLine Value="38"/> + <CursorPos X="13" Y="66"/> + <UsageCount Value="10"/> + <Loaded Value="True"/> + </Unit1> + <Unit2> + <Filename Value="../../xlsbiff8.pas"/> + <UnitName Value="xlsbiff8"/> + <EditorIndex Value="1"/> + <WindowIndex Value="0"/> + <TopLine Value="840"/> + <CursorPos X="10" Y="862"/> + <UsageCount Value="10"/> + <Loaded Value="True"/> + </Unit2> + <Unit3> + <Filename Value="../../xlscommon.pas"/> + <UnitName Value="xlscommon"/> + <EditorIndex Value="2"/> + <WindowIndex Value="0"/> + <TopLine Value="125"/> + <CursorPos X="1" Y="150"/> + <UsageCount Value="10"/> + <Loaded Value="True"/> + </Unit3> + </Units> + <JumpHistory Count="25" HistoryIndex="24"> + <Position1> + <Filename Value="test_write_formula.pas"/> + <Caret Line="3" Column="1" TopLine="1"/> + </Position1> + <Position2> + <Filename Value="test_write_formula.pas"/> + <Caret Line="179" Column="1" TopLine="136"/> + </Position2> + <Position3> + <Filename Value="test_write_formula.pas"/> + <Caret Line="29" Column="17" TopLine="10"/> + </Position3> + <Position4> + <Filename Value="test_write_formula.pas"/> + <Caret Line="27" Column="24" TopLine="1"/> + </Position4> + <Position5> + <Filename Value="../../fpspreadsheet.pas"/> + <Caret Line="205" Column="64" TopLine="181"/> + </Position5> + <Position6> + <Filename Value="test_write_formula.pas"/> + <Caret Line="35" Column="22" TopLine="3"/> + </Position6> + <Position7> + <Filename Value="test_write_formula.pas"/> + <Caret Line="13" Column="42" TopLine="1"/> + </Position7> + <Position8> + <Filename Value="../../xlsbiff8.pas"/> + <Caret Line="524" Column="14" TopLine="493"/> + </Position8> + <Position9> + <Filename Value="../../fpspreadsheet.pas"/> + <Caret Line="1147" Column="27" TopLine="1123"/> + </Position9> + <Position10> + <Filename Value="../../xlsbiff8.pas"/> + <Caret Line="124" Column="25" TopLine="94"/> + </Position10> + <Position11> + <Filename Value="../../fpspreadsheet.pas"/> + <Caret Line="1072" Column="74" TopLine="1067"/> + </Position11> + <Position12> + <Filename Value="../../fpspreadsheet.pas"/> + <Caret Line="206" Column="70" TopLine="175"/> + </Position12> + <Position13> + <Filename Value="../../xlsbiff8.pas"/> + <Caret Line="850" Column="14" TopLine="831"/> + </Position13> + <Position14> + <Filename Value="../../xlsbiff8.pas"/> + <Caret Line="104" Column="35" TopLine="102"/> + </Position14> + <Position15> + <Filename Value="../../xlscommon.pas"/> + <Caret Line="81" Column="76" TopLine="49"/> + </Position15> + <Position16> + <Filename Value="../../xlscommon.pas"/> + <Caret Line="40" Column="1" TopLine="17"/> + </Position16> + <Position17> + <Filename Value="../../xlscommon.pas"/> + <Caret Line="145" Column="41" TopLine="129"/> + </Position17> + <Position18> + <Filename Value="../../xlscommon.pas"/> + <Caret Line="91" Column="40" TopLine="69"/> + </Position18> + <Position19> + <Filename Value="../../xlscommon.pas"/> + <Caret Line="149" Column="3" TopLine="128"/> + </Position19> + <Position20> + <Filename Value="../../xlsbiff8.pas"/> + <Caret Line="104" Column="35" TopLine="102"/> + </Position20> + <Position21> + <Filename Value="../../xlsbiff8.pas"/> + <Caret Line="857" Column="72" TopLine="835"/> + </Position21> + <Position22> + <Filename Value="test_write_formula.pas"/> + <Caret Line="52" Column="1" TopLine="18"/> + </Position22> + <Position23> + <Filename Value="test_write_formula.pas"/> + <Caret Line="42" Column="27" TopLine="20"/> + </Position23> + <Position24> + <Filename Value="test_write_formula.pas"/> + <Caret Line="45" Column="15" TopLine="23"/> + </Position24> + <Position25> + <Filename Value="test_write_formula.pas"/> + <Caret Line="46" Column="38" TopLine="23"/> + </Position25> + </JumpHistory> + </ProjectOptions> + <CompilerOptions> + <Version Value="10"/> + <Target> + <Filename Value="test_write_formula"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)"/> + <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <Other> + <CompilerPath Value="$(CompPath)"/> + </Other> + </CompilerOptions> + <Debugging> + <Exceptions Count="3"> + <Item1> + <Name Value="EAbort"/> + </Item1> + <Item2> + <Name Value="ECodetoolError"/> + </Item2> + <Item3> + <Name Value="EFOpenError"/> + </Item3> + </Exceptions> + </Debugging> +</CONFIG> diff --git a/components/fpspreadsheet/examples/other/test_write_formula.pas b/components/fpspreadsheet/examples/other/test_write_formula.pas new file mode 100644 index 000000000..817ed8b9a --- /dev/null +++ b/components/fpspreadsheet/examples/other/test_write_formula.pas @@ -0,0 +1,84 @@ +{ +test_write_formula.pas + +Demonstrates how to write an formula using the fpspreadsheet library + +AUTHORS: Felipe Monteiro de Carvalho +} +program test_write_formula; + +{$mode delphi}{$H+} + +uses + Classes, SysUtils, fpspreadsheet, xlsbiff8, fpsopendocument, + laz_fpspreadsheet, fpsconvencoding; + +var + MyWorkbook: TsWorkbook; + MyWorksheet: TsWorksheet; + MyDir: string; + MyCell: PCell; + +procedure WriteFirstWorksheet(); +var + MyFormula: TsFormula; + MyRPNFormula: TsRPNFormula; +begin + MyWorksheet := MyWorkbook.AddWorksheet('Worksheet1'); + + // Write some cells + MyWorksheet.WriteUTF8Text(1, 0, 'Text Formulas');// A2 + + MyWorksheet.WriteUTF8Text(1, 1, '=Sum(D2:d5) Text Formula'); // B2 + + MyFormula.FormulaStr := '=Sum(D2:d5)'; + MyFormula.DoubleValue := 0.0; + MyWorksheet.WriteFormula(1, 2, MyFormula); // C2 + + MyWorksheet.WriteUTF8Text(1, 1, '=Sum(D2:d5) RPN'); // B3 + + MyFormula.FormulaStr := '=Sum(D2:d5)'; + MyFormula.DoubleValue := 0.0; + MyWorksheet.WriteFormula(1, 2, MyFormula); // C3 + + SetLength(MyRPNFormula, 2); + MyRPNFormula[0].ElementKind := fekOpSUM; + MyRPNFormula[1].ElementKind := fekCellRange; + MyRPNFormula[1].Row := 1; + MyRPNFormula[1].Row := 4; + MyRPNFormula[1].Col := 3; + MyRPNFormula[1].Col := 3; + MyWorksheet.WriteRPNFormula(1, 2, MyRPNFormula); // C2 +end; + +procedure WriteSecondWorksheet(); +begin +{ MyWorksheet := MyWorkbook.AddWorksheet('Worksheet2'); + + // Write some cells + + // Line 1 + + MyWorksheet.WriteUTF8Text(1, 1, 'Relatório'); + MyCell := MyWorksheet.GetCell(1, 1); + MyCell^.Border := [cbNorth, cbWest, cbSouth]; + MyCell^.BackgroundColor := scGrey20pct; + MyCell^.UsedFormattingFields := [uffBorder, uffBackgroundColor, uffBold];} +end; + +begin + MyDir := ExtractFilePath(ParamStr(0)); + + // Create the spreadsheet + MyWorkbook := TsWorkbook.Create; + + WriteFirstWorksheet(); + + WriteSecondWorksheet(); + + // Save the spreadsheet to a file + MyWorkbook.WriteToFile(MyDir + 'test_formula.xls', sfExcel8, False); +// MyWorkbook.WriteToFile(MyDir + 'test_formula.odt', sfOpenDocument, False); + MyWorkbook.Free; +end. + diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas index 8cb80271c..703f92021 100755 --- a/components/fpspreadsheet/fpspreadsheet.pas +++ b/components/fpspreadsheet/fpspreadsheet.pas @@ -57,7 +57,7 @@ type TFEKind = ( { Basic operands } - fekCell, fekNum, + fekCell, fekCellRange, fekNum, { Basic operations } fekAdd, fekSub, fekDiv, fekMul, { Build-in Functions} @@ -1161,6 +1161,14 @@ begin IterateThroughCells(AStream, ACells, WriteCellCallback); end; +{@@ + A generic method to iterate through all cells in a worksheet and call a callback + routine for each cell. + + @param AStream The output stream, passed to the callback routine. + @param ACells List of cells to be iterated + @param ACallback The callback routine +} procedure TsCustomSpreadWriter.IterateThroughCells(AStream: TStream; ACells: TAVLTree; ACallback: TCellsCallback); var AVLNode: TAVLTreeNode; diff --git a/components/fpspreadsheet/xlsbiff8.pas b/components/fpspreadsheet/xlsbiff8.pas index 7631b0172..d3990446b 100755 --- a/components/fpspreadsheet/xlsbiff8.pas +++ b/components/fpspreadsheet/xlsbiff8.pas @@ -121,6 +121,7 @@ type procedure WriteEOF(AStream: TStream); procedure WriteFont(AStream: TStream; AFont: TFPCustomFont); procedure WriteFormula(AStream: TStream; const ARow, ACol: Word; const AFormula: TsFormula); override; + procedure WriteRPNFormula(AStream: TStream; const ARow, ACol: Word; const AFormula: TsRPNFormula); override; procedure WriteIndex(AStream: TStream); procedure WriteLabel(AStream: TStream; const ARow, ACol: Word; const AValue: string; ACell: PCell); override; procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal; const AValue: double; ACell: PCell); override; @@ -808,6 +809,88 @@ begin AStream.position := FinalPos;*) end; +procedure TsSpreadBIFF8Writer.WriteRPNFormula(AStream: TStream; const ARow, + ACol: Word; const AFormula: TsRPNFormula); +var + FormulaResult: double; + i: Integer; + RPNLength: Word; + TokenArraySizePos, RecordSizePos, FinalPos: Int64; + TokenID: Byte; +begin + RPNLength := 0; + FormulaResult := 0.0; + + { 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 } + AStream.WriteWord($0000); + + { Result of the formula in IEE 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; + AStream.WriteWord(RPNLength); + + { Formula data (RPN token array) } + for i := 0 to Length(AFormula) - 1 do + begin + { Token identifier } + TokenID := FormulaElementKindToExcelTokenID(AFormula[i].ElementKind); + AStream.WriteByte(TokenID); + Inc(RPNLength); + + { Additional data } + case TokenID 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_TREFR, INT_EXCEL_TOKEN_TREFV, INT_EXCEL_TOKEN_TREFA: + begin + AStream.WriteWord(AFormula[i].Row and MASK_EXCEL_ROW); + AStream.WriteByte(AFormula[i].Col); + Inc(RPNLength, 3); + 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(22 + RPNLength)); + AStream.position := FinalPos; +end; + {******************************************************************* * TsSpreadBIFF8Writer.WriteIndex () * diff --git a/components/fpspreadsheet/xlscommon.pas b/components/fpspreadsheet/xlscommon.pas index d5e10b248..82a637140 100644 --- a/components/fpspreadsheet/xlscommon.pas +++ b/components/fpspreadsheet/xlscommon.pas @@ -19,7 +19,17 @@ const INT_EXCEL_TOKEN_TSUB = $04; INT_EXCEL_TOKEN_TMUL = $05; INT_EXCEL_TOKEN_TDIV = $06; - INT_EXCEL_TOKEN_TPOWER = $07; + INT_EXCEL_TOKEN_TPOWER = $07; // Power Exponentiation + INT_EXCEL_TOKEN_TCONCAT = $08; + INT_EXCEL_TOKEN_TLT = $09; // Less than + INT_EXCEL_TOKEN_TLE = $0A; // Less than or equal + INT_EXCEL_TOKEN_TEQ = $0B; // Equal + INT_EXCEL_TOKEN_TGE = $0C; // Greater than or equal + INT_EXCEL_TOKEN_TGT = $0D; // Greater than + INT_EXCEL_TOKEN_TNE = $0E; // Not equal + INT_EXCEL_TOKEN_TISECT = $0F; // Cell range intersection + INT_EXCEL_TOKEN_TLIST = $10; // Cell range list + INT_EXCEL_TOKEN_TRANGE = $11; // Cell range { Constant Operand Tokens } INT_EXCEL_TOKEN_TNUM = $1F; @@ -78,6 +88,7 @@ type function GetLastRowIndex(AWorksheet: TsWorksheet): Integer; procedure GetLastColCallback(ACell: PCell; AStream: TStream); function GetLastColIndex(AWorksheet: TsWorksheet): Word; + function FormulaElementKindToExcelTokenID(AElementKind: TFEKind): Byte; end; implementation @@ -131,5 +142,28 @@ begin Result := FLastCol; end; +function TsSpreadBIFFWriter.FormulaElementKindToExcelTokenID( + AElementKind: TFEKind): Byte; +begin + case AElementKind of + { Operand Tokens } + fekCell: Result := INT_EXCEL_TOKEN_TREFR; + fekCellRange: Result := INT_EXCEL_TOKEN_TRANGE; + fekNum: Result := INT_EXCEL_TOKEN_TNUM; + { Basic operations } + fekAdd: Result := INT_EXCEL_TOKEN_TADD; + fekSub: Result := INT_EXCEL_TOKEN_TSUB; + fekDiv: Result := INT_EXCEL_TOKEN_TDIV; + fekMul: Result := INT_EXCEL_TOKEN_TMUL; + { Build-in Functions} + fekABS: Result := INT_EXCEL_SHEET_FUNC_ABS; + fekROUND: Result := INT_EXCEL_SHEET_FUNC_ROUND; + { Other operations } + fekOpSUM: Result := 0; + else + Result := 0; + end; +end; + end.