aboutsummaryrefslogtreecommitdiffstats
path: root/Source/MGTemplateStandardMarkers.m
diff options
context:
space:
mode:
Diffstat (limited to 'Source/MGTemplateStandardMarkers.m')
-rw-r--r--Source/MGTemplateStandardMarkers.m620
1 files changed, 620 insertions, 0 deletions
diff --git a/Source/MGTemplateStandardMarkers.m b/Source/MGTemplateStandardMarkers.m
new file mode 100644
index 00000000..df4fcf3e
--- /dev/null
+++ b/Source/MGTemplateStandardMarkers.m
@@ -0,0 +1,620 @@
+//
+// MGTemplateStandardMarkers.m
+//
+// Created by Matt Gemmell on 13/05/2008.
+// Copyright 2008 Instinctive Code. All rights reserved.
+//
+
+#import "MGTemplateStandardMarkers.h"
+#import "MGTemplateFilter.h"
+
+//==============================================================================
+
+#define FOR_START @"for"
+#define FOR_END @"/for"
+
+#define FOR_TYPE_ENUMERATOR @"in" // e.g. for thing in things
+#define FOR_TYPE_RANGE @"to" // e.g. for 1 to 5
+#define FOR_REVERSE @"reversed"
+
+#define FOR_LOOP_VARS @"currentLoop"
+#define FOR_LOOP_CURR_INDEX @"currentIndex"
+#define FOR_LOOP_START_INDEX @"startIndex"
+#define FOR_LOOP_END_INDEX @"endIndex"
+#define FOR_PARENT_LOOP @"parentLoop"
+
+#define STACK_START_MARKER_RANGE @"markerRange"
+#define STACK_START_REMAINING_RANGE @"remainingRange"
+#define FOR_STACK_ENUMERATOR @"enumerator"
+#define FOR_STACK_ENUM_VAR @"enumeratorVariable"
+#define FOR_STACK_DISABLED_OUTPUT @"disabledOutput"
+
+//==============================================================================
+
+#define SECTION_START @"section"
+#define SECTION_END @"/section"
+
+//==============================================================================
+
+#define IF_START @"if"
+#define ELSE @"else"
+#define IF_END @"/if"
+
+#define IF_VARS @"currentIf"
+#define DISABLE_OUTPUT @"shouldDisableOutput"
+#define IF_ARG_TRUE @"argumentTrue"
+#define IF_ELSE_SEEN @"elseEncountered"
+
+//==============================================================================
+
+#define NOW @"now"
+
+//==============================================================================
+
+#define COMMENT_START @"comment"
+#define COMMENT_END @"/comment"
+
+//==============================================================================
+
+#define LOAD @"load"
+
+//==============================================================================
+
+#define CYCLE @"cycle"
+#define CYCLE_INDEX @"lastIndex"
+#define CYCLE_VALUES @"value"
+
+//==============================================================================
+
+#define SET @"set"
+
+//==============================================================================
+
+
+@implementation MGTemplateStandardMarkers
+
+
+- (id)initWithTemplateEngine:(MGTemplateEngine *)theEngine
+{
+ if (self = [super init]) {
+ engine = theEngine;
+ forStack = [[NSMutableArray alloc] init];
+ sectionStack = [[NSMutableArray alloc] init];
+ ifStack = [[NSMutableArray alloc] init];
+ commentStack = [[NSMutableArray alloc] init];
+ cycles = [[NSMutableDictionary alloc] init];
+ }
+ return self;
+}
+
+
+- (void)dealloc
+{
+ engine = nil;
+ [forStack release];
+ forStack = nil;
+ [sectionStack release];
+ sectionStack = nil;
+ [ifStack release];
+ ifStack = nil;
+ [commentStack release];
+ commentStack = nil;
+ [cycles release];
+ cycles = nil;
+
+ [super dealloc];
+}
+
+
+- (NSArray *)markers
+{
+ return [NSArray arrayWithObjects:
+ FOR_START, FOR_END,
+ SECTION_START, SECTION_END,
+ IF_START, ELSE, IF_END,
+ NOW,
+ COMMENT_START, COMMENT_END,
+ LOAD,
+ CYCLE,
+ SET,
+ nil];
+}
+
+
+- (NSArray *)endMarkersForMarker:(NSString *)marker
+{
+ if ([marker isEqualToString:FOR_START]) {
+ return [NSArray arrayWithObjects:FOR_END, nil];
+ } else if ([marker isEqualToString:SECTION_START]) {
+ return [NSArray arrayWithObjects:SECTION_END, nil];
+ } else if ([marker isEqualToString:IF_START]) {
+ return [NSArray arrayWithObjects:IF_END, ELSE, nil];
+ } else if ([marker isEqualToString:COMMENT_START]) {
+ return [NSArray arrayWithObjects:COMMENT_END, nil];
+ }
+ return nil;
+}
+
+
+- (NSObject *)markerEncountered:(NSString *)marker withArguments:(NSArray *)args inRange:(NSRange)markerRange
+ blockStarted:(BOOL *)blockStarted blockEnded:(BOOL *)blockEnded
+ outputEnabled:(BOOL *)outputEnabled nextRange:(NSRange *)nextRange
+ currentBlockInfo:(NSDictionary *)blockInfo newVariables:(NSDictionary **)newVariables
+{
+ if ([marker isEqualToString:FOR_START]) {
+ if (args && [args count] >= 3) {
+ // Determine which type of loop this is.
+ BOOL isRange = YES;
+ if ([[args objectAtIndex:1] isEqualToString:FOR_TYPE_ENUMERATOR]) {
+ isRange = NO;
+ }
+ BOOL reversed = NO;
+ if ([args count] == 4 && [[args objectAtIndex:3] isEqualToString:FOR_REVERSE]) {
+ reversed = YES;
+ }
+
+ // Determine if we have acceptable parameters.
+ NSObject *loopEnumObject = nil;
+ BOOL valid = NO;
+ NSString *startArg = [args objectAtIndex:0];
+ NSString *endArg = [args objectAtIndex:2];
+ int startIndex, endIndex;
+ if (isRange) {
+ // Check to see if either the arg itself is numeric, or it corresponds to a numeric variable.
+ valid = [self argIsNumeric:startArg intValue:&startIndex checkVariables:YES];
+ if (valid) {
+ valid = [self argIsNumeric:endArg intValue:&endIndex checkVariables:YES];
+ if (valid) {
+ // Check startIndex and endIndex are sensible.
+ valid = (startIndex <= endIndex);
+ }
+ }
+ } else {
+ startIndex = 0;
+
+ // Check that endArg is a collection.
+ NSObject *obj = [engine resolveVariable:endArg];
+ if (obj && [obj respondsToSelector:@selector(objectEnumerator)] && [obj respondsToSelector:@selector(count)]) {
+ endIndex = [(NSArray *)obj count];
+ if (endIndex > 0) {
+ loopEnumObject = obj;
+ valid = YES;
+ }
+ }
+ }
+
+ if (valid) {
+ *blockStarted = YES;
+
+ // Set up for-stack frame for this loop.
+ NSMutableDictionary *stackFrame = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ [NSValue valueWithRange:markerRange], STACK_START_MARKER_RANGE,
+ [NSValue valueWithRange:*nextRange], STACK_START_REMAINING_RANGE,
+ nil];
+ [forStack addObject:stackFrame];
+
+ // Set up variables for the block.
+ int currentIndex = (reversed) ? endIndex : startIndex;
+ NSMutableDictionary *loopVars = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithInt:startIndex], FOR_LOOP_START_INDEX,
+ [NSNumber numberWithInt:endIndex], FOR_LOOP_END_INDEX,
+ [NSNumber numberWithInt:currentIndex], FOR_LOOP_CURR_INDEX,
+ [NSNumber numberWithBool:reversed], FOR_REVERSE,
+ nil];
+ NSMutableDictionary *blockVars = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ loopVars, FOR_LOOP_VARS,
+ nil];
+
+ // Add enumerator variable if appropriate.
+ if (!isRange) {
+ NSEnumerator *enumerator;
+ if (reversed && [loopEnumObject respondsToSelector:@selector(reverseObjectEnumerator)]) {
+ enumerator = [(NSArray *)loopEnumObject reverseObjectEnumerator];
+ } else {
+ enumerator = [(NSArray *)loopEnumObject objectEnumerator];
+ }
+ [stackFrame setObject:enumerator forKey:FOR_STACK_ENUMERATOR];
+ [stackFrame setObject:startArg forKey:FOR_STACK_ENUM_VAR];
+ [blockVars setObject:[enumerator nextObject] forKey:startArg];
+ }
+
+ // Add parentLoop if it exists.
+ if (blockInfo) {
+ NSDictionary *parentLoop;
+ parentLoop = (NSDictionary *)[engine resolveVariable:FOR_LOOP_VARS]; // in case parent loop isn't in the first parent stack-frame.
+ if (parentLoop) {
+ [loopVars setObject:parentLoop forKey:FOR_PARENT_LOOP];
+ }
+ }
+
+ *newVariables = blockVars;
+ } else {
+ // Disable output for this block.
+ *blockStarted = YES;
+ NSMutableDictionary *stackFrame = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithBool:YES], FOR_STACK_DISABLED_OUTPUT,
+ [NSValue valueWithRange:markerRange], STACK_START_MARKER_RANGE,
+ [NSValue valueWithRange:*nextRange], STACK_START_REMAINING_RANGE,
+ nil];
+ [forStack addObject:stackFrame];
+ *outputEnabled = NO;
+ }
+ }
+
+ } else if ([marker isEqualToString:FOR_END]) {
+ // Decide whether to loop back or terminate.
+ if ([self currentBlock:blockInfo matchesTopOfStack:forStack]) {
+ NSMutableDictionary *frame = [forStack lastObject];
+
+ // Check to see if this was a block with an invalid looping condition.
+ NSNumber *disabledOutput = (NSNumber *)[frame objectForKey:FOR_STACK_DISABLED_OUTPUT];
+ if (disabledOutput && [disabledOutput boolValue]) {
+ *outputEnabled = YES;
+ *blockEnded = YES;
+ [forStack removeLastObject];
+ }
+
+ // This is the same loop that's on top of our stack. Check to see if we need to loop back.
+ BOOL loop = NO;
+ NSDictionary *blockVars = [blockInfo objectForKey:BLOCK_VARIABLES_KEY];
+ if ([blockVars count] == 0) {
+ *blockEnded = YES;
+ return nil;
+ }
+ NSMutableDictionary *loopVars = [[[blockVars objectForKey:FOR_LOOP_VARS] mutableCopy] autorelease];
+ BOOL reversed = [[loopVars objectForKey:FOR_REVERSE] boolValue];
+ NSEnumerator *loopEnum = [frame objectForKey:FOR_STACK_ENUMERATOR];
+ NSObject *newEnumValue = nil;
+ int currentIndex = [[loopVars objectForKey:FOR_LOOP_CURR_INDEX] intValue];
+ if (loopEnum) {
+ // Enumerator type.
+ newEnumValue = [loopEnum nextObject];
+ if (newEnumValue) {
+ loop = YES;
+ }
+ } else {
+ // Range type.
+ if (reversed) {
+ int minIndex = [[loopVars objectForKey:FOR_LOOP_START_INDEX] intValue];
+ if (currentIndex > minIndex) {
+ loop = YES;
+ }
+ } else {
+ int maxIndex = [[loopVars objectForKey:FOR_LOOP_END_INDEX] intValue];
+ if (currentIndex < maxIndex) {
+ loop = YES;
+ }
+ }
+ }
+
+ if (loop) {
+ // Set remainingRange from stack dict
+ *nextRange = [[frame objectForKey:STACK_START_REMAINING_RANGE] rangeValue];
+
+ // Set new currentIndex
+ if (reversed) {
+ currentIndex--;
+ } else {
+ currentIndex++;
+ }
+ [loopVars setObject:[NSNumber numberWithInt:currentIndex] forKey:FOR_LOOP_CURR_INDEX];
+
+ // Set new val for enumVar if specified
+ NSMutableDictionary *newVars = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ loopVars, FOR_LOOP_VARS,
+ nil];
+ if (newEnumValue) {
+ [newVars setObject:newEnumValue forKey:[frame objectForKey:FOR_STACK_ENUM_VAR]];
+ }
+
+ *newVariables = newVars;
+ } else {
+ // Don't need to do much here, since:
+ // 1. Each blockStack frame for a "for" has its own currentLoop dict.
+ // 2. Parent loop's enum-vars are still in place in the parent stack's vars.
+
+ // End block.
+ *blockEnded = YES;
+ [forStack removeLastObject];
+ }
+
+ // Return immediately.
+ return nil;
+ }
+ *blockEnded = YES;
+
+ } else if ([marker isEqualToString:SECTION_START]) {
+ if (args && [args count] == 1) {
+ *blockStarted = YES;
+
+ // Set up for-stack frame for this section.
+ NSMutableDictionary *stackFrame = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ [NSValue valueWithRange:markerRange], STACK_START_MARKER_RANGE,
+ nil];
+ [sectionStack addObject:stackFrame];
+ }
+
+ } else if ([marker isEqualToString:SECTION_END]) {
+ if ([self currentBlock:blockInfo matchesTopOfStack:sectionStack]) {
+ // This is the same section that's on top of our stack. Remove from stack.
+ [sectionStack removeLastObject];
+ }
+ *blockEnded = YES;
+
+ } else if ([marker isEqualToString:IF_START]) {
+ if (args && ([args count] == 1 || [args count] == 3)) {
+ *blockStarted = YES;
+
+ // Determine appropriate values for outputEnabled and for our if-stack frame.
+ BOOL elseEncountered = NO;
+ BOOL argTrue = NO;
+ if ([args count] == 1) {
+ argTrue = [self argIsTrue:[args objectAtIndex:0]];
+ } else if ([args count] == 2 && [[[args objectAtIndex:0] lowercaseString] isEqualToString:@"not"]) {
+ // e.g. if not x
+ argTrue = ![self argIsTrue:[args objectAtIndex:1]];
+ } else if ([args count] == 3) {
+ // Assumed to be of the form: operand comparison operand, e.g. x == y
+ NSString *firstArg = [args objectAtIndex:0];
+ NSString *secondArg = [args objectAtIndex:2];
+ BOOL firstTrue = [self argIsTrue:firstArg];
+ BOOL secondTrue = [self argIsTrue:secondArg];
+ int num1, num2;
+ BOOL firstNumeric, secondNumeric;
+ firstNumeric = [self argIsNumeric:firstArg intValue:&num1 checkVariables:YES];
+ secondNumeric = [self argIsNumeric:secondArg intValue:&num2 checkVariables:YES];
+ if (!firstNumeric) {
+ num1 = ([engine resolveVariable:firstArg]) ? 1 : 0;
+ }
+ if (!secondNumeric) {
+ num2 = ([engine resolveVariable:secondArg]) ? 1 : 0;
+ }
+ NSString *op = [[args objectAtIndex:1] lowercaseString];
+
+ if ([op isEqualToString:@"and"] || [op isEqualToString:@"&&"]) {
+ argTrue = (firstTrue && secondTrue);
+ } else if ([op isEqualToString:@"or"] || [op isEqualToString:@"||"]) {
+ argTrue = (firstTrue || secondTrue);
+ } else if ([op isEqualToString:@"="] || [op isEqualToString:@"=="]) {
+ argTrue = (num1 == num2);
+ } else if ([op isEqualToString:@"!="] || [op isEqualToString:@"<>"]) {
+ argTrue = (num1 != num2);
+ } else if ([op isEqualToString:@">"]) {
+ argTrue = (num1 > num2);
+ } else if ([op isEqualToString:@"<"]) {
+ argTrue = (num1 < num2);
+ } else if ([op isEqualToString:@">="]) {
+ argTrue = (num1 >= num2);
+ } else if ([op isEqualToString:@"<="]) {
+ argTrue = (num1 <= num2);
+ } else if ([op isEqualToString:@"\%"]) {
+ argTrue = ((num1 % num2) > 0);
+ }
+ }
+
+ BOOL shouldDisableOutput = *outputEnabled;
+ if (shouldDisableOutput && !argTrue) {
+ *outputEnabled = NO;
+ }
+
+ // Create variables.
+ NSMutableDictionary *ifVars = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithBool:argTrue], IF_ARG_TRUE,
+ [NSNumber numberWithBool:shouldDisableOutput], DISABLE_OUTPUT,
+ [NSNumber numberWithBool:elseEncountered], IF_ELSE_SEEN,
+ nil];
+
+ // Set up for-stack frame for this if-statement.
+ NSMutableDictionary *stackFrame = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ [NSValue valueWithRange:markerRange], STACK_START_MARKER_RANGE,
+ ifVars, IF_VARS,
+ nil];
+ [ifStack addObject:stackFrame];
+ }
+
+ } else if ([marker isEqualToString:ELSE]) {
+ if ([self currentBlock:blockInfo matchesTopOfStack:ifStack]) {
+ NSMutableDictionary *frame = [[ifStack lastObject] objectForKey:IF_VARS];
+ BOOL elseSeen = [[frame objectForKey:IF_ELSE_SEEN] boolValue];
+ BOOL argTrue = [[frame objectForKey:IF_ARG_TRUE] boolValue];
+ BOOL modifyOutput = [[frame objectForKey:DISABLE_OUTPUT] boolValue];
+
+ if (!elseSeen) {
+ if (modifyOutput) {
+ // Only make changes if we've not already seen an 'else' for this block,
+ // and if we're modifying output state at all.
+ *outputEnabled = !argTrue; // either turning it off, or turning it back on.
+ }
+
+ // Note that we've now seen the else marker.
+ [frame setObject:[NSNumber numberWithBool:YES] forKey:IF_ELSE_SEEN];
+ }
+ }
+
+ } else if ([marker isEqualToString:IF_END]) {
+ if ([self currentBlock:blockInfo matchesTopOfStack:ifStack]) {
+ NSMutableDictionary *frame = [[ifStack lastObject] objectForKey:IF_VARS];
+ BOOL modifyOutput = [[frame objectForKey:DISABLE_OUTPUT] boolValue];
+ if (modifyOutput) {
+ // If we're modifying output, it was enabled when this block started.
+ // Thus, it should be enabled after the block ends.
+ // If it's already enabled, this will have no harmful effect.
+ *outputEnabled = YES;
+ }
+
+ // End block.
+ [ifStack removeLastObject];
+ *blockEnded = YES;
+ }
+ *blockEnded = YES;
+
+ } else if ([marker isEqualToString:NOW]) {
+ return [NSDate date];
+
+ } else if ([marker isEqualToString:COMMENT_START]) {
+ // Work out if we need to start a block.
+ if (!args || [args count] == 0) {
+ *blockStarted = YES;
+
+ // Determine appropriate values for outputEnabled and for our stack frame.
+ BOOL shouldDisableOutput = *outputEnabled;
+ if (shouldDisableOutput) {
+ *outputEnabled = NO;
+ }
+
+ // Set up for-stack frame for this if-statement.
+ NSMutableDictionary *stackFrame = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ [NSValue valueWithRange:markerRange], STACK_START_MARKER_RANGE,
+ [NSNumber numberWithBool:shouldDisableOutput], DISABLE_OUTPUT,
+ nil];
+ [commentStack addObject:stackFrame];
+ }
+
+ } else if ([marker isEqualToString:COMMENT_END]) {
+ // Check this is block on top of stack.
+ if ([self currentBlock:blockInfo matchesTopOfStack:commentStack]) {
+ NSMutableDictionary *frame = [commentStack lastObject];
+ BOOL modifyOutput = [[frame objectForKey:DISABLE_OUTPUT] boolValue];
+ if (modifyOutput) {
+ // If we're modifying output, it was enabled when this block started.
+ // Thus, it should be enabled after the block ends.
+ // If it's already enabled, this will have no harmful effect.
+ *outputEnabled = YES;
+ }
+
+ // End block.
+ [commentStack removeLastObject];
+ *blockEnded = YES;
+ }
+ *blockEnded = YES;
+
+ } else if ([marker isEqualToString:LOAD]) {
+ if (args && [args count] > 0) {
+ for (NSString *className in args) {
+ Class class = NSClassFromString(className);
+ if (class && [(id)class isKindOfClass:[NSObject class]]) {
+ if ([class conformsToProtocol:@protocol(MGTemplateFilter)]) {
+ // Instantiate and load filter.
+ NSObject <MGTemplateFilter> *obj = [[[class alloc] init] autorelease];
+ [engine loadFilter:obj];
+ } else if ([class conformsToProtocol:@protocol(MGTemplateMarker)]) {
+ // Instantiate and load marker.
+ NSObject <MGTemplateMarker> *obj = [[[class alloc] initWithTemplateEngine:engine] autorelease];
+ [engine loadMarker:obj];
+ }
+ }
+ }
+ }
+
+ } else if ([marker isEqualToString:CYCLE]) {
+ if (args && [args count] > 0) {
+ // Check to see if it's an existing cycle.
+ NSString *rangeKey = NSStringFromRange(markerRange);
+ NSMutableDictionary *cycle = [cycles objectForKey:rangeKey];
+ if (cycle) {
+ NSArray *vals = [cycle objectForKey:CYCLE_VALUES];
+ int currIndex = [[cycle objectForKey:CYCLE_INDEX] intValue];
+ currIndex++;
+ if (currIndex >= [vals count]) {
+ currIndex = 0;
+ }
+ [cycle setObject:[NSNumber numberWithInt:currIndex] forKey:CYCLE_INDEX];
+ return [vals objectAtIndex:currIndex];
+ } else {
+ // New cycle. Create and output appropriately.
+ cycle = [NSMutableDictionary dictionaryWithCapacity:2];
+ [cycle setObject:[NSNumber numberWithInt:0] forKey:CYCLE_INDEX];
+ [cycle setObject:args forKey:CYCLE_VALUES];
+ [cycles setObject:cycle forKey:rangeKey];
+ return [args objectAtIndex:0];
+ }
+ }
+ } else if ([marker isEqualToString:SET]) {
+ if (args && [args count] == 2 && *outputEnabled) {
+ // Set variable arg1 to value arg2.
+ NSDictionary *newVar = [NSDictionary dictionaryWithObject:[args objectAtIndex:1]
+ forKey:[args objectAtIndex:0]];
+ if (newVar) {
+ *newVariables = newVar;
+ }
+ }
+ }
+
+ return nil;
+}
+
+
+- (BOOL)currentBlock:(NSDictionary *)blockInfo matchesTopOfStack:(NSMutableArray *)stack
+{
+ if (blockInfo && [stack count] > 0) { // end-tag should always have blockInfo, and correspond to a stack frame.
+ NSDictionary *frame = [stack lastObject];
+ NSRange stackSectionRange = [[frame objectForKey:STACK_START_MARKER_RANGE] rangeValue];
+ NSRange thisSectionRange = [[blockInfo objectForKey:BLOCK_START_MARKER_RANGE_KEY] rangeValue];
+ if (NSEqualRanges(stackSectionRange, thisSectionRange)) {
+ return YES;
+ }
+ }
+ return NO;
+}
+
+
+- (BOOL)argIsTrue:(NSString *)arg
+{
+ BOOL argTrue = NO;
+ if (arg) {
+ NSObject *val = [engine resolveVariable:arg];
+ if (val) {
+ if ([val isKindOfClass:[NSNumber class]]) {
+ argTrue = [(NSNumber *)val boolValue];
+ } else {
+ argTrue = YES;
+ }
+ }
+ }
+ return argTrue;
+}
+
+
+- (BOOL)argIsNumeric:(NSString *)arg intValue:(int *)val checkVariables:(BOOL)checkVars
+{
+ BOOL numeric = NO;
+ int value = 0;
+
+ if (arg && [arg length] > 0) {
+ if ([[arg substringToIndex:1] isEqualToString:@"0"] || [arg intValue] != 0) {
+ numeric = YES;
+ value = [arg intValue];
+ } else if (checkVars) {
+ // Check to see if arg is a variable with an intValue.
+ NSObject *argObj = [engine resolveVariable:arg];
+ NSString *argStr = [NSString stringWithFormat:@"%@", argObj];
+ if (argObj && [argObj respondsToSelector:@selector(intValue)] &&
+ [self argIsNumeric:argStr intValue:&value checkVariables:NO]) { // avoid recursion
+ numeric = YES;
+ }
+ }
+ }
+
+ if (val) {
+ *val = value;
+ }
+ return numeric;
+}
+
+
+- (void)engineFinishedProcessingTemplate
+{
+ // Clean up stacks etc.
+ [forStack release];
+ forStack = [[NSMutableArray alloc] init];
+ [sectionStack release];
+ sectionStack = [[NSMutableArray alloc] init];
+ [ifStack release];
+ ifStack = [[NSMutableArray alloc] init];
+ [commentStack release];
+ commentStack = [[NSMutableArray alloc] init];
+ cycles = [[NSMutableDictionary alloc] init];
+}
+
+
+@end