ftp.nice.ch/pub/next/developer/resources/classes/misckit/MiscKit.1.10.0.s.gnutar.gz#/MiscKit/Source/MiscMergeKit/MiscMergeEngine.m

This is MiscMergeEngine.m in view mode; [Download] [Up]

//
//	MiscMergeEngine.m -- an engine for merging records into templates
//		Written by Don Yacktman Copyright (c) 1995 by Don Yacktman.
//				Version 1.0.  All rights reserved.
//		This notice may not be removed from this source code.
//
//	This object is included in the MiscKit by permission from the author
//	and its use is governed by the MiscKit license, found in the file
//	"LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
//	for a list of all applicable permissions and restrictions.
//	

#import <misckit/misckit.h>
#import <misckit/miscmerge.h>
#import <misckit/MiscIfStack.h>

@implementation MiscMergeEngine
/*" A MiscMergeEngine is the heart of the merging object suite.  It
actually performs the merges.  To use it, simply give it a
MiscMergeTemplate that has been properly set up with
-#{setTemplate:}.  Next, give it a MiscDictionary object (-#{setMergeDictionary:}) that
is filled with the contents of the merge fields.  The keys are field names
and the values associated with the keys are the information that should be
substituted for the fields in the merge template.  Finally, send a -#{merge:}
message to start things off.  A MiscString will be returned that contains
the results of the merge.

The rest of the methods are an API to the internal stat of the engine
which may be used to implement MiscMergeCommand subclasses.

To implement MiscMergeCommands, it is important to understand
some of the internals of the MiscMergeEngine class.

The main thing to know is that there is an ªoutputº string that is
kept throughout the merge and returned at the end.  MiscMergeCommands
should append strings to it as necessary with the -#{appendToOutput:} method.

The MiscMergeEngine resolves field names
through a series of symbol tables.  Commands can request that arguments
be ªresolvedº through these symbol tables with the -#{getField:} method.
The process is to first look at the current merge dictionary.  If the
field name is found as a key in that dictionary, then the value for the
key is returned.  If not, then the ªlocalº symbol table is searched.  The
local symbol table may be populated by various MiscMergeCommands and
starts out empty for each merge.  If the local table doesn't have the
value, then the ªparentº merge, if it exists, is consulted, followed by
the global symbol table.  Somewhere along the way, if a key into the
merge dictionary is found, then the resolution is complete and the value
is returned.  If even the global symbol table doesn't have a desired key,
then the key itself is returned, since it could not be resolved.

By doing this extensive resolution, it is possible to use
MiscMergeCommands to create aliases for field names.  It is also possible
to use the global tables to conatin ªdefaultº values for any merge fields
that might turn up empty on a particular merge.  Note that there
are specific methods which may be used to manipulate both the local and
global symbol tables, as well as set up the parent merge.

Another special feature of the MiscMergeEngine is that it can carry
internal ªvariablesº.  A variable is some object that contains state
and needs to be accessible throughout a merge.  This is useful for
groups of MiscMergeCommands that need to pass information between
each other, but do not specifically know about each other.  A prime
example would be the if/else/endif structure supported by the kit.
In order to allow nested if statements, a stack is required.  The special
-#{ifStack} method returns this internal variable.  However, there is
a more general interface, using -#{setVariableNamed:} and
-#{getVariableNamed:} which allows arbitrary variables to be stored
and retreived by the engine.  The ªif stackº, in fact, uses the above
methods with a special internal name.  (The accessor method is used
to create the stack automatically the first time it is required; the
-#{getVariableNamed:} method can't do that since it doesn't know the
class of the requested variable.)  Variables are cleared at the start
of a new merge, so only data pertaining to a merge should be stored
there.  This is, of course, the preferred way for MiscMergeCommands to
ªcommunicateº with each other.

One final note about the ªif stackº special variable:  if it's state
suggests that the engine is walking through an ªinactiveº if block,
then all strings sent to be appended to the output will be thrown
out until the engine has entered an ªactiveº block.  (See MiscIfStack's
class description for a deeper understanding.)

The current API should be adequate to perform most things a
MiscMergeCommand would want to do.  However, it is possible that
function would be helpful or that some bit of information is still
inaccessible.  If this is the case, complain to the author
(Don Yacktman, yackd@xmission.com) and he will consider enhancing
the API to this object as necessary.  Of course, subclasses and
categories might also be workable approaches to such deficiencies.
"*/

// Creating and setting up an engine
+ newWithTemplate:(MiscMergeTemplate *)aTemplate
/*" Creates and initializes a new MiscMergeEngine instance, setting
the current template to %{aTemplate}.  Returns the newly created object.
"*/
{
	return [[[[self class] alloc] init] setTemplate:aTemplate];
}

- init
/*" Initializes a new MiscMergeEngine instance.  Returns self.
"*/
{
	[super init];
	parentMerge = nil;
	leaveDelimiters = NO; // default is to have them stripped, as originally
	symbolTable = [[MiscDictionary alloc] init];
	variables = [[MiscDictionary alloc] init];
	return self;
}

- free
/*" Frees a MiscMergeEngine instance.  Returns nil.
"*/
{
	[symbolTable freeObjects];
	[variables freeObjects];
	[symbolTable free];
	[variables free];
	return [super free];
}

// Setting up a merge
- setTemplate:(MiscMergeTemplate *)aTemplate
/*" Sets the current merge template.  All future invocations of -#{merge:} will
use %{aTemplate} as the merge template, until this method is called again.
Returns self.
"*/
{
	template = aTemplate; return self;
}

- setMergeDictionary:(MiscDictionary *)aDictionary
/*" Sets the current dictionary.  The next invocation of -#{merge:} will
use %{aDictionary} as the merge dictionary.  Returns self.
"*/
{
	dictionary = aDictionary;
	return self;
}

// Performing a merge
- (MiscString *)merge:sender
/*" Performs a merge using the current dictionary and template.  If
successful, then a MiscString containing the results of the merge is
returned.  If unsuccessful, nil is returned.  The argument %{sender}
should be the initiating driver.  If
not, some commands, such as ªnextº will not work properly.
"*/
{
	int i; id ifStack = [self ifStack];
	id templateCommands = [template commands];

	mergeInProgress = YES;
	[ifStack reset];
	outputString = [[MiscString alloc] init];
	driver = sender;
	outputOK  = YES;
	abort = NO;
	commandLocation = (_MergeEngineTag) 0;
	maxCommand = (_MergeEngineTag) [templateCommands count];

	// We could use commandLocation instead of "i" as the loop
	// variable, but I suspect that in the future I may want to
	// be able to do more inside the "if" statement below to
	// limit the types of jumps that are allowed.  That's better
	// than messing with the loop variable directly.  However,
	// perhaps that check should be placed in the method that
	// modifies "commandLocation".

	for (i=0; ((i < maxCommand) && !abort); i++) {
		id nextCmd = [templateCommands objectAt:i];
		outputOK = [ifStack currentConditionalIsActive];
		commandLocation = (_MergeEngineTag) i;
		[nextCmd executeForMerge:self];
		if (commandLocation != i) { // the command changes program flow
			i = (int) commandLocation;
		}
	}
	mergeInProgress = NO;
	if (abort) {
		[outputString free];
		return nil;
	}
	return outputString;
}

- (MiscString *)mergeWithDictionary:(MiscDictionary *)aDictionary sender:sender
/*" Initiates a merge with the current template and %{aDictionary}.  Returns
a MiscString containing the output of the merge if successful and nil
otherwise.  The argument %{sender} should be the initiating driver.  If
not, some commands, such as ªnextº will not work properly.
"*/
{
	[self setMergeDictionary:aDictionary];
	return [self merge:sender];
}

- (MiscMergeEngine *)parentMerge
/*"  Returns the ªparentº merge engine.
"*/
{
	return parentMerge;
}
- setParentMerge:(MiscMergeEngine *)aMergeEngine
/*" Sets the ªparentº merge for this merge engine.  If a symbol is
undefined in this instance's symbol table, then the parent will be
consulted to see if it is defined there.  Returns self.
"*/
{
	parentMerge = aMergeEngine; return self;
}

// Primitives that may be used by MiscMergeCommands to implement
// various functionality.

- abortMerge
/*" Aborts the current merge.  This means that the merge
output will be nil, as well.  Returns self.
"*/
{
	abort = YES;
	return self;
}

- advanceRecord
/*" Attempts to advance to the next merge dictionary while still
working with the current output string.  This might be used to allow
two merges to appear on the same "page" or document, for example.
For it to work properly, the driver that started the merge must
respond to the -#{advanceMergeLoop} method.  Returns self.
"*/
{
	if ([driver respondsTo:@selector(advanceMergeLoop)]) {
		dictionary = [driver advanceMergeLoop];
	}
	return self;
}

- appendToOutput:(MiscString *)newText
/*" Appends the contents of %{newText} to the merge output.  Returns self.
"*/
{
	if (outputOK) {
		if ([newText respondsTo:@selector(stringValue:)])
			[outputString concatenate:newText];
		else { // may be a list or some other class...
			if ([newText isKindOf:[List class]]) {
				[outputString concatenate:[(List *)newText miscStringForm]];
			} else {
				fprintf(stderr, "%s:  Don't know how to add an object of the %s class to the output.\n", [[self class] name], [[newText class] name]);
			}
		}
	}
	return self;
}

- (MiscDictionary *)dictionary
/*" Returns the current merge dictionary.
"*/
{
	return dictionary;
}

- (_MergeEngineTag)currentCommand
/*" Returns a tag that may be used as an argument for the
-#{goToCommand:} method.  This is usually called by a merge
command that implements some sort of looping structure so
that it can mark the beginning of a loop.
"*/
{
	return commandLocation;
}

- (_MergeEngineTag)lastCommand
/*" Returns a tag that may be used as an argument for the
-#{goToCommand:} method.  Jumping to this command is a way
to abort a merge.
"*/
{
	return (_MergeEngineTag)(maxCommand - 1);
}

- goToCommand:(_MergeEngineTag)newCommand
/*" Causes the merge engine to jump to a new merge command.  This
should be used with caution, obviously.  It is usually used by
end-of-loop commands to jump back to the loop's beginning.  Returns self.
"*/
{
	commandLocation = newCommand;
	return self;
}


@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.