From: Michael Hecht <Michael_Hecht@mac.sas.com>
Subject: Re: Parsing text files
X-Useragent: Nuntius v1.1.1d11
Date: Fri, 2 Oct 1992 13:46:55 GMT
X-Xxdate: Fri, 2 Oct 92 14:31:19 GMT
X-Xxmessage-Id: <A6F1C7674201050A@studly.mac.sas.com>
Organization: SAS Institute Inc.

In article <1992Sep30.140336.3663@ucsvc.ucs.unimelb.edu.au> ,
u2005681@ucsvc.ucs.unimelb.edu.au writes:
>An explanation (or even some source code in C) would be most excellent.

In article <BvEHB5.JL4@unx.sas.com> Michael Hecht,
Michael_Hecht@mac.sas.com writes:
>If you're interested, let me know and I'll post it.

Well, I guess five requests warrants my sending this source code around
the world. :-)

Import uses Task Manager 2.3, which includes AtTaskExit/TaskExit support.
If you don't have this version, you need to get it. Or, you could change
the TASK #define to 0.

Use it in good health!
--Michael

=======================================================================
Michael P. Hecht                 | Internet:  Michael_Hecht@mac.sas.com
SAS Institute Inc.; Cary, NC USA | AppleLink: SAS.HECHT

=======================================================================

// /* Add these to the appropriate header file */
// typedef struct {
//	short			numStrings;
//	Str255			string;
// } **StringListHandle;
// 
// typedef OSErr ( *RowActionProc )( StringListHandle rowData,
// 							long refCon );
//
// OSErr Import( FSSpecPtr file, short columnListID,
//				RowActionProc rowAction, long refCon );


/*
 *	Import() will read a tab-delimited text file given a file spec, 
 *	a list of column names to look for (via a STR# id), and an action 
 *	proc w/refCon to be executed for each row.
 *
 *	The column label string list should probably be purgeable.
 *
 *	This function is designed to be called from a task. If you don't 
 *	want to call this from a task, change the TASK #define to 0.
 *
 *	Michael Hecht (Michael_Hecht@mac.sas.com)
 *	1-oct-1992
 */


/* Change this to 0 if you're not using Task Manager 2.3 */
#define TASK		1


#if TASK
#include "Task.h"
#else
#define TaskYield()
#endif


/* Size of our import buffer */
#define kImportBufferSize		1024

/* Context information needed for import */
typedef struct {
	HParmBlkPtr			pb;
	long				bufIndex;
	Boolean				endOfFile;
	short				**columnMap;
	short				nReqColumns;
	StringListHandle	rowData;
} ImportRecord, **ImportHandle;


/* Import task exit routine. It cleans up all our storage, etc. */
static void ImportExit( ImportHandle ctx )
{
	if(( *ctx )->rowData )
		DisposHandle(( Handle )( *ctx )->rowData );

	if(( *ctx )->columnMap )
		DisposHandle(( Handle )( *ctx )->columnMap );

	if(( *ctx )->pb ) {
		Ptr			buffer;


		buffer = ( *ctx )->pb->ioParam.ioBuffer;
		if( buffer )
			DisposPtr( buffer );

		if(( *ctx )->pb->ioParam.ioRefNum )
			PBCloseSync(( ParmBlkPtr )(( *ctx )->pb ));

		DisposPtr(( Ptr )( *ctx )->pb );
	}

	DisposHandle(( Handle )ctx );
}

static OSErr TOpen( HParmBlkPtr pb )
{
	OSErr				err;


	pb->ioParam.ioCompletion = 0;

	err = PBHOpenDFAsync( pb );
	if( err != noErr )
		return err;

	while( pb->ioParam.ioResult > 0 )
		TaskYield();

	return pb->ioParam.ioResult;
}

static OSErr TRead( HParmBlkPtr pb )
{
	OSErr				err;


	pb->ioParam.ioCompletion = 0;

	err = PBReadAsync(( ParmBlkPtr )pb );
	if( err != noErr )
		return err;

	while( pb->ioParam.ioResult > 0 )
		TaskYield();

	return pb->ioParam.ioResult;
}

static OSErr SetIndString( ConstStr255Param s, StringListHandle strList,
short i )
{
	short				k;
	Size				offset;
	StringPtr			p;


	if( i <= 0 )
		return paramErr;

	/* Add strings if necessary */
	k = i - ( *strList )->numStrings;
	if( k > 0 ) {
		OSErr			err;
		short			j;


		/* Add enough length bytes to the end of the string list */
		offset = GetHandleSize(( Handle )strList );
		SetHandleSize(( Handle )strList, offset + k );
		err = MemError();
		if( err != noErr )
			return err;

		/* Initialize length bytes to 0 */
		p = ( StringPtr )*strList + offset;
		for( j = 0; j < k; j++ )
			*p++ = 0;

		( *strList )->numStrings = i;
	}

	/* Determine the offset to the requested string */
	offset = sizeof( short );
	p = ( *strList )->string;
	for( k = 1; k < i; k++ ) {
		Size			length;


		length = p[ 0 ] + 1;
		offset += length;
		p += length;
	}

	/* Replace the string */
	Munger(( Handle )strList, offset, 0, p[ 0 ] + 1, ( Ptr )s, s[ 0 ] + 1 );
	return MemError();
}

static OSErr ReadField( ImportHandle ctx, StringPtr field, Boolean
*endOfRecord )
{
	OSErr				err;
	Ptr					buffer;
	short				length;
	char				c;


	buffer = ( *ctx )->pb->ioParam.ioBuffer + ( *ctx )->bufIndex;
	length = 0;
	*endOfRecord = FALSE;
	err = noErr;

	for( ;; ) {
		/* If the buffer is depleted, read some more in */
		if(( *ctx )->bufIndex >= ( *ctx )->pb->ioParam.ioActCount ) {

			/* If we've already reached the end of file, break out */
			if(( *ctx )->endOfFile ) {
				err = eofErr;
				*endOfRecord = TRUE;
				break;
			}

			/* Read the next buffer full */
			err = TRead(( *ctx )->pb );
			if( err != noErr ) {

				/* Remember if we hit end of file, and keep going */
				if( err == eofErr ) {
					err = noErr;
					( *ctx )->endOfFile = TRUE;
				}

				/* Break out if any other read errors */
				else
					break;
			}
	
			/* Reset index into buffer */
			( *ctx )->bufIndex = 0;
			buffer = ( *ctx )->pb->ioParam.ioBuffer;
		}

		/* Retrieve next character from buffer */
		c = *buffer++;
		( *ctx )->bufIndex++;

		/* End of field? */
		if( c == '\t' )
			break;

		/* End of record? */
		if( c == '\r' ) {
			*endOfRecord = TRUE;
			break;
		}

		/* Length exceeded? */
		if( length == sizeof( field ) - 1 )
			continue;

		/* Transfer to field */
		field[ ++length ] = c;
	}

	/* Set string length */
	field[ 0 ] = length;
	return err;
}

static OSErr ReadHeader( ImportHandle ctx, short columnListID )
{
	OSErr				err;
	StringListHandle	columnList;
	short				i, nReqColumns, col;
	short				**columnMap;
	Str255				columnName, findName;
	Boolean				endOfRecord, found;


	/* Retrieve the string list */
	columnList = ( StringListHandle )GetResource( 'STR#', columnListID );
	if( !columnList ) {
		err = ResError();
		if( err == noErr )
			err = resNotFound;
		return err;
	}

	/* Retrieve number of columns */
	nReqColumns = ( *columnList )->numStrings;
	( *ctx )->nReqColumns = nReqColumns;

	/* Allocate column map */
	columnMap = ( short ** )NewHandle( 0 );
	if( !columnMap )
		return MemError();
	( *ctx )->columnMap = columnMap;

	/* Read labels from first record */
	for( col = 1;; col++ ) {

		/* Let other tasks run */
		TaskYield();

		/* Read a field */
		err = ReadField( ctx, columnName, &endOfRecord );
		if( err != noErr )
			break;

		/* Map labels to column names */
		found = FALSE;
		for( i = 1; i <= nReqColumns; i++ ) {
			GetIndString( findName, columnListID, i );
			if( EqualString( columnName, findName, FALSE, FALSE )) {
				found = TRUE;
				break;
			}
		}

		/* If not found, then this column will be ignored */
		if( !found )
			i = 0;

		/* Append to column map */
		err = PtrAndHand(( Ptr )&i, ( Handle )columnMap, sizeof( i ));
		if( err != noErr )
			break;

		if( endOfRecord )
			break;
	}

	if( err == eofErr )
		err = noErr;
	return err;
}

static OSErr ReadData( ImportHandle ctx, RowActionProc rowAction, long
refCon )
{
	OSErr				err;
	short				col, nReqColumns, nColumns;
	StringPtr			p;
	short				**columnMap, i;
	StringListHandle	rowData;
	Boolean				endOfRecord;
	Str255				field;


	columnMap = ( *ctx )->columnMap;
	nColumns = GetHandleSize(( Handle )columnMap )/sizeof( short );

	/* Allocate rowData string list */
	nReqColumns = ( *ctx )->nReqColumns;
	rowData = ( StringListHandle )NewHandle( sizeof( short ) + nReqColumns );
	if( !rowData )
		return MemError();
	( *ctx )->rowData = rowData;

	/* Read rows until an error occurs */
	for( ;; ) {

		/* Initialize row data to nReqColumns empty strings */
		( *rowData )->numStrings = nReqColumns;
		p = ( *rowData )->string;
		for( i = 0; i < nReqColumns; i++ ) {
			*p++ = 0;
		}

		/* Read a row */
		for( col = 0, endOfRecord = FALSE; !endOfRecord; col++ ) {

			/* Let other tasks run */
			TaskYield();
	
			/* Read a field */
			err = ReadField( ctx, field, &endOfRecord );
			if( err != noErr )
				break;

			/* Skip excess unlabeled columns */
			if( col >= nColumns )
				continue;

			/* Get column index; skip unrecognized columns */
			i = ( *columnMap )[ col ];
			if( i == 0 )
				continue;

			/* Insert this field into the row data */
			err = SetIndString( field, rowData, i );
			if( err != noErr )
				break;
		}

		/* Get out now if errors occurred */
		if( err != noErr )
			break;

		/* Call row action proc */
		err = ( *rowAction )( rowData, refCon );
		if( err != noErr )
			break;

		/* Reset row data for next row */
		SetHandleSize(( Handle )rowData, sizeof( short ) + nReqColumns );
	}

	if( err == eofErr )
		err = noErr;
	return err;
}

OSErr Import( FSSpecPtr file, short columnListID,
				RowActionProc rowAction, long refCon )
{
	OSErr				err;
	ImportHandle		ctx;
	short				exitLevel;
	HParmBlkPtr			pb;
	Ptr					buffer;


	/* Allocate import context info */
	ctx = ( ImportHandle )NewHandle( sizeof( ImportRecord ));
	if( !ctx )
		return MemError();

#if TASK
	/* Install exit routine */
	err = AtTaskExit(( TaskProcPtr )ImportExit, ( long )ctx, &exitLevel );
	if( err != noErr ) {
		DisposHandle(( Handle )ctx );
		return err;
	}
#endif

	/* Initialize context info */
	( *ctx )->pb = 0;
	( *ctx )->bufIndex = 0;
	( *ctx )->endOfFile = FALSE;
	( *ctx )->columnMap = 0;
	( *ctx )->rowData = 0;

	/* Allocate the parameter block */
	pb = ( HParmBlkPtr )NewPtr( sizeof( HParamBlockRec ));
	if( !pb ) {
		err = MemError();
		goto errExit;
	}
	( *ctx )->pb = pb;

	/* Fill out parameter block for open */
	pb->ioParam.ioRefNum = 0;
	pb->ioParam.ioBuffer = 0;
	pb->ioParam.ioVRefNum = file->vRefNum;
	pb->fileParam.ioDirID = file->parID;
	pb->ioParam.ioNamePtr = file->name;
	pb->ioParam.ioVersNum = 0;
	pb->ioParam.ioPermssn = fsRdPerm;
	pb->ioParam.ioMisc = 0;

	/* Open the import file */
	err = TOpen( pb );
	if( err != noErr )
		goto errExit;

	/* Allocate the input buffer */
	buffer = NewPtr( kImportBufferSize );
	if( !buffer ) {
		err = MemError();
		goto errExit;
	}

	/* Set up parameter block for reading */
	pb->ioParam.ioBuffer = buffer;
	pb->ioParam.ioReqCount = kImportBufferSize;
	pb->ioParam.ioActCount = 0;
	pb->ioParam.ioPosMode = fsAtMark;
	pb->ioParam.ioPosOffset = 0;

	/* Read the header */
	err = ReadHeader( ctx, columnListID );
	if( err != noErr )
		goto errExit;

	/* Read the data */
	err = ReadData( ctx, rowAction, refCon );

errExit:
#if TASK
	TaskExit( exitLevel );
#else
	ImportExit( ctx );
#endif
	return err;
}

=======================================================================

/* This example shows how you would create a task that calls Import */

#include "Task.h"

#define kColumnLabels	131


static OSErr ImportRow( StringListHandle rowData, long refCon )
{
	/* Process the row data here */

	/* Return an error code (ie: userCanceledErr) to stop */
	return noErr;
}

static void ImportTask( FSSpecPtr file )
{
	UnexpectedError( Import( file, kColumnLabels, ImportRow, 0 ));
}

OSErr NewImportTask( FSSpecPtr file )
{
	/* Spawn a task to import the text file */
	return NewTask(( TaskProcPtr )ImportTask, ( long )file, 0 );
}
Newsgroups: comp.sys.mac.programmer