Delphi 提供的通配符匹配函数 TMask.Matches 有些问题:如果通配符字符串太长,比如进入 hotmail 邮箱时的地址有大概250个字符。这会导致 TMask.Matches 函数出错,并导致整个程序崩溃。我在网上找了一些不同的实现,并且做了性能比较。现在我优化过的版本分享出来。
type
TMaskMatchResult = record
Offset: Integer;
PatternLength: Integer;
LeadingFlexibleLength: Integer;
end;
function HasWildcardsA(const S: string): Boolean;
var
I: Integer;
begin
Result := False;
for I := 1 to Length(S) do
if S[I] in ['*', '?'] then
begin
Result := True;
Exit;
end;
end;
// Indicates whether a string matches a wildcard pattern.
function MatchMaskA(const APattern, ASource: string): Boolean;
var
I: Integer;
begin
if APattern = '' then
Result := ASource = ''
else if ASource = '' then
begin
for I := 1 to Length(APattern) do
if APattern[I] <> '*' then
begin
Result := False;
Exit;
end;
Result := True;
end else
Result := MatchMaskExA(APattern, ASource).PatternLength = Length(ASource);
end;
// Indicates whether a string matches a wildcard pattern.
// When not matched, Result.PatternLength = 0.
function MatchMaskExA(const APattern, ASource: string; Offset: Integer = 1;
ScanPattern: Boolean = True): TMaskMatchResult;
var
StringPtr, StringRes, PatternPtr, PatternRes: PChar;
CountingFlexibleLength: Boolean;
I: Integer;
begin
FillChar(Result, SizeOf(Result), #0);
if Offset < 1 then
Offset := 1;
if ScanPattern and not HasWildcardsA(APattern) then
begin
if Offset = PosEx{ANSI only}(APattern, ASource, Offset) then
begin
Result.PatternLength := Length(APattern){! >0};
Result.Offset := Offset;
end;
Exit;
end;
StringPtr := PChar(ASource);
if Offset > 1 then
Inc(StringPtr, Offset - 1);
PatternPtr := PChar(APattern);
StringRes := nil;
PatternRes := nil;
CountingFlexibleLength := (Length(APattern) > 0) and (APattern[1] = '*');
repeat
repeat // ohne vorangegangenes "*"
case PatternPtr^ of
#0 : begin
Result.PatternLength := Length(ASource) + (1 - Offset) - Length(StringPtr);
if Result.PatternLength > 0 then
Result.Offset := Offset;
Exit;
end;
'*': begin
Inc(PatternPtr);
PatternRes := PatternPtr;
Break;
end;
'?': begin
if StringPtr^ = #0 then
Exit;
Inc(StringPtr);
Inc(PatternPtr);
end;
else begin
if StringPtr^ = #0 then
Exit;
if StringPtr^ <> PatternPtr^ then
begin
if (StringRes = nil) or (PatternRes = nil) then
Exit;
StringPtr := StringRes;
PatternPtr := PatternRes;
Break;
end else
begin
Inc(StringPtr);
Inc(PatternPtr);
end;
end;
end;
until False;
repeat // mit vorangegangenem "*"
case PatternPtr^ of
#0 : begin
Result.PatternLength := Length(ASource) + (1 - Offset);
if AnsiLastChar(APattern) <> '*' then
Dec(Result.PatternLength, Length(StringPtr));
if Result.PatternLength > 0 then
Result.Offset := Offset;
if CountingFlexibleLength then
Result.LeadingFlexibleLength := Result.PatternLength;
Exit;
end;
'*': begin
Inc(PatternPtr);
PatternRes := PatternPtr;
end;
'?': begin
if StringPtr^ = #0 then
Exit;
Inc(StringPtr);
Inc(PatternPtr);
end;
else begin
repeat
if StringPtr^ = #0 then
Exit;
if StringPtr^ = PatternPtr^ then
Break;
Inc(StringPtr);
until False;
Inc(StringPtr);
StringRes := StringPtr;
Inc(PatternPtr);
Break;
end;
end;
until False;
if CountingFlexibleLength then
begin
Result.LeadingFlexibleLength := Length(ASource) - Offset - Length(StringPtr);
CountingFlexibleLength := False;
end;
until False;
end;
TMaskMatchResult = record
Offset: Integer;
PatternLength: Integer;
LeadingFlexibleLength: Integer;
end;
function HasWildcardsA(const S: string): Boolean;
var
I: Integer;
begin
Result := False;
for I := 1 to Length(S) do
if S[I] in ['*', '?'] then
begin
Result := True;
Exit;
end;
end;
// Indicates whether a string matches a wildcard pattern.
function MatchMaskA(const APattern, ASource: string): Boolean;
var
I: Integer;
begin
if APattern = '' then
Result := ASource = ''
else if ASource = '' then
begin
for I := 1 to Length(APattern) do
if APattern[I] <> '*' then
begin
Result := False;
Exit;
end;
Result := True;
end else
Result := MatchMaskExA(APattern, ASource).PatternLength = Length(ASource);
end;
// Indicates whether a string matches a wildcard pattern.
// When not matched, Result.PatternLength = 0.
function MatchMaskExA(const APattern, ASource: string; Offset: Integer = 1;
ScanPattern: Boolean = True): TMaskMatchResult;
var
StringPtr, StringRes, PatternPtr, PatternRes: PChar;
CountingFlexibleLength: Boolean;
I: Integer;
begin
FillChar(Result, SizeOf(Result), #0);
if Offset < 1 then
Offset := 1;
if ScanPattern and not HasWildcardsA(APattern) then
begin
if Offset = PosEx{ANSI only}(APattern, ASource, Offset) then
begin
Result.PatternLength := Length(APattern){! >0};
Result.Offset := Offset;
end;
Exit;
end;
StringPtr := PChar(ASource);
if Offset > 1 then
Inc(StringPtr, Offset - 1);
PatternPtr := PChar(APattern);
StringRes := nil;
PatternRes := nil;
CountingFlexibleLength := (Length(APattern) > 0) and (APattern[1] = '*');
repeat
repeat // ohne vorangegangenes "*"
case PatternPtr^ of
#0 : begin
Result.PatternLength := Length(ASource) + (1 - Offset) - Length(StringPtr);
if Result.PatternLength > 0 then
Result.Offset := Offset;
Exit;
end;
'*': begin
Inc(PatternPtr);
PatternRes := PatternPtr;
Break;
end;
'?': begin
if StringPtr^ = #0 then
Exit;
Inc(StringPtr);
Inc(PatternPtr);
end;
else begin
if StringPtr^ = #0 then
Exit;
if StringPtr^ <> PatternPtr^ then
begin
if (StringRes = nil) or (PatternRes = nil) then
Exit;
StringPtr := StringRes;
PatternPtr := PatternRes;
Break;
end else
begin
Inc(StringPtr);
Inc(PatternPtr);
end;
end;
end;
until False;
repeat // mit vorangegangenem "*"
case PatternPtr^ of
#0 : begin
Result.PatternLength := Length(ASource) + (1 - Offset);
if AnsiLastChar(APattern) <> '*' then
Dec(Result.PatternLength, Length(StringPtr));
if Result.PatternLength > 0 then
Result.Offset := Offset;
if CountingFlexibleLength then
Result.LeadingFlexibleLength := Result.PatternLength;
Exit;
end;
'*': begin
Inc(PatternPtr);
PatternRes := PatternPtr;
end;
'?': begin
if StringPtr^ = #0 then
Exit;
Inc(StringPtr);
Inc(PatternPtr);
end;
else begin
repeat
if StringPtr^ = #0 then
Exit;
if StringPtr^ = PatternPtr^ then
Break;
Inc(StringPtr);
until False;
Inc(StringPtr);
StringRes := StringPtr;
Inc(PatternPtr);
Break;
end;
end;
until False;
if CountingFlexibleLength then
begin
Result.LeadingFlexibleLength := Length(ASource) - Offset - Length(StringPtr);
CountingFlexibleLength := False;
end;
until False;
end;