You've already forked lazarus-ccr
fpspreadsheet: Validity check for sheet names (test case included)
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3552 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
@@ -901,12 +901,13 @@ type
|
|||||||
procedure WriteToStream(AStream: TStream; AFormat: TsSpreadsheetFormat);
|
procedure WriteToStream(AStream: TStream; AFormat: TsSpreadsheetFormat);
|
||||||
|
|
||||||
{ Worksheet list handling methods }
|
{ Worksheet list handling methods }
|
||||||
function AddWorksheet(AName: string): TsWorksheet;
|
function AddWorksheet(AName: string; AcceptEmptyName: boolean = false): TsWorksheet;
|
||||||
function GetFirstWorksheet: TsWorksheet;
|
function GetFirstWorksheet: TsWorksheet;
|
||||||
function GetWorksheetByIndex(AIndex: Cardinal): TsWorksheet;
|
function GetWorksheetByIndex(AIndex: Cardinal): TsWorksheet;
|
||||||
function GetWorksheetByName(AName: String): TsWorksheet;
|
function GetWorksheetByName(AName: String): TsWorksheet;
|
||||||
function GetWorksheetCount: Cardinal;
|
function GetWorksheetCount: Cardinal;
|
||||||
procedure RemoveAllWorksheets;
|
procedure RemoveAllWorksheets;
|
||||||
|
function ValidWorksheetName(AName: String; AcceptEmptyName: Boolean = false): Boolean;
|
||||||
|
|
||||||
{ Font handling }
|
{ Font handling }
|
||||||
function AddFont(const AFontName: String; ASize: Single;
|
function AddFont(const AFontName: String; ASize: Single;
|
||||||
@@ -1237,7 +1238,7 @@ function HasFormula(ACell: PCell): Boolean;
|
|||||||
implementation
|
implementation
|
||||||
|
|
||||||
uses
|
uses
|
||||||
Math, StrUtils, TypInfo,
|
Math, StrUtils, TypInfo, lazutf8,
|
||||||
fpsStreams, fpsUtils, fpsNumFormatParser, fpsExprParser;
|
fpsStreams, fpsUtils, fpsNumFormatParser, fpsExprParser;
|
||||||
|
|
||||||
{ Translatable strings }
|
{ Translatable strings }
|
||||||
@@ -1245,6 +1246,7 @@ resourcestring
|
|||||||
lpUnsupportedReadFormat = 'Tried to read a spreadsheet using an unsupported format';
|
lpUnsupportedReadFormat = 'Tried to read a spreadsheet using an unsupported format';
|
||||||
lpUnsupportedWriteFormat = 'Tried to write a spreadsheet using an unsupported format';
|
lpUnsupportedWriteFormat = 'Tried to write a spreadsheet using an unsupported format';
|
||||||
lpNoValidSpreadsheetFile = '"%s" is not a valid spreadsheet file';
|
lpNoValidSpreadsheetFile = '"%s" is not a valid spreadsheet file';
|
||||||
|
lpInvalidWorksheetName = '"%s" is not a valid worksheet name.';
|
||||||
lpUnknownSpreadsheetFormat = 'unknown format';
|
lpUnknownSpreadsheetFormat = 'unknown format';
|
||||||
lpMaxRowsExceeded = 'This workbook contains %d rows, but the selected ' +
|
lpMaxRowsExceeded = 'This workbook contains %d rows, but the selected ' +
|
||||||
'file format does not support more than %d rows.';
|
'file format does not support more than %d rows.';
|
||||||
@@ -3123,13 +3125,20 @@ begin
|
|||||||
n := 0;
|
n := 0;
|
||||||
SetLength(AList, n);
|
SetLength(AList, n);
|
||||||
for r := 0 to GetLastOccupiedRowIndex do
|
for r := 0 to GetLastOccupiedRowIndex do
|
||||||
for c := 0 to GetLastOccupiedColIndex do
|
begin
|
||||||
|
c := 0;
|
||||||
|
while (c <= GetLastOccupiedColIndex) do
|
||||||
begin
|
begin
|
||||||
cell := FindCell(r, c);
|
cell := FindCell(r, c);
|
||||||
if IsMergeBase(cell) then begin
|
if IsMergeBase(cell) then
|
||||||
|
begin
|
||||||
FindMergedRange(cell, rng.Row1, rng.Col1, rng.Row2, rng.Col2);
|
FindMergedRange(cell, rng.Row1, rng.Col1, rng.Row2, rng.Col2);
|
||||||
SetLength(AList, n+1);
|
SetLength(AList, n+1);
|
||||||
AList[n] := rng;
|
AList[n] := rng;
|
||||||
|
inc(n);
|
||||||
|
c := rng.Col2; // jump to next cell not belonging to this block
|
||||||
|
end;
|
||||||
|
inc(c);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@@ -5684,11 +5693,16 @@ end;
|
|||||||
It is added to the end of the list of worksheets
|
It is added to the end of the list of worksheets
|
||||||
|
|
||||||
@param AName The name of the new worksheet
|
@param AName The name of the new worksheet
|
||||||
|
@param AcceptEmptyName Allow an empty worksheet name (for Excel2)
|
||||||
@return The instance of the newly created worksheet
|
@return The instance of the newly created worksheet
|
||||||
@see TsWorksheet
|
@see TsWorksheet
|
||||||
}
|
}
|
||||||
function TsWorkbook.AddWorksheet(AName: string): TsWorksheet;
|
function TsWorkbook.AddWorksheet(AName: string;
|
||||||
|
AcceptEmptyName: Boolean = false): TsWorksheet;
|
||||||
begin
|
begin
|
||||||
|
if not ValidWorksheetName(AName, AcceptEmptyName) then
|
||||||
|
raise Exception.CreateFmt(lpInvalidWorksheetName, [AName]);
|
||||||
|
|
||||||
Result := TsWorksheet.Create;
|
Result := TsWorksheet.Create;
|
||||||
|
|
||||||
Result.Name := AName;
|
Result.Name := AName;
|
||||||
@@ -5779,6 +5793,46 @@ procedure TsWorkbook.RemoveAllWorksheets;
|
|||||||
begin
|
begin
|
||||||
FWorksheets.ForEachCall(RemoveWorksheetsCallback, nil);
|
FWorksheets.ForEachCall(RemoveWorksheetsCallback, nil);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
{@@
|
||||||
|
Checks whether the passed string is a valid worksheet name according to Excel
|
||||||
|
(ODS seems to be a bit less restrictive, but if we follow Excel's convention
|
||||||
|
we always have valid sheet names independent of the format.
|
||||||
|
|
||||||
|
@param AName Name to be checked
|
||||||
|
@param AcceptEmptyName Accepts an empty name (for Excel2)
|
||||||
|
@return TRUE if it is a valid worksheet name, FALSE otherwise
|
||||||
|
}
|
||||||
|
function TsWorkbook.ValidWorksheetName(AName: String;
|
||||||
|
AcceptEmptyName: Boolean = false): Boolean;
|
||||||
|
// see: http://stackoverflow.com/questions/451452/valid-characters-for-excel-sheet-names
|
||||||
|
var
|
||||||
|
INVALID_CHARS: array [0..6] of char = ('[', ']', ':', '*', '?', '/', '\');
|
||||||
|
var
|
||||||
|
i: Integer;
|
||||||
|
begin
|
||||||
|
Result := false;
|
||||||
|
|
||||||
|
// Name must not be empty
|
||||||
|
if (AName = '') and (not AcceptEmptyName) then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
// Length must be less than 31 characters
|
||||||
|
if UTF8Length(AName) > 31 then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
// Name must not contain any of the INVALID_CHARS
|
||||||
|
for i:=0 to High(INVALID_CHARS) do
|
||||||
|
if UTF8Pos(INVALID_CHARS[i], AName) > 0 then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
// Name must be unique
|
||||||
|
if GetWorksheetByName(AName) <> nil then
|
||||||
|
exit;
|
||||||
|
|
||||||
|
Result := true;
|
||||||
|
end;
|
||||||
|
|
||||||
(*
|
(*
|
||||||
{@@
|
{@@
|
||||||
Sets the selected flag for the sheet with the given index.
|
Sets the selected flag for the sheet with the given index.
|
||||||
|
@@ -42,6 +42,8 @@ type
|
|||||||
// Verify GetSheetByName returns the correct sheet number
|
// Verify GetSheetByName returns the correct sheet number
|
||||||
// GetSheetByName was implemented in SVN revision 2857
|
// GetSheetByName was implemented in SVN revision 2857
|
||||||
procedure GetSheetByName;
|
procedure GetSheetByName;
|
||||||
|
// Test for invalid sheet names
|
||||||
|
procedure InvalidSheetName;
|
||||||
// Tests whether overwriting existing file works
|
// Tests whether overwriting existing file works
|
||||||
procedure OverwriteExistingFile;
|
procedure OverwriteExistingFile;
|
||||||
// Write out date cell and try to read as UTF8; verify if contents the same
|
// Write out date cell and try to read as UTF8; verify if contents the same
|
||||||
@@ -92,6 +94,41 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TSpreadInternalTests.InvalidSheetName;
|
||||||
|
type
|
||||||
|
TSheetNameCheck = record
|
||||||
|
Valid: Boolean;
|
||||||
|
SheetName: String;
|
||||||
|
end;
|
||||||
|
const
|
||||||
|
TestCases: array[0..7] of TSheetNameCheck = (
|
||||||
|
(Valid: true; SheetName:'Sheet'),
|
||||||
|
(Valid: true; SheetName:'äöü'),
|
||||||
|
(Valid: false; SheetName:'Test'), // duplicate
|
||||||
|
(Valid: false; SheetName:''), // empty string
|
||||||
|
(Valid: false; SheetName:'Very very very very very very very very long'), // too long
|
||||||
|
(Valid: false; SheetName:'[sheet]'), // fobidden chars in following cases
|
||||||
|
(Valid: false; SheetName:'/sheet/'),
|
||||||
|
(Valid: false; SheetName:'\sheet\')
|
||||||
|
);
|
||||||
|
var
|
||||||
|
i: Integer;
|
||||||
|
MyWorkbook: TsWorkbook;
|
||||||
|
ok: Boolean;
|
||||||
|
begin
|
||||||
|
MyWorkbook := TsWorkbook.Create;
|
||||||
|
try
|
||||||
|
MyWorkbook.AddWorksheet('Test');
|
||||||
|
for i:=0 to High(TestCases) do
|
||||||
|
begin
|
||||||
|
ok := MyWorkbook.ValidWorksheetName(TestCases[i].SheetName);
|
||||||
|
CheckEquals(TestCases[i].Valid, ok, 'Sheet name validity check mismatch: ' + TestCases[i].SheetName);
|
||||||
|
end;
|
||||||
|
finally
|
||||||
|
MyWorkbook.Free;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TSpreadInternalTests.OverwriteExistingFile;
|
procedure TSpreadInternalTests.OverwriteExistingFile;
|
||||||
const
|
const
|
||||||
FirstFileCellText='Old version';
|
FirstFileCellText='Old version';
|
||||||
|
@@ -48,6 +48,7 @@
|
|||||||
<Unit1>
|
<Unit1>
|
||||||
<Filename Value="datetests.pas"/>
|
<Filename Value="datetests.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
|
<UnitName Value="datetests"/>
|
||||||
</Unit1>
|
</Unit1>
|
||||||
<Unit2>
|
<Unit2>
|
||||||
<Filename Value="stringtests.pas"/>
|
<Filename Value="stringtests.pas"/>
|
||||||
@@ -56,7 +57,6 @@
|
|||||||
<Unit3>
|
<Unit3>
|
||||||
<Filename Value="numberstests.pas"/>
|
<Filename Value="numberstests.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
<UnitName Value="numberstests"/>
|
|
||||||
</Unit3>
|
</Unit3>
|
||||||
<Unit4>
|
<Unit4>
|
||||||
<Filename Value="manualtests.pas"/>
|
<Filename Value="manualtests.pas"/>
|
||||||
@@ -66,20 +66,20 @@
|
|||||||
<Unit5>
|
<Unit5>
|
||||||
<Filename Value="testsutility.pas"/>
|
<Filename Value="testsutility.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
<UnitName Value="testsutility"/>
|
|
||||||
</Unit5>
|
</Unit5>
|
||||||
<Unit6>
|
<Unit6>
|
||||||
<Filename Value="internaltests.pas"/>
|
<Filename Value="internaltests.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
|
<UnitName Value="internaltests"/>
|
||||||
</Unit6>
|
</Unit6>
|
||||||
<Unit7>
|
<Unit7>
|
||||||
<Filename Value="formattests.pas"/>
|
<Filename Value="formattests.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
<UnitName Value="formattests"/>
|
|
||||||
</Unit7>
|
</Unit7>
|
||||||
<Unit8>
|
<Unit8>
|
||||||
<Filename Value="colortests.pas"/>
|
<Filename Value="colortests.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
|
<UnitName Value="colortests"/>
|
||||||
</Unit8>
|
</Unit8>
|
||||||
<Unit9>
|
<Unit9>
|
||||||
<Filename Value="fonttests.pas"/>
|
<Filename Value="fonttests.pas"/>
|
||||||
@@ -110,10 +110,12 @@
|
|||||||
<Unit15>
|
<Unit15>
|
||||||
<Filename Value="errortests.pas"/>
|
<Filename Value="errortests.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
|
<UnitName Value="errortests"/>
|
||||||
</Unit15>
|
</Unit15>
|
||||||
<Unit16>
|
<Unit16>
|
||||||
<Filename Value="virtualmodetests.pas"/>
|
<Filename Value="virtualmodetests.pas"/>
|
||||||
<IsPartOfProject Value="True"/>
|
<IsPartOfProject Value="True"/>
|
||||||
|
<UnitName Value="virtualmodetests"/>
|
||||||
</Unit16>
|
</Unit16>
|
||||||
</Units>
|
</Units>
|
||||||
</ProjectOptions>
|
</ProjectOptions>
|
||||||
|
@@ -509,7 +509,7 @@ begin
|
|||||||
BIFF2EOF := False;
|
BIFF2EOF := False;
|
||||||
|
|
||||||
{ In BIFF2 files there is only one worksheet, let's create it }
|
{ In BIFF2 files there is only one worksheet, let's create it }
|
||||||
FWorksheet := AData.AddWorksheet('');
|
FWorksheet := AData.AddWorksheet('', true); // true --> accept empty sheet name
|
||||||
|
|
||||||
{ Read all records in a loop }
|
{ Read all records in a loop }
|
||||||
while not BIFF2EOF do
|
while not BIFF2EOF do
|
||||||
|
Reference in New Issue
Block a user