C#: Get first directory name of a relative path - c#

How to get the first directory name in a relative path, given that they can be different accepted directory separators?
For example:
foo\bar\abc.txt -> foo
bar/foo/foobar -> bar

Works with both forward and back slash
static string GetRootFolder(string path)
{
while (true)
{
string temp = Path.GetDirectoryName(path);
if (String.IsNullOrEmpty(temp))
break;
path = temp;
}
return path;
}

Seems like you could just use the string.Split() method on the string, then grab the first element.
example (untested):
string str = "foo\bar\abc.txt";
string str2 = "bar/foo/foobar";
string[] items = str.split(new char[] {'/', '\'}, StringSplitOptions.RemoveEmptyEntries);
Console.WriteLine(items[0]); // prints "foo"
items = str2.split(new char[] {'/', '\'}, StringSplitOptions.RemoveEmptyEntries);
Console.WriteLine(items[0]); // prints "bar"

The most robust solution would be to use DirectoryInfo and FileInfo. On a Windows NT-based system it should accept either forward or backslashes for separators.
using System;
using System.IO;
internal class Program
{
private static void Main(string[] args)
{
Console.WriteLine(GetTopRelativeFolderName(#"foo\bar\abc.txt")); // prints 'foo'
Console.WriteLine(GetTopRelativeFolderName("bar/foo/foobar")); // prints 'bar'
Console.WriteLine(GetTopRelativeFolderName("C:/full/rooted/path")); // ** throws
}
private static string GetTopRelativeFolderName(string relativePath)
{
if (Path.IsPathRooted(relativePath))
{
throw new ArgumentException("Path is not relative.", "relativePath");
}
FileInfo fileInfo = new FileInfo(relativePath);
DirectoryInfo workingDirectoryInfo = new DirectoryInfo(".");
string topRelativeFolderName = string.Empty;
DirectoryInfo current = fileInfo.Directory;
bool found = false;
while (!found)
{
if (current.FullName == workingDirectoryInfo.FullName)
{
found = true;
}
else
{
topRelativeFolderName = current.Name;
current = current.Parent;
}
}
return topRelativeFolderName;
}
}

Based on the answer provided by Hasan Khan ...
private static string GetRootFolder(string path)
{
var root = Path.GetPathRoot(path);
while (true)
{
var temp = Path.GetDirectoryName(path);
if (temp != null && temp.Equals(root))
break;
path = temp;
}
return path;
}
This will give the the top level folder

Based on the question you ask, the following should work:
public string GetTopLevelDir(string filePath)
{
string temp = Path.GetDirectoryName(filePath);
if(temp.Contains("\\"))
{
temp = temp.Substring(0, temp.IndexOf("\\"));
}
else if (temp.Contains("//"))
{
temp = temp.Substring(0, temp.IndexOf("\\"));
}
return temp;
}
When passed foo\bar\abc.txt it will foo as wanted- same for the / case

Here is another example in case your path if following format:
string path = "c:\foo\bar\abc.txt"; // or c:/foo/bar/abc.txt
string root = Path.GetPathRoot(path); // root == c:\

This should work fine
string str = "foo\bar\abc.txt";
string str2 = "bar/foo/foobar";
str.Replace("/", "\\").Split('\\').First(); // foo
str2.Replace("/", "\\").Split('\\').First(); // bar

Here my example, with no memory footprint (without creating new strings in memory):
var slashIndex = relativePath.IndexOf('/');
var backslashIndex = relativePath.IndexOf('\\');
var firstSlashIndex = (slashIndex > 0) ? (slashIndex < backslashIndex ? slashIndex : (backslashIndex == -1) ? slashIndex : backslashIndex) : backslashIndex;
var rootDirectory = relativePath.Substring(0, firstSlashIndex);

Related

Finding game launcher executables in directory C#

I'm trying to find executable files for games; however some are nested (For example Ark: Survival Evolved) (A:\Steam Games\steamapps\common\ARK\ShooterGame\Binaries\Win64\ShooterGame.exe)
I've spent ages trying to find a way to only find the executables which are relevant.
This link shows games when we don't search recursively. It finds most, but not all .exe's
This link shows games searching recursively, but also shows a bunch of binaries/redist exes.
Initially I tried excluding "bin","binary","binaries","redist" folders but that then didn't give me Ark: Survival Evolved for example.
I also considered filtering the .exe's based on their size, but Aperture Tag has a QC_Eyes.exe at 3055KB, but Tomb Raider II.exe is 892KB.
Here's the method I'm using to find the steam installation directory, and check the libraryfolders.vdf file for where the library locations are. For now I'm just writing to console so that I can see what the outputs are.
If anyone has any tips on how I can find the right files for the right games it would be much appreciated. Thanks
public void SearchSteam()
{
steamGameDirs.Clear();
string steam32 = "SOFTWARE\\VALVE\\";
string steam64 = "SOFTWARE\\Wow6432Node\\Valve\\";
string steam32path;
string steam64path;
string config32path;
string config64path;
RegistryKey key32 = Registry.LocalMachine.OpenSubKey(steam32);
RegistryKey key64 = Registry.LocalMachine.OpenSubKey(steam64);
foreach(string k32subKey in key32.GetSubKeyNames())
{
using (RegistryKey subKey = key32.OpenSubKey(k32subKey))
{
steam32path = subKey.GetValue("InstallPath").ToString();
config32path = steam32path + "/steamapps/libraryfolders.vdf";
if (File.Exists(config32path))
{
string[] configLines = File.ReadAllLines(config32path);
foreach(var item in configLines)
{
Console.WriteLine("32: " + item);
}
}
}
}
foreach(string k64subKey in key64.GetSubKeyNames())
{
using (RegistryKey subKey = key64.OpenSubKey(k64subKey))
{
steam64path = subKey.GetValue("InstallPath").ToString();
config64path = steam64path + "/steamapps/libraryfolders.vdf";
string driveRegex = #"[A-Z]:\\";
if (File.Exists(config64path))
{
string[] configLines = File.ReadAllLines(config64path);
foreach (var item in configLines)
{
Console.WriteLine("64: " + item);
Match match = Regex.Match(item, driveRegex);
if(item != string.Empty && match.Success)
{
string matched = match.ToString();
string item2 = item.Substring(item.IndexOf(matched));
item2 = item2.Replace("\\\\", "\\");
steamGameDirs.Add(item2);
}
}
steamGameDirs.Add(steam64path + "\\steamapps\\common\\");
}
}
}
foreach(string item in steamGameDirs)
{
string GameTitle;
string[] Executables = new string[0];
string[] steamGames = Directory.GetDirectories(item);
foreach (var dir in steamGames)
{
string title = dir.Substring(dir.IndexOf("\\common\\"));
string[] titlex = title.Split('\\');
title = titlex[2].ToString();
GameTitle = title;
Console.WriteLine("Title: " + GameTitle);
Console.WriteLine("Directory: " + dir);
string[] executables = Directory.GetFiles(dir, "*.exe", SearchOption.AllDirectories);
int num = 0;
foreach (var ex in executables)
{
//add "ex" to Executables[] if poss
Console.WriteLine(ex);
num++;
}
}
}
}
I've managed to do what I can, so first I detect where steam is installed through the registry, then I check /steamapps/libraryfolders.vdf for where the users libraries are, then check those libraries for any "top level" executables. The program then lets the user select their own exe if one isn't found in the top directory.
Seems like the best solution for now as the steamfiles module isn't currently active.
public void SearchSteam()
{
steamGameDirs.Clear();
string steam32 = "SOFTWARE\\VALVE\\";
string steam64 = "SOFTWARE\\Wow6432Node\\Valve\\";
string steam32path;
string steam64path;
string config32path;
string config64path;
RegistryKey key32 = Registry.LocalMachine.OpenSubKey(steam32);
RegistryKey key64 = Registry.LocalMachine.OpenSubKey(steam64);
if (key64.ToString() == null || key64.ToString() == "")
{
foreach (string k32subKey in key32.GetSubKeyNames())
{
using (RegistryKey subKey = key32.OpenSubKey(k32subKey))
{
steam32path = subKey.GetValue("InstallPath").ToString();
config32path = steam32path + "/steamapps/libraryfolders.vdf";
string driveRegex = #"[A-Z]:\\";
if (File.Exists(config32path))
{
string[] configLines = File.ReadAllLines(config32path);
foreach (var item in configLines)
{
Console.WriteLine("32: " + item);
Match match = Regex.Match(item, driveRegex);
if (item != string.Empty && match.Success)
{
string matched = match.ToString();
string item2 = item.Substring(item.IndexOf(matched));
item2 = item2.Replace("\\\\", "\\");
item2 = item2.Replace("\"", "\\steamapps\\common\\");
steamGameDirs.Add(item2);
}
}
steamGameDirs.Add(steam32path + "\\steamapps\\common\\");
}
}
}
}
foreach(string k64subKey in key64.GetSubKeyNames())
{
using (RegistryKey subKey = key64.OpenSubKey(k64subKey))
{
steam64path = subKey.GetValue("InstallPath").ToString();
config64path = steam64path + "/steamapps/libraryfolders.vdf";
string driveRegex = #"[A-Z]:\\";
if (File.Exists(config64path))
{
string[] configLines = File.ReadAllLines(config64path);
foreach (var item in configLines)
{
Console.WriteLine("64: " + item);
Match match = Regex.Match(item, driveRegex);
if(item != string.Empty && match.Success)
{
string matched = match.ToString();
string item2 = item.Substring(item.IndexOf(matched));
item2 = item2.Replace("\\\\", "\\");
item2 = item2.Replace("\"", "\\steamapps\\common\\");
steamGameDirs.Add(item2);
}
}
steamGameDirs.Add(steam64path + "\\steamapps\\common\\");
}
}
}
}
Attached the code so others can see how I've done it. I know it's not the best but it's working
The keydata seems to be in the appinfo.vdf file. So a somewhat working code to get the correct EXE file for steam games is as follows. Where the game is an EA game with a pointer to an URL or there is no data in the appinfo.vdf a fallback to your previous solution could be used. If anyone got a good parser of the appinfo.vdf the code would be better and not as hacky as the "IndexOf" solution.
GetSteamPath() in the below code can be updated to fetch from registry.
I am going to use that code to fix those petchy steam links that goes back to that anonymous globe-url icon.
(Written in .Net 5.0 - seems IndexOf for .4.7.2 is quirky. doesnt like \x00 I think)
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
namespace SteamAppsParser
{
class Program
{
static void Main(string[] args)
{
var libs = GetSteamLibs();
var apps = GetSteamApps(libs);
}
static List<AppInfo> GetSteamApps(List<string> steamLibs)
{
var apps = new List<AppInfo>();
foreach (var lib in steamLibs)
{
var appMetaDataPath = Path.Combine(lib, "SteamApps");
var files = Directory.GetFiles(appMetaDataPath, "*.acf");
foreach (var file in files)
{
var appInfo = GetAppInfo(file);
if (appInfo != null)
{
apps.Add(appInfo);
}
}
}
return apps;
}
static AppInfo GetAppInfo(string appMetaFile)
{
var fileDataLines = File.ReadAllLines(appMetaFile);
var dic = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var line in fileDataLines)
{
var match = Regex.Match(line, #"\s*""(?<key>\w+)""\s+""(?<val>.*)""");
if (match.Success)
{
var key = match.Groups["key"].Value;
var val = match.Groups["val"].Value;
dic[key] = val;
}
}
AppInfo appInfo = null;
if (dic.Keys.Count > 0)
{
appInfo = new AppInfo();
var appId = dic["appid"];
var name = dic["name"];
var installDir = dic["installDir"];
var path = Path.GetDirectoryName(appMetaFile);
var libGameRoot = Path.Combine(path, "common", installDir);
if (!Directory.Exists(libGameRoot)) return null;
appInfo.Id = appId;
appInfo.Name = name;
appInfo.Manifest = appMetaFile;
appInfo.GameRoot = libGameRoot;
appInfo.InstallDir = installDir;
appInfo.SteamUrl = $"steam://runsteamid/{appId}";
//if (appInfo.Name.StartsWith("Sid Meier"))
appInfo.Executable = GetExecutable(appInfo);
}
return appInfo;
}
static string _appInfoText = null;
static string GetExecutable(AppInfo appInfo)
{
if (_appInfoText == null)
{
var appInfoFile = Path.Combine(GetSteamPath(), "appcache", "appinfo.vdf");
var bytes = File.ReadAllBytes(appInfoFile);
_appInfoText = Encoding.UTF8.GetString(bytes);
}
var startIndex = 0;
int maxTries = 50;
var fullName = "";
do
{
var startOfDataArea = _appInfoText.IndexOf($"\x00\x01name\x00{appInfo.Name}\x00", startIndex);
if (startOfDataArea < 0 && maxTries == 50) startOfDataArea = _appInfoText.IndexOf($"\x00\x01gamedir\x00{appInfo.Name}\x00", startIndex); //Alternative1
if (startOfDataArea < 0 && maxTries == 50) startOfDataArea = _appInfoText.IndexOf($"\x00\x01name\x00{appInfo.Name}\x00", startIndex); //Alternative2
if (startOfDataArea > 0)
{
startIndex = startOfDataArea + 10;
int nextLaunch = -1;
do
{
var executable = _appInfoText.IndexOf($"\x00\x01executable\x00", startOfDataArea);
if (executable>-1 && nextLaunch == -1)
{
nextLaunch = _appInfoText.IndexOf($"\x00\x01launch\x00", executable);
}
if ((nextLaunch <= 0 || executable < nextLaunch) && executable > 0)
{
if (executable > 0)
{
executable += 10;
string filename = "";
while (_appInfoText[executable] != '\x00')
{
filename += _appInfoText[executable];
executable++;
}
if (filename.Contains("://"))
{
//EA or other external
return filename; //Need to use other means to grab the EXE here.
}
fullName = Path.Combine(appInfo.GameRoot, filename);
startOfDataArea = executable + 1;
startIndex = startOfDataArea + 10;
}
}
else
{
break;
}
}
while (!File.Exists(fullName) && maxTries-- > 0);
}
else
{
return null;
}
} while (!File.Exists(fullName) && maxTries-- > 0);
if (File.Exists(fullName)) return fullName;
return null;
}
static List<string> GetSteamLibs()
{
var steamPath = GetSteamPath();
var libraries = new List<string>() { steamPath };
var listFile = Path.Combine(steamPath, #"steamapps\libraryfolders.vdf");
var lines = File.ReadAllLines(listFile);
foreach (var line in lines)
{
var match = Regex.Match(line, #"""(?<path>\w:\\\\.*)""");
if (match.Success)
{
var path = match.Groups["path"].Value.Replace(#"\\", #"\");
if (Directory.Exists(path))
{
libraries.Add(path);
}
}
}
return libraries;
}
static string GetSteamPath()
{
return #"C:\Spill\Steam";
}
class AppInfo
{
public string Id { get; internal set; }
public string Name { get; internal set; }
public string SteamUrl { get; internal set; }
public string Manifest { get; internal set; }
public string GameRoot { get; internal set; }
public string Executable { get; internal set; }
public string InstallDir { get; internal set; }
public override string ToString()
{
return $"{Name} ({Id}) - {SteamUrl} - {Executable}";
}
}
}
}

Replace String in c#

I hava a specific string that I want to replace
string gerneralRootPath = docTab.Rows[0]["URL"].ToString();
string documentName = docTab.Rows[0]["NAME"].ToString();
var connectNamesAndURL = new StringBuilder(gerneralRootPath);
connectNamesAndURL.Remove(30,20);
connectNamesAndURL.Insert(30, documentName);
gerneralRootPath = connectNamesAndURL.ToString();
The output of gerneralRootPath is
"Documents/Z_Documentation/PDF/sales.2010+Implementation+Revised+Feb10.pdf"
The output of is documentName is
"doc123"
My gole is to remove everything after /PDF/.. so that final string looks like this
Documents/Z_Documentation/PDF/doc123
So how can I remove everything after the /PDF/..
Try this
string gerneralRootPath = "Documents/Z_Documentation/PDF/sales.2010+Implementation+Revised+Feb10.pdf";
gerneralRootPath = gerneralRootPath.Remove(gerneralRootPath.IndexOf("PDF") + 3);
gerneralRootPath = gerneralRootPath +"/"+documentName ;
You can achieve this using String.Split() function:
string input = "Documents/Z_Documentation/PDF/sales.2010+Implementation+Revised+Feb10.pdf";
string output = input.Split(new string[] { "/PDF/" }, StringSplitOptions.None).First() + "/PDF/doc123";
using System.IO;
string result = gerneralRootPath.Replace(Path.GetFileName(gerneralRootPath), documentName);
With Path.GetFileName (from System.IO) you get your filename:
sales.2010+Implementation+Revised+Feb10.pdf
The result is:
Documents/Z_Documentation/PDF/doc123
please find the sample code
int i = gerneralRootPath.IndexOf("/PDF/");
if (i >= 0) gerneralRootPath = gerneralRootPath.Substring(0,i+5);
i hope this will help you....
This is an example:
class Program
{
static string RemoveAfterPDF(string gerneralRootPath)
{
string pdf = "PDF";
int index = gerneralRootPath.IndexOf(pdf);
return gerneralRootPath.Substring(0, index + pdf.Length);
}
public static void Main()
{
string test = RemoveAfterPDF("Documents/Z_Documentation/PDF/sales.2010+Implementation+Revised+Feb10.pdf");
}
}
Edit
This is better and much more reusable example:
class Program
{
static string RemoveAfter(string gerneralRootPath, string removeAfter)
{
string result = string.Empty;
int index = gerneralRootPath.IndexOf(removeAfter);
if (index > 0)
result = gerneralRootPath.Substring(0, index + removeAfter.Length);
return result;
}
public static void Main()
{
string test = RemoveAfterPDF("Documents/Z_Documentation/PDF/sales.2010+Implementation+Revised+Feb10.pdf", "PDF");
}
}

Split special string in c#

I want to split the below string with given output.
Can anybody help me to do this.
Examples:
/TEST/TEST123
Output: /Test/
/TEST1/Test/Test/Test/
Output: /Test1/
/Text/12121/1212/
Output: /Text/
/121212121/asdfasdf/
Output: /121212121/
12345
Output: 12345
I have tried string.split function but it is not worked well. Is there any idea or logic that i can implement to achieve this situation.
If the answer in regular expression that would be fine for me.
You simply want the first result of Spiting by /
string output = input.Split('/')[0];
But in case that you have //TEST/ and output should be /TEST you can use regex.
string output = Regex.Matches(input, #"\/?(.+?)\/")[0].Groups[1].Value;
For your 5th case : you have to separate the logic. for example:
public static string Method(string input)
{
var split = input.Split(new[] {'/'}, StringSplitOptions.RemoveEmptyEntries);
if (split.Length == 0) return input;
return split[0];
}
Or using regex.
public static string Method(string input)
{
var matches = Regex.Matches(input, #"\/?(.+?)\/");
if (matches.Count == 0) return input;
return matches[0].Groups[1].Value;
}
Some results using method:
TEST/54/ => TEST
TEST => TEST
/TEST/ => TEST
I think this would work:
string s1 = "/TEST/TEST123";
string s2 = "/TEST1/Test/Test/Test/";
string s3 = "/Text/12121/1212/";
string s4 = "/121212121/asdfasdf/";
string s5 = "12345";
string pattern = #"\/?[a-zA-Z0-9]+\/?";
Console.WriteLine(Regex.Matches(s1, pattern)[0]);
Console.WriteLine(Regex.Matches(s2, pattern)[0]);
Console.WriteLine(Regex.Matches(s3, pattern)[0]);
Console.WriteLine(Regex.Matches(s4, pattern)[0]);
Console.WriteLine(Regex.Matches(s5, pattern)[0]);
class Program
{
static void Main(string[] args)
{
string example = "/TEST/TEST123";
var result = GetFirstItem(example);
Console.WriteLine("First in the list : {0}", result);
}
static string GetFirstItem(string value)
{
var collection = value?.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
var result = collection[0];
return result;
}
}
StringSplitOptions.RemoveEmptyEntries is an enum which tells the Split function that when it has split the string into an array, if there are elements in the array that are empty strings, the function should not include the empty elements in the results. Basically you want the collection to contain only values.
public string functionName(string input)
{
if(input.Contains('/'))
{
string SplitStr = input.Split('/')[1];
return "/"+SplitStr .Substring(0, 1) +SplitStr.Substring(1).ToLower()+"/"
}
return input;
}
output = (output.Contains("/"))? '/' +input.Split('/')[1]+'/':input;
private void button1_Click(object sender, EventArgs e)
{
string test = #"/Text/12121/1212/";
int first = test.IndexOf("/");
int last = test.Substring(first+1).IndexOf("/");
string finall = test.Substring(first, last+2);
}
i try this code with all your examples and get correct output. try this.
The following method may help you.
public string getValue(string st)
{
if (st.IndexOf('/') == -1)
return st;
return "/" + st.Split('/')[1] + "/";
}

how to find file's path according to filename?

i have filename according to this file name. i need to find file's path.
how to find file's path according to filename? i need to find file's path according to file name sample code:
string path= System.IO.Directory.GetDirectories(#"c:\", "kategori",System.IO.SearchOption.AllDirectories).First();
But i need :
string path= System.IO.Directory.GetDirectories(#"anyFolder", "kategori",System.IO.SearchOption.AllDirectories).First();
i need below
Main()
{
string path = PathFinder("Afilename")
}
output: C:\myFiles\AfileName
string PathFinder(string fileName)
{
..................
.................
........
.......
....
..
.
}
Probably a function like this could work for you:
public static String SearchFileRecursive(String baseFolderPath, String fileName)
{
// Returns, if found, the full path of the file; otherwise returns null.
var response = Path.Combine(baseFolderPath, fileName);
if (File.Exists(response))
{
return response;
}
// Recursion.
var directories = Directory.GetDirectories(baseFolderPath);
for (var i = 0; i < directories.Length; i++)
{
response = SearchFileRecursive(directories[i], fileName);
if (response != null) return response;
}
// { file was not found }
return null;
}
http://support.microsoft.com/kb/303974
I'd like more the LINQish way:
public static IEnumerable<FileInfo> FindFile(string fileName)
{
if (String.IsNullOrEmpty(fileName))
throw new ArgumentException("fileName");
return Directory.GetLogicalDrives()
.SelectMany(drive => FindFile(fileName, drive));
}
public static IEnumerable<FileInfo> FindFile(string fileName, string folderName)
{
if (String.IsNullOrEmpty(fileName))
throw new ArgumentException("fileName");
if (String.IsNullOrEmpty(fileName))
throw new ArgumentException("folderName");
var matchingFiles = Directory.EnumerateFiles(folderName)
.Where(file => Path.GetFileName(file) == fileName)
.Select(file => new FileInfo(file));
var matchingFilesFromSubDirs = Directory.EnumerateDirectories(folderName)
.SelectMany(directory => FindFile(fileName, directory));
return matchingFiles.Concat(matchingFilesFromSubDirs);
}
which can be used by:
foreach (var file in FindFile("myFile.ext"))
{
Console.WriteLine("Name: " + file.FullName);
}

C# Sanitize File Name

I recently have been moving a bunch of MP3s from various locations into a repository. I had been constructing the new file names using the ID3 tags (thanks, TagLib-Sharp!), and I noticed that I was getting a System.NotSupportedException:
"The given path's format is not supported."
This was generated by either File.Copy() or Directory.CreateDirectory().
It didn't take long to realize that my file names needed to be sanitized. So I did the obvious thing:
public static string SanitizePath_(string path, char replaceChar)
{
string dir = Path.GetDirectoryName(path);
foreach (char c in Path.GetInvalidPathChars())
dir = dir.Replace(c, replaceChar);
string name = Path.GetFileName(path);
foreach (char c in Path.GetInvalidFileNameChars())
name = name.Replace(c, replaceChar);
return dir + name;
}
To my surprise, I continued to get exceptions. It turned out that ':' is not in the set of Path.GetInvalidPathChars(), because it is valid in a path root. I suppose that makes sense - but this has to be a pretty common problem. Does anyone have some short code that sanitizes a path? The most thorough I've come up with this, but it feels like it is probably overkill.
// replaces invalid characters with replaceChar
public static string SanitizePath(string path, char replaceChar)
{
// construct a list of characters that can't show up in filenames.
// need to do this because ":" is not in InvalidPathChars
if (_BadChars == null)
{
_BadChars = new List<char>(Path.GetInvalidFileNameChars());
_BadChars.AddRange(Path.GetInvalidPathChars());
_BadChars = Utility.GetUnique<char>(_BadChars);
}
// remove root
string root = Path.GetPathRoot(path);
path = path.Remove(0, root.Length);
// split on the directory separator character. Need to do this
// because the separator is not valid in a filename.
List<string> parts = new List<string>(path.Split(new char[]{Path.DirectorySeparatorChar}));
// check each part to make sure it is valid.
for (int i = 0; i < parts.Count; i++)
{
string part = parts[i];
foreach (char c in _BadChars)
{
part = part.Replace(c, replaceChar);
}
parts[i] = part;
}
return root + Utility.Join(parts, Path.DirectorySeparatorChar.ToString());
}
Any improvements to make this function faster and less baroque would be much appreciated.
To clean up a file name you could do this
private static string MakeValidFileName( string name )
{
string invalidChars = System.Text.RegularExpressions.Regex.Escape( new string( System.IO.Path.GetInvalidFileNameChars() ) );
string invalidRegStr = string.Format( #"([{0}]*\.+$)|([{0}]+)", invalidChars );
return System.Text.RegularExpressions.Regex.Replace( name, invalidRegStr, "_" );
}
A shorter solution:
var invalids = System.IO.Path.GetInvalidFileNameChars();
var newName = String.Join("_", origFileName.Split(invalids, StringSplitOptions.RemoveEmptyEntries) ).TrimEnd('.');
Based on Andre's excellent answer but taking into account Spud's comment on reserved words, I made this version:
/// <summary>
/// Strip illegal chars and reserved words from a candidate filename (should not include the directory path)
/// </summary>
/// <remarks>
/// http://stackoverflow.com/questions/309485/c-sharp-sanitize-file-name
/// </remarks>
public static string CoerceValidFileName(string filename)
{
var invalidChars = Regex.Escape(new string(Path.GetInvalidFileNameChars()));
var invalidReStr = string.Format(#"[{0}]+", invalidChars);
var reservedWords = new []
{
"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4",
"COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4",
"LPT5", "LPT6", "LPT7", "LPT8", "LPT9"
};
var sanitisedNamePart = Regex.Replace(filename, invalidReStr, "_");
foreach (var reservedWord in reservedWords)
{
var reservedWordPattern = string.Format("^{0}\\.", reservedWord);
sanitisedNamePart = Regex.Replace(sanitisedNamePart, reservedWordPattern, "_reservedWord_.", RegexOptions.IgnoreCase);
}
return sanitisedNamePart;
}
And these are my unit tests
[Test]
public void CoerceValidFileName_SimpleValid()
{
var filename = #"thisIsValid.txt";
var result = PathHelper.CoerceValidFileName(filename);
Assert.AreEqual(filename, result);
}
[Test]
public void CoerceValidFileName_SimpleInvalid()
{
var filename = #"thisIsNotValid\3\\_3.txt";
var result = PathHelper.CoerceValidFileName(filename);
Assert.AreEqual("thisIsNotValid_3__3.txt", result);
}
[Test]
public void CoerceValidFileName_InvalidExtension()
{
var filename = #"thisIsNotValid.t\xt";
var result = PathHelper.CoerceValidFileName(filename);
Assert.AreEqual("thisIsNotValid.t_xt", result);
}
[Test]
public void CoerceValidFileName_KeywordInvalid()
{
var filename = "aUx.txt";
var result = PathHelper.CoerceValidFileName(filename);
Assert.AreEqual("_reservedWord_.txt", result);
}
[Test]
public void CoerceValidFileName_KeywordValid()
{
var filename = "auxillary.txt";
var result = PathHelper.CoerceValidFileName(filename);
Assert.AreEqual("auxillary.txt", result);
}
string clean = String.Concat(dirty.Split(Path.GetInvalidFileNameChars()));
there are a lot of working solutions here. just for the sake of completeness, here's an approach that doesn't use regex, but uses LINQ:
var invalids = Path.GetInvalidFileNameChars();
filename = invalids.Aggregate(filename, (current, c) => current.Replace(c, '_'));
Also, it's a very short solution ;)
I'm using the System.IO.Path.GetInvalidFileNameChars() method to check invalid characters and I've got no problems.
I'm using the following code:
foreach( char invalidchar in System.IO.Path.GetInvalidFileNameChars())
{
filename = filename.Replace(invalidchar, '_');
}
I wanted to retain the characters in some way, not just simply replace the character with an underscore.
One way I thought was to replace the characters with similar looking characters which are (in my situation), unlikely to be used as regular characters. So I took the list of invalid characters and found look-a-likes.
The following are functions to encode and decode with the look-a-likes.
This code does not include a complete listing for all System.IO.Path.GetInvalidFileNameChars() characters. So it is up to you to extend or utilize the underscore replacement for any remaining characters.
private static Dictionary<string, string> EncodeMapping()
{
//-- Following characters are invalid for windows file and folder names.
//-- \/:*?"<>|
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.Add(#"\", "Ì"); // U+OOCC
dic.Add("/", "Í"); // U+OOCD
dic.Add(":", "¦"); // U+00A6
dic.Add("*", "¤"); // U+00A4
dic.Add("?", "¿"); // U+00BF
dic.Add(#"""", "ˮ"); // U+02EE
dic.Add("<", "«"); // U+00AB
dic.Add(">", "»"); // U+00BB
dic.Add("|", "│"); // U+2502
return dic;
}
public static string Escape(string name)
{
foreach (KeyValuePair<string, string> replace in EncodeMapping())
{
name = name.Replace(replace.Key, replace.Value);
}
//-- handle dot at the end
if (name.EndsWith(".")) name = name.CropRight(1) + "°";
return name;
}
public static string UnEscape(string name)
{
foreach (KeyValuePair<string, string> replace in EncodeMapping())
{
name = name.Replace(replace.Value, replace.Key);
}
//-- handle dot at the end
if (name.EndsWith("°")) name = name.CropRight(1) + ".";
return name;
}
You can select your own look-a-likes. I used the Character Map app in windows to select mine %windir%\system32\charmap.exe
As I make adjustments through discovery, I will update this code.
I think the problem is that you first call Path.GetDirectoryName on the bad string. If this has non-filename characters in it, .Net can't tell which parts of the string are directories and throws. You have to do string comparisons.
Assuming it's only the filename that is bad, not the entire path, try this:
public static string SanitizePath(string path, char replaceChar)
{
int filenamePos = path.LastIndexOf(Path.DirectorySeparatorChar) + 1;
var sb = new System.Text.StringBuilder();
sb.Append(path.Substring(0, filenamePos));
for (int i = filenamePos; i < path.Length; i++)
{
char filenameChar = path[i];
foreach (char c in Path.GetInvalidFileNameChars())
if (filenameChar.Equals(c))
{
filenameChar = replaceChar;
break;
}
sb.Append(filenameChar);
}
return sb.ToString();
}
I have had success with this in the past.
Nice, short and static :-)
public static string returnSafeString(string s)
{
foreach (char character in Path.GetInvalidFileNameChars())
{
s = s.Replace(character.ToString(),string.Empty);
}
foreach (char character in Path.GetInvalidPathChars())
{
s = s.Replace(character.ToString(), string.Empty);
}
return (s);
}
Here's an efficient lazy loading extension method based on Andre's code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LT
{
public static class Utility
{
static string invalidRegStr;
public static string MakeValidFileName(this string name)
{
if (invalidRegStr == null)
{
var invalidChars = System.Text.RegularExpressions.Regex.Escape(new string(System.IO.Path.GetInvalidFileNameChars()));
invalidRegStr = string.Format(#"([{0}]*\.+$)|([{0}]+)", invalidChars);
}
return System.Text.RegularExpressions.Regex.Replace(name, invalidRegStr, "_");
}
}
}
Your code would be cleaner if you appended the directory and filename together and sanitized that rather than sanitizing them independently. As for sanitizing away the :, just take the 2nd character in the string. If it is equal to "replacechar", replace it with a colon. Since this app is for your own use, such a solution should be perfectly sufficient.
using System;
using System.IO;
using System.Linq;
using System.Text;
public class Program
{
public static void Main()
{
try
{
var badString = "ABC\\DEF/GHI<JKL>MNO:PQR\"STU\tVWX|YZA*BCD?EFG";
Console.WriteLine(badString);
Console.WriteLine(SanitizeFileName(badString, '.'));
Console.WriteLine(SanitizeFileName(badString));
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
private static string SanitizeFileName(string fileName, char? replacement = null)
{
if (fileName == null) { return null; }
if (fileName.Length == 0) { return ""; }
var sb = new StringBuilder();
var badChars = Path.GetInvalidFileNameChars().ToList();
foreach (var #char in fileName)
{
if (badChars.Contains(#char))
{
if (replacement.HasValue)
{
sb.Append(replacement.Value);
}
continue;
}
sb.Append(#char);
}
return sb.ToString();
}
}
Based #fiat's and #Andre's approach, I'd like to share my solution too.
Main difference:
its an extension method
regex is compiled at first use to save some time with a lot executions
reserved words are preserved
public static class StringPathExtensions
{
private static Regex _invalidPathPartsRegex;
static StringPathExtensions()
{
var invalidReg = System.Text.RegularExpressions.Regex.Escape(new string(Path.GetInvalidFileNameChars()));
_invalidPathPartsRegex = new Regex($"(?<reserved>^(CON|PRN|AUX|CLOCK\\$|NUL|COM0|COM1|COM2|COM3|COM4|COM5|COM6|COM7|COM8|COM9|LPT0|LPT1|LPT2|LPT3|LPT4|LPT5|LPT6|LPT7|LPT8|LPT9))|(?<invalid>[{invalidReg}:]+|\\.$)", RegexOptions.Compiled);
}
public static string SanitizeFileName(this string path)
{
return _invalidPathPartsRegex.Replace(path, m =>
{
if (!string.IsNullOrWhiteSpace(m.Groups["reserved"].Value))
return string.Concat("_", m.Groups["reserved"].Value);
return "_";
});
}
}

Categories

Resources