PageRenderTime 237ms CodeModel.GetById 3ms app.highlight 222ms RepoModel.GetById 1ms app.codeStats 0ms

/STSTemplateEngine.m

https://bitbucket.org/sunrisetel.co.jp/sts-template-engine
Objective C | 1610 lines | 746 code | 106 blank | 758 comment | 165 complexity | 1b86bfc2ba3c91efbaa59244ae423dc0 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1//
   2//	STSTemplateEngine.m
   3//	STS Template Engine ver 1.00
   4//
   5//	A universal template engine with conditional template expansion support.
   6//
   7//	Created by benjk on 6/28/05.
   8//	Copyright 2005 Sunrise Telephone Systems Ltd. All rights reserved.
   9//
  10//	This software is released as open source under the terms of the General
  11//	Public License (GPL) version 2.  A copy of the GPL license should have
  12//	been distributed along with this software.  If you have not received a
  13//	copy of the GPL license, you can obtain it from Free Software Foundation
  14//	Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
  15//
  16//	Permission is hereby granted to link this code against Apple's proprietary
  17//	Cocoa Framework, regardless of the limitations in the GPL license. The
  18//	Copyright notice and credits must be preserved at all times in every
  19//	redistributed copy and any derivative work of this software.
  20//
  21//	THIS SOFTWARE  IS PROVIDED "AS IS"  WITHOUT  ANY  WARRANTY  OF  ANY  KIND,
  22//	WHETHER  EXPRESSED OR IMPLIED,  INCLUDING  BUT NOT LIMITED TO  ANY IMPLIED
  23//	WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR ANY PARTICULAR PURPOSE.  THE
  24//	ENTIRE RISK  AS TO THE QUALITY AND PERFORMANCE OF THIS SOFTWARE  LIES WITH
  25//	THE LICENSEE.  FOR FURTHER INFORMATION PLEASE REFER TO THE GPL VERSION 2.
  26//
  27//	For projects and software products for which the terms of the GPL license
  28//	are not suitable, alternative licensing can be obtained directly from
  29//	Sunrise Telephone Systems Ltd. at http://www.sunrise-tel.com
  30//
  31
  32#import <LIFO.h>
  33#import <STSStringOps.h>
  34#import <STSNullError.h>
  35
  36#import "STSTemplateEngine.h"
  37
  38#define TODO {}; /* dummy statement for future sections */
  39
  40// ---------------------------------------------------------------------------
  41//	String literals
  42// ---------------------------------------------------------------------------
  43
  44#define kEmptyString @""
  45#define kLineFeed @"\n"
  46
  47// ---------------------------------------------------------------------------
  48//	Macro for use in if-clauses when testing NSRange variables
  49// ---------------------------------------------------------------------------
  50
  51#define found(x) (x.location != NSNotFound)
  52
  53// ---------------------------------------------------------------------------
  54//	Macros for testing if a key is present in an NSDictionary
  55// ---------------------------------------------------------------------------
  56
  57#define keyDefined(x,y) ([x objectForKey:y] != nil)
  58#define keyNotDefined(x,y) ([x objectForKey:y] == nil)
  59
  60
  61// ---------------------------------------------------------------------------
  62//  P r i v a t e   F u n c t i o n s
  63// ---------------------------------------------------------------------------
  64
  65// ---------------------------------------------------------------------------
  66// Private Function:  classExists(className)
  67// ---------------------------------------------------------------------------
  68//
  69// Returns YES if class className exists, otherwise NO.
  70
  71BOOL classExists (NSString *className) {
  72	Class classPtr = NSClassFromString(className);
  73	return (classPtr != nil);
  74} // end function
  75
  76
  77// ---------------------------------------------------------------------------
  78//  P r i v a t e   C a t e g o r i e s
  79// ---------------------------------------------------------------------------
  80
  81@interface NSFileManager (STSTemplateEnginePrivateCategory1)
  82
  83// ---------------------------------------------------------------------------
  84// Private Method:  isRegularFileAtPath:
  85// ---------------------------------------------------------------------------
  86//
  87// Returns YES if the file specified in path is a regular file, or NO if it is
  88// not. This method traverses symbolic links.
  89
  90- (BOOL)isRegularFileAtPath:(NSString *)path;
  91
  92@end
  93
  94@implementation NSFileManager (STSTemplateEnginePrivateCategory1);
  95
  96// ---------------------------------------------------------------------------
  97// Instance Method:  isRegularFileAtPath:
  98// ---------------------------------------------------------------------------
  99//
 100// description:
 101//  Returns YES if the file specified in path is a regular file, or NO if it
 102//  is not. If path specifies a symbolic link, this method traverses the link
 103//  and returns YES or NO based on the existence of the file at the link
 104//  destination. If path begins with a tilde, it must first be expanded with
 105//  stringByExpandingTildeInPath, or this method will return NO.
 106//
 107// pre-conditions:
 108//  receiver must be an object of class NSFileManager.
 109//  path must be a valid POSIX pathname.
 110//
 111// post-conditions:
 112//  return value is of type BOOL and contains YES if the file at path is a
 113//  regular file or if it is a symbolic link pointing to a regular file.
 114//  Otherwise, NO is returned.
 115
 116- (BOOL)isRegularFileAtPath:(NSString *)path
 117{
 118	return ([[[self fileAttributesAtPath:path traverseLink:YES] fileType]
 119		isEqualToString:@"NSFileTypeRegular"]);
 120} // end method
 121
 122@end // private category
 123
 124
 125@interface NSString (STSTemplateEnginePrivateCategory2)
 126
 127// ===========================================================================
 128// NOTE regarding the use of string literals with non 7-bit ASCII characters
 129// ---------------------------------------------------------------------------
 130//		The Cocoa runtime system always interprets string literals as if they
 131// were encoded in the system's default string encoding which is dependent on
 132// the current locale, regardless of their true encoding. Thus, if a source
 133// file is saved in a different encoding, all its string literals will contain
 134// data encoded with a different encoding than the encoding for the current
 135// locale but the runtime system will nevertheless interpret the data as
 136// having been encoded in the encoding for the current locale. Xcode does not
 137// adjust for this and as a result, characters which are not 7-bit ASCII
 138// characters will be garbled.
 139//		The default start and end tags of the template engine are non 7-bit
 140// ASCII characters. If they are passed as string literals and the source code
 141// is not in the same encoding as the current locale at the time the source
 142// code is compiled, the template engine will not work properly. Therefore
 143// the tags have to be embedded in an encoding-safe manner. This is what the
 144// following methods defaultStartTag and defaultEndTag are for.
 145// ===========================================================================
 146
 147// ---------------------------------------------------------------------------
 148// Private Method:  defaultStartTag
 149// ---------------------------------------------------------------------------
 150//
 151// Returns a new NSString initialised with the template engine's default start
 152// tag (the percent sign followed by the opening chevrons, "%�"). This method
 153// is source file encoding-safe.
 154
 155+ (NSString *)defaultStartTag;
 156
 157// ---------------------------------------------------------------------------
 158// Private Method:  defaultEndTag
 159// ---------------------------------------------------------------------------
 160//
 161// Returns a new NSString initialised with the template engine's default end
 162// tag (the closing chevrons, "�"). This method is source file encoding-safe.
 163
 164+ (NSString *)defaultEndTag;
 165
 166// ---------------------------------------------------------------------------
 167// Private Method:  stringByExpandingPlaceholdersWithStartTag:andEndTag:
 168//                  usingDictionary:errorsReturned:lineNumber:
 169// ---------------------------------------------------------------------------
 170//
 171// This method is invoked by the public methods below in order to expand
 172// individual lines in a template string or template file. It returns the
 173// receiver with tagged placeholders expanded. Placeholders are recognised by
 174// start and end tags passed and they are expanded by using key/value pairs in
 175// the dictionary passed. An errorLog is returned to indicate whether the
 176// expansion has been successful.
 177//		If a placeholder cannot be expanded, a partially expanded string is
 178// returned with one or more error messages inserted or appended and an
 179// error description of class TEError is added to an NSArray returned in
 180// errorLog. lineNumber is used to set the error description's line number.
 181
 182- (NSString *)stringByExpandingPlaceholdersWithStartTag:(NSString *)startTag
 183											  andEndTag:(NSString *)endTag
 184										usingDictionary:(NSDictionary *)dictionary
 185										 errorsReturned:(NSArray **)errorLog
 186											 lineNumber:(unsigned)lineNumber;
 187@end
 188
 189@implementation NSString (STSTemplateEnginePrivateCategory2);
 190
 191// ---------------------------------------------------------------------------
 192// Class Method:  defaultStartTag
 193// ---------------------------------------------------------------------------
 194//
 195// Returns a new NSString initialised with the template engine's default start
 196// tag (the percent sign followed by the opening chevrons, "%�"). This method
 197// is source file encoding-safe.
 198
 199+ (NSString *)defaultStartTag
 200{
 201	NSData *data;
 202	// avoid the use of string literals to be source file encoding-safe
 203	// use MacOS Roman hex codes for percent and opening chevrons "%�"
 204	char octets[2] = { 0x25, 0xc7 };
 205	data = [NSData dataWithBytes:&octets length:2];
 206	return [[[NSString alloc] initWithData:data encoding:NSMacOSRomanStringEncoding] autorelease];
 207} // end method
 208
 209// ---------------------------------------------------------------------------
 210// Class Method:  defaultEndTag
 211// ---------------------------------------------------------------------------
 212//
 213// Returns a new NSString initialised with the template engine's default end
 214// tag (the closing chevrons, "�"). This method is source file encoding-safe.
 215
 216+ (NSString *)defaultEndTag
 217{
 218	NSData *data;
 219	// avoid the use of string literals to be source file encoding-safe
 220	// use MacOS Roman hex code for closing chevrons "�"
 221	char octet = 0xc8;
 222	data = [NSData dataWithBytes:&octet length:1];
 223	return [[[NSString alloc] initWithData:data encoding:NSMacOSRomanStringEncoding] autorelease];
 224} // end method
 225
 226// ---------------------------------------------------------------------------
 227// Instance Method:  stringByExpandingPlaceholdersWithStartTag:andEndTag:
 228//                   usingDictionary:errorsReturned:lineNumber:
 229// ---------------------------------------------------------------------------
 230//
 231// description:
 232//  Returns the receiver with tagged placeholders expanded. Placeholders are
 233//  recognised by start and end tags passed and they are expanded by using
 234//  key/value pairs in the dictionary passed. A status code passed by
 235//  reference is set to indicate whether the expansion has been successful.
 236//  If a placeholder cannot be expanded, a partially expanded string is
 237//  returned with one or more error messages inserted or appended and an
 238//  error description of class TEError is added to an NSArray returned in
 239//  errorLog. lineNumber is used to set the error description's line number.
 240//
 241// pre-conditions:
 242//  startTag and endTag must not be empty strings and must not be nil.
 243//  dictionary contains keys to be replaced by their respective values.
 244//
 245// post-conditions:
 246//  Return value contains the receiver with all placeholders expanded for
 247//  which the dictionary contains keys. If there are no placeholders in the
 248//  receiver, the receiver will be returned unchanged.
 249//  Any placeholders for which the dictionary does not contain keys will
 250//  remain in their tagged placeholder form and have an error message
 251//  appended to them in the returned string. If a placeholder without
 252//  a closing tag is found, the offending placeholder will remain in its
 253//  incomplete start tag only form and have an error message appended to it.
 254//  Any text following the offending start tag only placeholder will not be
 255//  processed. An NSArray with error descriptions of class TEError will be
 256//  passed in errorLog to the caller.
 257//
 258// error-conditions:
 259//  If start tag is empty or nil, an exception StartTagEmptyOrNil will be
 260//  raised. If end tag is empty or nil, an exception EndTagEmptyOrNil will be
 261//  raised. It is the responsibility of the caller to catch the exception.
 262
 263- (NSString *)stringByExpandingPlaceholdersWithStartTag:(NSString *)startTag
 264											  andEndTag:(NSString *)endTag
 265										usingDictionary:(NSDictionary *)dictionary
 266										 errorsReturned:(NSArray **)errorLog
 267											 lineNumber:(unsigned)lineNumber
 268{	
 269	NSMutableString *remainder = [NSMutableString stringWithCapacity:[self length]];
 270	NSMutableString *result = [NSMutableString stringWithCapacity:[self length]];
 271	NSMutableString *placeholder = [NSMutableString stringWithCapacity:20];
 272	NSMutableString *value = [NSMutableString stringWithCapacity:20];
 273	NSMutableArray *_errorLog = [NSMutableArray arrayWithCapacity:5];
 274	#define errorsHaveOcurred ([_errorLog count] > 0)
 275	NSException* exception;
 276	NSRange tag, range;
 277	TEError *error;
 278
 279	// check if start tag is nil or empty
 280	if ((startTag == nil) || ([startTag length] == 0)) {
 281		// this is a fatal error -- bail out by raising an exception
 282		[NSException exceptionWithName:@"TEStartTagEmptyOrNil"
 283								reason:@"startTag is empty or nil" userInfo:nil];
 284		[exception raise];
 285	} // end if
 286	  // check if end tag is nil or empty
 287	if ((endTag == nil) || ([endTag length] == 0)) {
 288		// this is a fatal error -- bail out by raising an exception
 289		[NSException exceptionWithName:@"TEEndTagEmptyOrNil"
 290								reason:@"endTag is empty or nil" userInfo:nil];
 291		[exception raise];
 292	} // end if
 293	
 294	// initialise the source string
 295	[remainder setString:self];
 296	// look for the initial start tag
 297	tag = [remainder rangeOfString:startTag];
 298	// if we find a start tag ...
 299	if found(tag) {
 300		// continue for as long as we find start tags
 301		while found(tag) {
 302			// append substring before start tag to the result string
 303			[result appendString:[remainder substringToIndex:tag.location]];
 304			// remove preceeding text and start tag from the remainder
 305			range.location = 0; range.length = tag.location+tag.length;
 306			[remainder deleteCharactersInRange:range];
 307			// look for the end tag
 308			tag = [remainder rangeOfString:endTag];
 309			// if we did find the end tag ...
 310			if found(tag) {
 311				// extract the placeholder
 312				[placeholder setString:[remainder substringToIndex:tag.location]];
 313				// use placeholder as key for dictionary lookup
 314				value = [dictionary objectForKey:placeholder];
 315				// if the lookup returned nil (key not found)
 316				if (value == nil) {
 317					// append the tagged placeholder and an error message to the result string
 318					[result appendFormat:@"%@%@%@ *** ERROR: undefined key *** ", startTag, placeholder, endTag];
 319					// this is an error - create a new error description
 320					error = [TEError error:TE_UNDEFINED_PLACEHOLDER_FOUND_ERROR
 321									inLine:lineNumber atToken:TE_PLACEHOLDER];
 322					[error setLiteral:placeholder];
 323					// and add this error to the error log
 324					[_errorLog addObject:error];
 325					// log this error to the console
 326					[error logErrorMessageForTemplate:kEmptyString];
 327				}
 328				// if the lookup returns a value for the key ...
 329				else {
 330					// append the key's value to the result string
 331					[result appendString:value];
 332				} // end if
 333				  // remove placeholder and end tag from the remainder
 334				range.location = 0; range.length = tag.location+tag.length;
 335				[remainder deleteCharactersInRange:range];
 336			} // end if
 337			  // if we don't find any end tag ...
 338			else {
 339				// append the start tag and an error message to the result string
 340				[result appendFormat:@"%@ *** ERROR: end tag missing *** ", startTag];
 341				// remove all remaining text from the source string to force exit of while loop
 342				[remainder setString:kEmptyString];
 343				// this is an error - create a new error description
 344				error = [TEError error:TE_EXPECTED_ENDTAG_BUT_FOUND_TOKEN_ERROR
 345								inLine:lineNumber atToken:TE_EOL];
 346				// and add this error to the error log
 347				[_errorLog addObject:error];
 348				// log this error to the console
 349				[error logErrorMessageForTemplate:kEmptyString];
 350			} // end if
 351			  // look for follow-on start tag to prepare for another parsing cycle
 352			tag = [remainder rangeOfString:startTag];
 353		} // end while
 354		// if there are still characters in the remainder ...
 355		if ([remainder length] > 0) {
 356			// append the remaining characters to the result
 357			[result appendString:remainder];
 358		} // end if
 359	}
 360	// if we don't find a start tag ...
 361	else {
 362		// then there is nothing to expand and we return the original as is
 363		result = remainder;
 364	} // end if
 365	// if there were any errors ...
 366	if (errorsHaveOcurred) {
 367		// pass the error log back to the caller
 368		*errorLog = _errorLog;
 369		// get rid of the following line after testing
 370		NSLog(@"errors have ocurred while expanding placeholders in string");
 371	}
 372	// if there were no errors ...
 373	else {
 374		// pass nil in the errorLog back to the caller
 375		*errorLog = nil;
 376	} // end if
 377	// return the result string
 378	return result;
 379	#undef errorsHaveOcurred
 380} // end method
 381
 382@end // private category
 383
 384
 385// ---------------------------------------------------------------------------
 386//  P r i v a t e   C l a s s e s
 387// ---------------------------------------------------------------------------
 388
 389@interface TEFlags : NSObject {
 390	// instance variable declaration
 391	@public unsigned consumed, expand, condex;
 392} // end var
 393//
 394// public method: return new flags, allocated, initialised and autoreleased.
 395// initial values: consumed is false, expand is true, condex is false.
 396+ (TEFlags *)newFlags;
 397@end
 398
 399@implementation TEFlags
 400// private method: initialise instance
 401- (id)init {
 402	self = [super init];
 403	return self;
 404} // end method
 405
 406// private method: deallocate instance
 407- (void)dealloc {
 408	[super dealloc];
 409} // end method
 410
 411// public method: return new flags, allocated, initialised and autoreleased.
 412// initial values: consumed is false, expand is true, condex is false.
 413+ (TEFlags *)newFlags {
 414	TEFlags *thisInstance = [[[TEFlags alloc] init] autorelease];
 415	// initialise flags
 416	thisInstance->consumed = false;
 417	thisInstance->expand = true;
 418	thisInstance->condex = false;
 419	return thisInstance;
 420} // end method
 421
 422@end // private class
 423
 424
 425// ---------------------------------------------------------------------------
 426//  P u b l i c   C a t e g o r y   I m p l e m e n t a t i o n
 427// ---------------------------------------------------------------------------
 428
 429@implementation NSString (STSTemplateEngine);
 430
 431// ---------------------------------------------------------------------------
 432// Class Method:  stringByExpandingTemplate:usingDictionary:errorsReturned:
 433// ---------------------------------------------------------------------------
 434//
 435// description:
 436//  Invokes method stringByExpandingTemplate:withStartTag:andEndTag:
 437//  usingDictionary:errorsReturned: with the template engine's default tags:
 438//  startTag "%�" and endTag "�". This method is source file encoding-safe.
 439
 440+ (id)stringByExpandingTemplate:(NSString *)templateString
 441				usingDictionary:(NSDictionary *)dictionary
 442				 errorsReturned:(NSArray **)errorLog
 443{
 444	return [NSString stringByExpandingTemplate:templateString
 445								  withStartTag:[NSString defaultStartTag]
 446									 andEndTag:[NSString defaultEndTag]
 447							   usingDictionary:dictionary
 448								errorsReturned:errorLog];
 449} // end method
 450
 451// ---------------------------------------------------------------------------
 452// Class Method:  stringByExpandingTemplate:withStartTag:andEndTag:
 453//                usingDictionary:errorsReturned:
 454// ---------------------------------------------------------------------------
 455//
 456// description:
 457//  Returns a new NSString made by expanding templateString. Lines starting
 458//  with a % character are interpreted as comments or directives for the
 459//  template engine. Directives are %IF, %IFNOT, %IFEQ, %IFNEQ, %IFDEF,
 460//	%IFNDEF, %ELSIF, %ELSIFNOT, %ELSIFEQ, %ELSIFNEQ, %ELSIFDEF, %ELSIFNDEF,
 461//	%ELSE, %ENDIF, %DEFINE, %UNDEF, %LOG, %ECHO and %DEBUG.
 462//	Any line starting with a % character that is not part of a valid directive
 463//	nor part of a start tag is treated as a comment. Comment lines are not
 464//	copied to the result returned.
 465//		The %IF, %IFNOT, %IFEQ, %IFNEQ, %IFDEF, %IFNDEF, %ELSIF, %ELSEIFNOT,
 466//	%ELSIFEQ, %ELSIFNEQ, %ELSIFDEF, %ELSIFNDEF, %ELSE and %ENDIF directives
 467//	are for conditional template expansion. Any %IF, %IFNOT, %IFEQ, %IFNEQ,
 468//	%IFDEF or %IFNDEF directive opens a new if-block and a new if-branch
 469//	within the new if-block. Any %ELSIF, %ELSIFNOT, %ELSIFEQ, %ELSIFNEQ,
 470//	%ELSIFDEF or %ELSEIFNDEF directive opens a new else-if branch in the
 471//	current if-block. An %ELSE directive opens an else-branch in the current
 472//	if-block. An %ENDIF directive closes the current if-block.
 473//		An identifier following %IF is interpreted as a key which is looked
 474//	up in the dictionary. If the key's value represents logical true, the
 475//	subsequent lines are expanded until an elsif-, else- or endif-directive
 476//	is found or another if-block is opened.
 477//		An identifier following %IFNOT is interpreted as a key which is
 478//	looked up in the dictionary. If the key's value does not represent logical
 479//	true, the subsequent lines are expanded until an elsif-, else- or endif-
 480//	directive is found or another if-block is opened.
 481//		A key's value represents logical true if its all-lowercase
 482//	representation is "1", "yes" or "true".
 483//		An identifier following %IFEQ is interpreted as a key which is looked
 484//	up in the dictionary and its value is then compared to the operand that
 485//	follows the key. If the key's value and the operand match, the subsequent
 486//	lines are expanded until an  elsif-, else- or endif-directive is found or
 487//	another if block is opened.
 488//		An identifier following %IFNEQ is interpreted as a key which is
 489//	looked up in the dictionary and its value is then compared to the operand
 490//	that follows the key. If the key's value and the operand do not match,
 491//	the subsequent lines are expanded until an  elsif-, else- or endif-
 492//	directive is found or another if block is opened.
 493//		An identifier following %IFDEF is interpreted as a key which is
 494//	looked up in the dictionary. If the key is found in the dictionary, the
 495//	subsequent lines are expanded until an  elsif-, else- or endif-
 496//	directive is found or another if-block is opened.
 497//		An identifier following %IFNDEF is interpreted as a key which is
 498//	looked up in the dictionary. If the key is not found in the dictionary,
 499//	the subsequent lines are expanded until an  elsif-, else- or endif-
 500//	directive is found or another if-block is opened.
 501//		An %ELSEIF, %ELSIFNOT, %ELSIFEQ, %ELSIFNEQ, %ELSIFDEF or %ELSIFNDEF
 502//	directive opens an else-if branch in the current if block. An else-if
 503//	directive will only be evaluated if no prior if- or else-if branch was
 504//	expanded. The expression following such an else-if directive is evaluated
 505//	in the same way an expression following the corresponding if-directive is
 506//	evaluated.
 507//		An %ELSE directive opens an else branch in the current if-block. The
 508//	lines following an else branch will be expanded if no prior if- or else-if
 509//	branch was expanded. Lines are expanded until an %ENDIF directive is found
 510//	or another if-block is opened.
 511//		Any section outside any an if-block is expanded unconditionally,
 512//	excluding comment lines which are always ignored. If-blocks may be nested.
 513//		A %DEFINE directive followed by a key name causes that key to be added
 514//  to the dictionary. If any text follows the key name, that text is stored
 515//  as the key's value, otherwise the key's value will be an empty string. If
 516//  the key already exists in the dictionary then it's value will be replaced.
 517//		An %UNDEF directive followed by a key name causes that key to be
 518//	removed from the dictionary.
 519//		A %LOG directive followed by a key will cause that key and its value
 520//	to be logged to the console, which may be used for troubleshooting.
 521//		The %ECHO and %DEBUG directives are ignored as they have not been
 522//	implemented yet.
 523//		Any lines to be expanded which contain tagged placeholders are copied
 524//  to the result returned with the tagged placeholders expanded. Any lines
 525//  to be expanded which do not contain any placeholders are copied verbatim
 526//  to the result returned. Placeholders are recognised by start and end tags
 527//  passed in startTag and endTag and they are expanded by using key/value
 528//  pairs in dictionary. Placeholder names starting with an underscore "_"
 529//	character are reserved for automatic placeholder variables.
 530//		Automatic placeholder variables are automatically entered into the
 531//	dictionary by the template engine. Currently defined automatic placeholder
 532//	variables are: _timestamp, _uniqueID and _hostname.
 533//		The value of _timestamp is a datetime string with the system's current
 534//	date and time value formatted to follow the international string
 535//	representation format YYYY-MM-DD HH:MM:SSHHMM at the time the method is
 536//	invoked.
 537//		The value of _uniqueID is a globally unique ID string as generated by
 538//	method globallyUniqueString of class NSProcessInfo. For each invocation of
 539//	stringByExpandingTemplate: withStartTag:andEndTag:usingDictionary:
 540//	errorsReturned: a new value for _uniqueID is generated.
 541//		The value of _hostname is the system's host name at the time the
 542//	method is invoked.
 543//		On MacOS X 10.4 "Tiger" (and later) locale information is available
 544//	through automatic placeholder variables _userCountryCode, _userLanguage,
 545//	_systemCountryCode and _systemLanguage.
 546//		For every placeholder that cannot be expanded, a partially expanded
 547//	line is copied to the result returned with one or more error messages
 548//	inserted or appended.
 549//		An NSArray containing descriptions of any errors that may have ocurred
 550//  during expansion is passed back in errorLog to the caller. If expansion
 551//  completed withough any errors, then errorLog is set to nil.
 552//
 553// pre-conditions:
 554//  templateString is an NSString containing the template to be expanded.
 555//  startTag and endTag must not be empty strings and must not be nil.
 556//  dictionary contains keys to be replaced by their respective values.
 557//  errorLog is an NSArray passed by reference.
 558//
 559// post-conditions:
 560//  Return value contains a new NSString made by expanding templateString
 561//  with lines outside of %IF blocks expanded unconditionally and lines
 562//  inside of %IF blocks expanded conditionally. If any errors are
 563//  encountered during template expansion, errorLog will be set to an NSArray
 564//  containing error descriptions of class TEError for each error or warning.
 565//  If there were neither errors nor warnings during template expansion,
 566//  errorLog is set to nil.
 567//		Template expansion errors are treated gracefully. Various error
 568//  recovery strategies ensure that expansion can continue even in the event
 569//  of errors encountered in the template and error descriptions are added to
 570//  errorLog.
 571//		If an if-directive is not followed by an expression, the entire
 572//	if-block opened by that directive will be ignored, that is all lines will
 573//	be ignored until a matching endif-directive is found. An error description
 574//	is added to errorLog and an error message is written to the console log.
 575//		If an else-if directive is not followed by an expression, the else-if
 576//	branch opened by that directive will be ignored, that is all lines will be
 577//	ignored until an else-if-, else- or endif-directive is found or another
 578//	if-block is opened.
 579//		If any else-if-, else- or endif-directive appears without a prior
 580//  if-directive, then the line with that directive will be ignored and
 581//	expansion continues accordingly. An error description is added to
 582//	errorLog and an error message is written to the console log.
 583//		If the end of the template file is reached before an if-block was
 584//  closed by an endif-directive, the block is deemed to have been closed
 585//	implicitly. An error description is added to errorLog and an error
 586//	message is written to the console log.
 587//		Any placeholders for which the dictionary does not contain keys will
 588//  remain in their tagged placeholder form and have an error message
 589//  appended to them in the corresponding expanded line. Expansion continues
 590//  accordingly. An error description is added to errorLog and logged to the
 591//  console.
 592//		If a placeholder without a closing tag is found, the offending
 593//  placeholder will remain in its incomplete start tag only form, have an
 594//  error message appended to it and the remainder of the line is not
 595//  processed. Expansion then continues in the line that follows. An error
 596//  description is added to errorLog and logged to the console.
 597//		When template expansion is completed and any errors have occurred,
 598//  the NSArray returned in errorLog contains all error descriptions in the
 599//  order they ocurred.
 600//
 601// error-conditions:
 602//  If startTag is empty or nil, an exception TEStartTagEmptyOrNil will be
 603//  raised. If end tag is empty or nil, an exception TEEndTagEmptyOrNil will
 604//  be raised. It is the responsibility of the caller to catch the exception.
 605
 606+ (id)stringByExpandingTemplate:(NSString *)templateString
 607				   withStartTag:(NSString *)startTag
 608					  andEndTag:(NSString *)endTag
 609				usingDictionary:(NSDictionary *)dictionary
 610				 errorsReturned:(NSArray **)errorLog
 611{
 612	NSArray *template = [templateString arrayBySeparatingLinesUsingEOLmarkers];
 613	NSEnumerator *list = [template objectEnumerator];
 614	NSMutableString *result = [NSMutableString stringWithCapacity:[templateString length]];
 615	NSCharacterSet *whitespaceSet = [NSCharacterSet whitespaceCharacterSet];
 616	NSMutableDictionary *_dictionary = [NSMutableDictionary dictionaryWithDictionary:dictionary];
 617	NSProcessInfo *processInfo = [NSProcessInfo processInfo];
 618	
 619	// IF/IF-NOT groups:
 620	// Each if-directive is processed by the same code as the directive's
 621	// complement directive. For example, %IF and %IFNOT are processed by the
 622	// same code in the parser. In order to be able to determine whether the
 623	// directive was a complement or not, a complement flag is used. Upon entry
 624	// into the parser loop, the complement flag is set whenever an %IFNOT,
 625	// %IFNEQ, %IFNDEF, %ELSIFNEQ or %ELSIFNDEF directive is found, and it is
 626	// cleared whenever an %IF, %IFEQ, %IFDEF, %ELSIFEQ or %ELSIFDEF is found.
 627	// When evaluating the expression following an if- or else-if directive a
 628	// logical XOR of the complement flag is applied to the expression.
 629	unsigned complement = 0;
 630	
 631	// Nested %IF blocks:
 632	// When a new %IF block is opened by %IF, %IFEQ, %IFNEQ, %IFDEF or %IFNDEF,
 633	// the current state held in flags is saved to stack and a new set of flags
 634	// is initialised. When an open if-block is closed by %ENDIF, the state of
 635	// the previous if-block is restored from stack to flags.
 636	TEFlags *flags = [TEFlags newFlags];
 637	LIFO *stack = [LIFO stackWithCapacity:8];
 638	
 639	// Error log:
 640	NSMutableArray *_errorLog = [NSMutableArray arrayWithCapacity:5];
 641	#define errorsHaveOcurred ([_errorLog count] > 0)
 642	NSMutableArray *lineErrors = [NSMutableArray arrayWithCapacity:2];
 643	#define lineErrorsHaveOcurred ([lineErrors count] > 0)
 644	TEError *error;
 645
 646	// Temporary string variables and line counter
 647	NSString *line, *remainder, *keyword, *key, *value, *operand;
 648	unsigned len, lineNumber = 0;
 649	
 650	// -----------------------------------------------------------------------
 651	//  P r e c o n d i t i o n s   c h e c k
 652	// -----------------------------------------------------------------------
 653
 654	NSException *exception;
 655	// check if start tag is nil or empty
 656	if ((startTag == nil) || ([startTag length] == 0)) {
 657		// this is a fatal error -- bail out by raising an exception
 658		[NSException exceptionWithName:@"TEStartTagEmptyOrNil"
 659								reason:@"startTag is empty or nil" userInfo:nil];
 660		[exception raise];
 661	} // end if
 662	  // check if end tag is nil or empty
 663	if ((endTag == nil) || ([endTag length] == 0)) {
 664		// this is a fatal error -- bail out by raising an exception
 665		[NSException exceptionWithName:@"TEEndTagEmptyOrNil"
 666								reason:@"endTag is empty or nil" userInfo:nil];
 667		[exception raise];
 668	} // end if	
 669	
 670	// -----------------------------------------------------------------------
 671	//  A u t o m a t i c   p l a c e h o l d e r   v a r i a b l e s
 672	// -----------------------------------------------------------------------
 673	
 674	// Entering automatic placeholder variables into the dictionary:
 675	[_dictionary setObject:[[NSDate date] description] forKey:@"_timestamp"];
 676	[_dictionary setObject:[processInfo globallyUniqueString] forKey:@"_uniqueID"];
 677	[_dictionary setObject:[processInfo hostName] forKey:@"_hostname"];
 678	
 679	// -----------------------------------------------------------------------
 680	//  L o c a l e   i n f o r m a t i o n
 681	// -----------------------------------------------------------------------
 682	// Sometimes Apple have got their priorities dead wrong. Without ugly
 683	// hacks this information is only available as of MacOS X 10.4 "Tiger".
 684	
 685	// Define locale specific variables if the NSLocale class is available ...
 686	if (classExists(@"NSLocale")) {
 687		NSLocale *locale;
 688		// user's locale settings
 689		locale = [NSLocale currentLocale];
 690		[_dictionary setObject:[locale objectForKey:NSLocaleCountryCode] forKey:@"_userCountryCode"];
 691		[_dictionary setObject:[locale objectForKey:NSLocaleLanguageCode] forKey:@"_userLanguage"];
 692		// system locale settings
 693		locale = [NSLocale systemLocale];
 694		key = [locale objectForKey:NSLocaleCountryCode];
 695		// if NSLocaleCountryCode is undefined for the system locale ...
 696		if (key == nil) {
 697			// set the variable to empty string
 698			[_dictionary setObject:kEmptyString forKey:@"_systemCountryCode"];
 699		}
 700		else {
 701			// set the variable to the value of NSLocaleCountryCode
 702			[_dictionary setObject:key forKey:@"_systemCountryCode"];
 703		} // end if
 704		key = [locale objectForKey:NSLocaleLanguageCode];
 705		// if NSLocaleLanguageCode is undefined for the system locale ...
 706		if (key == nil) {
 707			// set the variable to empty string
 708			[_dictionary setObject:kEmptyString forKey:@"_systemLanguage"];
 709		}
 710		else {
 711			// set the variable to the value of NSLocaleLanguageCode
 712			[_dictionary setObject:key forKey:@"_systemLanguage"];
 713		} // end if
 714	} // end if
 715
 716	// -----------------------------------------------------------------------
 717	//  P a r s e r   l o o p
 718	// -----------------------------------------------------------------------
 719	
 720	while (line = [list nextObject]) {
 721		lineNumber++;
 722		// if the line begins with a % character but not the start tag ...
 723		if (([line hasPrefix:startTag] == NO) && ([line characterAtIndex:0] == '%')) {
 724			// then the first word is likely to be a keyword
 725			keyword = [line firstWordUsingDelimitersFromSet:whitespaceSet];
 726			// if keyword starts with "%IFN" or "%ELSIFN" set complement to 1, otherwise 0
 727			complement = (([keyword hasPrefix:@"%IFN"]) || ([keyword hasPrefix:@"%ELSIFN"]));
 728
 729			// ---------------------------------------------------------------
 730			//  % I F   a n d   % I F N O T   b r a n c h
 731			// ---------------------------------------------------------------
 732
 733			if (([keyword isEqualToString:@"%IF"]) || ([keyword isEqualToString:@"%IFNOT"])) {
 734				if (flags->expand) {
 735					// we are to evaluate if the key's value represents 'true'
 736					// evaluate expression following %IF/%IFNOT
 737					key = [line wordAtIndex:2 usingDelimitersFromSet:whitespaceSet];
 738					// if there is no identifier following %IF/%IFNOT ...
 739					if ([key isEmpty]) {
 740						// this is an error - create a new error description
 741						error = [TEError error:TE_MISSING_IDENTIFIER_AFTER_TOKEN_ERROR
 742										inLine:lineNumber atToken:(TE_IF + complement)];
 743						// and add this error to the error log
 744						[_errorLog addObject:error];
 745						// log this error to the console
 746						[error logErrorMessageForTemplate:kEmptyString];
 747						// *** we are going to ignore this entire if-elsif-else-endif block ***
 748						// if this is a nested if-else block ...
 749						if (flags->condex) {
 750							// save flags to the stack
 751							[stack pushObject:flags];
 752							// and initialise a new set of flags
 753							flags = [TEFlags newFlags];
 754						} // end if
 755						// clear the expand flag to ignore this if-branch
 756						flags->expand = false;
 757						// set the consumed flag to ignore any elsif- and else- branches
 758						flags->consumed = true;
 759						// set condex flag to indicate we're inside an if-else block
 760						flags->condex = true;
 761					}
 762					// if there is an identifier following %IF/%IFNOT ...
 763					else {
 764						// look up the value of the key in the dictionary
 765						value = [_dictionary valueForKey:key];
 766						// *** this is the surviving branch - others are error branches ***
 767						// if this is a nested if-else block ...
 768						if (flags->condex) {
 769							// save flags to the stack
 770							[stack pushObject:flags];
 771							// and initialise a new set of flags
 772							flags = [TEFlags newFlags];
 773						} // end if
 774						  // evaluate if the value of the key represents 'true'
 775						flags->expand = ([value representsTrue]) ^ complement;
 776						// remember evaluation
 777						flags->consumed = flags->expand;
 778						// remember that we're in an if-else block
 779						flags->condex = true;
 780					} // end if
 781				} // end if
 782			}
 783
 784			// ---------------------------------------------------------------
 785			//  % E L S I F   a n d   % E L S I F N O T   b r a n c h
 786			// ---------------------------------------------------------------
 787			
 788			else if (([keyword isEqualToString:@"%ELSIF"]) || ([keyword isEqualToString:@"%ELSIFNOT"])) {
 789				if (flags->condex) {
 790					// if any branch in this if-else block was true ...
 791					if (flags->consumed) {
 792						// do not evaluate
 793						flags->expand = false;
 794					}
 795					else {
 796						// evaluate expression following %ELSIF/%ELSIFNOT
 797						key = [line wordAtIndex:2 usingDelimitersFromSet:whitespaceSet];
 798						// if there is no identifier following %ELSIF/%ELSIFNOT ...
 799						if ([key isEmpty]) {
 800							// this is an error - create a new error description
 801							error = [TEError error:TE_MISSING_IDENTIFIER_AFTER_TOKEN_ERROR
 802											inLine:lineNumber atToken:(TE_ELSIF + complement)];
 803							// and add this error to the error log
 804							[_errorLog addObject:error];
 805							// log this error to the console
 806							[error logErrorMessageForTemplate:kEmptyString];
 807							// clear the expand flag to ignore this elsif-branch
 808							flags->expand = false;
 809						}
 810						else {
 811							// evaluate if the value of the key represents 'true'
 812							flags->expand = ([value representsTrue]) ^ complement;
 813						} // end if			
 814					} // end if
 815					  // remember evaluation
 816					flags->consumed = (flags->consumed || flags->expand);
 817				}
 818				else {
 819					// found %ELSIF/%ELSIFNOT without prior %IF block having been opened
 820					// this is an error - create a new error description
 821					error = [TEError error:TE_UNEXPECTED_TOKEN_ERROR
 822									inLine:lineNumber atToken:(TE_ELSIF + complement)];
 823					// and add this error to the error log
 824					[_errorLog addObject:error];
 825					// log this error to the console
 826					[error logErrorMessageForTemplate:kEmptyString];
 827					// clear the expand flag to ignore this elsif-branch
 828					flags->expand = false;
 829				} // end if
 830			}
 831			
 832			// ---------------------------------------------------------------
 833			//  % I F E Q   a n d   % I F N E Q   b r a n c h
 834			// ---------------------------------------------------------------
 835			
 836			else if (([keyword isEqualToString:@"%IFEQ"]) || ([keyword isEqualToString:@"%IFNEQ"])) {
 837				if (flags->expand) {
 838					// we are to compare the key's value with an operand ...
 839					// evaluate expression following %IFEQ/%IFNEQ
 840					key = [line wordAtIndex:2 usingDelimitersFromSet:whitespaceSet];
 841					// if there is no identifier following %IFEQ/%IFNEQ ...
 842					if ([key isEmpty]) {
 843						// this is an error - create a new error description
 844						error = [TEError error:TE_MISSING_IDENTIFIER_AFTER_TOKEN_ERROR
 845										inLine:lineNumber atToken:(TE_IFEQ + complement)];
 846						// and add this error to the error log
 847						[_errorLog addObject:error];
 848						// log this error to the console
 849						[error logErrorMessageForTemplate:kEmptyString];
 850						// *** we are going to ignore this entire if-elsif-else-endif block ***
 851						// if this is a nested if-else block ...
 852						if (flags->condex) {
 853							// save flags to the stack
 854							[stack pushObject:flags];
 855							// and initialise a new set of flags
 856							flags = [TEFlags newFlags];
 857						} // end if
 858						  // clear the expand flag to ignore this if-branch
 859						flags->expand = false;
 860						// set the consumed flag to ignore any elsif- and else- branches
 861						flags->consumed = true;
 862						// set condex flag to indicate we're inside an if-else block
 863						flags->condex = true;
 864					}
 865					// if there is an identifier following %IFEQ/%IFNEQ ...
 866					else {
 867						// look up the value of the key in the dictionary
 868						value = [_dictionary valueForKey:key];
 869						// get the remaining characters following the key
 870						remainder = [[line restOfWordsUsingDelimitersFromSet:whitespaceSet]
 871									restOfWordsUsingDelimitersFromSet:whitespaceSet];
 872						// check if we have an operand
 873						len = [remainder length];
 874						if (len == 0) {
 875							// this is an error - no operand to compare
 876							error = [TEError error:TE_MISSING_IDENTIFIER_AFTER_TOKEN_ERROR
 877											inLine:lineNumber atToken:TE_KEY];
 878							[error setLiteral:key];
 879							// and add this error to the error log
 880							[_errorLog addObject:error];
 881							// log this error to the console
 882							[error logErrorMessageForTemplate:kEmptyString];
 883							// *** we are going to ignore this entire if-elsif-else-endif block ***
 884							// if this is a nested if-else block ...
 885							if (flags->condex) {
 886								// save flags to the stack
 887								[stack pushObject:flags];
 888								// and initialise a new set of flags
 889								flags = [TEFlags newFlags];
 890							} // end if
 891							  // clear the expand flag to ignore this if-branch
 892							flags->expand = false;
 893							// set the consumed flag to ignore any elsif- and else- branches
 894							flags->consumed = true;
 895							// set condex flag to indicate we're inside an if-else block
 896							flags->condex = true;
 897						}
 898						else {
 899							if (len == 1) {
 900								// only one character - use it as it is
 901								operand = [remainder copy];
 902							}
 903							else {
 904								// multiple characters left on the line
 905								// check if we have a quoted string using single quotes
 906								if ([remainder characterAtIndex:0] == '\'') {
 907									// get the characters enclosed by the single quotes
 908									operand = [remainder substringWithStringInSingleQuotes];
 909									// if there are no closing quotes
 910									if (operand == nil) {
 911										// assume EOL terminates the quoted string
 912										operand = [remainder substringFromIndex:1];
 913									} // end if
 914								}
 915								// alternatively, a quoted string using double quotes
 916								else if ([remainder characterAtIndex:0 == '"']) {
 917									// get the characters enclosed by the double quotes
 918									operand = [remainder substringWithStringInDoubleQuotes];
 919									// if there are no closing quotes
 920									if (operand == nil) {
 921										// assume EOL terminates the quoted string
 922										operand = [remainder substringFromIndex:1];
 923									} // end if
 924								}
 925								// otherwise if we don't have a quoted string
 926								else {
 927									// get the first word of the remaining characters on the line
 928									operand = [remainder firstWordUsingDelimitersFromSet:whitespaceSet];
 929								} // end if
 930							} // end if
 931							// *** this is the surviving branch - others are error branches ***
 932							// if this is a nested if-else block ...
 933							if (flags->condex) {
 934								// save flags to the stack
 935								[stack pushObject:flags];
 936								// and initialise a new set of flags
 937								flags = [TEFlags newFlags];
 938							} // end if
 939							// compare the value of the key to the operand
 940							flags->expand = ([value isEqualToString:operand] == YES) ^ complement;
 941							// remember evaluation
 942							flags->consumed = flags->expand;
 943							// remember that we're in an if-else block
 944							flags->condex = true;
 945						} // end if
 946					} // end if
 947				} // end if
 948			}
 949			
 950			// ---------------------------------------------------------------
 951			//  % E L S I F E Q   a n d   % E L S I F N E Q   b r a n c h
 952			// ---------------------------------------------------------------
 953			
 954			if (([keyword isEqualToString:@"%ELSIFEQ"]) || ([keyword isEqualToString:@"%ELSIFNEQ"])) {
 955				// we only care about this block if it is part of an open %IF
 956				if (flags->condex) {
 957					// ignore if already consumed
 958					if (flags->consumed) {
 959						// do not expand this block
 960						flags->expand = false;
 961					}
 962					else {
 963						// evaluate expression following %ELSIFEQ/%ELSIFNEQ
 964						key = [line wordAtIndex:2 usingDelimitersFromSet:whitespaceSet];
 965						// if there is no identifier following %ELSIFEQ/%ELSIFNEQ ...
 966						if ([key isEmpty]) {
 967							// this is an error - create a new error description
 968							error = [TEError error:TE_MISSING_IDENTIFIER_AFTER_TOKEN_ERROR
 969											inLine:lineNumber atToken:(TE_ELSIFEQ + complement)];
 970							// and add this error to the error log
 971							[_errorLog addObject:error];
 972							// log this error to the console
 973							[error logErrorMessageForTemplate:kEmptyString];
 974							// clear the expand flag to ignore this elsif-branch
 975							flags->expand = false;
 976						}
 977						else {
 978							// look up the value of the key in the dictionary
 979							value = [_dictionary valueForKey:key];
 980							// get the remaining characters following the key
 981							remainder = [[line restOfWordsUsingDelimitersFromSet:whitespaceSet]
 982									restOfWordsUsingDelimitersFromSet:whitespaceSet];
 983							// check if we have an operand
 984							len = [remainder length];
 985							if (len == 0) {
 986								// this is an error - no operand to compare
 987								error = [TEError error:TE_MISSING_IDENTIFIER_AFTER_TOKEN_ERROR
 988												inLine:lineNumber atToken:TE_KEY];
 989								[error setLiteral:key];
 990								// and add this error to the error log
 991								[_errorLog addObject:error];
 992								// log this error to the console
 993								[error logErrorMessageForTemplate:kEmptyString];
 994								// clear the expand flag to ignore this elsif-branch
 995								flags->expand = false;
 996							}
 997							else {
 998								if (len == 1) {
 999									// only one character - use it as it is
1000									operand = [remainder copy];
1001								}
1002								else {
1003									// multiple characters left on the line
1004									// check if we have a quoted string using single quotes
1005									if ([remainder characterAtIndex:0] == '\'') {
1006										// get the characters enclosed by the single quotes
1007										operand = [remainder substringWithStringInSingleQuotes];
1008										// if there are no closing quotes
1009										if (operand == nil) {
1010											// assume EOL terminates the quoted string
1011											operand = [remainder substringFromIndex:1];
1012										} // end if
1013									}
1014									// alternatively, a quoted string using double quotes
1015									else if ([remainder characterAtIndex:0 == '"']) {
1016										// get the characters enclosed by the double quotes
1017										operand = [remainder substringWithStringInDoubleQuotes];
1018										// if there are no closing quotes
1019										if (operand == nil) {
1020											// assume EOL terminates the quoted string
1021											operand = [remainder substringFromIndex:1];
1022										} // end if
1023									}
1024									// otherwise if we don't have a quoted string
1025									else {
1026										// get the first word of the remaining characters on the line
1027										operand = [remainder firstWordUsingDelimitersFromSet:whitespaceSet];
1028									} // end if
1029								} // end if
1030								// *** this is the surviving branch - others are error branches ***
1031								// compare the value of the key to the operand
1032								flags->expand = ([value isEqualToString:operand] == YES) ^ complement;
1033								// remember evaluation
1034								flags->consumed = flags->expand;
1035							} // end if
1036						} // end if
1037					} // end if
1038				}
1039				// if this block is not part of an open %IF ...
1040				else {
1041					// found %ELSIFEQ/%ELSIFNEQ without prior %IF block having been opened
1042					// this is an error - create a new error description
1043					error = [TEError error:TE_UNEXPECTED_TOKEN_ERROR
1044									inLine:lineNumber atToken:(TE_ELSIFEQ + complement)];
1045					// and add this error to the error log
1046					[_errorLog addObject:error];
1047					// log this error to the console
1048					[error logErrorMessageForTemplate:kEmptyString];
1049					// clear the expand flag to ignore this elsif-branch
1050					flags->expand = false;
1051				} // end if
1052			}
1053			
1054			// ---------------------------------------------------------------
1055			//  % I F D E F   a n d   % I F N D E F   b r a n c h
1056			// ---------------------------------------------------------------
1057			
1058			else if (([keyword isEqualToString:@"%IFDEF"]) || ([keyword isEqualToString:@"%IFNDEF"])) {
1059				// get the identifier following %IFDEF/%IFNDEF
1060				key = [line wordAtIndex:2 usingDelimitersFromSet:whitespaceSet];
1061				// if there is no identifier following %IFDEF/%IFNDEF ...
1062				if ([key isEmpty]) {
1063					// this is an error - create a new error description
1064					error = [TEError error:TE_MISSING_IDENTIFIER_AFTER_TOKEN_ERROR
1065									inLine:lineNumber atToken:(TE_IFDEF + complement)];
1066					// and add this error to the error log
1067					[_errorLog addObject:error];
1068					// log this error to the console
1069					[error logErrorMessageForTemplate:kEmptyString];
1070					// *** we are going to ignore this entire if-elsif-else-endif block ***
1071					// if this is a nested if-else block ...
1072					if (flags->condex) {
1073						// save flags to the stack
1074						[stack pushObject:flags];
1075						// and initialise a new set of flags
1076						flags = [TEFlags newFlags];
1077					} // end if
1078					  // clear the expand flag to ignore this if-branch
1079					flags->expand = false;
1080					// set the consumed flag to ignore any elsif- and else- branches
1081					flags->consumed = true;
1082					// set condex flag to indicate we're inside an if-els

Large files files are truncated, but you can click here to view the full file