// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. #pragma warning disable 1634, 1691 using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Management.Automation.Internal; using System.Runtime.Serialization; using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation { /// /// Provides enumerated values to use to set wildcard pattern /// matching options. /// [Flags] public enum WildcardOptions { /// /// Indicates that no special processing is required. /// None = 0, /// /// Specifies that the wildcard pattern is compiled to an assembly. /// This yields faster execution but increases startup time. /// Compiled = 1, /// /// Specifies case-insensitive matching. /// IgnoreCase = 2, /// /// Specifies culture-invariant matching. /// CultureInvariant = 4 }; /// /// Represents a wildcard pattern. /// public sealed class WildcardPattern { // // char that escapes special chars // private const char escapeChar = '`'; // // we convert a wildcard pattern to a predicate // private Predicate _isMatch; // // wildcard pattern // internal string Pattern { get; } // // options that control match behavior // internal WildcardOptions Options { get; } = WildcardOptions.None; /// /// Wildcard pattern converted to regex pattern. /// internal string PatternConvertedToRegex { get { var patternRegex = WildcardPatternToRegexParser.Parse(this); return patternRegex.ToString(); } } /// /// Initializes and instance of the WildcardPattern class /// for the specified wildcard pattern. /// /// The wildcard pattern to match. /// The constructed WildcardPattern object. /// if wildCardType == None, the pattern does not have wild cards public WildcardPattern(string pattern) { if (pattern == null) { throw PSTraceSource.NewArgumentNullException("pattern"); } Pattern = pattern; } /// /// Initializes an instance of the WildcardPattern class for /// the specified wildcard pattern expression, with options /// that modify the pattern. /// /// The wildcard pattern to match. /// Wildcard options. /// The constructed WildcardPattern object. /// if wildCardType == None, the pattern does not have wild cards public WildcardPattern(string pattern, WildcardOptions options) { if (pattern == null) { throw PSTraceSource.NewArgumentNullException("pattern"); } Pattern = pattern; Options = options; } private static readonly WildcardPattern s_matchAllIgnoreCasePattern = new WildcardPattern("*", WildcardOptions.None); /// /// Create a new WildcardPattern, or return an already created one. /// /// The pattern. /// /// public static WildcardPattern Get(string pattern, WildcardOptions options) { if (pattern == null) throw PSTraceSource.NewArgumentNullException("pattern"); if (pattern.Length == 1 && pattern[0] == '*') return s_matchAllIgnoreCasePattern; return new WildcardPattern(pattern, options); } /// /// Instantiate internal regex member if not already done. /// /// True on success, false otherwise. private void Init() { if (_isMatch == null) { if (Pattern.Length == 1 && Pattern[0] == '*') { _isMatch = _ => true; } else { var matcher = new WildcardPatternMatcher(this); _isMatch = matcher.IsMatch; } } } /// /// Indicates whether the wildcard pattern specified in the WildcardPattern /// constructor finds a match in the input string. /// /// The string to search for a match. /// True if the wildcard pattern finds a match; otherwise, false. public bool IsMatch(string input) { Init(); return input != null && _isMatch(input); } /// /// Escape special chars, except for those specified in , in a string by replacing them with their escape codes. /// /// The input string containing the text to convert. /// Array of characters that not to escape. /// /// A string of characters with any metacharacters, except for those specified in , converted to their escaped form. /// internal static string Escape(string pattern, char[] charsNotToEscape) { #pragma warning disable 56506 if (pattern == null) { throw PSTraceSource.NewArgumentNullException("pattern"); } if (charsNotToEscape == null) { throw PSTraceSource.NewArgumentNullException("charsNotToEscape"); } char[] temp = new char[pattern.Length * 2 + 1]; int tempIndex = 0; for (int i = 0; i < pattern.Length; i++) { char ch = pattern[i]; // // if it is a wildcard char, escape it // if (IsWildcardChar(ch) && !charsNotToEscape.Contains(ch)) { temp[tempIndex++] = escapeChar; } temp[tempIndex++] = ch; } string s = null; if (tempIndex > 0) { s = new string(temp, 0, tempIndex); } else { s = string.Empty; } return s; #pragma warning restore 56506 } /// /// Escape special chars in a string by replacing them with their escape codes. /// /// The input string containing the text to convert. /// /// A string of characters with any metacharacters converted to their escaped form. /// public static string Escape(string pattern) { return Escape(pattern, Utils.EmptyArray()); } /// /// Checks to see if the given string has any wild card characters in it. /// /// /// String which needs to be checked for the presence of wildcard chars /// /// True if the string has wild card chars, false otherwise.. /// /// Currently { '*', '?', '[' } are considered wild card chars and /// '`' is the escape character. /// public static bool ContainsWildcardCharacters(string pattern) { if (string.IsNullOrEmpty(pattern)) { return false; } bool result = false; for (int index = 0; index < pattern.Length; ++index) { if (IsWildcardChar(pattern[index])) { result = true; break; } // If it is an escape character then advance past // the next character if (pattern[index] == escapeChar) { ++index; } } return result; } /// /// Unescapes any escaped characters in the input string. /// /// /// The input string containing the text to convert. /// /// /// A string of characters with any escaped characters /// converted to their unescaped form. /// /// /// If is null. /// public static string Unescape(string pattern) { if (pattern == null) { throw PSTraceSource.NewArgumentNullException("pattern"); } char[] temp = new char[pattern.Length]; int tempIndex = 0; bool prevCharWasEscapeChar = false; for (int i = 0; i < pattern.Length; i++) { char ch = pattern[i]; if (ch == escapeChar) { if (prevCharWasEscapeChar) { temp[tempIndex++] = ch; prevCharWasEscapeChar = false; } else { prevCharWasEscapeChar = true; } continue; } if (prevCharWasEscapeChar) { if (!IsWildcardChar(ch)) { temp[tempIndex++] = escapeChar; } } temp[tempIndex++] = ch; prevCharWasEscapeChar = false; } // Need to account for a trailing escape character as a real // character if (prevCharWasEscapeChar) { temp[tempIndex++] = escapeChar; prevCharWasEscapeChar = false; } string s = null; if (tempIndex > 0) { s = new string(temp, 0, tempIndex); } else { s = string.Empty; } return s; } private static bool IsWildcardChar(char ch) { return (ch == '*') || (ch == '?') || (ch == '[') || (ch == ']'); } /// /// Converts this wildcard to a string that can be used as a right-hand-side operand of the LIKE operator of WQL. /// For example: "a*" will be converted to "a%". /// /// public string ToWql() { bool needsClientSideFiltering; string likeOperand = Microsoft.PowerShell.Cmdletization.Cim.WildcardPatternToCimQueryParser.Parse(this, out needsClientSideFiltering); if (!needsClientSideFiltering) { return likeOperand; } else { throw new PSInvalidCastException( "UnsupportedWildcardToWqlConversion", null, ExtendedTypeSystem.InvalidCastException, this.Pattern, this.GetType().FullName, "WQL"); } } } /// /// Thrown when a wildcard pattern is invalid. /// [Serializable] public class WildcardPatternException : RuntimeException { /// /// Constructor for class WildcardPatternException that takes /// an ErrorRecord to use in constructing this exception. /// /// This is the recommended constructor to use for this exception. /// /// ErrorRecord object containing additional information about the error condition. /// /// Constructed object. internal WildcardPatternException(ErrorRecord errorRecord) : base(RetrieveMessage(errorRecord)) { if (errorRecord == null) { throw new ArgumentNullException("errorRecord"); } _errorRecord = errorRecord; } [NonSerialized] private ErrorRecord _errorRecord; /// /// Constructs an instance of the WildcardPatternException object. /// public WildcardPatternException() { } /// /// Constructs an instance of the WildcardPatternException object taking /// a message parameter to use in constructing the exception. /// /// The string to use as the exception message. public WildcardPatternException(string message) : base(message) { } /// /// Constructor for class WildcardPatternException that takes both a message to use /// and an inner exception to include in this object. /// /// The exception message to use. /// The innerException object to encapsulate. public WildcardPatternException(string message, Exception innerException) : base(message, innerException) { } /// /// Constructor for class WildcardPatternException for serialization. /// /// Serialization information. /// Streaming context. protected WildcardPatternException(SerializationInfo info, StreamingContext context) : base(info, context) { } } /// /// A base class for parsers of patterns. /// internal abstract class WildcardPatternParser { /// /// Called from method to indicate /// the beginning of the wildcard pattern. /// Default implementation simply returns. /// /// /// object that includes both /// the text of the pattern () /// and the pattern options () /// protected virtual void BeginWildcardPattern(WildcardPattern pattern) { } /// /// Called from method to indicate that the next /// part of the pattern should match /// a literal character . /// protected abstract void AppendLiteralCharacter(char c); /// /// Called from method to indicate that the next /// part of the pattern should match /// any string, including an empty string. /// protected abstract void AppendAsterix(); /// /// Called from method to indicate that the next /// part of the pattern should match /// any single character. /// protected abstract void AppendQuestionMark(); /// /// Called from method to indicate the end of the wildcard pattern. /// Default implementation simply returns. /// protected virtual void EndWildcardPattern() { } /// /// Called from method to indicate /// the beginning of a bracket expression. /// /// /// Bracket expressions of are /// a greatly simplified version of bracket expressions of POSIX wildcards /// (http://www.opengroup.org/onlinepubs/9699919799/functions/fnmatch.html). /// Only literal characters and character ranges are supported. /// Negation (with either '!' or '^' characters), /// character classes ([:alpha:]) /// and other advanced features are not supported. /// protected abstract void BeginBracketExpression(); /// /// Called from method to indicate that the bracket expression /// should include a literal character . /// protected abstract void AppendLiteralCharacterToBracketExpression(char c); /// /// Called from method to indicate that the bracket expression /// should include all characters from character range /// starting at /// and ending at /// protected abstract void AppendCharacterRangeToBracketExpression( char startOfCharacterRange, char endOfCharacterRange); /// /// Called from method to indicate the end of a bracket expression. /// protected abstract void EndBracketExpression(); /// /// PowerShell v1 and v2 treats all characters inside /// as literal characters, /// except '-' sign which denotes a range. In particular it means that /// '^', '[', ']' are escaped within the bracket expression and don't /// have their regex-y meaning. /// /// /// /// /// /// This method should be kept "internal" /// internal void AppendBracketExpression(string brackedExpressionContents, string bracketExpressionOperators, string pattern) { this.BeginBracketExpression(); int i = 0; while (i < brackedExpressionContents.Length) { if (((i + 2) < brackedExpressionContents.Length) && (bracketExpressionOperators[i + 1] == '-')) { char lowerBound = brackedExpressionContents[i]; char upperBound = brackedExpressionContents[i + 2]; i += 3; if (lowerBound > upperBound) { throw NewWildcardPatternException(pattern); } this.AppendCharacterRangeToBracketExpression(lowerBound, upperBound); } else { this.AppendLiteralCharacterToBracketExpression(brackedExpressionContents[i]); i++; } } this.EndBracketExpression(); } /// /// Parses , calling appropriate overloads /// in /// /// Pattern to parse. /// Parser to call back. public static void Parse(WildcardPattern pattern, WildcardPatternParser parser) { parser.BeginWildcardPattern(pattern); bool previousCharacterIsAnEscape = false; bool previousCharacterStartedBracketExpression = false; bool insideCharacterRange = false; StringBuilder characterRangeContents = null; StringBuilder characterRangeOperators = null; foreach (char c in pattern.Pattern) { if (insideCharacterRange) { if (c == ']' && !previousCharacterStartedBracketExpression && !previousCharacterIsAnEscape) { // An unescaped closing square bracket closes the character set. In other // words, there are no nested square bracket expressions // This is different than the POSIX spec // (at http://www.opengroup.org/onlinepubs/9699919799/functions/fnmatch.html), // but we are keeping this behavior for back-compatibility. insideCharacterRange = false; parser.AppendBracketExpression(characterRangeContents.ToString(), characterRangeOperators.ToString(), pattern.Pattern); characterRangeContents = null; characterRangeOperators = null; } else if (c != '`' || previousCharacterIsAnEscape) { characterRangeContents.Append(c); characterRangeOperators.Append((c == '-') && !previousCharacterIsAnEscape ? '-' : ' '); } previousCharacterStartedBracketExpression = false; } else { if (c == '*' && !previousCharacterIsAnEscape) { parser.AppendAsterix(); } else if (c == '?' && !previousCharacterIsAnEscape) { parser.AppendQuestionMark(); } else if (c == '[' && !previousCharacterIsAnEscape) { insideCharacterRange = true; characterRangeContents = new StringBuilder(); characterRangeOperators = new StringBuilder(); previousCharacterStartedBracketExpression = true; } else if (c != '`' || previousCharacterIsAnEscape) { parser.AppendLiteralCharacter(c); } } previousCharacterIsAnEscape = (c == '`') && (!previousCharacterIsAnEscape); } if (insideCharacterRange) { throw NewWildcardPatternException(pattern.Pattern); } if (previousCharacterIsAnEscape) { if (!pattern.Pattern.Equals("`", StringComparison.Ordinal)) // Win7 backcompatibility requires treating '`' pattern as '' pattern { parser.AppendLiteralCharacter(pattern.Pattern[pattern.Pattern.Length - 1]); } } parser.EndWildcardPattern(); } internal static WildcardPatternException NewWildcardPatternException(string invalidPattern) { string message = StringUtil.Format(WildcardPatternStrings.InvalidPattern, invalidPattern ); ParentContainsErrorRecordException pce = new ParentContainsErrorRecordException(message); ErrorRecord er = new ErrorRecord(pce, "WildcardPattern_Invalid", ErrorCategory.InvalidArgument, null); WildcardPatternException e = new WildcardPatternException(er); return e; } }; /// /// Convert a string with wild cards into its equivalent regex. /// /// /// A list of glob patterns and their equivalent regexes /// /// glob pattern regex /// ------------- ------- /// *foo* foo /// foo ^foo$ /// foo*bar ^foo.*bar$ /// foo`*bar ^foo\*bar$ /// /// for a more cases see the unit-test file RegexTest.cs /// internal class WildcardPatternToRegexParser : WildcardPatternParser { private StringBuilder _regexPattern; private RegexOptions _regexOptions; private const string regexChars = "()[.?*{}^$+|\\"; // ']' is missing on purpose private static bool IsRegexChar(char ch) { for (int i = 0; i < regexChars.Length; i++) { if (ch == regexChars[i]) { return true; } } return false; } internal static RegexOptions TranslateWildcardOptionsIntoRegexOptions(WildcardOptions options) { RegexOptions regexOptions = RegexOptions.Singleline; if ((options & WildcardOptions.Compiled) != 0) { regexOptions |= RegexOptions.Compiled; } if ((options & WildcardOptions.IgnoreCase) != 0) { regexOptions |= RegexOptions.IgnoreCase; } if ((options & WildcardOptions.CultureInvariant) == WildcardOptions.CultureInvariant) { regexOptions |= RegexOptions.CultureInvariant; } return regexOptions; } protected override void BeginWildcardPattern(WildcardPattern pattern) { _regexPattern = new StringBuilder(pattern.Pattern.Length * 2 + 2); _regexPattern.Append('^'); _regexOptions = TranslateWildcardOptionsIntoRegexOptions(pattern.Options); } internal static void AppendLiteralCharacter(StringBuilder regexPattern, char c) { if (IsRegexChar(c)) { regexPattern.Append('\\'); } regexPattern.Append(c); } protected override void AppendLiteralCharacter(char c) { AppendLiteralCharacter(_regexPattern, c); } protected override void AppendAsterix() { _regexPattern.Append(".*"); } protected override void AppendQuestionMark() { _regexPattern.Append('.'); } protected override void EndWildcardPattern() { _regexPattern.Append('$'); // lines below are not strictly necessary and are included to preserve // wildcard->regex conversion from PS v1 (i.e. not to break unit tests // and not to break backcompatibility). string regexPatternString = _regexPattern.ToString(); if (regexPatternString.Equals("^.*$", StringComparison.Ordinal)) { _regexPattern.Remove(0, 4); } else { if (regexPatternString.StartsWith("^.*", StringComparison.Ordinal)) { _regexPattern.Remove(0, 3); } if (regexPatternString.EndsWith(".*$", StringComparison.Ordinal)) { _regexPattern.Remove(_regexPattern.Length - 3, 3); } } } protected override void BeginBracketExpression() { _regexPattern.Append('['); } internal static void AppendLiteralCharacterToBracketExpression(StringBuilder regexPattern, char c) { if (c == '[') { regexPattern.Append('['); } else if (c == ']') { regexPattern.Append(@"\]"); } else if (c == '-') { regexPattern.Append(@"\x2d"); } else { AppendLiteralCharacter(regexPattern, c); } } protected override void AppendLiteralCharacterToBracketExpression(char c) { AppendLiteralCharacterToBracketExpression(_regexPattern, c); } internal static void AppendCharacterRangeToBracketExpression( StringBuilder regexPattern, char startOfCharacterRange, char endOfCharacterRange) { AppendLiteralCharacterToBracketExpression(regexPattern, startOfCharacterRange); regexPattern.Append('-'); AppendLiteralCharacterToBracketExpression(regexPattern, endOfCharacterRange); } protected override void AppendCharacterRangeToBracketExpression( char startOfCharacterRange, char endOfCharacterRange) { AppendCharacterRangeToBracketExpression(_regexPattern, startOfCharacterRange, endOfCharacterRange); } protected override void EndBracketExpression() { _regexPattern.Append(']'); } /// /// Parses a into a /// /// Wildcard pattern to parse. /// Regular expression equivalent to public static Regex Parse(WildcardPattern wildcardPattern) { WildcardPatternToRegexParser parser = new WildcardPatternToRegexParser(); WildcardPatternParser.Parse(wildcardPattern, parser); try { return new Regex(parser._regexPattern.ToString(), parser._regexOptions); } catch (ArgumentException) { throw WildcardPatternParser.NewWildcardPatternException(wildcardPattern.Pattern); } } } internal class WildcardPatternMatcher { private readonly PatternElement[] _patternElements; private readonly CharacterNormalizer _characterNormalizer; internal WildcardPatternMatcher(WildcardPattern wildcardPattern) { _characterNormalizer = new CharacterNormalizer(wildcardPattern.Options); _patternElements = MyWildcardPatternParser.Parse( wildcardPattern, _characterNormalizer); } internal bool IsMatch(string str) { // - each state of NFA is represented by (patternPosition, stringPosition) tuple // - state transitions are documented in // ProcessStringCharacter and ProcessEndOfString methods // - the algorithm below tries to see if there is a path // from (0, 0) to (lengthOfPattern, lengthOfString) // - this is a regular graph traversal // - there are O(1) edges per node (at most 2 edges) // so the whole graph traversal takes O(number of nodes in the graph) = // = O(lengthOfPattern * lengthOfString) time // - for efficient remembering which states have already been visited, // the traversal goes methodically from beginning to end of the string // therefore requiring only O(lengthOfPattern) memory for remembering // which states have been already visited // - Wikipedia calls this algorithm the "NFA" algorithm at // http://en.wikipedia.org/wiki/Regular_expression#Implementations_and_running_times var patternPositionsForCurrentStringPosition = new PatternPositionsVisitor(_patternElements.Length); patternPositionsForCurrentStringPosition.Add(0); var patternPositionsForNextStringPosition = new PatternPositionsVisitor(_patternElements.Length); for (int currentStringPosition = 0; currentStringPosition < str.Length; currentStringPosition++) { char currentStringCharacter = _characterNormalizer.Normalize(str[currentStringPosition]); patternPositionsForCurrentStringPosition.StringPosition = currentStringPosition; patternPositionsForNextStringPosition.StringPosition = currentStringPosition + 1; int patternPosition; while (patternPositionsForCurrentStringPosition.MoveNext(out patternPosition)) { _patternElements[patternPosition].ProcessStringCharacter( currentStringCharacter, patternPosition, patternPositionsForCurrentStringPosition, patternPositionsForNextStringPosition); } // swap patternPositionsForCurrentStringPosition // with patternPositionsForNextStringPosition var tmp = patternPositionsForCurrentStringPosition; patternPositionsForCurrentStringPosition = patternPositionsForNextStringPosition; patternPositionsForNextStringPosition = tmp; } int patternPosition2; while (patternPositionsForCurrentStringPosition.MoveNext(out patternPosition2)) { _patternElements[patternPosition2].ProcessEndOfString( patternPosition2, patternPositionsForCurrentStringPosition); } return patternPositionsForCurrentStringPosition.ReachedEndOfPattern; } private class PatternPositionsVisitor { private readonly int _lengthOfPattern; private readonly int[] _isPatternPositionVisitedMarker; private readonly int[] _patternPositionsForFurtherProcessing; private int _patternPositionsForFurtherProcessingCount; public PatternPositionsVisitor(int lengthOfPattern) { Dbg.Assert(lengthOfPattern >= 0, "Caller should verify lengthOfPattern >= 0"); _lengthOfPattern = lengthOfPattern; _isPatternPositionVisitedMarker = new int[lengthOfPattern + 1]; for (int i = 0; i < _isPatternPositionVisitedMarker.Length; i++) { _isPatternPositionVisitedMarker[i] = -1; } _patternPositionsForFurtherProcessing = new int[lengthOfPattern]; _patternPositionsForFurtherProcessingCount = 0; } public int StringPosition { private get; set; } public void Add(int patternPosition) { Dbg.Assert(patternPosition >= 0, "Caller should verify patternPosition >= 0"); Dbg.Assert( patternPosition <= _lengthOfPattern, "Caller should verify patternPosition <= this._lengthOfPattern"); // is patternPosition already visited?); if (_isPatternPositionVisitedMarker[patternPosition] == this.StringPosition) { return; } // mark patternPosition as visited _isPatternPositionVisitedMarker[patternPosition] = this.StringPosition; // add patternPosition to the queue for further processing if (patternPosition < _lengthOfPattern) { _patternPositionsForFurtherProcessing[_patternPositionsForFurtherProcessingCount] = patternPosition; _patternPositionsForFurtherProcessingCount++; Dbg.Assert( _patternPositionsForFurtherProcessingCount <= _lengthOfPattern, "There should never be more elements in the queue than the length of the pattern"); } } public bool ReachedEndOfPattern { get { return _isPatternPositionVisitedMarker[_lengthOfPattern] >= this.StringPosition; } } // non-virtual MoveNext is more performant // than implementing IEnumerable / virtual MoveNext public bool MoveNext(out int patternPosition) { Dbg.Assert( _patternPositionsForFurtherProcessingCount >= 0, "There should never be more elements in the queue than the length of the pattern"); if (_patternPositionsForFurtherProcessingCount == 0) { patternPosition = -1; return false; } _patternPositionsForFurtherProcessingCount--; patternPosition = _patternPositionsForFurtherProcessing[_patternPositionsForFurtherProcessingCount]; return true; } } private abstract class PatternElement { public abstract void ProcessStringCharacter( char currentStringCharacter, int currentPatternPosition, PatternPositionsVisitor patternPositionsForCurrentStringPosition, PatternPositionsVisitor patternPositionsForNextStringPosition); public abstract void ProcessEndOfString( int currentPatternPosition, PatternPositionsVisitor patternPositionsForEndOfStringPosition); } private class QuestionMarkElement : PatternElement { public override void ProcessStringCharacter( char currentStringCharacter, int currentPatternPosition, PatternPositionsVisitor patternPositionsForCurrentStringPosition, PatternPositionsVisitor patternPositionsForNextStringPosition) { // '?' : (patternPosition, stringPosition) => (patternPosition + 1, stringPosition + 1) patternPositionsForNextStringPosition.Add(currentPatternPosition + 1); } public override void ProcessEndOfString( int currentPatternPosition, PatternPositionsVisitor patternPositionsForEndOfStringPosition) { // '?' : (patternPosition, endOfString) => } } private class LiteralCharacterElement : QuestionMarkElement { private readonly char _literalCharacter; public LiteralCharacterElement(char literalCharacter) { _literalCharacter = literalCharacter; } public override void ProcessStringCharacter( char currentStringCharacter, int currentPatternPosition, PatternPositionsVisitor patternPositionsForCurrentStringPosition, PatternPositionsVisitor patternPositionsForNextStringPosition) { if (_literalCharacter == currentStringCharacter) { base.ProcessStringCharacter( currentStringCharacter, currentPatternPosition, patternPositionsForCurrentStringPosition, patternPositionsForNextStringPosition); } } } private class BracketExpressionElement : QuestionMarkElement { private readonly Regex _regex; public BracketExpressionElement(Regex regex) { Dbg.Assert(regex != null, "Caller should verify regex != null"); _regex = regex; } public override void ProcessStringCharacter( char currentStringCharacter, int currentPatternPosition, PatternPositionsVisitor patternPositionsForCurrentStringPosition, PatternPositionsVisitor patternPositionsForNextStringPosition) { if (_regex.IsMatch(new string(currentStringCharacter, 1))) { base.ProcessStringCharacter(currentStringCharacter, currentPatternPosition, patternPositionsForCurrentStringPosition, patternPositionsForNextStringPosition); } } } private class AsterixElement : PatternElement { public override void ProcessStringCharacter( char currentStringCharacter, int currentPatternPosition, PatternPositionsVisitor patternPositionsForCurrentStringPosition, PatternPositionsVisitor patternPositionsForNextStringPosition) { // '*' : (patternPosition, stringPosition) => (patternPosition + 1, stringPosition) patternPositionsForCurrentStringPosition.Add(currentPatternPosition + 1); // '*' : (patternPosition, stringPosition) => (patternPosition, stringPosition + 1) patternPositionsForNextStringPosition.Add(currentPatternPosition); } public override void ProcessEndOfString( int currentPatternPosition, PatternPositionsVisitor patternPositionsForEndOfStringPosition) { // '*' : (patternPosition, endOfString) => (patternPosition + 1, endOfString) patternPositionsForEndOfStringPosition.Add(currentPatternPosition + 1); } } private class MyWildcardPatternParser : WildcardPatternParser { private readonly List _patternElements = new List(); private CharacterNormalizer _characterNormalizer; private RegexOptions _regexOptions; private StringBuilder _bracketExpressionBuilder; public static PatternElement[] Parse( WildcardPattern pattern, CharacterNormalizer characterNormalizer) { var parser = new MyWildcardPatternParser { _characterNormalizer = characterNormalizer, _regexOptions = WildcardPatternToRegexParser.TranslateWildcardOptionsIntoRegexOptions(pattern.Options), }; WildcardPatternParser.Parse(pattern, parser); return parser._patternElements.ToArray(); } protected override void AppendLiteralCharacter(char c) { c = _characterNormalizer.Normalize(c); _patternElements.Add(new LiteralCharacterElement(c)); } protected override void AppendAsterix() { _patternElements.Add(new AsterixElement()); } protected override void AppendQuestionMark() { _patternElements.Add(new QuestionMarkElement()); } protected override void BeginBracketExpression() { _bracketExpressionBuilder = new StringBuilder(); _bracketExpressionBuilder.Append('['); } protected override void AppendLiteralCharacterToBracketExpression(char c) { WildcardPatternToRegexParser.AppendLiteralCharacterToBracketExpression( _bracketExpressionBuilder, c); } protected override void AppendCharacterRangeToBracketExpression( char startOfCharacterRange, char endOfCharacterRange) { WildcardPatternToRegexParser.AppendCharacterRangeToBracketExpression( _bracketExpressionBuilder, startOfCharacterRange, endOfCharacterRange); } protected override void EndBracketExpression() { _bracketExpressionBuilder.Append(']'); Regex regex = new Regex(_bracketExpressionBuilder.ToString(), _regexOptions); _patternElements.Add(new BracketExpressionElement(regex)); } } private struct CharacterNormalizer { private readonly CultureInfo _cultureInfo; private readonly bool _caseInsensitive; public CharacterNormalizer(WildcardOptions options) { _caseInsensitive = 0 != (options & WildcardOptions.IgnoreCase); if (_caseInsensitive) { _cultureInfo = 0 != (options & WildcardOptions.CultureInvariant) ? CultureInfo.InvariantCulture : CultureInfo.CurrentCulture; } else { // Don't bother saving the culture if we won't use it _cultureInfo = null; } } [Pure] public char Normalize(char x) { if (_caseInsensitive) { return _cultureInfo.TextInfo.ToLower(x); } return x; } } } /// /// Translates a into a DOS wildcard. /// internal class WildcardPatternToDosWildcardParser : WildcardPatternParser { private readonly StringBuilder _result = new StringBuilder(); protected override void AppendLiteralCharacter(char c) { _result.Append(c); } protected override void AppendAsterix() { _result.Append('*'); } protected override void AppendQuestionMark() { _result.Append('?'); } protected override void BeginBracketExpression() { } protected override void AppendLiteralCharacterToBracketExpression(char c) { } protected override void AppendCharacterRangeToBracketExpression(char startOfCharacterRange, char endOfCharacterRange) { } protected override void EndBracketExpression() { _result.Append('?'); } /// /// Converts into a DOS wildcard. /// internal static string Parse(WildcardPattern wildcardPattern) { var parser = new WildcardPatternToDosWildcardParser(); WildcardPatternParser.Parse(wildcardPattern, parser); return parser._result.ToString(); } } }