当前位置:  开发笔记 > 开发工具 > 正文

如何从绝对路径获得相对路径

如何解决《如何从绝对路径获得相对路径》经验,为你挑选了7个好方法。

我的应用程序中有一部分显示用户通过OpenFileDialog加载的文件路径.它占用了太多空间来显示整个路径,但我不想只显示文件名,因为它可能不明确.所以我更喜欢显示相对于assembly/exe目录的文件路径.

例如,程序集位于"C:\ Program Files\Dummy Folder\MyProgram",文件位于"C:\ Program Files\Dummy Folder\MyProgram\Data\datafile1.dat",然后我希望它显示".\DATA\datafile1.dat".如果文件位于"C:\ Program Files\Dummy Folder\datafile1.dat"中,那么我想要"..\datafile1.dat".但是,如果文件位于根目录下或根目录下的1个目录中,则显示完整路径.

你会推荐什么解决方案?正则表达式?

基本上我想显示有用的文件路径信息而不占用太多屏幕空间.

编辑:只是为了澄清一点.此解决方案的目的是帮助用户或我自己知道我最后加载了哪个文件,大致来自哪个目录.我正在使用只读文本框来显示路径.大多数情况下,文件路径比文本框的显示空间长得多.该路径应该是提供信息的,但不足以占用更多的屏幕空间.

Alex Brault的评论很好,Jonathan Leffler也是如此.DavidK提供的Win32功能只能帮助解决部分问题,而不是整个问题,但无论如何都要感谢.至于James Newton-King的解决方案,我稍后会在我自由时试一试.



1> 小智..:
/// 
/// Creates a relative path from one file or folder to another.
/// 
/// Contains the directory that defines the start of the relative path.
/// Contains the path that defines the endpoint of the relative path.
/// The relative path from the start directory to the end path or toPath if the paths are not related.
/// 
/// 
/// 
public static String MakeRelativePath(String fromPath, String toPath)
{
    if (String.IsNullOrEmpty(fromPath)) throw new ArgumentNullException("fromPath");
    if (String.IsNullOrEmpty(toPath))   throw new ArgumentNullException("toPath");

    Uri fromUri = new Uri(fromPath);
    Uri toUri = new Uri(toPath);

    if (fromUri.Scheme != toUri.Scheme) { return toPath; } // path can't be made relative.

    Uri relativeUri = fromUri.MakeRelativeUri(toUri);
    String relativePath = Uri.UnescapeDataString(relativeUri.ToString());

    if (toUri.Scheme.Equals("file", StringComparison.InvariantCultureIgnoreCase))
    {
        relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
    }

    return relativePath;
}


经过大量测试后,这种方法对我来说效果最好.您需要记住,Uri将不以路径分隔符结尾的文件夹视为文件(如果bar是文件夹,请使用c:\​​ foo\bar \而不是c:\ foo\bar).
斜杠问题的一般解决方案是使用`return relativeUri.ToString().Replace('/',Path.DirectorySeparatorChar);`
对我而言,**不是**返回相对路径.对于`c:\ test`和`c:\ test\abc.txt`,它返回`test\abc.txt`,在我看来这不是相对的.我希望只是`abc.txt`
你应该忽视这样创建的相对uri以获得有效的路径; .ToString()表示将包含无效且在路径中不必要的转义序列.
在arg检查之后添加以下if(fromPath.Last()!= Path.DirectorySeparatorChar){fromPath + = Path.DirectorySeparatorChar; } if(toPath.Last()!= Path.DirectorySeparatorChar){toPath + = Path.DirectorySeparatorChar; }
@juergend:我希望在Windows系统上使用`.\ abc.txt`.
请注意,当`toPath`包含转义字母时,此代码现在会失败.Net 4.5.例如`MakeRelativePath(@"c:\ root \",@"c:\ root \%74%65%73%74\filename.txt")`返回`test\filename.txt`而不是'%74% 65%73%74\filename.txt`.不是常见的情况,但有可能.
@juergend:文件夹路径需要以\结尾才能考虑文件夹.在您的情况下,'c:\ test \'和'c:\ test\abc.txt'将产生正确的结果.但这有点像黑客.
它会将反斜杠改为斜杠,对吧?否则它对我来说很好!

2> ctacke..:

这个问题有点晚了,但我也需要这个功能.我同意DavidK的观点,因为有一个提供此功能的内置API函数,您应该使用它.这是一个托管包装器:

public static string GetRelativePath(string fromPath, string toPath)
{
    int fromAttr = GetPathAttribute(fromPath);
    int toAttr = GetPathAttribute(toPath);

    StringBuilder path = new StringBuilder(260); // MAX_PATH
    if(PathRelativePathTo(
        path,
        fromPath,
        fromAttr,
        toPath,
        toAttr) == 0)
    {
        throw new ArgumentException("Paths must have a common prefix");
    }
    return path.ToString();
}

private static int GetPathAttribute(string path)
{
    DirectoryInfo di = new DirectoryInfo(path);
    if (di.Exists)
    {
        return FILE_ATTRIBUTE_DIRECTORY;
    }

    FileInfo fi = new FileInfo(path);
    if(fi.Exists)
    {
        return FILE_ATTRIBUTE_NORMAL;
    }

    throw new FileNotFoundException();
}

private const int FILE_ATTRIBUTE_DIRECTORY = 0x10;
private const int FILE_ATTRIBUTE_NORMAL = 0x80;

[DllImport("shlwapi.dll", SetLastError = true)]
private static extern int PathRelativePathTo(StringBuilder pszPath, 
    string pszFrom, int dwAttrFrom, string pszTo, int dwAttrTo);


如果文件或路径不存在,我不会抛出异常,因为这可能是完全合法的情况.
我发现它更清楚:它允许代码如bool success = PathRelativePathTo(...),我觉得比int更容易理解,你需要阅读有关int意味着什么的文档.
人们......你可以只删除整个GetPathAttribute,你知道.只要你绝对确定你给出的参数是目录,你只需要给它0x10它就可以使用完全不存在的路径.在我的情况下,首选的解决方案是返回完整的绝对目标路径而不是抛出该异常.
那么GetPathAttributes会返回什么呢?"文件不存在"没有标志,所以除了抛出之外我没有看到任何可行的选项,否则调用者会收到错误的信息.
请注意,如果无法创建相对路径,则PathRelativePathTo返回FALSE.在这种情况下,您应该返回String.Empty或抛出异常.
@ctacke:你也可以用返回类型bool进行P/Invoke并设置[return:MarshalAs(UnmanagedType.Bool)].
这是一个糟糕的功能,它不适用于不存在的路径.它不适用于正斜线......显然,基于Uri的技术似乎更加强大.

3> DavidK..:

shlwapi.dll中有一个Win32(C++)函数,可以完全按照您的要求执行: PathRelativePathTo()

不过,除了P/Invoke之外,我不知道有什么方法可以从.NET访问它.


我没有读到该页面,因为声明shlwapi.dll本身已被弃用:它说的是页面上列出的shlwapi.dll的包装函数已弃用.在该页面上没有提到PathRelativePathTo()本身,并且PathRelativePathTo()的主要文档没有提及弃用,所以据我所知它仍然是一个有效的函数来调用.
哪个部分没有帮助?阅读原始帖子,它看起来像PathRelativePathTo()做你想要的,但这可能是因为我误解了一些东西......
工作完美无瑕.有关如何设置P/Invoke的信息,请参见http://pinvoke.net/default.aspx/shlwapi.PathRelativePathTo.
谢谢!我其实是在寻找一个C++解决方案!
值得注意的是,`shlwapi.dll`中的函数现已弃用http://msdn.microsoft.com/en-us/library/windows/desktop/bb759845(v=vs.85).aspx`"这些函数可用通过Windows XP Service Pack 2(SP2)和Windows Server 2003.它们可能在后续版本的Windows中被更改或不可用."`

4> Muhammad Reh..:

如果路径是目录路径,当文件路径不以'/'结尾时,@ Dave的解决方案不起作用.该解决方案解决了该问题,并且还使用Uri.UriSchemeFile常量而不是硬编码"FILE".

这里提供的许多其他解决方案都使用字符串操作,但没有提供关于它们的可靠性的保证或指示,例如单元测试的数量等.总的来说,我建议使用Uri.MakeRelativeUri是最安全的纯.NET选项,而最好的选择是@ ctacke的Windows互操作示例.

/// 
/// Creates a relative path from one file or folder to another.
/// 
/// Contains the directory that defines the start of the relative path.
/// Contains the path that defines the endpoint of the relative path.
/// The relative path from the start directory to the end path.
///  or  is null.
/// 
/// 
public static string GetRelativePath(string fromPath, string toPath)
{
    if (string.IsNullOrEmpty(fromPath))
    {
        throw new ArgumentNullException("fromPath");
    }

    if (string.IsNullOrEmpty(toPath))
    {
        throw new ArgumentNullException("toPath");
    }

    Uri fromUri = new Uri(AppendDirectorySeparatorChar(fromPath));
    Uri toUri = new Uri(AppendDirectorySeparatorChar(toPath));

    if (fromUri.Scheme != toUri.Scheme)
    {
        return toPath;
    }

    Uri relativeUri = fromUri.MakeRelativeUri(toUri);
    string relativePath = Uri.UnescapeDataString(relativeUri.ToString());

    if (string.Equals(toUri.Scheme, Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase))
    {
        relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
    }

    return relativePath;
}

private static string AppendDirectorySeparatorChar(string path)
{
    // Append a slash only if the path is a directory and does not have a slash.
    if (!Path.HasExtension(path) &&
        !path.EndsWith(Path.DirectorySeparatorChar.ToString()))
    {
        return path + Path.DirectorySeparatorChar;
    }

    return path;
}



5> Ray Vega..:

如果您使用的是.NET Core 2.0,Path.GetRelativePath()则可以使用此特定功能:

        var relativeTo = @"C:\Program Files\Dummy Folder\MyProgram";
        var path = @"C:\Program Files\Dummy Folder\MyProgram\Data\datafile1.dat";

        string relativePath = System.IO.Path.GetRelativePath(relativeTo, path);

        System.Console.WriteLine(relativePath);
        // output --> Data\datafile1.dat 

否则,对于.NET完整框架(从v4.7开始),建议使用其他建议的答案之一.



6> James Newton..:

我过去曾经用过这个.

/// 
/// Creates a relative path from one file
/// or folder to another.
/// 
/// 
/// Contains the directory that defines the
/// start of the relative path.
/// 
/// 
/// Contains the path that defines the
/// endpoint of the relative path.
/// 
/// 
/// The relative path from the start
/// directory to the end path.
/// 
/// 
public static string MakeRelative(string fromDirectory, string toPath)
{
  if (fromDirectory == null)
    throw new ArgumentNullException("fromDirectory");

  if (toPath == null)
    throw new ArgumentNullException("toPath");

  bool isRooted = (Path.IsPathRooted(fromDirectory) && Path.IsPathRooted(toPath));

  if (isRooted)
  {
    bool isDifferentRoot = (string.Compare(Path.GetPathRoot(fromDirectory), Path.GetPathRoot(toPath), true) != 0);

    if (isDifferentRoot)
      return toPath;
  }

  List relativePath = new List();
  string[] fromDirectories = fromDirectory.Split(Path.DirectorySeparatorChar);

  string[] toDirectories = toPath.Split(Path.DirectorySeparatorChar);

  int length = Math.Min(fromDirectories.Length, toDirectories.Length);

  int lastCommonRoot = -1;

  // find common root
  for (int x = 0; x < length; x++)
  {
    if (string.Compare(fromDirectories[x], toDirectories[x], true) != 0)
      break;

    lastCommonRoot = x;
  }

  if (lastCommonRoot == -1)
    return toPath;

  // add relative folders in from path
  for (int x = lastCommonRoot + 1; x < fromDirectories.Length; x++)
  {
    if (fromDirectories[x].Length > 0)
      relativePath.Add("..");
  }

  // add to folders to path
  for (int x = lastCommonRoot + 1; x < toDirectories.Length; x++)
  {
    relativePath.Add(toDirectories[x]);
  }

  // create relative path
  string[] relativeParts = new string[relativePath.Count];
  relativePath.CopyTo(relativeParts, 0);

  string newPath = string.Join(Path.DirectorySeparatorChar.ToString(), relativeParts);

  return newPath;
}



7> Jonathan Lef..:

正如Alex Brault指出的那样,特别是在Windows上,绝对路径(带有驱动器号和所有内容)是明确的,通常更好.

您的OpenFileDialog不应该使用常规的树浏览器结构吗?

要获得一些术语,RefDir是您要指定路径的相对目录; 该AbsName是要映射的绝对路径名; 并且RelPath是生成的相对路径.

选择匹配的第一个选项:

如果您有不同的驱动器号,则没有从RefDir到AbsName的相对路径; 你必须使用AbsName.

如果AbsName位于RefDir的子目录中或者是RefDir中的文件,则只需从AbsName的开头删除RefDir即可创建RelPath; 可选地预先添加"./"(或".\",因为您在Windows上).

找到RefDir和AbsName的最长公共前缀(其中D:\ Abc\Def和D:\ Abc\Default共享D:\ Abc作为最长的公共前缀;它必须是名称组件的映射,而不是简单的最长公共子); 称之为LCP.从AbsName和RefDir中删除LCP.对于(RefDir - LCP)中剩下的每个路径组件,将".."添加到(AbsName - LCP)以产生RelPath.

为了说明最后一条规则(当然,到目前为止最复杂的规则),从以下开始:

RefDir = D:\Abc\Def\Ghi
AbsName = D:\Abc\Default\Karma\Crucible

然后

LCP = D:\Abc
(RefDir - LCP) = Def\Ghi
(Absname - LCP) = Default\Karma\Crucible
RelPath = ..\..\Default\Karma\Crucible

在我打字的时候,DavidK提出了一个答案,表明你不是第一个需要这个功能的人,并且有一个标准的功能来完成这项工作. 用它. 但是,从第一原则开始思考你的方式也没有坏处.

除了Unix系统不支持驱动器号(因此所有内容始终位于同一根目录下,因此第一个子弹无关紧要),可以在Unix上使用相同的技术.

推荐阅读
小妖694_807
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有