Finding a relative Path

If you haven't seen it before then the System.IO.Path class is really useful. It can quite happily concatenate paths and return the all parts of the filename that you might need.

What if lacks however is a way to subtract two paths from each other, and find the relative path.

Lets start with some unit tests. I've used mbUnit for its [RowTest], just look at how little code is actually written here.

[RowTest()]
[Row(@"c:\fred.txt", @"c:\", "fred.txt")]
[Row(@"c:\fred.txt", @"C:\", "fred.txt")]
[Row(@"c:\fred\Tom\Bill\fred.txt", @"c:\fred\Tom\Bill", "fred.txt")]
[Row(@"c:\fred\Tom\Bill\fred.txt", @"c:\fred\Tom\Bill\", "fred.txt")]
[Row(@"c:\fred\Tom\fred.txt", @"c:\fred\Tom\Bill\", @"..\fred.txt")]
[Row(@"c:\fred\fred.txt", @"c:\fred\Tom\Bill\", @"..\..\fred.txt")]
[Row(@"c:\fred.txt", @"c:\fred\Tom\Bill\", @"..\..\..\fred.txt")]
[Row(@"c:\fred\Tom\Bill\fred.txt", @"c:\fred\", @"Tom\Bill\fred.txt")]
[Row(@"c:\fred\Tom\Bill\fred.txt", @"c:\fred", @"Tom\Bill\fred.txt")]
[Row(@"c:\fred\Tom\Bill\fred.txt", @"c:\fred\Tom\", @"Bill\fred.txt")]
[Row(@"c:\fred\Tom\Bill\fred.txt", @"c:\fred\Tom", @"Bill\fred.txt")]
[Row(@"c:\Harry\Tom\Bill\fred.txt", @"c:\fred\Tom\Bill\", @"..\..\..\Harry\Tom\Bill\fred.txt")]
[Row(@"c:\Harry\Tom\Bill\fred.txt", @"c:\fred\Tom\Bill", @"..\..\..\Harry\Tom\Bill\fred.txt")]
public void RelativePaths(string path, string root, string expected)
{
    Assert.AreEqual(expected, Centivus.Net.PathExtensions.CalculateRelative(path, root));
}

In some ways feel that the last test pair demonstrate precisely what this code is attempting to correctly produce. A means of hopping out of one directory tree and into another.

What I am personally proud of in this code is the parallel loops written using enumerators. I've used enumerators and yield quite a bit recently in order to assist with some parallel processing and performance work, here was a very simple off shoot of that.

public static string CalculateRelative(string path, string root)
{
    string[] relativeParts = root.Split(
         new char[1] { Path.DirectorySeparatorChar},
         StringSplitOptions.RemoveEmptyEntries);
    string[] pathParts = Path.GetDirectoryName(path).Split(
         new char[1] { Path.DirectorySeparatorChar }, 
         StringSplitOptions.RemoveEmptyEntries);
 
    System.Collections.IEnumerator rootEnumerator = relativeParts.GetEnumerator();
    System.Collections.IEnumerator pathEnumerator = pathParts.GetEnumerator();
 
    StringBuilder result = new StringBuilder();
    string rootPart = null;
    string pathPart = null;
    bool haveNextRoot = true;
    bool haveNextPath = true;
 
    do //first is the drive or UNC root
    {
        haveNextRoot &= rootEnumerator.MoveNext();
        haveNextPath &= pathEnumerator.MoveNext();
 
        if (haveNextRoot)
            rootPart = (string)rootEnumerator.Current;
        if (haveNextPath)
            pathPart = (string) pathEnumerator.Current;
    }
    while (haveNextRoot
        && haveNextPath
        && String.Compare(rootPart, pathPart, true) == 0);       
 
    while (haveNextRoot)
    {
        result.Append("..");
        result.Append(Path.DirectorySeparatorChar);
        haveNextRoot = rootEnumerator.MoveNext();
    }
 
    while (haveNextPath)
    {
        if (String.IsNullOrEmpty((string) pathEnumerator.Current) == false)
        {
            result.Append((string)pathEnumerator.Current);
            result.Append(Path.DirectorySeparatorChar);
        }
        haveNextPath = pathEnumerator.MoveNext();
    }            
    return Path.Combine(result.ToString(), Path.GetFileName(path));
}

Add comment

  Country flag


  • Comment
  • Preview
Loading