PageRenderTime 150ms CodeModel.GetById 12ms RepoModel.GetById 0ms 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
Possible License(s): GPL-2.0

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

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