2009-10-27 10 views
18

¿Existe alguna manera confiable de calcular el inverso de Path.Combine()?Cálculo de la ruta relativa a algunas raíces: el inverso de Path.Combine

Path.Combine ("c: \ folder", "subdirectory \ something.txt") puede devolver algo como "c: \ folder \ subdirectory \ something.text". Lo que quiero es la inversa, una función donde Path.GetRelativeUrl ("c: \ folder", "c: \ folder \ subdirectory \ something.text") devolvería algo así como "" subdirectorio \ something.txt ".

Una solución es hacer comparaciones de cadenas y recortar las raíces, pero esto no funcionaría cuando la misma ruta se exprese de diferentes maneras (uso de ".." o "~ 1" en la expresión de ruta).

+0

¿Las rutas están en la misma máquina? Si no se normaliza puede ser complicado. Consulte http://stackoverflow.com/questions/684684/normalize-file-path-with-winapi/684787 –

+0

Buen punto que es un escenario difícil cuando una ruta es una unidad asignada a la red y la otra una ruta de archivo local en la misma unidad . Afortunadamente, no es un escenario con el que deba lidiar. –

+0

Rick Strahl tiene una solución que usa la clase Uri: http://west-wind.com/weblog/posts/857279.aspx –

Respuesta

17

Muy bien, en mi caso, no tengo algunos de los casos de borde más difíciles (ubicaciones de mapa de red de mezcla fullPath y relativePath, nombres de archivo súper largos). Lo que terminé haciendo fue crear la clase debajo de

public class PathUtil 
{ 
    static public string NormalizeFilepath(string filepath) 
    { 
     string result = System.IO.Path.GetFullPath(filepath).ToLowerInvariant(); 

     result = result.TrimEnd(new [] { '\\' }); 

     return result; 
    } 

    public static string GetRelativePath(string rootPath, string fullPath) 
    { 
     rootPath = NormalizeFilepath(rootPath); 
     fullPath = NormalizeFilepath(fullPath); 

     if (!fullPath.StartsWith(rootPath)) 
      throw new Exception("Could not find rootPath in fullPath when calculating relative path."); 

     return "." + fullPath.Substring(rootPath.Length); 
    } 
} 

Parece funcionar bastante bien. Al menos, pasa estas pruebas NUnit:

[TestFixture] 
public class PathUtilTest 
{ 
    [Test] 
    public void TestDifferencesInCapitolizationDontMatter() 
    { 
     string format1 = PathUtil.NormalizeFilepath("c:\\windows\\system32"); 
     string format2 = PathUtil.NormalizeFilepath("c:\\WindowS\\System32"); 

     Assert.AreEqual(format1, format2); 
    } 

    [Test] 
    public void TestDifferencesDueToBackstepsDontMatter() 
    { 
     string format1 = PathUtil.NormalizeFilepath("c:\\windows\\system32"); 
     string format2 = PathUtil.NormalizeFilepath("c:\\Program Files\\..\\Windows\\System32"); 

     Assert.AreEqual(format1, format2); 
    } 

    [Test] 
    public void TestDifferencesInFinalSlashDontMatter() 
    { 
     string format1 = PathUtil.NormalizeFilepath("c:\\windows\\system32"); 
     string format2 = PathUtil.NormalizeFilepath("c:\\windows\\system32\\"); 

     Console.WriteLine(format1); 
     Console.WriteLine(format2); 

     Assert.AreEqual(format1, format2); 
    } 

    [Test] 
    public void TestCanCalculateRelativePath() 
    { 
     string rootPath = "c:\\windows"; 
     string fullPath = "c:\\windows\\system32\\wininet.dll"; 
     string expectedResult = ".\\system32\\wininet.dll"; 

     string result = PathUtil.GetRelativePath(rootPath, fullPath); 

     Assert.AreEqual(expectedResult, result); 
    } 

    [Test] 
    public void TestThrowsExceptionIfRootDoesntMatchFullPath() 
    { 
     string rootPath = "c:\\windows"; 
     string fullPath = "c:\\program files\\Internet Explorer\\iexplore.exe"; 

     try 
     { 
      PathUtil.GetRelativePath(rootPath, fullPath); 
     } 
     catch (Exception) 
     { 
      return; 
     } 

     Assert.Fail("Exception expected"); 
    } 
} 

Los casos de prueba se basan en ciertos archivos existentes .. estos archivos son comunes en la mayoría de Windows instala pero su experiencia puede variar.

0

Probar ruta .GetFullPath primero, y luego. comparación de cadenas

5

he intentado encontrar una manera de hacer esto con rutas de archivos largo, pero yo no estoy consiguiendo resultados satisfactorios porque se pierde canónicos de caminos en Win32 cuando utiliza las versiones de ruta larga de el sistema de archivos estándar llama. Así que esto la solución no funciona necesariamente con elementos de más de 260 caracteres, pero de lo contrario es un código administrado y una muerte cerebral simple.

string path1 = @"c:\folder\subdirectory\something.text"; 
string path2 = @"c:\folder\foo\..\something.text"; 
Uri value = new Uri(path1); 
Uri value2 = new Uri(path2); 
Uri result = value.MakeRelativeUri(value2); 
Console.WriteLine(result.OriginalString); 

cual da

../something.text 

Ahora los nombres 8.3 (nombres cortos) para caminos es un asunto diferente. Entiendo que esas rutas se almacenan en el sistema de archivos y que tiene que usar win32 para obtenerlas. Además, se pueden desactivar, por lo que no hay garantía de que estén allí. Para obtener la ruta larga de una ruta corta, llame a GetLongPathName en Kernel32.dll. Esto también significa que el archivo debe existir.

Si quieres hacer eso, entonces este sitio es tu amigo. GetLongPathName

+0

Oh baby, ¿a quién no le gustan los problemas de ruta? Creo que señalar la clase de Uri es la mejor respuesta de todos modos, gracias. – anhoppe

3

Lo he hecho con la siguiente función. El primer parámetro es el directorio desde donde estamos buscando, el segundo parámetro es la ruta de destino. Ambos caminos pueden ser relativos. La función no está optimizada pero cumple su función.

private string _GetRelativePath(string fromPath, string toPath) 
{ 
    string fromFull = Path.Combine(Environment.CurrentDirectory, fromPath); 
    string toFull = Path.Combine(Environment.CurrentDirectory, toPath); 

    List<string> fromParts = new List<string> 
     fromFull.Split(Path.DirectorySeparatorChar)); 
    List<string> toParts = 
     new List<string>(toFull.Split(Path.DirectorySeparatorChar)); 

    fromParts.RemoveAll(string.IsNullOrEmpty); 
    toParts.RemoveAll(string.IsNullOrEmpty); 

    // Remove all the same parts in front 
    bool areRelative = false; 
    while (fromParts.Count > 0 && toParts.Count > 0 && 
     StringComparer.OrdinalIgnoreCase.Compare(fromParts[0], toParts[0]) == 0) 
    { 
     fromParts.RemoveAt(0); 
     toParts.RemoveAt(0); 

     areRelative = true; 
    } 

    if (!areRelative) 
     return toPath; 

    // Number of remaining fromParts is number of parent dirs 
    StringBuilder ret = new StringBuilder(); 

    for (int i = 0; i < fromParts.Count; i++) 
    { 
     if (ret.Length > 0) 
       ret.Append(Path.DirectorySeparatorChar); 

     ret.Append(".."); 
    } 

    // And the remainder of toParts 
    foreach (string part in toParts) 
    { 
     if (ret.Length > 0) 
       ret.Append(Path.DirectorySeparatorChar); 

     ret.Append(part); 
    } 

    return ret.ToString(); 
}