Monday, March 02, 2009

Replacement for SYS(2014)

SYS(2014) is a great little function that helps make your applications portable. Since it returns the relative path of a file to a specific folder, you can use it on an absolute file name (such as one returned from GETFILE()) and store the relative rather than absolute path.

However, one thing that's always bugged me about SYS(2014) is that it returns the path in upper-case. If you want to display the path to the user, they'll wonder why the path is upper-cased in your program but in a different case on disk.

Fortunately, there's a simple Windows API function you can call that does the same thing as SYS(2014) but respects the case. Here's an example of how to use that function:

(UPDATE: Walt Krzystek pointed out that GetRelativePath returned a blank string if the two paths are on different drives. Also, I neglected to trim trailing spaces from the return value.)

function GetRelativePath(tcTo, tcFrom)

#define FILE_ATTRIBUTE_DIRECTORY 0x10
#define FILE_ATTRIBUTE_NORMAL 0x80
#define MAX_PATH 260

declare integer PathRelativePathTo in shlwapi.dll ;
string @out, string @from, integer fromattrib, ;
string @to, integer toattrib
lcPath = space(MAX_PATH)
lcFrom = iif(vartype(tcFrom) = 'C', tcFrom, ;
sys(5) + curdir()) + chr(0)
lnFromAttrib = iif(directory(lcFrom), FILE_ATTRIBUTE_DIRECTORY, ;
FILE_ATTRIBUTE_NORMAL)
lcTo = iif(vartype(tcTo) = 'C', tcTo, ;
sys(5) + curdir()) + chr(0)
lnToAttrib = iif(directory(lcTo), FILE_ATTRIBUTE_DIRECTORY, ;
FILE_ATTRIBUTE_NORMAL)
PathRelativePathTo(@lcPath, @lcFrom, lnFromAttrib, @lcTo, lnToAttrib)
lcPath = alltrim(strtran(lcPath, chr(0), ' '))
if empty(lcPath)
lcPath = tcTo
endif empty(lcPath)
return lcPath

7 comments:

Walt Krzystek said...

Cool function. One thing I noticed in testing it is that it doesn't handle attempting to get a relative path from a different drive where SYS(2014) does. I guess this is because a relative path to a different drive technically doesn't exist or at the very least, isn't any different than the absolute path. At least SYS(2014) doesn't return an empty string though.

Doug Hennig said...

Good point, Walt. Updated.

Paul Newton said...

Just what I needed BUT it returns a relative path beginning with "\" when the first parameter is a path below the current folder ...

Now all I need is a replacement for GetFile()

Doug Hennig said...

Hi Paul. You can use _ComDlg in the FFC _System.VCX as a replacement for GETFILE(). It has a lot more options.

Paul Newton said...

Hi Doug. Thanks for the suggestion.

Now what about that errant leading \ ? It either shouldn't be there or should be ..\ instead

Paul Newton said...

Can I suggest the following modification to tour code ?

If Upper(Left(lcFrom,Len(lcFrom)-1)) == Upper(Left(lcTo,Len(lcFrom)-1))
lcPath = SubStr(lcTo,Len(lcFrom))
Else
PathRelativePathTo(@lcPath, @lcFrom, lnFromAttrib, @lcTo, lnToAttrib)
EndIf

Doug Hennig said...

Actually, what I get, Paul, is ".\" plus the name of the subdirectory, which is in fact correct. So, perhaps the function should strip off the leading ".\" if it finds that.