25 Years of Programming
An open source source for C, C++, OWL, BASIC, MDB, XLS, DOT, and more...
Home   Projects   Up   Sitemap   Search   Blog   Forum+Chat   About Us   Privacy   Terms of Use   Feedback   FAQ   Images   Services   Payments   Humor   Music  

File Array: Borland C++ class to build and store a list of disk files

FileArray is a C++ class that holds a list of disk files as an array of strings.

It can build the list from wildcards and provides functions for retrieving filenames from the list in sequential or random order and optionally deleting the file from the array (not the disk) upon retrieval.

It can build its list from the contents of a Borland ObjectWindows Library (OWL) TOpenSaveDialog::TData.FileName, and it can output its contents in a way that will display properly in a TEdit control, with one filename to a line. Thus, you can build the file list, from wildcards if desired, allow a user to edit the list in a TEdit, and read the revised list back into the array.

It is useful for managing lists of files given as command-line arguments, or that result from drag-and-drop operations, or anywhere else you need to manage a list of files. Many of my programs use it.

The FileArray class contains no OWL or Windows-specific code, and I use it in both DOS and Windows programs. It requires my.h, to which there is a link in the code below.

An associated utility class provides easy parsing of partial or full file names.

There is also a conversion of this class to Microsoft Visual C++ .NET.

filearay.h

/*	filearay.h			1/27/02
	Copyright (C)1999-2000, 2002 Steven Whitney.
	Initially published by http://25yearsofprogramming.com.

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	Version 3 as published by the Free Software Foundation.
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

	An array for holding a list of files, with handling functions,
	including ability to parse the FileList member of a TOpenSaveDialog::TData.
	Useful for holding files from TDropInfo lists.

------
TO DO:

I think you can now eliminate TEditControlOutput if you just use LFtoCRLF()
on the output string wherever you use it. Must search all current uses of it first.

------
NOTES:
--This class uses the string >> operator, whose Borland C++ 4.0 library version has a bug.
  MYLIB.CPP, containing a fix, must be a part of any project where this is used.
--YOU CAN USE THE LIST OF FILES AS A:
  1. Queue from which filenames are deleted as they're retrieved
  2. Fixed list through which you cycle.
  You should NOT use it both ways in the same program.
--REMEMBER TO SET THE DATA MEMBERS AFTER CONSTRUCTION, IF NECESSARY.
  For an ostrstream (for use by a TEdit), setting the stream to ios::binary does
  not have the same effect as using TEditControlOutput=TRUE.  Apparently,
  binary mode only applies to file output.  So TEditControlOutput is still necessary.
  TEditControlOutput could be eliminated if you could determine within operator << whether the
  ostream had been opened in binary mode, but you can't.  I only found 1 ostream-derived
  class (don't recall which) that even recorded its opening mode, and it is protected.
--the return of empty strings when the list is empty is only for protection.  you should always:
  if(FileList.GetItemsInContainer())
	filename = FileList.GetNext();

*/
#ifndef __FILEARAY_H
#define __FILEARAY_H

#include <dir.h>
#include <classlib\arrays.h>
#include "c:\bcs\my.h"
#pragma hdrstop

//////////////////////////////////////////////////////////////////////////////
// 	A utility class that provides easy parsing of partial or full file names
// 	and disk/directory management.
class FilePathParser
{
public:
	FilePathParser(const char* path = 0);
	FilePathParser(const string& path);

	// functions
	Parse(const char* path);	// reuse the parser on a new filename
	BOOL ChDir();          		// change default directory to the Drive+Dir it now holds

								// useful combinations you can retrieve.  they will reflect
								// any changes you've made to the basic elements
	string FileExt();			// File + Ext
	string DriveDir(); 			// Drive + Dir
	string DriveDirFile(); 		// DriveDir() + File
	string DriveDirFileExt(); 	// DriveDirFile() + Ext
	string Entire();            // DriveDirFileExt()

	// variables
	string Drive;  	// basic elements.  The contents follow the rules of fnsplit().
	string Dir;		// you can change these directly, but be sure to do it right
	string File;
	string Ext;
};
//----------------------------------------------------------------------------
// 							end class FilePathParser
//////////////////////////////////////////////////////////////////////////////
// the TArray is protected to prevent outside use of the TArray functions,
// forcing use of the provided manipulation functions instead.
// Might be better to just make the TArray a member.  Haven't been able to decide.
class FileArray : protected TArrayAsVector<string>
{
public:
	FileArray(BOOL editcontroloutput = TRUE);						// constructor
	~FileArray();                                                	// destructor

	friend ostream& operator << (ostream& os, const FileArray&);	// write
	friend istream& operator >> (istream& is, FileArray&);			// read

// If you create a newed FileArray f, there is a potential typing conflict
// in the case of: if(f)...  Which is used, if(the pointer f != 0) or if((int)f != 0) ?
// Does this problem exist for ios-based classes, which use a similar method?
// Use GetItemsInContainer() in new code, in case I phase out operator int().

	// handy for: if(FileArray) or while(FileArray)
	operator int() const { return(GetItemsInContainer()); }

	// functions
	int AddFile(const string& s);	// add 1 file OR all matching files if s contains wildcards
	int AddDialogList(char *s);		// from TOpenFileDialog.TData.FileName
	void Flush() 					{ TArrayAsVector<string>::Flush(); current = -1; }
	uint GetItemsInContainer() const
		{ return(TArrayAsVector<string>::GetItemsInContainer()); }

	// FOR SEQUENTIAL USE OF THE FILE NAMES:
	string GetNext(BOOL DeleteEntry = TRUE);

	// FOR CYCLING THROUGH THE FILE NAMES:
	string Next();
	string Prev();
	string Current();
	string Random();				// selects one at random

// write only if they're ever needed
// 	int Load(const string& filename);
// 	int Write(const string& filename);

	// variables
	BOOL TEditControlOutput;	// if it's TRUE, operator << uses \r\n instead of endl

protected:
	int current;							// index into the array, for cycling
};
//----------------------------------------------------------------------------
// 						end class FileArray
//////////////////////////////////////////////////////////////////////////////
#endif			// __FILEARAY_H

filearay.cpp

/*	filearay.cpp		1/27/02
	Copyright (C)1999-2000, 2002 Steven Whitney.
	Initially published by http://25yearsofprogramming.com.

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	Version 3 as published by the Free Software Foundation.
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

*/
#include "c:\bcs\library\filearay.h"

//////////////////////////////////////////////////////////////////////////////
// class FilePathParser code
//----------------------------------------------------------------------------
// constructor
FilePathParser::FilePathParser(const char* path)
{
string s;
if(path)
	s = path;
else					// if NOTHING provided, get and use currently logged disk & dir
{
	char temp[MAXPATH] = {0};
	if(getcwd(temp,MAXPATH) == 0)  // if error,
		temp[0] = 0;               // make it a null string
	s = temp;
	if(s.length()) 		// must add terminal \ to identify it as a path
		s += "\\";
}
Parse(s.c_str());
}                       	//constructor
//----------------------------------------------------------------------------
// constructor
FilePathParser::FilePathParser(const string& path) { Parse(path.c_str()); }
//----------------------------------------------------------------------------
// the various non-inline functions.
string FilePathParser::FileExt()			{ return File + Ext; }
string FilePathParser::DriveDir() 			{ return Drive + Dir; }
string FilePathParser::DriveDirFile() 		{ return DriveDir() + File; }
string FilePathParser::DriveDirFileExt() 	{ return DriveDirFile() + Ext; }
string FilePathParser::Entire()            	{ return DriveDirFileExt(); }
//----------------------------------------------------------------------------
// call this to reuse the parser on a new filename. (it resets everything).
// if path is ONLY a path, it must end with a "\", or the last dir will be parsed
// as a file name.
BOOL FilePathParser::Parse(const char* path)
{
Drive.remove(0); Dir.remove(0); File.remove(0); Ext.remove(0);

if(!path || !strlen(path))
	return FALSE;

char drive[MAXDRIVE] = {0}, dir[MAXDIR] = {0}, file[MAXFILE] = {0}, ext[MAXEXT] = {0};
int flags = fnsplit(path,drive,dir,file,ext);
if(flags & DRIVE)					// drive was specified
	Drive = drive;					// Drive has the :
else								// drive NOT specified: use current default
	Drive = string((uchar)(getdisk() + 'A')) + ":";
if(flags & DIRECTORY)				// dir was successfully parsed
	Dir = dir;						// dir has leading and trailing \
else                                // else get default dir for the drive we've just determined
{
	int newdrive = toupper(Drive[0]) - '@';		// (default=0), A=1, B=2, etc.
	// if specified drive was illegal, an empty Dir is your flag of the error.
	if(getcurdir(newdrive,dir) == 0)
		Dir = string("\\") + string(dir) + string("\\");
}
if(flags & FILENAME)
	File = file;
if(flags & EXTENSION)
	Ext = ext;
return TRUE;
}                    			//Parse
//----------------------------------------------------------------------------
// change the current default dir to the one it currently holds, changing disk if necessary.
BOOL FilePathParser::ChDir()
{
if(Drive.length() && Dir.length())
{
	setdisk(toupper(Drive[0]) - 'A');
	string s(Dir);
	int i = s.length() - 1;
	if(s[i] == '\\')			// remove trailing backslash
		s.remove(i);
	if(chdir(s.c_str()) == 0)
		return TRUE;
}
return FALSE;
}                      			//ChDir
//----------------------------------------------------------------------------
// 						end class FilePathParser
//////////////////////////////////////////////////////////////////////////////
// class FileArray code
//----------------------------------------------------------------------------
// constructor
FileArray::FileArray(BOOL editcontroloutput) : TArrayAsVector<string>(10,0,10)
{
current = -1;								// -1 when array is empty so ++ makes it 0
TEditControlOutput = editcontroloutput;		// see operator <<

}                       	// constructor
//----------------------------------------------------------------------------
// destructor
FileArray::~FileArray()
{
Flush();
}                       	// destructor
//----------------------------------------------------------------------------
// output the current list of files, one to a line
// for output to a string, remember to add the << ends after returning.
// A TEdit control is good for editing the list (see wanimate.cpp),
// and requires \r\n instead of endl.
ostream& operator << (ostream& os, const FileArray& a)
{
int N = a.GetItemsInContainer();
for(int i = 0 ; i < N ; i++)
{
	os << a[i];           		// output the string
	if(a.TEditControlOutput)	// choose a terminator
		os << "\r\n";
	else
		os << endl;
}
return(os);
}
//----------------------------------------------------------------------------
// add to file list from a stream
istream& operator >> (istream& is, FileArray& a)
{
string s;
while(is >> s)
	a.AddFile(s);
return(is);
}
//----------------------------------------------------------------------------
// add 1 file name, OR
// if the given name contains wildcards, find FILES that match the pattern, and add them all.
// could test for legality, but complicated, and a bad name will simply fail on open.
// returns number of filenames added.
int FileArray::AddFile(const string& filename)
{
int count = 0;

// builds a fully qualified name from scratch using either provided values or defaults
FilePathParser f(filename);
if(!f.Dir.length())       	// if an explicit or default Disk+Dir couldn't be determined,
	return(0);				// quit to avoid lots of problems

// you don't HAVE to test for wildcards and avoid searching if there aren't any:
// an unambiguous filename would just be found once by findfirst.
// But the original purpose of this function was to create a list of STRINGS.
// findfirst is for dealing with EXISTING FILES.  If user specified a SINGLE file,
// and it doesn't exist, findfirst will fail, and it won't go into the list.
// That's probably the effect you'd want, but there's no provision HERE for warning the user.
// It is better to unconditionally add the STRING now and let the open fail later,
// where user can be informed that named file doesn't exist.
// If there ARE wildcards, you MUST expand them now to whatever files DO exist.
string s = f.Entire();
string path = f.DriveDir();
if(s.contains("?") || s.contains("*"))
{
	struct ffblk fileblock;
	int done = findfirst(s.c_str(), &fileblock, 0);
	while(!done)
	{
		// retrieved name never has path, so must prepend to each file.
		// if the caller wants upper or lower case upon use, it can do it itself.
		count += Add(path + fileblock.ff_name);
		done = findnext(&fileblock);
	}
}
else
	count += Add(s);   	// just 1 file, and Add returns 1 if successful
current = -1;			// so that either Current() or Next() will restart at 0
return(count);
}                       	// AddFile
//----------------------------------------------------------------------------
// add files from a TOpenSaveDialog::TData.FileName,
// which contains either 1 legal filename OR multiple entries separated by spaces,
// where the first is a pathname, followed by all the filenames.
// Help|WinAPI says list might contain relative pathnames, but OWL seems to prohibit this.
// remember that multi-selected files are automatically alphabetized.
// If you want a particular order, you must select and add them one at a time.
// returns number of filenames ADDED.
int FileArray::AddDialogList(char* FileName)
{
int filecount = 0;					// number of files selected
string pathname;                   	// path to the files (or a single filename)
string filename;					// for copying file names into
istrstream is(FileName);			// input stream
is >> pathname;						// the first entry is a path OR a full file name
is >> filename;                     // try to read next filename, if any
if(filename.length())               // If there are multiple files,
{
	do                              // prepend pathname to each filename
	{
		filecount += AddFile(pathname + "\\" + filename);	// add them to FileList
	}
	while(is >> filename);			// while there are more filenames to copy
}
else                                // else just add the one file,
{
	if(pathname.length())     		// if there was one.
	{
		filecount += AddFile(pathname);
	}
}
current = -1;				// so either Current() or Next() will restart at 0
return(filecount);
}                       	// AddDialogList
//----------------------------------------------------------------------------
// for SEQUENTIAL use of the filenames:
//----------------------------------------------------------------------------
// return name of the next file (at index 0), and optionally (usually) delete it.
//
// this could also be BOOL GetNext(string& destination, BOOL delete), that
// fills the string and returns TRUE if there was anything to get; otherwise
// leaves the string unchanged and returns FALSE.  Unsure which is more useful.
//
string FileArray::GetNext(BOOL DeleteEntry)
{
string s;
if(GetItemsInContainer())
{
	s = (*this)[0];
	if(DeleteEntry)
		Destroy(0);
}
current = -1;			// EITHER Current() or Next() will start at 0
return(s);
}                		// GetNext
//----------------------------------------------------------------------------
// for CYCLING use of the filenames:
//----------------------------------------------------------------------------
// advances to the next filename, then returns it.
string FileArray::Next()
{
string s;
int N = GetItemsInContainer();
if(N)
{
	if(++current >= N)
		current = 0;
	s = (*this)[current];
}
return(s);
}                   	// Next
//----------------------------------------------------------------------------
// goes back to previous filename, then returns it.
// maybe useful for cycling backwards or back and forth
string FileArray::Prev()
{
string s;
int N = GetItemsInContainer();
if(N)
{
	if(--current < 0)
		current = N - 1;
	s = (*this)[current];
}
return(s);
}                    	// Prev
//----------------------------------------------------------------------------
// returns name of the filename currently pointed at.
string FileArray::Current()
{
string s;
int N = GetItemsInContainer();
if(N)
{
	current = range(0, N - 1, current);
	s = (*this)[current];
}
return(s);
}                    	// Current
//----------------------------------------------------------------------------
// returns a filename randomly selected from the list. (used sometimes for .MAP color files)
string FileArray::Random()
{
string s;
int N = GetItemsInContainer();
if(N)
	s = (*this)[random(N)];
return(s);
}                    	// Current
//----------------------------------------------------------------------------
// 						end class FileArray
//////////////////////////////////////////////////////////////////////////////

 

 

Valid HTML 4.01 Transitional
Yahoo! Search
Search the web Search this site
Valid CSS