Làm thế nào để có được đường dẫn tương đối từ đường dẫn tuyệt đối


174

Có một phần trong ứng dụng của tôi hiển thị đường dẫn tệp được người dùng tải thông qua OpenFileDialog. Nó chiếm quá nhiều không gian để hiển thị toàn bộ đường dẫn, nhưng tôi không muốn chỉ hiển thị tên tệp vì nó có thể mơ hồ. Vì vậy, tôi muốn hiển thị đường dẫn tệp liên quan đến thư mục assembly / exe.

Ví dụ, tập hợp nằm trong C:\Program Files\Dummy Folder\MyProgramvà tập tin lúc C:\Program Files\Dummy Folder\MyProgram\Data\datafile1.datđó tôi muốn nó hiển thị .\Data\datafile1.dat. Nếu các tập tin được trong C:\Program Files\Dummy Folder\datafile1.dat, sau đó tôi sẽ muốn ..\datafile1.dat. Nhưng nếu tệp nằm trong thư mục gốc hoặc 1 thư mục bên dưới root, thì hiển thị đường dẫn đầy đủ.

Giải pháp nào bạn muốn giới thiệu? Regex?

Về cơ bản tôi muốn hiển thị thông tin đường dẫn tệp hữu ích mà không chiếm quá nhiều không gian màn hình.

EDIT: Chỉ cần làm rõ hơn một chút. Mục đích của giải pháp này là giúp người dùng hoặc bản thân tôi biết tôi đã tải tập tin nào cuối cùng và đại khái là từ thư mục nào. Tôi đang sử dụng hộp văn bản chỉ đọc để hiển thị đường dẫn. Hầu hết thời gian, đường dẫn tệp dài hơn nhiều so với không gian hiển thị của hộp văn bản. Đường dẫn được cho là có nhiều thông tin nhưng không đủ quan trọng để chiếm nhiều không gian màn hình hơn.

Nhận xét của Alex Brault là tốt, Jonathan Leffler cũng vậy. Hàm Win32 do DavidK cung cấp chỉ giúp giải quyết một phần vấn đề, không phải toàn bộ vấn đề, nhưng dù sao cũng cảm ơn. Đối với giải pháp James Newton-King, tôi sẽ thử lại sau khi tôi rảnh.


Câu trả lời:


192

.NET Core 2.0 có Path.GetRelativePath, sử dụng cái này.

/// <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 or <c>toPath</c> if the paths are not related.</returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="UriFormatException"></exception>
/// <exception cref="InvalidOperationException"></exception>
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;
}

32
Sau rất nhiều thử nghiệm phương pháp này làm việc tốt nhất cho tôi. Bạn cần nhớ rằng Uri xử lý thư mục không kết thúc bằng dấu tách đường dẫn dưới dạng tệp (sử dụng c: \ foo \ bar \ thay vì c: \ foo \ bar nếu thanh là thư mục).
VVS

6
Một giải pháp chung cho vấn đề gạch chéo là sử dụngreturn relativeUri.ToString().Replace('/',Path.DirectorySeparatorChar);
Nyerguds 15/03/2016

4
Bạn nên bỏ cảnh uri tương đối do đó được tạo để có đường dẫn hợp lệ; đại diện .ToString () sẽ bao gồm các chuỗi thoát không hợp lệ và không cần thiết trong đường dẫn.
Eamon Nerbonne

3
đã thêm vào bên dưới sau khi arg kiểm tra if (fromPath.Last ()! = Path.DirectorySeparatorChar) {fromPath + = Path.DirectorySeparatorChar; } if (toPath.Last ()! = Path.DirectorySeparatorChar) {toPath + = Path.DirectorySeparatorChar; }
Simon

8
Đối với tôi điều này không trở lại con đường tương đối. Đối với c:\testc:\test\abc.txtnó trả về test\abc.txtmà không phải là tương đối theo ý kiến ​​của tôi. Tôi chỉ mong đợiabc.txt
juergen d

51

Một chút muộn cho câu hỏi, nhưng tôi chỉ cần tính năng này là tốt. Tôi đồng ý với DavidK rằng vì có một hàm API tích hợp cung cấp điều này, bạn nên sử dụng nó. Đây là một trình bao bọc được quản lý cho nó:

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);

4
Tôi sẽ không ném ngoại lệ nếu tệp hoặc đường dẫn không tồn tại vì đây có thể là một trường hợp hoàn toàn hợp pháp.
VVS

2
Vì vậy, những gì GetPathAttribution sẽ trở lại sau đó? Không có cờ cho "tập tin không tồn tại" vì vậy tôi không thấy bất kỳ tùy chọn khả thi nào ngoài việc ném, nếu không người gọi sẽ nhận được thông tin sai.
ctacke

2
Lưu ý rằng PathRelativePathTo trả về FALSE nếu không có đường dẫn tương đối nào có thể được tạo. Trong trường hợp đó, bạn nên trả về String.Empty hoặc ném ngoại lệ.
Daniel Rose

4
Tôi thấy rõ hơn: Nó cho phép mã như bool thành công = PathRelativePathTo (...) mà tôi thấy dễ hiểu hơn một int nơi bạn cần đọc tài liệu về ý nghĩa của int.
Daniel Rose

4
Mọi người ... bạn có thể xóa toàn bộ GetPathAttribution, bạn biết đấy. Miễn là bạn chắc chắn chắc chắn rằng các đối số bạn đưa ra là các thư mục, bạn chỉ cần cung cấp cho nó 0x10 và nó sẽ hoạt động với các đường dẫn hoàn toàn không tồn tại. Và trong trường hợp của tôi, giải pháp ưa thích chỉ đơn giản là trả về đường dẫn đích tuyệt đối đầy đủ thay vì ném ngoại lệ đó.
Nyerguds

31

.NET Core 2.0 Trả lời

.NET Core 2.0 có Path.GetRelativePath có thể được sử dụng như vậy:

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

Trong ví dụ trên, relativePathbiến bằng Data\datafile1.dat.

Trả lời .NET thay thế

Giải pháp của @ Dave không hoạt động khi đường dẫn tệp không kết thúc bằng ký tự gạch chéo ( /) có thể xảy ra nếu đường dẫn là đường dẫn thư mục. Giải pháp của tôi khắc phục vấn đề đó và cũng sử dụng Uri.UriSchemeFilehằng số thay vì mã hóa cứng "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 Trả lời

Có một API Windows được gọi là PathRelativePathToA có thể được sử dụng để tìm đường dẫn tương đối. Xin lưu ý rằng các đường dẫn tệp hoặc thư mục mà bạn chuyển đến hàm phải tồn tại để nó hoạt động.

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

1
Hoạt động giống như những gì người ta mong đợi - Tôi khuyên bạn nên thay vì câu trả lời được đánh giá cao nhất.
Người chơi

Xem như bạn cho AltDirectorySeparatorCharlà có khả năng, bạn cũng không nên AppendDirectorySeparatorCharkiểm tra nó?
Ohad Schneider

Ngoài ra, một tệp có thể không có phần mở rộng, vì vậy trong khi điều này có thể thuận tiện hơn trong hầu hết các trường hợp, nó không cho phép bạn chỉ định trường hợp đó. Có thể thêm một kiểm tra nếu mục hệ thống tệp tồn tại và nếu vậy hãy kiểm tra xem đó là tệp hoặc thư mục. Nếu nó không tồn tại, hãy giữ nguyên logic này. Hoặc thậm chí có thể thêm một số cách trong chữ ký để xác định xem một tệp hoặc thư mục đã được cung cấp (ví dụ 2 booleans).
Ohad Schneider

Cuối cùng, tôi sẽ ném một ngoại lệ nếu các phương án khác nhau.
Ohad Schneider

25

Có một chức năng Win32 (C ++) trong shlwapi.dll thực hiện chính xác những gì bạn muốn: PathRelativePathTo()

Mặc dù vậy, tôi không biết cách nào để truy cập cái này từ .NET ngoài P / Gọi nó.


3
Phần nào không giúp được? Đọc bài viết gốc, tôi thấy PathRelativePathTo () thực hiện những gì bạn muốn, nhưng có lẽ vì tôi đã hiểu sai điều gì đó ...
DavidK

3
Hoạt động hoàn hảo. Xem pinvoke.net/default.aspx/shlwapi.PathRelativePathTo về cách thiết lập P / Gọi.
joce

2
Cảm ơn bạn! Tôi đã thực sự tìm kiếm một giải pháp C ++!
NTDLS

2
Điều đáng chú ý là các chức năng shlwapi.dllhiện đang bị phản đối msdn.microsoft.com/en-us/l Library / windows / desktop / trộm "These functions are available through Windows XP Service Pack 2 (SP2) and Windows Server 2003. They might be altered or unavailable in subsequent versions of Windows."
Cơ bản

4
Tôi không đọc trang đó vì cho rằng chính shlwapi.dll không được dùng nữa: tất cả những gì nó nói là các hàm bao bọc từ shlwapi.dll được liệt kê trên trang đều không được dùng nữa. Bản thân PathRelativePathTo () không được đề cập trên trang đó và tài liệu chính cho PathRelativePathTo () không đề cập đến sự phản đối, theo như tôi có thể thấy nó vẫn là một hàm hợp lệ để gọi.
DavidK

14

Nếu bạn đang sử dụng .NET Core 2.0,Path.GetRelativePath() có sẵn cung cấp chức năng cụ thể này:

        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 

Mặt khác, đối với .NET full framework (kể từ v4.7) khuyên bạn nên sử dụng một trong những câu trả lời được đề xuất khác.


9

Tôi đã sử dụng điều này trong quá khứ.

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

1
Tôi sẽ xem xét nó, đôi khi tôi cần phải kiểm tra nó. Cảm ơn
bị lỗi

1
Tôi sẽ đề nghị sử dụng Path.GetFullPath () để so sánh thành công hai đường dẫn với các bit tương đối. Ví dụ: c: \ a \ .. \ b so với c: \ b so với c: \ b \. \
VVS

5

Như Alex Brault chỉ ra, đặc biệt là trên Windows, đường dẫn tuyệt đối (có ký tự ổ đĩa và tất cả) không rõ ràng và thường tốt hơn.

OpenFileDialog của bạn có nên sử dụng cấu trúc trình duyệt cây thông thường không?

Để có được một số danh pháp tại chỗ, RefDir là thư mục mà bạn muốn chỉ định đường dẫn; các AbsName là con đường tên tuyệt đối rằng bạn muốn ánh xạ; và RelPath là đường dẫn tương đối kết quả.

Thực hiện các tùy chọn đầu tiên phù hợp với:

  • Nếu bạn có các ký tự ổ đĩa khác nhau, không có đường dẫn tương đối từ RefDir đến absName; bạn phải sử dụng absName.
  • Nếu absName nằm trong thư mục con của RefDir hoặc là một tệp trong RefDir thì chỉ cần xóa RefDir khỏi đầu của absName để tạo RelPath; tùy chọn thêm "./" (hoặc ". \" vì bạn đang ở trên Windows).
  • Tìm tiền tố chung dài nhất của RefDir và absName (trong đó D: \ Abc \ Def và D: \ Abc \ Chia sẻ mặc định D: \ Abc là tiền tố phổ biến dài nhất, nó phải là ánh xạ của các thành phần tên, không phải là phổ biến dài nhất đơn giản chuỗi con); gọi nó là LCP. Xóa LCP khỏi absName và RefDir. Đối với mỗi thành phần đường dẫn còn lại trong (RefDir - LCP), hãy thêm ".. \" vào (absName - LCP) để mang lại RelPath.

Để minh họa quy tắc cuối cùng (dĩ nhiên là phức tạp nhất), hãy bắt đầu bằng:

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

Sau đó

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

Trong khi tôi đang gõ, DavidK đã đưa ra một câu trả lời cho thấy rằng bạn không phải là người đầu tiên cần tính năng này và có một chức năng tiêu chuẩn để thực hiện công việc này. Sử dụng nó. Nhưng cũng không có hại gì khi có thể nghĩ theo cách của bạn từ các nguyên tắc đầu tiên.

Ngoại trừ việc các hệ thống Unix không hỗ trợ các ký tự ổ đĩa (vì vậy mọi thứ luôn nằm trong cùng thư mục gốc và do đó viên đạn đầu tiên không liên quan), kỹ thuật tương tự có thể được sử dụng trên Unix.


4

Đó là một chặng đường dài, nhưng lớp System.Uri có một phương thức có tên MakeRelativeUri. Có lẽ bạn có thể sử dụng nó. Thật đáng tiếc khi System.IO.Path không có thứ này.


4

Như đã chỉ ra ở trên .NET Core 2.x đã thực hiện Path.GetRelativePath.

Mã dưới đây được điều chỉnh từ các nguồn và hoạt động tốt với .NET 4.7.1 Framework.

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

//Adapted from https://github.com/dotnet/corefx/blob/master/src/Common/src/CoreLib/System/IO/Path.cs#L697
// by Anton Krouglov

using System.Runtime.CompilerServices;
using System.Diagnostics;
using System.Text;
using Xunit;

namespace System.IO {
    // Provides methods for processing file system strings in a cross-platform manner.
    // Most of the methods don't do a complete parsing (such as examining a UNC hostname), 
    // but they will handle most string operations.
    public static class PathNetCore {

        /// <summary>
        /// Create a relative path from one path to another. Paths will be resolved before calculating the difference.
        /// Default path comparison for the active platform will be used (OrdinalIgnoreCase for Windows or Mac, Ordinal for Unix).
        /// </summary>
        /// <param name="relativeTo">The source path the output should be relative to. This path is always considered to be a directory.</param>
        /// <param name="path">The destination path.</param>
        /// <returns>The relative path or <paramref name="path"/> if the paths don't share the same root.</returns>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="relativeTo"/> or <paramref name="path"/> is <c>null</c> or an empty string.</exception>
        public static string GetRelativePath(string relativeTo, string path) {
            return GetRelativePath(relativeTo, path, StringComparison);
        }

        private static string GetRelativePath(string relativeTo, string path, StringComparison comparisonType) {
            if (string.IsNullOrEmpty(relativeTo)) throw new ArgumentNullException(nameof(relativeTo));
            if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path));
            Debug.Assert(comparisonType == StringComparison.Ordinal ||
                         comparisonType == StringComparison.OrdinalIgnoreCase);

            relativeTo = Path.GetFullPath(relativeTo);
            path = Path.GetFullPath(path);

            // Need to check if the roots are different- if they are we need to return the "to" path.
            if (!PathInternalNetCore.AreRootsEqual(relativeTo, path, comparisonType))
                return path;

            int commonLength = PathInternalNetCore.GetCommonPathLength(relativeTo, path,
                ignoreCase: comparisonType == StringComparison.OrdinalIgnoreCase);

            // If there is nothing in common they can't share the same root, return the "to" path as is.
            if (commonLength == 0)
                return path;

            // Trailing separators aren't significant for comparison
            int relativeToLength = relativeTo.Length;
            if (PathInternalNetCore.EndsInDirectorySeparator(relativeTo))
                relativeToLength--;

            bool pathEndsInSeparator = PathInternalNetCore.EndsInDirectorySeparator(path);
            int pathLength = path.Length;
            if (pathEndsInSeparator)
                pathLength--;

            // If we have effectively the same path, return "."
            if (relativeToLength == pathLength && commonLength >= relativeToLength) return ".";

            // We have the same root, we need to calculate the difference now using the
            // common Length and Segment count past the length.
            //
            // Some examples:
            //
            //  C:\Foo C:\Bar L3, S1 -> ..\Bar
            //  C:\Foo C:\Foo\Bar L6, S0 -> Bar
            //  C:\Foo\Bar C:\Bar\Bar L3, S2 -> ..\..\Bar\Bar
            //  C:\Foo\Foo C:\Foo\Bar L7, S1 -> ..\Bar

            StringBuilder
                sb = new StringBuilder(); //StringBuilderCache.Acquire(Math.Max(relativeTo.Length, path.Length));

            // Add parent segments for segments past the common on the "from" path
            if (commonLength < relativeToLength) {
                sb.Append("..");

                for (int i = commonLength + 1; i < relativeToLength; i++) {
                    if (PathInternalNetCore.IsDirectorySeparator(relativeTo[i])) {
                        sb.Append(DirectorySeparatorChar);
                        sb.Append("..");
                    }
                }
            }
            else if (PathInternalNetCore.IsDirectorySeparator(path[commonLength])) {
                // No parent segments and we need to eat the initial separator
                //  (C:\Foo C:\Foo\Bar case)
                commonLength++;
            }

            // Now add the rest of the "to" path, adding back the trailing separator
            int differenceLength = pathLength - commonLength;
            if (pathEndsInSeparator)
                differenceLength++;

            if (differenceLength > 0) {
                if (sb.Length > 0) {
                    sb.Append(DirectorySeparatorChar);
                }

                sb.Append(path, commonLength, differenceLength);
            }

            return sb.ToString(); //StringBuilderCache.GetStringAndRelease(sb);
        }

        // Public static readonly variant of the separators. The Path implementation itself is using
        // internal const variant of the separators for better performance.
        public static readonly char DirectorySeparatorChar = PathInternalNetCore.DirectorySeparatorChar;
        public static readonly char AltDirectorySeparatorChar = PathInternalNetCore.AltDirectorySeparatorChar;
        public static readonly char VolumeSeparatorChar = PathInternalNetCore.VolumeSeparatorChar;
        public static readonly char PathSeparator = PathInternalNetCore.PathSeparator;

        /// <summary>Returns a comparison that can be used to compare file and directory names for equality.</summary>
        internal static StringComparison StringComparison => StringComparison.OrdinalIgnoreCase;
    }

    /// <summary>Contains internal path helpers that are shared between many projects.</summary>
    internal static class PathInternalNetCore {
        internal const char DirectorySeparatorChar = '\\';
        internal const char AltDirectorySeparatorChar = '/';
        internal const char VolumeSeparatorChar = ':';
        internal const char PathSeparator = ';';

        internal const string ExtendedDevicePathPrefix = @"\\?\";
        internal const string UncPathPrefix = @"\\";
        internal const string UncDevicePrefixToInsert = @"?\UNC\";
        internal const string UncExtendedPathPrefix = @"\\?\UNC\";
        internal const string DevicePathPrefix = @"\\.\";

        //internal const int MaxShortPath = 260;

        // \\?\, \\.\, \??\
        internal const int DevicePrefixLength = 4;

        /// <summary>
        /// Returns true if the two paths have the same root
        /// </summary>
        internal static bool AreRootsEqual(string first, string second, StringComparison comparisonType) {
            int firstRootLength = GetRootLength(first);
            int secondRootLength = GetRootLength(second);

            return firstRootLength == secondRootLength
                   && string.Compare(
                       strA: first,
                       indexA: 0,
                       strB: second,
                       indexB: 0,
                       length: firstRootLength,
                       comparisonType: comparisonType) == 0;
        }

        /// <summary>
        /// Gets the length of the root of the path (drive, share, etc.).
        /// </summary>
        internal static int GetRootLength(string path) {
            int i = 0;
            int volumeSeparatorLength = 2; // Length to the colon "C:"
            int uncRootLength = 2; // Length to the start of the server name "\\"

            bool extendedSyntax = path.StartsWith(ExtendedDevicePathPrefix);
            bool extendedUncSyntax = path.StartsWith(UncExtendedPathPrefix);
            if (extendedSyntax) {
                // Shift the position we look for the root from to account for the extended prefix
                if (extendedUncSyntax) {
                    // "\\" -> "\\?\UNC\"
                    uncRootLength = UncExtendedPathPrefix.Length;
                }
                else {
                    // "C:" -> "\\?\C:"
                    volumeSeparatorLength += ExtendedDevicePathPrefix.Length;
                }
            }

            if ((!extendedSyntax || extendedUncSyntax) && path.Length > 0 && IsDirectorySeparator(path[0])) {
                // UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo")

                i = 1; //  Drive rooted (\foo) is one character
                if (extendedUncSyntax || (path.Length > 1 && IsDirectorySeparator(path[1]))) {
                    // UNC (\\?\UNC\ or \\), scan past the next two directory separators at most
                    // (e.g. to \\?\UNC\Server\Share or \\Server\Share\)
                    i = uncRootLength;
                    int n = 2; // Maximum separators to skip
                    while (i < path.Length && (!IsDirectorySeparator(path[i]) || --n > 0)) i++;
                }
            }
            else if (path.Length >= volumeSeparatorLength &&
                     path[volumeSeparatorLength - 1] == PathNetCore.VolumeSeparatorChar) {
                // Path is at least longer than where we expect a colon, and has a colon (\\?\A:, A:)
                // If the colon is followed by a directory separator, move past it
                i = volumeSeparatorLength;
                if (path.Length >= volumeSeparatorLength + 1 && IsDirectorySeparator(path[volumeSeparatorLength])) i++;
            }

            return i;
        }

        /// <summary>
        /// True if the given character is a directory separator.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static bool IsDirectorySeparator(char c) {
            return c == PathNetCore.DirectorySeparatorChar || c == PathNetCore.AltDirectorySeparatorChar;
        }

        /// <summary>
        /// Get the common path length from the start of the string.
        /// </summary>
        internal static int GetCommonPathLength(string first, string second, bool ignoreCase) {
            int commonChars = EqualStartingCharacterCount(first, second, ignoreCase: ignoreCase);

            // If nothing matches
            if (commonChars == 0)
                return commonChars;

            // Or we're a full string and equal length or match to a separator
            if (commonChars == first.Length
                && (commonChars == second.Length || IsDirectorySeparator(second[commonChars])))
                return commonChars;

            if (commonChars == second.Length && IsDirectorySeparator(first[commonChars]))
                return commonChars;

            // It's possible we matched somewhere in the middle of a segment e.g. C:\Foodie and C:\Foobar.
            while (commonChars > 0 && !IsDirectorySeparator(first[commonChars - 1]))
                commonChars--;

            return commonChars;
        }

        /// <summary>
        /// Gets the count of common characters from the left optionally ignoring case
        /// </summary>
        internal static unsafe int EqualStartingCharacterCount(string first, string second, bool ignoreCase) {
            if (string.IsNullOrEmpty(first) || string.IsNullOrEmpty(second)) return 0;

            int commonChars = 0;

            fixed (char* f = first)
            fixed (char* s = second) {
                char* l = f;
                char* r = s;
                char* leftEnd = l + first.Length;
                char* rightEnd = r + second.Length;

                while (l != leftEnd && r != rightEnd
                                    && (*l == *r || (ignoreCase &&
                                                     char.ToUpperInvariant((*l)) == char.ToUpperInvariant((*r))))) {
                    commonChars++;
                    l++;
                    r++;
                }
            }

            return commonChars;
        }

        /// <summary>
        /// Returns true if the path ends in a directory separator.
        /// </summary>
        internal static bool EndsInDirectorySeparator(string path)
            => path.Length > 0 && IsDirectorySeparator(path[path.Length - 1]);
    }

    /// <summary> Tests for PathNetCore.GetRelativePath </summary>
    public static class GetRelativePathTests {
        [Theory]
        [InlineData(@"C:\", @"C:\", @".")]
        [InlineData(@"C:\a", @"C:\a\", @".")]
        [InlineData(@"C:\A", @"C:\a\", @".")]
        [InlineData(@"C:\a\", @"C:\a", @".")]
        [InlineData(@"C:\", @"C:\b", @"b")]
        [InlineData(@"C:\a", @"C:\b", @"..\b")]
        [InlineData(@"C:\a", @"C:\b\", @"..\b\")]
        [InlineData(@"C:\a\b", @"C:\a", @"..")]
        [InlineData(@"C:\a\b", @"C:\a\", @"..")]
        [InlineData(@"C:\a\b\", @"C:\a", @"..")]
        [InlineData(@"C:\a\b\", @"C:\a\", @"..")]
        [InlineData(@"C:\a\b\c", @"C:\a\b", @"..")]
        [InlineData(@"C:\a\b\c", @"C:\a\b\", @"..")]
        [InlineData(@"C:\a\b\c", @"C:\a", @"..\..")]
        [InlineData(@"C:\a\b\c", @"C:\a\", @"..\..")]
        [InlineData(@"C:\a\b\c\", @"C:\a\b", @"..")]
        [InlineData(@"C:\a\b\c\", @"C:\a\b\", @"..")]
        [InlineData(@"C:\a\b\c\", @"C:\a", @"..\..")]
        [InlineData(@"C:\a\b\c\", @"C:\a\", @"..\..")]
        [InlineData(@"C:\a\", @"C:\b", @"..\b")]
        [InlineData(@"C:\a", @"C:\a\b", @"b")]
        [InlineData(@"C:\a", @"C:\A\b", @"b")]
        [InlineData(@"C:\a", @"C:\b\c", @"..\b\c")]
        [InlineData(@"C:\a\", @"C:\a\b", @"b")]
        [InlineData(@"C:\", @"D:\", @"D:\")]
        [InlineData(@"C:\", @"D:\b", @"D:\b")]
        [InlineData(@"C:\", @"D:\b\", @"D:\b\")]
        [InlineData(@"C:\a", @"D:\b", @"D:\b")]
        [InlineData(@"C:\a\", @"D:\b", @"D:\b")]
        [InlineData(@"C:\ab", @"C:\a", @"..\a")]
        [InlineData(@"C:\a", @"C:\ab", @"..\ab")]
        [InlineData(@"C:\", @"\\LOCALHOST\Share\b", @"\\LOCALHOST\Share\b")]
        [InlineData(@"\\LOCALHOST\Share\a", @"\\LOCALHOST\Share\b", @"..\b")]
        //[PlatformSpecific(TestPlatforms.Windows)]  // Tests Windows-specific paths
        public static void GetRelativePath_Windows(string relativeTo, string path, string expected) {
            string result = PathNetCore.GetRelativePath(relativeTo, path);
            Assert.Equal(expected, result);

            // Check that we get the equivalent path when the result is combined with the sources
            Assert.Equal(
                Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar),
                Path.GetFullPath(Path.Combine(Path.GetFullPath(relativeTo), result))
                    .TrimEnd(Path.DirectorySeparatorChar),
                ignoreCase: true,
                ignoreLineEndingDifferences: false,
                ignoreWhiteSpaceDifferences: false);
        }
    }
}

Wow, cảm ơn bạn rất nhiều! Điều này hoạt động hoàn hảo với .NET Framework 4.6.1. Câu trả lời này nên được nâng cấp hoặc thậm chí chấp nhận là giải pháp.
j00hi

Cảm ơn bạn! Với sửa đổi nhỏ, nó cũng hoạt động trong .NET Framework 4.5.2
Bja

3

Tôi đang sử dụng cái này:

public static class StringExtensions
{
  /// <summary>
  /// Creates a relative path from one file or folder to another.
  /// </summary>
  /// <param name="absPath">Absolute path.</param>
  /// <param name="relTo">Directory that defines the start of the relative path.</param> 
  /// <returns>The relative path from the start directory to the end path.</returns>
  public static string MakeRelativePath(this string absPath, string relTo)
  {
      string[] absParts = absPath.Split(Path.DirectorySeparatorChar);
      string[] relParts = relTo.Split(Path.DirectorySeparatorChar);

      // Get the shortest of the two paths
      int len = absParts.Length < relParts.Length
          ? absParts.Length : relParts.Length;

      // Use to determine where in the loop we exited
      int lastCommonRoot = -1;
      int index;

      // Find common root
      for (index = 0; index < len; index++)
      {
          if (absParts[index].Equals(relParts[index], StringComparison.OrdinalIgnoreCase))
              lastCommonRoot = index;
          else 
            break;
      }

      // If we didn't find a common prefix then throw
      if (lastCommonRoot == -1)
          throw new ArgumentException("The path of the two files doesn't have any common base.");

      // Build up the relative path
      var relativePath = new StringBuilder();

      // Add on the ..
      for (index = lastCommonRoot + 1; index < relParts.Length; index++)
      {
        relativePath.Append("..");
        relativePath.Append(Path.DirectorySeparatorChar);
      }

      // Add on the folders
      for (index = lastCommonRoot + 1; index < absParts.Length - 1; index++)
      {
        relativePath.Append(absParts[index]);
        relativePath.Append(Path.DirectorySeparatorChar);
      }
      relativePath.Append(absParts[absParts.Length - 1]);

      return relativePath.ToString();
  }
}

3

Sử dụng:

RelPath = AbsPath.Replace(ApplicationPath, ".")

Đối với một tập hợp hẹp các trường hợp này sẽ làm việc tuyệt vời! Tôi sẽ sử dụng này! Tôi nghĩ rằng những kẻ khác muốn một giải pháp xử lý lỗi, xử lý tình huống cạnh mục đích chung. Nhưng nếu đây là tất cả những gì bạn cần, nó chắc chắn là đơn giản!
DanO

Tôi đã kết thúc bằng cách sử dụng path.Replace(rootPath.TrimEnd('\\') + "\\", "").
Konard

2

Nếu bạn chắc chắn rằng đường dẫn tuyệt đối 2 của bạn luôn liên quan đến đường dẫn tuyệt đối, chỉ cần xóa N ký tự đầu tiên khỏi đường dẫn 2, trong đó N là độ dài của đường dẫn1.


2

Bạn muốn sử dụng CommonPathphương thức của RelativePathlớp này . Khi bạn có đường dẫn chung, chỉ cần loại bỏ nó khỏi đường dẫn bạn muốn hiển thị.

Namespace IO.Path

    Public NotInheritable Class RelativePath

        Private Declare Function PathRelativePathTo Lib "shlwapi" Alias "PathRelativePathToA" ( _
            ByVal pszPath As String, _
            ByVal pszFrom As String, _
            ByVal dwAttrFrom As Integer, _
            ByVal pszTo As String, _
            ByVal dwAttrTo As Integer) As Integer

        Private Declare Function PathCanonicalize Lib "shlwapi" Alias "PathCanonicalizeA" ( _
            ByVal pszBuf As String, _
            ByVal pszPath As String) As Integer

        Private Const FILE_ATTRIBUTE_DIRECTORY As Short = &H10S

        Private Const MAX_PATH As Short = 260

        Private _path As String
        Private _isDirectory As Boolean

#Region " Constructors "

        Public Sub New()

        End Sub

        Public Sub New(ByVal path As String)
            _path = path
        End Sub

        Public Sub New(ByVal path As String, ByVal isDirectory As Boolean)
            _path = path
            _isDirectory = isDirectory
        End Sub

#End Region

        Private Shared Function StripNulls(ByVal value As String) As String
            StripNulls = value
            If (InStr(value, vbNullChar) > 0) Then
                StripNulls = Left(value, InStr(value, vbNullChar) - 1)
            End If
        End Function

        Private Shared Function TrimCurrentDirectory(ByVal path As String) As String
            TrimCurrentDirectory = path
            If Len(path) >= 2 And Left(path, 2) = ".\" Then
                TrimCurrentDirectory = Mid(path, 3)
            End If
        End Function

        ''' <summary>
        ''' 3. conforming to general principles: conforming to accepted principles or standard practice
        ''' </summary>
        Public Shared Function Canonicalize(ByVal path As String) As String
            Dim sPath As String

            sPath = New String(Chr(0), MAX_PATH)

            If PathCanonicalize(sPath, path) = 0 Then
                Canonicalize = vbNullString
            Else
                Canonicalize = StripNulls(sPath)
            End If

        End Function

        ''' <summary>
        ''' Returns the most common path between two paths.
        ''' </summary>
        ''' <remarks>
        ''' <para>returns the path that is common between two paths</para>
        ''' <para>c:\FolderA\FolderB\FolderC</para>
        '''   c:\FolderA\FolderD\FolderE\File.Ext
        ''' 
        '''   results in:
        '''       c:\FolderA\
        ''' </remarks>
        Public Shared Function CommonPath(ByVal path1 As String, ByVal path2 As String) As String
            'returns the path that is common between two paths
            '
            '   c:\FolderA\FolderB\FolderC
            '   c:\FolderA\FolderD\FolderE\File.Ext
            '
            '   results in:
            '       c:\FolderA\

            Dim sResult As String = String.Empty
            Dim iPos1, iPos2 As Integer
            path1 = Canonicalize(path1)
            path2 = Canonicalize(path2)
            Do
                If Left(path1, iPos1) = Left(path2, iPos2) Then
                    sResult = Left(path1, iPos1)
                End If
                iPos1 = InStr(iPos1 + 1, path1, "\")
                iPos2 = InStr(iPos2 + 1, path1, "\")
            Loop While Left(path1, iPos1) = Left(path2, iPos2)

            Return sResult

        End Function

        Public Function CommonPath(ByVal path As String) As String
            Return CommonPath(_path, path)
        End Function

        Public Shared Function RelativePathTo(ByVal source As String, ByVal isSourceDirectory As Boolean, ByVal target As String, ByVal isTargetDirectory As Boolean) As String
            'DEVLIB
            '   05/23/05  1:47PM - Fixed call to PathRelativePathTo, iTargetAttribute is now passed to dwAttrTo instead of IsTargetDirectory.
            '       For Visual Basic 6.0, the fix does not change testing results,
            '           because when the Boolean IsTargetDirectory is converted to the Long dwAttrTo it happens to contain FILE_ATTRIBUTE_DIRECTORY,
            '
            Dim sRelativePath As String
            Dim iSourceAttribute, iTargetAttribute As Integer

            sRelativePath = New String(Chr(0), MAX_PATH)
            source = Canonicalize(source)
            target = Canonicalize(target)

            If isSourceDirectory Then
                iSourceAttribute = FILE_ATTRIBUTE_DIRECTORY
            End If

            If isTargetDirectory Then
                iTargetAttribute = FILE_ATTRIBUTE_DIRECTORY
            End If

            If PathRelativePathTo(sRelativePath, source, iSourceAttribute, target, iTargetAttribute) = 0 Then
                RelativePathTo = vbNullString
            Else
                RelativePathTo = TrimCurrentDirectory(StripNulls(sRelativePath))
            End If

        End Function

        Public Function RelativePath(ByVal target As String) As String
            Return RelativePathTo(_path, _isDirectory, target, False)
        End Function

    End Class

End Namespace

1

Tôi sẽ chia cả hai đường dẫn của bạn ở cấp thư mục. Từ đó, tìm điểm phân kỳ và tìm đường trở lại thư mục lắp ráp, chuẩn bị '../' mỗi khi bạn vượt qua một thư mục.

Tuy nhiên, hãy nhớ rằng một đường dẫn tuyệt đối hoạt động ở mọi nơi và thường dễ đọc hơn so với đường dẫn tương đối. Cá nhân tôi sẽ không chỉ cho người dùng một đường dẫn tương đối trừ khi nó thực sự cần thiết.


Hoàn toàn đồng ý - có nhiều trường hợp đường dẫn tương đối có thể là tên đường dẫn đầy đủ, ví dụ: gốc chung của bạn là ổ đĩa - c: \ - vì vậy bạn vẫn phải xử lý trường hợp này.
stephbu

1

Nếu bạn biết rằng toPath được chứa bởi fromPath thì bạn có thể giữ nó đơn giản. Tôi sẽ bỏ qua các khẳng định cho ngắn gọn.

public static string MakeRelativePath(string fromPath, string toPath)
{
    // use Path.GetFullPath to canonicalise the paths (deal with multiple directory seperators, etc)
    return Path.GetFullPath(toPath).Substring(Path.GetFullPath(fromPath).Length + 1);
}

2
Nếu chúng nằm trong các thư mục khác nhau thì sao? Điều này không nối thêm "..". Điều gì xảy ra nếu một trong các đường dẫn chứa ".."? Điều này sẽ trả lại mức độ sai của đường dẫn tương đối. Điều gì xảy ra nếu tệp A nằm trong "MyFolder" và tệp B nằm trong "MyLunchbox" - phương pháp này không biết về ký tự phân tách thư mục, vì vậy nó sẽ chỉ nghĩ "Hộp cơm trưa \ Tệp" là đường dẫn chính xác. Điều này thật kinh khủng.
BrainSlugs83

1

Hàm sử dụng URI trả về đường dẫn tương đối "gần như". Nó bao gồm thư mục chứa trực tiếp tệp mà đường dẫn tương đối tôi muốn nhận.

Cách đây một thời gian, tôi đã viết một hàm đơn giản trả về đường dẫn tương đối của thư mục hoặc tệp và ngay cả khi nó nằm trên một ổ đĩa khác, nó cũng bao gồm cả ký tự ổ đĩa.

Xin vui lòng xem qua:

    public static string GetRelativePath(string BasePath, string AbsolutePath)
    {
        char Separator = Path.DirectorySeparatorChar;
        if (string.IsNullOrWhiteSpace(BasePath)) BasePath = Directory.GetCurrentDirectory();
        var ReturnPath = "";
        var CommonPart = "";
        var BasePathFolders = BasePath.Split(Separator);
        var AbsolutePathFolders = AbsolutePath.Split(Separator);
        var i = 0;
        while (i < BasePathFolders.Length & i < AbsolutePathFolders.Length)
        {
            if (BasePathFolders[i].ToLower() == AbsolutePathFolders[i].ToLower())
            {
                CommonPart += BasePathFolders[i] + Separator;
            }
            else
            {
                break;
            }
            i += 1;
        }
        if (CommonPart.Length > 0)
        {
            var parents = BasePath.Substring(CommonPart.Length - 1).Split(Separator);
            foreach (var ParentDir in parents)
            {
                if (!string.IsNullOrEmpty(ParentDir))
                    ReturnPath += ".." + Separator;
            }
        }
        ReturnPath += AbsolutePath.Substring(CommonPart.Length);
        return ReturnPath;
    }

1

Nếu bạn có một hộp văn bản chỉ đọc, bạn có thể không biến nó thành nhãn và đặt AutoEllipsis = true không?

thay vào đó, có những bài đăng có mã để tự động tạo tự động: (điều này thực hiện cho lưới, bạn sẽ cần phải vượt qua i chiều rộng cho hộp văn bản. Thay vào đó, nó không hoàn toàn đúng vì nó cần nhiều hơn một chút và tôi không tìm thấy nơi tính toán không chính xác. Sẽ dễ dàng sửa đổi để loại bỏ phần đầu tiên của thư mục thay vì phần cuối nếu bạn muốn.

Private Function AddEllipsisPath(ByVal text As String, ByVal colIndex As Integer, ByVal grid As DataGridView) As String
    'Get the size with the column's width 
    Dim colWidth As Integer = grid.Columns(colIndex).Width

    'Calculate the dimensions of the text with the current font
    Dim textSize As SizeF = MeasureString(text, grid.Font)

    Dim rawText As String = text
    Dim FileNameLen As Integer = text.Length - text.LastIndexOf("\")
    Dim ReplaceWith As String = "\..."

    Do While textSize.Width > colWidth
        ' Trim to make room for the ellipsis
        Dim LastFolder As Integer = rawText.LastIndexOf("\", rawText.Length - FileNameLen - 1)

        If LastFolder < 0 Then
            Exit Do
        End If

        rawText = rawText.Substring(0, LastFolder) + ReplaceWith + rawText.Substring(rawText.Length - FileNameLen)

        If ReplaceWith.Length > 0 Then
            FileNameLen += 4
            ReplaceWith = ""
        End If
        textSize = MeasureString(rawText, grid.Font)
    Loop

    Return rawText
End Function

Private Function MeasureString(ByVal text As String, ByVal fontInfo As Font) As SizeF
    Dim size As SizeF
    Dim emSize As Single = fontInfo.Size
    If emSize = 0 Then emSize = 12

    Dim stringFont As New Font(fontInfo.Name, emSize)

    Dim bmp As New Bitmap(1000, 100)
    Dim g As Graphics = Graphics.FromImage(bmp)

    size = g.MeasureString(text, stringFont)
    g.Dispose()
    Return size
End Function

0
    public static string ToRelativePath(string filePath, string refPath)
    {
        var pathNormalized = Path.GetFullPath(filePath);

        var refNormalized = Path.GetFullPath(refPath);
        refNormalized = refNormalized.TrimEnd('\\', '/');

        if (!pathNormalized.StartsWith(refNormalized))
            throw new ArgumentException();
        var res = pathNormalized.Substring(refNormalized.Length + 1);
        return res;
    }

0

Điều này sẽ làm việc:

private string rel(string path) {
  string[] cwd  = new Regex(@"[\\]").Split(Directory.GetCurrentDirectory());
  string[] fp   = new Regex(@"[\\]").Split(path);

  int common = 0;

  for (int n = 0; n < fp.Length; n++) {
    if (n < cwd.Length && n < fp.Length && cwd[n] == fp[n]) {
      common++;
    }
  }

  if (common > 0) {
    List<string> rp = new List<string>();

    for (int n = 0; n < (cwd.Length - common); n++) {
      rp.Add("..");
    }

    for (int n = common; n < fp.Length; n++) {
      rp.Add(fp[n]);
    }

    return String.Join("/", rp.ToArray());
  } else {
    return String.Join("/", fp);
  }
}

0

Cách với Uri không hoạt động trên các hệ thống linux / macOS. Đường dẫn '/ var / www / root' không thể được chuyển đổi thành Uri. Cách phổ quát hơn - làm tất cả bằng tay.

public static string MakeRelativePath(string fromPath, string toPath, string sep = "/")
{
    var fromParts = fromPath.Split(new[] { '/', '\\'},
        StringSplitOptions.RemoveEmptyEntries);
    var toParts = toPath.Split(new[] { '/', '\\'},
        StringSplitOptions.RemoveEmptyEntries);

    var matchedParts = fromParts
        .Zip(toParts, (x, y) => string.Compare(x, y, true) == 0)
        .TakeWhile(x => x).Count();

    return string.Join("", Enumerable.Range(0, fromParts.Length - matchedParts)
        .Select(x => ".." + sep)) +
            string.Join(sep, toParts.Skip(matchedParts));
}        

PS: tôi sử dụng "/" làm giá trị mặc định của dấu phân cách thay vì Path.DirectorySeparatorChar, vì kết quả của phương pháp này được sử dụng làm uri trong ứng dụng của tôi.


0

đây là của tôi:

public static string RelativePathTo(this System.IO.DirectoryInfo @this, string to)
{
    var rgFrom = @this.FullName.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
    var rgTo = to.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
    var cSame = rgFrom.TakeWhile((p, i) => i < rgTo.Length && string.Equals(p, rgTo[i])).Count();

    return Path.Combine(
        Enumerable.Range(0, rgFrom.Length - cSame)
        .Select(_ => "..")
        .Concat(rgTo.Skip(cSame))
        .ToArray()
    );
}

0

Chơi với một cái gì đó như:

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;
    }

Và kiểm tra nó

[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

Trong ASP.NET Core 2, nếu bạn muốn đường dẫn tương đối đến, bin\Debug\netcoreapp2.2bạn có thể sử dụng kết hợp sau:

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;
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.