fpspreadsheet: Add parsing of cell range strings given in "R1C1" syntax

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@4376 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2015-10-09 19:33:02 +00:00
parent ced66bed8b
commit 180a1d049d
2 changed files with 134 additions and 10 deletions

View File

@ -53,6 +53,7 @@ function DWordLEtoN(AValue: Cardinal): Cardinal;
function WideStringLEToN(const AValue: WideString): WideString;
// Cell, column and row strings
// -- "A1" syntax
function ParseIntervalString(const AStr: string;
out AFirstCellRow, AFirstCellCol, ACount: Cardinal;
out ADirection: TsSelectionDirection): Boolean;
@ -76,20 +77,28 @@ function ParseCellRowString(const AStr: string;
function ParseCellColString(const AStr: string;
out AResult: Cardinal): Boolean;
function ParseCellString_R1C1(const AStr: String; ABaseRow, ABaseCol: Cardinal;
out ACellRow, ACellCol: Cardinal; out AFlags: TsRelFlags): Boolean;
function GetColString(AColIndex: Integer): String;
function GetCellString(ARow,ACol: Cardinal;
AFlags: TsRelFlags = [rfRelRow, rfRelCol]): String;
function GetCellString_R1C1(ARow, ACol: Cardinal; AFlags: TsRelFlags = [rfRelRow, rfRelCol];
ARefRow: Cardinal = Cardinal(-1); ARefCol: Cardinal = Cardinal(-1)): String;
function GetCellRangeString(ARow1, ACol1, ARow2, ACol2: Cardinal;
AFlags: TsRelFlags = rfAllRel; Compact: Boolean = false): String; overload;
function GetCellRangeString(ARange: TsCellRange;
AFlags: TsRelFlags = rfAllRel; Compact: Boolean = false): String; overload;
function GetCellString(ARow,ACol: Cardinal;
AFlags: TsRelFlags = [rfRelRow, rfRelCol]): String;
function GetColString(AColIndex: Integer): String;
// -- "R1C1" syntax
function ParseCellRangeString_R1C1(const AStr: string; ABaseRow, ABaseCol: Cardinal;
out AFirstCellRow, AFirstCellCol, ALastCellRow, ALastCellCol: Cardinal;
out AFlags: TsRelFlags): Boolean;
function ParseCellString_R1C1(const AStr: String; ABaseRow, ABaseCol: Cardinal;
out ACellRow, ACellCol: Cardinal; out AFlags: TsRelFlags): Boolean; overload;
function ParseCellString_R1C1(const AStr: string; ABaseRow, ABaseCol: Cardinal;
out ACellRow, ACellCol: Cardinal): Boolean; overload;
function GetCellString_R1C1(ARow, ACol: Cardinal; AFlags: TsRelFlags = [rfRelRow, rfRelCol];
ARefRow: Cardinal = Cardinal(-1); ARefCol: Cardinal = Cardinal(-1)): String;
// Error strings
function GetErrorValueStr(AErrorValue: TsErrorValue): String;
function TryStrToErrorValue(AErrorStr: String; out AErr: TsErrorValue): boolean;
@ -587,6 +596,54 @@ begin
Result := Scan(1);
end;
{@@ ----------------------------------------------------------------------------
Extracts information on cell range from a cellrange string in "R1C1" notation.
Returns in AFlags also information on relative/absolute cells.
@param AStr Cell range string, in R1C1 syntax,
such as R[2]C[3]:R[4]C[8]
@param ABaseRow Row index from which the cell reference is seen.
@param ABaseCol Column index from which the cell reference is seen.
@param AFirstCellRow Row index of the top/left cell of the range (output)
@param AFirstCellCol Column index of the top/left cell of the range (output)
@param ALastCellRow Row index of the bottom/right cell of the range (output)
@param ALastCellCol Column index of the bottom/right cell of the rng (output)
@param AFlags A set containing an element for AFirstCellRow,
AFirstCellCol, ALastCellRow, ALastCellCol if they
represent a relative cell address.
@return FALSE if the string is not a valid cell range
-------------------------------------------------------------------------------}
function ParseCellRangeString_R1C1(const AStr: string; ABaseRow, ABaseCol: Cardinal;
out AFirstCellRow, AFirstCellCol, ALastCellRow, ALastCellCol: Cardinal;
out AFlags: TsRelFlags): Boolean;
var
p: Integer;
s: String;
f: TsRelFlags;
begin
Result := True;
// First find the colon
p := pos(':', AStr);
if p = 0 then exit(false);
// Analyze part after the colon
s := copy(AStr, p+1, Length(AStr));
Result := ParseCellString_R1C1(s, ABaseRow, ABaseCol,
ALastCellRow, ALastCellCol, f);
if not Result then exit;
// Analyze part before the colon
s := copy(AStr, 1, p-1);
Result := ParseCellString_R1C1(s, ABaseRow, ABaseCol,
AFirstCellRow, AFirstCellCol, AFlags);
// Add flags of 2nd part
if rfRelRow in f then Include(AFlags, rfRelRow2);
if rfRelCol in f then Include(AFlags, rfRelCol2);
end;
{@@ ----------------------------------------------------------------------------
Parses a cell string in "R1C1" notation into zero-based column and row numbers
'AFlags' indicates relative addresses.
@ -682,6 +739,28 @@ begin
Result := true;
end;
{@@ ----------------------------------------------------------------------------
Parses a cell string in "R1C1" notation into zero-based column and row numbers
For compatibility with old version which does not return flags for relative
cell addresses.
@param AStr Cell reference in R1C1 syntax, such as R[2]C[3] or R1C5
@param ABaseRow Row index from which the cell reference is seen.
@param ABaseCol Column index from which the cell reference is seen.
@param ACellRow Row index of the top/left cell of the range (output)
@param ACellCol Column index of the top/left cell of the range (output)
-------------------------------------------------------------------------------}
function ParseCellString_R1C1(const AStr: string; ABaseRow, ABaseCol: Cardinal;
out ACellRow, ACellCol: Cardinal): Boolean;
var
flags: TsRelFlags;
begin
Result := ParseCellString_R1C1(AStr, ABaseRow, ABaseCol,
ACellRow, ACellCol, flags);
end;
{@@ ----------------------------------------------------------------------------
Parses a cell string, like 'A1' into zero-based column and row numbers
Note that there can be several letters to address for more than 26 columns.

View File

@ -41,6 +41,7 @@ type
procedure TestCellString;
// Tests cell references given in the "R1C1" syntax.
procedure TestCellString_R1C1;
procedure TestCellRangeString_R1C1;
//todo: add more calls, rename sheets, try to get sheets with invalid indexes etc
//(see strings tests for how to deal with expected exceptions)
@ -599,7 +600,51 @@ begin
// (E15) RC interchanged
res := ParseCellString_R1C1('C1R1', 10, 10, r, c, flags);
CheckEquals(res, false, 'Result mismatch in test E15');
end;
{ Tests cell range references given in the "R1C1" syntax. }
procedure TSpreadInternalTests.TestCellRangeString_R1C1;
var
r1,c1,r2,c2: Cardinal;
s: String;
flags: TsRelFlags;
res: Boolean;
begin
// (1) Absolute reference of the range between cells row0/cell0 and row2/col1
res := ParseCellRangeString_R1C1('R1C1:R3C2', 10, 10, r1, c1, r2, c2, flags);
CheckEquals(res, true, 'Result mismatch in test 1');
CheckEquals(r1, 0, 'Row1 mismatch in test 1'); // base cell coordinates are ignored with absolute refs!
CheckEquals(c1, 0, 'Col1 mismatch in test 1');
CheckEquals(r2, 2, 'Row2 mismatch in test 1'); // base cell coordinates are ignored with absolute refs!
CheckEquals(c2, 1, 'Col2 mismatch in test 1');
CheckEquals(true, flags = [], 'Flags mismatch in test 1');
// (2) Relative reference of the cell left of col 10 and above row 10
res := ParseCellRangeString_R1C1('R[-1]C[-1]:R[1]C[1]', 10, 10, r1, c1, r2, c2, flags);
CheckEquals(res, true, 'Result mismatch in test 2');
CheckEquals(r1, 9, 'Row mismatch in test 2');
CheckEquals(c1, 9, 'Col mismatch in test 2');
CheckEquals(r2, 11, 'Row mismatch in test 2');
CheckEquals(c2, 11, 'Col mismatch in test 2');
CheckEquals(true, flags = [rfRelRow, rfRelCol, rfRelRow2, rfRelCol2], 'Flags mismatch in test 2');
// (3) Absolute reference of first cell (row0/col0), Relative reference of second cell
res := ParseCellRangeString_R1C1('R1C1:R[1]C[1]', 10, 10, r1, c1, r2, c2, flags);
CheckEquals(res, true, 'Result mismatch in test 2');
CheckEquals(r1, 0, 'Row mismatch in test 3');
CheckEquals(c1, 0, 'Col mismatch in test 3');
CheckEquals(r2, 11, 'Row mismatch in test 3');
CheckEquals(c2, 11, 'Col mismatch in test 3');
CheckEquals(true, flags = [rfRelRow2, rfRelCol2], 'Flags mismatch in test 3');
// (4) Relative reference of first cell, absolute reference of second cell
res := ParseCellRangeString_R1C1('R[-1]C[-1]:R20C20', 10, 10, r1, c1, r2, c2, flags);
CheckEquals(res, true, 'Result mismatch in test 4');
CheckEquals(r1, 9, 'Row mismatch in test 4');
CheckEquals(c1, 9, 'Col mismatch in test 4');
CheckEquals(r2, 19, 'Row mismatch in test 4');
CheckEquals(c2, 19, 'Col mismatch in test 4');
CheckEquals(true, flags = [rfRelRow, rfRelCol], 'Flags mismatch in test 4');
end;
procedure TSpreadInternalTests.FractionTest(AMaxDigits: Integer);