fpspreadsheet: Rewrite "ParseCellString" to allow for multiletter column addresses.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3091 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2014-05-24 19:44:40 +00:00
parent 934e210a4f
commit f8e63e1f93
3 changed files with 71 additions and 84 deletions

View File

@ -231,10 +231,11 @@
<Unit13> <Unit13>
<Filename Value="..\..\fpsutils.pas"/> <Filename Value="..\..\fpsutils.pas"/>
<UnitName Value="fpsutils"/> <UnitName Value="fpsutils"/>
<IsVisibleTab Value="True"/>
<EditorIndex Value="2"/> <EditorIndex Value="2"/>
<WindowIndex Value="0"/> <WindowIndex Value="0"/>
<TopLine Value="62"/> <TopLine Value="279"/>
<CursorPos X="1" Y="63"/> <CursorPos X="52" Y="316"/>
<UsageCount Value="69"/> <UsageCount Value="69"/>
<Loaded Value="True"/> <Loaded Value="True"/>
</Unit13> </Unit13>
@ -263,7 +264,6 @@
<Unit17> <Unit17>
<Filename Value="..\..\xlsbiff8.pas"/> <Filename Value="..\..\xlsbiff8.pas"/>
<UnitName Value="xlsbiff8"/> <UnitName Value="xlsbiff8"/>
<IsVisibleTab Value="True"/>
<EditorIndex Value="3"/> <EditorIndex Value="3"/>
<WindowIndex Value="0"/> <WindowIndex Value="0"/>
<TopLine Value="847"/> <TopLine Value="847"/>

View File

@ -784,6 +784,8 @@ resourcestring
lpInvalidNumberFormat = 'Trying to use an incompatible number format.'; lpInvalidNumberFormat = 'Trying to use an incompatible number format.';
lpNoValidNumberFormatString = 'No valid number format string.'; lpNoValidNumberFormatString = 'No valid number format string.';
lpNoValidDateTimeFormatString = 'No valid date/time format string.'; lpNoValidDateTimeFormatString = 'No valid date/time format string.';
lpNoValidCellAddress = '"%s" is not a valid cell address.';
lpNoValidCellRangeAddress = '"%s" is not a valid cell range address.';
lpIllegalNumberFormat = 'Illegal number format.'; lpIllegalNumberFormat = 'Illegal number format.';
lpSpecifyNumberOfParams = 'Specify number of parameters for function %s'; lpSpecifyNumberOfParams = 'Specify number of parameters for function %s';
lpIncorrectParamCount = 'Funtion %s requires at least %d and at most %d parameters.'; lpIncorrectParamCount = 'Funtion %s requires at least %d and at most %d parameters.';
@ -3793,7 +3795,7 @@ var
flags: TsRelFlags; flags: TsRelFlags;
begin begin
if not ParseCellString(ACellAddress, r, c, flags) then if not ParseCellString(ACellAddress, r, c, flags) then
raise Exception.CreateFmt('"%s" is not a valid cell address.', [ACellAddress]); raise Exception.CreateFmt(lpNoValidCellAddress, [ACellAddress]);
Result := RPNCellRef(r,c, flags, ANext); Result := RPNCellRef(r,c, flags, ANext);
end; end;
@ -3826,7 +3828,7 @@ var
flags: TsRelFlags; flags: TsRelFlags;
begin begin
if not ParseCellRangeString(ACellRangeAddress, r1,c1, r2,c2, flags) then if not ParseCellRangeString(ACellRangeAddress, r1,c1, r2,c2, flags) then
raise Exception.CreateFmt('"%s" is not a valid cell range address.', [ACellRangeAddress]); raise Exception.CreateFmt(lpNoValidCellRangeAddress, [ACellRangeAddress]);
Result := RPNCellRange(r1,c1, r2,c2, flags, ANext); Result := RPNCellRange(r1,c1, r2,c2, flags, ANext);
end; end;

View File

@ -275,7 +275,7 @@ end;
{@@ {@@
Parses strings like A5:C10 into a range selection information. Parses strings like A5:C10 into a range selection information.
Return also information on relative/absolute cells. Returns in AFlags also information on relative/absolute cells.
} }
function ParseCellRangeString(const AStr: string; function ParseCellRangeString(const AStr: string;
var AFirstCellRow, AFirstCellCol, ALastCellRow, ALastCellCol: Integer; var AFirstCellRow, AFirstCellCol, ALastCellRow, ALastCellCol: Integer;
@ -283,6 +283,7 @@ function ParseCellRangeString(const AStr: string;
var var
p: Integer; p: Integer;
s: String; s: String;
f: TsRelFlags;
begin begin
Result := True; Result := True;
@ -292,109 +293,93 @@ begin
// Analyze part after the colon // Analyze part after the colon
s := copy(AStr, p+1, Length(AStr)); s := copy(AStr, p+1, Length(AStr));
Result := ParseCellString(s, ALastCellRow, ALastCellCol, AFlags); Result := ParseCellString(s, ALastCellRow, ALastCellCol, f);
if not Result then exit; if not Result then exit;
if (rfRelRow in AFlags) then begin
Include(AFlags, rfRelRow2);
Exclude(AFlags, rfRelRow);
end;
if (rfRelCol in AFlags) then begin
Include(AFlags, rfRelCol2);
Exclude(AFlags, rfRelCol);
end;
// Analyze part before the colon // Analyze part before the colon
s := copy(AStr, 1, p-1); s := copy(AStr, 1, p-1);
Result := ParseCellString(s, AFirstCellRow, AFirstCellCol, AFlags); Result := ParseCellString(s, 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; end;
{@@ {@@
Parses a cell string, like 'A1' into zero-based column and row numbers 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.
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
'AFlags' indicates relative addresses. 'AFlags' indicates relative addresses.
Example "AMP$200" --> (rel) column 1029 (= 26*26*1 + 26*16 + 26 - 1)
(abs) row = 199 (abs)
} }
function ParseCellString(const AStr: string; var ACellRow, ACellCol: Integer; function ParseCellString(const AStr: String; var ACellRow, ACellCol: Integer;
var AFlags: TsRelFlags): Boolean; var AFlags: TsRelFlags): Boolean;
var
i: Integer;
state: Integer;
Col, Row: string;
lChar: Char;
isAbs: Boolean;
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 := '';
AFlags := [rfRelCol, rfRelRow];
isAbs := false;
// Separates the string into a row and a col function Scan(AStartPos: Integer): Boolean;
for i := 1 to Length(AStr) do const
LETTERS = ['A'..'Z'];
DIGITS = ['0'..'9'];
var
i: Integer;
isAbs: Boolean;
begin begin
lChar := AStr[i]; Result := false;
if lChar = '$' then begin i := AStartPos;
if isAbs then // Scan letters
exit(false); while (i <= Length(AStr)) do begin
isAbs := true; if (UpCase(AStr[i]) in LETTERS) then begin
continue; ACellCol := ord(UpCase(AStr[i])) - ord('A') + 1 + ACellCol * 26;
end; inc(i);
case state of
0:
begin
if lChar in cLetters then
begin
Col := lChar;
if isAbs then
Exclude(AFlags, rfRelCol);
isAbs := false;
state := 1;
end end
else Exit(False); else
if (AStr[i] in DIGITS) or (AStr[i] = '$') then
break
else begin
ACellCol := 0;
exit; // Only letters or $ allowed
end;
end; end;
if AStartPos = 1 then Include(AFlags, rfRelCol);
1: isAbs := (AStr[i] = '$');
begin if isAbs then inc(i);
if lChar in cLetters then
Col := Col + lChar // Scan digits
else if lChar in cDigits then while (i <= Length(AStr)) do begin
begin if (AStr[i] in DIGITS) then begin
Row := lChar; ACellRow := (ord(AStr[i]) - ord('0')) + ACellRow * 10;
if isAbs then inc(i);
Exclude(AFlags, rfRelRow);
isAbs := false;
state := 2;
end end
else Exit(False); else begin
ACellCol := 0;
ACellRow := 0;
AFlags := [];
exit;
end;
end; end;
2: dec(ACellCol);
begin dec(ACellRow);
if lChar in cDigits then Row := Row + lChar if not isAbs then Include(AFlags, rfRelRow);
else Exit(False);
end;
end; Result := true;
end; end;
// Now parses each separetely begin
ParseCellRowString(Row, ACellRow); ACellCol := 0;
ParseCellColString(Col, ACellCol); ACellRow := 0;
AFlags := [];
if AStr = '' then
Exit(false);
if (AStr[1] = '$') then
Result := Scan(2)
else
Result := Scan(1);
end; end;
{ for compatibility with old version which does not return flags for relative { for compatibility with old version which does not return flags for relative