Wie man einen relativen Pfad vom absoluten Pfad erhält

174

In meinen Apps gibt es einen Teil, der den vom Benutzer über OpenFileDialog geladenen Dateipfad anzeigt. Es nimmt zu viel Platz ein, um den gesamten Pfad anzuzeigen, aber ich möchte nicht nur den Dateinamen anzeigen, da dieser möglicherweise nicht eindeutig ist. Daher würde ich lieber den Dateipfad relativ zum Assembly / exe-Verzeichnis anzeigen.

Zum Beispiel befindet sich die Assembly bei C:\Program Files\Dummy Folder\MyProgramund die Datei bei, die C:\Program Files\Dummy Folder\MyProgram\Data\datafile1.datich dann anzeigen möchte .\Data\datafile1.dat. Wenn die Datei in ist C:\Program Files\Dummy Folder\datafile1.dat, dann würde ich wollen ..\datafile1.dat. Befindet sich die Datei jedoch im Stammverzeichnis oder in einem Verzeichnis unter dem Stammverzeichnis, wird der vollständige Pfad angezeigt.

Welche Lösung würden Sie empfehlen? Regex?

Grundsätzlich möchte ich nützliche Dateipfadinformationen anzeigen, ohne zu viel Platz auf dem Bildschirm zu beanspruchen.

EDIT: Nur um ein bisschen mehr zu klären. Der Zweck dieser Lösung besteht darin, dem Benutzer oder mir zu helfen, zu wissen, aus welcher Datei ich zuletzt geladen habe und aus welchem ​​Verzeichnis sie ungefähr stammt. Ich verwende ein schreibgeschütztes Textfeld, um den Pfad anzuzeigen. In den meisten Fällen ist der Dateipfad viel länger als der Anzeigebereich des Textfelds. Der Pfad soll informativ sein, aber nicht wichtig genug, um mehr Platz auf dem Bildschirm zu beanspruchen.

Der Kommentar von Alex Brault war gut, ebenso Jonathan Leffler. Die von DavidK bereitgestellte Win32-Funktion hilft nur bei einem Teil des Problems, nicht bei dem gesamten, aber trotzdem danke. Die James Newton-King-Lösung werde ich später ausprobieren, wenn ich frei bin.

fehlerhaft
quelle

Antworten:

192

.NET Core 2.0 hat dies Path.GetRelativePathansonsten verwendet.

/// <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
quelle
32
Nach vielen Tests hat diese Methode am besten für mich funktioniert. Sie müssen sich daran erinnern, dass Uri einen Ordner, der nicht mit einem Pfadtrennzeichen endet, als Datei behandelt (verwenden Sie c: \ foo \ bar \ anstelle von c: \ foo \ bar, wenn bar ein Ordner ist).
VVS
6
Eine allgemeine Lösung für das Problem der Schrägstriche ist die Verwendungreturn relativeUri.ToString().Replace('/',Path.DirectorySeparatorChar);
Nyerguds
4
Sie sollten den so erstellten relativen Uri entfernen, um einen gültigen Pfad zu erhalten. Die .ToString () - Darstellung enthält Escape-Sequenzen, die nicht gültig und im Pfad nicht erforderlich sind.
Eamon Nerbonne
3
Folgendes hinzugefügt, nachdem arg überprüft hat, ob (fromPath.Last ()! = Path.DirectorySeparatorChar) {fromPath + = Path.DirectorySeparatorChar; } if (toPath.Last ()! = Path.DirectorySeparatorChar) {toPath + = Path.DirectorySeparatorChar; }
Simon
8
Für mich ist dies keine Rückkehr zum relativen Pfad. Denn c:\testund c:\test\abc.txtes kehrt zurück, test\abc.txtwas meiner Meinung nach nicht relativ ist. Ich würde nur erwartenabc.txt
Jürgen d
51

Ein bisschen spät zur Frage, aber ich brauchte auch nur diese Funktion. Ich stimme DavidK zu, dass Sie diese verwenden sollten , da es eine integrierte API-Funktion gibt , die dies bietet. Hier ist ein verwalteter Wrapper dafür:

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
quelle
4
Ich würde keine Ausnahme auslösen, wenn die Datei oder der Pfad nicht vorhanden sind, da dies ein völlig legaler Fall sein könnte.
VVS
2
Was würde GetPathAttributes dann zurückgeben? Es gibt kein Flag für "Datei existiert nicht", daher sehe ich keine andere Option als das Werfen, da der Anrufer sonst fehlerhafte Informationen erhält.
ctacke
2
Beachten Sie, dass PathRelativePathTo FALSE zurückgibt, wenn kein relativer Pfad erstellt werden konnte. In diesem Fall sollten Sie entweder String.Empty zurückgeben oder eine Ausnahme auslösen.
Daniel Rose
4
Ich finde es klarer: Es erlaubt Code wie bool success = PathRelativePathTo (...), den ich leichter zu verstehen finde als einen int, bei dem Sie die Dokumentation über die Bedeutung des int lesen müssen.
Daniel Rose
4
Leute ... Sie können einfach das gesamte GetPathAttribute entfernen , wissen Sie. Solange Sie absolut sicherstellen, dass die von Ihnen angegebenen Argumente Verzeichnisse sind, müssen Sie nur 0x10 angeben, und es funktioniert mit vollständig nicht vorhandenen Pfaden. In meinem Fall besteht die bevorzugte Lösung darin, einfach den vollständigen absoluten Zielpfad zurückzugeben, anstatt diese Ausnahme auszulösen.
Nyerguds
31

.NET Core 2.0 Antwort

.NET Core 2.0 verfügt über Path.GetRelativePath , das wie folgt verwendet werden kann:

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

Im obigen Beispiel ist die relativePathVariable gleich Data\datafile1.dat.

Alternative .NET-Antwort

Die Lösung von @ Dave funktioniert nicht, wenn die Dateipfade nicht mit einem Schrägstrich ( /) enden. Dies kann passieren, wenn der Pfad ein Verzeichnispfad ist. Meine Lösung behebt dieses Problem und verwendet auch die Uri.UriSchemeFileKonstante anstelle der harten Codierung "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 Antwort

Es gibt eine Windows-API namens PathRelativePathToA , mit der ein relativer Pfad gefunden werden kann. Bitte beachten Sie, dass die Datei- oder Verzeichnispfade, die Sie an die Funktion übergeben, vorhanden sein müssen, damit sie funktioniert.

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
quelle
1
Funktioniert viel mehr wie erwartet - ich empfehle dies anstelle der am höchsten bewerteten Antwort.
theMayer
Wenn Sie davon ausgehen, dass dies AltDirectorySeparatorChareine Möglichkeit ist, sollten Sie nicht auch danach AppendDirectorySeparatorCharsuchen?
Ohad Schneider
Eine Datei kann auch ohne Erweiterung sein. In den meisten Fällen ist dies zwar praktischer, Sie können diesen Fall jedoch nicht angeben. Fügen Sie möglicherweise eine Überprüfung hinzu, ob der Dateisystemeintrag vorhanden ist, und überprüfen Sie in diesem Fall, ob es sich um eine Datei oder einen Ordner handelt. Wenn es nicht existiert, bleiben Sie bei dieser Logik. Oder fügen Sie der Signatur eine Möglichkeit hinzu, um anzugeben, ob eine Datei oder ein Verzeichnis bereitgestellt wurde (z. B. 2 Boolesche Werte).
Ohad Schneider
Schließlich würde ich eine Ausnahme auslösen, wenn die Schemata unterschiedlich sind.
Ohad Schneider
25

In shlwapi.dll gibt es eine Win32 (C ++) - Funktion, die genau das tut, was Sie wollen: PathRelativePathTo()

Mir ist jedoch keine andere Möglichkeit bekannt, über .NET darauf zuzugreifen, als P / Invoke.

DavidK
quelle
3
Bei welchem ​​Teil hilft es nicht? Wenn ich den Originalbeitrag lese, sieht es für mich so aus, als ob PathRelativePathTo () das tut, was Sie wollten, aber das liegt wahrscheinlich daran, dass ich etwas falsch interpretiert habe ...
DavidK
3
Funktioniert einwandfrei. Informationen zum Einrichten von P / Invoke finden Sie unter pinvoke.net/default.aspx/shlwapi.PathRelativePathTo .
Joce
2
Danke dir! Ich suchte tatsächlich nach einer C ++ - Lösung!
NTDLS
2
Es ist erwähnenswert, dass Funktionen in shlwapi.dlljetzt veraltet sind 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
Ich habe diese Seite nicht als Hinweis darauf gelesen, dass shlwapi.dll selbst veraltet ist. Es heißt lediglich, dass die auf der Seite aufgeführten Wrapper-Funktionen von shlwapi.dll veraltet sind. PathRelativePathTo () selbst wird auf dieser Seite nicht erwähnt, und in der Hauptdokumentation für PathRelativePathTo () wird die Ablehnung nicht erwähnt, soweit ich sehen kann, ist es immer noch eine gültige Funktion zum Aufrufen.
DavidK
14

Wenn Sie .NET Core 2.0 verwenden,Path.GetRelativePath() steht Folgendes zur Verfügung:

        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 

Andernfalls empfehlen Sie für .NET Full Framework (ab Version 4.7) die Verwendung einer der anderen vorgeschlagenen Antworten.

Strahl
quelle
9

Ich habe das in der Vergangenheit benutzt.

/// <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
quelle
1
Ich werde es mir ansehen, ich muss es irgendwann ausprobieren. Danke
fehlerhaft
1
Ich würde vorschlagen, Path.GetFullPath () zu verwenden, um zwei Pfade erfolgreich mit relativen Bits zu vergleichen. Beispiel: c: \ a \ .. \ b gegen c: \ b gegen c: \ b \. \
VVS
5

Wie Alex Brault insbesondere unter Windows hervorhebt, ist der absolute Pfad (mit Laufwerksbuchstaben und allem) eindeutig und oft besser.

Sollte Ihr OpenFileDialog nicht eine normale Baumbrowser-Struktur verwenden?

Um eine Nomenklatur zu erstellen, ist RefDir das Verzeichnis, zu dem Sie den Pfad angeben möchten. Der AbsName ist der absolute Pfadname, den Sie zuordnen möchten. und der RelPath ist der resultierende relative Pfad.

Nehmen Sie die erste dieser Optionen, die übereinstimmen:

  • Wenn Sie unterschiedliche Laufwerksbuchstaben haben, gibt es keinen relativen Pfad von RefDir zu AbsName. Sie müssen den AbsName verwenden.
  • Wenn sich der AbsName in einem Unterverzeichnis von RefDir befindet oder eine Datei in RefDir ist, entfernen Sie einfach das RefDir vom Anfang von AbsName, um RelPath zu erstellen. Optional können Sie "./" (oder ". \" voranstellen, da Sie unter Windows arbeiten).
  • Suchen Sie das längste gemeinsame Präfix von RefDir und AbsName (wobei D: \ Abc \ Def und D: \ Abc \ Standardfreigabe D: \ Abc das längste gemeinsame Präfix ist; es muss eine Zuordnung von Namenskomponenten sein, keine einfache längste gemeinsame Teilzeichenfolge); Nennen wir es LCP. Entfernen Sie LCP aus AbsName und RefDir. Stellen Sie für jede in (RefDir - LCP) verbleibende Pfadkomponente ".. \" vor (AbsName - LCP), um RelPath zu erhalten.

Um die letzte Regel zu veranschaulichen (die natürlich bei weitem die komplexeste ist), beginnen Sie mit:

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

Dann

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

Während ich tippte, gab DavidK eine Antwort, die darauf hinweist, dass Sie nicht der erste sind, der diese Funktion benötigt, und dass es eine Standardfunktion gibt, um diesen Job auszuführen. Benutze es. Aber es schadet auch nicht, sich von den ersten Prinzipien durchdenken zu können.

Abgesehen davon, dass Unix-Systeme keine Laufwerksbuchstaben unterstützen (daher befindet sich immer alles im selben Stammverzeichnis, und das erste Aufzählungszeichen ist daher irrelevant), kann dieselbe Technik unter Unix verwendet werden.

Jonathan Leffler
quelle
4

Es ist ein langer Weg, aber die System.Uri-Klasse hat eine Methode namens MakeRelativeUri. Vielleicht könntest du das gebrauchen. Es ist wirklich eine Schande, dass System.IO.Path dies nicht hat.

Vilx-
quelle
4

Wie oben erwähnt, hat .NET Core 2.x die Implementierung von Path.GetRelativePath.

Der folgende Code wurde aus Quellen angepasst und funktioniert problemlos mit .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
quelle
Wow vielen Dank! Dies funktioniert perfekt mit .NET Framework 4.6.1. Diese Antwort sollte positiv bewertet oder sogar als Lösung akzeptiert werden.
J00hi
Danke dir! Mit geringfügigen Änderungen funktioniert es auch in .NET Framework 4.5.2
Bja
3

Ich benutze dies:

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
quelle
3

Verwenden:

RelPath = AbsPath.Replace(ApplicationPath, ".")
Kevin
quelle
Für eine enge Anzahl von Fällen funktioniert dies hervorragend! Ich werde das benutzen! Ich denke, diese anderen Leute wollen eine universelle Lösung für die Fehlerprüfung und die Behandlung von Randfällen. Aber wenn das alles ist, was Sie brauchen, ist es sicher einfach!
DanO
Am Ende habe ich verwendet path.Replace(rootPath.TrimEnd('\\') + "\\", "").
Konard
2

Wenn Sie sicher sind, dass Ihr absoluter Pfad 2 immer relativ zum absoluten Pfad ist, entfernen Sie einfach die ersten N Zeichen aus Pfad2, wobei N die Länge von Pfad1 ist.


quelle
2

Sie möchten die CommonPathMethode dieser RelativePathKlasse verwenden. Sobald Sie den gemeinsamen Pfad haben, entfernen Sie ihn einfach aus dem Pfad, den Sie anzeigen möchten.

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
quelle
1

Ich würde beide Pfade auf Verzeichnisebene aufteilen. Suchen Sie von dort aus den Punkt der Abweichung und arbeiten Sie sich zurück zum Assembly-Ordner, wobei Sie jedes Mal, wenn Sie ein Verzeichnis übergeben, ein '../' voranstellen.

Beachten Sie jedoch, dass ein absoluter Pfad überall funktioniert und normalerweise leichter zu lesen ist als ein relativer. Ich persönlich würde einem Benutzer keinen relativen Pfad anzeigen, es sei denn, dies wäre absolut notwendig.

3Doubloons
quelle
Stimmen Sie voll und ganz zu - es gibt viele Fälle, in denen der relative Pfad der vollständige Pfadname sein könnte, z. B. Ihr gemeinsamer Stamm war Laufwerk - c: \ -, sodass Sie diesen Fall noch behandeln müssten.
Stephbu
1

Wenn Sie wissen, dass toPath in fromPath enthalten ist, können Sie es einfach halten. Ich werde die Behauptungen der Kürze halber weglassen.

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
quelle
2
Was ist, wenn sie sich in verschiedenen Ordnern befinden? Dies fügt nicht ".." hinzu. Was ist, wenn einer der Pfade bereits ".." enthält? Dies würde die falsche Ebene des relativen Pfades zurückgeben. Was ist, wenn sich Datei A in "MyFolder" und Datei B in "MyLunchbox" befindet? Diese Methode kennt die Zeichen für Verzeichnis-Trennzeichen nicht und würde daher denken, dass "Lunchbox \ File" der richtige Pfad ist. Das ist schrecklich.
BrainSlugs83
1

Die Funktion, die den URI verwendet, hat einen "fast" relativen Pfad zurückgegeben. Es enthielt ein Verzeichnis, das direkt die Datei enthält, deren relativen Pfad ich erhalten wollte.

Vor einiger Zeit habe ich eine einfache Funktion geschrieben, die den relativen Pfad eines Ordners oder einer Datei zurückgibt, und selbst wenn sie sich auf einem anderen Laufwerk befindet, enthält sie auch den Laufwerksbuchstaben.

Bitte sehen Sie sich das an:

    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
quelle
1

Wenn Sie ein schreibgeschütztes Textfeld haben, können Sie es nicht zu einer Beschriftung machen und AutoEllipsis = true setzen?

Alternativ gibt es Beiträge mit Code zum Generieren der Autoellipse selbst: (Dies geschieht für ein Raster. Sie müssten stattdessen die Breite für das Textfeld übergeben. Es ist nicht ganz richtig, da es etwas mehr abhackt als nötig und ich bin nicht dazu gekommen, herauszufinden, wo die Berechnung falsch ist. Es wäre einfach genug, Änderungen vorzunehmen, um den ersten Teil des Verzeichnisses und nicht den letzten zu entfernen, wenn Sie dies wünschen.

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
quelle
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;
    }
user626528
quelle
0

Das sollte funktionieren:

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
quelle
0

Weg mit Uri nicht auf Linux / MacOS-Systemen funktioniert. Der Pfad '/ var / www / root' kann nicht in Uri konvertiert werden. Universeller Weg - alles von Hand machen.

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: Ich verwende "/" als Standardwert für Trennzeichen anstelle von Path.DirectorySeparatorChar, da das Ergebnis dieser Methode in meiner App als uri verwendet wird.

Alexey Makarenya
quelle
0

hier ist meins:

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
quelle
0

Spielen Sie mit so etwas wie:

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

Und teste es

[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
quelle
0

In ASP.NET Core 2, wenn Sie den relativen Pfad zu wollen , bin\Debug\netcoreapp2.2können Sie die folgende Kombination verwenden:

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
quelle