Cómo obtener una ruta relativa a partir de una ruta absoluta
Hay una parte en mis aplicaciones que muestra la ruta del archivo cargado por el usuario a través de OpenFileDialog. Está ocupando demasiado espacio mostrar la ruta completa, pero no quiero mostrar solo el nombre del archivo, ya que podría ser ambiguo. Por lo tanto, preferiría mostrar la ruta del archivo relativa al directorio ensamblador/exe.
Por ejemplo, el ensamblaje reside en C:\Program Files\Dummy Folder\MyProgram
y el archivo en el C:\Program Files\Dummy Folder\MyProgram\Data\datafile1.dat
que me gustaría que se muestre .\Data\datafile1.dat
. Si el archivo está en formato C:\Program Files\Dummy Folder\datafile1.dat
, entonces lo querría ..\datafile1.dat
. Pero si el archivo está en el directorio raíz o 1 directorio debajo de la raíz, entonces muestre la ruta completa.
¿Qué solución recomendarías? ¿Expresión regular?
Básicamente, quiero mostrar información útil sobre la ruta del archivo sin ocupar demasiado espacio en la pantalla.
EDITAR: Sólo para aclarar un poco más. El propósito de esta solución es ayudar al usuario o a mí mismo a saber qué archivo cargué por última vez y aproximadamente de qué directorio proviene. Estoy usando un cuadro de texto de solo lectura para mostrar la ruta. La mayoría de las veces, la ruta del archivo es mucho más larga que el espacio de visualización del cuadro de texto. Se supone que la ruta es informativa pero no lo suficientemente importante como para ocupar más espacio en la pantalla.
El comentario de Alex Brault fue bueno, al igual que Jonathan Leffler. La función Win32 proporcionada por DavidK sólo ayuda con parte del problema, no con todo, pero gracias de todos modos. En cuanto a la solución de James Newton-King, la probaré más adelante, cuando esté libre.
.NET Core 2.0 tiene Path.GetRelativePath
; de lo contrario, use esto.
/// <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;
}
Respuesta de .NET Core 2.0
.NET Core 2.0 tiene Path.GetRelativePath que se puede usar así:
var relativePath = Path.GetRelativePath(
@"C:\Program Files\Dummy Folder\MyProgram",
@"C:\Program Files\Dummy Folder\MyProgram\Data\datafile1.dat");
En el ejemplo anterior, la relativePath
variable es igual a Data\datafile1.dat
.
Respuesta alternativa de .NET
La solución de @Dave no funciona cuando las rutas de los archivos no terminan con una barra diagonal ( /
), lo que puede suceder si la ruta es una ruta de directorio. Mi solución soluciona ese problema y también utiliza la Uri.UriSchemeFile
constante en lugar de la codificación física "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;
}
Respuesta de interoperabilidad de Windows
Existe una API de Windows llamada PathRelativePathToA que se puede utilizar para encontrar una ruta relativa. Tenga en cuenta que las rutas de archivo o directorio que pase a la función deben existir para que funcione.
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);
}