简体   繁体   中英

Normalize directory names in C#

Here's the problem, I have a bunch of directories like

S:\\HELLO\\HI
S:\\HELLO2\\HI\\HElloAgain

On the file system it shows these directories as

S:\\hello\\Hi
S:\\hello2\\Hi\\helloAgain

Is there any function in C# that will give me what the file system name of a directory is with the proper casing?

string FileSystemCasing = new System.IO.DirectoryInfo("H:\\...").FullName;

EDIT:

As iceman pointed out, the FullName returns the correct casing only if the DirectoryInfo (or in general the FileSystemInfo) comes from a call to the GetDirectories (or GetFileSystemInfos) method.

Now I'm posting a tested and performance-optimized solution. It work well both on directory and file paths, and has some fault tolerance on input string. It's optimized for "conversion" of single paths (not the entire file system), and faster than getting the entire file system tree. Of course, if you have to renormalize the entire file system tree, you may prefer iceman's solution, but i tested on 10000 iterations on paths with medium level of deepness, and it takes just few seconds ;)

    private string GetFileSystemCasing(string path)
    {
        if (Path.IsPathRooted(path))
        {
            path = path.TrimEnd(Path.DirectorySeparatorChar); // if you type c:\foo\ instead of c:\foo
            try
            {
                string name = Path.GetFileName(path);
                if (name == "") return path.ToUpper() + Path.DirectorySeparatorChar; // root reached

                string parent = Path.GetDirectoryName(path); // retrieving parent of element to be corrected

                parent = GetFileSystemCasing(parent); //to get correct casing on the entire string, and not only on the last element

                DirectoryInfo diParent = new DirectoryInfo(parent);
                FileSystemInfo[] fsiChildren = diParent.GetFileSystemInfos(name);
                FileSystemInfo fsiChild = fsiChildren.First();
                return fsiChild.FullName; // coming from GetFileSystemImfos() this has the correct case
            }
            catch (Exception ex) { Trace.TraceError(ex.Message); throw new ArgumentException("Invalid path"); }
            return "";
        }
        else throw new ArgumentException("Absolute path needed, not relative");
    }

Here's a basic and relatively fast solution, keep reading below for some commentary:

private static string GetCase(string path)
{      
  DirectoryInfo dir = new DirectoryInfo(path);
  if (dir.Exists)
  {
    string[] folders = dir.FullName.Split(Path.DirectorySeparatorChar);
    dir = dir.Root;

    foreach (var f in folders.Skip(1))
    {          
      dir = dir.GetDirectories(f).First();
    }

    return dir.FullName;
  }
  else
  {
    return path;
  }
}

The basic idea is that getting subdirectories from a DirectoryInfo object will get you the correct case, so we just need to split the directory name and walk from the root to the target directory, getting the proper case at each step.

My initial answer relied on getting the casing for every folder on the drive, and it worked but was slow. I came up with a slight improvement that stored the results, but it was still too slow for everyday usage. You can see the edit history for this comment if you need to do this for every thing on the drive, and even then there are probably ways to speed up that code. It was "here's how you might do it" and not "here's a great way to do it."

Bertu, in his answer, came up with the idea of splitting the path into its components and getting the casing piece by piece, which results in a huge speed increase since you're no longer checking everything as in my original answer. Bertu also generalized his solution to do files as well as directories. In my tests, the code posted above (which uses Bertu's "split the path and do it by pieces" idea but approaches it iteratively instead of recursively) runs in about half the time of Bertu's code. I'm not sure if that's because his method also handles files, because his use of recursion introduces extra overhead, or because he ends up calling Path.GetFileName(path) and Path.GetDirectoryName(path) in each iteration. Depending on your exact needs, some combination of his answer and mine will likely solve your problem as well as is possible in C#.

On that note, I should mention that there are some limitations to .Net filename handling, and since doing this in .Net requires making a lot of DirectoryInfo objects, you might want to consider unmanaged code if this is your bottleneck.

My version (similar to BertuPG 's and Kevin 's using GetDirectories() and GetFileSystemInfos() but allowing for non-existent paths and files and working as an extension method):

/// <summary>
/// Gets the full path with all the capitalization properly done as it exists in the file system.
/// Non-existent paths returned as passed in.
/// </summary>
/// <param name="path">
/// The path to make full and make properly cased.
/// </param>
/// <returns>
/// The complete and capitalization-accurate path based on the 
/// given <paramref name="path"/>.
/// </returns>
/// <remarks>
/// Walks the path using <see cref="DirectoryInfo"/>.<see cref="DirectoryInfo.GetDirectories()"/>
/// and <see cref="DirectoryInfo.GetFileSystemInfos()"/> so don't expect awesome performance.
/// </remarks>
public static string GetFileSystemFullPath(this string path)
{
    if (path == null)
    {
        return path;
    }

    string[] pathParts = Path.GetFullPath(path).Split(Path.DirectorySeparatorChar);
    if (pathParts.Any())
    {
        var dir = new DirectoryInfo(pathParts[0]).Root;

        for (int i = 1; i < pathParts.Length; ++i)
        {
            var next = dir.GetDirectories(pathParts[i]).FirstOrDefault();
            if (next == null)
            {
                if (i == pathParts.Length - 1)
                {
                    var fileInfo = dir.GetFileSystemInfos(pathParts[i]).FirstOrDefault();
                    if (fileInfo != null)
                    {
                        return fileInfo.FullName;
                    }
                }

                var ret = dir.FullName;
                do
                {
                    ret = Path.Combine(ret, pathParts[i++]);
                } while (i < pathParts.Length);

                return ret;
            }

            dir = next;
        }

        return dir.FullName;
    }

    // cannot do anything with it.
    return path;
}

static void Main( string[] args )
      {
         string[] paths = new string[] { "S:\hello\Hi", "S:\hello2\Hi\helloAgain" };
         foreach( string aPath in paths )
         {
            string normalizedPath = NormalizePath( aPath );
            Console.WriteLine( "Previous: '{0}', Normalized: '{1}'", aPath, normalizedPath );
         }
         Console.Write( "\n\n\nPress any key..." );
         Console.Read();
      }

  public static string NormalizePath( string path )
  {
     StringBuilder sb = new StringBuilder( path );
     string[] paths = path.Split('\\');
     foreach( string folderName in paths )
     {
        string normalizedFolderName = ToProperCase( folderName );
        sb.Replace( folderName, normalizedFolderName );
     }
     return sb.ToString();
  }

  /// <summary>
  /// Converts a string to first character upper and rest lower (Camel Case).
  /// </summary>
  /// <param name="stringValue"></param>
  /// <returns></returns>
  public static string ToProperCase( string stringValue )
  {
     if( string.IsNullOrEmpty( stringValue ) )
        return stringValue;

     return CultureInfo.CurrentCulture.TextInfo.ToTitleCase( stringValue.ToLower() );
  }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM