Будет ли работать этот метод создания относительных путей?

Поскольку .NET не включает API для создания относительных путей, вместо этого я использовал метод Uri MakeRelativeUri. Это работает, но я столкнулся с несколькими случаями, когда это не происходит из-за того, что Uri экранирован. Так что я исправил и это:

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);
}

Эта версия, похоже, работает, но вызывает у меня некоторое недоумение: нет ли каких-либо действительных путей в локальной файловой системе, которые может повредить это необоснованное удаление?

Связано: Как получить относительный путь из абсолютного path Ответы на этот вопрос вообще не касаются проблемы необычных символов и экранирования и, как таковые, не отвечают на этот вопрос.


person Eamon Nerbonne    schedule 05.06.2011    source источник
comment
возможный дубликат Как получить относительный путь из абсолютного пути   -  person David Heffernan    schedule 05.06.2011
comment
@Eamon Здесь есть несколько хороших ответов: stackoverflow.com/questions/275689/ Если бы это был я, я бы выбрал решение, которое P/Invokes to PathRelativePathTo()   -  person David Heffernan    schedule 05.06.2011
comment
Да, я вижу ответ, в котором также используется Uri, но без экранирования, т.е. borked. П/Вызов; ну, это медленно и зависит от платформы, а это означает, что вы можете столкнуться с проблемами x86/x64, проблемами с неполным доверием и, конечно, с такими вещами, как silverlight/XNA, они не обязательно его поддерживают. Это сработает, но это не мой первый выбор.   -  person Eamon Nerbonne    schedule 05.06.2011
comment
@ Эймон, я вижу это. Я ориентирован на Windows, поэтому P/Invoking в порядке. Я не вижу, чтобы скорость была проблемой - вы же не собираетесь вызывать это в горячем цикле вашего приложения, не так ли?!!   -  person David Heffernan    schedule 05.06.2011
comment
Я сделал что-то подобное в индексаторе пользовательского поиска, но всего с несколькими сотнями тысяч путей это, вероятно, не большая проблема. Еще; во-первых, приятно не волноваться. Кстати, PathRelativePathTo выполняет какой-либо ввод-вывод и/или нормализацию? MSDN не говорит.   -  person Eamon Nerbonne    schedule 05.06.2011
comment
PathRelativePathTo не затрагивает файловую систему, насколько мне известно. Он основан исключительно на тексте. Если вы хотите узнать, как это реализовано, вы всегда можете посмотреть исходный код Wine!   -  person David Heffernan    schedule 05.06.2011
comment
Спасибо, мне очень нравятся такие маленькие идеи!   -  person Eamon Nerbonne    schedule 05.06.2011


Ответы (1)


Вместо экранирования, отмены экранирования и замены вы можете просто использовать базовый алгоритм, используемый System.Uri и методом PathDifference. Вот он, восстановленный через Reflector и модифицированный для лучшей читабельности. Он также был изменен для использования обратной косой черты для путей в стиле DOS вместо прямой косой черты для URI, и сравнение всегда нечувствительно к регистру.

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);
}

Предполагается, что входные данные состоят в том, что если какой-либо путь представляет собой каталог, он должен иметь завершающую обратную косую черту. Очевидно, что пути также должны быть ненулевыми.

Вот несколько примеров входных и выходных данных... посмотрите, соответствует ли это вашим потребностям.

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\            ..\..\
person Michael Petito    schedule 05.06.2011
comment
да, обратная косая черта довольно неотъемлема, если подумать — у алгоритма на самом деле нет доступа к файловой системе, верно? - person Eamon Nerbonne; 05.06.2011
comment
Мне не совсем удобно поднимать это через отражатель — я не юрист по авторским правам, и кто знает, что в порядке… - person Eamon Nerbonne; 05.06.2011
comment
Кстати, спасибо за усилия, это, безусловно, интересный подход! - person Eamon Nerbonne; 05.06.2011
comment
... и это показывает, что Uri не выполняет никакой нормализации, что здесь немного неудачно; например сравнение C:\test\path1\path2` to C:\test\\path1\pathA` не делает того, что должно. С другой стороны, C:\test\xy\..\path1\pathA работает с MakeRelativeUri выше, но не здесь... - person Eamon Nerbonne; 05.06.2011