How to get relative path from absolute path

2018-12-31 16:48发布

There's a part in my apps that displays the file path loaded by the user through OpenFileDialog. It's taking up too much space to display the whole path, but I don't want to display only the filename as it might be ambiguous. So I would prefer to show the file path relative to the assembly/exe directory.

For example, the assembly resides at "C:\Program Files\Dummy Folder\MyProgram" and the file at "C:\Program Files\Dummy Folder\MyProgram\Data\datafile1.dat" then I would like it to show ".\Data\datafile1.dat". If the file is in "C:\Program Files\Dummy Folder\datafile1.dat", then I would want "..\datafile1.dat". But if the file is at the root directory or 1 directory below root, then display the full path.

What solution would you recommend? Regex?

Basically I want to display useful file path info without taking too much screen space.

EDIT: Just to clarify a little bit more. The purpose of this solution is to help user or myself knowing which file did I loaded last and roughly from which directory was it from. I'm using a readonly textbox to display the path. Most of the time, the file path is much longer than the display space of the textbox. The path is supposed to be informative but not important enough as to take up more screen space.

Alex Brault comment was good, so is Jonathan Leffler. The Win32 function provided by DavidK only help with part of the problem, not the whole of it, but thanks anyway. As for James Newton-King solution, I'll give it a try later when I'm free.

标签: .net
22条回答
美炸的是我
2楼-- · 2018-12-31 16:57

Use:

RelPath = AbsPath.Replace(ApplicationPath, ".")
查看更多
查无此人
3楼-- · 2018-12-31 16:58
    public static string ToRelativePath(string filePath, string refPath)
    {
        var pathNormalized = Path.GetFullPath(filePath);

        var refNormalized = Path.GetFullPath(refPath);
        refNormalized = refNormalized.TrimEnd('\\', '/');

        if (!pathNormalized.StartsWith(refNormalized))
            throw new ArgumentException();
        var res = pathNormalized.Substring(refNormalized.Length + 1);
        return res;
    }
查看更多
孤独寂梦人
4楼-- · 2018-12-31 17:00

You want to use the CommonPath method of this RelativePath class. Once you have the common path, just strip it out of the path you want to display.

Namespace IO.Path

    Public NotInheritable Class RelativePath

        Private Declare Function PathRelativePathTo Lib "shlwapi" Alias "PathRelativePathToA" ( _
            ByVal pszPath As String, _
            ByVal pszFrom As String, _
            ByVal dwAttrFrom As Integer, _
            ByVal pszTo As String, _
            ByVal dwAttrTo As Integer) As Integer

        Private Declare Function PathCanonicalize Lib "shlwapi" Alias "PathCanonicalizeA" ( _
            ByVal pszBuf As String, _
            ByVal pszPath As String) As Integer

        Private Const FILE_ATTRIBUTE_DIRECTORY As Short = &H10S

        Private Const MAX_PATH As Short = 260

        Private _path As String
        Private _isDirectory As Boolean

#Region " Constructors "

        Public Sub New()

        End Sub

        Public Sub New(ByVal path As String)
            _path = path
        End Sub

        Public Sub New(ByVal path As String, ByVal isDirectory As Boolean)
            _path = path
            _isDirectory = isDirectory
        End Sub

#End Region

        Private Shared Function StripNulls(ByVal value As String) As String
            StripNulls = value
            If (InStr(value, vbNullChar) > 0) Then
                StripNulls = Left(value, InStr(value, vbNullChar) - 1)
            End If
        End Function

        Private Shared Function TrimCurrentDirectory(ByVal path As String) As String
            TrimCurrentDirectory = path
            If Len(path) >= 2 And Left(path, 2) = ".\" Then
                TrimCurrentDirectory = Mid(path, 3)
            End If
        End Function

        ''' <summary>
        ''' 3. conforming to general principles: conforming to accepted principles or standard practice
        ''' </summary>
        Public Shared Function Canonicalize(ByVal path As String) As String
            Dim sPath As String

            sPath = New String(Chr(0), MAX_PATH)

            If PathCanonicalize(sPath, path) = 0 Then
                Canonicalize = vbNullString
            Else
                Canonicalize = StripNulls(sPath)
            End If

        End Function

        ''' <summary>
        ''' Returns the most common path between two paths.
        ''' </summary>
        ''' <remarks>
        ''' <para>returns the path that is common between two paths</para>
        ''' <para>c:\FolderA\FolderB\FolderC</para>
        '''   c:\FolderA\FolderD\FolderE\File.Ext
        ''' 
        '''   results in:
        '''       c:\FolderA\
        ''' </remarks>
        Public Shared Function CommonPath(ByVal path1 As String, ByVal path2 As String) As String
            'returns the path that is common between two paths
            '
            '   c:\FolderA\FolderB\FolderC
            '   c:\FolderA\FolderD\FolderE\File.Ext
            '
            '   results in:
            '       c:\FolderA\

            Dim sResult As String = String.Empty
            Dim iPos1, iPos2 As Integer
            path1 = Canonicalize(path1)
            path2 = Canonicalize(path2)
            Do
                If Left(path1, iPos1) = Left(path2, iPos2) Then
                    sResult = Left(path1, iPos1)
                End If
                iPos1 = InStr(iPos1 + 1, path1, "\")
                iPos2 = InStr(iPos2 + 1, path1, "\")
            Loop While Left(path1, iPos1) = Left(path2, iPos2)

            Return sResult

        End Function

        Public Function CommonPath(ByVal path As String) As String
            Return CommonPath(_path, path)
        End Function

        Public Shared Function RelativePathTo(ByVal source As String, ByVal isSourceDirectory As Boolean, ByVal target As String, ByVal isTargetDirectory As Boolean) As String
            'DEVLIB
            '   05/23/05  1:47PM - Fixed call to PathRelativePathTo, iTargetAttribute is now passed to dwAttrTo instead of IsTargetDirectory.
            '       For Visual Basic 6.0, the fix does not change testing results,
            '           because when the Boolean IsTargetDirectory is converted to the Long dwAttrTo it happens to contain FILE_ATTRIBUTE_DIRECTORY,
            '
            Dim sRelativePath As String
            Dim iSourceAttribute, iTargetAttribute As Integer

            sRelativePath = New String(Chr(0), MAX_PATH)
            source = Canonicalize(source)
            target = Canonicalize(target)

            If isSourceDirectory Then
                iSourceAttribute = FILE_ATTRIBUTE_DIRECTORY
            End If

            If isTargetDirectory Then
                iTargetAttribute = FILE_ATTRIBUTE_DIRECTORY
            End If

            If PathRelativePathTo(sRelativePath, source, iSourceAttribute, target, iTargetAttribute) = 0 Then
                RelativePathTo = vbNullString
            Else
                RelativePathTo = TrimCurrentDirectory(StripNulls(sRelativePath))
            End If

        End Function

        Public Function RelativePath(ByVal target As String) As String
            Return RelativePathTo(_path, _isDirectory, target, False)
        End Function

    End Class

End Namespace
查看更多
冷夜・残月
5楼-- · 2018-12-31 17:00

here's mine:

public static string RelativePathTo(this System.IO.DirectoryInfo @this, string to)
{
    var rgFrom = @this.FullName.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
    var rgTo = to.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
    var cSame = rgFrom.TakeWhile((p, i) => i < rgTo.Length && string.Equals(p, rgTo[i])).Count();

    return Path.Combine(
        Enumerable.Range(0, rgFrom.Length - cSame)
        .Select(_ => "..")
        .Concat(rgTo.Skip(cSame))
        .ToArray()
    );
}
查看更多
残风、尘缘若梦
6楼-- · 2018-12-31 17:01

Play with something like:

private String GetRelativePath(Int32 level, String directory, out String errorMessage) {
        if (level < 0 || level > 5) {
            errorMessage = "Find some more smart input data";
            return String.Empty;
        }
        // ==========================
        while (level != 0) {
            directory = Path.GetDirectoryName(directory);
            level -= 1;
        }
        // ==========================
        errorMessage = String.Empty;
        return directory;
    }

And test it

[Test]
    public void RelativeDirectoryPathTest() {
        var relativePath =
            GetRelativePath(3, AppDomain.CurrentDomain.BaseDirectory, out var errorMessage);
        Console.WriteLine(relativePath);
        if (String.IsNullOrEmpty(errorMessage) == false) {
            Console.WriteLine(errorMessage);
            Assert.Fail("Can not find relative path");
        }
    }
查看更多
孤独总比滥情好
7楼-- · 2018-12-31 17:04

A bit late to the question, but I just needed this feature as well. I agree with DavidK that since there is a built-in API function that provides this, you should use it. Here's a managed wrapper for it:

public static string GetRelativePath(string fromPath, string toPath)
{
    int fromAttr = GetPathAttribute(fromPath);
    int toAttr = GetPathAttribute(toPath);

    StringBuilder path = new StringBuilder(260); // MAX_PATH
    if(PathRelativePathTo(
        path,
        fromPath,
        fromAttr,
        toPath,
        toAttr) == 0)
    {
        throw new ArgumentException("Paths must have a common prefix");
    }
    return path.ToString();
}

private static int GetPathAttribute(string path)
{
    DirectoryInfo di = new DirectoryInfo(path);
    if (di.Exists)
    {
        return FILE_ATTRIBUTE_DIRECTORY;
    }

    FileInfo fi = new FileInfo(path);
    if(fi.Exists)
    {
        return FILE_ATTRIBUTE_NORMAL;
    }

    throw new FileNotFoundException();
}

private const int FILE_ATTRIBUTE_DIRECTORY = 0x10;
private const int FILE_ATTRIBUTE_NORMAL = 0x80;

[DllImport("shlwapi.dll", SetLastError = true)]
private static extern int PathRelativePathTo(StringBuilder pszPath, 
    string pszFrom, int dwAttrFrom, string pszTo, int dwAttrTo);
查看更多
登录 后发表回答