Have you ever needed to mask files being pulled from an FTP site? Many compontents pull down and delete all files from a folder. That may not be what you want. This article shows you how to implement sophisticated file masking in the FTP component to pull only the types and or names you want to retrieve.
I have a requirement to be able to selectively pull files from an FTP Folder. Some of the clienst with which I am dealing insist on mixing file types and names in the same folder although the files and types go to different applications. In a simple FTP component, the Download method usually pulls and alternately delets all of the files found in the folder. If you delete the files pulled down from the FTP folder and then find that the files do not all belong to the application for which you are pulling files, you have to jump through hoops to put the unwanted files back on the FTP folder. At the least you will have to separate the download and delete logic and put some filemasking in the middle.
This article will demonstrate the code for a complete filmasking methodology. You can either implement the code inside of your FTP component, assuming you have the code for it. Optionally, you can call it between the time that you download files and the time that you delete the files via the FTP component. The point is that as in my case I need to be able to mask both the filename and the extension. This article will give you the code to do it in both VB.NET and C#.
Let me say up front that the code you see is much more compact and straight forward than it was when I first started. If you think about the problem, it is not trivial and if you try to write it in one method, you will soon have code that is very hard to understand and debeg and ensure that you have covered all cases. As you may know if you have read any of my many articles on Refactoring, I am big on it.
In addition to studying and writing about Refactoring, you probably know that our KnowDotNet team has written NetRefactor which is a comprehensive refactoring tool for both C# and VB.NET. As a result of these activities, I have developed a refactoring mindset as I code new applications. I rarely write a method any more that does more than one major thing, unless it is a "driver" method that call many "worker" methods that accomplish what I used to do in one method. That type of design is readily apparent in the code shown in this article.
The goal in this code is to allow any combination of starting or ending wildcard characters in both the filename and the extension. That means that the code should be able to handle a filename and extension mask combination that is represented by the following expression.
"*|[*]filenamepart[*].*|[*]extpart[*]"
Figure 1 will show the VB.NET code for the main file masking method. The helper methods will be shown after it.
Figure 1 - VB.NET File Masking.
| Private Enum wcPos none wrap start [end] End Enum ''' <summary> ''' Filemask can be "*|[*]filenamepart[*].*|[*]extpart[*]" ''' </summary> ''' <param name="fileName" ></param> ''' <param name="fileMask" ></param> ''' <returns>Boolean</returns> ''' <remarks> ''' Handles positional start and/or ending wildcards. ''' </remarks> Private Function FileMatchesMask(ByVal fileName As String, _ ByVal fileMask As String) As Boolean Const asterisk As String = "*" Dim sE As String = String.Empty If fileMask.Equals("*.*") Then Return True ' first, get comparable filenames and extensions Dim fn As String = Path.GetFileNameWithoutExtension(fileName).ToLower Dim ext As String = Path.GetExtension(fileName).Replace(".", sE).ToLower Dim maskName As String = Path.GetFileNameWithoutExtension(fileMask).ToLower Dim maskExt As String = Path.GetExtension(fileMask).ToLower.Replace(".", sE) Dim maskExtStripped As String = maskExt.ToLower.Replace(asterisk, sE) Dim maskNameStripped As String = maskName.ToLower.Replace(asterisk, sE) Dim fnWCP As wcPos = GetWildCardPosition(maskName) Dim extWCP As wcPos = GetWildCardPosition(maskExt) If maskExt.Equals(asterisk) Then ' ext can be anything, but fn may be masked If maskName.IndexOf(asterisk) > -1 Then ' fn masked, ck position of wildcard Select Case fnWCP Case wcPos.wrap : Return IsValueMatch(fn, maskNameStripped, wcPos.wrap) Case wcPos.start : Return IsValueMatch(fn, maskNameStripped, wcPos.start) Case wcPos.end : Return IsValueMatch(fn, maskNameStripped, wcPos.end) Case Else : Return IsValueMatch(fn, maskNameStripped, wcPos.wrap) End Select Else ' need exact match on filename, any ext is ok If maskName.Equals(fn) Then Return True End If End If ElseIf maskName.Equals(asterisk) Then ' fn can be anything, but ext may be masked If maskExt.IndexOf(asterisk) > -1 Then ' ext masked, ck position of wildcard Select Case extWCP Case wcPos.wrap : Return IsValueMatch(ext, maskExtStripped, wcPos.wrap) Case wcPos.start : Return IsValueMatch(ext, maskExtStripped, wcPos.start) Case wcPos.end : Return IsValueMatch(ext, maskExtStripped, wcPos.end) Case Else : Return IsValueMatch(ext, maskExtStripped, wcPos.wrap) End Select Else ' need exact match on ext, any filename is ok If maskExt.Equals(ext) Then Return True End If End If Else ' here neither fn or ext = asterisk ' so must ck for existence of asterisk in either ' b/c of position of * this will get hairy... If extWCP <> wcPos.none Then ' ext is masked, ck for masking in fn If maskName.IndexOf(asterisk) > -1 Then ' ext and fn are masked Return IsValueMatch(fn, maskNameStripped, fnWCP) And _ IsValueMatch(ext, maskExtStripped, extWCP) Else ' fn not masked, look for fn exact match and ext masked If fn.Equals(maskName) AndAlso _ IsValueMatch(ext, maskExtStripped, extWCP) Then Return True End If End If Else ' ext is not masked, requires exact ext and ck for fn masking If maskName.IndexOf(asterisk) > -1 Then ' fn masked If IsValueMatch(fn, maskNameStripped, fnWCP) AndAlso _ ext.Equals(maskExtStripped) Then Return True ElseIf fn.Equals(maskName) AndAlso _ ext.Equals(maskExtStripped) Then Return True End If End If End If End If Return False End Function |
| ''' <summary> ''' Returns position of existing wildcard[s]. ''' start, wrap, end, none ''' </summary> ''' <param name = "mask"></param> ''' <returns>wcPos</returns> Private Function GetWildCardPosition(ByVal mask As String) As wcPos Dim wildStart As String = "^\*\w+" Dim wildEnd As String = "^[a-z0-9_ ]*\*" Dim wildWrap As String = "^\*[a-z0-9_ ]*\*" If mask.Equals("*") Then Return wcPos.none ElseIf Regex.IsMatch(mask, wildWrap) Then Return wcPos.wrap ElseIf Regex.IsMatch(mask, wildStart) Then Return wcPos.start ElseIf Regex.IsMatch(mask, wildEnd) Then Return wcPos.end Else Return wcPos.none End If End Function ''' <summary> ''' Returns true if mask matches value, based on position specifier. ''' </summary> ''' <param name = "value"></param> ''' <param name = "mask"></param> ''' <returns>Boolean</returns> Private Function IsValueMatch(ByVal value As String, ByVal mask As String, ByVal position As wcPos) As Boolean Select Case position Case wcPos.wrap : If value.IndexOf(mask) > -1 Then Return True Case wcPos.end : If value.StartsWith(mask) Then Return True Case wcPos.start : If value.EndsWith(mask) Then Return True End Select End Function |
| Console.WriteLine(FileMatchesMask("filename.txt", "*.*").ToString) ' true Console.WriteLine(FileMatchesMask("filename.txt", "*.txt").ToString) ' true Console.WriteLine(FileMatchesMask("filename.text", "*.*ex*").ToString) ' true Console.WriteLine(FileMatchesMask("filename.exe", "*.ex*").ToString) ' true Console.WriteLine(FileMatchesMask("filename.exe", "*.*xe").ToString) ' true Console.WriteLine(FileMatchesMask("filename.txt", "filename.*").ToString) ' true Console.WriteLine(FileMatchesMask("filename.txt", "*ilenam*.*").ToString) ' true Console.WriteLine(FileMatchesMask("FILENAME_90.txt", "filename*.*").ToString) ' true Console.WriteLine(FileMatchesMask("90_FILENAME.txt", "*filename*.*").ToString) ' true Console.WriteLine(FileMatchesMask("90_FILENAME.txt", "filename.*").ToString) ' false Console.WriteLine(FileMatchesMask("filename.txt", "*.tx").ToString) ' false Console.WriteLine(FileMatchesMask("filename.text", "*.ex*").ToString) 'false Console.WriteLine(FileMatchesMask("filename.exe", "*.*xe").ToString) 'true Console.WriteLine(FileMatchesMask("filename.exe", "*.xe*").ToString) 'false Console.WriteLine(FileMatchesMask("filename.txt", "filenam.*").ToString) 'false Console.WriteLine(FileMatchesMask("filename.txt", "*ilnam*.*").ToString) ' true Console.WriteLine(FileMatchesMask("FILENAME_90.txt", "filename.*").ToString) ' true |
| private enum wcPos: int { none, wrap, start, @end } /// <summary> /// Filemask can be "*|[*]filenamepart[*].*|[*]extpart[*]" /// </summary> /// <param name="fileName" ></param> /// <param name="fileMask" ></param> /// <returns>Boolean</returns> /// <remarks> /// Handles positional start and/or ending wildcards. /// </remarks> private bool FileMatchesMask(string fileName, string fileMask) { const string asterisk = "*"; string se = string.Empty; if (fileMask.Equals("*.*")) return true; // first, get comparable filenames and extensions string fn = Path.GetFileNameWithoutExtension(fileName).ToLower(); string ext = Path.GetExtension(fileName).Replace(".", se).ToLower(); string maskName = Path.GetFileNameWithoutExtension(fileMask).ToLower(); string maskExt = Path.GetExtension(fileMask).ToLower().Replace(".", se); string maskExtStripped = maskExt.ToLower().Replace(asterisk, se); string maskNameStripped = maskName.ToLower().Replace(asterisk, se); wcPos fnWCP = GetWildCardPosition(maskName); wcPos extWCP = GetWildCardPosition(maskExt); if (maskExt.Equals(asterisk)) { // ext can be anything, but fn may be masked if (maskName.IndexOf(asterisk) > -1) { // fn masked, ck position of wildcard switch (fnWCP) { case wcPos.wrap: return IsValueMatch(fn, maskNameStripped, wcPos.wrap); case wcPos.start: return IsValueMatch(fn, maskNameStripped, wcPos.start); case wcPos.@end: return IsValueMatch(fn, maskNameStripped, wcPos.@end); default: return IsValueMatch(fn, maskNameStripped, wcPos.wrap); } } else { // need exact match on filename, any ext is ok if (maskName.Equals(fn)) return true; } } else if (maskName.Equals(asterisk)) { // fn can be anything, but ext may be masked if (maskExt.IndexOf(asterisk) > -1) { // ext masked, ck position of wildcard switch (extWCP) { case wcPos.wrap: return IsValueMatch(ext, maskExtStripped, wcPos.wrap); case wcPos.start: return IsValueMatch(ext, maskExtStripped, wcPos.start); case wcPos.@end: return IsValueMatch(ext, maskExtStripped, wcPos.@end); default: return IsValueMatch(ext, maskExtStripped, wcPos.wrap); } } else { // need exact match on ext, any filename is ok if (maskExt.Equals(ext)) return true; } } else { // here neither fn or ext = asterisk so must ck for // existence of asterisk in either // b/c of position of * this will get hairy... if (extWCP != wcPos.none) { // ext is masked, ck for masking in fn if (maskName.IndexOf(asterisk) > -1) { // ext and fn are masked return IsValueMatch(fn, maskNameStripped, fnWCP) & IsValueMatch(ext, maskExtStripped, extWCP); } else { // fn not masked, look for fn exact match and // ext masked if (fn.Equals(maskName) && IsValueMatch(ext, maskExtStripped, extWCP)) return true; } } else { // ext is not masked, requires exact ext // and ck for fn masking if (maskName.IndexOf(asterisk) > -1) { // fn masked if (IsValueMatch(fn, maskNameStripped, fnWCP) && ext.Equals(maskExtStripped)) return true; else if (fn.Equals(maskName) && ext.Equals(maskExtStripped)) return true; } } } return false; } /// <summary> /// Returns position of existing wildcard[s]. /// start, wrap, end, none /// </summary> /// <param name = "mask"></param> /// <returns>wcPos</returns> private wcPos GetWildCardPosition(string mask) { string wildStart = "^\\*\\w+"; string wildEnd = "^[a-z0-9_ ]*\\*"; string wildWrap = "^\\*[a-z0-9_ ]*\\*"; if (mask.Equals("*")) return wcPos.none; else if (Regex.IsMatch(mask, wildWrap)) return wcPos.wrap; else if (Regex.IsMatch(mask, wildStart)) return wcPos.start; else if (Regex.IsMatch(mask, wildEnd)) return wcPos.@end; else return wcPos.none; } /// <summary> /// Returns true if mask matches value, based on position specifier. /// </summary> /// <param name = "value"></param> /// <param name = "mask"></param> /// <returns>Boolean</returns> private bool IsValueMatch(string valu, string mask, wcPos position) { switch (position) { case wcPos.wrap: if (valu.IndexOf(mask) > -1) return true; break; case wcPos.@end: if (valu.StartsWith(mask)) return true; break; case wcPos.start: if (valu.EndsWith(mask)) return true; break; } return false; } |
| Ask a Question, or give your feedback on my articles or products by going to the KnowDotNet Forum or by clicking on My Blog. | ![]() |