Page 1 of 2

Create a relative path

Posted: Tue Sep 21, 2010 8:36 pm
by Little John
Works also with PB 5.20

Hi,

sometimes it is useful, to know the name of a path relative to a given directory. The following code converts an absolute path to a relative path.

Regards, Little John

//edit 2010-09-22: Slightly simplified the code.

Code: Select all

; -- Convert an absolute path to a relative one.
; Tested with PB 4.51 on Windows XP and Ubuntu 10.04

CompilerSelect #PB_Compiler_OS
   CompilerCase #PB_OS_Windows
      #Slash = '\'
      #BadSlash = '/'
   CompilerDefault
      #Slash = '/'
      #BadSlash = '\'
CompilerEndSelect

Procedure.i EqualPathLen (s1$, s2$)
   ; -- return length of identical part of the paths in s1$ and s2$
   Protected maxEqual, temp, equal, ret
   Protected *p1.Character, *p2.Character
   
   CompilerIf #PB_Compiler_OS = #PB_OS_Windows
      s1$ = UCase(s1$)
      s2$ = UCase(s2$)
   CompilerEndIf
   
   maxEqual = Len(s1$)
   temp = Len(s2$)
   If maxEqual > temp
      maxEqual = temp
   EndIf
   
   *p1 = @s1$
   *p2 = @s2$
   equal = 0
   ret = 0
   While equal < maxEqual And *p1\c = *p2\c
      equal + 1
      If *p1\c = #Slash
         ret = equal
      EndIf   
      *p1 + SizeOf(character)
      *p2 + SizeOf(character)
   Wend
   
   ProcedureReturn ret
EndProcedure

Procedure.s RelativePath (baseDir$, absPath$)
   ; -- convert an absolute path to a relative one
   ; in : baseDir$: full name of a directory, with trailing (back)slash
   ;      absPath$: full name of a path to a directory or file
   ; out: absPath$ converted, so that it is relative to baseDir$
   Protected equal, s, i, parent$, ret$=""
   
   CompilerIf #PB_Compiler_OS = #PB_OS_Windows
      If UCase(Left(baseDir$, 1)) <> UCase(Left(absPath$, 1))
         ProcedureReturn absPath$  ; can't build a relative path
      EndIf
   CompilerEndIf

   ReplaceString(baseDir$, Chr(#BadSlash), Chr(#Slash), #PB_String_InPlace)
   ReplaceString(absPath$, Chr(#BadSlash), Chr(#Slash), #PB_String_InPlace)
   equal = EqualPathLen(baseDir$, absPath$)
   
   s = CountString(Mid(baseDir$, equal+1), Chr(#Slash))
   parent$ = ".." + Chr(#Slash)
   For i = 1 To s
      ret$ + parent$
   Next   
   
   ProcedureReturn ret$ + Mid(absPath$, equal+1)
EndProcedure


;-- Demo
CompilerSelect #PB_Compiler_OS
   CompilerCase #PB_OS_Windows
      Debug RelativePath("F:\Data\John\", "f:\Data\John\PC")
      Debug RelativePath("F:\Data\John\", "f:\Data\John\")
      Debug RelativePath("F:\Data\John\", "f:\Data\John")
      Debug RelativePath("F:\Data\John\", "f:\Data\Peter\")
      Debug RelativePath("F:\Data\John\", "f:\")
      Debug RelativePath("F:\", "f:\Data\John\")
      Debug RelativePath("F:\Data\John\", "g:\Videos")
   CompilerDefault
      Debug RelativePath("/media/Corsair/Data/John/", "/media/Corsair/Data/John/PC")
      Debug RelativePath("/media/Corsair/Data/John/", "/media/Corsair/Data/John/")
      Debug RelativePath("/media/Corsair/Data/John/", "/media/Corsair/Data/John")
      Debug RelativePath("/media/Corsair/Data/John/", "/media/Corsair/Data/Peter/")
      Debug RelativePath("/media/Corsair/Data/John/", "/media/Corsair/")
      Debug RelativePath("/media/Corsair/", "/media/Corsair/Data/John/")
      Debug RelativePath("/media/Corsair/Data/John/", "/media/Maxtor/Videos")
CompilerEndSelect

Re: Create a relative path

Posted: Tue Sep 21, 2010 10:28 pm
by luis
Very useful, thanks :-)

Re: Create a relative path

Posted: Wed Sep 22, 2010 1:30 am
by Flower
FYI, if you're on Windows, you can use shell function PathRelativePathTo:
http://msdn.microsoft.com/en-us/library ... S.85).aspx

Re: Create a relative path

Posted: Wed Sep 22, 2010 7:21 am
by blueznl
Duh, after looking at http://msdn.microsoft.com/en-us/library ... 85%29.aspx I realized perhaps I should now and again have a look at the MSDN...

Re: Create a relative path

Posted: Wed Sep 22, 2010 11:05 am
by kvitaliy
Flower wrote:FYI, if you're on Windows, you can use shell function PathRelativePathTo:
http://msdn.microsoft.com/en-us/library ... S.85).aspx
Examples for PureBasic

Code: Select all

szOut.s=Space(#MAX_PATH)
szFrom.s = "c:\\a\\b\\path";
szTo.s = "c:\\a\\x\\y\\file";

PathRelativePathTo_(@szOut,szFrom,#FILE_ATTRIBUTE_DIRECTORY,szTo,#FILE_ATTRIBUTE_NORMAL);
Debug szOut
; OUTPUT:
; ==================
; The relative path is relative from: c:\a\b\path
; The relative path is relative To: c:\a\x\y\file
; The relative path is: ..\..\x\y\file

Re: Create a relative path

Posted: Wed Sep 22, 2010 2:24 pm
by Little John
Hi,

thanks for your replies, and the additional information.
Flower wrote:FYI, if you're on Windows, you can use shell function PathRelativePathTo:
http://msdn.microsoft.com/en-us/library ... S.85).aspx
I knew about the WinAPI function GetFullPathName_(), and looked it up on MSDN. On that page there is a link to another page about Naming Files, Paths, and Namespaces, which even contains a section named "Fully Qualified vs. Relative Paths" -- but nowhere a word about PathRelativePathTo_() ... :x
blueznl wrote:Duh, after looking at http://msdn.microsoft.com/en-us/library ... 85%29.aspx I realized perhaps I should now and again have a look at the MSDN...
Uuh, that's really impressive. :D

Regards, Little John

Re: Create a relative path

Posted: Wed Sep 22, 2010 4:10 pm
by Flower
Little John wrote: I knew about the WinAPI function GetFullPathName_(), and looked it up on MSDN. On that page there is a link to another page about Naming Files, Paths, and Namespaces, which even contains a section named "Fully Qualified vs. Relative Paths" -- but nowhere a word about PathRelativePathTo_() ... :x
Yep, seems kernel32 documentation writer at Microsoft only knows (or intentionally ignores) shell32, user32 and other useful libraries.

Explorer.exe use shell functions heavily, but IMHO shell function is not as bullet-proof as kernel32 functions, which might be why Explorer.exe crashes so frequently :wink:

BTW, the regular shell functions are more powerful than these "Shell Lightweight Utility Functions", but usually requires a COM interface.
http://msdn.microsoft.com/en-us/library ... S.85).aspx

Re: Create a relative path

Posted: Wed Sep 22, 2010 4:36 pm
by cas
Flower wrote:Yep, seems kernel32 documentation writer at Microsoft only knows (or intentionally ignores) shell32, user32 and other useful libraries.
You mean 'writers', not 'writer' :lol: .
I don't know any function that is available for use but missing from MSDN. And those two that you quoted are in this groups:
PathRelativePathTo: http://msdn.microsoft.com/en-us/library ... S.85).aspx
GetFullPathName: http://msdn.microsoft.com/en-us/library ... S.85).aspx
Flower wrote:Explorer.exe use shell functions heavily, but IMHO shell function is not as bullet-proof as kernel32 functions
All functions listed on MSDN are bullet-proof. They can't sell something that is not working as expected and have market share more than 90% :shock: .
Flower wrote:which might be why Explorer.exe crashes so frequently :wink:
It crashes because of buggy 3rd party apps. :wink:

Re: Create a relative path

Posted: Wed Sep 22, 2010 5:52 pm
by Little John
Flower wrote:Yep, seems kernel32 documentation writer at Microsoft only knows (or intentionally ignores) shell32, user32 and other useful libraries.
Quote from a recent post on the German forum, regarding MSDN and a different topic:
Weiß bei M$ eigentlich die eine Hand, was die andere tut???
Translation by me: "Does at MS the right hand know what the left hand's doing?"

:)

Regards, Little John

Re: Create a relative path

Posted: Wed Sep 22, 2010 6:19 pm
by cas
Of course they can't know, Microsoft has around 100,000 employees.
@Flower:
You wrote "kernel32 documentation writer", it is "writers", not "writer". :wink:

Re: Create a relative path

Posted: Thu Sep 23, 2010 3:00 am
by Flower
cas wrote:Of course they can't know, Microsoft has around 100,000 employees.
@Flower:
You wrote "kernel32 documentation writer", it is "writers", not "writer". :wink:
Yeah, writers, that's right.
Anyways, hope they do mention more shell functions in the related articles in the future, those function can really save some coding time... and MSDN search is kinda painful, I have to use third party search engines. :(

Re: Create a relative path

Posted: Thu Sep 23, 2010 6:33 am
by Little John
Back to topic. :)

A small comparison between the WinAPI function and my routine is interesting:

Code: Select all

<Include constants and procedures from the first post of this thread here>

Define ret.s{#MAX_PATH}

Debug "PathRelativePathTo_():"
Debug "----------------------"
PathRelativePathTo_(@ret, @"F:\Data\John\", #FILE_ATTRIBUTE_DIRECTORY, @"f:\Data\John\pc.txt", #FILE_ATTRIBUTE_NORMAL)
Debug ret
PathRelativePathTo_(@ret, @"F:\Data\John\", #FILE_ATTRIBUTE_DIRECTORY, @"f:\Data\John\", #FILE_ATTRIBUTE_DIRECTORY)
Debug ret
PathRelativePathTo_(@ret, @"F:\Data\John\", #FILE_ATTRIBUTE_DIRECTORY, @"f:\Data\John", #FILE_ATTRIBUTE_NORMAL)
Debug ret
PathRelativePathTo_(@ret, @"F:\Data\John\", #FILE_ATTRIBUTE_DIRECTORY, @"f:\Data\Peter\", #FILE_ATTRIBUTE_DIRECTORY)
Debug ret
PathRelativePathTo_(@ret, @"F:\Data\John\", #FILE_ATTRIBUTE_DIRECTORY, @"f:\", #FILE_ATTRIBUTE_DIRECTORY)
Debug ret
PathRelativePathTo_(@ret, @"F:\", #FILE_ATTRIBUTE_DIRECTORY, @"f:\Data\John\", #FILE_ATTRIBUTE_DIRECTORY)
Debug ret
Debug ""
Debug ""
Debug "RelativePath():"
Debug "---------------"
Debug RelativePath("F:\Data\John\", "f:\Data\John\pc.txt")
Debug RelativePath("F:\Data\John\", "f:\Data\John\")
Debug RelativePath("F:\Data\John\", "f:\Data\John")
Debug RelativePath("F:\Data\John\", "f:\Data\Peter\")
Debug RelativePath("F:\Data\John\", "f:\")
Debug RelativePath("F:\", "f:\Data\John\")
The output is (on Windows XP SP 3 x86):

Code: Select all

PathRelativePathTo_():
----------------------
\pc.txt
.
..\John
..\Peter\
..\..
.\Data\John\


RelativePath():
---------------
pc.txt

..\John
..\Peter\
..\..\
Data\John\
Conclusions:
  • The WinAPI function PathRelativePathTo produces inconsistent results.
    A returned directory name sometimes has and sometimes doesn't have a trailing backslash (last but one and last example).
  • The WinAPI function PathRelativePathTo sometimes returns wrong results (first example)!
    "\pc.txt" denotes a file named "pc.txt" in the root directory of the current drive. But looking at the original absolute name, we see that that file is not in the root directory!
  • My function doesn't have these problems. :D
Regards, Little John

Re: Create a relative path

Posted: Thu Sep 23, 2010 7:55 am
by cas
The results of PathRelativePathTo_() are always exact and consistent, same as in your own function posted here. There are no problems, your function handles it in different way so we have different outputs but both are perfectly fine and reliable in situations when they are needed.

Re: Create a relative path

Posted: Thu Sep 23, 2010 8:14 am
by Little John
cas wrote:The results of PathRelativePathTo_() are always exact and consistent,
False. I just proved the contrary.

Re: Create a relative path

Posted: Thu Sep 23, 2010 1:20 pm
by cas
Little John wrote:
cas wrote:The results of PathRelativePathTo_() are always exact and consistent,
False. I just proved the contrary.
False. You proved nothing. Backslash is not added because you didn't have it when you called PathRelativePathTo_() in 3rd example ( and you are talking about consistency? :lol: ) and 5th example is root path so there is no need for backslash. Look again at your code. :wink:
PathRelativePathTo_() returns technically perfect results and your code returns path that is not 'technically' correct but still works fine and i thank you for that. At least your solution is cross-platform.