Skip to content

Instantly share code, notes, and snippets.

@grumpydev
Created January 4, 2011 15:26
Show Gist options
  • Select an option

  • Save grumpydev/764902 to your computer and use it in GitHub Desktop.

Select an option

Save grumpydev/764902 to your computer and use it in GitHub Desktop.
Helper class to try to convert an absolute path to one relative to another path
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace AbsoluteToRelativePathTest
{
public static class PathHelper
{
/// <summary>
/// Attempts to convert an absolute path (with or without filename) to a path relative to another path.
///
/// e.g. GetRelativePathIfPossible(@"c:\testdir1\testdir2\", @"c:\testdir1\testdir3\test.doc") will return @"..\testdir3\test.doc"
/// </summary>
/// <param name="relativeToDirectoryPath">Absolute path to calculate the relative path from</param>
/// <param name="absolutePath">Absolute path (with or without a filename) to the file/directory</param>
/// <param name="ignoreCase">Whether to ignore case when comparing directories</param>
/// <returns>A relative path, or absolutePath if a relative coversion isn't possible.</returns>
public static string GetRelativePathIfPossible(string relativeToDirectoryPath, string absolutePath, bool ignoreCase = true)
{
if (Path.GetFileName(relativeToDirectoryPath) != String.Empty)
throw new ArgumentException("relativeToDirectoryPath must be a directory name, not a filename");
if (!Path.IsPathRooted(relativeToDirectoryPath))
throw new ArgumentException("relativeToDirectoryPath must be an absolute path!");
if (!Path.IsPathRooted(absolutePath))
throw new ArgumentException("absolutePath must be an absolute path!");
var relativeToDirectory = relativeToDirectoryPath.TrimEnd('\\');
var absolutePathDirectory = Path.GetDirectoryName(absolutePath).TrimEnd('\\');
var absoluteFileName = Path.GetFileName(absolutePath);
if (string.Equals(absolutePathDirectory, relativeToDirectory, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
return absoluteFileName;
if (Path.GetPathRoot(relativeToDirectoryPath) != Path.GetPathRoot(absolutePath))
return absolutePath;
var relativeToDirectories = relativeToDirectory.Split(Path.DirectorySeparatorChar);
var absolutePathDirectories = absolutePathDirectory.Split(Path.DirectorySeparatorChar);
var commonPathCount = GetCommonPathCount(relativeToDirectories, absolutePathDirectories, ignoreCase);
if (commonPathCount == 0)
return absolutePath;
var remainingRelativeToDirectories = relativeToDirectories.Skip(commonPathCount);
var remainingAbsolutePathDirectories = absolutePathDirectories.Skip(commonPathCount);
var pathBuilder = new StringBuilder();
BuildParentDirectories(remainingRelativeToDirectories, pathBuilder);
BuildSubDirectories(remainingAbsolutePathDirectories, pathBuilder);
pathBuilder.Append(absoluteFileName);
return pathBuilder.ToString();
}
private static int GetCommonPathCount(string[] path1Directories, string[] path2Directories, bool ignoreCase)
{
int minLength = Math.Min(path1Directories.Length, path2Directories.Length);
bool isMatching = true;
var matchingDirectoryCount = 0;
for (int index = 0; index < minLength && isMatching; index++)
{
if (String.Equals(path1Directories[index], path2Directories[index], ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
matchingDirectoryCount++;
else
isMatching = false;
}
return matchingDirectoryCount;
}
private static void BuildParentDirectories(IEnumerable<string> remainingRelativeToDirectories, StringBuilder pathBuilder)
{
for (int index = 0; index < remainingRelativeToDirectories.Count(); index++)
{
pathBuilder.Append("..");
pathBuilder.Append(Path.DirectorySeparatorChar);
}
}
private static void BuildSubDirectories(IEnumerable<string> remainingAbsolutePathDirectories, StringBuilder pathBuilder)
{
foreach (var directory in remainingAbsolutePathDirectories)
{
pathBuilder.Append(directory);
pathBuilder.Append(Path.DirectorySeparatorChar);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace AbsoluteToRelativePathTest.Tests
{
[TestClass]
public class PathHelperTests
{
[TestMethod]
public void GetRelativePathIfPossible_RelativeToRelativePath_ThrowsArgumentException()
{
try
{
var relativeTo = @"..\";
var absolutePath = @"c:\testdir\testdir2\test2.doc";
var relativePath = PathHelper.GetRelativePathIfPossible(relativeTo, absolutePath);
Assert.Fail("Should have thrown");
}
catch (ArgumentException)
{
}
}
[TestMethod]
public void GetRelativePathIfPossible_AbsolutePathRelativePath_ThrowsArgumentException()
{
try
{
var relativeTo = @"c:\testdir\testdir2\filename.doc";
var absolutePath = @"..\test.doc";
var relativePath = PathHelper.GetRelativePathIfPossible(relativeTo, absolutePath);
Assert.Fail("Should have thrown");
}
catch (ArgumentException)
{
}
}
[TestMethod]
public void GetRelativePathIfPossible_RelativeToPathWithFilename_ThrowsArgumentException()
{
try
{
var relativeTo = @"c:\testdir\testdir2\filename.doc";
var absolutePath = @"c:\testdir\testdir2\test2.doc";
var relativePath = PathHelper.GetRelativePathIfPossible(relativeTo, absolutePath);
Assert.Fail("Should have thrown");
}
catch (ArgumentException)
{
}
}
[TestMethod]
public void GetRelativePathIfPossible_SameDirectoryAndFilename_ReturnsJustFileName()
{
var relativeTo = @"c:\testdir\testdir2\";
var absolutePath = @"c:\testdir\testdir2\test2.doc";
var relativePath = PathHelper.GetRelativePathIfPossible(relativeTo, absolutePath);
Assert.AreEqual(@"test2.doc", relativePath);
}
[TestMethod]
public void GetRelativePathIfPossible_SameDirectoryNoFilename_ReturnsBlank()
{
var relativeTo = @"c:\testdir\testdIr2\";
var absolutePath = @"c:\testdir\testdir2\";
var relativePath = PathHelper.GetRelativePathIfPossible(relativeTo, absolutePath);
Assert.AreEqual(@"", relativePath);
}
[TestMethod]
public void GetRelativePathIfPossible_DifferentDrives_ReturnsAbsolute()
{
var relativeTo = @"c:\testdir\";
var absolutePath = @"d:\testdir";
var relativePath = PathHelper.GetRelativePathIfPossible(relativeTo, absolutePath);
Assert.AreEqual(absolutePath, relativePath);
}
[TestMethod]
public void GetRelativePathIfPossible_OneDirectoryUp_ReturnsDoubleDotAndFilename()
{
var relativeTo = @"c:\testdir\testdir2\";
var absolutePath = @"c:\testdir\test2.doc";
var relativePath = PathHelper.GetRelativePathIfPossible(relativeTo, absolutePath);
Assert.AreEqual(@"..\test2.doc", relativePath);
}
[TestMethod]
public void GetRelativePathIfPossible_TwoDirectoriesUp_ReturnsTwoDoubleDotAndFilename()
{
var relativeTo = @"c:\testdir\testdir2\";
var absolutePath = @"c:\test2.doc";
var relativePath = PathHelper.GetRelativePathIfPossible(relativeTo, absolutePath);
Assert.AreEqual(@"..\..\test2.doc", relativePath);
}
[TestMethod]
public void GetRelativePathIfPossible_OneDirectoryDeep_ReturnsDirectoryAndFilename()
{
var relativeTo = @"c:\testdir\testdir2\";
var absolutePath = @"c:\testdir\test2.doc";
var relativePath = PathHelper.GetRelativePathIfPossible(relativeTo, absolutePath);
Assert.AreEqual(@"..\test2.doc", relativePath);
}
[TestMethod]
public void GetRelativePathIfPossible_TwoDirectoriesUpAndOneDirectoryDeep_ReturnsTwoDoubleDotAndSubdirAndFilename()
{
var relativeTo = @"c:\testdir1\testdir2\";
var absolutePath = @"c:\testdir1\testdir3\test.doc";
var relativePath = PathHelper.GetRelativePathIfPossible(relativeTo, absolutePath);
Assert.AreEqual(@"..\testdir3\test.doc", relativePath);
}
[TestMethod]
public void GetRelativePathIfPossible_TwoDirectoriesUpAndTwoDirectoriesDeep_ReturnsTwoDoubleDotAndSubdirsAndFilename()
{
var relativeTo = @"c:\a\b\c\testdir1\testdir2\";
var absolutePath = @"c:\a\b\c\testdir1\testdir3\testdir4\test.doc";
var relativePath = PathHelper.GetRelativePathIfPossible(relativeTo, absolutePath);
Assert.AreEqual(@"..\testdir3\testdir4\test.doc", relativePath);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment