How do I check if a filename matches a wildcard pattern - regex

I've got a wildcard pattern, perhaps "*.txt" or "POS??.dat".
I also have list of filenames in memory that I need to compare to that pattern.
How would I do that, keeping in mind I need exactly the same semantics that IO.DirectoryInfo.GetFiles(pattern) uses.
EDIT: Blindly translating this into a regex will NOT work.

I have a complete answer in code for you that's 95% like FindFiles(string).
The 5% that isn't there is the short names/long names behavior in the second note on the MSDN documentation for this function.
If you would still like to get that behavior, you'll have to complete a computation of the short name of each string you have in the input array, and then add the long name to the collection of matches if either the long or short name matches the pattern.
Here is the code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace FindFilesRegEx
{
class Program
{
static void Main(string[] args)
{
string[] names = { "hello.t", "HelLo.tx", "HeLLo.txt", "HeLLo.txtsjfhs", "HeLLo.tx.sdj", "hAlLo20984.txt" };
string[] matches;
matches = FindFilesEmulator("hello.tx", names);
matches = FindFilesEmulator("H*o*.???", names);
matches = FindFilesEmulator("hello.txt", names);
matches = FindFilesEmulator("lskfjd30", names);
}
public string[] FindFilesEmulator(string pattern, string[] names)
{
List<string> matches = new List<string>();
Regex regex = FindFilesPatternToRegex.Convert(pattern);
foreach (string s in names)
{
if (regex.IsMatch(s))
{
matches.Add(s);
}
}
return matches.ToArray();
}
internal static class FindFilesPatternToRegex
{
private static Regex HasQuestionMarkRegEx = new Regex(#"\?", RegexOptions.Compiled);
private static Regex IllegalCharactersRegex = new Regex("[" + #"\/:<>|" + "\"]", RegexOptions.Compiled);
private static Regex CatchExtentionRegex = new Regex(#"^\s*.+\.([^\.]+)\s*$", RegexOptions.Compiled);
private static string NonDotCharacters = #"[^.]*";
public static Regex Convert(string pattern)
{
if (pattern == null)
{
throw new ArgumentNullException();
}
pattern = pattern.Trim();
if (pattern.Length == 0)
{
throw new ArgumentException("Pattern is empty.");
}
if(IllegalCharactersRegex.IsMatch(pattern))
{
throw new ArgumentException("Pattern contains illegal characters.");
}
bool hasExtension = CatchExtentionRegex.IsMatch(pattern);
bool matchExact = false;
if (HasQuestionMarkRegEx.IsMatch(pattern))
{
matchExact = true;
}
else if(hasExtension)
{
matchExact = CatchExtentionRegex.Match(pattern).Groups[1].Length != 3;
}
string regexString = Regex.Escape(pattern);
regexString = "^" + Regex.Replace(regexString, #"\\\*", ".*");
regexString = Regex.Replace(regexString, #"\\\?", ".");
if(!matchExact && hasExtension)
{
regexString += NonDotCharacters;
}
regexString += "$";
Regex regex = new Regex(regexString, RegexOptions.Compiled | RegexOptions.IgnoreCase);
return regex;
}
}
}
}

You can simply do this. You do not need regular expressions.
using Microsoft.VisualBasic.CompilerServices;
if (Operators.LikeString("pos123.txt", "pos?23.*", CompareMethod.Text))
{
Console.WriteLine("Filename matches pattern");
}
Or, in VB.Net,
If "pos123.txt" Like "pos?23.*" Then
Console.WriteLine("Filename matches pattern")
End If
In c# you could simulate this with an extension method. It wouldn't be exactly like VB Like, but it would be like...very cool.

You could translate the wildcards into a regular expression:
*.txt -> ^.+\.txt$
POS??.dat _> ^POS..\.dat$
Use the Regex.Escape method to escape the characters that are not wildcars into literal strings for the pattern (e.g. converting ".txt" to "\.txt").
The wildcard * translates into .+, and ? translates into .
Put ^ at the beginning of the pattern to match the beginning of the string, and $ at the end to match the end of the string.
Now you can use the Regex.IsMatch method to check if a file name matches the pattern.

Just call the Windows API function PathMatchSpecExW().
[Flags]
public enum MatchPatternFlags : uint
{
Normal = 0x00000000, // PMSF_NORMAL
Multiple = 0x00000001, // PMSF_MULTIPLE
DontStripSpaces = 0x00010000 // PMSF_DONT_STRIP_SPACES
}
class FileName
{
[DllImport("Shlwapi.dll", SetLastError = false)]
static extern int PathMatchSpecExW([MarshalAs(UnmanagedType.LPWStr)] string file,
[MarshalAs(UnmanagedType.LPWStr)] string spec,
MatchPatternFlags flags);
/*******************************************************************************
* Function: MatchPattern
*
* Description: Matches a file name against one or more file name patterns.
*
* Arguments: file - File name to check
* spec - Name pattern(s) to search foe
* flags - Flags to modify search condition (MatchPatternFlags)
*
* Return value: Returns true if name matches the pattern.
*******************************************************************************/
public static bool MatchPattern(string file, string spec, MatchPatternFlags flags)
{
if (String.IsNullOrEmpty(file))
return false;
if (String.IsNullOrEmpty(spec))
return true;
int result = PathMatchSpecExW(file, spec, flags);
return (result == 0);
}
}

Some kind of regex/glob is the way to go, but there are some subtleties; your question indicates you want identical semantics to IO.DirectoryInfo.GetFiles. That could be a challenge, because of the special cases involving 8.3 vs. long file names and the like. The whole story is on MSDN.
If you don't need an exact behavioral match, there are a couple of good SO questions:
glob pattern matching in .NET
How to implement glob in C#

For anyone who comes across this question now that it is years later, I found over at the MSDN social boards that the GetFiles() method will accept * and ? wildcard characters in the searchPattern parameter. (At least in .Net 3.5, 4.0, and 4.5)
Directory.GetFiles(string path, string searchPattern)
http://msdn.microsoft.com/en-us/library/wz42302f.aspx

Plz try the below code.
static void Main(string[] args)
{
string _wildCardPattern = "*.txt";
List<string> _fileNames = new List<string>();
_fileNames.Add("text_file.txt");
_fileNames.Add("csv_file.csv");
Console.WriteLine("\nFilenames that matches [{0}] pattern are : ", _wildCardPattern);
foreach (string _fileName in _fileNames)
{
CustomWildCardPattern _patetrn = new CustomWildCardPattern(_wildCardPattern);
if (_patetrn.IsMatch(_fileName))
{
Console.WriteLine("{0}", _fileName);
}
}
}
public class CustomWildCardPattern : Regex
{
public CustomWildCardPattern(string wildCardPattern)
: base(WildcardPatternToRegex(wildCardPattern))
{
}
public CustomWildCardPattern(string wildcardPattern, RegexOptions regexOptions)
: base(WildcardPatternToRegex(wildcardPattern), regexOptions)
{
}
private static string WildcardPatternToRegex(string wildcardPattern)
{
string patternWithWildcards = "^" + Regex.Escape(wildcardPattern).Replace("\\*", ".*");
patternWithWildcards = patternWithWildcards.Replace("\\?", ".") + "$";
return patternWithWildcards;
}
}

For searching against a specific pattern, it might be worth using File Globbing which allows you to use search patterns like you would in a .gitignore file.
See here: https://learn.microsoft.com/en-us/dotnet/core/extensions/file-globbing
This allows you to add both inclusions & exclusions to your search.
Please see below the example code snippet from the Microsoft Source above:
Matcher matcher = new Matcher();
matcher.AddIncludePatterns(new[] { "*.txt" });
IEnumerable<string> matchingFiles = matcher.GetResultsInFullPath(filepath);

The use of RegexOptions.IgnoreCase will fix it.
public class WildcardPattern : Regex {
public WildcardPattern(string wildCardPattern)
: base(ConvertPatternToRegex(wildCardPattern), RegexOptions.IgnoreCase) {
}
public WildcardPattern(string wildcardPattern, RegexOptions regexOptions)
: base(ConvertPatternToRegex(wildcardPattern), regexOptions) {
}
private static string ConvertPatternToRegex(string wildcardPattern) {
string patternWithWildcards = Regex.Escape(wildcardPattern).Replace("\\*", ".*");
patternWithWildcards = string.Concat("^", patternWithWildcards.Replace("\\?", "."), "$");
return patternWithWildcards;
}
}

Related

Using Matcher to extract URL domain name

static String AdrPattern="http://www.([^&]+)\\.com\\.*";
static Pattern WebUrlPattern = Pattern.compile (AdrPattern);
static Matcher WebUrlMatcher;
WebUrlMatcher = WebUrlPattern.matcher ("keyword");
if(WebUrlMatcher.matches())
String extractedPath = WebUrlMatcher.group (1);
Considering above codes, My aim is to extract the domain name from the URL and dismiss the rest. But the trouble is that, first of all, if the URL has deeper path, it will not ignore it and second, it does not work for all URL with .com extension.
For example, if the URL is http://www.lego.com/en-us/technic/?domainredir=technic.lego, the result will not be lego but lego.com/en-us/technic/?domainredir=technic.lego.
Use
static String AdrPattern="http://www\\.([^&]+)\\.com.*";
^^ ^
You escaped the final dot, and it was treated as a literal, and matches could not match the entire string. Also, the first dot must be escaped.
Also, to make the regex a bit more strict, you can replace the [^&]+ with [^/&].
UPDATE:
static String AdrPattern="http://www\\.([^/&]+)\\.com/([^/]+)/([^/]+)/([^/]+).*";
static Pattern WebUrlPattern = Pattern.compile (AdrPattern);
static Matcher WebUrlMatcher = WebUrlPattern.matcher("http://www.lego.com/en-us/technic/?domainredir=technic.lego");
if(WebUrlMatcher.matches()) {
String extractedPath = WebUrlMatcher.group(1);
String extractedPart1 = WebUrlMatcher.group(2);
String extractedPart2 = WebUrlMatcher.group(3);
String extractedPart3 = WebUrlMatcher.group(4);
}
Or, with \G:
static String AdrPattern="(?:http://www\\.([^/&]+)\\.com/|(?!^)\\G)/?([^/]+)";
static String AdrPattern="http://www\\.([^/&]+)\\.com/([^/]+)/([^/]+)/([^/]+)";
static Pattern WebUrlPattern = Pattern.compile (AdrPattern);
static Matcher WebUrlMatcher = WebUrlPattern.matcher("http://www.lego.com/en-us/technic/?domainredir=technic.lego");
int cnt = 0;
while(WebUrlMatcher.find()) {
if (cnt == 0) {
String extractedPath = WebUrlMatcher.group(1);
String extractedPart = WebUrlMatcher.group(2);
cnt = cnt + 1;
}
else {
String extractedPart = WebUrlMatcher.group(2);
}
}

email regex for upper and lowercase - allow for match in validation

I need a regex for email that allows uppercase and lowercase and I want to validate with jquery .match(). Right now I have this working for lowercase only.
^[_a-z0-9-]+(\.[_a-z0-9-]+)*#[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$
I'd like one for UC and LC or a combo. Thanks for your help.
function validateEmail($email) {
var emailRegex = /^[a-zA-Z]+#[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/;
if (!emailRegex.test( $email )) {
return false;
} else {
return true;
}
}
You can use this like:
if( !validateEmail("LowUp#gmail.com")) { /* Code for an invalid emailaddress */ }
Resource: https://stackoverflow.com/a/9082446/4168014
Ran across this when looking for a regex for uppercase and lowercase letters in an email, so wanted to update this question with an answer that worked for me since the others had some limitations.
^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*(\+[a-zA-Z0-9-]+)?#[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*$
This will allow for .'s, numbers, lowercase, and uppercase letters in an email with no limit of characters after the '#' sign.
regex
"^([\\w-]+(?:\\.[\\w-]+)*)#((?:[\\w-]+\\.)*\\w[\\w-]{0,66})\\.([A-Za-z]{2,6}(?:\\.[A-Za-z]{2,6})?)$"
Reg expression also works when do we work with capital letters.
public class EmailValidator {
public static final String EMAIL_REGEX_PATTERN = "^([\\w-]+(?:\\.[\\w-]+)*)#((?:[\\w-]+\\.)*\\w[\\w-]{0,66})\\.([A-Za-z]{2,6}(?:\\.[A-Za-z]{2,6})?)$";
// static String emailId = "GAVIN.HOUSTON#TRANSPORT.NSW.au";
// static String emailId = "GAVIN.HOUSTON#transport.gov.nsw.au";
static String emailId = "ap.invoices#sydney.edu.au";
public static void main(String[] args) {
System.out.println("is valid email " + isValidEmail(emailId));
}
public static boolean isValidEmail(final String email) {
boolean result = false;
if (email != null) {
Pattern pattern = Pattern.compile(EMAIL_REGEX_PATTERN);
Matcher matcher = pattern.matcher(email);
result = matcher.matches();
}
return result;
}
}

String replace with dictionary exception handling

I've implemented the answer here to do token replacements of a string:
https://stackoverflow.com/a/1231815/1224021
My issue now is when this method finds a token with a value that is not in the dictionary. I get the exception "The given key was not present in the dictionary." and just return the normal string. What I'd like to happen obviously is all the good tokens get replaced, but the offending one remains au naturale. Guessing I'll need to do a loop vs. the one line regex replace? Using vb.net. Here's what I'm currently doing:
Shared ReadOnly re As New Regex("\$(\w+)\$", RegexOptions.Compiled)
Public Shared Function GetTokenContent(ByVal val As String) As String
Dim retval As String = val
Try
If Not String.IsNullOrEmpty(val) AndAlso val.Contains("$") Then
Dim args = GetRatesDictionary()
retval = re.Replace(val, Function(match) args(match.Groups(1).Value))
End If
Catch ex As Exception
' not sure how to handle?
End Try
Return retval
End Function
The exception is likely thrown in the line
retval = re.Replace(val, Function(match) args(match.Groups(1).Value))
because this is the only place you are keying the dictionary. Make use of the Dictionary.ContainsKey method before accessing it.
retval = re.Replace(val,
Function(match)
return If(args.ContainsKey(match.Groups(1).Value), args(match.Groups(1).Value), val)
End Function)
This is what I got to work vs. the regex, which was also a suggestion on the original thread by Allen Wang: https://stackoverflow.com/a/7957728/1224021
Public Shared Function GetTokenContent(ByVal val As String) As String
Dim retval As String = val
Try
If Not String.IsNullOrEmpty(val) AndAlso val.Contains("$") Then
Dim args = GetRatesDictionary("$")
retval = args.Aggregate(val, Function(current, value) current.Replace(value.Key, value.Value))
End If
Catch ex As Exception
End Try
Return retval
End Function
I know it's been a while since this question was answered, but FYI for anyone wanting to still use the Regex / Dictionary match approach, the following works (based on the sample in the OP question):
retVal = re.Replace(formatString,
match => args.ContainsKey(match.Groups[1].Captures[0].Value)
? args[match.Groups[1].Captures[0].Value]
: string.Empty);
... or my full sample as a string extension method is:
public static class StringExtensions
{
// Will replace parameters enclosed in double curly braces
private static readonly Lazy<Regex> ParameterReplaceRegex = new Lazy<Regex>(() => new Regex(#"\{\{(?<key>\w+)\}\}", RegexOptions.Compiled));
public static string InsertParametersIntoFormatString(this string formatString, string parametersJsonArray)
{
if (parametersJsonArray != null)
{
var deserialisedParamsDictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(parametersJsonArray);
formatString = ParameterReplaceRegex.Value.Replace(formatString,
match => deserialisedParamsDictionary.ContainsKey(match.Groups[1].Captures[0].Value)
? deserialisedParamsDictionary[match.Groups[1].Captures[0].Value]
: string.Empty);
}
return formatString;
}
}
There are a few things to note here:
1) My parameters are passed in as a JSON array, e.g.: {"ProjectCode":"12345","AnotherParam":"Hi there!"}
2) The actual template / format string to do the replacements on has the parameters enclosed in double curly braces: "This is the Project Code: {{ProjectCode}}, this is another param {{AnotherParam}}"
3) Regex is both Lazy initialized and Compiled to suit my particular use case of:
the screen this code serves may not be used often
but once it is, it will get heavy use
so it should be as efficient on subsequent calls as possible.

Regular Expression Matcher

I am using pattern matching to match file extension with my expression String for which code is as follows:-
public static enum FileExtensionPattern
{
WORDDOC_PATTERN( "([^\\s]+(\\.(?i)(txt|docx|doc))$)" ), PDF_PATTERN(
"([^\\s]+(\\.(?i)(pdf))$)" );
private String pattern = null;
FileExtensionPattern( String pattern )
{
this.pattern = pattern;
}
public String getPattern()
{
return pattern;
}
}
pattern = Pattern.compile( FileExtensionPattern.WORDDOC_PATTERN.getPattern() );
matcher = pattern.matcher( fileName );
if ( matcher.matches() )
icon = "blue-document-word.png";
when file name comes as "Home & Artifact.docx" still matcher.matches returns false.It works fine with filename with ".doc" extension.
Can you please point out what i am doing wrong.
"Home & Artifact.docx" contains spaces. Since you allow any char except whitespaces [^\s]+, this filename is not matched.
Try this instead:
(.+?(\.(?i)(txt|docx|doc))$
It is because you have spaces in filename ("Home & Artifact.docx") but your regex has [^\\s]+ which won't allow any spaces.
Use this regex instead for WORDDOC_PATTERN:
"(?i)^.+?\\.(txt|docx|doc)$"

.NET ComponentModel.DataAnnotations issue with RegularExpression attribute

I have to validate a string that's supposed to contain an hour number (e.g. 00 to 23).
I hence set an annotation like:
[RegularExpression("[01]?[0-9]|2[0-3]", ErrorMessage = "Error")]
public string JobStartHour {...}
Unfortunately, this regex doesn't match the inputs from 20 to 23, as it's supposed to do (IMHO).
Doesn't this RegularExpression attribute use the plain old Regex.IsMatch ?
Regex.IsMatch("22", "[01]?[0-9]|2[0-3]")
returns true...
Edit: I know, using a string isn't the best idea so as to store a number, nevertheless, this regex issue is annoying.
This pattern will work. I ran into the same thing. It has to do with using parens to correctly establish the groupings. If the RegExAttribute can't figure it out, it seems to just quit at the pipe symbol.
Here's a unit test.
[TestMethod]
public void CheckHours()
{
var pattern = "([0-1][0-9])|(2[0-3])|([0-9])";
int cnt = 0;
var hours = new string[]
{ "1","2","3","4","5","6","7","8","9",
"01","02","03","04","05","06","07","08","09",
"10","11","12","13","14","15","16","17","18","19",
"20","21","22","23" };
var attribute = new RegularExpressionAttribute(pattern);
bool isMatchOk = false;
bool isAttrOk = false;
foreach (var hour in hours)
{
isMatchOk = System.Text.RegularExpressions.Regex.IsMatch(hour, pattern);
isAttrOk = attribute.IsValid(hour);
if (isMatchOk & isAttrOk)
{ cnt += 1; }
else
{ Debug.WriteLine(hour + " / "
+ isMatchOk.ToString() + " / "
+ isAttrOk.ToString()); }
}
Assert.AreEqual(32, cnt);
}
Try this:
[RegularExpression("2[0-3]|[01]?[0-9]", ErrorMessage = "Error")]
public string JobStartHour {...}
Don't know why this regex isn't correctly interpreted, but a solution is to implement a CustomValidation, which is pretty handy.
[CustomValidation(typeof(MyCustomValidation), "Validate24Hour")]
public string JobStartHour {...}
...
public class MyCustomValidation
{
public static ValidationResult Validate24Hour(string candidate)
{
bool isValid = false;
...
if (isValid)
{
return ValidationResult.Success;
}
else
{
return new ValidationResult("Error");
}
}
}
You have to group the | to work properly.
I successfully tried, which should be exactly your regex but grouped and limited to start & end:
^([01]?[0-9]|2[0-3])$
Your named Regex.IsMatch line returns true on every expression on my machine.