home *** CD-ROM | disk | FTP | other *** search
- //
- // 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;
-
- for (i=0; (i<[templateCommands count] && !abort); i++) {
- id nextCmd = [templateCommands objectAt:i];
- outputOK = [ifStack currentConditionalIsActive];
- [nextCmd executeForMerge:self];
- }
- 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) [outputString concatenate:newText];
- return self;
- }
-
- - (MiscDictionary *)dictionary
- /*" Returns the current merge dictionary.
- "*/
- {
- return dictionary;
- }
-
- @end