40

Are there standard functions to perform absolute <--> relative path conversion in Delphi?

For example:

  • 'Base' path is 'C:\Projects\Project1\'
  • Relative path is '..\Shared\somefile.pas'
  • Absolute path is 'C:\Projects\Shared\somefile.pas'

I am looking for something like this:

function AbsToRel(const AbsPath, BasePath: string): string;
// '..\Shared\somefile.pas' =
//   AbsToRel('C:\Projects\Shared\somefile.pas', 'C:\Projects\Project1\')  
function RelToAbs(const RelPath, BasePath: string): string;
// 'C:\Projects\Shared\somefile.pas' =
//   RelToAbs('..\Shared\somefile.pas', 'C:\Projects\Project1\')  
Jerry Dodge
  • 26,858
  • 31
  • 155
  • 327
kludg
  • 27,213
  • 5
  • 67
  • 118
  • 1
    is absolute path 'C:\Projects\Shared\somefile.pas' or 'C:\Projects\Project1\Shared\somefile.pas' ? – philnext Mar 16 '11 at 20:08
  • 2
    @philnext It's `C:\Projects\Shared\somefile.pas` - the `..` indicates *up a folder* which in turn removes the `Project1\` from the base (or root) path. – Jerry Dodge Dec 12 '11 at 13:38

10 Answers10

52

To convert to the absolute you have :

ExpandFileName

To have the relative path you have :

ExtractRelativePath

philnext
  • 3,242
  • 5
  • 39
  • 62
  • @Andreas : Yes but it needs some work to fit to the question. – philnext Mar 16 '11 at 18:44
  • 9
    ExpandFileName is no good here. It uses the working directory rather than a user specified directory. You are going to need something like Andreas's answer or my own. – David Heffernan Mar 16 '11 at 19:29
  • Although as a side point, if like me you want to resolve /data/whatever/parent/child/../ to /data/whatever/parent/child - this ExpandFileName works a treat and is cross platform too. – Steve Childs Apr 17 '15 at 14:07
39

I would use PathRelativePathTo as the first function and PathCanonicalize as the second. In the latter case, as argument you pass the string sum of the base path and the relative path.

function PathRelativePathTo(pszPath: PChar; pszFrom: PChar; dwAttrFrom: DWORD;
  pszTo: PChar; dwAtrTo: DWORD): LongBool; stdcall; external 'shlwapi.dll' name 'PathRelativePathToW';

function AbsToRel(const AbsPath, BasePath: string): string;
var
  Path: array[0..MAX_PATH-1] of char;
begin
  PathRelativePathTo(@Path[0], PChar(BasePath), FILE_ATTRIBUTE_DIRECTORY, PChar(AbsPath), 0);
  result := Path;
end;

function PathCanonicalize(lpszDst: PChar; lpszSrc: PChar): LongBool; stdcall;
  external 'shlwapi.dll' name 'PathCanonicalizeW';

function RelToAbs(const RelPath, BasePath: string): string;
var
  Dst: array[0..MAX_PATH-1] of char;
begin
  PathCanonicalize(@Dst[0], PChar(IncludeTrailingBackslash(BasePath) + RelPath));
  result := Dst;
end;


procedure TForm4.FormCreate(Sender: TObject);
begin
  ShowMessage(AbsToRel('C:\Users\Andreas Rejbrand\Desktop\file.txt', 'C:\Users\Andreas Rejbrand\Pictures'));
  ShowMessage(RelToAbs('..\Videos\movie.wma', 'C:\Users\Andreas Rejbrand\Desktop'));
end;

Of course, if you use a non-Unicode version of Delphi (that is, <= Delphi 2007), you need to use the Ansi functions (*A) instead of the Unicode functions (*W).

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384
  • 2
    No need to declare PathCanonicalize yourself, just take it out of ShLwApi – David Heffernan Mar 16 '11 at 19:23
  • @David: Yes, both of them are there, I see now. – Andreas Rejbrand Mar 16 '11 at 19:24
  • 3
    Note that RelToAbs only works if RelPath really is a relative path. If it's an absolute path then it will fail. In practical application, it's nice to have a function that is resilient to this because usually you allow users to specify paths either absolute or relative. – David Heffernan Mar 17 '11 at 21:14
13

For what it's worth, my codebase uses SysUtils.ExtractRelativePath in one direction and the following home-grown wrapper coming back:

function ExpandFileNameRelBaseDir(const FileName, BaseDir: string): string;
var
  Buffer: array [0..MAX_PATH-1] of Char;
begin
  if PathIsRelative(PChar(FileName)) then begin
    Result := IncludeTrailingBackslash(BaseDir)+FileName;
  end else begin
    Result := FileName;
  end;
  if PathCanonicalize(@Buffer[0], PChar(Result)) then begin
    Result := Buffer;
  end;
end;

You'll need to use the ShLwApi unit for PathIsRelative and PathCanonicalize.

The call to PathIsRelative means that the routine is robust to absolute paths being specified.

So, SysUtils.ExtractRelativePath can be your AbsToRel only the parameters are reversed. And my ExpandFileNameRelBaseDir will serve as your RelToAbs.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • I suppose a `TWin32PathBuffer` is a `array[0..MAX_PATH-1] of char`? – Andreas Rejbrand Mar 16 '11 at 19:35
  • 1
    @Andreas Correct and thanks. It's always fun picking out bits of code and making then standalone. – David Heffernan Mar 16 '11 at 19:37
  • I'm an upvoter, not a downvoter, and I don't see anything wrong, other than a typo in your comment: *them not then – Jerry Dodge Dec 11 '11 at 00:14
  • Perhaps @downvoter gave you -1 because you did not specify what modules(`Windows`, `ShLwApi`) are required to compile your example. The "best" answer does not specify that either. – Wodzu Nov 07 '14 at 12:50
  • And one more thing: variable `s` is declared but not used. – Wodzu Nov 07 '14 at 12:54
  • @Wodzu That would not warrant a downvote! Thanks for the unused var. – David Heffernan Nov 07 '14 at 13:00
  • Do not want to beat a dead horse here, but you often demand from folks a SSCCE, is your answer a SSCCE?;) I think it should contain an uses clause with these module names included. Just my opinion. – Wodzu Nov 07 '14 at 13:26
  • @Wodzu Answers are different. Questions describing bugs and issues need to be precise and complete. Answers have more leeway. In this case though, the ShLwAPI unit is less known. Readers may not find it. Also, we need SysUtils too. Not just Windows. – David Heffernan Nov 07 '14 at 13:37
  • I am assuming that the call to PathCanonicalize is needless if the path is not relative. If you agree, I would rather call PathCanonicalize only in the first branch of the if statement. – Emmanuel Ichbiah Nov 08 '14 at 20:59
  • @Emmanuel PathCanonicalize is always optional. Call it if you like, or not. – David Heffernan Nov 08 '14 at 21:11
  • Ok, I assumed that the input parameters are in canonical form. In this case PathCanonicalize is superfluous for an absolute path. – Emmanuel Ichbiah Nov 08 '14 at 21:23
5

I just brewed this together:

uses
  ShLwApi;

function RelToAbs(const ARelPath, ABasePath: string): string;
begin
  SetLength(Result, MAX_PATH);
  if PathCombine(@Result[1], PChar(IncludeTrailingPathDelimiter(ABasePath)), PChar(ARelPath)) = nil then
    Result := ''
  else
    SetLength(Result, StrLen(@Result[1]));
end;

Thanks to Andreas and David for calling my attention to the Shell Path Handling Functions.

Uli Gerhardt
  • 13,748
  • 1
  • 45
  • 83
3
TPath.Combine(S1, S2);

Should be available since Delphi XE.

ZzZombo
  • 1,082
  • 1
  • 11
  • 26
1

I am not too certain if this is still needed after 2+ years, but here is a way to get the Relative to Absolute (As for Absolute to Relative I would suggest philnext's ExtractRelativePath answer):

Unit: IOUtils

Parent: TPath

function GetFullPath(const BasePath: string): string;

It will return the full, absolute path for a given relative path. If the given path is already absolute, it will just return it as is.

Here is the link at Embarcadero: Get Full Path

And here is a link for Path Manipulation Routines

Community
  • 1
  • 1
1

An alternate solution for RelToAbs is simply:

ExpandFileName(IncludeTrailingPathDelimiter(BasePath) + RelPath)
1

Check if your solution will works with Relative Path To Full Path in case when you change current directory. This will works:

function PathRelativeToFull(APath : string) : string;
var
  xDir : string;
begin
  xDir := GetCurrentDir;
  try
    SetCurrentDir('C:\Projects\Project1\');
    Result := ExpandFileName(APath);
  finally
    SetCurrentDir(xDir);
  end{try..finally};
end;

function PathFullToRelative(APath : string; ABaseDir : string = '') : string;
begin
  if ABaseDir = '' then
    ABaseDir := 'C:\Projects\Project1\';
  Result := ExtractRelativePath(ABaseDir, APath);
end;
0

Another version of RelToAbs (compatible with all Delphi XE versions).

uses
  ShLwApi;

    function RelPathToAbsPath(const ARelPath, ABasePath: string): string;
    var Buff:array[0..MAX_PATH] of Char;
    begin
      if PathCombine(Buff, PChar(IncludeTrailingPathDelimiter(ABasePath)), PChar(ARelPath)) = nil then
        Result := ''
      else Result:=Buff;
    end;
SuatDmk
  • 1
  • 1
0

Combination of two standard methods Combine/GetFullPath (unit IOUtils) should give the desired result:

CombinedPath := TPath.Combine('Base path', 'Relative path');
FileName := TPath.GetFullPath(CombinedPath);