简体   繁体   English

在C#中规范化目录名称

[英]Normalize directory names in C#

Here's the problem, I have a bunch of directories like 这是问题,我有一堆像

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

On the file system it shows these directories as 在文件系统上,它将这些目录显示为

S:\\hello\\Hi S:\\你好\\喜
S:\\hello2\\Hi\\helloAgain S:\\ hello2 \\你好\\ helloAgain

Is there any function in C# that will give me what the file system name of a directory is with the proper casing? C#中是否有任何函数可以为我提供目录的文件系统名称与正确的大小写?

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. 正如iceman指出的那样,只有当DirectoryInfo(或通常是FileSystemInfo)来自对GetDirectories(或GetFileSystemInfos)方法的调用时,FullName才会返回正确的大小写。

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 ;) 当然,如果你必须重新规范化整个文件系统树,你可能更喜欢冰人的解决方案,但我在具有中等深度的路径上进行10000次迭代测试,只需几秒钟;)

    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. 基本的想法是从DirectoryInfo对象获取子目录将获得正确的大小写,因此我们只需要拆分目录名称并从根目录步行到目标目录,在每一步获得正确的大小写。

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在他的回答中提出了将路径分成其组件并逐个获取外壳的想法,这导致了大幅度的速度增加,因为您不再像我原来的答案那样检查所有内容 Bertu also generalized his solution to do files as well as directories. Bertu还推广了他的解决方案来做文件和目录。 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. 在我的测试中,上面发布的代码(使用Bertu的“分割路径并按部分分离”的想法,但迭代地而不是递归地接近它)在Bertu代码的大约一半时间内运行。 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. 我不确定是不是因为他的方法也处理文件,因为他使用递归会带来额外的开销,或者因为他最终在每次迭代中调用Path.GetFileName(path)Path.GetDirectoryName(path) Depending on your exact needs, some combination of his answer and mine will likely solve your problem as well as is possible in C#. 根据您的确切需求,他和我的答案的某些组合可能会解决您的问题以及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. 在这方面,我应该提到.Net文件名处理有一些限制 ,因为在.Net中这样做需要制作大量的DirectoryInfo对象,如果这是你的瓶颈,你可能想要考虑非托管代码。

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): 我的版本(类似于BertuPGKevin使用GetDirectories()GetFileSystemInfos()但允许不存在的路径和文件,并作为扩展方法):

/// <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() );
  }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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