Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100755 733 lines (643 sloc) 23.541 kB
069d48a @rentzsch [CHANGE] Add MiscMerge directly to project (back in the rentzsch.com …
authored
1 //
2 // MiscMergeEngine.m
3 //
4 // Written by Don Yacktman and Carl Lindberg
5 //
6 // Copyright 2001-2004 by Don Yacktman and Carl Lindberg.
7 // All rights reserved.
8 //
9 // This notice may not be removed from this source code.
10 //
11 // This header is included in the MiscKit by permission from the author
12 // and its use is governed by the MiscKit license, found in the file
13 // "License.rtf" in the MiscKit distribution. Please refer to that file
14 // for a list of all applicable permissions and restrictions.
15 //
16
17 #import "MiscMergeEngine.h"
18 #import <Foundation/NSString.h>
19 #import <Foundation/NSDictionary.h>
20 #import <Foundation/NSArray.h>
21 #import <Foundation/NSAutoreleasePool.h>
22 #import "MiscMergeTemplate.h"
23 #import "MiscMergeCommand.h"
24 #import "MiscMergeCommandBlock.h"
25 #import "KeyValue+MiscMerge.h"
26 #import "NSNull.h"
27 #import "MiscMergeFunctions.h"
28
29 #define RECURSIVE_LOOKUP_LIMIT 100
30
31 /*
32 * We can't #import a header, since we could be using either MiscKeyValue,
33 * EOControl, or (on MacOS X) Foundation.
34 */
35 @interface NSObject (WarningAvoidance)
36 - (id)valueForKeyPath:(NSString *)keyPath;
37 @end
38
39 @implementation MiscMergeEngine
40 /*"
41 * A MiscMergeEngine is the heart of the merging object suite. It actually
42 * performs the merges. To use it, simply give it a MiscMergeTemplate that
43 * has been properly set up with -#{setTemplate:}. Next, give it a data
44 * object (-#{setMainObject:}) that has values for the contents of the
45 * merge fields. Finally, send an -#{execute:} message to start things
46 * off. -#valueForKeyPath: will be called on the main object with the
47 * field names in the template, and the values returned will be substituted
48 * in the output. An NSString will be returned that contains the results of
49 * the merge.
50 *
51 * The rest of the methods are an API to the internal state of the engine
52 * which may be used in MiscMergeCommand subclass implementations.
53 *
54 * To implement MiscMergeCommands, it is important to understand some of
55 * the internals of the MiscMergeEngine class.
56 *
57 * The main thing to know is that there is an "output" string that is kept
58 * throughout the merge and returned at the end. MiscMergeCommands should
59 * append strings to it as necessary with the -#{appendToOutput:} method.
60 *
61 * The MiscMergeEngine resolves field names through a series of symbol
62 * tables. Commands can request that arguments be "resolved" through these
63 * symbol tables with the -#{valueForField:} method. The process is to walk
64 * down the context stack until an object with the desired key is found.
65 * The search will look first for local variables, then on the main object,
66 * then in the engine variables, and finally in the global variables. If
67 * any context objects are placed on the stack by MiscMergeCommands, they
68 * are searched first. If the key is not found, then the "parent" merge, if
69 * it exists, is consulted. If the key is not found, then the key itself is
70 * returned.
71 *
72 * If recursive lookups are turned on, the value returned by a lookup will
73 * be used as a field name and the lookup repeated, causing an indirection
74 * to take place (if a value is found for the new field name). This process
75 * will be repeated as far as possible, so there can be multiple levels of
76 * indirection. Use the -#setUseRecursiveLookups: method to turn this
77 * feature on or off.
78 *
79 * By doing this extensive resolution, it is possible to use
80 * MiscMergeCommands to create aliases for field names. It is also
81 * possible to use the global tables to contain "default" values for any
82 * merge fields that might turn up empty on a particular merge. Note that
83 * there are specific methods which may be used to manipulate the local,
84 * engine, and global symbol tables, as well as set up the parent merge.
85 *
86 * Another special feature of the MiscMergeEngine is that it can carry
87 * internal "variables" in its userInfo dictionary. A variable is some
88 * object that contains state and needs to be accessible throughout a
89 * merge. This is useful for groups of MiscMergeCommands that need to pass
90 * information between each other, but do not specifically know about each
91 * other. Simply manipulate the userInfo dictionary (returned by the
92 * -#userInfo method) to store or retrieve information as desired. The
93 * userInfo dictionary is not consulted during symbol lookups, and is
94 * cleared at the start of a new merge, so only data pertaining to a merge
95 * should be stored there. This is the preferred way for MiscMergeCommands
96 * to communicate with each other.
97 *
98 * The current API should be adequate to perform most things a
99 * MiscMergeCommand would want to do. However, it is possible that more
100 * functionality would be helpful or that some bit of information is still
101 * inaccessible. If this is the case, complain to the author (Don
102 * Yacktman, don@misckit.com) and he will consider enhancing the API to
103 * this object as necessary. Of course, subclasses and categories might
104 * also be workable approaches to such deficiencies.
105 "*/
106
107 static NSMutableDictionary *globalSymbols = nil;
108
109 /*"
110 * Returns the static NSMutableDictionary used to store global symbols.
111 * Global symbols are the last context searched when resolving names, and
112 * are valid for every merge done in your program (i.e. the same dictionary
113 * is used in all MiscMergeEngine instances).
114 "*/
115 + (NSMutableDictionary *)globalSymbolsDictionary
116 {
117 if (globalSymbols == nil) globalSymbols = [[NSMutableDictionary alloc] init];
118 return globalSymbols;
119 }
120
121 /*"
122 * Sets the global symbol aKey to anObject. A value of nil is the same as
123 * removing the value.
124 "*/
125 + (void)setGlobalValue:(id)anObject forKey:(NSString *)aKey
126 {
127 if (anObject == nil)
128 [self removeGlobalValueForKey:aKey];
129 else
130 [[self globalSymbolsDictionary] setObject:anObject forKey:aKey];
131 }
132
133 /*" Removes the global value associated with aKey. "*/
134 + (void)removeGlobalValueForKey:(NSString *)aKey
135 {
136 [[self globalSymbolsDictionary] removeObjectForKey:aKey];
137 }
138
139 /*" Returns the global value for aKey. "*/
140 + (id)globalValueForKey:(NSString *)aKey
141 {
142 return [globalSymbols objectForKey:aKey];
143 }
144
145
146 /*" The designated initializer. "*/
147 - init
148 {
149 [super init];
150 userInfo = [[NSMutableDictionary alloc] init];
151 engineSymbols = [[NSMutableDictionary alloc] init];
152 mergeSymbols = [[NSMutableDictionary alloc] init];
153 localSymbols = mergeSymbols;
154 contextStack = [[NSMutableArray alloc] init];
155 commandStack = [[NSMutableArray alloc] init];
156 return self;
157 }
158
159 /*"
160 * Initializes a new MiscMergeEngine instance, setting the current template
161 * to %{aTemplate}.
162 "*/
163 - initWithTemplate:(MiscMergeTemplate *)aTemplate
164 {
165 [self init];
166 [self setTemplate:aTemplate];
167 return self;
168 }
169
170 - (void)dealloc
171 {
172 [userInfo release];
173 [contextStack release];
174 [commandStack release];
175 [engineSymbols release];
176 [mergeSymbols release];
177 [template release];
178 [currentObject release];
179 [outputString release];
180 [super dealloc];
181 }
182
183 /*"
184 * Returns whether delimiters around unresolvable field names will be left
185 * in the generated output, or if just the field name itself will be left.
186 * The default is NO; i.e. the field name will remain. Setting this to YES
187 * can be helpful during debugging.
188 "*/
189 - (BOOL)keepsDelimiters
190 {
191 return keepDelimiters;
192 }
193
194 /*"
195 * Sets whether to leave the delimiters in the generated output around
196 * field names that could not be resolved.
197 "*/
198 - (void)setKeepsDelimiters:(BOOL)shouldKeep
199 {
200 keepDelimiters = shouldKeep;
201 }
202
203
204 - (MiscMergeFailedLookupResultType)failedLookupResult
205 {
206 return failedLookupResult;
207 }
208 - (void)setFailedLookupResult:(MiscMergeFailedLookupResultType)type
209 {
210 failedLookupResult = type;
211 }
212
213 - (MiscMergeNilLookupResultType)nilLookupResult
214 {
215 return nilLookupResult;
216 }
217 - (void)setNilLookupResult:(MiscMergeNilLookupResultType)type
218 {
219 nilLookupResult = type;
220 }
221
222
223 /*"
224 * Returns YES if recursive lookups are being used. During symbol
225 * resolution, if a resolved value is an NSString object and recursive
226 * lookups are turned on, then the value is used as a key itself and the
227 * symbol lookup is repeated. This process repeats until a value is not
228 * found or it's not an NSString object, at which point the last valid
229 * value will be returned. This allows for multiple levels of indirection.
230 * Be careful when using this feature, as it can lead to unexpected
231 * problems. For example, if the main object is not a dictionary,
232 * returning a string that has the same name as a method on that object
233 * (such as "description" or "zone") can lead to interesting (unintended)
234 * results or even exceptions being raised. Also, if an indirect value is
235 * the same as a previously-resolved key, then the merge engine will go
236 * into an infinite loop. By default, recursive lookups are turned off.
237 "*/
238 - (BOOL)useRecursiveLookups
239 {
240 return useRecursiveLookups;
241 }
242
243 /*"
244 * Set whether to use recursive lookups when resolving field names.
245 * setUseRecursiveLookup
246 "*/
247 - (void)setUseRecursiveLookups:(BOOL)shouldRecurse
248 {
249 useRecursiveLookups = shouldRecurse;
250 recursiveLookupLimit = RECURSIVE_LOOKUP_LIMIT;
251 }
252
253
254 - (int)recursiveLookupLimit
255 {
256 return recursiveLookupLimit;
257 }
258 - (void)setRecursiveLookupLimit:(int)recurseLimit
259 {
260 recursiveLookupLimit = recurseLimit;
261 }
262
263
264 - (void)setUseRecursiveLookups:(BOOL)shouldRecurse limit:(int)recurseLimit
265 {
266 useRecursiveLookups = shouldRecurse;
267 recursiveLookupLimit = recurseLimit;
268 }
269
270
271
272 /*"
273 * Returns the output string from the latest merge, the same string
274 * returned by the -#execute: method.
275 "*/
276 - (NSString *)outputString
277 {
278 return outputString;
279 }
280
281 /*" Returns the "parent" merge engine, or nil if not set. "*/
282 - (MiscMergeEngine *)parentMerge
283 {
284 return parentMerge;
285 }
286
287 /*"
288 * Returns the userInfo dictionary, which can be manipulated by commands
289 * for their needs.
290 "*/
291 - (NSMutableDictionary *)userInfo
292 {
293 return userInfo;
294 }
295
296 /*"
297 * Sets the "parent" merge for this merge engine. If a symbol cannot be
298 * found in the receiving instance's symbol table during lookup, the parent
299 * will be consulted to see if it is defined there.
300 "*/
301 - (void)setParentMerge:(MiscMergeEngine *)anEngine
302 {
303 parentMerge = anEngine;
304 }
305
306 /*" An instance method convenience for +#setGlobalValue:forKey:. "*/
307 - (void)setGlobalValue:(id)anObject forKey:(NSString *)aKey
308 {
309 [[self class] setGlobalValue:anObject forKey:aKey];
310 }
311 /*" An instance method convenience for +#removeGlobalValueForKey:. "*/
312 - (void)removeGlobalValueForKey:(NSString *)aKey
313 {
314 [[self class] removeGlobalValueForKey:aKey];
315 }
316 /*" An instance method convenience for +#globalValueForKey:. "*/
317 - (id)globalValueForKey:(NSString *)aKey
318 {
319 return [[self class] globalValueForKey:aKey];
320 }
321
322
323 /*"
324 * Sets the engine symbol aKey to anObject. A value of nil is the same as
325 * removing the value. The engine symbols are searched after the main
326 * object but before the global symbols when resolving a name. They remain
327 * valid for every merge executed by the receiving MiscMergeEngine instance.
328 "*/
329 - (void)setEngineValue:(id)anObject forKey:(NSString *)aKey
330 {
331 if (anObject)
332 [engineSymbols setObject:anObject forKey:aKey];
333 else
334 [self removeGlobalValueForKey:aKey];
335 }
336
337 /*" Removes the engine value associated with aKey "*/
338 - (void)removeEngineValueForKey:(NSString *)aKey
339 {
340 [engineSymbols removeObjectForKey:aKey];
341 }
342 /*" Returns the engine value associated with aKey "*/
343 - (id)engineValueForKey:(NSString *)aKey
344 {
345 return [engineSymbols objectForKey:aKey];
346 }
347
348
349 /*"
350 * Sets the merge symbol aKey to anObject. A value of nil is the same as
351 * removing the value. The engine symbols are searched before the main
352 * object when resolving a name. Local symbols are only valid for the
353 * current merge; the local symbol table is emptied before executing a
354 * merge.
355 "*/
356 - (void)setMergeValue:(id)anObject forKey:(NSString *)aKey
357 {
358 if (anObject)
359 [mergeSymbols setObject:anObject forKey:aKey];
360 else
361 [self removeMergeValueForKey:aKey];
362 }
363 /*" Removes the merge value associated with aKey "*/
364 - (void)removeMergeValueForKey:(NSString *)aKey
365 {
366 [mergeSymbols removeObjectForKey:aKey];
367 }
368 /*" Returns the merge value associated with aKey "*/
369 - (id)mergeValueForKey:(NSString *)aKey
370 {
371 return [mergeSymbols objectForKey:aKey];
372 }
373
374
375 /*"
376 * Sets the local symbol aKey to anObject. A value of nil is the same as
377 * removing the value. The engine symbols are searched before the main
378 * object when resolving a name. Local symbols are only valid for the
379 * current merge; the local symbol table is emptied before executing a
380 * merge.
381 "*/
382 - (void)setLocalValue:(id)anObject forKey:(NSString *)aKey
383 {
384 if (anObject)
385 [localSymbols setObject:anObject forKey:aKey];
386 else
387 [self removeLocalValueForKey:aKey];
388 }
389 /*" Removes the local value associated with aKey "*/
390 - (void)removeLocalValueForKey:(NSString *)aKey
391 {
392 [localSymbols removeObjectForKey:aKey];
393 }
394 /*" Returns the local value associated with aKey "*/
395 - (id)localValueForKey:(NSString *)aKey
396 {
397 return [localSymbols objectForKey:aKey];
398 }
399
400
401 /*"
402 * Adds a new context for symbol lookups. MiscMergeCommands can use this
403 * to add their own contexts to define variables that last only during the
404 * execution of that command.
405 "*/
406 - (void)addContextObject:(id)anObject andSetLocalSymbols:(BOOL)flag
407 {
408 [contextStack addObject:anObject];
409
410 if ( flag && [anObject isKindOfClass:[NSMutableDictionary class]] )
411 localSymbols = anObject;
412 }
413 - (void)addContextObject:(id)anObject
414 {
415 [self addContextObject:anObject andSetLocalSymbols:NO];
416 }
417
418 /*" Removes anObject from the context stack. "*/
419 - (void)removeContextObject:(id)anObject
420 {
421 if (anObject && anObject != currentObject
422 && anObject != globalSymbols
423 && anObject != engineSymbols
424 && anObject != mergeSymbols)
425 {
426 // should only remove last occurrence, not all FIXME
427 [contextStack removeObjectIdenticalTo:anObject];
428
429 if ( anObject == localSymbols ) {
a4aa3b9 @nzhuk - Fixed a lot of NS(U)Integer issues which caused potential 64-bit in…
nzhuk authored
430 NSInteger i;
069d48a @rentzsch [CHANGE] Add MiscMerge directly to project (back in the rentzsch.com …
authored
431
432 localSymbols = nil;
433
434 for ( i = [contextStack count] - 1; i >= 0; i-- ) {
435 id object = [contextStack objectAtIndex:i];
436
437 if ( [object isKindOfClass:[NSMutableDictionary class]] ) {
438 localSymbols = object;
439 break;
440 }
441 }
442
443 if ( localSymbols == nil ) {
444 localSymbols = mergeSymbols;
445 }
446 }
447 }
448 }
449
450 /*" Returns the main data object to be used in the next merge. "*/
451 - (id)mainObject
452 {
453 return currentObject;
454 }
455
456 /*" Returns the MiscMergeTemplate to be used for the next merge. "*/
457 - (MiscMergeTemplate *)template
458 {
459 return template;
460 }
461
462 /*"
463 * Sets the main data object. The next invocation of -#{execute:} will use
464 * %{anObject} as the main data object for the merge. Can be called during
465 * a merge to change the main object.
466 "*/
467 - (void)setMainObject:(id)anObject
468 {
2cc83ab @nzhuk - Fixed a bug which caused out-of-bounds index exception when code wa…
nzhuk authored
469 NSUInteger oldIndex = NSNotFound;
069d48a @rentzsch [CHANGE] Add MiscMerge directly to project (back in the rentzsch.com …
authored
470
471 if (currentObject != nil)
472 {
473 oldIndex = [contextStack indexOfObject:currentObject];
474 if (oldIndex != NSNotFound)
475 [contextStack removeObjectAtIndex:oldIndex];
476 }
477
478 /* Insert the new object; if there was no previous object put before local symbols. */
479 if (anObject != nil) {
480 if (oldIndex == NSNotFound)
481 oldIndex = [contextStack indexOfObject:mergeSymbols];
482 if (oldIndex != NSNotFound)
483 [contextStack insertObject:anObject atIndex:oldIndex];
484 }
485
486 [anObject retain];
487 [currentObject release];
488 currentObject = anObject;
489 }
490
491 /*"
492 * Sets the current merge template. All future invocations of -#{execute:}
493 * will use %{aTemplate} as the merge template, until this method is called
494 * again.
495 "*/
496 - (void)setTemplate:(MiscMergeTemplate *)aTemplate
497 {
498 [aTemplate retain];
499 [template release];
500 template = aTemplate;
501 }
502
503 /*"
504 * Performs a merge using the current data object and template. If
505 * successful, then an NSString containing the results of the merge is
506 * returned. If unsuccessful, nil is returned. The argument %{sender}
507 * should be the initiating driver. If not, some commands, such as "next"
508 * will not work properly.
509 "*/
510 - (NSString *)execute:sender
511 {
512 driver = sender;
513
514 aborted = NO;
515 [outputString release];
516 outputString = [[NSMutableString alloc] init];
517 [contextStack removeAllObjects];
518 [commandStack removeAllObjects];
519 [mergeSymbols removeAllObjects];
520
521 [contextStack addObject:[[self class] globalSymbolsDictionary]];
522 [contextStack addObject:engineSymbols];
523 if (currentObject) [contextStack addObject:currentObject];
524 [contextStack addObject:mergeSymbols];
525
526 [self executeCommandBlock:[template topLevelCommandBlock]];
527
528 driver = nil;
529
530 if (aborted) return @"";
531 return outputString;
532 }
533
534 /*"
535 * Initiates a merge with the current template and %{anObject}. Returns an
536 * NSString containing the output of the merge if successful and nil
537 * otherwise. The argument %{sender} should be the initiating driver. If
538 * not, some commands, such as "next" will not work properly. This method
539 * is just a convenience method that calls -#setMainObject: and then
540 * -#execute:.
541 "*/
542 - (NSString *)executeWithObject:(id)anObject sender:sender
543 {
544 [self setMainObject:anObject];
545 return [self execute:sender];
546 }
547
548 /*"
549 * Executes a single command. MiscMergeCommand subclasses should always
550 * call this method instead of calling -executeForMerge: on the command
551 * itself if they need to execute a command.
552 "*/
553 - (MiscMergeCommandExitType)executeCommand:(MiscMergeCommand *)command
554 {
555 /*
556 * Just execute the command. This is meant to be a hook where we can
557 * insert other stuff to be done on each command execution, such as
558 * logging for debug purposes.
559 */
560 return [command executeForMerge:self];
561 }
562
563 /*"
564 * Executes all the commands in block. MiscMergeCommand subclasses should
565 * use this method when they need to execute a command block. A local
566 * autorelease pool is used during execution of the block.
567 "*/
568 - (MiscMergeCommandExitType)executeCommandBlock:(MiscMergeCommandBlock *)block
569 {
570 NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init];
571 NSArray *commandArray = [block commandArray];
a4aa3b9 @nzhuk - Fixed a lot of NS(U)Integer issues which caused potential 64-bit in…
nzhuk authored
572 NSInteger i, count = [commandArray count];
069d48a @rentzsch [CHANGE] Add MiscMerge directly to project (back in the rentzsch.com …
authored
573 MiscMergeCommandExitType exitCode = MiscMergeCommandExitNormal;
574 /*
575 * Maintain the execution stack. This stack isn't being used at the
576 * moment, but command implementations may in the future make use of it
577 * (a break command, for example).
578 */
579 [commandStack addObject:block];
580
581 for (i=0; (exitCode == MiscMergeCommandExitNormal) && (i<count); i++)
582 {
583 exitCode = [self executeCommand:[commandArray objectAtIndex:i]];
584 }
585
586 [commandStack removeLastObject];
f711fc5 @seanm Fixed bugs 8, 10, and 11; added machine-generated primitive accessors…
seanm authored
587 [localPool drain];
069d48a @rentzsch [CHANGE] Add MiscMerge directly to project (back in the rentzsch.com …
authored
588 return exitCode;
589 }
590
591 /*"
592 * Attempts to resolve a field name, by going down the context stack until
593 * an object containing that key is found, at which point the
594 * -#valueForKeyPath: is returned, thus treating the fieldName as a key
595 * path. If recursive lookups are turned on, then a resolved value that is
596 * an NSString objects is treated as a field name, and the lookup process
597 * is started again. This will be repeated until a value is not found or
598 * the resolved value is not an NSString object, at which point the last
599 * valid result will be returned. If the field name is not found at all,
600 * then the field string itself is returned (unless keepDelimiters is set
601 * to YES, in which case the field string surrounded by the original field
602 * delimiters will be returned).
603 "*/
604 - (id)valueForField:(NSString *)fieldName quoted:(int)quoted
605 {
a4aa3b9 @nzhuk - Fixed a lot of NS(U)Integer issues which caused potential 64-bit in…
nzhuk authored
606 NSInteger i;
069d48a @rentzsch [CHANGE] Add MiscMerge directly to project (back in the rentzsch.com …
authored
607 id value = nil;
608 id prevValue = nil;
609 id returnValue = nil;
610 BOOL found = NO;
611 BOOL prevFound = NO;
612 int lookupCount = 0;
613
614 if ( quoted == 1 )
615 return fieldName;
616
617 for (i=[contextStack count]; i > 0 && !found; i--)
618 {
619 id currContext = [contextStack objectAtIndex:i-1];
620
621 if ([currContext hasMiscMergeKeyPath:fieldName])
622 {
623 value = [currContext valueForKeyPath:fieldName];
624 found = YES;
625 }
626
627 /*
628 * If recursive lookup is on, use the current value as a fieldName
629 * and restart the search. Store the last found value, so we know
630 * which one to return.
631 */
632 if (found && useRecursiveLookups && (lookupCount < recursiveLookupLimit) && [value isKindOfClass:[NSString class]])
633 {
634 fieldName = value;
635 prevValue = value;
636 prevFound = YES;
637 found = NO;
638 i = [contextStack count];
639 lookupCount++;
640 }
641 else if ( useRecursiveLookups && (lookupCount >= recursiveLookupLimit) )
642 NSLog(@"Recursion limit %d reached for %@.", recursiveLookupLimit, fieldName);
643 }
644
645 if (value == [NSNull null]) value = nil;
646
647 /* If we found it, return it. */
648 if (found) returnValue = value;
649
650 /* If the previous iteration of the recursive search found it, return it */
651 else if (prevFound) returnValue = prevValue;
652
653 if ( found || prevFound ) {
654 if ( returnValue == nil ) {
655 switch ( nilLookupResult ) {
656 case MiscMergeNilLookupResultKeyIfQuoted:
657 if ( quoted == 2 )
658 returnValue = fieldName;
659 break;
660
661 case MiscMergeNilLookupResultKey:
662 returnValue = fieldName;
663 break;
664
665 case MiscMergeNilLookupResultKeyWithDelims:
666 returnValue = [NSString stringWithFormat:@"%@%@%@", [template startDelimiter], fieldName, [template endDelimiter]];
667 break;
668
669 default:
670 break;
671 }
672 }
673
674 return returnValue;
675 }
676
677 /* If not found, try our parent merge */
678 if (parentMerge) return [parentMerge valueForField:fieldName quoted:quoted];
679
680 switch ( failedLookupResult ) {
681 case MiscMergeFailedLookupResultKeyWithDelims:
682 return [NSString stringWithFormat:@"%@%@%@", [template startDelimiter], fieldName, [template endDelimiter]];
683
684 case MiscMergeFailedLookupResultNil:
685 return nil;
686
687 case MiscMergeFailedLookupResultKeyIfNumeric:
688 return MMIsObjectANumber(fieldName) ? fieldName : nil;
689
690 case MiscMergeFailedLookupResultKey:
691 default:
692 return fieldName;
693 }
694 }
695
696 - (id)valueForField:(NSString *)fieldName
697 {
698 return [self valueForField:fieldName quoted:0];
699 }
700
701 /*"
702 * Appends %{aString} to the merge output.
703 "*/
704 - (void)appendToOutput:(NSString *)aString
705 {
706 if ( aString != nil )
707 [outputString appendString:[aString description]];
708 }
709
710 /*"
711 * Aborts the current merge. This means that the merge output will be nil,
712 * as well.
713 "*/
714 - (void)abortMerge
715 {
716 aborted = YES;
717 }
718
719 /*"
720 * Attempts to advance to the next merge object while still working with
721 * the current output string. This might be used to allow two merges to
722 * appear on the same "page" or document, for example. For it to work
723 * properly, the driver that started the merge must respond to the
724 * -#{advanceRecord} method.
725 "*/
726 - (void)advanceRecord
727 {
728 if ([driver respondsToSelector:@selector(advanceRecord)])
729 [driver advanceRecord];
730 }
731
732 @end
Something went wrong with that request. Please try again.