home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / SourceCode / MiscKit1.7.1 / MiscKit / Source / MiscMergeKit / MiscMergeEngine.m < prev    next >
Encoding:
Text File  |  1995-12-19  |  8.7 KB  |  243 lines

  1. //
  2. //    MiscMergeEngine.m -- an engine for merging records into templates
  3. //        Written by Don Yacktman Copyright (c) 1995 by Don Yacktman.
  4. //                Version 1.0.  All rights reserved.
  5. //        This notice may not be removed from this source code.
  6. //
  7. //    This object is included in the MiscKit by permission from the author
  8. //    and its use is governed by the MiscKit license, found in the file
  9. //    "LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
  10. //    for a list of all applicable permissions and restrictions.
  11. //    
  12.  
  13. #import <misckit/misckit.h>
  14. #import <misckit/miscmerge.h>
  15. #import <misckit/MiscIfStack.h>
  16.  
  17. @implementation MiscMergeEngine
  18. /*" A MiscMergeEngine is the heart of the merging object suite.  It
  19. actually performs the merges.  To use it, simply give it a
  20. MiscMergeTemplate that has been properly set up with
  21. -#{setTemplate:}.  Next, give it a MiscDictionary object (-#{setMergeDictionary:}) that
  22. is filled with the contents of the merge fields.  The keys are field names
  23. and the values associated with the keys are the information that should be
  24. substituted for the fields in the merge template.  Finally, send a -#{merge:}
  25. message to start things off.  A MiscString will be returned that contains
  26. the results of the merge.
  27.  
  28. The rest of the methods are an API to the internal stat of the engine
  29. which may be used to implement MiscMergeCommand subclasses.
  30.  
  31. To implement MiscMergeCommands, it is important to understand
  32. some of the internals of the MiscMergeEngine class.
  33.  
  34. The main thing to know is that there is an “output” string that is
  35. kept throughout the merge and returned at the end.  MiscMergeCommands
  36. should append strings to it as necessary with the -#{appendToOutput:} method.
  37.  
  38. The MiscMergeEngine resolves field names
  39. through a series of symbol tables.  Commands can request that arguments
  40. be “resolved” through these symbol tables with the -#{getField:} method.
  41. The process is to first look at the current merge dictionary.  If the
  42. field name is found as a key in that dictionary, then the value for the
  43. key is returned.  If not, then the “local” symbol table is searched.  The
  44. local symbol table may be populated by various MiscMergeCommands and
  45. starts out empty for each merge.  If the local table doesn't have the
  46. value, then the “parent” merge, if it exists, is consulted, followed by
  47. the global symbol table.  Somewhere along the way, if a key into the
  48. merge dictionary is found, then the resolution is complete and the value
  49. is returned.  If even the global symbol table doesn't have a desired key,
  50. then the key itself is returned, since it could not be resolved.
  51.  
  52. By doing this extensive resolution, it is possible to use
  53. MiscMergeCommands to create aliases for field names.  It is also possible
  54. to use the global tables to conatin “default” values for any merge fields
  55. that might turn up empty on a particular merge.  Note that there
  56. are specific methods which may be used to manipulate both the local and
  57. global symbol tables, as well as set up the parent merge.
  58.  
  59. Another special feature of the MiscMergeEngine is that it can carry
  60. internal “variables”.  A variable is some object that contains state
  61. and needs to be accessible throughout a merge.  This is useful for
  62. groups of MiscMergeCommands that need to pass information between
  63. each other, but do not specifically know about each other.  A prime
  64. example would be the if/else/endif structure supported by the kit.
  65. In order to allow nested if statements, a stack is required.  The special
  66. -#{ifStack} method returns this internal variable.  However, there is
  67. a more general interface, using -#{setVariableNamed:} and
  68. -#{getVariableNamed:} which allows arbitrary variables to be stored
  69. and retreived by the engine.  The “if stack”, in fact, uses the above
  70. methods with a special internal name.  (The accessor method is used
  71. to create the stack automatically the first time it is required; the
  72. -#{getVariableNamed:} method can't do that since it doesn't know the
  73. class of the requested variable.)  Variables are cleared at the start
  74. of a new merge, so only data pertaining to a merge should be stored
  75. there.  This is, of course, the preferred way for MiscMergeCommands to
  76. “communicate” with each other.
  77.  
  78. One final note about the “if stack” special variable:  if it's state
  79. suggests that the engine is walking through an “inactive” if block,
  80. then all strings sent to be appended to the output will be thrown
  81. out until the engine has entered an “active” block.  (See MiscIfStack's
  82. class description for a deeper understanding.)
  83.  
  84. The current API should be adequate to perform most things a
  85. MiscMergeCommand would want to do.  However, it is possible that
  86. function would be helpful or that some bit of information is still
  87. inaccessible.  If this is the case, complain to the author
  88. (Don Yacktman, yackd@xmission.com) and he will consider enhancing
  89. the API to this object as necessary.  Of course, subclasses and
  90. categories might also be workable approaches to such deficiencies.
  91. "*/
  92.  
  93. // Creating and setting up an engine
  94. + newWithTemplate:(MiscMergeTemplate *)aTemplate
  95. /*" Creates and initializes a new MiscMergeEngine instance, setting
  96. the current template to %{aTemplate}.  Returns the newly created object.
  97. "*/
  98. {
  99.     return [[[[self class] alloc] init] setTemplate:aTemplate];
  100. }
  101.  
  102. - init
  103. /*" Initializes a new MiscMergeEngine instance.  Returns self.
  104. "*/
  105. {
  106.     [super init];
  107.     parentMerge = nil;
  108.     leaveDelimiters = NO; // default is to have them stripped, as originally
  109.     symbolTable = [[MiscDictionary alloc] init];
  110.     variables = [[MiscDictionary alloc] init];
  111.     return self;
  112. }
  113.  
  114. - free
  115. /*" Frees a MiscMergeEngine instance.  Returns nil.
  116. "*/
  117. {
  118.     [symbolTable freeObjects];
  119.     [variables freeObjects];
  120.     [symbolTable free];
  121.     [variables free];
  122.     return [super free];
  123. }
  124.  
  125. // Setting up a merge
  126. - setTemplate:(MiscMergeTemplate *)aTemplate
  127. /*" Sets the current merge template.  All future invocations of -#{merge:} will
  128. use %{aTemplate} as the merge template, until this method is called again.
  129. Returns self.
  130. "*/
  131. {
  132.     template = aTemplate; return self;
  133. }
  134.  
  135. - setMergeDictionary:(MiscDictionary *)aDictionary
  136. /*" Sets the current dictionary.  The next invocation of -#{merge:} will
  137. use %{aDictionary} as the merge dictionary.  Returns self.
  138. "*/
  139. {
  140.     dictionary = aDictionary;
  141.     return self;
  142. }
  143.  
  144. // Performing a merge
  145. - (MiscString *)merge:sender
  146. /*" Performs a merge using the current dictionary and template.  If
  147. successful, then a MiscString containing the results of the merge is
  148. returned.  If unsuccessful, nil is returned.  The argument %{sender}
  149. should be the initiating driver.  If
  150. not, some commands, such as “next” will not work properly.
  151. "*/
  152. {
  153.     int i; id ifStack = [self ifStack];
  154.     id templateCommands = [template commands];
  155.  
  156.     mergeInProgress = YES;
  157.     [ifStack reset];
  158.     outputString = [[MiscString alloc] init];
  159.     driver = sender;
  160.     outputOK  = YES;
  161.     abort = NO;
  162.  
  163.     for (i=0; (i<[templateCommands count] && !abort); i++) {
  164.         id nextCmd = [templateCommands objectAt:i];
  165.         outputOK = [ifStack currentConditionalIsActive];
  166.         [nextCmd executeForMerge:self];
  167.     }
  168.     mergeInProgress = NO;
  169.     if (abort) {
  170.         [outputString free];
  171.         return nil;
  172.     }
  173.     return outputString;
  174. }
  175.  
  176. - (MiscString *)mergeWithDictionary:(MiscDictionary *)aDictionary sender:sender
  177. /*" Initiates a merge with the current template and %{aDictionary}.  Returns
  178. a MiscString containing the output of the merge if successful and nil
  179. otherwise.  The argument %{sender} should be the initiating driver.  If
  180. not, some commands, such as “next” will not work properly.
  181. "*/
  182. {
  183.     [self setMergeDictionary:aDictionary];
  184.     return [self merge:sender];
  185. }
  186.  
  187. - (MiscMergeEngine *)parentMerge
  188. /*"  Returns the “parent” merge engine.
  189. "*/
  190. {
  191.     return parentMerge;
  192. }
  193. - setParentMerge:(MiscMergeEngine *)aMergeEngine
  194. /*" Sets the “parent” merge for this merge engine.  If a symbol is
  195. undefined in this instance's symbol table, then the parent will be
  196. consulted to see if it is defined there.  Returns self.
  197. "*/
  198. {
  199.     parentMerge = aMergeEngine; return self;
  200. }
  201.  
  202. // Primitives that may be used by MiscMergeCommands to implement
  203. // various functionality.
  204.  
  205. - abortMerge
  206. /*" Aborts the current merge.  This means that the merge
  207. output will be nil, as well.  Returns self.
  208. "*/
  209. {
  210.     abort = YES;
  211.     return self;
  212. }
  213.  
  214. - advanceRecord
  215. /*" Attempts to advance to the next merge dictionary while still
  216. working with the current output string.  This might be used to allow
  217. two merges to appear on the same "page" or document, for example.
  218. For it to work properly, the driver that started the merge must
  219. respond to the -#{advanceMergeLoop} method.  Returns self.
  220. "*/
  221. {
  222.     if ([driver respondsTo:@selector(advanceMergeLoop)]) {
  223.         dictionary = [driver advanceMergeLoop];
  224.     }
  225.     return self;
  226. }
  227.  
  228. - appendToOutput:(MiscString *)newText
  229. /*" Appends the contents of %{newText} to the merge output.  Returns self.
  230. "*/
  231. {
  232.     if (outputOK) [outputString concatenate:newText];
  233.     return self;
  234. }
  235.  
  236. - (MiscDictionary *)dictionary
  237. /*" Returns the current merge dictionary.
  238. "*/
  239. {
  240.     return dictionary;
  241. }
  242.  
  243. @end