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:
Kenneth Skovhede
2013-08-31 22:59:00 +02:00
parent 9f4e5b4589
commit c64fce2d18
+94 -3
View File
@@ -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;