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:\a\b\c -> c:\a\b\c\d\file.txt = d\file.txt
c:\a\b\c -> c:\a\file.txt = ..\..\file.txt
c:\a\b\c -> c:\a\x\file.txt = ..\..\x\file.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();
}

Comments

Anonymous said…
Your relative path and absolute path parameters are backwards, but other than that a great help. Thanks!
Unknown said…
Here is a C# open-source library that helps handling complex path scenario such as relative/absolute conversion, rebasing, and much more
http://www.codeplex.com/FileDirectoryPath
Anonymous said…
thanks for this code !
ciberado said…
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();
}
Anonymous said…
Very useful thanks
Anonymous said…
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;
}
Anonymous said…
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]);
Anonymous said…
...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;
Unknown said…
Thanks, this was very helpful, I was about to write my own when I found this, saves me trouble.
Kseda said…
Wow... Are you crazy inventing the wheels?

string relativePath = Path.Combine(pathToRelate, absolutePath)
Bram Smulders said…
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.
Unknown said…
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:\a\b\c -> c:\a\b\c\d\file.txt = d\file.txt

Pseudo code:
using System.IO;
Console.Write(Path.Combine("c:\a\b\c\d\file.txt", "c:\a\b\c");

Result:
c:\a\b\c

This is not a relative path from c:\a\b\c to c:\a\b\c\d\file.txt which is d\file.txt.
Unknown said…
QDir("/home/bob").relativeFilePath("/home/mary/images/img.jpg");

will give you "../images/img.jpg"
Unknown said…
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
);
Unknown said…
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:\a\b\c" + "c:\a\x\file.txt" => "..\..\x\file.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;
}
Ian said…
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()
Anonymous said…
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;
}
Niket said…
Hi can any body give me a Function signature or example of how to use function
EternaL said…
I found an error when comparing directories instead of files.

eg.: C:\a\b -> 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.
EternaL said…
I found an error when comparing directories instead of files.

eg.: C:\a\b -> 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.

Popular posts from this blog

Connascence

Printing bitmaps using CPCL