Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Will this method of making relative paths work?

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.

like image 287
Eamon Nerbonne Avatar asked Oct 27 '25 03:10

Eamon Nerbonne


1 Answers

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\            ..\..\
like image 87
Michael Petito Avatar answered Oct 28 '25 17:10

Michael Petito



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!