|
@@ -1,99 +1,417 @@
|
|
{
|
|
{
|
|
- /***************************************************************************
|
|
|
|
- fpmasks.pas
|
|
|
|
- ---------
|
|
|
|
-
|
|
|
|
- Moved here from LCL
|
|
|
|
- ***************************************************************************/
|
|
|
|
-
|
|
|
|
- *****************************************************************************
|
|
|
|
- * *
|
|
|
|
- * This file is part of the Lazarus Component Library (LCL) *
|
|
|
|
- * *
|
|
|
|
- * See the file COPYING.modifiedLGPL.txt, included in this distribution, *
|
|
|
|
- * for details about the copyright. *
|
|
|
|
- * *
|
|
|
|
- * This program is distributed in the hope that it will be useful, *
|
|
|
|
- * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
|
|
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
|
|
|
- * *
|
|
|
|
- *****************************************************************************
|
|
|
|
|
|
+* ***************************************************************************
|
|
|
|
+* This file is an adapted version of the masks unit which is part of part *
|
|
|
|
+* of the LazUtils package of Lazarus *
|
|
|
|
+* *
|
|
|
|
+* See the file COPYING.modifiedLGPL.txt, included in this distribution, *
|
|
|
|
+* for details about the license. *
|
|
|
|
+* *
|
|
|
|
+* This program is distributed in the hope that it will be useful, *
|
|
|
|
+* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
|
|
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
|
|
|
|
+*****************************************************************************
|
|
|
|
+
|
|
|
|
+ Implement TMask and related classes.
|
|
|
|
+ It is fast and optimized, and fully supports Unicode. Also supports
|
|
|
|
+ DOS/Windows compatible mask which behaves differently from standard mask.
|
|
|
|
+
|
|
|
|
+ Original Author: José Mejuto
|
|
|
|
+ Adapted for fpc by Bart Broersma
|
|
}
|
|
}
|
|
unit FPMasks;
|
|
unit FPMasks;
|
|
|
|
|
|
{$mode objfpc}{$H+}
|
|
{$mode objfpc}{$H+}
|
|
|
|
+{.$define debug_maskcompiled}
|
|
|
|
+{.$define debug_anycharornone}
|
|
|
|
|
|
interface
|
|
interface
|
|
|
|
|
|
uses
|
|
uses
|
|
- // For Smart Linking: Do not use the LCL!
|
|
|
|
- Classes, SysUtils, Contnrs;
|
|
|
|
|
|
+ Classes, SysUtils, Contnrs
|
|
|
|
+ {$ifdef unix}, cwstring{$endif unix} //we need a widestring manager
|
|
|
|
+ ;
|
|
|
|
+
|
|
|
|
+type
|
|
|
|
+ { EMaskError }
|
|
|
|
+
|
|
|
|
+ EMaskError=class(EConvertError)
|
|
|
|
+ public
|
|
|
|
+ type
|
|
|
|
+ TMaskExceptionCode=(mecInternalError,
|
|
|
|
+ mecInvalidCharMask,
|
|
|
|
+ mecMissingClose,
|
|
|
|
+ mecIncompleteMask,
|
|
|
|
+ mecInvalidEscapeChar
|
|
|
|
+ //mecInvalidUTF8Sequence
|
|
|
|
+ );
|
|
|
|
+ protected
|
|
|
|
+ FCode: TMaskExceptionCode;
|
|
|
|
+ public
|
|
|
|
+ constructor Create(const msg: Utf8String; aCode: TMaskExceptionCode);
|
|
|
|
+ constructor CreateFmt(const msg: Utf8String; args: array of const; aCode: TMaskExceptionCode);
|
|
|
|
+ property Code: TMaskExceptionCode read FCode;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ TMaskOpCode=(mocAnyChar, //treat ? as a wildcard to match exactly one char
|
|
|
|
+ mocAnyCharOrNone, //treat [?] to match any char or the absence of a char
|
|
|
|
+ mocAnyText, //treat * as a wildcard to mach zero or any mumber of chars
|
|
|
|
+ mocRange, //treat [a-c] to match either 'a', 'b' or 'c'. '-' is always treated as a range indicator.
|
|
|
|
+ //to have a literal '-' in a range, you must escape it with EscapeChar (defaults to '\'): [+-\-] matches '+', ',', or '-'.
|
|
|
|
+ mocSet, //treat [a-c] to match either 'a', '-' or 'c'
|
|
|
|
+ mocNegateGroup, //treat [!a-c] to not match 'a', 'b', or 'c', but match any other char. Requires mocRange and/or mocSet
|
|
|
|
+ mocEscapeChar //treat EscapeChar (defaults to '\') to take the next char as a literal, so '\*' is treated as a literal '*'.
|
|
|
|
+ );
|
|
|
|
+ TMaskOpCodes=set of TMaskOpCode;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+const
|
|
|
|
+ AllMaskOpCodes=[mocAnyChar,
|
|
|
|
+ mocAnyCharOrNone,
|
|
|
|
+ mocAnyText,
|
|
|
|
+ mocRange,
|
|
|
|
+ mocSet,
|
|
|
|
+ mocNegateGroup,
|
|
|
|
+ mocEscapeChar];
|
|
|
|
+
|
|
|
|
|
|
|
|
+ // Match [x] literally, not as a range.
|
|
|
|
+ // Leave out mocAnyCharOrNone, mocRange and mocSet.
|
|
|
|
+ MaskOpCodesDisableRange=[mocAnyChar,
|
|
|
|
+ mocAnyText,
|
|
|
|
+ mocNegateGroup,
|
|
|
|
+ mocEscapeChar];
|
|
|
|
+
|
|
|
|
+ // Interpret [?] as literal question mark instead of 0..1 chars wildcard.
|
|
|
|
+ // Disable backslash escaping characters like "\?".
|
|
|
|
+ // Leave out mocAnyCharOrNone and mocEscapeChar
|
|
|
|
+ MaskOpCodesNoEscape=[mocAnyChar,
|
|
|
|
+ mocAnyText,
|
|
|
|
+ mocRange,
|
|
|
|
+ mocSet,
|
|
|
|
+ mocNegateGroup];
|
|
|
|
+
|
|
|
|
+ DefaultMaskOpCodes=MaskOpCodesNoEscape;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ (*
|
|
|
|
+ Windows mask works in a different mode than regular mask, it has too many
|
|
|
|
+ quirks and corner cases inherited from CP/M, then adapted to DOS (8.3) file
|
|
|
|
+ names and adapted again for long file names.
|
|
|
|
+
|
|
|
|
+ Anyth?ng.abc = "?" matches exactly 1 char
|
|
|
|
+ Anyth*ng.abc = "*" matches 0 or more of chars
|
|
|
|
+
|
|
|
|
+ ------- Quirks -------
|
|
|
|
+ *)
|
|
type
|
|
type
|
|
- TMaskCharType = (mcChar, mcCharSet, mcAnyChar, mcAnyText);
|
|
|
|
-
|
|
|
|
- TCharSet = set of Char;
|
|
|
|
- PCharSet = ^TCharSet;
|
|
|
|
-
|
|
|
|
- TMaskChar = record
|
|
|
|
- case CharType: TMaskCharType of
|
|
|
|
- mcChar: (CharValue: Char);
|
|
|
|
- mcCharSet: (Negative: Boolean; SetValue: PCharSet);
|
|
|
|
- mcAnyChar, mcAnyText: ();
|
|
|
|
|
|
+ TWindowsQuirk=(wqAnyExtension, // Anything*.* : ".*" is removed. Also makes "foo.*" match "foo"
|
|
|
|
+ wqFilenameEnd, // Anything??.abc : "?" matches 1 or 0 chars (except '.')
|
|
|
|
+ // (Not the same as "Anything*.abc", but the same
|
|
|
|
+ // as regex "Anything.{0,2}\.abc")
|
|
|
|
+ // Internally converted to "Anything[??].abc"
|
|
|
|
+
|
|
|
|
+ wqExtension3More, // Anything.abc : Matches "Anything.abc" but also "Anything.abc*" (3 char extension)
|
|
|
|
+ // Anything.ab : Matches "Anything.ab" and never "anything.abcd"
|
|
|
|
+ // *.pas : Matches "Unit1.pas.bak". Not good.
|
|
|
|
+ wqEmptyIsAny, // "" : Empty string matches anything "*"
|
|
|
|
+ wqAllByExtension, // .abc : Runs as "*.abc" (Not in use anymore)
|
|
|
|
+ wqNoExtension); // Anything*. : Matches "Anything*" without extension
|
|
|
|
+ TWindowsQuirks=set of TWindowsQuirk;
|
|
|
|
+
|
|
|
|
+const
|
|
|
|
+ AllWindowsQuirks=[wqAnyExtension,
|
|
|
|
+ wqFilenameEnd,
|
|
|
|
+ wqExtension3More,
|
|
|
|
+ wqEmptyIsAny,
|
|
|
|
+ wqAllByExtension,
|
|
|
|
+ wqNoExtension];
|
|
|
|
+
|
|
|
|
+ // Leave out wqExtension3More and wqAllByExtension
|
|
|
|
+ DefaultWindowsQuirks=[wqAnyExtension,
|
|
|
|
+ wqEmptyIsAny,
|
|
|
|
+ wqNoExtension];
|
|
|
|
+
|
|
|
|
+ //Maybe move to rtlconst unit?
|
|
|
|
+ rsInvalidCharMaskAt = 'Invalid character "%s" in mask (offset %d).';
|
|
|
|
+ rsInvalidCharMask = 'Invalid character "%s" in mask.';
|
|
|
|
+ rsMissingCloseCharMaskAt = 'Missing closing character "%s" in mask (offset %d).';
|
|
|
|
+ rsMissingCloseCharMask = 'Missing closing character "%s" in mask.';
|
|
|
|
+ rsIncompleteMask = 'Reached end of mask, but missing close/escape sequence.';
|
|
|
|
+ rsInvalidEscapeChar = 'Escape character must be ASCII <= 127.';
|
|
|
|
+ rsInternalError = 'Internal error in %s.';
|
|
|
|
+
|
|
|
|
+type
|
|
|
|
+
|
|
|
|
+ { TMaskBase }
|
|
|
|
+
|
|
|
|
+ TMaskBase = class
|
|
|
|
+ private
|
|
|
|
+ procedure SetAutoReverseRange(AValue: Boolean);
|
|
|
|
+ procedure SetCaseSensitive(AValue: Boolean);
|
|
|
|
+ procedure SetMaskEscapeChar(AValue: Char);
|
|
|
|
+ procedure SetMaskOpCodesAllowed(AValue: TMaskOpCodes);
|
|
|
|
+ protected
|
|
|
|
+ // Literal = It must match
|
|
|
|
+ // Range = Match any char in the range
|
|
|
|
+ // Negate = Negate match in a group
|
|
|
|
+ // AnyChar = It matches any char, but one must match
|
|
|
|
+ // AnyCharOrNone = Matches one or none char (only in a group)
|
|
|
|
+ // AnyCharToNext = Matches any chars amount, if fail, restart in the
|
|
|
|
+ // next position up to finish the mask or the matched string
|
|
|
|
+ // OptionalChar = Optional char
|
|
|
|
+ // CharsGroupBegin = Begin optional chars or ranges "["
|
|
|
|
+ // CharsGroupEnd = End optional chars or ranges "]"
|
|
|
|
+ type
|
|
|
|
+ TMaskParsedCode = (
|
|
|
|
+ Literal=0,
|
|
|
|
+ Range=1,
|
|
|
|
+ Negate=2,
|
|
|
|
+ AnyChar=3,
|
|
|
|
+ AnyCharOrNone=4,
|
|
|
|
+ AnyCharToNext=5,
|
|
|
|
+ OptionalChar=6,
|
|
|
|
+ CharsGroupBegin=10,
|
|
|
|
+ CharsGroupEnd=11
|
|
|
|
+ );
|
|
|
|
+ TMaskFailCause = (
|
|
|
|
+ mfcSuccess,
|
|
|
|
+ mfcMatchStringExhausted,
|
|
|
|
+ mfcMaskExhausted,
|
|
|
|
+ mfcMaskNotMatch,
|
|
|
|
+ mfcUnexpectedEnd
|
|
|
|
+ );
|
|
|
|
+ const
|
|
|
|
+ GROW_BY=100;
|
|
|
|
+ DefaultSpecialChars=['*', '?', '['];
|
|
|
|
+ procedure Add(aLength: integer; aData: PBYTE);
|
|
|
|
+ procedure Add(aValue: integer);
|
|
|
|
+ procedure Add(aValue: TMaskParsedCode);
|
|
|
|
+ procedure IncrementLastCounterBy(aOpcode: TMaskParsedCode; aValue: integer);
|
|
|
|
+ protected
|
|
|
|
+ fCaseSensitive: Boolean;
|
|
|
|
+ fAutoReverseRange: Boolean; // If enabled, range [z-a] is interpreted as [a-z]
|
|
|
|
+ fMaskIsCompiled: Boolean;
|
|
|
|
+ fMaskCompiled: TBytes;
|
|
|
|
+ fMaskCompiledIndex: integer;
|
|
|
|
+ fMaskCompiledAllocated: integer;
|
|
|
|
+ fMaskCompiledLimit: integer;
|
|
|
|
+ fMaskLimit: integer;
|
|
|
|
+ fMatchStringLimit: integer;
|
|
|
|
+ fMatchMinimumLiteralBytes: SizeInt;
|
|
|
|
+ fMatchMaximumLiteralBytes: SizeInt;
|
|
|
|
+ fMaskOpcodesAllowed: TMaskOpCodes;
|
|
|
|
+ // EscapeChar forces next char to be a literal one, not a wildcard.
|
|
|
|
+ fMaskEscapeChar: Char;
|
|
|
|
+ procedure PrepareCompile;
|
|
|
|
+ class procedure Exception_InvalidCharMask(const aMaskChar: Utf8String; aOffset: integer=-1); static;
|
|
|
|
+ class procedure Exception_MissingCloseChar(const aMaskChar: Utf8String; aOffset: integer=-1); static;
|
|
|
|
+ class procedure Exception_IncompleteMask(); static;
|
|
|
|
+ class procedure Exception_InvalidEscapeChar(); static;
|
|
|
|
+ procedure Exception_InternalError();
|
|
|
|
+ public
|
|
|
|
+ constructor Create; // Sets value of CaseSensistive to System.FileNameCaseSensitive
|
|
|
|
+ constructor Create(aCaseSensitive: Boolean;
|
|
|
|
+ aOpcodesAllowed: TMaskOpCodes=DefaultMaskOpCodes);
|
|
|
|
+ public
|
|
|
|
+ property CaseSensitive: Boolean read fCaseSensitive write SetCaseSensitive;
|
|
|
|
+ property AutoReverseRange: Boolean read fAutoReverseRange write SetAutoReverseRange;
|
|
|
|
+ property EscapeChar: Char read fMaskEscapeChar write SetMaskEscapeChar; //Must be lower ASCII (#0-#127)
|
|
|
|
+ property MaskOpCodes: TMaskOpCodes read fMaskOpcodesAllowed write SetMaskOpCodesAllowed;
|
|
end;
|
|
end;
|
|
-
|
|
|
|
- TMaskString = record
|
|
|
|
- MinLength: Integer;
|
|
|
|
- MaxLength: Integer;
|
|
|
|
- Chars: Array of TMaskChar;
|
|
|
|
|
|
+
|
|
|
|
+ { TMaskUTF8 }
|
|
|
|
+
|
|
|
|
+ TMaskUTF8 = class (TMaskBase)
|
|
|
|
+ private
|
|
|
|
+ fMatchString: Utf8String;
|
|
|
|
+ // Used by Compile.
|
|
|
|
+ fMaskInd: Integer;
|
|
|
|
+ fCPLength: integer; // Size of codepoint.
|
|
|
|
+ fLastOC: TMaskParsedCode; // Last OpCode.
|
|
|
|
+ fMask: Utf8String;
|
|
|
|
+ procedure AddAnyChar;
|
|
|
|
+ procedure AddLiteral;
|
|
|
|
+ procedure AddRange(lFirstRange, lSecondRange: Integer);
|
|
|
|
+ procedure AddRangeReverse(lFirstRange, lSecondRange: Integer);
|
|
|
|
+ procedure CompileRange;
|
|
|
|
+ procedure CompileEscapeCharPlusLiteral;
|
|
|
|
+ procedure CompileSpecialChars;
|
|
|
|
+ procedure CompileAnyCharOrNone(QChar: Char; BracketsRequired: Boolean);
|
|
|
|
+ function GetMask: Utf8String; virtual;
|
|
|
|
+ procedure SetMask(AValue: Utf8String); virtual;
|
|
|
|
+ protected
|
|
|
|
+ fOriginalMask: Utf8String;
|
|
|
|
+ function IsSpecialChar({%H-}AChar: Char): Boolean; virtual;
|
|
|
|
+ procedure CompileOtherSpecialChars; virtual;
|
|
|
|
+ class function CompareUTF8Sequences(const P1,P2: PChar): integer; static;
|
|
|
|
+ function intfMatches(aMatchOffset: integer; aMaskIndex: integer): TMaskFailCause; //override;
|
|
|
|
+ public
|
|
|
|
+
|
|
|
|
+ {$IFDEF debug_maskcompiled}
|
|
|
|
+ procedure DumpMaskCompiled;
|
|
|
|
+ {$ENDIF}
|
|
|
|
+
|
|
|
|
+ constructor Create(const aMask: Utf8String); overload;
|
|
|
|
+ constructor Create(const aMask: Utf8String; aCaseSensitive: Boolean); overload;
|
|
|
|
+ constructor Create(const aMask: Utf8String; aCaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes); virtual; overload;
|
|
|
|
+
|
|
|
|
+ procedure Compile; virtual;
|
|
|
|
+ function Matches(const aStringToMatch: Utf8String): Boolean; virtual;
|
|
|
|
+ public
|
|
|
|
+ property Mask: Utf8String read GetMask write SetMask;
|
|
end;
|
|
end;
|
|
|
|
|
|
- { TMask }
|
|
|
|
|
|
+ TMask = class(TMaskUTF8);
|
|
|
|
+
|
|
|
|
+ { TWindowsMaskUTF8 }
|
|
|
|
|
|
- TMask = class
|
|
|
|
|
|
+ TWindowsMaskUTF8=class(TMask)
|
|
private
|
|
private
|
|
- FMask: TMaskString;
|
|
|
|
|
|
+ procedure SetMask(AValue: Utf8String); override;
|
|
|
|
+ function GetMask: Utf8String; override;
|
|
|
|
+ procedure SetWindowsQuirkAllowed(AValue: TWindowsQuirks);
|
|
|
|
+ protected
|
|
|
|
+ fWindowsQuirkAllowed: TWindowsQuirks;
|
|
|
|
+ fWindowsQuirkInUse: TWindowsQuirks;
|
|
|
|
+ fWindowsMask: Utf8String;
|
|
|
|
+ procedure CompileOtherSpecialChars; override;
|
|
|
|
+ function IsSpecialChar(AChar: Char): Boolean; override;
|
|
|
|
+ class procedure SplitFileNameExtension(const aSourceFileName: Utf8String;
|
|
|
|
+ out aFileName: Utf8String; out aExtension: Utf8String; aIsMask: Boolean=False); static;
|
|
public
|
|
public
|
|
- constructor Create(const AValue: UTF8string);
|
|
|
|
- destructor Destroy; override;
|
|
|
|
-
|
|
|
|
- function Matches(const AFileName: UTF8string): Boolean;
|
|
|
|
|
|
+ constructor Create(const aMask: Utf8String; aCaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes); override;
|
|
|
|
+ constructor Create(const aMask: Utf8String; aCaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes; aWindowsQuirksAllowed: TWindowsQuirks);
|
|
|
|
+
|
|
|
|
+ procedure Compile; override;
|
|
|
|
+ function Matches(const aFileName: Utf8String): Boolean; override;
|
|
|
|
+ public
|
|
|
|
+ property Quirks: TWindowsQuirks read fWindowsQuirkAllowed write SetWindowsQuirkAllowed;
|
|
end;
|
|
end;
|
|
-
|
|
|
|
|
|
+
|
|
|
|
+ TWindowsMask = class(TWindowsMaskUTF8);
|
|
|
|
+
|
|
|
|
+ TMaskClass = class of TMaskUtf8;
|
|
|
|
+
|
|
{ TParseStringList }
|
|
{ TParseStringList }
|
|
|
|
|
|
TParseStringList = class(TStringList)
|
|
TParseStringList = class(TStringList)
|
|
public
|
|
public
|
|
- constructor Create(const AText, ASeparators: UTF8string);
|
|
|
|
|
|
+ constructor Create(const AText, ASeparators: Utf8String);
|
|
end;
|
|
end;
|
|
-
|
|
|
|
|
|
+
|
|
{ TMaskList }
|
|
{ TMaskList }
|
|
|
|
|
|
TMaskList = class
|
|
TMaskList = class
|
|
private
|
|
private
|
|
- FMasks: TObjectList;
|
|
|
|
|
|
+ fAutoReverseRange: Boolean;
|
|
|
|
+ fMasks: TObjectList;
|
|
|
|
+ FMaskClass: TMaskClass;
|
|
|
|
+ fMask: Utf8String;
|
|
|
|
+ fSeparator: Char;
|
|
|
|
+ fCaseSensitive: Boolean;
|
|
|
|
+ fMaskOpcodes: TMaskOpcodes;
|
|
function GetCount: Integer;
|
|
function GetCount: Integer;
|
|
function GetItem(Index: Integer): TMask;
|
|
function GetItem(Index: Integer): TMask;
|
|
|
|
+ procedure SetAutoReverseRange(AValue: Boolean);
|
|
|
|
+ procedure SetCaseSensitive(AValue: Boolean);
|
|
|
|
+ procedure SetMask(AValue: Utf8String); virtual;
|
|
|
|
+ procedure SetMaskOpCodes(AValue: TMaskOpCodes);
|
|
|
|
+ protected
|
|
|
|
+ function GetMaskClass: TMaskClass; virtual;
|
|
|
|
+ procedure AddMasksToList(const aValue: Utf8String; aSeparator: Char; CaseSensitive: Boolean;
|
|
|
|
+ aOpcodesAllowed: TMaskOpCodes); virtual;
|
|
public
|
|
public
|
|
- constructor Create(const AValue: UTF8string; ASeparator: Char = ';');
|
|
|
|
|
|
+ constructor Create(const aValue: Utf8String; aSeparator: Char=';'; CaseSensitive: Boolean=False;
|
|
|
|
+ aOpcodesAllowed: TMaskOpCodes=DefaultMaskOpCodes); virtual;
|
|
|
|
+
|
|
destructor Destroy; override;
|
|
destructor Destroy; override;
|
|
-
|
|
|
|
- function Matches(const AFileName: UTF8string): Boolean;
|
|
|
|
-
|
|
|
|
|
|
+
|
|
|
|
+ function Matches(const AFileName: Utf8String): Boolean;
|
|
|
|
+
|
|
property Count: Integer read GetCount;
|
|
property Count: Integer read GetCount;
|
|
property Items[Index: Integer]: TMask read GetItem;
|
|
property Items[Index: Integer]: TMask read GetItem;
|
|
|
|
+ property Mask: Utf8String read fMask write SetMask;
|
|
|
|
+ property MaskOpCodes: TMaskOpCodes read fMaskOpCodes write SetMaskOpCodes;
|
|
|
|
+ property AutoReverseRange: Boolean read fAutoReverseRange write SetAutoReverseRange;
|
|
|
|
+ property CaseSensitive: Boolean read fCaseSensitive write SetCaseSensitive;
|
|
end;
|
|
end;
|
|
|
|
|
|
-function MatchesMask(const FileName, Mask: UTF8string): Boolean;
|
|
|
|
-function MatchesMaskList(const FileName, Mask: UTF8string; Separator: Char = ';'): Boolean;
|
|
|
|
|
|
+
|
|
|
|
+ { TWindowsMaskList }
|
|
|
|
+
|
|
|
|
+ TWindowsMaskList = class(TMaskList)
|
|
|
|
+ private
|
|
|
|
+ fWindowsQuirks: TWindowsQuirks;
|
|
|
|
+ procedure SetQuirks(AValue: TWindowsQuirks);
|
|
|
|
+ protected
|
|
|
|
+ function GetMaskClass: TMaskClass; override;
|
|
|
|
+ procedure AddMasksToList(const aValue: Utf8String; aSeparator: Char; aCaseSensitive: Boolean;
|
|
|
|
+ aOpcodesAllowed: TMaskOpCodes); override;
|
|
|
|
+ public
|
|
|
|
+ constructor Create(const aValue: Utf8String; aSeparator: Char=';'; aCaseSensitive: Boolean=False;
|
|
|
|
+ aOpcodesAllowed: TMaskOpCodes=DefaultMaskOpCodes); override;
|
|
|
|
+
|
|
|
|
+ constructor Create(const aValue: Utf8String; aSeparator: Char; aCaseSensitive: Boolean;
|
|
|
|
+ aOpcodesAllowed: TMaskOpCodes;
|
|
|
|
+ aWindowsQuirksAllowed: TWindowsQuirks); overload;
|
|
|
|
+
|
|
|
|
+ property Quirks: TWindowsQuirks read fWindowsQuirks write SetQuirks;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+function MatchesMask(const FileName, Mask: Utf8String): Boolean; // Value of CaseSensitive equals System.FileNameCaseSesnsitive!
|
|
|
|
+
|
|
|
|
+function MatchesMask(const FileName, Mask: Utf8String; CaseSensitive: Boolean;
|
|
|
|
+ aOpcodesAllowed: TMaskOpCodes=DefaultMaskOpCodes): Boolean;
|
|
|
|
+
|
|
|
|
+function MatchesWindowsMask(const FileName, Mask: Utf8String; CaseSensitive: Boolean=False;
|
|
|
|
+ aOpcodesAllowed: TMaskOpCodes=DefaultMaskOpCodes;
|
|
|
|
+ aWindowsQuirksAllowed: TWindowsQuirks=DefaultWindowsQuirks): Boolean;
|
|
|
|
+
|
|
|
|
+function MatchesMaskList(const FileName, Mask: Utf8String): Boolean; // Value of CaseSensitive equals System.FileNameCaseSesnsitive!
|
|
|
|
+
|
|
|
|
+function MatchesMaskList(const FileName, Mask: Utf8String; Separator: Char;
|
|
|
|
+ CaseSensitive: Boolean=False;
|
|
|
|
+ aOpcodesAllowed: TMaskOpCodes=DefaultMaskOpCodes): Boolean;
|
|
|
|
+
|
|
|
|
+function MatchesWindowsMaskList(const FileName, Mask: Utf8String; Separator: Char=';';
|
|
|
|
+ CaseSensitive: Boolean=False;
|
|
|
|
+ aOpcodesAllowed: TMaskOpCodes=DefaultMaskOpCodes;
|
|
|
|
+ aWindowsQuirksAllowed: TWindowsQuirks=DefaultWindowsQuirks): Boolean;
|
|
|
|
+
|
|
|
|
|
|
implementation
|
|
implementation
|
|
|
|
|
|
-function MatchesMask(const FileName, Mask: UTF8string): Boolean;
|
|
|
|
|
|
+//copied form LazUtf8
|
|
|
|
+//does NOT check the validity of a codepoint!
|
|
|
|
+function UTF8CodepointSizeFast(p: PChar): integer; inline;
|
|
|
|
+begin
|
|
|
|
+ case p^ of
|
|
|
|
+ #0..#191 : Result := 1;
|
|
|
|
+ #192..#223 : Result := 2;
|
|
|
|
+ #224..#239 : Result := 3;
|
|
|
|
+ #240..#247 : Result := 4;
|
|
|
|
+ else Result := 1; // An optimization + prevents compiler warning about uninitialized Result.
|
|
|
|
+ end;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function Utf8LowerCase(const S: Utf8String): Utf8String;
|
|
|
|
+begin
|
|
|
|
+ Result := Utf8Encode(WideLowerCase(Utf8Decode(S)));
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+function MatchesMask(const FileName, Mask: Utf8String): Boolean;
|
|
|
|
+begin
|
|
|
|
+ Result := MatchesMask(FileName, Mask, System.FileNameCaseSensitive, DefaultMaskOpCodes);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function MatchesMask(const FileName, Mask: Utf8String; CaseSensitive: Boolean;
|
|
|
|
+ aOpcodesAllowed: TMaskOpCodes): Boolean;
|
|
var
|
|
var
|
|
AMask: TMask;
|
|
AMask: TMask;
|
|
begin
|
|
begin
|
|
- AMask := TMask.Create(Mask);
|
|
|
|
|
|
+ AMask := TMask.Create(Mask, CaseSensitive, aOpcodesAllowed);
|
|
try
|
|
try
|
|
Result := AMask.Matches(FileName);
|
|
Result := AMask.Matches(FileName);
|
|
finally
|
|
finally
|
|
@@ -101,11 +419,32 @@ begin
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
-function MatchesMaskList(const FileName, Mask: UTF8string; Separator: Char): Boolean;
|
|
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+function MatchesWindowsMask(const FileName, Mask: Utf8String; CaseSensitive: Boolean;
|
|
|
|
+ aOpcodesAllowed: TMaskOpCodes; aWindowsQuirksAllowed: TWindowsQuirks): Boolean;
|
|
|
|
+var
|
|
|
|
+ AMask: TWindowsMask;
|
|
|
|
+begin
|
|
|
|
+ AMask := TWindowsMask.Create(Mask, CaseSensitive, aOpcodesAllowed, aWindowsQuirksAllowed);
|
|
|
|
+ try
|
|
|
|
+ Result := AMask.Matches(FileName);
|
|
|
|
+ finally
|
|
|
|
+ AMask.Free;
|
|
|
|
+ end;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function MatchesMaskList(const FileName, Mask: Utf8String): Boolean;
|
|
|
|
+begin
|
|
|
|
+ Result := MatchesMaskList(FileName, Mask, ';', System.FileNameCaseSensitive, DefaultMaskOpCodes);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function MatchesMaskList(const FileName, Mask: Utf8String; Separator: Char;
|
|
|
|
+ CaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes): Boolean;
|
|
var
|
|
var
|
|
AMaskList: TMaskList;
|
|
AMaskList: TMaskList;
|
|
begin
|
|
begin
|
|
- AMaskList := TMaskList.Create(Mask, Separator);
|
|
|
|
|
|
+ AMaskList := TMaskList.Create(Mask, Separator, CaseSensitive, aOpcodesAllowed);
|
|
try
|
|
try
|
|
Result := AMaskList.Matches(FileName);
|
|
Result := AMaskList.Matches(FileName);
|
|
finally
|
|
finally
|
|
@@ -113,293 +452,1119 @@ begin
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
-{ TMask }
|
|
|
|
|
|
|
|
-constructor TMask.Create(const AValue: UTF8string);
|
|
|
|
|
|
+function MatchesWindowsMaskList(const FileName, Mask: Utf8String; Separator: Char;
|
|
|
|
+ CaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes; aWindowsQuirksAllowed: TWindowsQuirks): Boolean;
|
|
var
|
|
var
|
|
- I: Integer;
|
|
|
|
- SkipAnyText: Boolean;
|
|
|
|
-
|
|
|
|
- procedure CharSetError;
|
|
|
|
- begin
|
|
|
|
- raise EConvertError.CreateFmt('Invalid charset %s', [AValue]);
|
|
|
|
|
|
+ AMaskList: TWindowsMaskList;
|
|
|
|
+begin
|
|
|
|
+ AMaskList := TWindowsMaskList.Create(Mask, Separator, CaseSensitive, aOpcodesAllowed, aWindowsQuirksAllowed);
|
|
|
|
+ try
|
|
|
|
+ Result := AMaskList.Matches(FileName);
|
|
|
|
+ finally
|
|
|
|
+ AMaskList.Free;
|
|
end;
|
|
end;
|
|
-
|
|
|
|
- procedure AddAnyText;
|
|
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+{ TWindowsMaskList }
|
|
|
|
+
|
|
|
|
+procedure TWindowsMaskList.SetQuirks(AValue: TWindowsQuirks);
|
|
|
|
+var
|
|
|
|
+ i: Integer;
|
|
|
|
+begin
|
|
|
|
+ if fWindowsQuirks = AValue then Exit;
|
|
|
|
+ fWindowsQuirks := AValue;
|
|
|
|
+ for i := 0 to fMasks.Count - 1 do
|
|
begin
|
|
begin
|
|
- if SkipAnyText then
|
|
|
|
|
|
+ TWindowsMask(fMasks.Items[i]).Quirks := FWindowsQuirks;
|
|
|
|
+ TWindowsMask(fMasks.Items[i]).MaskOpCodes := fMaskOpcodes;
|
|
|
|
+ end;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TWindowsMaskList.GetMaskClass: TMaskClass;
|
|
|
|
+begin
|
|
|
|
+ Result := TWindowsMask;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TWindowsMaskList.AddMasksToList(const aValue: Utf8String;
|
|
|
|
+ aSeparator: Char; aCaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes);
|
|
|
|
+var
|
|
|
|
+ i: Integer;
|
|
|
|
+begin
|
|
|
|
+ inherited AddMasksToList(aValue, aSeparator, aCaseSensitive, aOpcodesAllowed);
|
|
|
|
+ if (FWindowsQuirks <> DefaultWindowsQuirks) then //inherited did not pass Quirks to the constructor
|
|
begin
|
|
begin
|
|
- Inc(I);
|
|
|
|
- Exit;
|
|
|
|
|
|
+ for i := 0 to fMasks.Count - 1 do
|
|
|
|
+ begin
|
|
|
|
+ TWindowsMask(fMasks.Items[i]).fWindowsQuirkAllowed := FWindowsQuirks;
|
|
end;
|
|
end;
|
|
-
|
|
|
|
- SetLength(FMask.Chars, Length(FMask.Chars) + 1);
|
|
|
|
- FMask.Chars[High(FMask.Chars)].CharType := mcAnyText;
|
|
|
|
-
|
|
|
|
- FMask.MaxLength := MaxInt;
|
|
|
|
- SkipAnyText := True;
|
|
|
|
- Inc(I);
|
|
|
|
end;
|
|
end;
|
|
-
|
|
|
|
- procedure AddAnyChar;
|
|
|
|
- begin
|
|
|
|
- SkipAnyText := False;
|
|
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+constructor TWindowsMaskList.Create(const aValue: Utf8String; aSeparator: Char;
|
|
|
|
+ aCaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes);
|
|
|
|
+begin
|
|
|
|
+ Create(aValue, aSeparator, aCaseSensitive, aOpcodesAllowed, DefaultWindowsQuirks);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+constructor TWindowsMaskList.Create(const aValue: Utf8String; aSeparator: Char;
|
|
|
|
+ aCaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes;
|
|
|
|
+ aWindowsQuirksAllowed: TWindowsQuirks);
|
|
|
|
+begin
|
|
|
|
+ fWindowsQuirks := aWindowsQuirksAllowed;
|
|
|
|
+ //if (wqFilenameEnd in aWindowsQuirksAllowed) then
|
|
|
|
+ // Include(aOpcodesAllowed, mocAnyCharOrNone);
|
|
|
|
+ inherited Create(aValue, aSeparator, aCaseSensitive, aOpcodesAllowed);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+{ EMaskError }
|
|
|
|
+
|
|
|
|
+constructor EMaskError.Create(const msg: Utf8String; aCode: TMaskExceptionCode);
|
|
|
|
+begin
|
|
|
|
+ CreateFmt(msg,[],aCode);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+constructor EMaskError.CreateFmt(const msg: Utf8String; args: array of const;
|
|
|
|
+ aCode: TMaskExceptionCode);
|
|
|
|
+begin
|
|
|
|
+ FCode:=aCode;
|
|
|
|
+ Inherited CreateFmt(msg,args);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+{ TMaskBase }
|
|
|
|
|
|
- SetLength(FMask.Chars, Length(FMask.Chars) + 1);
|
|
|
|
- FMask.Chars[High(FMask.Chars)].CharType := mcAnyChar;
|
|
|
|
|
|
+procedure TMaskBase.SetAutoReverseRange(AValue: Boolean);
|
|
|
|
+begin
|
|
|
|
+ if fAutoReverseRange = AValue then Exit;
|
|
|
|
+ fAutoReverseRange := AValue;
|
|
|
|
+ fMaskIsCompiled := False;
|
|
|
|
+end;
|
|
|
|
|
|
- Inc(FMask.MinLength);
|
|
|
|
- if FMask.MaxLength < MaxInt then Inc(FMask.MaxLength);
|
|
|
|
-
|
|
|
|
- Inc(I);
|
|
|
|
|
|
+procedure TMaskBase.SetCaseSensitive(AValue: Boolean);
|
|
|
|
+begin
|
|
|
|
+ if fCaseSensitive = AValue then Exit;
|
|
|
|
+ fCaseSensitive := AValue;
|
|
|
|
+ fMaskIsCompiled := False;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TMaskBase.SetMaskEscapeChar(AValue: Char);
|
|
|
|
+begin
|
|
|
|
+ if fMaskEscapeChar=AValue then Exit;
|
|
|
|
+ if fMaskEscapeChar>#127 then
|
|
|
|
+ Exception_InvalidEscapeChar();
|
|
|
|
+ fMaskEscapeChar:=AValue;
|
|
|
|
+ fMaskIsCompiled:=False;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TMaskBase.SetMaskOpCodesAllowed(AValue: TMaskOpCodes);
|
|
|
|
+begin
|
|
|
|
+ if fMaskOpcodesAllowed = AValue then Exit;
|
|
|
|
+ fMaskOpcodesAllowed := AValue;
|
|
|
|
+ fMaskIsCompiled := False;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TMaskBase.Add(aLength: integer; aData: PBYTE);
|
|
|
|
+var
|
|
|
|
+ lCounter: integer;
|
|
|
|
+begin
|
|
|
|
+ if fMaskCompiledIndex+aLength>=fMaskCompiledAllocated then begin
|
|
|
|
+ fMaskCompiledAllocated:=fMaskCompiledAllocated+aLength+GROW_BY;
|
|
|
|
+ SetLength(fMaskCompiled,fMaskCompiledAllocated);
|
|
end;
|
|
end;
|
|
-
|
|
|
|
- procedure AddCharSet;
|
|
|
|
- var
|
|
|
|
- CharSet: TCharSet;
|
|
|
|
- Valid: Boolean;
|
|
|
|
- C, Last: Char;
|
|
|
|
- begin
|
|
|
|
- SkipAnyText := False;
|
|
|
|
-
|
|
|
|
- SetLength(FMask.Chars, Length(FMask.Chars) + 1);
|
|
|
|
- FMask.Chars[High(FMask.Chars)].CharType := mcCharSet;
|
|
|
|
|
|
+ for lCounter := 0 to Pred(aLength) do begin
|
|
|
|
+ fMaskCompiled[fMaskCompiledIndex]:=(aData+lCounter)^;
|
|
|
|
+ inc(fMaskCompiledIndex);
|
|
|
|
+ end;
|
|
|
|
+end;
|
|
|
|
|
|
- Inc(I);
|
|
|
|
- if (I <= Length(AValue)) and (AValue[I] = '!') then
|
|
|
|
- begin
|
|
|
|
- FMask.Chars[High(FMask.Chars)].Negative := True;
|
|
|
|
- Inc(I);
|
|
|
|
- end
|
|
|
|
- else FMask.Chars[High(FMask.Chars)].Negative := False;
|
|
|
|
-
|
|
|
|
- Last := '-';
|
|
|
|
- CharSet := [];
|
|
|
|
- Valid := False;
|
|
|
|
- while I <= Length(AValue) do
|
|
|
|
|
|
+procedure TMaskBase.Add(aValue: integer);
|
|
|
|
+begin
|
|
|
|
+ Add(sizeof(aValue),@aValue);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TMaskBase.Add(aValue: TMaskParsedCode);
|
|
|
|
+var
|
|
|
|
+ v: BYTE;
|
|
|
|
+begin
|
|
|
|
+ //writeln('Add: ',aValue);
|
|
|
|
+ v:=BYTE(aValue);
|
|
|
|
+ Add(1,@v);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TMaskBase.IncrementLastCounterBy(aOpcode: TMaskParsedCode; aValue: integer);
|
|
|
|
+var
|
|
|
|
+ p: PInteger;
|
|
|
|
+begin
|
|
|
|
+ //writeln('TMaskBase.IncrementLastCounterBy: aOPcode=',aOpcode,', aValue=',aValue);
|
|
|
|
+ fMaskCompiledIndex:=fMaskCompiledIndex-sizeof(aValue);
|
|
|
|
+ if TMaskParsedCode(fMaskCompiled[fMaskCompiledIndex-1])<>aOpcode then
|
|
|
|
+ Exception_InternalError();
|
|
|
|
+ P:=@fMaskCompiled[fMaskCompiledIndex];
|
|
|
|
+ Add(P^+aValue);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TMaskBase.PrepareCompile;
|
|
|
|
+begin
|
|
|
|
+ fMaskCompiled := nil;
|
|
|
|
+ fMaskCompiledAllocated := 0;
|
|
|
|
+ fMaskCompiledIndex := 0;
|
|
|
|
+ fMaskIsCompiled:=False;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+class procedure TMaskBase.Exception_InvalidCharMask(const aMaskChar: Utf8String;
|
|
|
|
+ aOffset: integer);
|
|
|
|
+begin
|
|
|
|
+ if aOffset>=0 then
|
|
|
|
+ raise EMaskError.CreateFmt(rsInvalidCharMaskAt, [aMaskChar, aOffset], mecInvalidCharMask)
|
|
|
|
+ else
|
|
|
|
+ raise EMaskError.CreateFmt(rsInvalidCharMask, [aMaskChar], mecInvalidCharMask);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+class procedure TMaskBase.Exception_MissingCloseChar(const aMaskChar: Utf8String;
|
|
|
|
+ aOffset: integer);
|
|
|
|
+begin
|
|
|
|
+ if aOffset>=0 then
|
|
|
|
+ raise EMaskError.CreateFmt(rsMissingCloseCharMaskAt, [aMaskChar, aOffset], mecMissingClose)
|
|
|
|
+ else
|
|
|
|
+ raise EMaskError.CreateFmt(rsMissingCloseCharMask, [aMaskChar], mecMissingClose);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+class procedure TMaskBase.Exception_IncompleteMask();
|
|
|
|
+begin
|
|
|
|
+ raise EMaskError.CreateFmt(rsIncompleteMask, [], mecIncompleteMask);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+class procedure TMaskBase.Exception_InvalidEscapeChar();
|
|
|
|
+begin
|
|
|
|
+ raise EMaskError.Create(rsInvalidEscapeChar, mecInvalidEscapeChar);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TMaskBase.Exception_InternalError();
|
|
|
|
+begin
|
|
|
|
+ raise EMaskError.CreateFmt(rsInternalError, [self.ClassName], mecInternalError);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+constructor TMaskBase.Create;
|
|
|
|
+begin
|
|
|
|
+ Create(System.FileNameCaseSensitive, DefaultMaskOpCodes);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+constructor TMaskBase.Create(aCaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes);
|
|
|
|
+begin
|
|
|
|
+ fCaseSensitive:=aCaseSensitive;
|
|
|
|
+ fMaskOpcodesAllowed:=aOpcodesAllowed;
|
|
|
|
+ fAutoReverseRange:=True;
|
|
|
|
+ fMaskEscapeChar:='\';
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+{ TMask }
|
|
|
|
+
|
|
|
|
+procedure TMaskUTF8.AddAnyChar;
|
|
|
|
+begin
|
|
|
|
+ Add(TMaskParsedCode.AnyChar);
|
|
|
|
+ inc(fMatchMinimumLiteralBytes,1);
|
|
|
|
+ if fMatchMaximumLiteralBytes<High(fMatchMaximumLiteralBytes) then
|
|
|
|
+ inc(fMatchMaximumLiteralBytes,4);
|
|
|
|
+ fLastOC:=TMaskParsedCode.AnyChar;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TMaskUTF8.AddLiteral;
|
|
|
|
+begin
|
|
|
|
+ Add(TMaskParsedCode.Literal);
|
|
|
|
+ Add(fCPLength,@fMask[fMaskInd]);
|
|
|
|
+ inc(fMatchMinimumLiteralBytes,fCPLength);
|
|
|
|
+ if fMatchMaximumLiteralBytes<High(fMatchMaximumLiteralBytes) then
|
|
|
|
+ inc(fMatchMaximumLiteralBytes,fCPLength);
|
|
|
|
+ fLastOC:=TMaskParsedCode.Literal;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TMaskUTF8.AddRange(lFirstRange, lSecondRange: Integer);
|
|
|
|
+begin
|
|
|
|
+ fCPLength:=UTF8CodepointSizeFast(@fMask[lFirstRange]);
|
|
|
|
+ Add(fCPLength,@fMask[lFirstRange]);
|
|
|
|
+ fCPLength:=UTF8CodepointSizeFast(@fMask[lSecondRange]);
|
|
|
|
+ Add(fCPLength,@fMask[lSecondRange]);
|
|
|
|
+ fMaskInd:=lSecondRange;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TMaskUTF8.AddRangeReverse(lFirstRange, lSecondRange: Integer);
|
|
|
|
+begin
|
|
|
|
+ fCPLength:=UTF8CodepointSizeFast(@fMask[lSecondRange]);
|
|
|
|
+ Add(fCPLength,@fMask[lSecondRange]);
|
|
|
|
+ Add(UTF8CodepointSizeFast(@fMask[lFirstRange]),@fMask[lFirstRange]);
|
|
|
|
+ fMaskInd:=lSecondRange;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TMaskUTF8.SetMask(AValue: Utf8String);
|
|
|
|
+begin
|
|
|
|
+ if fOriginalMask = AValue then Exit;
|
|
|
|
+ fOriginalMask := AValue;
|
|
|
|
+ fMaskIsCompiled := False;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TMaskUTF8.IsSpecialChar(AChar: Char): Boolean;
|
|
|
|
+begin
|
|
|
|
+ Result := False
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TMaskUTF8.CompileOtherSpecialChars;
|
|
|
|
+begin
|
|
|
|
+ //Nothing to do
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TMaskUTF8.CompileRange;
|
|
|
|
+ function IsARange(aPosition: integer; out aFirstSequence: integer; out aSecondSequence: integer): Boolean;// {$IFDEF USE_INLINE}inline;{$ENDIF}
|
|
|
|
+ var
|
|
|
|
+ llCPL: integer;
|
|
begin
|
|
begin
|
|
- case AValue[I] of
|
|
|
|
- '-':
|
|
|
|
- begin
|
|
|
|
- if Last = '-' then CharSetError;
|
|
|
|
- Inc(I);
|
|
|
|
-
|
|
|
|
- if (I > Length(AValue)) then CharSetError;
|
|
|
|
- //DebugLn('Set: ' + Last + '-' + UpCase(AValue[I]));
|
|
|
|
- for C := Last to UpCase(AValue[I]) do Include(CharSet, C);
|
|
|
|
- Inc(I);
|
|
|
|
|
|
+ Result:=false;
|
|
|
|
+ aFirstSequence:=0;
|
|
|
|
+ aSecondSequence:=0;
|
|
|
|
+ if (mocEscapeChar in FMaskOpcodesAllowed) and (fMask[aPosition]=FMaskEscapeChar) then begin
|
|
|
|
+ llCPL:=UTF8CodepointSizeFast(@fMask[aPosition]);
|
|
|
|
+ if aPosition+llCPL>fMaskLimit then exit; // ==>
|
|
|
|
+ inc(aPosition,llCPL);
|
|
|
|
+ aFirstSequence:=aPosition;
|
|
|
|
+ end else begin
|
|
|
|
+ aFirstSequence:=aPosition;
|
|
|
|
+ end;
|
|
|
|
+ llCPL:=UTF8CodepointSizeFast(@fMask[aPosition]);
|
|
|
|
+ inc(aPosition,llCPL);
|
|
|
|
+ if aPosition+llCPL>fMaskLimit then exit; // ==>
|
|
|
|
+ // It is not a range, return false
|
|
|
|
+ if fMask[aPosition]<>'-' then exit;
|
|
|
|
+
|
|
|
|
+ llCPL:=UTF8CodepointSizeFast(@fMask[aPosition]);
|
|
|
|
+ inc(aPosition,llCPL);
|
|
|
|
+ if (mocEscapeChar in FMaskOpcodesAllowed) and (fMask[aPosition]=FMaskEscapeChar) then begin
|
|
|
|
+ llCPL:=UTF8CodepointSizeFast(@fMask[aPosition]);
|
|
|
|
+ if aPosition+llCPL>fMaskLimit then exit; // ==>
|
|
|
|
+ inc(aPosition,llCPL);
|
|
|
|
+ aSecondSequence:=aPosition;
|
|
|
|
+ end else begin
|
|
|
|
+ aSecondSequence:=aPosition;
|
|
|
|
+ end;
|
|
|
|
+ Result:=true;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+var
|
|
|
|
+ lCharsGroupInsertSize, lFirstRange, lSecondRange: integer;
|
|
|
|
+begin
|
|
|
|
+ //writeln('CompileRange: fMask[fMaskInd]=',fMask[fMaskInd]);
|
|
|
|
+ if ([mocSet,mocRange,mocAnyCharOrNone]*fMaskOpCodesAllowed = [mocAnyCharOrNone]) or
|
|
|
|
+ ((mocAnyCharOrNone in fMaskOpcodesAllowed) and (fMaskInd<fMaskLimit) and (fMask[fMaskInd+1]='?'))
|
|
|
|
+ then begin
|
|
|
|
+ CompileAnyCharOrNone('?', True);
|
|
|
|
+ end else begin//not AnyCharOrNone
|
|
|
|
+ fLastOC:=TMaskParsedCode.CharsGroupBegin;
|
|
|
|
+ Add(TMaskParsedCode.CharsGroupBegin);
|
|
|
|
+ inc(fMatchMinimumLiteralBytes,1);
|
|
|
|
+ if fMatchMaximumLiteralBytes<High(fMatchMaximumLiteralBytes) then
|
|
|
|
+ begin inc(fMatchMaximumLiteralBytes,4); end;
|
|
|
|
+ lCharsGroupInsertSize:=fMaskCompiledIndex;
|
|
|
|
+ Add(0);
|
|
|
|
+ inc(fMaskInd); // CP length is 1 because it is "["
|
|
|
|
+ if fMaskInd<fMaskLimit then begin
|
|
|
|
+ if (fMask[fMaskInd]='!') and (mocNegateGroup in fMaskOpcodesAllowed) then begin
|
|
|
|
+ Add(TMaskParsedCode.Negate);
|
|
|
|
+ inc(fMaskInd); // CP length is 1 because it is "!"
|
|
|
|
+ fLastOC:=TMaskParsedCode.Negate;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+ while fMaskInd<=fMaskLimit do begin //while
|
|
|
|
+ fCPLength:=UTF8CodepointSizeFast(@fMask[fMaskInd]);
|
|
|
|
+ if (mocRange in fMaskOpcodesAllowed) and IsARange(fMaskInd,lFirstRange,lSecondRange) then begin //is a range
|
|
|
|
+ Add(TMaskParsedCode.Range);
|
|
|
|
+ // Check if reverse range is needed
|
|
|
|
+ if (not fAutoReverseRange)
|
|
|
|
+ or (CompareUTF8Sequences(@fMask[lFirstRange],@fMask[lSecondRange])<0) then
|
|
|
|
+ AddRange(lFirstRange, lSecondRange)
|
|
|
|
+ else
|
|
|
|
+ AddRangeReverse(lFirstRange, lSecondRange);
|
|
|
|
+ fLastOC:=TMaskParsedCode.Range;
|
|
|
|
+
|
|
|
|
+ end //is a range
|
|
|
|
+ else if fMask[fMaskInd]=']' then begin //end of range or set
|
|
|
|
+ if (fLastOC=TMaskParsedCode.CharsGroupBegin) or (fLastOC=TMaskParsedCode.Negate) then
|
|
|
|
+ begin //empty set or range
|
|
|
|
+ //writeln('CompileRange: empty match');
|
|
|
|
+ Exception_InvalidCharMask(fMask[fMaskInd],fMaskInd); //Error empty match
|
|
|
|
+ end; //empty set or range
|
|
|
|
+ // Insert the new offset in case of a positive match in CharsGroup
|
|
|
|
+ PInteger(@fMaskCompiled[lCharsGroupInsertSize])^:=fMaskCompiledIndex;
|
|
|
|
+ Add(TMaskParsedCode.CharsGroupEnd);
|
|
|
|
+ fLastOC:=TMaskParsedCode.CharsGroupEnd;
|
|
|
|
+ break;
|
|
|
|
+ end // end of range or set
|
|
|
|
+ else begin //not a range, not AnyCharOrNone, must be a set
|
|
|
|
+ //Note: why not raise an exception right here if mocSet is NOT enabled?
|
|
|
|
+
|
|
|
|
+ // handle escaping if mocSet is enabled, but mocRange not
|
|
|
|
+ if (fMask[fMaskInd]=FMaskEscapeChar) and (mocEscapeChar in fMaskOpcodesAllowed) then begin //escaped literal in set
|
|
|
|
+ // next is optional char in set or literal, consume the EscapeChar
|
|
|
|
+ inc(fMaskInd,fCPLength);
|
|
|
|
+ if fMaskInd<=fMaskLimit then begin
|
|
|
|
+ fCPLength:=UTF8CodepointSizeFast(@fMask[fMaskInd]);
|
|
|
|
+ end else begin
|
|
|
|
+ //writeln('CompileRange: incomplete mask');
|
|
|
|
+ Exception_IncompleteMask();
|
|
end;
|
|
end;
|
|
- ']':
|
|
|
|
- begin
|
|
|
|
- Valid := True;
|
|
|
|
- Break;
|
|
|
|
|
|
+ end;//escaped literal in set
|
|
|
|
+
|
|
|
|
+ if (mocSet in fMaskOpcodesAllowed) then begin //add to set
|
|
|
|
+ Add(TMaskParsedCode.OptionalChar);
|
|
|
|
+ Add(fCPLength,@fMask[fMaskInd]);
|
|
|
|
+ fLastOC:=TMaskParsedCode.OptionalChar;
|
|
|
|
+ end //add to set
|
|
|
|
+ else begin // mocRange enabled but IsRange=False, mocSet disabled
|
|
|
|
+ Exception_InvalidCharMask(fMask[fMaskInd],fMaskInd);
|
|
|
|
+ end;
|
|
|
|
+ end; //not a range, not AnyCharOrNone, must be a set
|
|
|
|
+ inc(fMaskInd,fCPLength);
|
|
|
|
+ end;//while
|
|
|
|
+ if fMaskInd>fMaskLimit then
|
|
|
|
+ Exception_MissingCloseChar(']',fMaskLimit);
|
|
|
|
+ end;//not AnyCharOrNone
|
|
|
|
+ //writeln('CompileRange end: fMask[fMaskInd]=',fMask[fMaskInd]);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TMaskUTF8.GetMask: Utf8String;
|
|
|
|
+begin
|
|
|
|
+ Result := fOriginalMask;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+procedure TMaskUtf8.CompileEscapeCharPlusLiteral;
|
|
|
|
+begin
|
|
|
|
+ inc(fMaskInd,fCPLength);
|
|
|
|
+ if fMaskInd<=fMaskLimit then begin
|
|
|
|
+ fCPLength:=UTF8CodepointSizeFast(@fMask[fMaskInd]);
|
|
|
|
+ AddLiteral;
|
|
|
|
+ inc(fMaskInd,fCPLength);
|
|
|
|
+ end
|
|
|
|
+ else
|
|
|
|
+ Exception_IncompleteMask();
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TMaskUtf8.CompileSpecialChars;
|
|
|
|
+begin
|
|
|
|
+ case fMask[fMaskInd] of
|
|
|
|
+ '*':
|
|
|
|
+ begin
|
|
|
|
+ if mocAnyText in fMaskOpcodesAllowed then begin
|
|
|
|
+ if fLastOC<>TMaskParsedCode.AnyCharToNext then begin
|
|
|
|
+ Add(TMaskParsedCode.AnyCharToNext);
|
|
|
|
+ fLastOC:=TMaskParsedCode.AnyCharToNext;
|
|
|
|
+ // * = No limit
|
|
|
|
+ fMatchMaximumLiteralBytes:=High(fMatchMaximumLiteralBytes);
|
|
end;
|
|
end;
|
|
|
|
+ end
|
|
else
|
|
else
|
|
- begin
|
|
|
|
- Last := UpCase(AValue[I]);
|
|
|
|
- Include(CharSet, Last);
|
|
|
|
- Inc(I);
|
|
|
|
|
|
+ AddLiteral;
|
|
|
|
+ end;
|
|
|
|
+ '?':
|
|
|
|
+ begin
|
|
|
|
+ if mocAnyChar in fMaskOpcodesAllowed then
|
|
|
|
+ AddAnyChar
|
|
|
|
+ else
|
|
|
|
+ AddLiteral;
|
|
|
|
+ end;
|
|
|
|
+ '[':
|
|
|
|
+ begin
|
|
|
|
+ if ([mocSet,mocRange,
|
|
|
|
+ mocAnyCharOrNone] * fMaskOpcodesAllowed <> [])
|
|
|
|
+ //in this case the '[' always mus be the start of a Range, a Set or AnyCharOrNone
|
|
|
|
+ then
|
|
|
|
+ begin //it's just a bit easier later on if we handle this before we call CompileRange
|
|
|
|
+ //if ([mocSet,mocRange]*fMaskOpCodesAllowed = []) {only mocAnyCharOrNone enabled}
|
|
|
|
+ // and (fMaskInd<fMaskLimit) and (fMask[fMaskInd+1]<>'?') {next char is not '?'} then
|
|
|
|
+ // Exception_InvalidCharMask(fMask[fMaskInd+1], fMaskInd+1);
|
|
|
|
+ CompileRange;
|
|
|
|
+ end
|
|
|
|
+ else begin //mocSet,MocRange and mocAnyCharOrNone are all disabled
|
|
|
|
+ {
|
|
|
|
+ if (fMask[fMaskInd]=FMaskEscapeChar) and (mocEscapeChar in FMaskOpcodesAllowed) then begin //DEAD CODE?
|
|
|
|
+ //This codepath could only be chosen if, at this point '[' is the escapechar
|
|
|
|
+ //but, if that is the case, it should already have been handled in Compile
|
|
|
|
+ //and CompileRange should not have been called.
|
|
|
|
+ //Maybe I'm wrong, so I just comment this section out instead of
|
|
|
|
+ //completely removing it
|
|
|
|
+
|
|
|
|
+ // next is Literal
|
|
|
|
+ inc(fMaskInd,fCPLength);
|
|
|
|
+ if fMaskInd<=fMaskLimit then begin
|
|
|
|
+ fCPLength:=UTF8CodepointSizeFast(@fMask[fMaskInd]);
|
|
|
|
+ end else begin
|
|
|
|
+ Exception_IncompleteMask();
|
|
|
|
+ end;
|
|
|
|
+ end; //DEAD CODE??
|
|
|
|
+ }
|
|
|
|
+ AddLiteral;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
+ otherwise
|
|
|
|
+ begin
|
|
|
|
+ CompileOtherSpecialChars;
|
|
end;
|
|
end;
|
|
-
|
|
|
|
- if (not Valid) or (CharSet = []) then CharSetError;
|
|
|
|
-
|
|
|
|
- New(FMask.Chars[High(FMask.Chars)].SetValue);
|
|
|
|
- FMask.Chars[High(FMask.Chars)].SetValue^ := CharSet;
|
|
|
|
-
|
|
|
|
- Inc(FMask.MinLength);
|
|
|
|
- if FMask.MaxLength < MaxInt then Inc(FMask.MaxLength);
|
|
|
|
-
|
|
|
|
- Inc(I);
|
|
|
|
end;
|
|
end;
|
|
-
|
|
|
|
- procedure AddChar;
|
|
|
|
- begin
|
|
|
|
- SkipAnyText := False;
|
|
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TMaskUTF8.CompileAnyCharOrNone(QChar: Char; BracketsRequired: Boolean);
|
|
|
|
+var
|
|
|
|
+ QCount, lCharsGroupInsertSize: Integer;
|
|
|
|
+begin
|
|
|
|
+ //if any of the 2 conditions is true, this procedure should not have been called.
|
|
|
|
+ {$IFDEF debug_anycharornone}
|
|
|
|
+ writeln('CompileAnyCharOrNone: QChar=#',Ord(QChar),', BracketsRequired=',BracketsRequired,', fMask[fMaskInd]=',fMask[fMaskInd]);
|
|
|
|
+ if (BracketsRequired and (fMask[fMaskInd]<>'[')) or ((not BracketsRequired) and (fMask[fMaskInd]<>QChar)) then
|
|
|
|
+ Exception_InternalError();
|
|
|
|
+ {$ENDIF}
|
|
|
|
|
|
- SetLength(FMask.Chars, Length(FMask.Chars) + 1);
|
|
|
|
- with FMask.Chars[High(FMask.Chars)] do
|
|
|
|
|
|
+ if BracketsRequired then
|
|
begin
|
|
begin
|
|
- CharType := mcChar;
|
|
|
|
- CharValue := UpCase(AValue[I]);
|
|
|
|
|
|
+ Inc(fMaskInd); //consume the '['
|
|
|
|
+ //the following happens when mocSet amd mocRange are disabled and the first char after the bracket is not a '?'
|
|
|
|
+ if fMask[fMaskInd]<>QChar then
|
|
|
|
+ Exception_InvalidCharMask(fMask[fMaskInd], fMaskInd);
|
|
end;
|
|
end;
|
|
|
|
|
|
- Inc(FMask.MinLength);
|
|
|
|
- if FMask.MaxLength < MaxInt then Inc(FMask.MaxLength);
|
|
|
|
|
|
+ QCount:=1;
|
|
|
|
+ while (fMaskInd+QCount<=fMaskLimit) and (fMask[fMaskInd+QCount]=QChar) do Inc(QCount);
|
|
|
|
+ {$IFDEF debug_anycharornone}
|
|
|
|
+ writeln('CompileAnyCharOrNone: Nr of AnyCharOrNone-tokens: ',QCount);
|
|
|
|
+ if (fMaskInd+QCount>fMaskLimit) then writeln('(fMaskInd+QCount>fMaskLimit): ',fMaskInd+QCount,'>',fMaskLimit);
|
|
|
|
+ {$ENDIF}
|
|
|
|
+
|
|
|
|
+ //is Last found QChar also last character of the mask, while we expect a closing ']'?
|
|
|
|
+ if BracketsRequired and (fMaskInd+QCount>fMaskLimit) then
|
|
|
|
+ Exception_MissingCloseChar(']',fMaskInd+QCount-1);
|
|
|
|
|
|
- Inc(I);
|
|
|
|
|
|
+ {$IFDEF debug_anycharornone}
|
|
|
|
+ if not (fMask[fMaskInd+QCount]=']') then writeln('fMask[fMaskInd+QCount]: expected ], found: ',fMask[fMaskInd+QCount]);
|
|
|
|
+ {$ENDIF}
|
|
|
|
+
|
|
|
|
+ if BracketsRequired and not (fMask[fMaskInd+QCount]=']') then
|
|
|
|
+ Exception_InvalidCharMask(fMask[fMaskInd+QCount],fMaskInd+QCount);
|
|
|
|
+
|
|
|
|
+ Add(TMaskParsedCode.CharsGroupBegin);
|
|
|
|
+ lCharsGroupInsertSize:=fMaskCompiledIndex;
|
|
|
|
+ Add(0);
|
|
|
|
+ Add(TMaskParsedCode.AnyCharOrNone);
|
|
|
|
+ Add(1);
|
|
|
|
+ if QCount>1 then
|
|
|
|
+ IncrementLastCounterBy(TMaskParsedCode.AnyCharOrNone,QCount-1);
|
|
|
|
+ PInteger(@fMaskCompiled[lCharsGroupInsertSize])^:=fMaskCompiledIndex;
|
|
|
|
+ Add(TMaskParsedCode.CharsGroupEnd);
|
|
|
|
+ fLastOC:=TMaskParsedCode.CharsGroupEnd;
|
|
|
|
+ Inc(fMatchMaximumLiteralBytes,QCount*4);
|
|
|
|
+ if BracketsRequired then
|
|
|
|
+ Inc(fMaskInd,QCount) //go to ending ']'
|
|
|
|
+ else
|
|
|
|
+ Inc(fMaskInd,QCount-1); //go to last found QChar
|
|
|
|
+
|
|
|
|
+ {$IFDEF debug_anycharornone}
|
|
|
|
+ write('fMaskInd=',fMaskInd,', fMaskLimit=',fMaskLimit,' fMask[fMaskInd]=');if fMaskInd<=fMaskLimit then writeln('#',Ord(fMask[fMaskInd]),': ',fMask[fMaskInd])else writeln('>>');
|
|
|
|
+ writeln('CompileAnyCharOrNone end.');
|
|
|
|
+ {$ENDIF}
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TMaskUTF8.Compile;
|
|
|
|
+begin
|
|
|
|
+ PrepareCompile;
|
|
|
|
+ if fCaseSensitive then
|
|
|
|
+ fMask:=fOriginalMask
|
|
|
|
+ else
|
|
|
|
+ fMask:=UTF8LowerCase(fOriginalMask);
|
|
|
|
+ fMaskLimit:=Length(fMask);
|
|
|
|
+ fLastOC:=TMaskParsedCode.Literal;
|
|
|
|
+ SetLength(fMaskCompiled,0);
|
|
|
|
+ fMaskInd:=1;
|
|
|
|
+
|
|
|
|
+ while fMaskInd<=fMaskLimit do begin //while
|
|
|
|
+ fCPLength:=UTF8CodepointSizeFast(@fMask[fMaskInd]);
|
|
|
|
+ if (mocEscapeChar in fMaskOpcodesAllowed) and (fMask[fMaskInd]=fMaskEscapeChar) then
|
|
|
|
+ CompileEscapeCharPlusLiteral
|
|
|
|
+ else begin // not an escaped literal
|
|
|
|
+ //writeln('Compile while: fMask[',fMaskInd,']=',fMask[fMaskInd]);
|
|
|
|
+ if (fMask[fMaskInd] in DefaultSpecialChars) or IsSpecialChar(fMask[fMaskInd]) then begin // handle special chars
|
|
|
|
+ CompileSpecialChars;
|
|
|
|
+ //writeln('After CompileSpecialChar');
|
|
|
|
+ //write('fMaskInd=',fMaskInd,', fMaskLimit=',fMaskLimit,' fMask[fMaskInd]=');if fMaskInd<=fMaskLimit then writeln('#',Ord(fMask[fMaskInd]),': ',fMask[fMaskInd])else writeln('>>');
|
|
|
|
+ end //handle special chars
|
|
|
|
+ else
|
|
|
|
+ begin
|
|
|
|
+ //writeln('Compile: AddLiteral: fMask[',fMaskInd,']=',fMask[fMaskInd]);
|
|
|
|
+ AddLiteral;
|
|
|
|
+ end;
|
|
|
|
+ inc(fMaskInd,fCPLength);
|
|
|
|
+ end; //not an escaped literal
|
|
|
|
+ end; //while
|
|
|
|
+
|
|
|
|
+ SetLength(fMaskCompiled,fMaskCompiledIndex);
|
|
|
|
+ fMaskCompiledLimit:=fMaskCompiledIndex-1;
|
|
|
|
+ {
|
|
|
|
+ fMaskIsCompiled Indicates that Compile was succesfull
|
|
|
|
+ If you override this method make sure that it is only set to True if
|
|
|
|
+ the overridden method also succeeds!
|
|
|
|
+ }
|
|
|
|
+ fMaskIsCompiled:=True;
|
|
|
|
+
|
|
|
|
+ //writeln('Compile end.');
|
|
|
|
+ {$IFDEF debug_maskcompiled}
|
|
|
|
+ writeln('fMaskInd=',fMaskInd,', fMaskLimit=',fMaskLimit);
|
|
|
|
+ writeln('fMaskCompiled:');
|
|
|
|
+ writeln('fMaskCompiledLimit=',fMaskCompiledLimit);
|
|
|
|
+ writeln('fMaskCompiledIndex=',fMaskCompiledIndex);
|
|
|
|
+ DumpMaskCompiled;
|
|
|
|
+ {$ENDIF}
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+class function TMaskUTF8.CompareUTF8Sequences(const P1, P2: PChar): integer;
|
|
|
|
+var
|
|
|
|
+ l1,l2: integer;
|
|
|
|
+ l: integer;
|
|
|
|
+begin
|
|
|
|
+ l1:=UTF8CodepointSizeFast(p1);
|
|
|
|
+ l2:=UTF8CodepointSizeFast(p2);
|
|
|
|
+ Result:=0;
|
|
|
|
+ l:=0;
|
|
|
|
+ while (l<l1) and (l<l2) do begin
|
|
|
|
+ Result:=Integer((P1+l)^)-integer((P2+l)^);
|
|
|
|
+ if Result<>0 then exit;
|
|
|
|
+ inc(l);
|
|
end;
|
|
end;
|
|
-
|
|
|
|
-begin
|
|
|
|
- SetLength(FMask.Chars, 0);
|
|
|
|
- FMask.MinLength := 0;
|
|
|
|
- FMask.MaxLength := 0;
|
|
|
|
- SkipAnyText := False;
|
|
|
|
-
|
|
|
|
- I := 1;
|
|
|
|
- while I <= Length(AValue) do
|
|
|
|
- begin
|
|
|
|
- case AValue[I] of
|
|
|
|
- '*': AddAnyText;
|
|
|
|
- '?': AddAnyChar;
|
|
|
|
- '[': AddCharSet;
|
|
|
|
- else AddChar;
|
|
|
|
|
|
+ Result:=l1-l2;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TMaskUTF8.intfMatches(aMatchOffset: integer; aMaskIndex: integer): TMaskFailCause;
|
|
|
|
+var
|
|
|
|
+ c1,c2: PChar;
|
|
|
|
+ lFailCause: TMaskFailCause;
|
|
|
|
+ lNegateCharGroup: Boolean;
|
|
|
|
+ lSkipOnSuccessGroup: integer;
|
|
|
|
+ t1: Boolean;
|
|
|
|
+ j: integer;
|
|
|
|
+ lTryCounter: integer;
|
|
|
|
+begin
|
|
|
|
+ lSkipOnSuccessGroup:=0;
|
|
|
|
+ Result:=mfcUnexpectedEnd;
|
|
|
|
+ lNegateCharGroup:=false;
|
|
|
|
+ while aMaskIndex<=fMaskCompiledLimit do begin
|
|
|
|
+ case TMaskParsedCode(fMaskCompiled[aMaskIndex]) of
|
|
|
|
+ TMaskParsedCode.Literal:
|
|
|
|
+ begin
|
|
|
|
+ if aMatchOffset>fMatchStringLimit then
|
|
|
|
+ exit(TMaskFailCause.mfcMatchStringExhausted); // Error, no char to match.
|
|
|
|
+ inc(aMaskIndex);
|
|
|
|
+ if CompareUTF8Sequences(@fMaskCompiled[aMaskIndex],@fMatchString[aMatchOffset])<>0 then
|
|
|
|
+ exit(TMaskFailCause.mfcMaskNotMatch);
|
|
|
|
+ inc(aMaskIndex,UTF8CodepointSizeFast(@fMaskCompiled[aMaskIndex]));
|
|
|
|
+ inc(aMatchOffset,UTF8CodepointSizeFast(@fMatchString[aMatchOffset]));
|
|
|
|
+ end;
|
|
|
|
+ TMaskParsedCode.AnyChar:
|
|
|
|
+ begin
|
|
|
|
+ inc(aMaskIndex);
|
|
|
|
+ if aMatchOffset>fMatchStringLimit then
|
|
|
|
+ exit(TMaskFailCause.mfcMatchStringExhausted); // Error, no char to match.
|
|
|
|
+ inc(aMatchOffset,UTF8CodepointSizeFast(@fMatchString[aMatchOffset]));
|
|
|
|
+ end;
|
|
|
|
+ TMaskParsedCode.Negate:
|
|
|
|
+ begin
|
|
|
|
+ lNegateCharGroup:=true;
|
|
|
|
+ inc(aMaskIndex);
|
|
|
|
+ end;
|
|
|
|
+ TMaskParsedCode.CharsGroupBegin:
|
|
|
|
+ begin
|
|
|
|
+ lNegateCharGroup:=false;
|
|
|
|
+ inc(aMaskIndex);
|
|
|
|
+ lSkipOnSuccessGroup:=PInteger(@fMaskCompiled[aMaskIndex])^;
|
|
|
|
+ inc(aMaskIndex,sizeof(integer));
|
|
|
|
+ end;
|
|
|
|
+ TMaskParsedCode.CharsGroupEnd:
|
|
|
|
+ begin
|
|
|
|
+ if lNegateCharGroup then begin
|
|
|
|
+ aMaskIndex:=lSkipOnSuccessGroup+1;
|
|
|
|
+ inc(aMatchOffset,UTF8CodepointSizeFast(@fMatchString[aMatchOffset]));
|
|
|
|
+ end
|
|
|
|
+ else
|
|
|
|
+ exit(TMaskFailCause.mfcMaskNotMatch);
|
|
|
|
+ end;
|
|
|
|
+ TMaskParsedCode.OptionalChar:
|
|
|
|
+ begin
|
|
|
|
+ inc(aMaskIndex);
|
|
|
|
+ if aMatchOffset>fMatchStringLimit then
|
|
|
|
+ exit(TMaskFailCause.mfcMatchStringExhausted); // Error, no char to match.
|
|
|
|
+ if CompareUTF8Sequences(@fMaskCompiled[aMaskIndex],@fMatchString[aMatchOffset])=0 then begin
|
|
|
|
+ if lNegateCharGroup then
|
|
|
|
+ exit(TMaskFailCause.mfcMaskNotMatch);
|
|
|
|
+ aMaskIndex:=lSkipOnSuccessGroup+1;
|
|
|
|
+ inc(aMatchOffset,UTF8CodepointSizeFast(@fMatchString[aMatchOffset]));
|
|
|
|
+ end
|
|
|
|
+ else
|
|
|
|
+ inc(aMaskIndex,UTF8CodepointSizeFast(@fMaskCompiled[aMaskIndex]));
|
|
|
|
+ end;
|
|
|
|
+ TMaskParsedCode.Range:
|
|
|
|
+ begin
|
|
|
|
+ if aMatchOffset>fMatchStringLimit then
|
|
|
|
+ exit(TMaskFailCause.mfcMatchStringExhausted); // Error, no char to match.
|
|
|
|
+ inc(aMaskIndex);
|
|
|
|
+ c1:=@fMaskCompiled[aMaskIndex];
|
|
|
|
+ inc(aMaskIndex,UTF8CodepointSizeFast(C1));
|
|
|
|
+ c2:=@fMaskCompiled[aMaskIndex];
|
|
|
|
+ inc(aMaskIndex,UTF8CodepointSizeFast(C2));
|
|
|
|
+ t1:=(CompareUTF8Sequences(@fMatchString[aMatchOffset],c1)>=0) and
|
|
|
|
+ (CompareUTF8Sequences(@fMatchString[aMatchOffset],c2)<=0);
|
|
|
|
+ if t1 then begin
|
|
|
|
+ if not lNegateCharGroup then begin
|
|
|
|
+ //Jump to CharsGroupEnd+1 because if CharsGroupEnd is reached
|
|
|
|
+ //it means that all optional chars and ranges have not matched the Utf8String.
|
|
|
|
+ aMaskIndex:=lSkipOnSuccessGroup+1;
|
|
|
|
+ inc(aMatchOffset,UTF8CodepointSizeFast(@fMatchString[aMatchOffset]));
|
|
|
|
+ end
|
|
|
|
+ else
|
|
|
|
+ exit(TMaskFailCause.mfcMaskNotMatch);
|
|
|
|
+ end
|
|
|
|
+ end;
|
|
|
|
+ TMaskParsedCode.AnyCharToNext:
|
|
|
|
+ begin
|
|
|
|
+ // if last is "*", everything in remain data matches
|
|
|
|
+ if aMaskIndex=fMaskCompiledLimit then
|
|
|
|
+ exit(TMaskFailCause.mfcSuccess);
|
|
|
|
+ if aMatchOffset>fMatchStringLimit then begin
|
|
|
|
+ if aMaskIndex=fMaskCompiledLimit then
|
|
|
|
+ exit(TMaskFailCause.mfcSuccess);
|
|
|
|
+ exit(TMaskFailCause.mfcMatchStringExhausted);
|
|
|
|
+ end;
|
|
|
|
+ inc(aMaskIndex);
|
|
|
|
+ while aMatchOffset<=fMatchStringLimit do begin
|
|
|
|
+ lFailCause:=intfMatches(aMatchOffset,aMaskIndex);
|
|
|
|
+ if lFailCause=TMaskFailCause.mfcSuccess then
|
|
|
|
+ exit(TMaskFailCause.mfcSuccess)
|
|
|
|
+ else if lFailCause=TMaskFailCause.mfcMatchStringExhausted then
|
|
|
|
+ exit(TMaskFailCause.mfcMatchStringExhausted);
|
|
|
|
+ inc(aMatchOffset,UTF8CodepointSizeFast(@fMatchString[aMatchOffset]));
|
|
|
|
+ end;
|
|
|
|
+ exit(TMaskFailCause.mfcMatchStringExhausted);
|
|
|
|
+ end;
|
|
|
|
+ TMaskParsedCode.AnyCharOrNone:
|
|
|
|
+ begin
|
|
|
|
+ inc(aMaskIndex);
|
|
|
|
+ lTryCounter:=PInteger(@fMaskCompiled[aMaskIndex])^;
|
|
|
|
+ inc(aMaskIndex,sizeof(integer));
|
|
|
|
+ if TMaskParsedCode(fMaskCompiled[aMaskIndex])<>TMaskParsedCode.CharsGroupEnd then
|
|
|
|
+ begin //writeln('TMaskUtf8.infMatches: error parsing AnyCharOrNone, missing CharsGroupEnd, fMaskCompiled[',aMaskIndex,']=',fMaskCompiled[aMaskIndex]);
|
|
|
|
+ Exception_InternalError() end
|
|
|
|
+ else
|
|
|
|
+ aMaskIndex:=lSkipOnSuccessGroup+1;
|
|
|
|
+
|
|
|
|
+ // Try to match remain mask eating, 0,1,2,...,lTryCounter chars.
|
|
|
|
+ for j := 0 to lTryCounter do begin
|
|
|
|
+ if aMatchOffset>fMatchStringLimit then begin
|
|
|
|
+ if aMaskIndex=fMaskCompiledLimit+1 then
|
|
|
|
+ exit(TMaskFailCause.mfcSuccess);
|
|
|
|
+ exit(TMaskFailCause.mfcMatchStringExhausted);
|
|
|
|
+ end;
|
|
|
|
+ lFailCause:=intfMatches(aMatchOffset,aMaskIndex);
|
|
|
|
+ if lFailCause=TMaskFailCause.mfcSuccess then
|
|
|
|
+ exit(TMaskFailCause.mfcSuccess)
|
|
|
|
+ else if lFailCause=TMaskFailCause.mfcMatchStringExhausted then
|
|
|
|
+ exit(TMaskFailCause.mfcMatchStringExhausted);
|
|
|
|
+ inc(aMatchOffset,UTF8CodepointSizeFast(@fMatchString[aMatchOffset]));
|
|
|
|
+ end;
|
|
|
|
+ exit(TMaskFailCause.mfcMatchStringExhausted);
|
|
|
|
+ end;
|
|
|
|
+ else // case
|
|
|
|
+ begin
|
|
|
|
+ writeln('TMaskUtf8.infMatches: XXXX InternalError');
|
|
|
|
+ Exception_InternalError();
|
|
|
|
+ end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
+ if (aMaskIndex>fMaskCompiledLimit) and (aMatchOffset>fMatchStringLimit) then
|
|
|
|
+ Result:=TMaskFailCause.mfcSuccess
|
|
|
|
+ else if aMaskIndex>fMaskCompiledLimit then
|
|
|
|
+ Result:=TMaskFailCause.mfcMaskExhausted
|
|
|
|
+ else
|
|
|
|
+ Result:=TMaskFailCause.mfcMatchStringExhausted;
|
|
end;
|
|
end;
|
|
|
|
|
|
-destructor TMask.Destroy;
|
|
|
|
|
|
+{$IFDEF debug_maskcompiled}
|
|
|
|
+procedure TMaskUTF8.DumpMaskCompiled;
|
|
var
|
|
var
|
|
- I: Integer;
|
|
|
|
|
|
+ i: Integer;
|
|
|
|
+ b: Byte;
|
|
begin
|
|
begin
|
|
- for I := 0 to High(FMask.Chars) do
|
|
|
|
- if FMask.Chars[I].CharType = mcCharSet then
|
|
|
|
- Dispose(FMask.Chars[I].SetValue);
|
|
|
|
|
|
+ for i := low(fMaskCompiled) to fMaskCompiledIndex-1 do
|
|
|
|
+ begin
|
|
|
|
+ b := fMaskCompiled[i];
|
|
|
|
+ writeln(i:2,': ',b:3);
|
|
|
|
+ end;
|
|
|
|
+end;
|
|
|
|
+{$ENDIF}
|
|
|
|
|
|
- inherited Destroy;
|
|
|
|
|
|
+constructor TMaskUTF8.Create(const aMask: Utf8String);
|
|
|
|
+begin
|
|
|
|
+ Create(aMask, System.FileNameCaseSensitive, DefaultMaskOpCodes);
|
|
end;
|
|
end;
|
|
|
|
|
|
-function TMask.Matches(const AFileName: UTF8string): Boolean;
|
|
|
|
|
|
+constructor TMaskUTF8.Create(const aMask: Utf8String; aCaseSensitive: Boolean);
|
|
|
|
+begin
|
|
|
|
+ Create(aMask, aCaseSensitive, DefaultMaskOpCodes);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+constructor TMaskUTF8.Create(const aMask: Utf8String;
|
|
|
|
+ aCaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes);
|
|
|
|
+begin
|
|
|
|
+ inherited Create(aCaseSensitive,aOpcodesAllowed);
|
|
|
|
+ fOriginalMask:=aMask;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+function TMaskUTF8.Matches(const aStringToMatch: Utf8String): Boolean;
|
|
|
|
+begin
|
|
|
|
+ if not fMaskIsCompiled then Compile;
|
|
|
|
+ if fCaseSensitive then
|
|
|
|
+ fMatchString:=aStringToMatch
|
|
|
|
+ else
|
|
|
|
+ fMatchString:=UTF8LowerCase(aStringToMatch);
|
|
|
|
+ fMatchStringLimit:=length(fMatchString);
|
|
|
|
+ if (fMatchStringLimit>=fMatchMinimumLiteralBytes)
|
|
|
|
+ and (fMatchStringLimit<=fMatchMaximumLiteralBytes) then
|
|
|
|
+ Result:=intfMatches(1,0)=TMaskFailCause.mfcSuccess
|
|
|
|
+ else
|
|
|
|
+ Result:=false; // There are too many or not enough bytes to match the string
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+{ TWindowsMask }
|
|
|
|
+
|
|
|
|
+procedure TWindowsMaskUTF8.SetMask(AValue: Utf8String);
|
|
|
|
+begin
|
|
|
|
+ if (AValue = fWindowsMask) then Exit;
|
|
|
|
+ inherited SetMask(AValue);
|
|
|
|
+ fWindowsMask := AValue;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TWindowsMaskUTF8.GetMask: Utf8String;
|
|
|
|
+begin
|
|
|
|
+ Result := fWindowsMask;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TWindowsMaskUTF8.SetWindowsQuirkAllowed(AValue: TWindowsQuirks);
|
|
|
|
+begin
|
|
|
|
+ if fWindowsQuirkAllowed = AValue then Exit;
|
|
|
|
+ fWindowsQuirkAllowed := AValue;
|
|
|
|
+ //if (wqFilenameEnd in fWindowsQuirkAllowed) then
|
|
|
|
+ // Include(fMaskOpcodesAllowed, mocAnyCharOrNone);
|
|
|
|
+ fMaskIsCompiled := False;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TWindowsMaskUTF8.CompileOtherSpecialChars;
|
|
|
|
+begin
|
|
|
|
+ inherited CompileOtherSpecialChars;
|
|
|
|
+ if (fMask[fMaskInd]=#0) and not (wqFileNameEnd in self.fWindowsQuirkInUse) then
|
|
|
|
+ Exception_InternalError;
|
|
|
|
+ CompileAnyCharOrNone(#0, False);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TWindowsMaskUTF8.IsSpecialChar(AChar: Char): Boolean;
|
|
|
|
+begin
|
|
|
|
+ Result := (AChar = #0);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+class procedure TWindowsMaskUTF8.SplitFileNameExtension(
|
|
|
|
+ const aSourceFileName: Utf8String; out aFileName: Utf8String;
|
|
|
|
+ out aExtension: Utf8String; aIsMask: Boolean);
|
|
var
|
|
var
|
|
- L: Integer;
|
|
|
|
- S: UTF8string;
|
|
|
|
-
|
|
|
|
- function MatchToEnd(MaskIndex, CharIndex: Integer): Boolean;
|
|
|
|
|
|
+ j: Integer;
|
|
|
|
+ lLowLimit: integer;
|
|
|
|
+begin
|
|
|
|
+ // Default values
|
|
|
|
+ aFileName:=aSourceFileName;
|
|
|
|
+ aExtension:='';
|
|
|
|
+
|
|
|
|
+ // This is because .foo is considered a file name ".foo" as one.
|
|
|
|
+ if aIsMask then
|
|
|
|
+ lLowLimit:=0
|
|
|
|
+ else
|
|
|
|
+ lLowLimit:=1;
|
|
|
|
+
|
|
|
|
+ j:=Length(aSourceFileName);
|
|
|
|
+ while j>lLowLimit do begin
|
|
|
|
+ if aSourceFileName[j]='.' then begin
|
|
|
|
+ aFileName:=copy(aSourceFileName,1,j-1);
|
|
|
|
+ aExtension:=copy(aSourceFileName,j);
|
|
|
|
+ break;
|
|
|
|
+ end;
|
|
|
|
+ dec(j);
|
|
|
|
+ end;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+constructor TWindowsMaskUTF8.Create(const aMask: Utf8String;
|
|
|
|
+ aCaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes);
|
|
|
|
+begin
|
|
|
|
+ Create(aMask, aCaseSensitive, aOpcodesAllowed, DefaultWindowsQuirks);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+constructor TWindowsMaskUTF8.Create(const aMask: Utf8String; aCaseSensitive: Boolean;
|
|
|
|
+ aOpcodesAllowed: TMaskOpCodes; aWindowsQuirksAllowed: TWindowsQuirks);
|
|
|
|
+begin
|
|
|
|
+ fWindowsQuirkAllowed:=aWindowsQuirksAllowed;
|
|
|
|
+ fWindowsMask:=aMask;
|
|
|
|
+ //if (wqFilenameEnd in fWindowsQuirkAllowed) then
|
|
|
|
+ // Include(aOpcodesAllowed, mocAnyCharOrNone);
|
|
|
|
+ inherited Create(aMask,aCaseSensitive,aOpcodesAllowed);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TWindowsMaskUTF8.Compile;
|
|
|
|
+
|
|
|
|
+ function OptionalQMarksAtEnd(aMask: Utf8String): Utf8String;
|
|
var
|
|
var
|
|
- I, J: Integer;
|
|
|
|
|
|
+ //lCounter: integer;
|
|
|
|
+ i: integer;
|
|
begin
|
|
begin
|
|
- Result := False;
|
|
|
|
-
|
|
|
|
- for I := MaskIndex to High(FMask.Chars) do
|
|
|
|
- begin
|
|
|
|
- case FMask.Chars[I].CharType of
|
|
|
|
- mcChar:
|
|
|
|
- begin
|
|
|
|
- if CharIndex > L then Exit;
|
|
|
|
- //DebugLn('Match ' + S[CharIndex] + '<?>' + FMask.Chars[I].CharValue);
|
|
|
|
- if S[CharIndex] <> FMask.Chars[I].CharValue then Exit;
|
|
|
|
- Inc(CharIndex);
|
|
|
|
- end;
|
|
|
|
- mcCharSet:
|
|
|
|
- begin
|
|
|
|
- if CharIndex > L then Exit;
|
|
|
|
- if FMask.Chars[I].Negative xor
|
|
|
|
- (S[CharIndex] in FMask.Chars[I].SetValue^) then Inc(CharIndex)
|
|
|
|
- else Exit;
|
|
|
|
- end;
|
|
|
|
- mcAnyChar:
|
|
|
|
- begin
|
|
|
|
- if CharIndex > L then Exit;
|
|
|
|
- Inc(CharIndex);
|
|
|
|
- end;
|
|
|
|
- mcAnyText:
|
|
|
|
- begin
|
|
|
|
- if I = High(FMask.Chars) then
|
|
|
|
- begin
|
|
|
|
- Result := True;
|
|
|
|
- Exit;
|
|
|
|
- end;
|
|
|
|
-
|
|
|
|
- for J := CharIndex to L do
|
|
|
|
- if MatchToEnd(I + 1, J) then
|
|
|
|
- begin
|
|
|
|
- Result := True;
|
|
|
|
- Exit;
|
|
|
|
- end;
|
|
|
|
- end;
|
|
|
|
- end;
|
|
|
|
|
|
+ //Change ending ? in #0
|
|
|
|
+ Result := aMask;
|
|
|
|
+ for i := Length(aMask) downto 1 do begin
|
|
|
|
+ if Result[i]='?' then
|
|
|
|
+ Result[i]:=#0
|
|
|
|
+ else
|
|
|
|
+ Exit;
|
|
|
|
+ end;
|
|
|
|
+ {
|
|
|
|
+ lCounter:=0;
|
|
|
|
+ for i := Length(aMask) downto 1 do begin
|
|
|
|
+ if aMask[i]='?' then
|
|
|
|
+ inc(lCounter)
|
|
|
|
+ else
|
|
|
|
+ break;
|
|
end;
|
|
end;
|
|
-
|
|
|
|
- Result := CharIndex > L;
|
|
|
|
|
|
+ if lCounter>0 then
|
|
|
|
+ aMask:=copy(aMask,1,Length(aMask)-lCounter)+'['+StringOfChar('?',lCounter)+']';
|
|
|
|
+ Result:=aMask;
|
|
|
|
+ }
|
|
end;
|
|
end;
|
|
-
|
|
|
|
|
|
+
|
|
|
|
+var
|
|
|
|
+ lFileNameMask: Utf8String;
|
|
|
|
+ lExtensionMask: Utf8String;
|
|
|
|
+ lModifiedMask: Utf8String;
|
|
|
|
+ ZeroPos: SizeInt;
|
|
|
|
+
|
|
begin
|
|
begin
|
|
- Result := False;
|
|
|
|
- L := Length(AFileName);
|
|
|
|
- if L = 0 then
|
|
|
|
- begin
|
|
|
|
- if FMask.MinLength = 0 then Result := True;
|
|
|
|
- Exit;
|
|
|
|
|
|
+ lModifiedMask:=fWindowsMask;
|
|
|
|
+
|
|
|
|
+ //We cannot have #0 in the mask,since we use it internally
|
|
|
|
+ ZeroPos:=Pos(#0, fWindowsMask);
|
|
|
|
+ if ZeroPos>0 then
|
|
|
|
+ Exception_InvalidCharMask('NullCharacter', ZeroPos);
|
|
|
|
+
|
|
|
|
+ // Quirk "blah.*" = "blah*"
|
|
|
|
+ if wqAnyExtension in fWindowsQuirkAllowed then begin
|
|
|
|
+ if (RightStr(lModifiedMask,2)='.*') and (Length(lModifiedMask)>2) then begin
|
|
|
|
+ //if RightStr(lModifiedMask,3)='*.*' then begin
|
|
|
|
+ lModifiedMask:=copy(lModifiedMask,1,Length(lModifiedMask)-2);
|
|
|
|
+ fWindowsQuirkInUse:=fWindowsQuirkInUse+[wqAnyExtension];
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ SplitFileNameExtension(lModifiedMask,lFileNameMask,lExtensionMask,true);
|
|
|
|
+
|
|
|
|
+ // Quirk "blah.abc" = "blah.abc*"
|
|
|
|
+ if wqExtension3More in fWindowsQuirkAllowed then begin
|
|
|
|
+ if (Length(lExtensionMask)=4) and (Length(lFileNameMask)>0) then begin
|
|
|
|
+ lExtensionMask:=lExtensionMask+'*';
|
|
|
|
+ fWindowsQuirkInUse:=fWindowsQuirkInUse+[wqExtension3More];
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ // Quirk "" = "*"
|
|
|
|
+ if (Length(lFileNameMask)=0) and (Length(lExtensionMask)=0) then begin
|
|
|
|
+ if wqEmptyIsAny in fWindowsQuirkAllowed then begin
|
|
|
|
+ lFileNameMask:='*';
|
|
|
|
+ fWindowsQuirkInUse:=fWindowsQuirkInUse+[wqEmptyIsAny];
|
|
|
|
+ end;
|
|
|
|
+ end else begin
|
|
|
|
+ // Quirk ".abc"
|
|
|
|
+ if wqAllByExtension in fWindowsQuirkAllowed then begin
|
|
|
|
+ if (Length(lFileNameMask)=0) and (length(lExtensionMask)>0) then begin
|
|
|
|
+ if lExtensionMask[1]='.' then begin
|
|
|
|
+ lFileNameMask:='*';
|
|
|
|
+ fWindowsQuirkInUse:=fWindowsQuirkInUse+[wqAllByExtension];
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
end;
|
|
end;
|
|
-
|
|
|
|
- if (L < FMask.MinLength) or (L > FMask.MaxLength) then Exit;
|
|
|
|
|
|
|
|
- S := UpperCase(AFileName);
|
|
|
|
- Result := MatchToEnd(0, 1);
|
|
|
|
|
|
+
|
|
|
|
+ // Quirk "file???.ab?" matches "file1.ab1" and "file123.ab"
|
|
|
|
+ if wqFilenameEnd in fWindowsQuirkAllowed then begin
|
|
|
|
+ lFileNameMask:=OptionalQMarksAtEnd(lFileNameMask);
|
|
|
|
+ lExtensionMask:=OptionalQMarksAtEnd(lExtensionMask);
|
|
|
|
+ if (Pos(#0, lFileNameMask)>0) or (Pos(#0, lExtensionMask)>0) then
|
|
|
|
+ fWindowsQuirkInUse:=fWindowsQuirkInUse+[wqFilenameEnd];
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ if wqNoExtension in fWindowsQuirkAllowed then begin
|
|
|
|
+ if Length(lExtensionMask)=1 then begin
|
|
|
|
+ fWindowsQuirkInUse:=fWindowsQuirkInUse+[wqNoExtension];
|
|
|
|
+ lExtensionMask:='';
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+
|
|
|
|
+ inherited Mask:=lFileNameMask+lExtensionMask;
|
|
|
|
+ inherited Compile;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TWindowsMaskUTF8.Matches(const aFileName: Utf8String): Boolean;
|
|
|
|
+var
|
|
|
|
+ lFileName, lExtension: Utf8String;
|
|
|
|
+begin
|
|
|
|
+ if not fMaskIsCompiled then
|
|
|
|
+ Compile;
|
|
|
|
+ if wqNoExtension in fWindowsQuirkInUse then begin
|
|
|
|
+ SplitFileNameExtension(aFileName,lFileName,lExtension,false);
|
|
|
|
+ // wqNoExtension = Empty extension
|
|
|
|
+ //if lExtension<>'' then exit(false);
|
|
|
|
+ // Its not clear if a file "file." should match an "*." mask because
|
|
|
|
+ // there is no way in Windows that a file ends with a dot.
|
|
|
|
+ if (lExtension<>'') and (lExtension<>'.') then
|
|
|
|
+ exit(false)
|
|
|
|
+ end else if wqAnyExtension in fWindowsQuirkInUse then begin
|
|
|
|
+ SplitFileNameExtension(aFileName,lFileName,lExtension,false);
|
|
|
|
+ Result:=inherited Matches(lFileName);
|
|
|
|
+ exit;
|
|
|
|
+ end;
|
|
|
|
+ Result:=Inherited Matches(aFileName);
|
|
end;
|
|
end;
|
|
|
|
|
|
|
|
+
|
|
{ TParseStringList }
|
|
{ TParseStringList }
|
|
|
|
|
|
-constructor TParseStringList.Create(const AText, ASeparators: UTF8string);
|
|
|
|
|
|
+constructor TParseStringList.Create(const AText, ASeparators: Utf8String);
|
|
var
|
|
var
|
|
I, S: Integer;
|
|
I, S: Integer;
|
|
begin
|
|
begin
|
|
inherited Create;
|
|
inherited Create;
|
|
-
|
|
|
|
S := 1;
|
|
S := 1;
|
|
for I := 1 to Length(AText) do
|
|
for I := 1 to Length(AText) do
|
|
begin
|
|
begin
|
|
if Pos(AText[I], ASeparators) > 0 then
|
|
if Pos(AText[I], ASeparators) > 0 then
|
|
begin
|
|
begin
|
|
- if I > S then Add(Copy(AText, S, I - S));
|
|
|
|
|
|
+ if I > S then
|
|
|
|
+ Add(Copy(AText, S, I - S));
|
|
S := I + 1;
|
|
S := I + 1;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
-
|
|
|
|
- if Length(AText) >= S then Add(Copy(AText, S, Length(AText) - S + 1));
|
|
|
|
|
|
+ if Length(AText) >= S then
|
|
|
|
+ Add(Copy(AText, S, Length(AText) - S + 1));
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TMaskList }
|
|
{ TMaskList }
|
|
|
|
|
|
|
|
+constructor TMaskList.Create(const aValue: Utf8String; aSeparator: Char;
|
|
|
|
+ CaseSensitive: Boolean; aOpcodesAllowed: TMaskOpCodes);
|
|
|
|
+begin
|
|
|
|
+ fMask := aValue;
|
|
|
|
+ fSeparator := aSeparator;
|
|
|
|
+ fCaseSensitive := CaseSensitive;
|
|
|
|
+ fMaskOpcodes := aOpcodesAllowed;
|
|
|
|
+ fAutoReverseRange := True;
|
|
|
|
+
|
|
|
|
+ fMasks := TObjectList.Create(True);
|
|
|
|
+ FMaskClass := GetMaskClass;
|
|
|
|
+ AddMasksToList(aValue, aSeparator, CaseSensitive, aOpcodesAllowed);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+destructor TMaskList.Destroy;
|
|
|
|
+begin
|
|
|
|
+ fMasks.Free;
|
|
|
|
+ inherited Destroy;
|
|
|
|
+end;
|
|
|
|
+
|
|
function TMaskList.GetItem(Index: Integer): TMask;
|
|
function TMaskList.GetItem(Index: Integer): TMask;
|
|
begin
|
|
begin
|
|
- Result := TMask(FMasks.Items[Index]);
|
|
|
|
|
|
+ Result := TMask(fMasks.Items[Index]);
|
|
end;
|
|
end;
|
|
|
|
|
|
-function TMaskList.GetCount: Integer;
|
|
|
|
|
|
+procedure TMaskList.SetAutoReverseRange(AValue: Boolean);
|
|
|
|
+var
|
|
|
|
+ i: Integer;
|
|
begin
|
|
begin
|
|
- Result := FMasks.Count;
|
|
|
|
|
|
+ if fAutoReverseRange = AValue then Exit;
|
|
|
|
+ fAutoReverseRange := AValue;
|
|
|
|
+ for i := 0 to fMasks.Count - 1 do
|
|
|
|
+ TMask(fMasks.Items[i]).AutoReverseRange := fAutoReverseRange;
|
|
end;
|
|
end;
|
|
|
|
|
|
-constructor TMaskList.Create(const AValue: UTF8string; ASeparator: Char);
|
|
|
|
|
|
+procedure TMaskList.SetCaseSensitive(AValue: Boolean);
|
|
|
|
+var
|
|
|
|
+ i: Integer;
|
|
|
|
+begin
|
|
|
|
+ if fCaseSensitive = AValue then Exit;
|
|
|
|
+ fCaseSensitive := AValue;
|
|
|
|
+ for i := 0 to fMasks.Count - 1 do
|
|
|
|
+ TMask(fMasks.Items[i]).CaseSensitive := fCaseSensitive;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TMaskList.SetMask(AValue: Utf8String);
|
|
|
|
+begin
|
|
|
|
+ if fMask = AValue then Exit;
|
|
|
|
+ fMask := AValue;
|
|
|
|
+ fMasks.Clear;
|
|
|
|
+ AddMasksToList(fMask, fSeparator, fCaseSensitive, fMaskOpCodes);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TMaskList.SetMaskOpCodes(AValue: TMaskOpCodes);
|
|
|
|
+var
|
|
|
|
+ i: Integer;
|
|
|
|
+begin
|
|
|
|
+ if FMaskOpCodes = AValue then Exit;
|
|
|
|
+ fMaskOpCodes := AValue;
|
|
|
|
+ for i := 0 to fMasks.Count - 1 do
|
|
|
|
+ TMask(fMasks.Items[i]).MaskOpCodes := fMaskOpcodes;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+function TMaskList.GetMaskClass: TMaskClass;
|
|
|
|
+begin
|
|
|
|
+ Result := TMask;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure TMaskList.AddMasksToList(const aValue: Utf8String; aSeparator: Char; CaseSensitive: Boolean;
|
|
|
|
+ aOpcodesAllowed: TMaskOpCodes);
|
|
var
|
|
var
|
|
S: TParseStringList;
|
|
S: TParseStringList;
|
|
- I: Integer;
|
|
|
|
|
|
+ i: Integer;
|
|
begin
|
|
begin
|
|
- FMasks := TObjectList.Create(True);
|
|
|
|
-
|
|
|
|
- S := TParseStringList.Create(AValue, ASeparator + ' ');
|
|
|
|
|
|
+ S := TParseStringList.Create(aValue, aSeparator);
|
|
try
|
|
try
|
|
- for I := 0 to S.Count - 1 do
|
|
|
|
- FMasks.Add(TMask.Create(S[I]));
|
|
|
|
|
|
+ for i := 0 to S.Count-1 do begin
|
|
|
|
+ fMasks.Add(FMaskClass.Create(S[i], CaseSensitive, aOpcodesAllowed));
|
|
|
|
+ end;
|
|
finally
|
|
finally
|
|
S.Free;
|
|
S.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
-destructor TMaskList.Destroy;
|
|
|
|
|
|
+function TMaskList.GetCount: Integer;
|
|
begin
|
|
begin
|
|
- FMasks.Free;
|
|
|
|
-
|
|
|
|
- inherited Destroy;
|
|
|
|
|
|
+ Result := fMasks.Count;
|
|
end;
|
|
end;
|
|
|
|
|
|
-function TMaskList.Matches(const AFileName: UTF8string): Boolean;
|
|
|
|
|
|
+function TMaskList.Matches(const AFileName: Utf8String): Boolean;
|
|
var
|
|
var
|
|
I: Integer;
|
|
I: Integer;
|
|
begin
|
|
begin
|
|
Result := False;
|
|
Result := False;
|
|
-
|
|
|
|
- for I := 0 to FMasks.Count - 1 do
|
|
|
|
- begin
|
|
|
|
- if TMask(FMasks.Items[I]).Matches(AFileName) then
|
|
|
|
- begin
|
|
|
|
- Result := True;
|
|
|
|
- Exit;
|
|
|
|
- end;
|
|
|
|
- end;
|
|
|
|
|
|
+ for I := 0 to fMasks.Count-1 do
|
|
|
|
+ if ((fMasks.Items[I]) as FMaskClass).Matches(AFileName) then
|
|
|
|
+ Exit(True);
|
|
end;
|
|
end;
|
|
|
|
|
|
|
|
+
|
|
end.
|
|
end.
|
|
|
|
|