Cara mendapatkan jalur relatif dari jalur absolut

174

Ada bagian dalam aplikasi saya yang menampilkan jalur file yang dimuat oleh pengguna melalui OpenFileDialog. Butuh terlalu banyak ruang untuk menampilkan seluruh jalur, tapi saya tidak ingin hanya menampilkan nama file karena mungkin ambigu. Jadi saya lebih suka menunjukkan path file relatif ke direktori assembly / exe.

Misalnya, rakitan berada di C:\Program Files\Dummy Folder\MyProgramdan file pada C:\Program Files\Dummy Folder\MyProgram\Data\datafile1.datsaat itu saya ingin ditampilkan .\Data\datafile1.dat. Jika file ada di C:\Program Files\Dummy Folder\datafile1.dat, maka saya ingin..\datafile1.dat . Tetapi jika file berada di direktori root atau 1 direktori di bawah root, maka tampilkan path lengkap.

Solusi apa yang akan Anda rekomendasikan? Regex?

Pada dasarnya saya ingin menampilkan info jalur file yang berguna tanpa mengambil terlalu banyak ruang layar.

EDIT: Hanya untuk memperjelas sedikit lagi. Tujuan dari solusi ini adalah untuk membantu pengguna atau saya sendiri mengetahui file mana yang saya muat terakhir dan kira-kira dari mana direktori itu berasal. Saya menggunakan kotak teks hanya baca untuk menampilkan jalan. Sebagian besar waktu, jalur file jauh lebih panjang daripada ruang tampilan kotak teks. Jalan itu seharusnya informatif tetapi tidak cukup penting untuk mengambil lebih banyak ruang layar.

Komentar Alex Brault bagus, begitu juga Jonathan Leffler. Fungsi Win32 yang disediakan oleh DavidK hanya membantu bagian dari masalah, bukan keseluruhan masalahnya, tetapi terima kasih. Sedangkan untuk solusi James Newton-King, saya akan mencobanya nanti saat saya bebas.

salah
sumber

Jawaban:

192

.NET Core 2.0 telah Path.GetRelativePath, menggunakan ini.

/// <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;
}
Ramon Smits
sumber
32
Setelah banyak pengujian, metode ini bekerja paling baik untuk saya. Anda harus ingat bahwa Uri memperlakukan folder yang tidak diakhiri dengan pemisah jalur sebagai file (gunakan c: \ foo \ bar \ alih-alih c: \ foo \ bar jika bar adalah folder).
VVS
6
Solusi umum untuk masalah garis miring adalah menggunakanreturn relativeUri.ToString().Replace('/',Path.DirectorySeparatorChar);
Nyerguds
4
Anda harus menghapus uri relatif yang dibuat untuk mendapatkan jalur yang valid; representasi .ToString () akan mencakup urutan pelarian yang tidak valid dan tidak diperlukan di jalur.
Eamon Nerbonne
3
menambahkan di bawah ini setelah arg memeriksa apakah (fromPath.Last ()! = Path.DirectorySeparatorChar) {fromPath + = Path.DirectorySeparatorChar; } if (toPath.Last ()! = Path.DirectorySeparatorChar) {toPath + = Path.DirectorySeparatorChar; }
Simon
8
Bagi saya ini tidak mengembalikan jalur relatif. Untuk c:\testdan c:\test\abc.txtmengembalikan test\abc.txtyang tidak relatif menurut saya. Saya harapkanabc.txt
juergen d
51

Agak terlambat untuk pertanyaan, tetapi saya hanya perlu fitur ini juga. Saya setuju dengan DavidK bahwa karena ada fungsi API bawaan yang menyediakan ini, Anda harus menggunakannya. Inilah pembungkus yang dikelola untuk itu:

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);
ctacke
sumber
4
Saya tidak akan memberikan pengecualian jika file atau path tidak ada karena ini bisa menjadi kasus yang sepenuhnya legal.
VVS
2
Jadi apa yang akan dikembalikan oleh GetPathAttributes? Tidak ada tanda untuk "file tidak ada" jadi saya tidak melihat opsi yang layak selain melempar, jika tidak penelepon mendapat info yang salah.
ctacke
2
Perhatikan bahwa PathRelativePathTo mengembalikan FALSE jika tidak ada jalur relatif yang bisa dibuat. Dalam hal ini Anda harus mengembalikan String.Empty atau melempar pengecualian.
Daniel Rose
4
Saya merasa lebih jelas: Ini memungkinkan kode seperti bool success = PathRelativePathTo (...) yang menurut saya lebih mudah dimengerti daripada int di mana Anda perlu membaca dokumentasi tentang apa arti int.
Daniel Rose
4
Orang-orang ... Anda bisa menghapus seluruh GetPathAttribute, Anda tahu. Selama Anda benar-benar yakin argumen yang Anda berikan adalah direktori, Anda hanya perlu memberikannya 0x10 dan itu akan bekerja dengan jalur yang sama sekali tidak ada. Dan dalam kasus saya, solusi yang lebih disukai adalah mengembalikan jalur target absolut penuh alih-alih melemparkan pengecualian itu.
Nyerguds
31

.NET Core 2.0 Jawaban

.NET Core 2.0 memiliki Path.GetRelativePath yang dapat digunakan seperti:

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

Dalam contoh di atas, relativePathvariabelnya sama dengan Data\datafile1.dat.

Alternatif .NET Answer

Solusi @ Dave tidak berfungsi ketika path file tidak diakhiri dengan karakter garis miring ( /) yang dapat terjadi jika path tersebut adalah path direktori. Solusi saya memperbaiki masalah itu dan juga menggunakan Uri.UriSchemeFilekonstanta alih-alih 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;
}

Jawaban Interop Windows

Ada Windows API yang disebut PathRelativePathToA yang dapat digunakan untuk menemukan jalur relatif. Harap perhatikan bahwa jalur file atau direktori yang Anda lewati untuk fungsi harus ada agar berfungsi.

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);
}
Muhammad Rehan Saeed
sumber
1
Berfungsi jauh lebih seperti apa yang diharapkan - saya sarankan ini alih-alih jawaban tertinggi-tervotasikan.
theMayer
Melihat seperti yang Anda asumsikan AltDirectorySeparatorCharadalah suatu kemungkinan, bukankah harus AppendDirectorySeparatorCharmemeriksanya juga?
Ohad Schneider
Selain itu, file bisa tanpa ekstensi, jadi meskipun ini mungkin lebih nyaman dalam kebanyakan kasus, itu tidak memungkinkan Anda untuk menentukan kasus itu. Mungkin menambahkan cek jika entri sistem file ada, dan jika demikian periksa apakah itu file atau folder. Jika tidak ada, tetap gunakan logika ini. Atau bahkan mungkin menambahkan beberapa cara di tanda tangan untuk menentukan apakah file atau direktori telah disediakan (mis. 2 booleans).
Ohad Schneider
Akhirnya, saya akan melemparkan pengecualian jika skema berbeda.
Ohad Schneider
25

Ada fungsi Win32 (C ++) di shlwapi.dll yang melakukan persis seperti yang Anda inginkan: PathRelativePathTo()

Saya tidak mengetahui cara apa pun untuk mengakses ini dari. NET selain ke P / Aktifkan saja.

DavidK
sumber
3
Bagian mana yang tidak membantu? Membaca posting asli sepertinya bagi saya seperti PathRelativePathTo () melakukan apa yang Anda inginkan, tapi itu mungkin karena saya salah mengartikan sesuatu ...
DavidK
3
Bekerja dengan sempurna. Lihat pinvoke.net/default.aspx/shlwapi.PathRelativePathTo tentang cara mengatur P / Invoke.
joce
2
Terima kasih! Saya sebenarnya mencari solusi C ++!
NTDLS
2
Perlu dicatat bahwa fungsi dalam shlwapi.dll sekarang sudah tidak digunakan msdn.microsoft.com/en-us/library/windows/desktop/… "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."
Basic
4
Saya tidak membaca halaman itu karena menyatakan bahwa shlwapi.dll sendiri sudah tidak digunakan lagi: yang dikatakan hanyalah fungsi wrapper dari shlwapi.dll yang tercantum di halaman sudah tidak digunakan lagi. PathRelativePathTo () sendiri tidak disebutkan pada halaman itu, dan dokumentasi utama untuk PathRelativePathTo () tidak menyebutkan penghinaan, sejauh yang saya bisa lihat itu masih merupakan fungsi yang valid untuk dipanggil.
DavidK
14

Jika Anda menggunakan .NET Core 2.0,Path.GetRelativePath() tersedia dengan menyediakan fungsionalitas khusus ini:

        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 

Jika tidak, untuk .NET framework penuh (per v4.7) merekomendasikan menggunakan salah satu jawaban yang disarankan lainnya.

sinar
sumber
9

Saya telah menggunakan ini di masa lalu.

/// <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;
}
James Newton-King
sumber
1
Saya akan memeriksanya, saya perlu waktu untuk mengujinya. Terima kasih
salah
1
Saya akan menyarankan untuk menggunakan Path.GetFullPath () untuk berhasil membandingkan dua jalur dengan bit relatif. Contoh: c: \ a \ .. \ b vs. c: \ b vs c: \ b \. \
VVS
5

Seperti yang ditunjukkan Alex Brault, terutama pada Windows, jalur absolut (dengan huruf drive dan semuanya) tidak ambigu dan seringkali lebih baik.

Bukankah seharusnya OpenFileDialog Anda menggunakan struktur tree-browser biasa?

Untuk mendapatkan beberapa nomenklatur, RefDir adalah direktori relatif yang Anda ingin tentukan path; itu AbsName adalah mutlak nama jalan yang Anda ingin peta; dan RelPath adalah jalur relatif yang dihasilkan.

Ambil yang pertama dari opsi ini yang cocok:

  • Jika Anda memiliki huruf drive yang berbeda, tidak ada jalur relatif dari RefDir ke AbsName; Anda harus menggunakan AbsName.
  • Jika AbsName berada dalam sub-direktori RefDir atau file dalam RefDir maka cukup hapus RefDir dari awal AbsName untuk membuat RelPath; opsional tambahkan "./" (atau ". \" karena Anda menggunakan Windows).
  • Temukan awalan umum terpanjang dari RefDir dan AbsName (di mana D: \ Abc \ Def dan D: \ Abc \ Default share D: \ Abc sebagai awalan umum terpanjang; harus berupa pemetaan komponen nama, bukan yang umum terpanjang sederhana substring); sebut saja LCP. Hapus LCP dari AbsName dan RefDir. Untuk setiap komponen jalur yang tersisa di (RefDir - LCP), tambahkan ".. \" ke (AbsName - LCP) untuk menghasilkan RelPath.

Untuk menggambarkan aturan terakhir (yang, tentu saja, sejauh ini paling kompleks), mulailah dengan:

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

Kemudian

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

Saat saya mengetik, DavidK menghasilkan jawaban yang menunjukkan bahwa Anda bukan orang pertama yang membutuhkan fitur ini dan ada fungsi standar untuk melakukan pekerjaan ini. Gunakan. Tetapi tidak ada salahnya untuk bisa memikirkan jalan Anda dari prinsip pertama, baik.

Kecuali bahwa sistem Unix tidak mendukung huruf drive (jadi semuanya selalu terletak di bawah direktori root yang sama, dan karena itu peluru pertama tidak relevan), teknik yang sama dapat digunakan pada Unix.

Jonathan Leffler
sumber
4

Masih jauh, tapi kelas System.Uri memiliki metode bernama MakeRelativeUri. Mungkin Anda bisa menggunakannya. Sungguh memalukan bahwa System.IO.Path tidak memiliki ini.

Vilx-
sumber
4

Seperti yang ditunjukkan di atas . NET Core 2.x memiliki implementasi Path.GetRelativePath.

Kode di bawah ini diadaptasi dari sumber dan berfungsi baik dengan .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);
        }
    }
}
Anton Krouglov
sumber
Wow, terima kasih banyak! Ini berfungsi sempurna dengan .NET Framework 4.6.1. Jawaban ini harus dibalik atau bahkan diterima sebagai solusi.
j00hi
Terima kasih! Dengan modifikasi kecil ini juga berfungsi di .NET Framework 4.5.2
Bja
3

Saya menggunakan ini:

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();
  }
}
Maxence
sumber
3

Menggunakan:

RelPath = AbsPath.Replace(ApplicationPath, ".")
Kevin
sumber
untuk serangkaian kasus yang sempit ini akan bekerja dengan sangat baik! Saya akan menggunakan ini! Saya pikir orang-orang ini menginginkan solusi umum pemeriksaan kesalahan, penanganan tepi-kasus. tetapi jika ini yang Anda butuhkan, itu pasti sederhana!
DanO
Saya akhirnya menggunakan path.Replace(rootPath.TrimEnd('\\') + "\\", "").
Konard
2

Jika Anda yakin bahwa path absolut 2 Anda selalu relatif terhadap path absolut, cukup hapus karakter N pertama dari path2, di mana N adalah panjang path1.


sumber
2

Anda ingin menggunakan CommonPathmetode RelativePathkelas ini . Setelah Anda memiliki jalur yang sama, cukup lepaskan dari jalur yang ingin Anda tampilkan.

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
AMissico
sumber
1

Saya akan membagi kedua jalur Anda di tingkat direktori. Dari sana, cari titik divergensi dan kembalilah ke folder assembly, tambahkan sebuah '../' setiap kali Anda melewati direktori.

Namun perlu diingat, bahwa jalur absolut bekerja di mana-mana dan biasanya lebih mudah dibaca daripada yang relatif. Saya pribadi tidak akan menunjukkan kepada pengguna jalur relatif kecuali jika itu mutlak diperlukan.

3 keraguan
sumber
Sepenuhnya setuju - ada banyak contoh di mana path relatif bisa menjadi pathname lengkap misalnya root umum Anda adalah drive - c: \ - jadi Anda masih harus menangani kasus ini.
stephbu
1

Jika Anda tahu bahwa toPath terkandung oleh fromPath maka Anda dapat membuatnya sederhana. Saya akan meninggalkan pernyataan untuk singkatnya.

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);
}
Cameron Stone
sumber
2
Bagaimana jika mereka ada di folder yang berbeda? Ini tidak menambahkan "..". Bagaimana jika salah satu jalur sudah berisi ".."? Ini akan mengembalikan tingkat jalur relatif yang salah. Bagaimana jika file A ada di "MyFolder" dan file B ada di "MyLunchbox" - metode ini tidak tahu tentang karakter pemisah direktori, jadi itu hanya akan berpikir "Lunchbox \ File" adalah jalan yang benar. Ini mengerikan.
BrainSlugs83
1

Fungsi yang menggunakan URI mengembalikan "hampir" jalur relatif. Ini termasuk direktori yang secara langsung berisi file yang relatif ingin saya dapatkan.

Beberapa waktu lalu saya menulis fungsi sederhana yang mengembalikan path relatif folder atau file, dan bahkan jika itu ada di drive lain, itu termasuk huruf drive juga.

Silakan lihat:

    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;
    }
Szybki
sumber
1

Jika Anda memiliki kotak teks hanya baca, tidak bisakah Anda menjadikannya label dan mengatur AutoEllipsis = true?

atau ada posting dengan kode untuk membuat autoellipsis sendiri: (ini melakukannya untuk kotak, Anda perlu memberikan lebar untuk kotak teks sebagai gantinya. Tidak benar karena meretas sedikit lebih dari yang diperlukan , dan saya belum sempat menemukan di mana perhitungannya salah. akan cukup mudah untuk memodifikasi untuk menghapus bagian pertama dari direktori daripada yang terakhir jika Anda inginkan.

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
CestLaGalere
sumber
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;
    }
pengguna626528
sumber
0

Ini seharusnya bekerja:

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);
  }
}
excanoe
sumber
0

Cara dengan Uri tidak bekerja pada sistem linux / macOS. Path '/ var / www / root' tidak dapat dikonversi ke Uri. Cara yang lebih universal - lakukan semuanya dengan tangan.

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: saya menggunakan "/" sebagai nilai default separator alih-alih Path.DirectorySeparatorChar, karena hasil dari metode ini digunakan sebagai uri di aplikasi saya.

Alexey Makarenya
sumber
0

ini milikku:

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()
    );
}
Spongman
sumber
0

Main dengan sesuatu seperti:

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

Dan mengujinya

[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");
        }
    }
Sergey Orlov
sumber
0

Di ASP.NET Core 2, jika Anda menginginkan jalur relatif ke bin\Debug\netcoreapp2.2Anda dapat menggunakan kombinasi berikut:

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;
    }
}
Dragos Durlut
sumber