Convert absolute path to relative path

Today I needed to convert an absolute path to a relative path based on a specified base path. E.g.

c:abc -> c:abcdfile.txt = dfile.txt
c:abc -> c:afile.txt = ….file.txt
c:abc -> c:axfile.txt = ….xfile.txt

I am surprised there is nothing in the .NET framework so I had a hunt around and converted the code from the following URL (http://www.vergentsoftware.com/blogs/ckinsman/default.aspx?date=2006-08-07) into C#….


private string RelativePath(string absolutePath, string relativeTo)
{
string[] absoluteDirectories = absolutePath.Split('\');
string[] relativeDirectories = relativeTo.Split('\');

//Get the shortest of the two paths
int length = absoluteDirectories.Length < relativeDirectories.Length ? absoluteDirectories.Length : relativeDirectories.Length;

//Use to determine where in the loop we exited
int lastCommonRoot = -1;
int index;

//Find common root
for (index = 0; index < length; index++)
if (absoluteDirectories[index] == relativeDirectories[index])
lastCommonRoot = index;
else
break;

//If we didn't find a common prefix then throw
if (lastCommonRoot == -1)
throw new ArgumentException("Paths do not have a common base");

//Build up the relative path
StringBuilder relativePath = new StringBuilder();

//Add on the ..
for (index = lastCommonRoot + 1; index < absoluteDirectories.Length; index++)
if (absoluteDirectories[index].Length > 0)
relativePath.Append("..\");

//Add on the folders
for (index = lastCommonRoot + 1; index < relativeDirectories.Length - 1; index++)
relativePath.Append(relativeDirectories[index] + "\");
relativePath.Append(relativeDirectories[relativeDirectories.Length - 1]);

return relativePath.ToString();
}

20 thoughts on “Convert absolute path to relative path

  1. I found an error when comparing directories instead of files.

    eg.: C:ab -> C:a = ..a

    It should be ..

    To correct that i made a change:

    int lastPiece = relativeDirectories.length – 1;

    if (lastPiece > lastCommonRoot) {
    relativePath.append(relativeDirectories[lastPiece]);
    }

    return relativePath.toString();

    The code is Java, but is easy to convert as you can see.

  2. I found an error when comparing directories instead of files.

    eg.: C:ab -> C:a = ..a

    It should be ..

    To correct that i made a change:

    int lastPiece = relativeDirectories.length – 1;

    if (lastPiece > lastCommonRoot) {
    relativePath.append(relativeDirectories[lastPiece]);
    }

    return relativePath.toString();

    The code is Java, but is easy to convert as you can see.

  3. Nice, Very useful,
    Need PHP Version, here it is

    function RelativePath($absolutePath, $relativeTo)
    {
    $absoluteDirectories = explode('/',$absolutePath);
    $relativeDirectories = explode('/',$relativeTo);

    //Get the shortest of the two paths
    $length = count($absoluteDirectories) < count($relativeDirectories) ? count($absoluteDirectories) : count($relativeDirectories);

    //Use to determine where in the loop we exited
    $lastCommonRoot = -1;
    $index;

    //Find common root
    for ($index = 0; $index < $length; $index++)
    if ($absoluteDirectories[$index] == $relativeDirectories[$index])
    $lastCommonRoot = $index;
    else
    break;

    //If we didn't find a common prefix then throw
    if ($lastCommonRoot == -1)
    return "Paths do not have a common base";

    //Build up the relative path
    //StringBuilder relativePath = new StringBuilder();
    $relativePath = "";

    //Add on the ..
    for ($index = $lastCommonRoot + 1; $index < count($absoluteDirectories); $index++)
    if (strlen($absoluteDirectories[$index]) > 0)
    $relativePath .= "..\";

    //Add on the folders
    for ($index = $lastCommonRoot + 1; $index < count($relativeDirectories) – 1; $index++)
    $relativePath .= $relativeDirectories[$index]."\";

    $relativePath .= $relativeDirectories[count($relativeDirectories) – 1];

    return $relativePath;
    }

  4. use PathRelativePathTo via pinvoke

    [DllImport("shlwapi.dll", CharSet=CharSet.Auto)]
    static extern bool PathRelativePathTo(
    [Out] StringBuilder pszPath,
    [In] string pszFrom,
    [In] FileAttributes dwAttrFrom,
    [In] string pszTo,
    [In] FileAttributes dwAttrTo
    );

  5. Here's my C++ version.
    ( note: contains Berndwilli fix )

    bool StringEQ( const std::string& str0, const std::string& str1 )
    {
    if ( str0.size() != str1.size() )
    return false;
    return ( strcmp( str0.c_str(), str1.c_str() ) == 0 );
    }

    // 7.3: http://www.oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html
    //
    void Tokenize(const std::string& str, std::vector& tokens, const std::string& delimiters)
    {
    // Skip delimiters at beginning.
    std::string::size_type lastPos = str.find_first_not_of(delimiters, 0);
    // Find first "non-delimiter".
    std::string::size_type pos = str.find_first_of(delimiters, lastPos);

    while (std::string::npos != pos || std::string::npos != lastPos)
    {
    // Found a token, add it to the vector.
    tokens.push_back(str.substr(lastPos, pos – lastPos));
    // Skip delimiters. Note the "not_of"
    lastPos = str.find_first_not_of(delimiters, pos);
    // Find next "non-delimiter"
    pos = str.find_first_of(delimiters, lastPos);
    }
    }

    //
    // "c:abc" + "c:axfile.txt" => "….xfile.txt"
    // http://mrpmorris.blogspot.com/2007/05/convert-absolute-path-to-relative-path.html
    //
    std::string ConvertAbsoluteToRelative( const std::string& absolutePath, const std::string& relativeTo )
    {
    std::vector< std::string > absoluteDirectories;
    std::vector< std::string > relativeDirectories;
    Tokenize( absolutePath, absoluteDirectories, "/" );
    Tokenize( relativeTo, relativeDirectories, "/" );

    // Get the shortest of the two paths
    int length = absoluteDirectories.size() < relativeDirectories.size() ? absoluteDirectories.size() : relativeDirectories.size();

    // Use to determine where in the loop we exited
    int lastCommonRoot = -1;
    int index;

    // Find common root
    for (index = 0; index < length; index++)
    if (StringEQ( absoluteDirectories[index], relativeDirectories[index]))
    lastCommonRoot = index;
    else
    break;

    // If we didn't find a common prefix then throw
    if (lastCommonRoot == -1)
    {
    assert( 0 ); // "Paths do not have a common base"
    return "";
    }

    std::string relativePath;

    // Add on the ..
    for (index = lastCommonRoot + 1; index < relativeDirectories.size(); index++)
    if (relativeDirectories[index].size() > 0)
    relativePath += "../";

    // Add on the folders
    for (index = lastCommonRoot + 1; index < absoluteDirectories.size() – 1; index++)
    {
    relativePath += absoluteDirectories[index];
    relativePath += "/";
    }

    relativePath += absoluteDirectories[absoluteDirectories.size() – 1];

    // 🙂
    return relativePath;
    }

  6. Kseda is right, Path.Combine is the best way to go if you know the path you are resolving from.

    If you just want a path relative to the current directory, use Path.GetFullPath(relativePath).

    Path.GetFullPath(".") == Directory.GetCurrentDirectory()

  7. QDir("/home/bob").relativeFilePath("/home/mary/images/img.jpg");

    will give you "../images/img.jpg"

  8. Kseda, your one liner does NOT convert absolute paths to relative ones. If you use the example given, the result of the call to Path.combine is an absolute path that not even close to the what the correct relative path is.
    Examples:
    c:abc -> c:abcdfile.txt = dfile.txt

    Pseudo code:
    using System.IO;
    Console.Write(Path.Combine("c:abcdfile.txt", "c:abc");

    Result:
    c:abc

    This is not a relative path from c:abc to c:abcdfile.txt which is dfile.txt.

  9. Kseda, thanks, you made my day 🙂 It's so funny to see people struggling to get their algorithms right when there is such a simple oneliner.

  10. Wow… Are you crazy inventing the wheels?

    string relativePath = Path.Combine(pathToRelate, absolutePath)

  11. Thanks, this was very helpful, I was about to write my own when I found this, saves me trouble.

  12. …and a Delphi version…

    function GetRelativePath(RelativeTo, AbsolutePath: String): String;

    procedure SplitDirectories(Directory: String; const Directories: TStringList);
    var
    i: Integer;
    begin
    for i := length(Directory) downto 1 do
    begin
    if (Directory[i] = '') or (i = 1) then
    begin
    Directories.Insert(0, copy(Directory, i + 1, MaxInt));
    Directory := copy(Directory, 1, i – 1);
    end;
    end;
    end;

    var
    AbsoluteDirectories,
    RelativeDirectories: TStringList;
    Len, LastCommonRoot, i: Integer;
    begin
    Result := '';

    AbsoluteDirectories := TStringList.Create;
    RelativeDirectories := TStringList.Create;
    try
    SplitDirectories(AbsolutePath, AbsoluteDirectories);
    SplitDirectories(RelativeTo, RelativeDirectories);

    //Get the shortest of the two paths
    Len := AbsoluteDirectories.Count;
    if Len < RelativeDirectories.Count then
    Len := RelativeDirectories.Count;

    //Use to determine where in the loop we exited
    LastCommonRoot := -1;

    //Find common root
    for i := 0 to Len do
    begin
    if AbsoluteDirectories[i] = RelativeDirectories[i] then
    LastCommonRoot := i
    else
    break;
    end;

    //If we didn't find a common prefix then throw exception
    if lastCommonRoot = -1 then
    raise Exception.Create('Paths do not have a common base');

    //Build up the relative path
    //Add on the ..
    for i := LastCommonRoot + 1 to pred(AbsoluteDirectories.Count) do
    if length(AbsoluteDirectories[i]) > 0 then
    Result := concat(Result, '..');

    //Add on the folders
    for i := LastCommonRoot + 1 to pred(RelativeDirectories.Count) do
    Result := concat(Result, RelativeDirectories[i], '');
    finally
    // Free the stringlists
    FreeAndNil(AbsoluteDirectories);
    FreeAndNil(RelativeDirectories);
    end;
    end;

  13. I changed the following lines and it works for me like expected:

    //Add on the ..
    for (index = lastCommonRoot + 1; index < relativeDirectories.Length; index++)
    if (relativeDirectories[index].Length > 0)
    relativePath.Append("..\");

    //Add on the folders
    for (index = lastCommonRoot + 1; index < absoluteDirectories.Length – 1; index++)
    relativePath.Append(absoluteDirectories[index] + "\");

    relativePath.Append(absoluteDirectories[absoluteDirectories.Length – 1]);

  14. Great! Thank you! Here is the Qt version:

    QString RelativePath( QString absolutePath, QString relativeTo, bool bIsFile /*= false*/ )
    {
    QStringList absoluteDirectories = absolutePath.split( '/', QString::SkipEmptyParts );
    QStringList relativeDirectories = relativeTo.split( '/', QString::SkipEmptyParts );

    //Get the shortest of the two paths
    int length = absoluteDirectories.count() < relativeDirectories.count() ? absoluteDirectories.count() : relativeDirectories.count();

    //Use to determine where in the loop we exited
    int lastCommonRoot = -1;
    int index;

    //Find common root
    for (index = 0; index < length; index++)
    if (absoluteDirectories[index] == relativeDirectories[index])
    lastCommonRoot = index;
    else
    break;

    //If we didn't find a common prefix then throw
    if (lastCommonRoot == -1)
    throw QString("Paths do not have a common base");

    //Build up the relative path
    QString relativePath;

    //Add on the ..
    for (index = lastCommonRoot + 1; index < absoluteDirectories.count() – (bIsFile?1:0); index++)
    if (absoluteDirectories[index].length() > 0)
    relativePath.append("../");

    //Add on the folders
    for (index = lastCommonRoot + 1; index < relativeDirectories.count() – 1; index++)
    relativePath.append( relativeDirectories[index] ).append( "/" );
    relativePath.append(relativeDirectories[relativeDirectories.count() – 1]);

    return relativePath;
    }

  15. Cool!!!! Here's the java version, thanks! 😉

    public static String convertToRelativePath(String absolutePath, String relativeTo) {
    StringBuilder relativePath = null;

    // Thanks to:
    // http://mrpmorris.blogspot.com/2007/05/convert-absolute-path-to-relative-path.html
    absolutePath = absolutePath.replaceAll("\\", "/");
    relativeTo = relativeTo.replaceAll("\\", "/");

    if (absolutePath.equals(relativeTo) == true) {

    } else {
    String[] absoluteDirectories = absolutePath.split("/");
    String[] relativeDirectories = relativeTo.split("/");

    //Get the shortest of the two paths
    int length = absoluteDirectories.length < relativeDirectories.length ?
    absoluteDirectories.length : relativeDirectories.length;

    //Use to determine where in the loop we exited
    int lastCommonRoot = -1;
    int index;

    //Find common root
    for (index = 0; index < length; index++) {
    if (absoluteDirectories[index].equals(relativeDirectories[index])) {
    lastCommonRoot = index;
    } else {
    break;
    //If we didn't find a common prefix then throw
    }
    }
    if (lastCommonRoot != -1) {
    //Build up the relative path
    relativePath = new StringBuilder();
    //Add on the ..
    for (index = lastCommonRoot + 1; index < absoluteDirectories.length; index++) {
    if (absoluteDirectories[index].length() > 0) {
    relativePath.append("../");
    }
    }
    for (index = lastCommonRoot + 1; index < relativeDirectories.length – 1; index++) {
    relativePath.append(relativeDirectories[index] + "/");
    }
    relativePath.append(relativeDirectories[relativeDirectories.length – 1]);
    }
    }
    return relativePath == null ? null : relativePath.toString();
    }

  16. Your relative path and absolute path parameters are backwards, but other than that a great help. Thanks!

Leave a Reply

Your email address will not be published.