Recent

Author Topic: fpsSpreadsheet Formula Issue  (Read 1048 times)

dgrhoads

  • New Member
  • *
  • Posts: 48
fpsSpreadsheet Formula Issue
« on: October 07, 2025, 11:08:37 pm »
The formula '=(A10-A$8)' raises an error flag.
The formula '=1*(A10-A$8)' does not raise an error.

I entered the formula in an Excel spreadsheet and then opened it in my application using fpspreadsheet.  In both cases, it worked correctly when run under Excel.

Observed in fpspreadsheet-2.0.0

wp

  • Hero Member
  • *****
  • Posts: 13222
Re: fpsSpreadsheet Formula Issue
« Reply #1 on: October 07, 2025, 11:57:56 pm »
Please post a mini project demonstrating the issue. In my own test, the formula is handled correctly:
Code: Pascal  [Select][+][-]
  1. program Project1;
  2. uses
  3.   fpSpreadsheet, fpsTypes, fpsOpenDocument, xlsxOOXML;
  4. var
  5.   b: TsWorkbook;
  6.   sh: TsWorksheet;
  7. begin
  8.   b := TsWorkbook.Create;
  9.   try
  10.     b.Options := [boAutoCalc];
  11.     sh := b.AddWorksheet('Sheet1');
  12.     sh.WriteNumber(7,0, 10);    // A8
  13.     sh.WriteNumber(9,0, 20);    // A10
  14.     sh.WriteFormula(0,1, '=(A10-A$8)');
  15. //    WriteLn(sh.ReadAsNumber(0, 1):0:0);
  16.     b.WriteToFile('test.ods', true);
  17.     b.WriteToFile('test.xlsx', true);
  18.   finally
  19.     b.Free;
  20.   end;
  21.   ReadLn;
  22. end.

dgrhoads

  • New Member
  • *
  • Posts: 48
Re: fpsSpreadsheet Formula Issue
« Reply #2 on: October 08, 2025, 06:53:52 pm »
My apologies for not providing you with a simple program showing the problem. 

I have written a program which attempts to show the problem.  It compiles cleanly.  However when I go to run it I get an error message which occurs before it accesses any of my code.  The error message references an Access Error at line 1384 of fpsReaderWriter.  I have no clue as how to handle this.  Furthermore, this error message also appears when I attempt to run a program which successfully ran before.

As I explored the errors that I was attempting to share yesterday in my original program, it seemed that the errors occurred when I was trying to copy formulas which involved calculations using dates.  If the formula was "=(A10-A$8)/7, the error message in that formula cell was "#VALUE!'.  If the formula was 'DateDif(A$8, A10, "D"), the error message was '<FMLA?>'.

I wish I could be more helpful in providing working software which clearly illustrates the problem.  Thank you in advance.

wp

  • Hero Member
  • *****
  • Posts: 13222
Re: fpsSpreadsheet Formula Issue
« Reply #3 on: October 08, 2025, 07:31:11 pm »
Could you please also upload the xlsx file used by the demo project?

dgrhoads

  • New Member
  • *
  • Posts: 48
Re: fpsSpreadsheet Formula Issue
« Reply #4 on: October 08, 2025, 08:12:24 pm »
Sorry.  I thought I had included it.

This zip file includes all the files in test folder including the xlsx file.

wp

  • Hero Member
  • *****
  • Posts: 13222
Re: fpsSpreadsheet Formula Issue
« Reply #5 on: October 08, 2025, 11:55:26 pm »
Your code has an issue in the copy block:
Code: Pascal  [Select][+][-]
  1.   // copy 3 rows of cells
  2.   for aRow := 9 to 12 do
  3.   for aCol := 0 to 3  do begin
  4.     srcCell := ws.FindCell(9, aCol);
  5.     cell := ws.FindCell(aRow, aCol);
  6.     ws.AddCell(aRow, aCol);
  7.     ws.CopyFormula(srcCell, cell);
  8.   end;
In Line 5 you try to find the destination cell, then you add a new cell (line 6) and copy the formula to the previously found cell (line 7), but not to the added cell. But what if the destination cell was nil? Then you would have copied the formula to a nil cell. TsWorksheet.CopyFormula detects this case and does not copy anything at all (well, it could even be argued that FPSpreadsheet should raise an exception here...).

But why do you call FindCell at all? You could comment this line out and simple call AddCell and take its return value as destination cell:
Code: Pascal  [Select][+][-]
  1.   // copy 3 rows of cells
  2.   for aRow := 9 to 12 do
  3.   for aCol := 0 to 3  do begin
  4.     srcCell := ws.FindCell(9, aCol);
  5.     // cell := ws.FindCell(aRow, aCol);
  6.     cell := ws.AddCell(aRow, aCol);
  7.     ws.CopyFormula(srcCell, cell);
  8.   end;
In this case I have an exception in the expression parser. This is because when you look for the source cell in row 9 which is the first row handled by the loop. In other words, the destination cell already exists, but the code still adds a new cell to the cells tree at a position where already a cell exists. This is something which the AVL_Tree (the data structure working behind the scenes) does not like at all - there is also a comment in the header of TsWorksheetAddCell: "It is not checked if another cell already exists at the same location. This case must be avoided. USE CAREFULLY WITHOUT FindCell (e.g., during reading into empty worksheets)."

Therefore, the correct code is to checks whether the destination cell already exists (FindCell) and to add a new one only if it does not yet exist:
Code: Pascal  [Select][+][-]
  1.   // copy 3 rows of cells
  2.   for aRow := 9 to 12 do
  3.     for aCol := 0 to 3  do begin
  4.       srcCell := ws.FindCell(9, aCol);
  5.       cell := ws.FindCell(aRow, aCol);
  6.       if cell = nil then
  7.         cell := ws.AddCell(aRow, aCol);
  8.       ws.CopyFormula(srcCell, cell);
  9.     end;

OK - no more crash, but probably still not what you are expecting because the routine copies - nothing...

Again while copying the first row (row 9): a formula cell it copied here to itself. One of the first steps that FPSpreadsheet is doing in CopyFormula, is to delete the formula in the destination cell. But since in this special case source and destination cells are the same, it deletes the formula in the source cell  and has nothing left to copy... To fix this I added a check to FPSpreadsheet which exits the copying process prematurely when source and destination cells are the same. If you don't work with the development version of FPSpreadsheet from CCR patch your fpspreadsheet.pas unit as shown here:

Code: Pascal  [Select][+][-]
  1. procedure TsWorksheet.CopyFormula(AFromCell, AToCell: PCell);
  2. var
  3.   srcBook, destBook: TsWorkbook;
  4.   srcSheet, destSheet: TsWorksheet;
  5.   srcFormula, destFormula: PsFormula;
  6. begin
  7.   if (AFromCell = nil) or (AToCell = nil) then
  8.     exit;
  9.  
  10.   srcSheet := TsWorksheet(AFromCell^.Worksheet);
  11.   destSheet := TsWorksheet(AToCell^.Worksheet);
  12.   srcBook := TsWorkbook(srcSheet.Workbook);
  13.   destBook := TsWorkbook(destSheet.Workbook);
  14.  
  15.   if (AFromCell = AToCell) and (srcSheet = destSheet) then  // <---- ADDED
  16.     exit;                                                   // <---- ADDED
  17.  
  18.   destSheet.DeleteFormula(AToCell);
  19.  
  20.   if not HasFormula(AFromCell) then
  21.     exit;
  22.   ...


wp

  • Hero Member
  • *****
  • Posts: 13222
Re: fpsSpreadsheet Formula Issue
« Reply #6 on: October 09, 2025, 12:44:34 am »
Fixed the DATEDIF formula. Could not reproduce the <FMLA?>, but found several other issues.

dgrhoads

  • New Member
  • *
  • Posts: 48
Re: fpsSpreadsheet Formula Issue
« Reply #7 on: October 09, 2025, 03:20:56 am »
Thank you.  What you said was good and valuable.  The test program after making the modifications you suggested worked. 

I now understand why I have been getting the #VALUE? error.  The dates are not the standard date/time number, but are actual text.  Excel does the text to date conversion at runtime.  fpsSpreadsheet does not.  Whether it should or not is of  course your decision.

I have attacched an application changed per your suggestions and an xlsx file which illustrates the issue with the text formatted dates.

wp

  • Hero Member
  • *****
  • Posts: 13222
Re: fpsSpreadsheet Formula Issue
« Reply #8 on: October 09, 2025, 06:07:26 pm »
OK, I see. I extended the formula parser such that date-formatted strings can also be reckognized as date/time values. However, it works only when the date is formatted using the system settings or when the workbook's FormatSettings are adjusted. When I format the date strings according to my system ('6.1.2025') I can read these cells correctly. But I can also read the original strings ('2025-01-06') after putting the correct FormatSettings into the reading procedure:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormActivate(Sender: TObject);
  2. VAR
  3.   wb: TSWorkbook;
  4.   ws: TSWorksheet;
  5.   wg: TsWorksheetGrid;
  6.   aRow, aCol, srcRow: Integer;
  7.   cell, srcCell: PCell;
  8.   sg: TStringGrid;
  9.   fName: String = 'TestSSformula.xlsx';
  10. begin
  11.   sWorkbookSource1.FileName:= fName;
  12.   wb := sWorkbookSource1.Workbook;
  13.   wb.FormatSettings.DateSeparator := '-';               // <--- ADDED
  14.   wb.FormatSettings.ShortDateFormat := 'yyyy/mm/dd';    // <--- ADDED
  15.   ws := wb.GetWorksheetByIndex(0);              
  16.   ...

dgrhoads

  • New Member
  • *
  • Posts: 48
Re: fpsSpreadsheet Formula Issue
« Reply #9 on: October 10, 2025, 01:20:50 am »
WB:  Thank you for your suggestions.  I have changed my code per your suggestions.  Unfortunately, we are not yet home free.  My code and the associated xlsx file are attached.

You specified that the date format should be "yyyy/mm/dd" and that the date separator should be "-".  I was uncertain whether that would replace the"/" in that date format so I also provided for an alternative of "yyyy-mm-dd".  My problems persisted with both date format specifications.

Issues that I have found are:
1)  Even with your proposed Workbook setup changes, it did not see the date strings as date/time number.
2)  I discovered on looking at the various functions that you have implemented "DateValue" which converts a date string to date/time.  I tried this function (Row 4, Formula A) but it didn't work for me.  Undoubtedly, there is something I am missing. 

Do I need to be using a different fpsSpreadsheet build than 2.0.0?  If so, how to I find it?

Thanks in advance for your excellent support.

wp

  • Hero Member
  • *****
  • Posts: 13222
Re: fpsSpreadsheet Formula Issue
« Reply #10 on: October 10, 2025, 10:59:03 am »
You specified that the date format should be "yyyy/mm/dd" and that the date separator should be "-".  I was uncertain whether that would replace the"/" in that date format so I also provided for an alternative of "yyyy-mm-dd".  My problems persisted with both date format specifications.
The string/date conversion routine replaces the '/' in the date format by the DateSeparator. Therefore, both format strings ('yyyy/mm/dd' and 'yyyy-mm-dd') are equivalent when the DateSeparator is '-'.

Do I need to be using a different fpsSpreadsheet build than 2.0.0?  If so, how to I find it?
I write all my changes to the development version at ccr, the OPM version (2.0.0) is not affected until a new release is made. You must install the ccr version: either use SVN or download and install the snapshot from https://sourceforge.net/p/lazarus-ccr/svn/HEAD/tree/components/fpspreadsheet/.

2)  I discovered on looking at the various functions that you have implemented "DateValue" which converts a date string to date/time.  I tried this function (Row 4, Formula A) but it didn't work for me. 
The function did not take care of the workbook's FormatSettings and furthermore did not account for the case where the argument is a reference pointing to a cell with string content.

BTW, how did it happen that you put string values rather than date values into the worksheet?

dgrhoads

  • New Member
  • *
  • Posts: 48
Re: fpsSpreadsheet Formula Issue
« Reply #11 on: October 10, 2025, 10:40:53 pm »
Good information, thanks.

I got the date strings from the output of an SQL query which went into a CSV file and was subsequently read into a worksheet. The original data was in date/time format.

Up until now, the date format had not been an issue since Excel handled it transparently.  I see now that I will have to explicitly address the format issue of the date.  I am considering alternatives.

wp

  • Hero Member
  • *****
  • Posts: 13222
Re: fpsSpreadsheet Formula Issue
« Reply #12 on: October 10, 2025, 11:16:23 pm »
I got the date strings from the output of an SQL query which went into a CSV file and was subsequently read into a worksheet. The original data was in date/time format.
Before you read the CSV file into fpspreadsheet by means of the CSVReader you should set the CSVOptions.FormatSettings such that the dates are reckognized correctly:
Code: Pascal  [Select][+][-]
  1.  // untested...
  2. uses
  3.   fpsSpreadsheet, fpsTypes, fpsCSV, xlsxOOXML;
  4. var
  5.   book: TsWorkbook;
  6. begin
  7.   CSVOptions.FormatSettings.ShortdateFormat := 'yyyy/mm/dd';
  8.   CSVOptions.FormatSettings.DateSeparator := '-';
  9.   // if needed, adjust other options as well. CSVOptions is in unit fpsTypes.
  10.  
  11.   book := TsWorkbook.Create;
  12.   try
  13.     book.ReadFromFile(SomeCSVFile, sfCSV);
  14.     book.SaveToFile(SomeXLSXFile, sfOOXML, true);
  15.   finally
  16.     book.Free;
  17.   end;

 

TinyPortal © 2005-2018