{
  Basic pattern matching.  Supports '*' and '?' only.

  This code is based on a unit by Markus Stephany (mirbir.st@t-online.de) that
  I found on DSP (http://sunsite.icm.edu.pl/delphi/).  Please DO NOT email
  Markus about any problems you may find in it.  All problems with it should be
  reported to me, since I'm the last one who mucked with it.

  The original did a bunch of stuff that I didn't need (like searching inside
  of files), didn't do some stuff that I did need (like handling spaces in the
  filename), and had a few bugs, too.  In order to help me find the bugs more
  quickly, I stripped out the stuff I didn't need, renamed things to be a little
  more clear, and generally tried to clean up the mess that comes from stripping
  out stuff you don't need.  :)
}
unit MaskSearch;

interface

uses
  Classes, SysUtils;


procedure BuildMask(Str: string; MaskList: TStringList);

function FileMatches(AFile: string; MaskList: TStringList): boolean;


implementation


// fills the grep_list with the parts of 's' (divided by ';')
procedure BuildMask(Str: string; MaskList: TStringList);
var
  ct: integer;
begin
  MaskList.clear;
  MaskList.sorted := false;
  if Str = '' then
  begin
    MaskList.add('*');
  end else begin
    if Str[length(Str)] <> ';' then
      Str := Str + ';';

    // divide the string
    ct := Pos(';', Str);
    while ct > 0 do
    begin
      MaskList.Add(AnsiLowerCase(Copy(Str, 1, ct-1)));
      Delete(Str, 1, ct);
      ct := Pos(';', Str);
    end;

    MaskList.sorted := TRUE;
    MaskList.duplicates := dupIgnore;
  end;
end;

// tests whether the string 'Str' fits to the search mask in 'Mask'
function SimpleGrep(Str, Mask: string):boolean;
var
  sr, s2: string;
  ps1,ps2,ps3: integer;
  DontCare: boolean;
  OneChar: char;
  TmpList: TStringList;
begin
  if (Mask = '*') or // fits always
     ((Mask = '*.*') and (Pos('.', Str) > 0)) then // always fits, too
    Result := TRUE
  else begin
    if (Pos('*', Mask) = 0) and (Pos('?', Mask) = 0) and (Mask = Str) then
      // searched text was found (searchstring IN text)
      Result := TRUE
    else begin
      Result := FALSE;
      if Mask = '' then
        exit;

      TmpList := TStringList.Create;
      try
        // divide partial strings ('?','*' or text) to TmpList
        repeat
          OneChar := Mask[1];
          if (OneChar in ['*', '?']) then
          begin
            TmpList.Add(OneChar);
            Delete(Mask, 1, 1);
          end else begin
            ps1 := Pos('?', Mask);
            if ps1 = 0 then
              ps1 := MaxInt;
            ps2 := Pos('*', Mask);
            if ps2 = 0 then
              ps2 := MaxInt;
            if ps2 > ps1 then
              ps2 := ps1;

            TmpList.Add(Copy(Mask, 1, ps2-1));
            Delete(Mask, 1, ps2-1);
          end;
        until Mask = '';

        // now compare the string with the partial search masks
        DontCare := FALSE;
        ps2 := 1;
        if TmpList.Count > 0 then
        begin
          for ps1 := 0 to pred(TmpList.Count) do
          begin
            sr := TmpList[ps1];
            if sr = '?' then
            begin
              inc(ps2);
              if ps2 > length(Str)+1 then
                exit;
            end else begin
              if sr = '*' then
                DontCare := TRUE
              else begin
                if DontCare then
                begin
                  if ps1 = pred(TmpList.Count) then
                  begin
                    s2 := Copy(Str, ps2, maxint);
                    ps2 := length(Str); // just something to make the thing fail
                    if Length(s2) >= Length(SR) then
                      if sr = Copy(s2, Length(s2)-Length(SR)+1, MaxInt) then
                        ps2 := length(Str) + 1;
                  end else begin
                    ps3:= Pos(sr, Copy(Str, ps2, maxint));
                    if ps3 = 0 then
                      exit;
                    ps2 := ps2 + ps3 + length(sr) - 1;
                  end;
                  DontCare := FALSE;
                end else begin
                  if Copy(Str, ps2, length(sr)) <> sr then
                    exit;
                  ps2 := ps2 + length(sr);
                end;
              end;
            end;
          end;
        end;

        if (not DontCare) and (ps2 <> length(Str)+1) then
          Result := FALSE
        else
          Result := TRUE;
      finally
        TmpList.free;
      end;
    end;
  end;
end;

// tests whether the filename fits the search masks in MaskList
function FileMatches(AFile: string; MaskList: TStringList): boolean;
var
  ct: integer;
begin
  AFile := AnsiLowerCase(AFile);
  if (MaskList = NIL) or (MaskList.Count = 0) then
    Result := TRUE // if no search AFileing, the always return TRUE
  else begin
    if Pos('.', AFile) = 0 then
      AFile := AFile + '.'; // '.' is implied for filenames

    Result := FALSE;
    // compare with the whole MaskList until one fits
    for ct := 0 to Pred(MaskList.Count) do
    begin
      if SimpleGrep(AFile, MaskList[ct]) then
      begin
        Result := TRUE;
        break;
      end;
    end;
  end;
end;


end.
