Hidden treasures of Sysutils, part 3
by Mark G. Wiseman
What’s in a file name? Well, actually there are a lot of parts to what is generally referred to as a file name. A file name may contain a drive letter, a directory and subdirectories, the filename itself, and an extension. The Sysutils namespace of the VCL provides several useful functions for working with filenames. In Parts 1 and 2 of this series, I discussed variables and functions that worked with times, dates, numbers, and currency. In this article, I will talk about functions that work with file names.
As I mentioned in Parts 1 and 2, the treasures in Sysutils are not really hidden. Most of them are in the online help, although many of them can be hard to find if you don’t know exactly what you are looking for.
A useless typedef
Before I get into the heart of the article, I want to tell you about a typedef in the VCL. That typedef is defined as follows:
typedef AnsiString TFileName;
In the online help, Borland says, “Use TFileName to represent strings that are only used for file names.” On the surface, this typedef makes a lot of sense. But, as you will see in this article, the folks at Borland did not follow their own advice. All of the following functions use AnsiString instead of TFileName!
File name parts
Earlier I asked, “What’s in a file name?” You can use these functions to find out:
AnsiString ExtractFilePath( const AnsiString FileName); AnsiString ExtractFileDir( const AnsiString FileName); AnsiString ExtractFileDrive( const AnsiString FileName); AnsiString ExtractFileName( const AnsiString FileName); AnsiString ExtractFileExt( const AnsiString FileName);
If you have version 5 of C++Builder, there are two more handy functions that you can use:
AnsiString IncludeTrailingBackslash( const AnsiString S); AnsiString ExcludeTrailingBackslash( const AnsiString S);
The names of these functions are fairly self-explanatory. The following examples will show what these functions do using the following file and path:
c:\windows\notepad.exe
ExtractFilePath() will return an AnsiString containing “c:\windows\”. ExtractFileDir(), on the other hand, returns “c:\windows”. Notice that the difference between ExtractFilePath() and ExtractFileDir() is that ExtractFilePath() returns the trailing backslash in addition to the path.
ExtractFileDrive() returns an AnsiString containing “c:”. Note that the drive separator (the colon) is considered part of the drive name.
Calling ExtractFileName() for the preceding file name will return the string “notepad.exe”. ExtractFileName() considers the file extension part of the name.
The ExtractFileExt() function returns an AnsiString containing the string “.exe”. The extension separator (the period) is returned as part of the extension.
If the file name you are working with is actually a directory name, the functions IncludeTrailingBackslash() and ExcludeTrailingBackslash() can be use to ensure that the name either has or does not have a trailing backslash. These two functions are new in C++Builder 5.
Alternate paths
There are two functions in Sysutils that allow you to use alternate forms of a file name’s path.
AnsiString ExtractRelativePath( const AnsiString BaseName, const AnsiString DestName); AnsiString ExtractShortPathName( const AnsiString FileName);
The ExtractRelativePath() function takes a file name in the DestName argument and a path name in the BaseName argument and returns an AnsiString that represents a path to DestName relative to BaseName.
Lets take a look at a couple of examples.
String filename = "c:\\My Documents\\" "Reports\\1999 Annual Report.doc"; String base1 = "c:\\My Documents\\Reports\\"; String relpath1 = ExtractRelativePath(base1, filename); String base2 = "c:\\Windows\\System\\"; String relpath2 = ExtractRelativePath(base2, filename);
When this code executes, the relpath1 variable will contain “1999 Annual Report.doc” because the path to filename and the path specified in base1 are the same. The path is relative to itself.
The value of the relpath2 variable will be “..\..\My Documents\Reports\1999 Annual Report.doc”. To get from base2 to filename, you must move up two directories and then down through the path of filename.
For ExtractFilePath() to work correctly, you must include the trailing path delimiter in the BaseName argument.
The ExtractShortPathName() function takes a file name and returns the DOS 8.3 version of that name. For this function to work correctly, the file name must actually exist on the system. Take this code, for example:
String filename = "c:\\My Documents\\" "Reports\\1999 Annual Report.doc"; String shortpath = ExtractShortPathName(filename);
On my system this code returns:
c:\MYDOCU~1\REPORTS\1999AN~1.DOC
Expanding paths
There are two functions you can use to expand paths:
AnsiString ExpandFileName( const AnsiString FileName); AnsiString ExpandUNCFileName( const AnsiString FileName);
The ExpandFileName() functions takes a file name as an argument and returns an AnsiString containing the file name with the path equal to that of the current drive and directory on your system. For example:
ExpandFileName("dummy.txt");
Executing this code on my system resulted in:
C:\Writing\Reisdorph\SysUtils\dummy.txt
The file name used in the call to ExpandFileName() does not have to exist for this function to work.
The ExpandUNCFileName() function works like ExpandFileName() except that ExpandUNCFileName() will expand the drive name to its UNC version, if the drive is mapped. The return value will be dependent on the specific system. Take the following code:
ExpandUNCFileName("k:\\on_net.txt);
On my system ExpandUNCFileName() returns:
\\SERVER1\D\on_net.txt
File name case and comparison
There are two functions in Sysutils that allow you to change the case of a file name and one function that compares files in a case-insensitive manner:
AnsiString AnsiLowerCaseFileName( const AnsiString S); AnsiString AnsiUpperCaseFileName( const AnsiString S); int AnsiCompareFileName( const AnsiString S1, const AnsiString S2);
Here is an example of their use:
String filename = "c:\\My Documents\\mixed CASE.doc"; String lower = AnsiLowerCaseFileName(filename); String upper = AnsiUpperCaseFileName(filename); int compare1 = AnsiCompareFileName(filename, lower); int compare2 = AnsiCompareFileName(filename, upper); int compare3 = AnsiCompareFileName( filename, "d:\\junk.txt"); int compare4 = AnsiCompareFileName(filename, "c:\\My Documents\\Stuff.txt");
In the above example, AnsiLowerCaseFileName() and AnsiUpperCaseFileName() take filename as an argument and these functions do exactly what you think they do. They return AnsiStrings, lower and upper, that contain the following strings, respectively:
c:\\My Documents\\mixed CASE.doc C:\\MY DOCUMENTS\\MIXED CASE.DOC
In Windows, file names are case insensitive. The function, AnsiCompareFileName(), uses this fact to compare two file names and return 0 if they are equal. If the first file name argument is “less than” the second argument, AnsiCompareFileName() returns -1. If the first argument is “greater”, AnsiCompareFileName() returns 1. AnsiCompareFileName() is smart enough to work correctly with Asian locales and with multi-byte character sets.
Miscellaneous file name functions
There are three more functions I want to tell you about. Those functions are:
AnsiString ChangeFileExt( const AnsiString FileName, const AnsiString Extension); bool IsPathDelimiter( const AnsiString S, int Index); bool FileExists( const AnsiString FileName
The ChangeFileExt() function takes two arguments: a file name and a file extension. It will return an AnsiString that contains the path and file name with the extension contained in the second argument. For example:
String filename = "c:\\My Documents\\1999 Report.doc"; String newext = ChangeFileExt(filename, ".rpt");
When this code executes, the newext variable contains:
c:\My Documents\1999 Report.rpt
Notice that the extension argument to ChangeFileExt() must contain the extension separator character.
The IsPathDelimiter() functions takes a file name and an index into that file name and returns true if the character at that index is a path delimiter character.
Using the filename value shown previously, IsPathDelimiter(filename, 7) returns false and IsPathDelimiter(filename, 16) returns true.
Also, be aware that this function manifests its Pascal heritage by being 1-based and not 0-based. This means that the first character in the file name is considered to be at index 1.
The last function I wanted to mention, FileExists() takes a file name as an argument and returns true if the file exists on the system and false if it does not. This function is convenient when you want to check for the existence of a file before attempting to open it.
Conclusion
You may be wondering how I knew that there were two new file name functions in C++Builder 5. I simply looked at the source code in SYSUTILS.PAS to see what was new. I encourage you to browse the VCL source to see what you can find. It is often easier (and more interesting) to look at the source rather than hunting around in the VCL help file.
In writing this article, I chose to include the FileExists() function with those dealing with file names. It could also be grouped with file control functions, which will be the topic of Part 4 of this series.