From 180a1d049d771739e746586c0f3845d5ba481b43 Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Fri, 9 Oct 2015 19:33:02 +0000 Subject: [PATCH] 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 --- components/fpspreadsheet/fpsutils.pas | 99 +++++++++++++++++-- .../fpspreadsheet/tests/internaltests.pas | 45 +++++++++ 2 files changed, 134 insertions(+), 10 deletions(-) diff --git a/components/fpspreadsheet/fpsutils.pas b/components/fpspreadsheet/fpsutils.pas index b21f26763..a0a392606 100644 --- a/components/fpspreadsheet/fpsutils.pas +++ b/components/fpspreadsheet/fpsutils.pas @@ -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. diff --git a/components/fpspreadsheet/tests/internaltests.pas b/components/fpspreadsheet/tests/internaltests.pas index 1acf92250..44e1a0f73 100644 --- a/components/fpspreadsheet/tests/internaltests.pas +++ b/components/fpspreadsheet/tests/internaltests.pas @@ -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);