Added a new wildcard matcher that is much faster at matching wildcards than the generic regex fallback.
Code from: http://www.c-sharpcorner.com/uploadfile/b81385/efficient-string-matching-algorithm-with-use-of-wildcard-characters/
This commit is contained in:
@@ -48,13 +48,23 @@ namespace Duplicati.Library.Utility
|
||||
/// Represents a filter that can comprise multiple filter strings
|
||||
/// </summary>
|
||||
public class FilterExpression : IFilter
|
||||
{
|
||||
{
|
||||
private struct FilterEntry
|
||||
{
|
||||
public readonly FilterType Type;
|
||||
public readonly string Filter;
|
||||
public readonly System.Text.RegularExpressions.Regex Regexp;
|
||||
|
||||
/// <summary>
|
||||
/// The single wildcard character (DOS style)
|
||||
/// </summary>
|
||||
private const char SINGLE_WILDCARD = '?';
|
||||
/// <summary>
|
||||
/// The multiple wildcard character (DOS style)
|
||||
/// </summary>
|
||||
private const char MULTIPLE_WILDCARD = '*';
|
||||
|
||||
|
||||
private static readonly System.Text.RegularExpressions.RegexOptions REGEXP_OPTIONS = Library.Utility.Utility.IsFSCaseSensitive ? System.Text.RegularExpressions.RegexOptions.Compiled : System.Text.RegularExpressions.RegexOptions.Compiled | System.Text.RegularExpressions.RegexOptions.IgnoreCase;
|
||||
|
||||
public FilterEntry(string filter)
|
||||
@@ -73,12 +83,92 @@ namespace Duplicati.Library.Utility
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Type = (filter.Contains("*") || filter.Contains("?")) ? FilterType.Wildcard : FilterType.Simple;
|
||||
this.Filter = filter;
|
||||
this.Type = (filter.Contains(MULTIPLE_WILDCARD) || filter.Contains(SINGLE_WILDCARD)) ? FilterType.Wildcard : FilterType.Simple;
|
||||
this.Filter = Utility.IsFSCaseSensitive ? filter.ToUpper() : filter;
|
||||
this.Regexp = new System.Text.RegularExpressions.Regex(Library.Utility.Utility.ConvertGlobbingToRegExp(filter), REGEXP_OPTIONS);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests whether specified string can be matched agains provided pattern string. Pattern may contain single- and multiple-replacing
|
||||
/// wildcard characters.
|
||||
/// </summary>
|
||||
/// <param name="input">String which is matched against the pattern.</param>
|
||||
/// <param name="pattern">Pattern against which string is matched.</param>
|
||||
/// <returns>true if <paramref name="pattern"/> matches the string <paramref name="input"/>; otherwise false.</returns>
|
||||
/// <note>From: http://www.c-sharpcorner.com/uploadfile/b81385/efficient-string-matching-algorithm-with-use-of-wildcard-characters/</note>
|
||||
private static bool IsWildcardMatch(string input, string pattern)
|
||||
{
|
||||
int[] inputPosStack = new int[(input.Length + 1) * (pattern.Length + 1)]; // Stack containing input positions that should be tested for further matching
|
||||
int[] patternPosStack = new int[inputPosStack.Length]; // Stack containing pattern positions that should be tested for further matching
|
||||
int stackPos = -1; // Points to last occupied entry in stack; -1 indicates that stack is empty
|
||||
bool[,] pointTested = new bool[input.Length + 1, pattern.Length + 1]; // Each true value indicates that input position vs. pattern position has been tested
|
||||
int inputPos = 0; // Position in input matched up to the first multiple wildcard in pattern
|
||||
int patternPos = 0; // Position in pattern matched up to the first multiple wildcard in pattern
|
||||
// Match beginning of the string until first multiple wildcard in pattern
|
||||
while (inputPos < input.Length && patternPos < pattern.Length && pattern[patternPos] != MULTIPLE_WILDCARD && (input[inputPos] == pattern[patternPos] || pattern[patternPos] == SINGLE_WILDCARD))
|
||||
{
|
||||
inputPos++;
|
||||
patternPos++;
|
||||
}
|
||||
|
||||
|
||||
// Push this position to stack if it points to end of pattern or to a general wildcard
|
||||
if (patternPos == pattern.Length || pattern[patternPos] == MULTIPLE_WILDCARD)
|
||||
{
|
||||
pointTested[inputPos, patternPos] = true;
|
||||
inputPosStack[++stackPos] = inputPos;
|
||||
patternPosStack[stackPos] = patternPos;
|
||||
}
|
||||
bool matched = false;
|
||||
// Repeat matching until either string is matched against the pattern or no more parts remain on stack to test
|
||||
while (stackPos >= 0 && !matched)
|
||||
{
|
||||
inputPos = inputPosStack[stackPos]; // Pop input and pattern positions from stack
|
||||
patternPos = patternPosStack[stackPos--]; // Matching will succeed if rest of the input string matches rest of the pattern
|
||||
|
||||
// Modified from original version to match zero or more characters
|
||||
//if (inputPos == input.Length && patternPos == pattern.Length)
|
||||
|
||||
if (inputPos == input.Length && (patternPos == pattern.Length || (patternPos == pattern.Length - 1 && pattern[patternPos] == MULTIPLE_WILDCARD)))
|
||||
matched = true; // Reached end of both pattern and input string, hence matching is successful
|
||||
else
|
||||
{
|
||||
// First character in next pattern block is guaranteed to be multiple wildcard
|
||||
// So skip it and search for all matches in value string until next multiple wildcard character is reached in pattern
|
||||
for(int curInputStart = inputPos; curInputStart < input.Length; curInputStart++)
|
||||
{
|
||||
int curInputPos = curInputStart;
|
||||
int curPatternPos = patternPos + 1;
|
||||
if (curPatternPos == pattern.Length)
|
||||
{ // Pattern ends with multiple wildcard, hence rest of the input string is matched with that character
|
||||
curInputPos = input.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
while (curInputPos < input.Length && curPatternPos < pattern.Length && pattern[curPatternPos] != MULTIPLE_WILDCARD &&
|
||||
(input[curInputPos] == pattern[curPatternPos] || pattern[curPatternPos] == SINGLE_WILDCARD))
|
||||
{
|
||||
curInputPos++;
|
||||
curPatternPos++;
|
||||
}
|
||||
}
|
||||
// If we have reached next multiple wildcard character in pattern without breaking the matching sequence, then we have another candidate for full match
|
||||
// This candidate should be pushed to stack for further processing
|
||||
// At the same time, pair (input position, pattern position) will be marked as tested, so that it will not be pushed to stack later again
|
||||
if (((curPatternPos == pattern.Length && curInputPos == input.Length) || (curPatternPos < pattern.Length && pattern[curPatternPos] == MULTIPLE_WILDCARD))
|
||||
&& !pointTested[curInputPos, curPatternPos])
|
||||
{
|
||||
pointTested[curInputPos, curPatternPos] = true;
|
||||
inputPosStack[++stackPos] = curInputPos;
|
||||
patternPosStack[stackPos] = curPatternPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating if the filter matches the path
|
||||
/// </summary>
|
||||
@@ -90,6 +180,7 @@ namespace Duplicati.Library.Utility
|
||||
case FilterType.Simple:
|
||||
return string.Equals(this.Filter, path, Library.Utility.Utility.ClientFilenameStringComparision);
|
||||
case FilterType.Wildcard:
|
||||
return IsWildcardMatch(Utility.IsFSCaseSensitive ? path.ToUpper() : path, this.Filter);
|
||||
case FilterType.Regexp:
|
||||
var m = this.Regexp.Match(path);
|
||||
return m.Success && m.Length == path.Length;
|
||||
|
||||
Reference in New Issue
Block a user