Since .NET doesn't include an API to make relative paths, I've used Uri's MakeRelativeUri method instead. This works, but I've encountered several cases in which it doesn't due to the fact the Uri is escaped. So I've patched up that too:
public static string MakeRelativePath(string basePath, string tgtPath) {
return
Uri.UnescapeDataString(
new Uri(basePath, UriKind.Absolute)
.MakeRelativeUri(new Uri(tgtPath, UriKind.Absolute))
.ToString()
).Replace('/', Path.DirectorySeparatorChar);
}
This versions seems to work, but it leaves me feeling a little quesy: aren't there any valid local filesystem paths that this gratuitous unescaping might corrupt?
Related: How to get relative path from absolute path The answers to that question do not address the issue of unusual characters and escaping at all, and as such don't answer this question.
Instead of escaping, unescaping and replacing, you could just use the underlying algorithm used by System.Uri and the PathDifference method. Here it is, recovered via Reflector and modified for slightly better readability. It has also been modified to use backslashes for DOS-style paths instead of forward slashes for URIs, and the comparison is always case-insensitive.
static string PathDifference(string path1, string path2)
{
int c = 0; //index up to which the paths are the same
int d = -1; //index of trailing slash for the portion where the paths are the same
while (c < path1.Length && c < path2.Length)
{
if (char.ToLowerInvariant(path1[c]) != char.ToLowerInvariant(path2[c]))
{
break;
}
if (path1[c] == '\\')
{
d = c;
}
c++;
}
if (c == 0)
{
return path2;
}
if (c == path1.Length && c == path2.Length)
{
return string.Empty;
}
System.Text.StringBuilder builder = new System.Text.StringBuilder();
while (c < path1.Length)
{
if (path1[c] == '\\')
{
builder.Append(@"..\");
}
c++;
}
if (builder.Length == 0 && path2.Length - 1 == d)
{
return @".\";
}
return builder.ToString() + path2.Substring(d + 1);
}
The expectation for the inputs seems to be that if either path represents a directory, it must have a trailing backslash. Obviously, the paths must be non-null as well.
Here are some example inputs and outputs... see if it meets your needs.
Path1 Path2 Output
C:\test\path1\path2\ C:\test\ ..\..\
C:\test\path1\file C:\test\ ..\
C:\test\path1\path2\ C:\ ..\..\..\
C:\test\path1\path2\ D:\ D:\
C:\test\path1\path2\ C:\test\path1\pathA ..\pathA
C:\test\ C:\test\
C:\test\ C:\test\file file
C:\test\file C:\test\ .\
C:\test\path #1!\path2\ C:\test\ ..\..\
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With