diff --git a/components/fpspreadsheet/examples/fpschart/fpschart.lpi b/components/fpspreadsheet/examples/fpschart/fpschart.lpi
index 7d8bced85..4610fff23 100644
--- a/components/fpspreadsheet/examples/fpschart/fpschart.lpi
+++ b/components/fpspreadsheet/examples/fpschart/fpschart.lpi
@@ -4,6 +4,7 @@
+
@@ -72,6 +73,9 @@
+
+
+
@@ -79,6 +83,9 @@
+
+
+
diff --git a/components/fpspreadsheet/examples/fpschart/fpschart.lpr b/components/fpspreadsheet/examples/fpschart/fpschart.lpr
index a0cd3af67..fbe063756 100644
--- a/components/fpspreadsheet/examples/fpschart/fpschart.lpr
+++ b/components/fpspreadsheet/examples/fpschart/fpschart.lpr
@@ -13,7 +13,6 @@ uses
{$R *.res}
begin
- Application.Title:='project1';
Application.Initialize;
Application.CreateForm(TFPSChartForm, FPSChartForm);
Application.Run;
diff --git a/components/fpspreadsheet/examples/fpschart/mainform.lfm b/components/fpspreadsheet/examples/fpschart/mainform.lfm
index 89c70905a..6260e0ad5 100644
--- a/components/fpspreadsheet/examples/fpschart/mainform.lfm
+++ b/components/fpspreadsheet/examples/fpschart/mainform.lfm
@@ -1,16 +1,17 @@
object FPSChartForm: TFPSChartForm
- Left = 179
- Height = 331
- Top = 157
- Width = 742
+ Left = 239
+ Height = 382
+ Top = 154
+ Width = 700
Caption = 'FPSpreadsheet Chart Example'
- ClientHeight = 331
- ClientWidth = 742
+ ClientHeight = 382
+ ClientWidth = 700
+ OnCreate = FormCreate
LCLVersion = '0.9.29'
object MyChart: TChart
- Left = 400
+ Left = 352
Height = 240
- Top = 24
+ Top = 136
Width = 336
AxisList = <
item
@@ -37,26 +38,106 @@ object FPSChartForm: TFPSChartForm
object WorksheetGrid: TsWorksheetGrid
Left = 16
Height = 240
- Top = 24
- Width = 360
+ Top = 136
+ Width = 328
Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goRangeSelect, goEditing, goSmoothScroll]
TabOrder = 1
end
object btnCreateGraphic: TButton
- Left = 104
+ Left = 464
Height = 25
- Top = 280
+ Top = 56
Width = 128
Caption = 'Create Graphic'
OnClick = btnCreateGraphicClick
TabOrder = 2
end
+ object Label1: TLabel
+ Left = 16
+ Height = 34
+ Top = 8
+ Width = 676
+ AutoSize = False
+ Caption = 'Please add data to the grid or load it from a file, then choose the location of the data for the X and Y axises and click on the button "Create Graphic" to generate a chart.'
+ ParentColor = False
+ WordWrap = True
+ end
+ object editSourceFile: TFileNameEdit
+ Left = 152
+ Height = 22
+ Top = 48
+ Width = 136
+ DialogOptions = []
+ FilterIndex = 0
+ HideDirectories = False
+ ButtonWidth = 23
+ NumGlyphs = 0
+ MaxLength = 0
+ TabOrder = 3
+ end
+ object Label2: TLabel
+ Left = 14
+ Height = 18
+ Top = 51
+ Width = 130
+ Caption = 'Source Spreadsheet:'
+ ParentColor = False
+ end
+ object btnLoadSpreadsheet: TButton
+ Left = 320
+ Height = 25
+ Top = 48
+ Width = 75
+ Caption = 'Load'
+ OnClick = btnLoadSpreadsheetClick
+ TabOrder = 4
+ end
+ object editXAxis: TLabeledEdit
+ Left = 64
+ Height = 22
+ Top = 80
+ Width = 80
+ EditLabel.AnchorSideLeft.Control = editXAxis
+ EditLabel.AnchorSideTop.Control = editXAxis
+ EditLabel.AnchorSideTop.Side = asrCenter
+ EditLabel.AnchorSideRight.Control = editXAxis
+ EditLabel.AnchorSideBottom.Control = editXAxis
+ EditLabel.Left = 17
+ EditLabel.Height = 18
+ EditLabel.Top = 82
+ EditLabel.Width = 44
+ EditLabel.Caption = 'X-Axis:'
+ EditLabel.ParentColor = False
+ LabelPosition = lpLeft
+ TabOrder = 5
+ Text = 'A1:A5'
+ end
+ object EditYAxis: TLabeledEdit
+ Left = 208
+ Height = 22
+ Top = 80
+ Width = 80
+ EditLabel.AnchorSideLeft.Control = EditYAxis
+ EditLabel.AnchorSideTop.Control = EditYAxis
+ EditLabel.AnchorSideTop.Side = asrCenter
+ EditLabel.AnchorSideRight.Control = EditYAxis
+ EditLabel.AnchorSideBottom.Control = EditYAxis
+ EditLabel.Left = 161
+ EditLabel.Height = 18
+ EditLabel.Top = 82
+ EditLabel.Width = 44
+ EditLabel.Caption = 'Y-Axis:'
+ EditLabel.ParentColor = False
+ LabelPosition = lpLeft
+ TabOrder = 6
+ Text = 'B1:B5'
+ end
object FPSChartSource: TsWorksheetChartSource
PointsNumber = 5
YFirstCellCol = 1
XSelectionDirection = fpsVerticalSelection
YSelectionDirection = fpsVerticalSelection
- left = 376
- top = 264
+ left = 632
+ top = 56
end
end
diff --git a/components/fpspreadsheet/examples/fpschart/mainform.pas b/components/fpspreadsheet/examples/fpschart/mainform.pas
index 6b92a64f4..b9ba09255 100644
--- a/components/fpspreadsheet/examples/fpschart/mainform.pas
+++ b/components/fpspreadsheet/examples/fpschart/mainform.pas
@@ -6,7 +6,8 @@ interface
uses
Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
- StdCtrls, Grids, fpspreadsheetchart, fpspreadsheetgrid, TAGraph, TASeries;
+ StdCtrls, Grids, EditBtn, ExtCtrls, fpspreadsheetchart, fpspreadsheetgrid,
+ TAGraph, TASeries;
type
@@ -14,11 +15,19 @@ type
TFPSChartForm = class(TForm)
btnCreateGraphic: TButton;
+ btnLoadSpreadsheet: TButton;
+ editSourceFile: TFileNameEdit;
+ Label1: TLabel;
+ Label2: TLabel;
+ editXAxis: TLabeledEdit;
+ EditYAxis: TLabeledEdit;
MyChart: TChart;
FPSChartSource: TsWorksheetChartSource;
MyChartLineSeries: TLineSeries;
WorksheetGrid: TsWorksheetGrid;
procedure btnCreateGraphicClick(Sender: TObject);
+ procedure btnLoadSpreadsheetClick(Sender: TObject);
+ procedure FormCreate(Sender: TObject);
private
{ private declarations }
public
@@ -30,14 +39,40 @@ var
implementation
+uses
+ // FPSpreadsheet and supported formats
+ fpspreadsheet, xlsbiff8, xlsbiff5, xlsbiff2, xlsxooxml, fpsopendocument;
+
{$R *.lfm}
{ TFPSChartForm }
procedure TFPSChartForm.btnCreateGraphicClick(Sender: TObject);
begin
+ FPSChartSource.LoadPropertiesFromStrings(editXAxis.Text, editYAxis.Text, '', '', '');
FPSChartSource.LoadFromWorksheetGrid(WorksheetGrid);
end;
+procedure TFPSChartForm.btnLoadSpreadsheetClick(Sender: TObject);
+var
+ Format: TsSpreadsheetFormat;
+ lExt: string;
+begin
+ // First some logic to detect the format from the extension
+ lExt := ExtractFileExt(editSourceFile.Text);
+ if lExt = STR_EXCEL_EXTENSION then Format := sfExcel2
+ else if lExt = STR_OOXML_EXCEL_EXTENSION then Format := sfOOXML
+ else if lExt = STR_OPENDOCUMENT_CALC_EXTENSION then Format := sfOpenDocument
+ else raise Exception.Create('Invalid File Extension');
+
+ // Now the actual loading
+ WorksheetGrid.LoadFromSpreadsheetFile(editSourceFile.Text, Format);
+end;
+
+procedure TFPSChartForm.FormCreate(Sender: TObject);
+begin
+ editSourceFile.InitialDir := ExtractFilePath(ParamStr(0));
+end;
+
end.
diff --git a/components/fpspreadsheet/examples/fpschart/t1.xls b/components/fpspreadsheet/examples/fpschart/t1.xls
new file mode 100644
index 000000000..4947be62c
Binary files /dev/null and b/components/fpspreadsheet/examples/fpschart/t1.xls differ
diff --git a/components/fpspreadsheet/examples/fpschart/t2.ods b/components/fpspreadsheet/examples/fpschart/t2.ods
new file mode 100644
index 000000000..819f6d8bf
Binary files /dev/null and b/components/fpspreadsheet/examples/fpschart/t2.ods differ
diff --git a/components/fpspreadsheet/fpsopendocument.pas b/components/fpspreadsheet/fpsopendocument.pas
index ec8618f9c..a542544fd 100755
--- a/components/fpspreadsheet/fpsopendocument.pas
+++ b/components/fpspreadsheet/fpsopendocument.pas
@@ -189,7 +189,8 @@ begin
//process each cell of the row
CellNode:=RowNode.FindNode('table:table-cell');
- while Assigned(CellNode) do begin
+ while Assigned(CellNode) do
+ begin
ParamColsRepeated:=GetAttrValue(CellNode,'table:number-columns-repeated');
if ParamColsRepeated='' then ParamColsRepeated:='1';
@@ -200,11 +201,9 @@ begin
for ColsCount:=0 to StrToInt(ParamColsRepeated)-1 do begin
if ParamValueType='string' then
ReadLabel(Row+RowsCount,Col+ColsCount,CellNode)
- else
- if ParamFormula<>'' then
+ else if ParamFormula<>'' then
ReadFormula(Row+RowsCount,Col+ColsCount,CellNode)
- else
- if ParamValueType='float' then
+ else if ParamValueType='float' then
ReadNumber(Row+RowsCount,Col+ColsCount,CellNode);
end; //for ColsCount
end; //for RowsCount
@@ -226,7 +225,8 @@ end;
procedure TsSpreadOpenDocReader.ReadFormula(ARow: Word; ACol : Word; ACellNode : TDOMNode);
begin
-
+ // For now just read the number
+ ReadNumber(ARow, ACol, ACellNode);
end;
procedure TsSpreadOpenDocReader.ReadLabel(ARow: Word; ACol : Word; ACellNode : TDOMNode);
@@ -237,14 +237,21 @@ end;
procedure TsSpreadOpenDocReader.ReadNumber(ARow: Word; ACol : Word; ACellNode : TDOMNode);
var
FSettings: TFormatSettings;
- Value: String;
+ Value, Str: String;
+ lNumber: Double;
begin
FSettings.DecimalSeparator:='.';
Value:=GetAttrValue(ACellNode,'office:value');
- if UpperCase(Value)='1.#INF' then begin
+ if UpperCase(Value)='1.#INF' then
+ begin
FWorkSheet.WriteNumber(Arow,ACol,1.0/0.0);
- end else begin
- FWorkSheet.WriteNumber(Arow,ACol,StrToFloat(GetAttrValue(ACellNode,'office:value'),FSettings));
+ end
+ else
+ begin
+ // Don't merge, or else we can't debug
+ Str := GetAttrValue(ACellNode,'office:value');
+ lNumber := StrToFloat(Str,FSettings);
+ FWorkSheet.WriteNumber(Arow,ACol,lNumber);
end;
end;
diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas
index 089faaacd..cfcaaad29 100755
--- a/components/fpspreadsheet/fpspreadsheet.pas
+++ b/components/fpspreadsheet/fpspreadsheet.pas
@@ -74,7 +74,14 @@ type
TCellContentType = (cctEmpty, cctFormula, cctRPNFormula, cctNumber, cctUTF8String);
- {@@ Cell structure for TsWorksheet }
+ {@@ Cell structure for TsWorksheet
+
+ Never suppose that all *Value fields are valid,
+ only one of the ContentTypes is valid. For other fields
+ use TWorksheet.ReadAsUTF8Text and similar methods
+
+ @see TWorksheet.ReadAsUTF8Text
+ }
TCell = record
Col: Byte; // zero-based
@@ -211,6 +218,8 @@ procedure RegisterSpreadFormat(
AWriterClass: TsSpreadWriterClass;
AFormat: TsSpreadsheetFormat);
+
+
implementation
uses
diff --git a/components/fpspreadsheet/fpspreadsheetchart.pas b/components/fpspreadsheet/fpspreadsheetchart.pas
index d2e020184..87725a5fa 100644
--- a/components/fpspreadsheet/fpspreadsheetchart.pas
+++ b/components/fpspreadsheet/fpspreadsheetchart.pas
@@ -19,12 +19,10 @@ uses
// FPSpreadsheet Visual
fpspreadsheetgrid,
// FPSpreadsheet
- fpspreadsheet;
+ fpspreadsheet, fpsutils;
type
- TsSelectionDirection = (fpsVerticalSelection, fpsHorizontalSelection);
-
{@@ Chart data source designed to work together with TChart from Lazarus
to display the data.
@@ -60,6 +58,7 @@ type
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure LoadFromWorksheetGrid(const AValue: TsWorksheetGrid);
+ procedure LoadPropertiesFromStrings(AXInterval, AYInterval, AXTitle, AYTitle, ATitle: string);
public
published
// property WorksheetGrid: TsWorksheetGrid read FWorksheetGrid write SetWorksheetGrid;
@@ -205,4 +204,16 @@ begin
Notify;
end;
+procedure TsWorksheetChartSource.LoadPropertiesFromStrings(AXInterval,
+ AYInterval, AXTitle, AYTitle, ATitle: string);
+var
+ lXCount, lYCount: Integer;
+begin
+ ParseIntervalString(AXInterval, FXFirstCellRow, FXFirstCellCol, lXCount, FXSelectionDirection);
+ ParseIntervalString(AYInterval, FYFirstCellRow, FYFirstCellCol, lYCount, FYSelectionDirection);
+ if lXCount <> lYCount then raise Exception.Create(
+ 'TsWorksheetChartSource.LoadPropertiesFromStrings: Interval sizes don''t match');
+ FPointsNumber := lXCount;
+end;
+
end.
diff --git a/components/fpspreadsheet/fpspreadsheetgrid.pas b/components/fpspreadsheet/fpspreadsheetgrid.pas
index ba72a9ea8..36ed1cd4d 100644
--- a/components/fpspreadsheet/fpspreadsheetgrid.pas
+++ b/components/fpspreadsheet/fpspreadsheetgrid.pas
@@ -31,6 +31,7 @@ type
{ methods }
constructor Create(AOwner: TComponent); override;
procedure LoadFromWorksheet(AWorksheet: TsWorksheet);
+ procedure LoadFromSpreadsheetFile(AFileName: string; AFormat: TsSpreadsheetFormat; AWorksheetIndex: Integer = 0);
procedure SaveToWorksheet(AWorksheet: TsWorksheet);
property DisplayFixedColRow: Boolean read FDisplayFixedColRow write SetDisplayFixedColRow;
end;
@@ -210,7 +211,7 @@ begin
begin
lCol := lCell^.Col;
lRow := lCell^.Row;
- lStr := lCell^.UTF8StringValue;
+ lStr := FWorksheet.ReadAsUTF8Text(lRow, lCol);
if DisplayFixedColRow then
SetCells(lCol + 1, lRow + 1, lStr)
@@ -221,6 +222,20 @@ begin
end;
end;
+procedure TsCustomWorksheetGrid.LoadFromSpreadsheetFile(AFileName: string;
+ AFormat: TsSpreadsheetFormat; AWorksheetIndex: Integer);
+var
+ lWorkbook: TsWorkbook;
+begin
+ lWorkbook := TsWorkbook.Create;
+ try
+ lWorkbook.ReadFromFile(AFileName, AFormat);
+ LoadFromWorksheet(lWorkbook.GetWorksheetByIndex(AWorksheetIndex));
+ finally
+ lWorkbook.Free;
+ end;
+end;
+
procedure TsCustomWorksheetGrid.SaveToWorksheet(AWorksheet: TsWorksheet);
var
x, y: Integer;
diff --git a/components/fpspreadsheet/fpsutils.pas b/components/fpspreadsheet/fpsutils.pas
index 5c477a3c8..070abefe0 100644
--- a/components/fpspreadsheet/fpsutils.pas
+++ b/components/fpspreadsheet/fpsutils.pas
@@ -1,3 +1,6 @@
+{
+ Utility functions from FPSpreadsheet
+}
unit fpsutils;
{$mode objfpc}{$H+}
@@ -5,8 +8,13 @@ unit fpsutils;
interface
uses
- Classes, SysUtils;
+ Classes, SysUtils, StrUtils;
+// Exported types
+type
+ TsSelectionDirection = (fpsVerticalSelection, fpsHorizontalSelection);
+
+// Endianess helper functions
function WordToLE(AValue: Word): Word;
function DWordToLE(AValue: Cardinal): Cardinal;
function IntegerToLE(AValue: Integer): Integer;
@@ -16,6 +24,17 @@ function WordLEtoN(AValue: Word): Word;
function DWordLEtoN(AValue: Cardinal): Cardinal;
function WideStringLEToN(const AValue: WideString): WideString;
+// Other routines
+function ParseIntervalString(const AStr: string;
+ var AFirstCellRow, AFirstCellCol, ACount: Integer;
+ var ADirection: TsSelectionDirection): Boolean;
+function ParseCellString(const AStr: string;
+ var ACellRow, ACellCol: Integer): Boolean;
+function ParseCellRowString(const AStr: string;
+ var AResult: Integer): Boolean;
+function ParseCellColString(const AStr: string;
+ var AResult: Integer): Boolean;
+
implementation
{
@@ -114,5 +133,136 @@ begin
{$ENDIF}
end;
+{@@
+ Parses strings like A5:A10 into an selection interval information
+}
+function ParseIntervalString(const AStr: string;
+ var AFirstCellRow, AFirstCellCol, ACount: Integer;
+ var ADirection: TsSelectionDirection): Boolean;
+var
+ Cells: TStringList;
+ LastCellRow, LastCellCol: Integer;
+begin
+ Result := True;
+
+ // First get the cells
+ Cells := TStringList.Create;
+ ExtractStrings([':'],[], PChar(AStr), Cells);
+
+ // Then parse each of them
+ Result := ParseCellString(Cells[0], AFirstCellRow, AFirstCellCol);
+ if not Result then Exit;
+ Result := ParseCellString(Cells[1], LastCellRow, LastCellCol);
+ if not Result then Exit;
+
+ if AFirstCellRow = LastCellRow then
+ begin
+ ADirection := fpsHorizontalSelection;
+ ACount := LastCellCol - AFirstCellCol + 1;
+ end
+ else if AFirstCellCol = LastCellCol then
+ begin
+ ADirection := fpsVerticalSelection;
+ ACount := LastCellRow - AFirstCellRow + 1;
+ end
+ else Exit(False);
+end;
+
+{@@
+ Parses a cell string, like 'A1' into zero-based column and row numbers
+
+ The parser is a simple state machine, with the following states:
+
+ 0 - Reading Column part 1 (necesserely needs a letter)
+ 1 - Reading Column part 2, but could be the first number as well
+ 2 - Reading Row
+}
+function ParseCellString(const AStr: string; var ACellRow, ACellCol: Integer): Boolean;
+var
+ i: Integer;
+ state: Integer;
+ Col, Row: string;
+ lChar: Char;
+const
+ cLetters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
+ 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'W', 'X', 'Y', 'Z'];
+ cDigits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
+begin
+ // Starting state
+ Result := True;
+ state := 0;
+ Col := '';
+ Row := '';
+
+ // Separates the string into a row and a col
+ for i := 0 to Length(AStr) - 1 do
+ begin
+ lChar := AStr[i + 1];
+
+ case state of
+
+ 0:
+ begin
+ if lChar in cLetters then
+ begin
+ Col := lChar;
+ state := 1;
+ end
+ else Exit(False);
+ end;
+
+ 1:
+ begin
+ if lChar in cLetters then Col := Col + lChar
+ else if lChar in cDigits then
+ begin
+ Row := lChar;
+ state := 2;
+ end
+ else Exit(False);
+ end;
+
+ 2:
+ begin
+ if lChar in cDigits then Row := Row + lChar
+ else Exit(False);
+ end;
+
+ end;
+ end;
+
+ // Now parses each separetely
+ ParseCellRowString(Row, ACellRow);
+ ParseCellColString(Col, ACellCol);
+end;
+
+function ParseCellRowString(const AStr: string; var AResult: Integer): Boolean;
+begin
+ try
+ AResult := StrToInt(AStr) - 1;
+ except
+ Result := False;
+ end;
+ Result := True;
+end;
+
+function ParseCellColString(const AStr: string; var AResult: Integer): Boolean;
+const
+ INT_NUM_LETTERS = 26;
+begin
+ Result := False;
+ AResult := 0;
+
+ if Length(AStr) = 1 then AResult := Ord(AStr[1]) - Ord('A')
+ else if Length(AStr) = 2 then
+ begin
+ AResult := (Ord(AStr[1]) - Ord('A') + 1) * INT_NUM_LETTERS
+ + Ord(AStr[2]) - Ord('A');
+ end
+ else Exit(False);
+
+ Result := True;
+end;
+
end.
diff --git a/components/fpspreadsheet/xlsbiff5.pas b/components/fpspreadsheet/xlsbiff5.pas
index 51edc752a..940b10673 100755
--- a/components/fpspreadsheet/xlsbiff5.pas
+++ b/components/fpspreadsheet/xlsbiff5.pas
@@ -1250,9 +1250,11 @@ begin
try
// Only one stream is necessary for any number of worksheets
OLEDocument.Stream := MemStream;
-
OLEStorage.ReadOLEFile(AFileName, OLEDocument);
+ // Check if the operation succeded
+ if MemStream.Size = 0 then raise Exception.Create('FPSpreadsheet: Reading the OLE document failed');
+
// Rewind the stream and read from it
MemStream.Position := 0;
ReadFromStream(MemStream, AData);
diff --git a/components/fpspreadsheet/xlsbiff8.pas b/components/fpspreadsheet/xlsbiff8.pas
index 3de7d01d1..4566ec521 100755
--- a/components/fpspreadsheet/xlsbiff8.pas
+++ b/components/fpspreadsheet/xlsbiff8.pas
@@ -1234,9 +1234,11 @@ begin
try
// Only one stream is necessary for any number of worksheets
OLEDocument.Stream := MemStream;
-
OLEStorage.ReadOLEFile(AFileName, OLEDocument,'Workbook');
+ // Check if the operation succeded
+ if MemStream.Size = 0 then raise Exception.Create('FPSpreadsheet: Reading the OLE document failed');
+
// Rewind the stream and read from it
MemStream.Position := 0;
ReadFromStream(MemStream, AData);