Author: Tomas Rutkauskas
I have a body of text. I want to allow the user to enter a string that could
contain wildcards (well, just the " * ") and search for it.
Answer:
Your first task is to split the paragraph into words (since i take it from your
description that the match has to be inside a word). The next is to match each word
to the mask. The following implementation is certainly not the fastest possible but
it should make the algorithm clear.
1
2 procedure SplitTextIntoWords(const S: string; words: TStringlist);
3 var
4 startpos, endpos: Integer;
5 begin
6 Assert(Assigned(words));
7 words.clear;
8 startpos := 1;
9 while startpos <= Length(S) do
10 begin
11 {skip non-letters }
12 while (startpos <= Length(S)) and not IsCharAlpha(S[startpos]) do
13 Inc(startpos);
14 if startpos <= Length(S) then
15 begin
16 {find next non-letter}
17 endpos := startpos + 1;
18 while (endpos <= Length(S)) and IsCharAlpha(S[endpos]) do
19 Inc(endpos);
20 words.add(Copy(S, startpos, endpos - startpos));
21 startpos := endpos + 1;
22 end;
23 end;
24 end;
25
26 function StringMatchesMask(S, mask: string; case_sensitive: Boolean): Boolean;
27 var
28 sIndex, maskIndex: Integer;
29 begin
30 if not case_sensitive then
31 begin
32 S := AnsiUpperCase(S);
33 mask := AnsiUpperCase(mask);
34 end;
35 Result := True; {blatant optimism}
36 sIndex := 1;
37 maskIndex := 1;
38 while (sIndex <= Length(S)) and (maskIndex <= Length(mask)) do
39 begin
40 case mask[maskIndex] of
41 '?':
42 begin
43 {matches any character}
44 Inc(sIndex);
45 Inc(maskIndex);
46 end;
47 '*':
48 begin
49 {matches 0 or more characters, so need to check for next character in
50 mask}
51 Inc(maskIndex);
52 if maskIndex > Length(mask) then
53 { * at end matches rest of string}
54 Exit
55 else if mask[maskindex] in ['*', '?'] then
56 raise Exception.Create('Invalid mask');
57 {look for mask character in S}
58 while (sIndex <= Length(S)) and (S[sIndex] <> mask[maskIndex]) do
59 Inc(sIndex);
60 if sIndex > Length(S) then
61 begin
62 {character not found, no match}
63 Result := false;
64 Exit;
65 end;
66 end;
67 else
68 if S[sIndex] = mask[maskIndex] then
69 begin
70 Inc(sIndex);
71 Inc(maskIndex);
72 end
73 else
74 begin
75 {no match}
76 Result := False;
77 Exit;
78 end;
79 end;
80 end;
81 {if we have reached the end of both S and mask we have a complete match,
82 otherwise we only have a partial match}
83 if (sIndex <= Length(S)) or (maskIndex <= Length(mask)) then
84 Result := false;
85 end;
86
87 procedure FindMatchingWords(const S, mask: string; case_sensitive: Boolean;
88 matches: TStringlist);
89 var
90 words: TStringlist;
91 i: Integer;
92 begin
93 Assert(Assigned(matches));
94 words := TStringlist.Create;
95 try
96 SplitTextIntoWords(S, words);
97 matches.clear;
98 for i := 0 to words.count - 1 do
99 begin
100 if StringMatchesMask(words[i], mask, case_sensitive) then
101 matches.Add(words[i]);
102 end;
103 finally
104 words.free;
105 end;
106 end;
107
108 {Form has one memo for the text to check, one edit for the mask, one checkbox
109 (check = case sensitive), one listbox for the results, one button }
110
111 procedure TForm1.Button1Click(Sender: TObject);
112 begin
113 FindMatchingWords(memo1.text, edit1.text, checkbox1.checked, listbox1.items);
114 end;
|