fpspreadsheet: Redo searching (better OO code), some identifiers renamed with respect to initial commit.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@4313 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2015-09-07 15:48:43 +00:00
parent c8d8277863
commit 49dea52ee1
8 changed files with 650 additions and 572 deletions

View File

@ -355,7 +355,9 @@ type
const AHyperlink: TsHyperlink);
private
{ private declarations }
procedure SearchFound(Sender: TObject; ACell: PCell);
procedure SearchClose(Sender: TObject; var CloseAction: TCloseAction);
procedure SearchFound(Sender: TObject; AFound: Boolean;
AWorksheet: TsWorksheet; ARow, ACol: Cardinal);
procedure UpdateCaption;
protected
procedure ReadFromIni;
@ -496,7 +498,9 @@ begin
if SearchForm = nil then
SearchForm := TSearchForm.Create(self);
SearchForm.OnFound := @SearchFound;
SearchForm.Execute(WorkbookSource.Workbook, DefaultSearchParams);
SearchForm.OnClose := @SearchClose;
SearchForm.SearchParams := DefaultSearchParams;
SearchForm.Execute(WorkbookSource.Workbook);
end;
procedure TMainForm.AcSettingsCSVParamsExecute(Sender: TObject);
@ -601,9 +605,30 @@ begin
end;
end;
procedure TMainForm.SearchFound(Sender: TObject; ACell: PCell);
procedure TMainForm.SearchClose(Sender: TObject; var CloseAction: TCloseAction);
begin
// There could be status message "search string found", here
Unused(CloseAction);
DefaultSearchParams := TSearchForm(Sender).SearchParams;
end;
procedure TMainForm.SearchFound(Sender: TObject; AFound: Boolean;
AWorksheet: TsWorksheet; ARow, ACol: Cardinal);
begin
Unused(AWorksheet, ARow, ACol);
if AFound then
begin
//
end
else
begin
DefaultSearchParams := TSearchForm(Sender).SearchParams;
MessageDlg(
Format('The search text "%s" could not be found.', [DefaultSearchParams.SearchText]),
mtInformation,
[mbOK], 0
);
end;
end;
procedure TMainForm.UpdateCaption;

View File

@ -1,12 +1,12 @@
object SearchForm: TSearchForm
Left = 238
Height = 271
Height = 272
Top = 157
Width = 392
Width = 483
BorderStyle = bsDialog
Caption = 'Search'
ClientHeight = 271
ClientWidth = 392
ClientHeight = 272
ClientWidth = 483
FormStyle = fsStayOnTop
OnClose = FormClose
OnCreate = FormCreate
@ -24,16 +24,16 @@ object SearchForm: TSearchForm
Left = 93
Height = 23
Top = 14
Width = 283
Width = 374
Anchors = [akTop, akLeft, akRight]
ItemHeight = 15
TabOrder = 0
end
object CgSearchOptions: TCheckGroup
Left = 19
Left = 16
Height = 163
Top = 53
Width = 189
Width = 192
AutoFill = True
Caption = 'Search options'
ChildSizing.LeftRightSpacing = 6
@ -45,24 +45,24 @@ object SearchForm: TSearchForm
ChildSizing.Layout = cclLeftToRightThenTopToBottom
ChildSizing.ControlsPerLine = 1
ClientHeight = 143
ClientWidth = 185
ClientWidth = 188
Items.Strings = (
'Compare full cell '
'Ignore case'
'Compare entire cell '
'Match case'
'Regular expression'
'Backwards'
'Search along rows'
'Continue at start/end'
)
TabOrder = 1
Data = {
050000000202020202
}
end
object RgSearchSource: TRadioGroup
Left = 237
Height = 75
object RgSearchWithin: TRadioGroup
Left = 232
Height = 67
Top = 53
Width = 139
Width = 232
AutoFill = True
Caption = 'Search within'
ChildSizing.LeftRightSpacing = 6
@ -70,39 +70,43 @@ object SearchForm: TSearchForm
ChildSizing.EnlargeVertical = crsHomogenousChildResize
ChildSizing.ShrinkHorizontal = crsScaleChilds
ChildSizing.ShrinkVertical = crsScaleChilds
ChildSizing.Layout = cclLeftToRightThenTopToBottom
ChildSizing.ControlsPerLine = 1
ClientHeight = 55
ClientWidth = 135
ChildSizing.Layout = cclTopToBottomThenLeftToRight
ChildSizing.ControlsPerLine = 2
ClientHeight = 47
ClientWidth = 228
ColumnLayout = clVerticalThenHorizontal
Columns = 2
ItemIndex = 0
Items.Strings = (
'worksheet'
'workbook'
'worksheet'
'column'
'row'
)
TabOrder = 2
end
object ButtonPanel: TPanel
Left = 0
Height = 38
Top = 233
Width = 392
Top = 234
Width = 483
Align = alBottom
BevelOuter = bvNone
ClientHeight = 38
ClientWidth = 392
ClientWidth = 483
TabOrder = 3
object Bevel1: TBevel
Left = 6
Height = 3
Top = 0
Width = 380
Width = 471
Align = alTop
BorderSpacing.Left = 6
BorderSpacing.Right = 6
Shape = bsTopLine
end
object BtnSearchBack: TBitBtn
Left = 149
Left = 240
Height = 25
Top = 7
Width = 75
@ -149,18 +153,19 @@ object SearchForm: TSearchForm
Visible = False
end
object BtnClose: TBitBtn
Left = 309
Left = 400
Height = 25
Top = 7
Width = 75
Anchors = [akTop, akRight]
Cancel = True
DefaultCaption = True
Kind = bkClose
ModalResult = 11
TabOrder = 1
end
object BtnSearch: TBitBtn
Left = 229
Left = 320
Height = 25
Top = 7
Width = 75
@ -208,10 +213,10 @@ object SearchForm: TSearchForm
end
end
object RgSearchStart: TRadioGroup
Left = 237
Height = 75
Top = 141
Width = 139
Left = 232
Height = 56
Top = 160
Width = 232
AutoFill = True
Caption = 'Start search at'
ChildSizing.LeftRightSpacing = 6
@ -220,13 +225,14 @@ object SearchForm: TSearchForm
ChildSizing.ShrinkHorizontal = crsScaleChilds
ChildSizing.ShrinkVertical = crsScaleChilds
ChildSizing.Layout = cclLeftToRightThenTopToBottom
ChildSizing.ControlsPerLine = 1
ClientHeight = 55
ClientWidth = 135
ChildSizing.ControlsPerLine = 2
ClientHeight = 36
ClientWidth = 228
Columns = 2
ItemIndex = 0
Items.Strings = (
'beginning/end'
'active cell'
'beginning/end'
)
TabOrder = 4
end

View File

@ -5,25 +5,12 @@ unit sSearchForm;
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ButtonPanel,
StdCtrls, ExtCtrls, Buttons, fpsTypes, fpspreadsheet;
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs,
StdCtrls, ExtCtrls, Buttons, fpsTypes, fpspreadsheet, fpsSearch;
type
{ TSearchParams }
TsSearchSource = (spsWorksheet, spsWorkbook);
TsSearchStart = (spsBeginningEnd, spsActiveCell);
TsSearchParams = record
SearchText: String;
Options: TsSearchOptions;
Source: TsSearchSource;
Start: TsSearchStart;
end;
TsSearchEvent = procedure (Sender: TObject; ACell: PCell) of object;
TsSearchEvent = procedure (Sender: TObject; AFound: Boolean;
AWorksheet: TsWorksheet; ARow, ACol: Cardinal) of object;
{ TSearchForm }
@ -37,24 +24,25 @@ type
LblSearchText: TLabel;
ButtonPanel: TPanel;
RgSearchStart: TRadioGroup;
RgSearchSource: TRadioGroup;
RgSearchWithin: TRadioGroup;
procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
procedure FormCreate(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure SearchButtonClick(Sender: TObject);
private
{ private declarations }
FSearchEngine: TsSearchEngine;
FWorkbook: TsWorkbook;
FFoundCell: PCell;
FFoundWorksheet: TsWorksheet;
FFoundRow, FFoundCol: Cardinal;
FOnFound: TsSearchEvent;
procedure CtrlsToParams(var ASearchParams: TsSearchParams);
function FindStartCell(AParams: TsSearchParams; var AWorksheet: TsWorksheet;
var AStartRow, AStartCol: Cardinal): Boolean;
procedure ParamsToCtrls(const ASearchParams: TsSearchParams);
function GetParams: TsSearchParams;
procedure SetParams(const AValue: TsSearchParams);
public
{ public declarations }
procedure Execute(AWorkbook: TsWorkbook; var ASearchParams: TsSearchParams);
procedure Execute(AWorkbook: TsWorkbook);
property Workbook: TsWorkbook read FWorkbook;
property SearchParams: TsSearchParams read GetParams write SetParams;
property OnFound: TsSearchEvent read FOnFound write FOnFound;
end;
@ -63,9 +51,8 @@ var
DefaultSearchParams: TsSearchParams = (
SearchText: '';
Options: [soIgnoreCase];
Source: spsWorksheet;
Start: spsActiveCell;
Options: [];
Within: swWorksheet
);
@ -73,26 +60,33 @@ implementation
{$R *.lfm}
const
MAX_SEARCH_ITEMS = 10;
uses
fpsUtils;
procedure TSearchForm.CtrlsToParams(var ASearchParams: TsSearchParams);
var
i: Integer;
const
MAX_SEARCH_ITEMS = 10;
COMPARE_ENTIRE_CELL = 0;
MATCH_CASE = 1;
REGULAR_EXPRESSION = 2;
SEARCH_ALONG_ROWS = 3;
CONTINUE_AT_START_END = 4;
{ TSearchForms }
procedure TSearchForm.Execute(AWorkbook: TsWorkbook);
begin
ASearchParams.SearchText := CbSearchText.Text;
ASearchParams.Options := [];
for i:=0 to CgSearchOptions.Items.Count-1 do
if CgSearchOptions.Checked[i] then
Include(ASearchparams.Options, TsSearchOption(i));
ASearchParams.Source := TsSearchSource(RgSearchSource.ItemIndex);
ASearchParams.Start := TsSearchStart(RgSearchStart.ItemIndex);
FWorkbook := AWorkbook;
Show;
end;
procedure TSearchForm.FormClose(Sender: TObject; var CloseAction: TCloseAction);
var
P: TPoint;
begin
Unused(CloseAction);
FreeAndNil(FSearchEngine);
P.X := Left;
P.Y := Top;
Position := poDesigned;
@ -109,203 +103,37 @@ procedure TSearchForm.FormShow(Sender: TObject);
begin
BtnSearch.Caption := 'Search';
BtnSearchBack.Visible := false;
FFoundCell := nil;
FFoundCol := UNASSIGNED_ROW_COL_INDEX;
FFoundRow := UNASSIGNED_ROW_COL_INDEX;
FFoundWorksheet := nil;
end;
procedure TSearchForm.Execute(AWorkbook: TsWorkbook;
var ASearchParams: TsSearchParams);
function TSearchForm.GetParams: TsSearchParams;
begin
FWorkbook := AWorkbook;
ParamsToCtrls(ASearchParams);
Show;
CtrlsToParams(ASearchParams);
end;
function TSearchForm.FindStartCell(AParams: TsSearchParams;
var AWorksheet: TsWorksheet; var AStartRow, AStartCol: Cardinal): Boolean;
var
sheetIndex: integer;
cell: PCell;
begin
Result := false;
cell := nil;
// Case (1): Search not executed before
if FFoundCell = nil then
begin
case AParams.Start of
spsActiveCell:
begin
AWorksheet := FWorkbook.ActiveWorksheet;
AStartRow := AWorksheet.ActiveCellRow;
AStartCol := AWorksheet.ActiveCellCol;
end;
spsBeginningEnd:
if (soBackward in AParams.Options) then
begin
AWorksheet := FWorkbook.GetWorksheetByIndex(FWorkbook.GetWorksheetCount-1);
AStartCol := AWorksheet.GetLastColIndex;
AStartRow := AWorksheet.GetlastRowIndex;
end else
begin
AWorksheet := FWorkbook.GetWorksheetByIndex(0);
AStartCol := AWorksheet.GetFirstColIndex;
AStartRow := AWorksheet.GetFirstRowIndex;
end;
end;
end else
// Case (2):
// Repeated execution of search to start at cell adjacent to the one found in
// previous call.
begin
//AWorksheet := TsWorksheet(FFoundCell^.Worksheet);
// FoundCell is the cell found in the previous call.
//AStartRow := FFoundCell^.Row;
//AStartCol := FFoundCell^.Col;
sheetIndex := FWorkbook.GetWorksheetIndex(AWorksheet);
// Case (1): Find prior occupied cell along row
if (AParams.Options * [soAlongRows, soBackward] = [soAlongRows, soBackward]) then
begin
cell := AWorksheet.FindPrevCellInRow(AStartRow, AStartCol);
// No "prior" cell found in this row --> Proceed with previous row
while (cell = nil) and (AStartRow > 0) do
begin
dec(AStartRow);
AStartCol := AWorksheet.GetLastColIndex;
cell := AWorksheet.FindCell(AStartRow, AStartCol);
if (cell = nil) then
cell := AWorksheet.FindPrevCellInRow(AStartRow, AStartCol);
// No "prior" cell found in this sheet --> Proceed with previous sheet
if (cell = nil) and (AStartRow = 0) then
begin
if sheetIndex = 0 then
exit;
dec(sheetIndex);
AWorksheet := FWorkbook.GetWorksheetByIndex(sheetIndex);
AStartCol := AWorksheet.GetLastColIndex;
AStartRow := AWorksheet.GetLastRowIndex;
cell := AWorksheet.FindCell(AStartRow, AStartCol);
if (cell = nil) then
cell := AWorksheet.FindPrevCellInRow(AStartRow, AStartCol);
end;
end;
end
else
// Case (2): Find prior occupied cell along columns
if (AParams.Options * [soAlongRows, soBackward] = [soBackward]) then
begin
cell := AWorksheet.FindPrevCellInCol(AStartRow, AStartCol);
// No "preior" cell found in this column --> Proceed with previous column
while (cell = nil) and (AStartCol > 0) do
begin
dec(AStartCol);
AStartRow := AWorksheet.GetLastRowIndex;
cell := AWorksheet.FindCell(AStartRow, AStartCol);
if (cell = nil) then
cell := AWorksheet.FindPrevCellInCol(AStartRow, AStartCol);
// No "prior" cell found in this sheet --> Proceed with previous sheet
if (cell = nil) and (AStartCol = 0) then
begin
if sheetIndex = 0 then
exit;
dec(sheetIndex);
AWorksheet := FWorkbook.GetWorksheetByIndex(sheetIndex);
AStartCol := AWorksheet.GetLastColIndex;
AStartRow := AWorksheet.GetLastRowIndex;
cell := AWorksheet.FindCell(AStartRow, AStartCol);
if (cell = nil) then
cell := AWorksheet.FindPrevCellinCol(AStartRow, AStartCol);
end;
end;
end
else
// Case (3): Find next occupied cell along row
if (AParams.Options * [soAlongRows, soBackward] = [soAlongRows]) then
begin
cell := AWorksheet.FindNextCellInRow(AStartRow, AStartCol);
// No cell found in this row --> Proceed with next row
while (cell = nil) and (AStartRow < AWorksheet.GetLastRowIndex) do
begin
inc(AStartRow);
AStartCol := AWorksheet.GetFirstColIndex;
cell := AWorksheet.FindCell(AStartRow, AStartCol);
if (cell = nil) then
cell := AWorksheet.FindNextCellInRow(AStartRow, AStartCol);
// No "next" cell found in this sheet --> Proceed with next sheet
if (cell = nil) and (AStartRow = AWorksheet.GetLastRowIndex) then
begin
if sheetIndex = 0 then
exit;
inc(sheetIndex);
AWorksheet := FWorkbook.GetWorksheetByIndex(sheetIndex);
AStartCol := AWorksheet.GetLastColIndex;
AStartRow := AWorksheet.GetLastRowIndex;
cell := AWorksheet.FindCell(AStartRow, AStartCol);
if (cell = nil) then
cell := AWorksheet.FindNextCellInRow(AStartRow, AStartCol);
end;
end;
end
else
// Case (4): Find next occupied cell along column
if (AParams.Options * [soAlongRows, soBackward] = []) then
begin
cell := AWorksheet.FindNextCellInCol(AStartRow, AStartCol);
// No "next" occupied cell found in this column --> Proceed with next column
while (cell = nil) and (AStartCol < AWorksheet.GetLastColIndex) do
begin
inc(AStartCol);
AStartRow := AWorksheet.GetFirstRowIndex;
cell := AWorksheet.FindCell(AStartRow, AStartCol);
if (cell = nil) then
cell := AWorksheet.FindNextCellInCol(AStartRow, AStartCol);
// No "next" cell found in this sheet --> Proceed with next sheet
if (cell = nil) and (AStartCol = 0) then
begin
if sheetIndex = 0 then
exit;
inc(sheetIndex);
AWorksheet := FWorkbook.GetWorksheetByIndex(sheetIndex);
AStartCol := AWorksheet.GetLastColIndex;
AStartRow := AWorksheet.GetLastRowIndex;
cell := AWorksheet.FindCell(AStartRow, AStartCol);
if (cell = nil) then
cell := AWorksheet.FindNextCellInCol(AStartRow, AStartCol);
end;
end;
end;
end;
if cell <> nil then
begin
AStartRow := cell^.Row;
AStartCol := cell^.Col;
end;
Result := true;
end;
procedure TSearchForm.ParamsToCtrls(const ASearchParams: TsSearchParams);
var
i: Integer;
o: TsSearchOption;
begin
CbSearchText.Text := ASearchParams.SearchText;
for o in TsSearchOption do
if ord(o) < CgSearchOptions.Items.Count then
CgSearchOptions.Checked[ord(o)] := (o in ASearchParams.Options);
RgSearchSource.ItemIndex := ord(ASearchParams.Source);
RgSearchStart.ItemIndex := ord(ASearchParams.Start);
Result.SearchText := CbSearchText.Text;
Result.Options := [];
if CgSearchOptions.Checked[COMPARE_ENTIRE_CELL] then
Include(Result.Options, soCompareEntireCell);
if CgSearchOptions.Checked[MATCH_CASE] then
Include(Result.Options, soMatchCase);
if CgSearchOptions.Checked[REGULAR_EXPRESSION] then
Include(Result.Options, soRegularExpr);
if CgSearchOptions.Checked[SEARCH_ALONG_ROWS] then
Include(Result.Options, soAlongRows);
if CgSearchOptions.Checked[CONTINUE_AT_START_END] then
Include(Result.Options, soWrapDocument);
if RgSearchStart.ItemIndex = 1 then
Include(Result.Options, soEntireDocument);
Result.Within := TsSearchWithin(RgSearchWithin.ItemIndex);
end;
procedure TSearchForm.SearchButtonClick(Sender: TObject);
var
startsheet: TsWorksheet;
sheetIdx: Integer;
r,c: Cardinal;
backward: Boolean;
params: TsSearchParams;
cell: PCell;
found: Boolean;
begin
CtrlsToParams(params);
params := GetParams;
if params.SearchText = '' then
exit;
@ -316,66 +144,43 @@ begin
CbSearchText.Items.Delete(CbSearchText.Items.Count-1);
end;
if FFoundcell = nil then
backward := (soBackward in params.Options) // 1st call: use value from Options
else
backward := (Sender = BtnSearchBack); // subseq call: follow button
if backward then Include(params.Options, soBackward) else
Exclude(params.Options, soBackward);
if params.Start = spsActiveCell then
if FSearchEngine = nil then
begin
startSheet := FWorkbook.ActiveWorksheet;
FFoundCell := startSheet.FindCell(startSheet.ActiveCellRow, startSheet.ActiveCellCol);
FSearchEngine := TsSearchEngine.Create(FWorkbook);
if (soBackward in params.Options) then
Include(params.Options, soBackward) else
Exclude(params.Options, soBackward);
found := FSearchEngine.FindFirst(params.SearchText, params, FFoundWorksheet, FFoundRow, FFoundCol);
end else
begin
if (Sender = BtnSearchBack) then
Include(params.Options, soBackward) else
Exclude(params.Options, soBackward);
// User may select a different worksheet/different cell to continue search!
FFoundWorksheet := FWorkbook.ActiveWorksheet;
FFoundRow := FFoundWorksheet.ActiveCellRow;
FFoundCol := FFoundWorksheet.ActiveCellCol;
found := FSearchEngine.FindNext(params.SearchText, params, FFoundWorksheet, FFoundRow, FFoundCol);
end;
if FFoundCell <> nil then
begin
startsheet := TsWorksheet(FFoundCell^.Worksheet);
r := FFoundCell^.Row;
c := FFoundCell^.Col;
end;
cell := nil;
while FindStartCell(params, startsheet, r, c) and (cell = nil) do
begin
cell := startsheet.Search(params.SearchText, params.Options, r, c);
if (cell <> nil) then
begin
FWorkbook.SelectWorksheet(startsheet);
startsheet.SelectCell(cell^.Row, cell^.Col);
if Assigned(FOnFound) then FOnFound(Self, cell);
FFoundCell := cell;
break;
end;
// not found --> go to next sheet
case params.Source of
spsWorksheet:
break;
spsWorkbook:
begin
sheetIdx := FWorkbook.GetWorksheetIndex(startsheet);
if backward then
begin
if (sheetIdx = 0) then exit;
startsheet := FWorkbook.GetWorksheetByIndex(sheetIdx-1);
r := startsheet.GetLastRowIndex;
c := startsheet.GetLastColIndex;
end else
begin
if (sheetIdx = FWorkbook.GetWorksheetCount-1) then exit;
startsheet := FWorkbook.GetWorksheetByIndex(sheetIdx+1);
r := 0;
c := 0;
end;
end;
end;
end;
if Assigned(FOnFound) then
FOnFound(self, found, FFoundWorksheet, FFoundRow, FFoundCol);
BtnSearchBack.Visible := true;
BtnSearch.Caption := 'Next';
end;
procedure TSearchForm.SetParams(const AValue: TsSearchParams);
begin
CbSearchText.Text := Avalue.SearchText;
CgSearchOptions.Checked[COMPARE_ENTIRE_CELL] := (soCompareEntireCell in AValue.Options);
CgSearchOptions.Checked[MATCH_CASE] := (soMatchCase in AValue.Options);
CgSearchOptions.Checked[REGULAR_EXPRESSION] := (soRegularExpr in Avalue.Options);
CgSearchOptions.Checked[SEARCH_ALONG_ROWS] := (soAlongRows in AValue.Options);
CgSearchOptions.Checked[CONTINUE_AT_START_END] := (soWrapDocument in Avalue.Options);
RgSearchWithin.ItemIndex := ord(AValue.Within);
RgSearchStart.ItemIndex := ord(soEntireDocument in AValue.Options);
end;
end.

View File

@ -450,11 +450,6 @@ type
function GetSelectionCount: Integer;
procedure SetSelection(const ASelection: TsCellRangeArray);
// Searching
function Search(ASearchText: String; AOptions: TsSearchOptions;
AStartRow: Cardinal = UNASSIGNED_ROW_COL_INDEX;
AStartCol: Cardinal = UNASSIGNED_ROW_COL_INDEX): PCell;
// Comments
function FindComment(ACell: PCell): PsComment;
function HasComment(ACell: PCell): Boolean;
@ -617,6 +612,7 @@ type
FFileName: String;
FLockCount: Integer;
FLog: TStringList;
FSearchEngine: TObject;
{ Setter/Getter }
function GetErrorMsg: String;
@ -733,12 +729,13 @@ type
ABigEndian: Boolean = false);
function UsesColor(AColorIndex: TsColor): Boolean;
*)
(*
{ Searching }
function Search(ASearchText: String; AOptions: TsSearchOptions;
AStartSheet: TsWorksheet = nil; AStartRow: Cardinal = UNASSIGNED_ROW_COL_INDEX;
AStartCol: Cardinal = UNASSIGNED_ROW_COL_INDEX): PCell;
function SearchFirst(ASearchText: String; AParams: TsSearchParams;
out AWorksheet: TsWorksheet; out ARow, ACol: Cardinal): Boolean;
function SearchNext(out AWorksheet: TsWorksheet;
out ARow, ACol: Cardinal): Boolean;
*)
{ Utilities }
procedure UpdateCaches;
@ -834,7 +831,7 @@ procedure CopyCellFormat(AFromCell, AToCell: PCell);
implementation
uses
Math, StrUtils, DateUtils, TypInfo, lazutf8, lazFileUtils, URIParser, RegExpr,
Math, StrUtils, DateUtils, TypInfo, lazutf8, lazFileUtils, URIParser,
fpsStrings, uvirtuallayer_ole,
fpsUtils, fpsHTMLUtils, fpsreaderwriter, fpsCurrency, fpsExprParser,
fpsNumFormatParser;
@ -3594,186 +3591,6 @@ begin
FSelection[i] := ASelection[i];
end;
{@@ ----------------------------------------------------------------------------
Searches the cell containing a specified text. The search begins with the
cell "AStartCell". A set of options is respected. Returns a pointer to the
first cell meeting the criteria.
-------------------------------------------------------------------------------}
function TsWorksheet.Search(ASearchText: String; AOptions: TsSearchOptions;
AStartRow: Cardinal = UNASSIGNED_ROW_COL_INDEX;
AStartCol: Cardinal = UNASSIGNED_ROW_COL_INDEX): PCell;
var
regex: TRegExpr;
cell, startCell: PCell;
r, c: Cardinal;
firstR, firstC, lastR, lastC: Cardinal;
function CellMatches(ACell: PCell): boolean;
var
txt: String;
begin
txt := ReadAsText(ACell);
if (soRegularExpr in AOptions) then
Result := regex.Exec(txt)
else
if (soIgnoreCase in AOptions) then
txt := UTF8Lowercase(txt);
if (soCompareFullCell in AOptions) then
exit(txt = ASearchText);
if UTF8Pos(ASearchText, txt) > 0 then
exit(true);
Result := false;
end;
begin
Result := nil;
regex := nil;
firstR := 0;
firstC := 0;
lastR := GetLastRowIndex;
lastC := GetLastColIndex;
// Find first occupied cell to start with
if (soBackward in AOptions) then
begin
if AStartRow = UNASSIGNED_ROW_COL_INDEX then AStartRow := lastR;
if AStartCol = UNASSIGNED_ROW_COL_INDEX then AStartCol := lastC;
end else
begin
if AStartRow = UNASSIGNED_ROW_COL_INDEX then AStartRow := firstR;
if AStartCol = UNASSIGNED_ROW_COL_INDEX then AStartCol := firstC;
end;
startcell := FindCell(AStartRow, AStartCol);
if startcell = nil then
// Backward search along rows
if (AOptions * [soBackward, soAlongRows] = [soBackward, soAlongRows]) then
begin
startcell := FindPrevCellInRow(AStartRow, AStartCol);
// Not found in this row? Go to previous row
while (startcell = nil) and (AStartRow > 0) do begin
AStartCol := lastC;
dec(AStartRow);
startcell := FindPrevCellInRow(AStartRow, AStartCol);
end;
end
else
// Backward search along columns
if (AOptions * [soBackward, soAlongRows] = [soBackward]) then
begin
startcell := FindPrevCellInCol(AStartRow, AStartCol);
// not found in this column? Go to previous column.
while (startcell = nil) and (AStartcol > 0) do begin
AStartRow := lastR;
dec(AStartCol);
startcell := FindPrevCellInCol(AStartRow, AStartCol);
end;
end
else
// Forward search along rows
if (AOptions * [soBackward, soAlongRows] = [soAlongRows]) then
begin
startcell := FindNextCellInRow(AStartRow, AStartCol);
// Not found in this row? Proceed to next row
while (startcell = nil) and (AStartRow <= lastR) do begin
AStartCol := firstC;
inc(AStartRow);
startcell := FindNextCellInRow(AStartRow, AStartCol);
end;
end
else
// Forward search along columns
if (AOptions * [soBackward, soAlongRows] = []) then
begin
startCell := FindNextCellInCol(AStartRow, AStartCol);
// Not found in this column? Proceed to next column
while (startcell = nil) and (AStartCol <= lastC) do begin
AStartRow := firstR;
inc(AStartCol);
startcell := FindNextCellinCol(AStartRow, AStartCol);
end;
end;
// Still no occupied cell found for starting? Nothing to do...
if startcell = nil then
exit;
// Iterate through cells in order defined by the search options
try
if soRegularExpr in AOptions then
begin
regex := TRegExpr.Create;
regex.Expression := ASearchText
end else
if soIgnoreCase in AOptions then
ASearchText := UTF8Lowercase(ASearchText);
// Perform backward search along rows
if (AOptions * [soBackward, soAlongRows] = [soBackward, soAlongRows]) then
begin
r := startCell^.Row;
for cell in Cells.GetReverseRowEnumerator(r, startCell^.Col) do
if CellMatches(cell) then exit(cell);
if r = 0 then
exit;
while r > 0 do begin
dec(r);
for cell in Cells.GetReverseRowEnumerator(r) do
if CellMatches(cell) then exit(cell);
end;
end
else
// Perform forward search along rows
if (AOptions * [soBackward, soAlongRows] = [soAlongRows]) then
begin
r := startCell^.Row;
for cell in Cells.GetRowEnumerator(r, startCell^.Col) do
if CellMatches(cell) then exit(cell);
if r = lastR then
exit;
while (r < lastR) do
begin
inc(r);
for cell in Cells.GetRowEnumerator(r) do
if CellMatches(cell) then exit(cell);
end;
end
else
// Perform backward search along columns
if (AOptions * [soBackward, soAlongRows] = [soBackward]) then
begin
c := startCell^.Col;
for cell in Cells.GetReverseColEnumerator(c, 0, startCell^.Row) do
if CellMatches(cell) then exit(cell);
if c = 0 then
exit;
while (c > 0) do
begin
dec(c);
for cell in Cells.GetReverseColEnumerator(c) do
if CellMatches(cell) then exit(cell);
end;
end
else
// Perform forward search along columns
if (AOptions * [soBackward, soAlongRows] = []) then
begin
c := startCell^.Col;
for cell in Cells.GetColEnumerator(c, startCell^.Row) do
if CellMatches(cell) then exit(cell);
if c = lastC then
exit;
while (c < lastC) do
begin
inc(c);
for cell in Cells.GetColEnumerator(c) do
if CellMatches(cell) then exit(cell);
end;
end;
finally
if regex <> nil then regex.Free;
end;
end;
{@@ ----------------------------------------------------------------------------
Helper method to update internal caching variables
-------------------------------------------------------------------------------}
@ -6513,66 +6330,37 @@ begin
Unused(arg);
TsWorksheet(data).Free;
end;
(*
{@@ ----------------------------------------------------------------------------
Searches the entire workbook for the first cell (after AStartCell) containing
a specified text.
Searches the first cell matching the ASearchText according to the
specified AParams.
Use SearchNext for subsequent calls for the next occurances.
The function result is TRUE if the search text has been found. In this case
AWorksheet, ARow and ACol specify the cell containing the search text.
-------------------------------------------------------------------------------}
function TsWorkbook.Search(ASearchText: String; AOptions: TsSearchOptions;
AStartSheet: TsWorksheet = nil; AStartRow: Cardinal = UNASSIGNED_ROW_COL_INDEX;
AStartCol: Cardinal = UNASSIGNED_ROW_COL_INDEX): PCell;
var
i, idxSheet: Integer;
sheet: TsWorksheet;
function TsWorkbook.SearchFirst(ASearchText: String; AParams: TsSearchParams;
out AWorksheet: TsWorksheet; out ARow, ACol: Cardinal): Boolean;
begin
// Setup missing default parameters
if soBackward in AOptions then
begin
if (AStartRow = UNASSIGNED_ROW_COL_INDEX) and (AStartCol = UNASSIGNED_ROW_COL_INDEX) and (AStartSheet = nil)
then AStartsheet := GetWorksheetByIndex(GetWorksheetCount-1);
if AStartRow = UNASSIGNED_ROW_COL_INDEX then
AStartRow := AStartsheet.GetLastRowIndex;
if AStartCol = UNASSIGNED_ROW_COL_INDEX then
AStartCol := AStartsheet.GetLastColIndex;
end else
begin
if (AStartRow = UNASSIGNED_ROW_COL_INDEX) and (AStartCol = UNASSIGNED_ROW_COL_INDEX) and (AStartSheet = nil)
then AStartsheet := GetWorksheetByIndex(0);
if (AStartRow = UNASSIGNED_ROW_COL_INDEX) then
AStartRow := AStartsheet.GetFirstRowIndex;
if (AStartCol = UNASSIGNED_ROW_COL_INDEX) then
AStartCol := AStartsheet.GetFirstColIndex;
end;
if AStartSheet = nil then
AStartSheet := ActiveWorksheet;
// Search this worksheet
Result := AStartSheet.Search(ASearchText, AOptions, AStartRow, AStartCol);
if Result <> nil then
exit;
// If not found continue with other sheets in requested order...
idxSheet := GetWorksheetIndex(AStartSheet);
if (soBackward in AOptions) then
// ... backward
for i := idxSheet - 1 downto 0 do
begin
sheet := GetWorksheetByIndex(i);
Result := sheet.Search(ASearchText, AOptions);
if Result <> nil then
exit;
end
else
// ... forward
for i := idxSheet + 1 to GetWorksheetCount-1 do
begin
sheet := GetWorksheetByIndex(i);
Result := sheet.Search(ASearchText, AOptions);
if Result <> nil then
exit;
end;
FreeAndNil(FSearchEngine);
FSearchEngine := TsSearchEngine.Create(self);
with (FSearchEngine as TsSearchEngine) do
Result := FindFirst(ASearchText, AParams, AWorksheet, ARow, ACol);
end;
{@@ ----------------------------------------------------------------------------
Searches the next cell matching the text and params specified a the preceding
call to SearchFirst.
The function result is TRUE if the search text has been found. In this case
AWorksheet, ARow and ACol specify the cell containing the search text.
-------------------------------------------------------------------------------}
function TsWorkbook.SearchNext(out AWorksheet: TsWorksheet;
out ARow, ACol: Cardinal): Boolean;
begin
if FSearchEngine = nil then
Result := false else
Result := (FSearchEngine as TsSearchEngine).FindNext(AWorksheet, ARow, ACol);
end;
*)
{@@ ----------------------------------------------------------------------------
Helper method to update internal caching variables
-------------------------------------------------------------------------------}
@ -6628,6 +6416,8 @@ begin
FFontList.Free;
FLog.Free;
FreeAndNil(FSearchEngine);
inherited Destroy;
end;

View File

@ -0,0 +1,435 @@
unit fpsSearch;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, RegExpr, fpstypes, fpspreadsheet;
type
TsSearchEngine = class
private
FWorkbook: TsWorkbook;
FSearchText: String;
FParams: TsSearchParams;
FCurrSel: Integer;
FRegEx: TRegExpr;
protected
function ExecSearch(var AWorksheet: TsWorksheet;
var ARow, ACol: Cardinal): Boolean;
procedure GotoFirst(out AWorksheet: TsWorksheet; out ARow, ACol: Cardinal);
procedure GotoLast(out AWorksheet: TsWorksheet; out ARow, ACol: Cardinal);
function GotoNext(var AWorksheet: TsWorksheet;
var ARow, ACol: Cardinal): Boolean;
function GotoNextInWorksheet(AWorksheet: TsWorksheet;
var ARow, ACol: Cardinal): Boolean;
function GotoPrev(var AWorksheet: TsWorksheet;
var ARow, ACol: Cardinal): Boolean;
function GotoPrevInWorksheet(AWorksheet: TsWorksheet;
var ARow, ACol: Cardinal): Boolean;
function Matches(AWorksheet: TsWorksheet; ARow, ACol: Cardinal): Boolean;
procedure PrepareSearchText(const ASearchText: String);
public
constructor Create(AWorkbook: TsWorkbook);
destructor Destroy; override;
function FindFirst(const ASearchText: String; const AParams: TsSearchParams;
out AWorksheet: TsWorksheet; out ARow, ACol: Cardinal): Boolean;
function FindNext(const ASearchText: String; const AParams: TsSearchParams;
var AWorksheet: TsWorksheet; var ARow, ACol: Cardinal): Boolean;
end;
implementation
uses
lazutf8;
constructor TsSearchEngine.Create(AWorkbook: TsWorkbook);
begin
inherited Create;
FWorkbook := AWorkbook;
end;
destructor TsSearchEngine.Destroy;
begin
FreeAndNil(FRegEx);
inherited Destroy;
end;
function TsSearchEngine.ExecSearch(var AWorksheet: TsWorksheet;
var ARow, ACol: Cardinal): Boolean;
var
complete: boolean;
r, c: LongInt;
sheet: TsWorksheet;
begin
sheet := AWorksheet;
r := ARow;
c := ACol;
complete := false;
while (not complete) and (not Matches(AWorksheet, ARow, ACol)) do
begin
if soBackward in FParams.Options then
complete := not GotoPrev(AWorkSheet, ARow, ACol) else
complete := not GotoNext(AWorkSheet, ARow, ACol);
// Avoid infinite loop if search phrase does not exist in document.
if (AWorksheet = sheet) and (ARow = r) and (ACol = c) then
complete := true;
end;
Result := not complete;
if Result then
begin
FWorkbook.SelectWorksheet(AWorksheet);
AWorksheet.SelectCell(ARow, ACol);
end else
begin
AWorksheet := nil;
ARow := UNASSIGNED_ROW_COL_INDEX;
ACol := UNASSIGNED_ROW_COL_INDEX;
end;
end;
function TsSearchEngine.FindFirst(const ASearchText: String;
const AParams: TsSearchParams; out AWorksheet: TsWorksheet;
out ARow, ACol: Cardinal): Boolean;
begin
FParams := AParams;
PrepareSearchText(ASearchText);
if soBackward in FParams.Options then
GotoLast(AWorksheet, ARow, ACol) else
GotoFirst(AWorksheet, ARow, ACol);
Result := ExecSearch(AWorksheet, ARow, ACol);
end;
function TsSearchEngine.FindNext(const ASearchText: String;
const AParams: TsSearchParams; var AWorksheet: TsWorksheet;
var ARow, ACol: Cardinal): Boolean;
begin
FParams := AParams;
PrepareSearchText(ASearchText);
if soBackward in FParams.Options then
GotoPrev(AWorksheet, ARow, ACol) else
GotoNext(AWorksheet, ARow, ACol);
Result := ExecSearch(AWorksheet, ARow, ACol);
end;
procedure TsSearchEngine.GotoFirst(out AWorksheet: TsWorksheet;
out ARow, ACol: Cardinal);
begin
if soEntireDocument in FParams.Options then
// Search entire document forward from start
case FParams.Within of
swWorkbook :
begin
AWorksheet := FWorkbook.GetWorksheetByIndex(0);
ARow := 0;
ACol := 0;
end;
swWorksheet:
begin
AWorksheet := FWorkbook.ActiveWorksheet;
ARow := 0;
ACol := 0;
end;
swColumn:
begin
AWorksheet := FWorkbook.ActiveWorksheet;
ARow := 0;
ACol := AWorksheet.ActiveCellCol;
end;
swRow:
begin
AWorksheet := FWorkbook.ActiveWorksheet;
ARow := AWorksheet.ActiveCellRow;
ACol := 0;
end;
end
else
begin
// Search starts at active cell
AWorksheet := FWorkbook.ActiveWorksheet;
ARow := AWorksheet.ActiveCellRow;
ACol := AWorksheet.ActiveCellCol;
end;
end;
procedure TsSearchEngine.GotoLast(out AWorksheet: TsWorksheet;
out ARow, ACol: Cardinal);
var
cell: PCell;
sel: TsCellRangeArray;
begin
if soEntireDocument in FParams.Options then
// Search entire document backward from end
case FParams.Within of
swWorkbook :
begin
AWorksheet := FWorkbook.GetWorksheetByIndex(FWorkbook.GetWorksheetCount-1);
ARow := AWorksheet.GetLastRowIndex;
ACol := AWorksheet.GetLastColIndex;
end;
swWorksheet:
begin
AWorksheet := FWorkbook.ActiveWorksheet;
ARow := AWorksheet.GetLastRowIndex;
ACol := AWorksheet.GetLastColIndex;
end;
swColumn:
begin
AWorksheet := FWorkbook.ActiveWorksheet;
ARow := AWorksheet.GetLastRowIndex;
ACol := AWorksheet.ActiveCellCol;
end;
swRow:
begin
AWorksheet := FWorkbook.ActiveWorksheet;
ARow := AWorksheet.ActiveCellRow;
ACol := AWorksheet.GetLastColIndex;
end;
end
else
begin
// Search starts at active cell
AWorksheet := FWorkbook.ActiveWorksheet;
ARow := AWorksheet.ActiveCellRow;
ACol := AWorksheet.ActiveCellCol;
end;
end;
function TsSearchEngine.GotoNext(var AWorksheet: TsWorksheet;
var ARow, ACol: Cardinal): Boolean;
var
idx: Integer;
sel: TsCellRangeArray;
begin
Result := true;
if GotoNextInWorksheet(AWorksheet, ARow, ACol) then
exit;
case FParams.Within of
swWorkbook:
begin
// Need to go to next sheet
idx := FWorkbook.GetWorksheetIndex(AWorksheet) + 1;
if idx < FWorkbook.GetWorksheetCount then
begin
AWorksheet := FWorkbook.GetWorksheetByIndex(idx);
ARow := 0;
ACol := 0;
exit;
end;
// Continue search with first worksheet
if (soWrapDocument in FParams.Options) then
begin
AWorksheet := FWorkbook.GetWorksheetByIndex(0);
ARow := 0;
ACol := 0;
exit;
end;
end;
swWorksheet:
if soWrapDocument in FParams.Options then begin
ARow := 0;
ACol := 0;
exit;
end;
swColumn:
if soWrapDocument in FParams.Options then begin
ARow := 0;
ACol := AWorksheet.ActiveCellCol;
exit;
end;
swRow:
if soWrapDocument in FParams.Options then begin
ARow := AWorksheet.ActiveCellRow;
ACol := 0;
exit;
end;
end; // case
Result := false;
end;
function TsSearchEngine.GotoNextInWorksheet(AWorksheet: TsWorksheet;
var ARow, ACol: Cardinal): Boolean;
begin
Result := true;
if (soAlongRows in FParams.Options) or (FParams.Within = swRow) then
begin
inc(ACol);
if ACol <= AWorksheet.GetLastColIndex then
exit;
if (FParams.Within <> swRow) then
begin
ACol := 0;
inc(ARow);
if ARow <= AWorksheet.GetLastRowIndex then
exit;
end;
end else
if not (soAlongRows in FParams.Options) or (FParams.Within = swColumn) then
begin
inc(ARow);
if ARow <= AWorksheet.GetLastRowIndex then
exit;
if (FParams.Within <> swColumn) then
begin
ARow := 0;
inc(ACol);
if (ACol <= AWorksheet.GetLastColIndex) then
exit;
end;
end;
// We reached the last cell, there is no "next" cell in this sheet
Result := false;
end;
function TsSearchEngine.GotoPrev(var AWorksheet: TsWorksheet;
var ARow, ACol: Cardinal): Boolean;
var
idx: Integer;
sel: TsCellRangeArray;
begin
Result := true;
if GotoPrevInWorksheet(AWorksheet, ARow, ACol) then
exit;
case FParams.Within of
swWorkbook:
begin
// Need to go to previous sheet
idx := FWorkbook.GetWorksheetIndex(AWorksheet) - 1;
if idx >= 0 then
begin
AWorksheet := FWorkbook.GetWorksheetByIndex(idx);
ARow := AWorksheet.GetLastRowIndex;
ACol := AWorksheet.GetlastColIndex;
exit;
end;
if (soWrapDocument in FParams.Options) then
begin
AWorksheet := FWorkbook.GetWorksheetByIndex(FWorkbook.GetWorksheetCount-1);
ARow := AWorksheet.GetLastRowIndex;
ACol := AWorksheet.GetLastColIndex;
exit;
end;
end;
swWorksheet:
if soWrapDocument in FParams.Options then
begin
ARow := AWorksheet.GetLastRowIndex;
ACol := AWorksheet.GetLastColIndex;
exit;
end;
swColumn:
if soWrapDocument in FParams.Options then
begin
ARow := AWorksheet.GetLastRowIndex;
ACol := AWorksheet.ActiveCellCol;
exit;
end;
swRow:
if soWrapDocument in FParams.Options then
begin
ARow := AWorksheet.ActiveCellRow;
ACol := AWorksheet.GetLastColIndex;
exit;
end;
end; // case
Result := false;
end;
function TsSearchEngine.GotoPrevInWorksheet(AWorksheet: TsWorksheet;
var ARow, ACol: Cardinal): Boolean;
begin
Result := true;
if (soAlongRows in FParams.Options) or (FParams.Within = swRow) then
begin
if ACol > 0 then begin
dec(ACol);
exit;
end;
if (FParams.Within <> swRow) then
begin
ACol := AWorksheet.GetLastColIndex;
if ARow > 0 then
begin
dec(ARow);
exit;
end;
end;
end else
if not (soAlongRows in FParams.Options) or (FParams.Within = swColumn) then
begin
if ARow > 0 then begin
dec(ARow);
exit;
end;
if (FParams.Within <> swColumn) then
begin
ARow := AWorksheet.GetlastRowIndex;
if ACol > 0 then
begin
dec(ACol);
exit;
end;
end;
end;
// We reached the first cell, there is no "previous" cell
Result := false;
end;
function TsSearchEngine.Matches(AWorksheet: TsWorksheet; ARow, ACol: Cardinal): Boolean;
var
cell: PCell;
celltxt: String;
begin
cell := AWorksheet.FindCell(ARow, ACol);
if cell <> nil then
celltxt := AWorksheet.ReadAsText(cell) else
celltxt := '';
if soRegularExpr in FParams.Options then
Result := FRegEx.Exec(celltxt)
else
begin
if not (soMatchCase in FParams.Options) then
celltxt := UTF8Lowercase(celltxt);
if soCompareEntireCell in FParams.Options then
exit(celltxt = FSearchText);
if UTF8Pos(FSearchText, celltxt) > 0 then
exit(true);
Result := false;
end;
end;
procedure TsSearchEngine.PrepareSearchText(const ASearchText: String);
begin
if soRegularExpr in FParams.Options then
begin
FreeAndNil(FRegEx);
FRegEx := TRegExpr.Create;
FRegEx.Expression := ASearchText
end else
if (soMatchCase in FParams.Options) then
FSearchText := ASearchText else
FSearchText := UTF8Lowercase(ASearchText);
end;
end.

View File

@ -698,12 +698,6 @@ type
{@@ Pointer to a page layout record }
PsPageLayout = ^TsPageLayout;
{@@ Search option }
TsSearchOption = (soCompareFullCell, soIgnoreCase, soRegularExpr,
soBackward, soAlongRows);
TsSearchOptions = set of TsSearchOption;
const
{@@ Indexes to be used for the various headers and footers }
HEADER_FOOTER_INDEX_FIRST = 0;
@ -712,6 +706,25 @@ const
HEADER_FOOTER_INDEX_ALL = 1;
type
{@@ Search option }
TsSearchOption = (soCompareEntireCell, soMatchCase, soRegularExpr, soAlongRows,
soBackward, soWrapDocument, soEntireDocument);
{@@ A set of search options }
TsSearchOptions = set of TsSearchOption;
{@@ Defines which part of document is scanned }
TsSearchWithin = (swWorkbook, swWorksheet, swColumn, swRow);
{@@ Search parameters }
TsSearchParams = record
SearchText: String;
Options: TsSearchOptions;
Within: TsSearchWithin;
end;
implementation
constructor TsFont.Create(AFontName: String; ASize: Single; AStyle: TsFontStyles;

View File

@ -30,7 +30,7 @@
This package is all you need if you don't want graphical components (like grids and charts)."/>
<License Value="LGPL with static linking exception. This is the same license as is used in the LCL (Lazarus Component Library)."/>
<Version Major="1" Minor="7"/>
<Files Count="38">
<Files Count="39">
<Item1>
<Filename Value="fpolestorage.pas"/>
<UnitName Value="fpolestorage"/>
@ -183,6 +183,10 @@ This package is all you need if you don't want graphical components (like grids
<Filename Value="fpscell.pas"/>
<UnitName Value="fpsCell"/>
</Item38>
<Item39>
<Filename Value="fpssearch.pas"/>
<UnitName Value="fpsSearch"/>
</Item39>
</Files>
<RequiredPkgs Count="2">
<Item1>

View File

@ -14,7 +14,7 @@ uses
fpolebasic, wikitable, fpsNumFormatParser, fpsfunc, fpsRPN, fpsStrings,
fpscsv, fpsCsvDocument, fpspatches, fpsTypes, xlsEscher, fpsReaderWriter,
fpsNumFormat, fpsclasses, fpsHeaderFooterParser, fpsPalette, fpsHTML,
fpsHTMLUtils, fpsCell;
fpsHTMLUtils, fpsCell, fpsSearch;
implementation