How to get relative path from absolute path

前端 未结 23 1845
既然无缘
既然无缘 2020-11-22 11:52

There\'s a part in my apps that displays the file path loaded by the user through OpenFileDialog. It\'s taking up too much space to display the whole path, but I don\'t want

相关标签:
23条回答
  • 2020-11-22 12:46

    .NET Core 2.0 Answer

    .NET Core 2.0 has Path.GetRelativePath which can be used like so:

    var relativePath = Path.GetRelativePath(
        @"C:\Program Files\Dummy Folder\MyProgram",
        @"C:\Program Files\Dummy Folder\MyProgram\Data\datafile1.dat");
    

    In the above example, the relativePath variable is equal to Data\datafile1.dat.

    Alternative .NET Answer

    @Dave's solution does not work when the file paths do not end with a forward slash character (/) which can happen if the path is a directory path. My solution fixes that problem and also makes use of the Uri.UriSchemeFile constant instead of hard coding "FILE".

    /// <summary>
    /// Creates a relative path from one file or folder to another.
    /// </summary>
    /// <param name="fromPath">Contains the directory that defines the start of the relative path.</param>
    /// <param name="toPath">Contains the path that defines the endpoint of the relative path.</param>
    /// <returns>The relative path from the start directory to the end path.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="fromPath"/> or <paramref name="toPath"/> is <c>null</c>.</exception>
    /// <exception cref="UriFormatException"></exception>
    /// <exception cref="InvalidOperationException"></exception>
    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;
    }
    

    Windows Interop Answer

    There is a Windows API called PathRelativePathToA that can be used to find a relative path. Please note that the file or directory paths that you pass to the function must exist for it to work.

    var relativePath = PathExtended.GetRelativePath(
        @"C:\Program Files\Dummy Folder\MyProgram",
        @"C:\Program Files\Dummy Folder\MyProgram\Data\datafile1.dat");
    
    public static class PathExtended
    {
        private const int FILE_ATTRIBUTE_DIRECTORY = 0x10;
        private const int FILE_ATTRIBUTE_NORMAL = 0x80;
        private const int MaximumPath = 260;
    
        public static string GetRelativePath(string fromPath, string toPath)
        {
            var fromAttribute = GetPathAttribute(fromPath);
            var toAttribute = GetPathAttribute(toPath);
    
            var stringBuilder = new StringBuilder(MaximumPath);
            if (PathRelativePathTo(
                stringBuilder,
                fromPath,
                fromAttribute,
                toPath,
                toAttribute) == 0)
            {
                throw new ArgumentException("Paths must have a common prefix.");
            }
    
            return stringBuilder.ToString();
        }
    
        private static int GetPathAttribute(string path)
        {
            var directory = new DirectoryInfo(path);
            if (directory.Exists)
            {
                return FILE_ATTRIBUTE_DIRECTORY;
            }
    
            var file = new FileInfo(path);
            if (file.Exists)
            {
                return FILE_ATTRIBUTE_NORMAL;
            }
    
            throw new FileNotFoundException(
                "A file or directory with the specified path was not found.",
                path);
        }
    
        [DllImport("shlwapi.dll", SetLastError = true)]
        private static extern int PathRelativePathTo(
            StringBuilder pszPath,
            string pszFrom,
            int dwAttrFrom,
            string pszTo,
            int dwAttrTo);
    }
    
    0 讨论(0)
  • 2020-11-22 12:46

    Play with something like:

    private String GetRelativePath(Int32 level, String directory, out String errorMessage) {
            if (level < 0 || level > 5) {
                errorMessage = "Find some more smart input data";
                return String.Empty;
            }
            // ==========================
            while (level != 0) {
                directory = Path.GetDirectoryName(directory);
                level -= 1;
            }
            // ==========================
            errorMessage = String.Empty;
            return directory;
        }
    

    And test it

    [Test]
        public void RelativeDirectoryPathTest() {
            var relativePath =
                GetRelativePath(3, AppDomain.CurrentDomain.BaseDirectory, out var errorMessage);
            Console.WriteLine(relativePath);
            if (String.IsNullOrEmpty(errorMessage) == false) {
                Console.WriteLine(errorMessage);
                Assert.Fail("Can not find relative path");
            }
        }
    
    0 讨论(0)
  • 2020-11-22 12:48

    In ASP.NET Core 2, if you want the relative path to bin\Debug\netcoreapp2.2 you can use the following combination:

    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.Configuration;
    public class RenderingService : IRenderingService
    {
    
        private readonly IHostingEnvironment _hostingEnvironment;
        public RenderingService(IHostingEnvironment hostingEnvironment)
        {
        _hostingEnvironment = hostingEnvironment;
        }
    
        public string RelativeAssemblyDirectory()
        {
            var contentRootPath = _hostingEnvironment.ContentRootPath;
            string executingAssemblyDirectoryAbsolutePath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
            string executingAssemblyDirectoryRelativePath = System.IO.Path.GetRelativePath(contentRootPath, executingAssemblyDirectoryAbsolutePath);
            return executingAssemblyDirectoryRelativePath;
        }
    }
    
    0 讨论(0)
  • 2020-11-22 12:49

    A bit late to the question, but I just needed this feature as well. I agree with DavidK that since there is a built-in API function that provides this, you should use it. Here's a managed wrapper for it:

    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);
    
    0 讨论(0)
  • 2020-11-22 12:49

    There is a Win32 (C++) function in shlwapi.dll that does exactly what you want: PathRelativePathTo()

    I'm not aware of any way to access this from .NET other than to P/Invoke it, though.

    0 讨论(0)
  • 2020-11-22 12:49

    I have used this in the past.

    /// <summary>
    /// Creates a relative path from one file
    /// or folder to another.
    /// </summary>
    /// <param name="fromDirectory">
    /// Contains the directory that defines the
    /// start of the relative path.
    /// </param>
    /// <param name="toPath">
    /// Contains the path that defines the
    /// endpoint of the relative path.
    /// </param>
    /// <returns>
    /// The relative path from the start
    /// directory to the end path.
    /// </returns>
    /// <exception cref="ArgumentNullException"></exception>
    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<string> relativePath = new List<string>();
      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;
    }
    
    0 讨论(0)
提交回复
热议问题