Comment obtenir un chemin relatif à partir d'un chemin absolu

174

Il y a une partie dans mes applications qui affiche le chemin du fichier chargé par l'utilisateur via OpenFileDialog. Cela prend trop de place pour afficher le chemin complet, mais je ne veux pas afficher uniquement le nom du fichier car il peut être ambigu. Je préférerais donc afficher le chemin du fichier par rapport au répertoire assembly / exe.

Par exemple, l'assemblage réside à C:\Program Files\Dummy Folder\MyProgramet le fichier à C:\Program Files\Dummy Folder\MyProgram\Data\datafile1.datalors je voudrais qu'il s'affiche .\Data\datafile1.dat. Si le fichier est dans C:\Program Files\Dummy Folder\datafile1.dat, alors je voudrais ..\datafile1.dat. Mais si le fichier se trouve dans le répertoire racine ou dans 1 répertoire sous la racine, affichez le chemin complet.

Quelle solution recommanderiez-vous? Regex?

Fondamentalement, je veux afficher des informations utiles sur le chemin du fichier sans prendre trop d'espace à l'écran.

EDIT: Juste pour clarifier un peu plus. Le but de cette solution est d'aider l'utilisateur ou moi-même à savoir de quel fichier j'ai chargé en dernier et à peu près de quel répertoire provient-il. J'utilise une zone de texte en lecture seule pour afficher le chemin. La plupart du temps, le chemin du fichier est beaucoup plus long que l'espace d'affichage de la zone de texte. Le chemin est censé être informatif mais pas assez important pour occuper plus d'espace à l'écran.

Le commentaire d'Alex Brault était bon, tout comme Jonathan Leffler. La fonction Win32 fournie par DavidK n'aide qu'une partie du problème, pas la totalité, mais merci quand même. Quant à la solution James Newton-King, je vais l'essayer plus tard quand je serai libre.

défectueux
la source

Réponses:

192

.NET Core 2.0 a Path.GetRelativePath, sinon, utilisez ceci.

/// <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
la source
32
Après de nombreux tests, cette méthode a fonctionné le mieux pour moi. Vous devez vous rappeler que Uri traite un dossier qui ne se termine pas par un séparateur de chemin comme un fichier (utilisez c: \ foo \ bar \ au lieu de c: \ foo \ bar si bar est un dossier).
VVS
6
Une solution générale au problème des barres obliques consiste à utiliserreturn relativeUri.ToString().Replace('/',Path.DirectorySeparatorChar);
Nyerguds
4
Vous devez échapper à l'URI relatif ainsi créé pour obtenir un chemin valide; la représentation .ToString () inclura des séquences d'échappement qui ne sont pas valides et ne sont pas nécessaires dans le chemin.
Eamon Nerbonne
3
ajouté ce qui suit après que arg vérifie si (fromPath.Last ()! = Path.DirectorySeparatorChar) {fromPath + = Path.DirectorySeparatorChar; } if (toPath.Last ()! = Path.DirectorySeparatorChar) {toPath + = Path.DirectorySeparatorChar; }
Simon
8
Pour moi, cela ne revient pas sur le chemin relatif. Pour c:\testet c:\test\abc.txtça revient test\abc.txtce qui n'est pas relatif à mon avis. Je m'attendrais justeabc.txt
juergen d
51

Un peu tard à la question, mais j'avais juste besoin de cette fonctionnalité aussi. Je suis d'accord avec DavidK pour dire que, comme il existe une fonction API intégrée qui fournit cela, vous devez l'utiliser. Voici un wrapper géré pour cela:

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
la source
4
Je ne lancerais pas d'exception si le fichier ou le chemin n'existe pas, car cela pourrait être un cas totalement légal.
VVS
2
Alors, que retournerait GetPathAttributes? Il n'y a pas de drapeau pour "le fichier n'existe pas" donc je ne vois aucune option viable autre que jeter, sinon l'appelant obtient des informations erronées.
ctacke le
2
Notez que PathRelativePathTo renvoie FALSE si aucun chemin relatif n'a pu être créé. Dans ce cas, vous devez renvoyer String.Empty ou lever une exception.
Daniel Rose
4
Je le trouve plus clair: il permet du code tel que bool success = PathRelativePathTo (...) que je trouve plus facile à comprendre qu'un int où vous devez lire la documentation sur ce que signifie l'int.
Daniel Rose
4
Personnes ... vous pouvez simplement supprimer l'ensemble de GetPathAttribute, vous savez. Tant que vous vous assurez absolument que les arguments que vous donnez sont des répertoires, il vous suffit de lui donner 0x10 et cela fonctionnera avec des chemins complètement inexistants. Et dans mon cas, la solution préférée est simplement de renvoyer le chemin cible absolu complet au lieu de lever cette exception.
Nyerguds le
31

Réponse .NET Core 2.0

.NET Core 2.0 a Path.GetRelativePath qui peut être utilisé comme ceci:

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

Dans l'exemple ci-dessus, la relativePathvariable est égale à Data\datafile1.dat.

Réponse alternative .NET

La solution de @ Dave ne fonctionne pas lorsque les chemins de fichiers ne se terminent pas par une barre oblique ( /), ce qui peut arriver si le chemin est un chemin de répertoire. Ma solution résout ce problème et utilise également la Uri.UriSchemeFileconstante au lieu du codage en dur"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;
}

Réponse Windows Interop

Il existe une API Windows appelée PathRelativePathToA qui peut être utilisée pour rechercher un chemin relatif. Veuillez noter que les chemins de fichier ou de répertoire que vous transmettez à la fonction doivent exister pour qu'elle fonctionne.

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
la source
1
Fonctionne beaucoup plus comme ce à quoi on pourrait s'attendre - je recommande ceci au lieu de la réponse la plus élevée.
theMayer
Vu que vous supposez que AltDirectorySeparatorCharc'est une possibilité, ne devriez-vous pas la AppendDirectorySeparatorCharvérifier aussi?
Ohad Schneider
De plus, un fichier peut être sans extension, donc bien que cela puisse être plus pratique dans la plupart des cas, cela ne vous permet pas de spécifier ce cas. Peut-être ajouter une vérification si l'entrée du système de fichiers existe, et si c'est le cas, vérifier s'il s'agit d'un fichier ou d'un dossier. S'il n'existe pas, restez dans cette logique. Ou peut-être même ajouter un moyen dans la signature pour spécifier si un fichier ou un répertoire a été fourni (par exemple 2 booléens).
Ohad Schneider
Enfin, je jetterais une exception si les schémas sont différents.
Ohad Schneider
25

Il existe une fonction Win32 (C ++) dans shlwapi.dll qui fait exactement ce que vous voulez: PathRelativePathTo()

Je ne connais aucun moyen d'accéder à cela à partir de .NET autre que P / Invoke, cependant.

DavidK
la source
3
Avec quelle partie cela n'aide-t-il pas? En lisant le message original, il me semble que PathRelativePathTo () fait ce que vous vouliez, mais c'est probablement parce que j'ai mal interprété quelque chose ...
DavidK
3
Fonctionne parfaitement. Voir pinvoke.net/default.aspx/shlwapi.PathRelativePathTo pour savoir comment configurer le P / Invoke.
joce
2
Je vous remercie! Je cherchais en fait une solution C ++!
NTDLS
2
Il convient de noter que les fonctions de shlwapi.dllsont désormais obsolètes 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
Je n'ai pas lu cette page comme indiquant que shlwapi.dll lui-même est obsolète: tout ce qu'il dit, c'est que les fonctions wrapper de shlwapi.dll répertoriées sur la page sont obsolètes. PathRelativePathTo () lui-même n'est pas mentionné sur cette page, et la documentation principale de PathRelativePathTo () ne fait aucune mention de la dépréciation, donc pour autant que je puisse le voir, c'est toujours une fonction valide à appeler.
DavidK
14

Si vous utilisez .NET Core 2.0,Path.GetRelativePath() est disponible en fournissant cette fonctionnalité spécifique:

        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 

Sinon, pour le framework complet .NET (à partir de la v4.7), il est recommandé d'utiliser l'une des autres réponses suggérées.

Rayon
la source
9

J'ai utilisé cela dans le passé.

/// <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
la source
1
Je vais l'examiner, j'ai besoin d'un certain temps pour le tester. Merci
défectueux
1
Je suggérerais d'utiliser Path.GetFullPath () pour comparer avec succès deux chemins avec des bits relatifs. Exemple: c: \ a \ .. \ b contre c: \ b contre c: \ b \. \
VVS
5

Comme le souligne Alex Brault, en particulier sous Windows, le chemin absolu (avec lettre de lecteur et tout) est sans ambiguïté et souvent meilleur.

Votre OpenFileDialog ne devrait-il pas utiliser une structure arborescente régulière?

Pour mettre en place une nomenclature, le RefDir est le répertoire par rapport auquel vous souhaitez spécifier le chemin; le AbsName est le nom du chemin absolu que vous souhaitez mapper; et le RelPath est le chemin relatif résultant.

Prenez la première de ces options qui correspond:

  • Si vous avez des lettres de lecteur différentes, il n'y a pas de chemin relatif entre RefDir et AbsName; vous devez utiliser le AbsName.
  • Si AbsName se trouve dans un sous-répertoire de RefDir ou est un fichier dans RefDir, supprimez simplement RefDir du début d'AbsName pour créer RelPath; ajoutez éventuellement "./" (ou ". \" puisque vous êtes sous Windows).
  • Trouvez le plus long préfixe commun de RefDir et AbsName (où D: \ Abc \ Def et D: \ Abc \ Default share D: \ Abc comme le plus long préfixe commun; il doit s'agir d'un mappage de composants de nom, pas d'un simple plus long commun sous-chaîne); appelez-le LCP. Supprimez LCP de AbsName et RefDir. Pour chaque composant de chemin laissé dans (RefDir - LCP), ajoutez «.. \» à (AbsName - LCP) pour obtenir RelPath.

Pour illustrer la dernière règle (qui est, bien entendu, de loin la plus complexe), commencez par:

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

ensuite

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

Pendant que je tapais, DavidK a produit une réponse qui suggère que vous n'êtes pas le premier à avoir besoin de cette fonctionnalité et qu'il existe une fonction standard pour faire ce travail. Utilise le. Mais il n'y a pas non plus de mal à pouvoir réfléchir à partir des premiers principes.

Sauf que les systèmes Unix ne prennent pas en charge les lettres de lecteur (donc tout est toujours situé sous le même répertoire racine, et la première puce n'est donc pas pertinente), la même technique pourrait être utilisée sous Unix.

Jonathan Leffler
la source
4

C'est un long chemin, mais la classe System.Uri a une méthode nommée MakeRelativeUri. Vous pourriez peut-être l'utiliser. C'est vraiment dommage que System.IO.Path ne l'ait pas.

Vilx-
la source
4

Comme indiqué ci-dessus .NET Core 2.x a une implémentation de Path.GetRelativePath.

Le code ci-dessous est adapté des sources et fonctionne correctement avec .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
la source
Woah, merci beaucoup! Cela fonctionne parfaitement avec .NET Framework 4.6.1. Cette réponse doit être votée ou même acceptée comme solution.
j00hi
Je vous remercie! Avec des modifications mineures, il fonctionne également dans .NET Framework 4.5.2
Bja
3

J'utilise ceci:

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
la source
3

Utilisation:

RelPath = AbsPath.Replace(ApplicationPath, ".")
Kevin
la source
pour un ensemble étroit de boîtiers, cela fonctionnera à merveille! Je vais utiliser ça! Je pense que ces autres gars veulent une solution générale de vérification des erreurs et de gestion des cas de périphérie. mais si c'est tout ce dont vous avez besoin, c'est simple!
DanO
J'ai fini par utiliser path.Replace(rootPath.TrimEnd('\\') + "\\", "").
Konard
2

Si vous êtes sûr que votre chemin absolu 2 est toujours relatif au chemin absolu, supprimez simplement les N premiers caractères de chemin2, où N est la longueur de chemin1.


la source
2

Vous souhaitez utiliser la CommonPathméthode de cette RelativePathclasse. Une fois que vous avez le chemin commun, supprimez-le simplement du chemin que vous souhaitez afficher.

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
la source
1

Je diviserais vos deux chemins au niveau du répertoire. À partir de là, trouvez le point de divergence et revenez au dossier d'assemblage, en ajoutant un «../» à chaque fois que vous passez un répertoire.

Gardez toutefois à l'esprit qu'un chemin absolu fonctionne partout et est généralement plus facile à lire qu'un chemin relatif. Personnellement, je ne montrerais pas à un utilisateur un chemin relatif à moins que cela ne soit absolument nécessaire.

3Doublons
la source
Tout à fait d'accord - il existe de nombreux cas où le chemin relatif pourrait être le chemin complet, par exemple votre racine commune était le lecteur - c: \ - donc vous devrez toujours gérer ce cas.
stephbu
1

Si vous savez que toPath est contenu par fromPath, vous pouvez rester simple. Je vais laisser de côté les affirmations par souci de concision.

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
la source
2
Et s'ils se trouvent dans des dossiers différents? Cela n'ajoute pas "..". Que faire si l'un des chemins contient déjà ".."? Cela renverrait le mauvais niveau de chemin relatif. Que faire si le fichier A est dans "MyFolder" et le fichier B est dans "MyLunchbox" - cette méthode ne connaît pas les caractères de séparation de répertoire, donc elle penserait simplement que "Lunchbox \ File" était le chemin correct. C'est affreux.
BrainSlugs83
1

La fonction qui utilise l'URI a renvoyé un chemin relatif "presque". Il comprenait un répertoire contenant directement le fichier dont le chemin relatif que je voulais obtenir.

Il y a quelque temps, j'ai écrit une fonction simple qui renvoie le chemin relatif du dossier ou du fichier, et même si c'est sur un autre lecteur, elle inclut également la lettre du lecteur.

S'il vous plaît, jetez un oeil:

    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
la source
1

Si vous avez une zone de texte en lecture seule, ne pourriez-vous pas en faire une étiquette et définir AutoEllipsis = true?

alternativement, il y a des articles avec du code pour générer vous-même l'autoellipse: (cela le fait pour une grille, vous auriez besoin de passer la largeur de la zone de texte à la place. Ce n'est pas tout à fait correct car il pirate un peu plus que nécessaire , et je n'ai pas réussi à trouver où le calcul est incorrect. Il serait assez facile de modifier pour supprimer la première partie du répertoire plutôt que la dernière si vous le désirez.

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
CestLaGalère
la source
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
la source
0

Cela devrait fonctionner:

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);
  }
}
excanoë
la source
0

Façon avec Uri ne fonctionnait pas sur les systèmes linux / macOS. Le chemin «/ var / www / root» ne peut pas être converti en Uri. De manière plus universelle - faites tout à la main.

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: j'utilise "/" comme valeur par défaut du séparateur au lieu de Path.DirectorySeparatorChar, car le résultat de cette méthode est utilisé comme uri dans mon application.

Alexey Makarenya
la source
0

Voici la mienne:

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
la source
0

Jouez avec quelque chose comme:

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

Et testez-le

[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
la source
0

Dans ASP.NET Core 2, si vous voulez le chemin d'accès relatif, bin\Debug\netcoreapp2.2vous pouvez utiliser la combinaison suivante:

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
la source