2020-03-24 11:08:37 -07:00
// Copyright (c) Microsoft Corporation.
2018-02-13 09:23:53 -08:00
// Licensed under the MIT License.
2016-03-30 14:20:52 -07:00
#pragma warning disable 1634 , 1691
2019-06-27 20:14:41 -07:00
using System.Buffers ;
2016-03-30 14:20:52 -07:00
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
{
/// <summary>
2017-01-16 13:31:14 -08:00
/// Provides enumerated values to use to set wildcard pattern
2016-03-30 14:20:52 -07:00
/// matching options.
/// </summary>
[Flags]
public enum WildcardOptions
{
/// <summary>
/// Indicates that no special processing is required.
/// </summary>
None = 0 ,
/// <summary>
2017-01-16 13:31:14 -08:00
/// Specifies that the wildcard pattern is compiled to an assembly.
2016-03-30 14:20:52 -07:00
/// This yields faster execution but increases startup time.
/// </summary>
Compiled = 1 ,
2016-07-29 13:02:49 -07:00
2016-03-30 14:20:52 -07:00
/// <summary>
/// Specifies case-insensitive matching.
/// </summary>
IgnoreCase = 2 ,
/// <summary>
/// Specifies culture-invariant matching.
/// </summary>
CultureInvariant = 4
};
/// <summary>
/// Represents a wildcard pattern.
/// </summary>
public sealed class WildcardPattern
{
// char that escapes special chars
2016-07-29 13:02:49 -07:00
private const char escapeChar = '`' ;
2016-03-30 14:20:52 -07:00
2019-07-20 01:10:10 +05:00
// Threshold for stack allocation.
// The size is less than MaxShortPath = 260.
private const int StackAllocThreshold = 256 ;
2016-03-30 14:20:52 -07:00
// we convert a wildcard pattern to a predicate
private Predicate < string > _isMatch ;
2019-07-02 11:25:09 -07:00
// chars that are considered special in a wildcard pattern
private static readonly char [] s_specialChars = new [] { '*' , '?' , '[' , ']' , '`' };
// static match-all delegate that is shared by all WildcardPattern instances
private static readonly Predicate < string > s_matchAll = _ => true ;
2016-03-30 14:20:52 -07:00
// wildcard pattern
2016-08-02 13:29:09 -07:00
internal string Pattern { get ; }
2016-03-30 14:20:52 -07:00
2019-07-10 21:06:34 +05:00
// Options that control match behavior.
// Default is WildcardOptions.None.
internal WildcardOptions Options { get ; }
2016-03-30 14:20:52 -07:00
/// <summary>
2019-01-10 02:07:41 +05:00
/// Wildcard pattern converted to regex pattern.
2016-03-30 14:20:52 -07:00
/// </summary>
internal string PatternConvertedToRegex
{
get
{
var patternRegex = WildcardPatternToRegexParser . Parse ( this );
return patternRegex . ToString ();
}
}
/// <summary>
2017-01-16 13:31:14 -08:00
/// Initializes and instance of the WildcardPattern class
2016-03-30 14:20:52 -07:00
/// for the specified wildcard pattern.
/// </summary>
2018-12-28 16:39:06 +05:00
/// <param name="pattern">The wildcard pattern to match.</param>
2018-12-25 21:43:03 +05:00
/// <returns>The constructed WildcardPattern object.</returns>
2019-07-10 21:06:34 +05:00
public WildcardPattern ( string pattern ) : this ( pattern , WildcardOptions . None )
2016-03-30 14:20:52 -07:00
{
}
/// <summary>
2017-01-16 13:31:14 -08:00
/// Initializes an instance of the WildcardPattern class for
/// the specified wildcard pattern expression, with options
2016-03-30 14:20:52 -07:00
/// that modify the pattern.
/// </summary>
/// <param name="pattern">The wildcard pattern to match.</param>
2018-12-28 16:39:06 +05:00
/// <param name="options">Wildcard options.</param>
2018-12-25 21:43:03 +05:00
/// <returns>The constructed WildcardPattern object.</returns>
2019-07-10 21:06:34 +05:00
public WildcardPattern ( string pattern , WildcardOptions options )
2016-03-30 14:20:52 -07:00
{
if ( pattern == null )
{
2019-07-10 21:06:34 +05:00
throw PSTraceSource . NewArgumentNullException ( nameof ( pattern ));
2016-03-30 14:20:52 -07:00
}
2016-08-02 13:29:09 -07:00
Pattern = pattern ;
Options = options ;
2016-03-30 14:20:52 -07:00
}
2016-07-29 13:02:49 -07:00
private static readonly WildcardPattern s_matchAllIgnoreCasePattern = new WildcardPattern ( "*" , WildcardOptions . None );
2016-03-30 14:20:52 -07:00
/// <summary>
/// Create a new WildcardPattern, or return an already created one.
/// </summary>
2018-12-28 16:39:06 +05:00
/// <param name="pattern">The pattern.</param>
2016-03-30 14:20:52 -07:00
/// <param name="options"></param>
/// <returns></returns>
public static WildcardPattern Get ( string pattern , WildcardOptions options )
{
if ( pattern == null )
throw PSTraceSource . NewArgumentNullException ( "pattern" );
if ( pattern . Length == 1 && pattern [ 0 ] == '*' )
2016-07-29 13:02:49 -07:00
return s_matchAllIgnoreCasePattern ;
2016-03-30 14:20:52 -07:00
return new WildcardPattern ( pattern , options );
}
/// <summary>
/// Instantiate internal regex member if not already done.
/// </summary>
2018-12-25 21:43:03 +05:00
/// <returns>True on success, false otherwise.</returns>
2016-03-30 14:20:52 -07:00
private void Init ()
{
2019-07-10 21:15:17 +05:00
StringComparison GetStringComparison ()
2016-03-30 14:20:52 -07:00
{
2019-07-02 11:25:09 -07:00
StringComparison stringComparison ;
if ( Options . HasFlag ( WildcardOptions . IgnoreCase ))
2016-03-30 14:20:52 -07:00
{
2019-07-02 11:25:09 -07:00
stringComparison = Options . HasFlag ( WildcardOptions . CultureInvariant )
? StringComparison . InvariantCultureIgnoreCase
2019-07-26 10:58:37 -07:00
: CultureInfo . CurrentCulture . Name . Equals ( "en-US-POSIX" , StringComparison . OrdinalIgnoreCase )
// The collation behavior of the POSIX locale (also known as the C locale) is case sensitive.
// For this specific locale, we use 'OrdinalIgnoreCase'.
? StringComparison . OrdinalIgnoreCase
: StringComparison . CurrentCultureIgnoreCase ;
2016-03-30 14:20:52 -07:00
}
else
{
2019-07-02 11:25:09 -07:00
stringComparison = Options . HasFlag ( WildcardOptions . CultureInvariant )
? StringComparison . InvariantCulture
: StringComparison . CurrentCulture ;
2016-03-30 14:20:52 -07:00
}
2019-07-02 11:25:09 -07:00
2019-07-10 21:15:17 +05:00
return stringComparison ;
}
if ( _isMatch != null )
{
return ;
}
if ( Pattern . Length == 1 && Pattern [ 0 ] == '*' )
{
_isMatch = s_matchAll ;
return ;
}
int index = Pattern . IndexOfAny ( s_specialChars );
if ( index == - 1 )
{
// No special characters present in the pattern, so we can just do a string comparison.
_isMatch = str => string . Equals ( str , Pattern , GetStringComparison ());
return ;
}
if ( index == Pattern . Length - 1 && Pattern [ index ] == '*' )
{
// No special characters present in the pattern before last position and last character is asterisk.
var patternWithoutAsterisk = Pattern . AsMemory (). Slice ( 0 , index );
_isMatch = str => str . AsSpan (). StartsWith ( patternWithoutAsterisk . Span , GetStringComparison ());
2019-07-02 11:25:09 -07:00
return ;
2016-03-30 14:20:52 -07:00
}
2019-07-02 11:25:09 -07:00
var matcher = new WildcardPatternMatcher ( this );
_isMatch = matcher . IsMatch ;
2016-03-30 14:20:52 -07:00
}
/// <summary>
/// Indicates whether the wildcard pattern specified in the WildcardPattern
/// constructor finds a match in the input string.
/// </summary>
/// <param name="input">The string to search for a match.</param>
2018-12-25 21:43:03 +05:00
/// <returns>True if the wildcard pattern finds a match; otherwise, false.</returns>
2016-03-30 14:20:52 -07:00
public bool IsMatch ( string input )
{
Init ();
return input != null && _isMatch ( input );
}
/// <summary>
/// Escape special chars, except for those specified in <paramref name="charsNotToEscape"/>, in a string by replacing them with their escape codes.
/// </summary>
/// <param name="pattern">The input string containing the text to convert.</param>
2018-12-28 16:39:06 +05:00
/// <param name="charsNotToEscape">Array of characters that not to escape.</param>
2016-03-30 14:20:52 -07:00
/// <returns>
/// A string of characters with any metacharacters, except for those specified in <paramref name="charsNotToEscape"/>, converted to their escaped form.
/// </returns>
internal static string Escape ( string pattern , char [] charsNotToEscape )
{
if ( pattern == null )
{
2019-07-20 01:10:10 +05:00
throw PSTraceSource . NewArgumentNullException ( nameof ( pattern ));
2016-03-30 14:20:52 -07:00
}
if ( charsNotToEscape == null )
{
2019-07-20 01:10:10 +05:00
throw PSTraceSource . NewArgumentNullException ( nameof ( charsNotToEscape ));
2016-03-30 14:20:52 -07:00
}
2019-07-20 01:10:10 +05:00
if ( pattern == string . Empty )
{
return pattern ;
}
Span < char > temp = pattern . Length < StackAllocThreshold ? stackalloc char [ pattern . Length * 2 + 1 ] : new char [ pattern . Length * 2 + 1 ];
2016-03-30 14:20:52 -07:00
int tempIndex = 0 ;
2016-07-29 13:02:49 -07:00
for ( int i = 0 ; i < pattern . Length ; i ++)
2016-03-30 14:20:52 -07:00
{
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 ;
2019-07-20 01:10:10 +05:00
if ( tempIndex == pattern . Length )
2016-03-30 14:20:52 -07:00
{
2019-07-20 01:10:10 +05:00
s = pattern ;
2016-03-30 14:20:52 -07:00
}
else
{
2019-07-20 01:10:10 +05:00
s = new string ( temp . Slice ( 0 , tempIndex ));
2016-03-30 14:20:52 -07:00
}
return s ;
}
/// <summary>
/// Escape special chars in a string by replacing them with their escape codes.
/// </summary>
/// <param name="pattern">The input string containing the text to convert.</param>
/// <returns>
/// A string of characters with any metacharacters converted to their escaped form.
/// </returns>
public static string Escape ( string pattern )
{
2019-03-10 06:53:31 +05:00
return Escape ( pattern , Array . Empty < char >());
2016-03-30 14:20:52 -07:00
}
/// <summary>
/// Checks to see if the given string has any wild card characters in it.
/// </summary>
/// <param name="pattern">
/// String which needs to be checked for the presence of wildcard chars
/// </param>
2018-12-25 21:43:03 +05:00
/// <returns>True if the string has wild card chars, false otherwise..</returns>
2016-03-30 14:20:52 -07:00
/// <remarks>
/// Currently { '*', '?', '[' } are considered wild card chars and
/// '`' is the escape character.
/// </remarks>
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 ;
}
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
return result ;
}
/// <summary>
/// Unescapes any escaped characters in the input string.
/// </summary>
/// <param name="pattern">
2017-01-16 13:31:14 -08:00
/// The input string containing the text to convert.
2016-03-30 14:20:52 -07:00
/// </param>
/// <returns>
2017-01-16 13:31:14 -08:00
/// A string of characters with any escaped characters
2016-03-30 14:20:52 -07:00
/// converted to their unescaped form.
/// </returns>
/// <exception cref="ArgumentNullException">
/// If <paramref name="pattern" /> is null.
/// </exception>
public static string Unescape ( string pattern )
{
if ( pattern == null )
{
2019-07-20 01:10:10 +05:00
throw PSTraceSource . NewArgumentNullException ( nameof ( pattern ));
2016-03-30 14:20:52 -07:00
}
2019-07-20 01:10:10 +05:00
if ( pattern == string . Empty )
{
return pattern ;
}
Span < char > temp = pattern . Length < StackAllocThreshold ? stackalloc char [ pattern . Length ] : new char [ pattern . Length ];
2016-03-30 14:20:52 -07:00
int tempIndex = 0 ;
bool prevCharWasEscapeChar = false ;
2016-07-29 13:02:49 -07:00
for ( int i = 0 ; i < pattern . Length ; i ++)
2016-03-30 14:20:52 -07:00
{
char ch = pattern [ i ];
2016-07-29 13:02:49 -07:00
if ( ch == escapeChar )
2016-03-30 14:20:52 -07:00
{
if ( prevCharWasEscapeChar )
{
temp [ tempIndex ++] = ch ;
prevCharWasEscapeChar = false ;
}
else
{
prevCharWasEscapeChar = true ;
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
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 ;
2019-07-20 01:10:10 +05:00
if ( tempIndex == pattern . Length )
2016-03-30 14:20:52 -07:00
{
2019-07-20 01:10:10 +05:00
s = pattern ;
2016-03-30 14:20:52 -07:00
}
else
{
2019-07-20 01:10:10 +05:00
s = new string ( temp . Slice ( 0 , tempIndex ));
2016-03-30 14:20:52 -07:00
}
return s ;
2018-12-08 18:53:30 +02:00
}
2016-03-30 14:20:52 -07:00
private static bool IsWildcardChar ( char ch )
{
return ( ch == '*' ) || ( ch == '?' ) || ( ch == '[' ) || ( ch == ']' );
}
/// <summary>
/// Converts this wildcard to a string that can be used as a right-hand-side operand of the LIKE operator of WQL.
2017-01-16 13:31:14 -08:00
/// For example: "a*" will be converted to "a%".
2016-03-30 14:20:52 -07:00
/// </summary>
/// <returns></returns>
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" );
}
}
}
/// <summary>
/// Thrown when a wildcard pattern is invalid.
/// </summary>
[Serializable]
public class WildcardPatternException : RuntimeException
{
/// <summary>
2017-01-16 13:31:14 -08:00
/// Constructor for class WildcardPatternException that takes
2016-03-30 14:20:52 -07:00
/// an ErrorRecord to use in constructing this exception.
/// </summary>
/// <remarks>This is the recommended constructor to use for this exception.</remarks>
/// <param name="errorRecord">
/// ErrorRecord object containing additional information about the error condition.
/// </param>
2018-12-25 21:43:03 +05:00
/// <returns>Constructed object.</returns>
2016-07-29 13:02:49 -07:00
internal WildcardPatternException ( ErrorRecord errorRecord )
: base ( RetrieveMessage ( errorRecord ))
2016-03-30 14:20:52 -07:00
{
2018-06-26 02:37:09 +05:00
if ( errorRecord == null )
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
throw new ArgumentNullException ( "errorRecord" );
2016-03-30 14:20:52 -07:00
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
_errorRecord = errorRecord ;
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
[NonSerialized]
private ErrorRecord _errorRecord ;
/// <summary>
/// Constructs an instance of the WildcardPatternException object.
/// </summary>
public WildcardPatternException ()
{
}
/// <summary>
/// Constructs an instance of the WildcardPatternException object taking
2016-08-28 05:44:55 +00:00
/// a message parameter to use in constructing the exception.
2016-03-30 14:20:52 -07:00
/// </summary>
2018-12-28 16:39:06 +05:00
/// <param name="message">The string to use as the exception message.</param>
2016-03-30 14:20:52 -07:00
public WildcardPatternException ( string message ) : base ( message )
{
}
/// <summary>
/// Constructor for class WildcardPatternException that takes both a message to use
/// and an inner exception to include in this object.
/// </summary>
2018-12-28 16:39:06 +05:00
/// <param name="message">The exception message to use.</param>
2016-03-30 14:20:52 -07:00
/// <param name="innerException">The innerException object to encapsulate.</param>
public WildcardPatternException ( string message ,
Exception innerException )
: base ( message , innerException )
{
}
/// <summary>
/// Constructor for class WildcardPatternException for serialization.
/// </summary>
2018-12-29 08:25:49 +05:00
/// <param name="info">Serialization information.</param>
/// <param name="context">Streaming context.</param>
2016-03-30 14:20:52 -07:00
protected WildcardPatternException ( SerializationInfo info ,
StreamingContext context )
: base ( info , context )
{
}
}
/// <summary>
/// A base class for parsers of <see cref="WildcardPattern"/> patterns.
/// </summary>
internal abstract class WildcardPatternParser
{
/// <summary>
2017-01-16 13:31:14 -08:00
/// Called from <see cref="Parse"/> method to indicate
2016-03-30 14:20:52 -07:00
/// the beginning of the wildcard pattern.
/// Default implementation simply returns.
/// </summary>
/// <param name="pattern">
2017-01-16 13:31:14 -08:00
/// <see cref="WildcardPattern"/> object that includes both
/// the text of the pattern (<see cref="WildcardPattern.Pattern"/>)
2016-03-30 14:20:52 -07:00
/// and the pattern options (<see cref="WildcardPattern.Options"/>)
/// </param>
protected virtual void BeginWildcardPattern ( WildcardPattern pattern )
{
}
/// <summary>
/// Called from <see cref="Parse"/> method to indicate that the next
2017-01-16 13:31:14 -08:00
/// part of the pattern should match
2016-03-30 14:20:52 -07:00
/// a literal character <paramref name="c"/>.
/// </summary>
protected abstract void AppendLiteralCharacter ( char c );
/// <summary>
/// Called from <see cref="Parse"/> method to indicate that the next
2017-01-16 13:31:14 -08:00
/// part of the pattern should match
2016-03-30 14:20:52 -07:00
/// any string, including an empty string.
/// </summary>
protected abstract void AppendAsterix ();
/// <summary>
/// Called from <see cref="Parse"/> method to indicate that the next
2017-01-16 13:31:14 -08:00
/// part of the pattern should match
/// any single character.
2016-03-30 14:20:52 -07:00
/// </summary>
protected abstract void AppendQuestionMark ();
/// <summary>
/// Called from <see cref="Parse"/> method to indicate the end of the wildcard pattern.
/// Default implementation simply returns.
/// </summary>
protected virtual void EndWildcardPattern ()
{
}
/// <summary>
2017-01-16 13:31:14 -08:00
/// Called from <see cref="Parse"/> method to indicate
2016-03-30 14:20:52 -07:00
/// the beginning of a bracket expression.
/// </summary>
/// <remarks>
2017-01-16 13:31:14 -08:00
/// Bracket expressions of <see cref="WildcardPattern"/> are
/// a greatly simplified version of bracket expressions of POSIX wildcards
2019-01-24 20:50:11 +00:00
/// (https://www.opengroup.org/onlinepubs/9699919799/functions/fnmatch.html).
2017-01-16 13:31:14 -08:00
/// Only literal characters and character ranges are supported.
/// Negation (with either '!' or '^' characters),
/// character classes ([:alpha:])
2016-03-30 14:20:52 -07:00
/// and other advanced features are not supported.
/// </remarks>
protected abstract void BeginBracketExpression ();
/// <summary>
/// Called from <see cref="Parse"/> method to indicate that the bracket expression
/// should include a literal character <paramref name="c"/>.
/// </summary>
protected abstract void AppendLiteralCharacterToBracketExpression ( char c );
/// <summary>
/// Called from <see cref="Parse"/> method to indicate that the bracket expression
2017-01-16 13:31:14 -08:00
/// should include all characters from character range
/// starting at <paramref name="startOfCharacterRange"/>
2016-03-30 14:20:52 -07:00
/// and ending at <paramref name="endOfCharacterRange"/>
/// </summary>
protected abstract void AppendCharacterRangeToBracketExpression (
2016-07-29 13:02:49 -07:00
char startOfCharacterRange ,
2016-03-30 14:20:52 -07:00
char endOfCharacterRange );
/// <summary>
/// Called from <see cref="Parse"/> method to indicate the end of a bracket expression.
/// </summary>
protected abstract void EndBracketExpression ();
/// <summary>
/// PowerShell v1 and v2 treats all characters inside
2017-01-16 13:31:14 -08:00
/// <paramref name="brackedExpressionContents"/> as literal characters,
2016-03-30 14:20:52 -07:00
/// 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.
/// </summary>
/// <param name="brackedExpressionContents"></param>
/// <param name="bracketExpressionOperators"></param>
/// <param name="pattern"></param>
/// <remarks>
/// This method should be kept "internal"
/// </remarks>
internal void AppendBracketExpression ( string brackedExpressionContents , string bracketExpressionOperators , string pattern )
{
this . BeginBracketExpression ();
int i = 0 ;
while ( i < brackedExpressionContents . Length )
{
2016-07-29 13:02:49 -07:00
if ((( i + 2 ) < brackedExpressionContents . Length ) &&
2016-03-30 14:20:52 -07:00
( 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 ();
}
/// <summary>
2017-01-16 13:31:14 -08:00
/// Parses <paramref name="pattern"/>, calling appropriate overloads
2016-03-30 14:20:52 -07:00
/// in <paramref name="parser"/>
/// </summary>
2018-12-28 16:39:06 +05:00
/// <param name="pattern">Pattern to parse.</param>
/// <param name="parser">Parser to call back.</param>
2016-08-02 19:00:50 -07:00
public static void Parse ( WildcardPattern pattern , WildcardPatternParser parser )
2016-03-30 14:20:52 -07:00
{
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
2017-01-16 13:31:14 -08:00
// This is different than the POSIX spec
2019-01-24 20:50:11 +00:00
// (at https://www.opengroup.org/onlinepubs/9699919799/functions/fnmatch.html),
2016-03-30 14:20:52 -07:00
// 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 =
2016-07-29 13:02:49 -07:00
new ErrorRecord ( pce ,
2016-03-30 14:20:52 -07:00
"WildcardPattern_Invalid" ,
ErrorCategory . InvalidArgument ,
2016-07-29 13:02:49 -07:00
null );
2016-03-30 14:20:52 -07:00
WildcardPatternException e =
2016-07-29 13:02:49 -07:00
new WildcardPatternException ( er );
2016-03-30 14:20:52 -07:00
return e ;
}
};
/// <summary>
2019-01-06 04:40:24 +05:00
/// Convert a string with wild cards into its equivalent regex.
2016-03-30 14:20:52 -07:00
/// </summary>
/// <remarks>
/// 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
/// </remarks>
internal class WildcardPatternToRegexParser : WildcardPatternParser
{
2016-07-29 13:02:49 -07:00
private StringBuilder _regexPattern ;
private RegexOptions _regexOptions ;
2016-03-30 14:20:52 -07:00
private const string regexChars = "()[.?*{}^$+|\\" ; // ']' is missing on purpose
private static bool IsRegexChar ( char ch )
{
2016-07-29 13:02:49 -07:00
for ( int i = 0 ; i < regexChars . Length ; i ++)
2016-03-30 14:20:52 -07:00
{
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 ;
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
if (( options & WildcardOptions . IgnoreCase ) != 0 )
{
regexOptions |= RegexOptions . IgnoreCase ;
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
if (( options & WildcardOptions . CultureInvariant ) == WildcardOptions . CultureInvariant )
{
regexOptions |= RegexOptions . CultureInvariant ;
}
return regexOptions ;
}
protected override void BeginWildcardPattern ( WildcardPattern pattern )
{
2016-07-29 13:02:49 -07:00
_regexPattern = new StringBuilder ( pattern . Pattern . Length * 2 + 2 );
_regexPattern . Append ( '^' );
2016-03-30 14:20:52 -07:00
2016-07-29 13:02:49 -07:00
_regexOptions = TranslateWildcardOptionsIntoRegexOptions ( pattern . Options );
2016-03-30 14:20:52 -07:00
}
internal static void AppendLiteralCharacter ( StringBuilder regexPattern , char c )
{
if ( IsRegexChar ( c ))
{
regexPattern . Append ( '\\' );
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
regexPattern . Append ( c );
}
protected override void AppendLiteralCharacter ( char c )
{
2016-07-29 13:02:49 -07:00
AppendLiteralCharacter ( _regexPattern , c );
2016-03-30 14:20:52 -07:00
}
protected override void AppendAsterix ()
{
2016-07-29 13:02:49 -07:00
_regexPattern . Append ( ".*" );
2016-03-30 14:20:52 -07:00
}
protected override void AppendQuestionMark ()
{
2016-07-29 13:02:49 -07:00
_regexPattern . Append ( '.' );
2016-03-30 14:20:52 -07:00
}
protected override void EndWildcardPattern ()
{
2016-07-29 13:02:49 -07:00
_regexPattern . Append ( '$' );
2016-03-30 14:20:52 -07:00
// 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).
2016-07-29 13:02:49 -07:00
string regexPatternString = _regexPattern . ToString ();
2016-03-30 14:20:52 -07:00
if ( regexPatternString . Equals ( "^.*$" , StringComparison . Ordinal ))
{
2016-07-29 13:02:49 -07:00
_regexPattern . Remove ( 0 , 4 );
2016-03-30 14:20:52 -07:00
}
2016-07-29 13:02:49 -07:00
else
2016-03-30 14:20:52 -07:00
{
if ( regexPatternString . StartsWith ( "^.*" , StringComparison . Ordinal ))
{
2016-07-29 13:02:49 -07:00
_regexPattern . Remove ( 0 , 3 );
2016-03-30 14:20:52 -07:00
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
if ( regexPatternString . EndsWith ( ".*$" , StringComparison . Ordinal ))
{
2016-07-29 13:02:49 -07:00
_regexPattern . Remove ( _regexPattern . Length - 3 , 3 );
2016-03-30 14:20:52 -07:00
}
}
}
protected override void BeginBracketExpression ()
{
2016-07-29 13:02:49 -07:00
_regexPattern . Append ( '[' );
2016-03-30 14:20:52 -07:00
}
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 );
}
}
2018-12-23 22:20:06 -08:00
2016-03-30 14:20:52 -07:00
protected override void AppendLiteralCharacterToBracketExpression ( char c )
{
2016-07-29 13:02:49 -07:00
AppendLiteralCharacterToBracketExpression ( _regexPattern , c );
2016-03-30 14:20:52 -07:00
}
internal static void AppendCharacterRangeToBracketExpression (
StringBuilder regexPattern ,
2016-07-29 13:02:49 -07:00
char startOfCharacterRange ,
2016-03-30 14:20:52 -07:00
char endOfCharacterRange )
{
AppendLiteralCharacterToBracketExpression ( regexPattern , startOfCharacterRange );
regexPattern . Append ( '-' );
AppendLiteralCharacterToBracketExpression ( regexPattern , endOfCharacterRange );
}
protected override void AppendCharacterRangeToBracketExpression (
char startOfCharacterRange ,
char endOfCharacterRange )
{
2016-07-29 13:02:49 -07:00
AppendCharacterRangeToBracketExpression ( _regexPattern , startOfCharacterRange , endOfCharacterRange );
2016-03-30 14:20:52 -07:00
}
protected override void EndBracketExpression ()
{
2016-07-29 13:02:49 -07:00
_regexPattern . Append ( ']' );
2016-03-30 14:20:52 -07:00
}
/// <summary>
/// Parses a <paramref name="wildcardPattern"/> into a <see cref="Regex"/>
/// </summary>
2018-12-28 16:39:06 +05:00
/// <param name="wildcardPattern">Wildcard pattern to parse.</param>
2016-03-30 14:20:52 -07:00
/// <returns>Regular expression equivalent to <paramref name="wildcardPattern"/></returns>
2016-08-02 19:00:50 -07:00
public static Regex Parse ( WildcardPattern wildcardPattern )
2016-03-30 14:20:52 -07:00
{
WildcardPatternToRegexParser parser = new WildcardPatternToRegexParser ();
WildcardPatternParser . Parse ( wildcardPattern , parser );
try
{
2019-11-01 23:14:31 +05:00
return ParserOps . NewRegex ( parser . _regexPattern . ToString (), parser . _regexOptions );
2016-03-30 14:20:52 -07:00
}
2016-07-29 13:02:49 -07:00
catch ( ArgumentException )
2016-03-30 14:20:52 -07:00
{
throw WildcardPatternParser . NewWildcardPatternException ( wildcardPattern . Pattern );
}
}
}
internal class WildcardPatternMatcher
{
private readonly PatternElement [] _patternElements ;
private readonly CharacterNormalizer _characterNormalizer ;
internal WildcardPatternMatcher ( WildcardPattern wildcardPattern )
{
2016-07-29 13:02:49 -07:00
_characterNormalizer = new CharacterNormalizer ( wildcardPattern . Options );
_patternElements = MyWildcardPatternParser . Parse (
wildcardPattern ,
_characterNormalizer );
2016-03-30 14:20:52 -07:00
}
internal bool IsMatch ( string str )
{
// - each state of NFA is represented by (patternPosition, stringPosition) tuple
// - state transitions are documented in
// ProcessStringCharacter and ProcessEndOfString methods
2017-01-16 13:31:14 -08:00
// - the algorithm below tries to see if there is a path
2016-03-30 14:20:52 -07:00
// from (0, 0) to (lengthOfPattern, lengthOfString)
// - this is a regular graph traversal
2017-01-16 13:31:14 -08:00
// - there are O(1) edges per node (at most 2 edges)
2016-03-30 14:20:52 -07:00
// so the whole graph traversal takes O(number of nodes in the graph) =
// = O(lengthOfPattern * lengthOfString) time
2017-01-16 13:31:14 -08:00
// - for efficient remembering which states have already been visited,
2016-03-30 14:20:52 -07:00
// 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
2019-01-24 20:50:11 +00:00
// https://en.wikipedia.org/wiki/Regular_expression#Implementations_and_running_times
2016-03-30 14:20:52 -07:00
2016-07-29 13:02:49 -07:00
var patternPositionsForCurrentStringPosition =
new PatternPositionsVisitor ( _patternElements . Length );
2016-03-30 14:20:52 -07:00
patternPositionsForCurrentStringPosition . Add ( 0 );
2016-07-29 13:02:49 -07:00
var patternPositionsForNextStringPosition =
new PatternPositionsVisitor ( _patternElements . Length );
2016-03-30 14:20:52 -07:00
2019-06-27 20:14:41 -07:00
try
2016-03-30 14:20:52 -07:00
{
2019-06-27 20:14:41 -07:00
for ( int currentStringPosition = 0 ;
currentStringPosition < str . Length ;
currentStringPosition ++)
2016-03-30 14:20:52 -07:00
{
2019-06-27 20:14:41 -07:00
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 ;
2016-03-30 14:20:52 -07:00
}
2019-06-27 20:14:41 -07:00
int patternPosition2 ;
while ( patternPositionsForCurrentStringPosition . MoveNext ( out patternPosition2 ))
{
_patternElements [ patternPosition2 ]. ProcessEndOfString (
patternPosition2 ,
patternPositionsForCurrentStringPosition );
}
2016-03-30 14:20:52 -07:00
2019-06-27 20:14:41 -07:00
return patternPositionsForCurrentStringPosition . ReachedEndOfPattern ;
}
finally
2016-03-30 14:20:52 -07:00
{
2019-06-27 20:14:41 -07:00
patternPositionsForCurrentStringPosition . Dispose ();
patternPositionsForNextStringPosition . Dispose ();
2016-03-30 14:20:52 -07:00
}
}
2019-06-27 20:14:41 -07:00
private class PatternPositionsVisitor : IDisposable
2016-03-30 14:20:52 -07:00
{
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" );
2016-07-29 13:02:49 -07:00
_lengthOfPattern = lengthOfPattern ;
2016-03-30 14:20:52 -07:00
2019-06-27 20:14:41 -07:00
_isPatternPositionVisitedMarker = ArrayPool < int >. Shared . Rent ( _lengthOfPattern + 1 );
for ( int i = 0 ; i <= _lengthOfPattern ; i ++)
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
_isPatternPositionVisitedMarker [ i ] = - 1 ;
2016-03-30 14:20:52 -07:00
}
2019-06-27 20:14:41 -07:00
_patternPositionsForFurtherProcessing = ArrayPool < int >. Shared . Rent ( _lengthOfPattern );
2016-07-29 13:02:49 -07:00
_patternPositionsForFurtherProcessingCount = 0 ;
2016-03-30 14:20:52 -07:00
}
2019-06-27 20:14:41 -07:00
public void Dispose ()
{
ArrayPool < int >. Shared . Return ( _isPatternPositionVisitedMarker , clearArray : true );
ArrayPool < int >. Shared . Return ( _patternPositionsForFurtherProcessing , clearArray : true );
}
2016-03-30 14:20:52 -07:00
public int StringPosition { private get ; set ; }
public void Add ( int patternPosition )
{
Dbg . Assert ( patternPosition >= 0 , "Caller should verify patternPosition >= 0" );
Dbg . Assert (
2016-07-29 13:02:49 -07:00
patternPosition <= _lengthOfPattern ,
2016-03-30 14:20:52 -07:00
"Caller should verify patternPosition <= this._lengthOfPattern" );
2019-06-27 20:14:41 -07:00
// is patternPosition already visited?
2016-07-29 13:02:49 -07:00
if ( _isPatternPositionVisitedMarker [ patternPosition ] == this . StringPosition )
2016-03-30 14:20:52 -07:00
{
return ;
}
// mark patternPosition as visited
2016-07-29 13:02:49 -07:00
_isPatternPositionVisitedMarker [ patternPosition ] = this . StringPosition ;
2016-03-30 14:20:52 -07:00
// add patternPosition to the queue for further processing
2016-07-29 13:02:49 -07:00
if ( patternPosition < _lengthOfPattern )
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
_patternPositionsForFurtherProcessing [ _patternPositionsForFurtherProcessingCount ] = patternPosition ;
_patternPositionsForFurtherProcessingCount ++;
2016-03-30 14:20:52 -07:00
Dbg . Assert (
2016-07-29 13:02:49 -07:00
_patternPositionsForFurtherProcessingCount <= _lengthOfPattern ,
2016-03-30 14:20:52 -07:00
"There should never be more elements in the queue than the length of the pattern" );
}
}
public bool ReachedEndOfPattern
{
get
{
2016-07-29 13:02:49 -07:00
return _isPatternPositionVisitedMarker [ _lengthOfPattern ] >= this . StringPosition ;
2016-03-30 14:20:52 -07:00
}
}
2017-01-16 13:31:14 -08:00
// non-virtual MoveNext is more performant
// than implementing IEnumerable / virtual MoveNext
2016-03-30 14:20:52 -07:00
public bool MoveNext ( out int patternPosition )
{
Dbg . Assert (
2016-07-29 13:02:49 -07:00
_patternPositionsForFurtherProcessingCount >= 0 ,
2016-03-30 14:20:52 -07:00
"There should never be more elements in the queue than the length of the pattern" );
2016-07-29 13:02:49 -07:00
if ( _patternPositionsForFurtherProcessingCount == 0 )
2016-03-30 14:20:52 -07:00
{
patternPosition = - 1 ;
return false ;
}
2016-07-29 13:02:49 -07:00
_patternPositionsForFurtherProcessingCount --;
patternPosition = _patternPositionsForFurtherProcessing [ _patternPositionsForFurtherProcessingCount ];
2016-03-30 14:20:52 -07:00
return true ;
}
}
private abstract class PatternElement
{
public abstract void ProcessStringCharacter (
2016-07-29 13:02:49 -07:00
char currentStringCharacter ,
int currentPatternPosition ,
PatternPositionsVisitor patternPositionsForCurrentStringPosition ,
2016-03-30 14:20:52 -07:00
PatternPositionsVisitor patternPositionsForNextStringPosition );
public abstract void ProcessEndOfString (
2016-07-29 13:02:49 -07:00
int currentPatternPosition ,
2016-03-30 14:20:52 -07:00
PatternPositionsVisitor patternPositionsForEndOfStringPosition );
}
private class QuestionMarkElement : PatternElement
{
public override void ProcessStringCharacter (
2016-07-29 13:02:49 -07:00
char currentStringCharacter ,
int currentPatternPosition ,
PatternPositionsVisitor patternPositionsForCurrentStringPosition ,
2016-03-30 14:20:52 -07:00
PatternPositionsVisitor patternPositionsForNextStringPosition )
{
// '?' : (patternPosition, stringPosition) => (patternPosition + 1, stringPosition + 1)
patternPositionsForNextStringPosition . Add ( currentPatternPosition + 1 );
}
public override void ProcessEndOfString (
2016-07-29 13:02:49 -07:00
int currentPatternPosition ,
2016-03-30 14:20:52 -07:00
PatternPositionsVisitor patternPositionsForEndOfStringPosition )
{
// '?' : (patternPosition, endOfString) => <no transitions out of this state - cannot move beyond end of string>
}
}
private class LiteralCharacterElement : QuestionMarkElement
{
private readonly char _literalCharacter ;
public LiteralCharacterElement ( char literalCharacter )
{
2016-07-29 13:02:49 -07:00
_literalCharacter = literalCharacter ;
2016-03-30 14:20:52 -07:00
}
public override void ProcessStringCharacter (
2016-07-29 13:02:49 -07:00
char currentStringCharacter ,
int currentPatternPosition ,
PatternPositionsVisitor patternPositionsForCurrentStringPosition ,
2016-03-30 14:20:52 -07:00
PatternPositionsVisitor patternPositionsForNextStringPosition )
{
2016-07-29 13:02:49 -07:00
if ( _literalCharacter == currentStringCharacter )
2016-03-30 14:20:52 -07:00
{
base . ProcessStringCharacter (
2016-07-29 13:02:49 -07:00
currentStringCharacter ,
currentPatternPosition ,
patternPositionsForCurrentStringPosition ,
2016-03-30 14:20:52 -07:00
patternPositionsForNextStringPosition );
}
}
}
private class BracketExpressionElement : QuestionMarkElement
{
private readonly Regex _regex ;
public BracketExpressionElement ( Regex regex )
{
Dbg . Assert ( regex != null , "Caller should verify regex != null" );
2016-07-29 13:02:49 -07:00
_regex = regex ;
2016-03-30 14:20:52 -07:00
}
public override void ProcessStringCharacter (
2016-07-29 13:02:49 -07:00
char currentStringCharacter ,
int currentPatternPosition ,
PatternPositionsVisitor patternPositionsForCurrentStringPosition ,
2016-03-30 14:20:52 -07:00
PatternPositionsVisitor patternPositionsForNextStringPosition )
{
2016-07-29 13:02:49 -07:00
if ( _regex . IsMatch ( new string ( currentStringCharacter , 1 )))
2016-03-30 14:20:52 -07:00
{
base . ProcessStringCharacter ( currentStringCharacter , currentPatternPosition ,
patternPositionsForCurrentStringPosition ,
patternPositionsForNextStringPosition );
}
}
}
private class AsterixElement : PatternElement
{
public override void ProcessStringCharacter (
2016-07-29 13:02:49 -07:00
char currentStringCharacter ,
int currentPatternPosition ,
PatternPositionsVisitor patternPositionsForCurrentStringPosition ,
2016-03-30 14:20:52 -07:00
PatternPositionsVisitor patternPositionsForNextStringPosition )
{
// '*' : (patternPosition, stringPosition) => (patternPosition + 1, stringPosition)
patternPositionsForCurrentStringPosition . Add ( currentPatternPosition + 1 );
// '*' : (patternPosition, stringPosition) => (patternPosition, stringPosition + 1)
patternPositionsForNextStringPosition . Add ( currentPatternPosition );
}
public override void ProcessEndOfString (
2016-07-29 13:02:49 -07:00
int currentPatternPosition ,
2016-03-30 14:20:52 -07:00
PatternPositionsVisitor patternPositionsForEndOfStringPosition )
{
// '*' : (patternPosition, endOfString) => (patternPosition + 1, endOfString)
patternPositionsForEndOfStringPosition . Add ( currentPatternPosition + 1 );
}
}
private class MyWildcardPatternParser : WildcardPatternParser
{
private readonly List < PatternElement > _patternElements = new List < PatternElement >();
private CharacterNormalizer _characterNormalizer ;
private RegexOptions _regexOptions ;
private StringBuilder _bracketExpressionBuilder ;
2016-08-02 19:00:50 -07:00
public static PatternElement [] Parse (
2016-07-29 13:02:49 -07:00
WildcardPattern pattern ,
2016-03-30 14:20:52 -07:00
CharacterNormalizer characterNormalizer )
{
var parser = new MyWildcardPatternParser
2016-07-29 13:02:49 -07:00
{
_characterNormalizer = characterNormalizer ,
_regexOptions = WildcardPatternToRegexParser . TranslateWildcardOptionsIntoRegexOptions ( pattern . Options ),
};
2016-03-30 14:20:52 -07:00
WildcardPatternParser . Parse ( pattern , parser );
return parser . _patternElements . ToArray ();
}
protected override void AppendLiteralCharacter ( char c )
{
2016-07-29 13:02:49 -07:00
c = _characterNormalizer . Normalize ( c );
_patternElements . Add ( new LiteralCharacterElement ( c ));
2016-03-30 14:20:52 -07:00
}
protected override void AppendAsterix ()
{
2016-07-29 13:02:49 -07:00
_patternElements . Add ( new AsterixElement ());
2016-03-30 14:20:52 -07:00
}
protected override void AppendQuestionMark ()
{
2016-07-29 13:02:49 -07:00
_patternElements . Add ( new QuestionMarkElement ());
2016-03-30 14:20:52 -07:00
}
protected override void BeginBracketExpression ()
{
2016-07-29 13:02:49 -07:00
_bracketExpressionBuilder = new StringBuilder ();
_bracketExpressionBuilder . Append ( '[' );
2016-03-30 14:20:52 -07:00
}
protected override void AppendLiteralCharacterToBracketExpression ( char c )
{
WildcardPatternToRegexParser . AppendLiteralCharacterToBracketExpression (
2016-07-29 13:02:49 -07:00
_bracketExpressionBuilder ,
2016-03-30 14:20:52 -07:00
c );
}
protected override void AppendCharacterRangeToBracketExpression (
2016-07-29 13:02:49 -07:00
char startOfCharacterRange ,
2016-03-30 14:20:52 -07:00
char endOfCharacterRange )
{
WildcardPatternToRegexParser . AppendCharacterRangeToBracketExpression (
2016-07-29 13:02:49 -07:00
_bracketExpressionBuilder ,
startOfCharacterRange ,
2016-03-30 14:20:52 -07:00
endOfCharacterRange );
}
protected override void EndBracketExpression ()
{
2016-07-29 13:02:49 -07:00
_bracketExpressionBuilder . Append ( ']' );
2019-11-01 23:14:31 +05:00
Regex regex = ParserOps . NewRegex ( _bracketExpressionBuilder . ToString (), _regexOptions );
2016-07-29 13:02:49 -07:00
_patternElements . Add ( new BracketExpressionElement ( regex ));
2016-03-30 14:20:52 -07:00
}
}
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 )
{
2016-07-29 13:02:49 -07:00
if ( _caseInsensitive )
2016-03-30 14:20:52 -07:00
{
2016-07-29 13:02:49 -07:00
return _cultureInfo . TextInfo . ToLower ( x );
2016-03-30 14:20:52 -07:00
}
return x ;
}
}
}
/// <summary>
2019-01-06 04:40:24 +05:00
/// Translates a <see cref="WildcardPattern"/> into a DOS wildcard.
2016-03-30 14:20:52 -07:00
/// </summary>
internal class WildcardPatternToDosWildcardParser : WildcardPatternParser
{
2016-07-29 13:02:49 -07:00
private readonly StringBuilder _result = new StringBuilder ();
2016-03-30 14:20:52 -07:00
protected override void AppendLiteralCharacter ( char c )
{
2016-07-29 13:02:49 -07:00
_result . Append ( c );
2016-03-30 14:20:52 -07:00
}
protected override void AppendAsterix ()
{
2016-07-29 13:02:49 -07:00
_result . Append ( '*' );
2016-03-30 14:20:52 -07:00
}
protected override void AppendQuestionMark ()
{
2016-07-29 13:02:49 -07:00
_result . Append ( '?' );
2016-03-30 14:20:52 -07:00
}
protected override void BeginBracketExpression ()
{
}
protected override void AppendLiteralCharacterToBracketExpression ( char c )
{
}
protected override void AppendCharacterRangeToBracketExpression ( char startOfCharacterRange , char endOfCharacterRange )
{
}
protected override void EndBracketExpression ()
{
2016-07-29 13:02:49 -07:00
_result . Append ( '?' );
2016-03-30 14:20:52 -07:00
}
/// <summary>
2019-01-06 04:40:24 +05:00
/// Converts <paramref name="wildcardPattern"/> into a DOS wildcard.
2016-03-30 14:20:52 -07:00
/// </summary>
2016-08-02 19:00:50 -07:00
internal static string Parse ( WildcardPattern wildcardPattern )
2016-03-30 14:20:52 -07:00
{
var parser = new WildcardPatternToDosWildcardParser ();
WildcardPatternParser . Parse ( wildcardPattern , parser );
2016-07-29 13:02:49 -07:00
return parser . _result . ToString ();
2016-03-30 14:20:52 -07:00
}
}
}