Storing Relative File Names

Environment: MS Visual C++ 6

Many MFC documents use CArchive to store the filenames of nested documents or project files. When the user copies the projects into different directories or to a different computer, the paths become invalid unless the programmer painstakingly controls these directory name dependencies. It also frustrates the user quite a lot if the application does not support such project movements. This article describes an easy way to solve this problem.

We will suppose that we are creating an MFC Document/View application and our archived CDocument contains the filenames of project-related files. For instance, the document contains a reference to the measured file somewhere and the settings file somewhere else. Typically, the user holds the measured data and settings data in the same root directory. Frequently, the settings are stored in some subfolder. So, we will obtain something like this (mdc means our measurement document extension):

c:measurementsberlin2002-10-12

  meas1.mdc

  data1.dat

  meas2.mdc

  data2.dat

  meas3.mdc

  data3.dat

c:measurementsberlin2002-10-12settings

  setup.dat

Yes, all three mdc files refers to the same setup.dat internally, as you can imagine. Also, what do we do if we can expect that the user will move the project onto a file server? We can handle filenames as relative, but the application will contain a lot of code for name splitting and concatenating. Also, direct references (for instance, if setup.dat is in a completely separate directory, such as “c:setups”) must be painstakingly controlled.

The following code tries to solve the problem in the root—in archive storing and reading. At that moment, we know the archive path and we can compare which file names are in the archive directory and its subfolders. By feeding _getDocRelativeFileName() with the full path of document archive and the full path of the stored file name, the method can decide whether the file name is relative to the archive. Also, from
  “c:measurementsberlin2002-10-12data1.dat”
we will get
  “data1.dat”
and from
  “c:measurementsberlin2002-10-12settingssetup.dat”
we will get
  “settingssetup.dat”

When reading back the archive, _getDocAbsoluteFileName() will restore the full file names. Also, when the user moves the project into

  “\ourservermeasurementsberlin_0210”,
we will read back the correct, fully qualified file names
  “\ourservermeasurementsberlin_0210data1.dat”
  “\ourservermeasurementsberlin_0210settingssetup.dat”

The code fragment presented is dirty and could be better, of course, but it works in many situations. Comments are included in the code fragment. Some additional safety checks can be implemented, of course. Currently, the code:

  • Converts only files in the project and nested directories to the path-relative file names
  • Works both for drive locations and server locations (“c:something” vs. “\servernamesomething”)
  • Does not serve directory-up file placement (like “..settingssetup.dat”)
  • Was not tested on a unicode build

You could need more sophisticated file position support; then, it is necessary either to enhance the presented functionality or to create other, similar function pairs. However, placement in the Serialize method is easy and understandable and it frees the rest of the application from lot of stupid work.

To use the code, simply copy it into the application document code (somewhere in xxxdoc.cpp, before the CxxxDoc::Serialize method) or make them methods of the CxxxDoc class. Add function calls to the Serialize method, as shown in the example. No more work is needed; the rest of application from this moment does not depend on current project placement.

// static functions implementation to operate with project-
// relative file names
// an include might be needed if not specified already somewhere

#include <stdlib.h>

// ===============================================================
// compare drive and path of the document and the filename;
// returns only the different part (eg. filename with extension if
// the file is in the same folder as the document file, or subpath
// and filename if file is in nested directory). Use
// _getDocAbsoluteFileName to revert to the full path.
// sDocName is the full file name of the archived CDocument;
// sSubFileName is the full file name of (possibly) project-nested
// data file.

static CString _getDocRelativeFileName( CString& sDocName,
CString& sSubFileName )
{
CString sResult = sSubFileName;

char ddrive[_MAX_DRIVE];
char ddir[_MAX_DIR];
char dfname[_MAX_FNAME];
char dext[_MAX_EXT];
char fdrive[_MAX_DRIVE];
char fdir[_MAX_DIR];
char ffname[_MAX_FNAME];
char fext[_MAX_EXT];

_splitpath( sDocName, ddrive, ddir, dfname, dext );
_splitpath( sSubFileName, fdrive, fdir, ffname, fext );

if ( strlen(ddrive) > 0 || strlen(ddir) > 0 )
{
CString s = fdir;
if ( CString( ddrive ) == CString( fdrive ) &&
s.Find( ddir ) == 0 )
{
sResult = s.Mid( strlen( ddir ));
sResult += ffname;
sResult += fext;
}
}

return sResult;
}

// Changes the relative file name from _getDocRelativeFileName
// to the absolute path. Checks for server name
// \xxx and drive name x: to be sure to not expand fully
// qualified names. sDocName is the full file name of the restored
// CDocument. sSubFileName is the optionally shortened file name
// of the project-nested data file.

static CString _getDocAbsoluteFileName( CString& sDocName,
CString& sSubFileName )
{
CString sResult = sSubFileName;

char ddrive[_MAX_DRIVE];
char ddir[_MAX_DIR];
char dfname[_MAX_FNAME];
char dext[_MAX_EXT];

_splitpath( sDocName, ddrive, ddir, dfname, dext );

// there should not be a server of the disk identifier in
// the relative name
// (a little bit of safe expansion was used)

if ( sSubFileName.Find( “\\” ) < 0 &&
sSubFileName.Find( “:\” ) < 0 )
{
sResult = CString(ddrive) + CString(ddir) + sSubFileName;
}

return sResult;
}

// example of the function use

void CMyAppDoc::Serialize(CArchive& ar)
{
CString sPath;
CFile* pFile = ar.GetFile();
if ( pFile )
{
sPath = pFile->GetFilePath();
}
if (ar.IsStoring())
{
// TODO: add storing code here
ar << _getDocRelativeFileName( sPath, m_sFileName1 );
ar << _getDocRelativeFileName( sPath, m_sFileName2 );
}
else
{
// TODO: add loading code here
CString s;
ar >> s;
m_sFileName1 = _getDocAbsoluteFileName( sPath, s );
ar >> s;
m_sFileName2 = _getDocAbsoluteFileName( sPath, s );
}
}

// ===============================================================

Downloads

Please copy the code directly from the article body….

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read