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