PageRenderTime 6ms CodeModel.GetById 11ms app.highlight 325ms RepoModel.GetById 1ms app.codeStats 1ms

/STSTemplateEngine/STSTemplateEngine.m

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

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