PageRenderTime 60ms CodeModel.GetById 22ms 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
  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:(TE_IFDEF + complement)];
  1002. // and add this error to the error log
  1003. [_errorLog addObject:error];
  1004. // log this error to the console
  1005. [error logErrorMessageForTemplate:kEmptyString];
  1006. // *** we are going to ignore this entire if-elsif-else-endif block ***
  1007. // if this is a nested if-else block ...
  1008. if (flags->condex) {
  1009. // save flags to the stack
  1010. [stack pushObject:flags];
  1011. // and initialise a new set of flags
  1012. flags = [TEFlags newFlags];
  1013. } // end if
  1014. // clear the expand flag to ignore this if-branch
  1015. flags->expand = false;
  1016. // set the consumed flag to ignore any elsif- and else- branches
  1017. flags->consumed = true;
  1018. // set condex flag to indicate we're inside an if-else block
  1019. flags->condex = true;
  1020. }
  1021. // if there is an identifier following %IFDEF/%IFNDEF ...
  1022. else {
  1023. // if this is a nested if-else block ...
  1024. if (flags->condex) {
  1025. // this is a nested %IFDEF - save flags to the stack
  1026. [stack pushObject:flags];
  1027. // and initialise a new set of flags
  1028. flags = [TEFlags newFlags];
  1029. } // end if
  1030. // set expand flag to true if key is defined, false if undefined
  1031. flags->expand = (keyDefined(_dictionary, key)) ^ complement;
  1032. // remember evaluation
  1033. flags->consumed = flags->expand;
  1034. // remember that we're in an if-else block
  1035. flags->condex = true;
  1036. } // end if
  1037. } else {
  1038. ++unexpandIf;
  1039. flags->condex = false;
  1040. } // end if
  1041. }
  1042. // ---------------------------------------------------------------
  1043. // % E L S I F D E F a n d % E L S I F N D E F b r a n c h
  1044. // ---------------------------------------------------------------
  1045. else if (!flags->forBranch && (([keyword isEqualToString:@"%ELSIFDEF"]) || ([keyword isEqualToString:@"%ELSIFNDEF"]))) {
  1046. if (flags->condex) {
  1047. // if any branch in this if-else block was true ...
  1048. if (flags->consumed) {
  1049. // do not evaluate
  1050. flags->expand = false;
  1051. }
  1052. else {
  1053. // evaluate expression following %ELSIFDEF/%ELSIFNDEF
  1054. key = [line wordAtIndex:2 usingDelimitersFromSet:whitespaceSet];
  1055. // if there is no identifier following %ELSIFDEF/%ELSIFNDEF
  1056. if ([key isEmpty]) {
  1057. // this is an error - create a new error description
  1058. error = [TEError error:TE_MISSING_IDENTIFIER_AFTER_TOKEN_ERROR
  1059. inLine:lineNumber atToken:(TE_ELSIFDEF + complement)];
  1060. // and add this error to the error log
  1061. [_errorLog addObject:error];
  1062. // log this error to the console
  1063. [error logErrorMessageForTemplate:kEmptyString];
  1064. // clear the expand flag to ignore this elsif-branch
  1065. flags->expand = false;
  1066. }
  1067. else {
  1068. // set expand flag to true if key is defined, false if undefined
  1069. flags->expand = (keyDefined(_dictionary, key)) ^ complement;
  1070. } // end if
  1071. } // end if
  1072. // remember evaluation
  1073. flags->consumed = (flags->consumed || flags->expand);
  1074. }
  1075. else if(unexpandIf == 0) {
  1076. // found %ELSIFDEF/%ELSIFNDEF without prior %IF block having been opened
  1077. // this is an error - create a new error description
  1078. error = [TEError error:TE_UNEXPECTED_TOKEN_ERROR
  1079. inLine:lineNumber atToken:(TE_ELSIFDEF + complement)];
  1080. // and add this error to the error log
  1081. [_errorLog addObject:error];
  1082. // log this error to the console
  1083. [error logErrorMessageForTemplate:kEmptyString];
  1084. // clear the expand flag to ignore this elsif-branch
  1085. flags->expand = false;
  1086. } // end if
  1087. }
  1088. // ---------------------------------------------------------------
  1089. // % E L S E b r a n c h
  1090. // ---------------------------------------------------------------
  1091. else if (!flags->forBranch && [keyword isEqualToString:@"%ELSE"]) {
  1092. if (flags->condex) {
  1093. // if any branch in this if-else block was true ...
  1094. flags->expand = !(flags->consumed);
  1095. flags->consumed = true;
  1096. }
  1097. else if(unexpandIf == 0) {
  1098. // found %ELSE without any prior %IF block having been opened
  1099. // this is an error - create a new error description
  1100. error = [TEError error:TE_UNEXPECTED_TOKEN_ERROR
  1101. inLine:lineNumber atToken:TE_ELSE];
  1102. // and add this error to the error log
  1103. [_errorLog addObject:error];
  1104. // log this error to the console
  1105. [error logErrorMessageForTemplate:kEmptyString];
  1106. // clear the expand flag to ignore this else-branch
  1107. flags->expand = false;
  1108. } // end if
  1109. }
  1110. // ---------------------------------------------------------------
  1111. // % E N D I F b r a n c h
  1112. // ---------------------------------------------------------------
  1113. else if (!flags->forBranch && [keyword isEqualToString:@"%ENDIF"]) {
  1114. if (flags->condex) {
  1115. // we're leaving the if-else block ...
  1116. // check if there were any enclosing blocks
  1117. if ([stack count] > 0) {
  1118. // we're in a nested if-block
  1119. // restore the flags for the enclosing block
  1120. flags = [stack popObject];
  1121. }
  1122. else {
  1123. // we're not in a nested if-block
  1124. // reset flags to start conditions
  1125. flags->expand = true;
  1126. flags->consumed = false;
  1127. flags->condex = false;
  1128. flags->forBranch = false;
  1129. } // end if
  1130. }
  1131. else if(unexpandIf > 0) {
  1132. --unexpandIf;
  1133. if(unexpandIf == 0) flags->condex = true;
  1134. } else {
  1135. // found %ENDIF without prior %IF block having been opened
  1136. // this is an error - create a new error description
  1137. error = [TEError error:TE_UNEXPECTED_TOKEN_ERROR
  1138. inLine:lineNumber atToken:TE_ENDIF];
  1139. // and add this error to the error log
  1140. [_errorLog addObject:error];
  1141. // log this error to the console
  1142. [error logErrorMessageForTemplate:kEmptyString];
  1143. } // end if
  1144. }
  1145. // ---------------------------------------------------------------
  1146. // % D E F I N E b r a n c h
  1147. // ---------------------------------------------------------------
  1148. else if (!flags->forBranch && [keyword isEqualToString:@"%DEFINE"]) {
  1149. if (flags->expand) {
  1150. // we are to define a new key ...
  1151. // evaluate expression following %DEFINE
  1152. key = [line wordAtIndex:2 usingDelimitersFromSet:whitespaceSet];
  1153. // if there is no identifier following %DEFINE
  1154. if ([key isEmpty]) {
  1155. // this is an error - create a new error description
  1156. error = [TEError error:TE_MISSING_IDENTIFIER_AFTER_TOKEN_ERROR
  1157. inLine:lineNumber atToken:TE_DEFINE];
  1158. // and add this error to the error log
  1159. [_errorLog addObject:error];
  1160. // log this error to the console
  1161. [error logErrorMessageForTemplate:kEmptyString];
  1162. }
  1163. else {
  1164. // obtain the value for this key
  1165. value = [[line restOfWordsUsingDelimitersFromSet:whitespaceSet]
  1166. restOfWordsUsingDelimitersFromSet:whitespaceSet];
  1167. // add the new key to the dictionary
  1168. [_dictionary setObject:[value copy] forKey:key];
  1169. } // end if
  1170. } // end if
  1171. }
  1172. // ---------------------------------------------------------------
  1173. // % U N D E F b r a n c h
  1174. // ---------------------------------------------------------------
  1175. else if (!flags->forBranch && [keyword isEqualToString:@"%UNDEF"]) {
  1176. if (flags->expand) {
  1177. // evaluate expression following %UNDEF
  1178. key = [line wordAtIndex:2 usingDelimitersFromSet:whitespaceSet];
  1179. // if there is no identifier following %UNDEF
  1180. if ([key isEmpty]) {
  1181. // this is an error - create a new error description
  1182. error = [TEError error:TE_MISSING_IDENTIFIER_AFTER_TOKEN_ERROR
  1183. inLine:lineNumber atToken:TE_UNDEF];
  1184. // and add this error to the error log
  1185. [_errorLog addObject:error];
  1186. // log this error to the console
  1187. [error logErrorMessageForTemplate:kEmptyString];
  1188. }
  1189. else {
  1190. // remove this key from the dictionary
  1191. [_dictionary removeObjectForKey:key];
  1192. } // end if
  1193. } // end if
  1194. }
  1195. // ---------------------------------------------------------------
  1196. // % L O G b r a n c h
  1197. // ---------------------------------------------------------------
  1198. else if (!flags->forBranch && [keyword isEqualToString:@"%LOG"]) {
  1199. if (flags->expand) {
  1200. // we are to log text/keys to the console ...
  1201. // evaluate expression following %LOG
  1202. key = [line wordAtIndex:2 usingDelimitersFromSet:whitespaceSet];
  1203. // if there is no identifier following %UNDEF
  1204. if ([key isEmpty]) {
  1205. // this is an error - create a new error description
  1206. error = [TEError error:TE_MISSING_IDENTIFIER_AFTER_TOKEN_ERROR
  1207. inLine:lineNumber atToken:TE_LOG];
  1208. // and add this error to the error log
  1209. [_errorLog addObject:error];
  1210. // log this error to the console
  1211. [error logErrorMessageForTemplate:kEmptyString];
  1212. }
  1213. else {
  1214. // lookup the value for this key in the dictionary
  1215. value = [NSString valueForDictionary:_dictionary key:key];
  1216. // and log it to the console
  1217. NSLog(@"value for key '%@' is '%@'", key, value);
  1218. } // end if
  1219. } // end if
  1220. }
  1221. // ---------------------------------------------------------------
  1222. // % E C H O b r a n c h
  1223. // ---------------------------------------------------------------
  1224. else if (!flags->forBranch && [keyword isEqualToString:@"%ECHO"]) {
  1225. if (flags->expand) {
  1226. // we are to log text/keys to stdout ...
  1227. TODO
  1228. // this is not implemented yet
  1229. error = [TEError error:TE_UNIMPLEMENTED_TOKEN_ERROR
  1230. inLine:lineNumber atToken:TE_ECHO];
  1231. // and add this error to the error log
  1232. [_errorLog addObject:error];
  1233. // log this error to the console
  1234. [error logErrorMessageForTemplate:kEmptyString];
  1235. } // end if
  1236. }
  1237. // ---------------------------------------------------------------
  1238. // % D E B U G b r a n c h
  1239. // ---------------------------------------------------------------
  1240. else if (!flags->forBranch && [keyword isEqualToString:@"%DEBUG"]) {
  1241. if (flags->expand) {
  1242. // we are to enable/disable debug mode ...
  1243. TODO
  1244. // this is not implemented yet
  1245. error = [TEError error:TE_UNIMPLEMENTED_TOKEN_ERROR
  1246. inLine:lineNumber atToken:TE_DEBUG];
  1247. // and add this error to the error log
  1248. [_errorLog addObject:error];
  1249. // log this error to the console
  1250. [error logErrorMessageForTemplate:kEmptyString];
  1251. } // end if
  1252. }
  1253. // ---------------------------------------------------------------
  1254. // % F O R E A C H b r a n c h
  1255. // ---------------------------------------------------------------
  1256. else if([keyword isEqualToString:@"%FOREACH"]) {
  1257. if(!flags->forBranch && flags->expand) {
  1258. varName = [line wordAtIndex:2 usingDelimitersFromSet:whitespaceSet];
  1259. key = [line wordAtIndex:4 usingDelimitersFromSet:whitespaceSet];
  1260. if (flags->expand) {
  1261. // if there is no identifier following %FOREACH ...
  1262. if ([key isEmpty] || [varName isEmpty]) {
  1263. // this is an error - create a new error description
  1264. error = [TEError error:TE_MISSING_IDENTIFIER_AFTER_TOKEN_ERROR
  1265. inLine:lineNumber atToken:(complement)];
  1266. // and add this error to the error log
  1267. [_errorLog addObject:error];
  1268. // log this error to the console
  1269. [error logErrorMessageForTemplate:kEmptyString];
  1270. // *** we are going to ignore this entire if-elsif-else-endif block ***
  1271. // if this is a nested if-else block ...
  1272. if (flags->condex) {
  1273. // save flags to the stack
  1274. [stack pushObject:flags];
  1275. // and initialise a new set of flags
  1276. flags = [TEFlags newFlags];
  1277. } // end if
  1278. // clear the expand flag to ignore this if-branch
  1279. flags->expand = false;
  1280. // set the consumed flag to ignore any elsif- and else- branches
  1281. flags->consumed = true;
  1282. // set condex flag to indicate we're inside an if-else block
  1283. flags->condex = true;
  1284. }
  1285. // if there is an identifier following %FOREACH ...
  1286. else {
  1287. // look up the value of the key in the dictionary
  1288. value = [NSString valueForDictionary:_dictionary key:key];
  1289. if(value == nil || ![value isKindOfClass:[NSArray class]])
  1290. {
  1291. error = [TEError error:TE_ABORTING_TEMPLATE_EXPANSION
  1292. inLine:lineNumber atToken:TE_DEBUG];
  1293. // and add this error to the error log
  1294. [_errorLog addObject:error];
  1295. // log this error to the console
  1296. [error logErrorMessageForTemplate:kEmptyString];
  1297. }
  1298. // *** this is the surviving branch - others are error branches ***
  1299. // if this is a nested if-else block ...
  1300. if (flags->condex) {
  1301. // save flags to the stack
  1302. [stack pushObject:flags];
  1303. // and initialise a new set of flags
  1304. flags = [TEFlags newFlags];
  1305. } // end if
  1306. // compare the value of the key to the operand
  1307. flags->expand = false;
  1308. flags->consumed = true;
  1309. flags->forBranch = true;
  1310. flags->condex = true;
  1311. } // end if
  1312. }
  1313. else {
  1314. [stack pushObject:flags];
  1315. flags = [TEFlags newFlags];
  1316. flags->expand = false;
  1317. flags->consumed = true;
  1318. flags->forBranch = true;
  1319. flags->condex = false;
  1320. } // end if
  1321. } else {
  1322. unexpandFor++;
  1323. }
  1324. }
  1325. // ---------------------------------------------------------------
  1326. // % E N D F O R b r a n c h
  1327. // ---------------------------------------------------------------
  1328. else if([keyword isEqualToString:@"%ENDFOR"]) {
  1329. if(unexpandFor > 0) {
  1330. --unexpandFor;
  1331. } else {
  1332. if (flags->forBranch && flags->condex) {
  1333. // we're leaving the for block ...
  1334. // check if there were any enclosing blocks
  1335. if ([stack count] > 0) {
  1336. // we're in a nested if-block
  1337. // restore the flags for the enclosing block
  1338. flags = [stack popObject];
  1339. }
  1340. else {
  1341. // we're not in a nested if-block
  1342. // reset flags to start conditions
  1343. flags->expand = true;
  1344. flags->consumed = false;
  1345. flags->condex = false;
  1346. flags->forBranch = false;
  1347. } // end if
  1348. NSArray *array = [NSString valueForDictionary:_dictionary key:key];
  1349. for(id varValue in array) {
  1350. [_dictionary setObject:varValue forKey:varName];
  1351. [result appendString:[NSString stringByExpandingTemplate:innerString
  1352. withStartTag:startTag
  1353. andEndTag:endTag
  1354. usingDictionary:_dictionary
  1355. errorsReturned:errorLog]];
  1356. }
  1357. [_dictionary removeObjectForKey:varName];
  1358. [innerString release];
  1359. innerString = nil;
  1360. }
  1361. else {
  1362. if ([stack count] > 0) {
  1363. // we're in a nested if-block
  1364. // restore the flags for the enclosing block
  1365. flags = [stack popObject];
  1366. } else {
  1367. // found %ENDIF without prior %IF block having been opened
  1368. // this is an error - create a new error description
  1369. error = [TEError error:TE_UNEXPECTED_TOKEN_ERROR
  1370. inLine:lineNumber atToken:TE_ENDIF];
  1371. // and add this error to the error log
  1372. [_errorLog addObject:error];
  1373. // log this error to the console
  1374. [error logErrorMessageForTemplate:kEmptyString];
  1375. }
  1376. } // end if
  1377. }
  1378. }
  1379. // ---------------------------------------------------------------
  1380. // % c o m m e n t b r a n c h
  1381. // ---------------------------------------------------------------
  1382. // if the '%' character is not part of a placeholder/directive ...
  1383. else {
  1384. // then the current line is a template comment.
  1385. // it will not be expanded nor copied to the result
  1386. } // end if
  1387. }
  1388. // if line does not begin with a % character ...
  1389. // then it is neither comment nor template command
  1390. // if expand flag is set, expand it, otherwise ignore it
  1391. else if (flags->expand) {
  1392. // expand the line and add it to the result
  1393. [result appendString:[line stringByExpandingPlaceholdersWithStartTag:startTag
  1394. andEndTag:endTag
  1395. usingDictionary:_dictionary
  1396. errorsReturned:&lineErrors
  1397. lineNumber:lineNumber]];
  1398. [result appendString:kLineFeed];
  1399. // if there were any errors ...
  1400. if (lineErrorsHaveOcurred) {
  1401. // add the errors to the error log
  1402. [_errorLog addObjectsFromArray:lineErrors];
  1403. } // end if
  1404. } // end if
  1405. if (flags->forBranch) {
  1406. if(innerString == nil) {
  1407. innerString = [NSMutableString string];
  1408. } else {
  1409. [innerString appendString:line];
  1410. [innerString appendString:kLineFeed];
  1411. }
  1412. } // end if
  1413. } // end while
  1414. if (flags->condex) {
  1415. // we've reached the end of the template without previous %IF block having been closed
  1416. // this is an error - create a new error description
  1417. error = [TEError error:TE_EXPECTED_ENDIF_BUT_FOUND_TOKEN_ERROR
  1418. inLine:lineNumber atToken:TE_EOF];
  1419. // and add this error to the error log
  1420. [_errorLog addObject:error];
  1421. // log this error to the console
  1422. [error logErrorMessageForTemplate:kEmptyString];
  1423. } // end if
  1424. // if there were any errors ...
  1425. if (errorsHaveOcurred) {
  1426. // pass the error log back to the caller
  1427. *errorLog = _errorLog;
  1428. // get rid of the following line after testing
  1429. NSLog(@"errors have ocurred while expanding placeholders in string using dictionary:\n%@", dictionary);
  1430. NSLog(@"using template:\n%@", template);
  1431. }
  1432. // if there were no errors ...
  1433. else {
  1434. // pass nil in the errorLog back to the caller
  1435. if(errorLog != nil) *errorLog = nil;
  1436. } // end if
  1437. // return the result string
  1438. return [NSString stringWithString:result];
  1439. #undef errorsHaveOcurred
  1440. #undef lineErrorsHaveOcurred
  1441. } // end method
  1442. // ---------------------------------------------------------------------------
  1443. // Class Method: stringByExpandingTemplateAtPath:usingDictionary:
  1444. // encoding:errorsReturned:
  1445. // ---------------------------------------------------------------------------
  1446. //
  1447. // description:
  1448. // Invokes method stringByExpandingTemplateAtPath:withStartTag:andEndTag:
  1449. // usingDictionary:encoding:errorsReturned: with the template engine's
  1450. // default tags: startTag "%«" and endTag "»". This method is source file
  1451. // encoding-safe.
  1452. + (id)stringByExpandingTemplateAtPath:(NSString *)path
  1453. usingDictionary:(NSDictionary *)dictionary
  1454. encoding:(NSStringEncoding)enc
  1455. errorsReturned:(NSArray **)errorLog
  1456. {
  1457. return [NSString stringByExpandingTemplateAtPath:path
  1458. withStartTag:[NSString defaultStartTag]
  1459. andEndTag:[NSString defaultEndTag]
  1460. usingDictionary:dictionary
  1461. encoding:enc
  1462. errorsReturned:errorLog];
  1463. } // end method
  1464. // ---------------------------------------------------------------------------
  1465. // Class Method: stringByExpandingTemplateAtPath:withStartTag:andEndTag:
  1466. // usingDictionary:encoding:errorsReturned:
  1467. // ---------------------------------------------------------------------------
  1468. //
  1469. // description:
  1470. // Returns a new NSString made by expanding the template file at path. This
  1471. // method reads the template file specified in path interpreted in the string
  1472. // encoding specified by encoding and then it invokes class method
  1473. // stringByExpandingTemplate:withStartTag:andEndTag:usingDictionary:
  1474. // errorsReturned: to expand the template read from the template file.
  1475. //
  1476. // pre-conditions:
  1477. // path is a valid path to the template file to be expanded.
  1478. // startTag and endTag must not be empty strings and must not be nil.
  1479. // dictionary contains keys to be replaced by their respective values.
  1480. // encoding is a valid string encoding as defined by NSStringEncoding.
  1481. // errorLog is an NSArray passed by reference.
  1482. //
  1483. // post-conditions:
  1484. // Return value contains a new NSString made by expanding the template file
  1485. // at path with lines outside of %IF blocks expanded unconditionally and
  1486. // lines inside of %IF blocks expanded conditionally. If any errors are
  1487. // encountered while attempting to read the template file or during template
  1488. // expansion, errorLog will be set to an NSArray containing error
  1489. // descriptions of class TEError for each error or warning. If there were
  1490. // neither errors nor warnings during template expansion, errorLog is set to
  1491. // nil.
  1492. // Errors that prevent the template file to be found or read will cause
  1493. // the method to abort the attempt to expand the template.
  1494. // For a description of error recovery for other errors see method
  1495. // stringByExpandingTemplate:withStartTag:andEndTag:usingDictionary:
  1496. // errorsReturned:
  1497. //
  1498. // error-conditions:
  1499. // If startTag is empty or nil, an exception StartTagEmptyOrNil will be
  1500. // raised. If end tag is empty or nil, an exception EndTagEmptyOrNil will be
  1501. // raised. It is the responsibility of the caller to catch the exception.
  1502. // If the template file cannot be found or opened, or if there is an encoding
  1503. // error with the contents of the file, one or more error descriptions of
  1504. // class TEError are returned in errorLog.
  1505. + (id)stringByExpandingTemplateAtPath:(NSString *)path
  1506. withStartTag:(NSString *)startTag
  1507. andEndTag:(NSString *)endTag
  1508. usingDictionary:(NSDictionary *)dictionary
  1509. encoding:(NSStringEncoding)enc
  1510. errorsReturned:(NSArray **)errorLog
  1511. {
  1512. NSFileManager *fileMgr = [NSFileManager defaultManager];
  1513. NSString *templateString, *desc, *reason;
  1514. NSData *templateData;
  1515. NSError *fileError = [[NSError alloc] initWithNullError];
  1516. #define fileErrorOcurred ([fileError isNullError] == NO)
  1517. TEError *error;
  1518. // check if a path was specified
  1519. if ((path == nil) || ([path length] == 0)) {
  1520. // create error description - invalid path
  1521. error = [TEError error:TE_INVALID_PATH_ERROR inLine:0 atToken:TE_PATH];
  1522. [error setLiteral:@"(empty path)"];
  1523. // log this error to the console
  1524. [error logErrorMessageForTemplate:path];
  1525. // and add this error to the error log
  1526. *errorLog = [NSArray arrayWithObject:error];
  1527. // this error is not recoverable - abort
  1528. return nil;
  1529. } // end if
  1530. // check if path is an absolute path
  1531. path = [path stringByStandardizingPath];
  1532. if ([path isAbsolutePath] == NO) {
  1533. // create error description - invalid path
  1534. error = [TEError error:TE_INVALID_PATH_ERROR inLine:0 atToken:TE_PATH];
  1535. [error setLiteral:path];
  1536. // log this error to the console
  1537. [error logErrorMessageForTemplate:path];
  1538. // and add this error to the error log
  1539. *errorLog = [NSArray arrayWithObject:error];
  1540. // this error is not recoverable - abort
  1541. return nil;
  1542. } // end if
  1543. // if the specified file does not exist ...
  1544. if ([fileMgr fileExistsAtPath:path] == NO) {
  1545. // create error description - file not found
  1546. error = [TEError error:TE_FILE_NOT_FOUND_ERROR inLine:0 atToken:TE_PATH];
  1547. [error setLiteral:path];
  1548. // log this error to the console
  1549. [error logErrorMessageForTemplate:path];
  1550. // and add this error to the error log
  1551. *errorLog = [NSArray arrayWithObject:error];
  1552. // this error is not recoverable - abort
  1553. return nil;
  1554. }
  1555. // if the specified file is not readable ...
  1556. else if ([fileMgr isReadableFileAtPath:path] == NO) {
  1557. // create error description - cannot read file
  1558. error = [TEError error:TE_UNABLE_TO_READ_FILE_ERROR inLine:0 atToken:TE_PATH];
  1559. [error setLiteral:path];
  1560. // log this error to the console
  1561. [error logErrorMessageForTemplate:path];
  1562. // and add this error to the error log
  1563. *errorLog = [NSArray arrayWithObject:error];
  1564. // this error is not recoverable - abort
  1565. return nil;
  1566. }
  1567. // if the specified file is not a regular file nor a symlink pointing to a regular file ...
  1568. else if ([fileMgr isRegularFileAtPath:path] == NO) {
  1569. // create error description - not regular file
  1570. error = [TEError error:TE_UNABLE_TO_READ_FILE_ERROR inLine:0 atToken:TE_PATH];
  1571. [error setLiteral:path];
  1572. // log this error to the console
  1573. [error logErrorMessageForTemplate:path];
  1574. // and add this error to the error log
  1575. *errorLog = [NSArray arrayWithObject:error];
  1576. // this error is not recoverable - abort
  1577. return nil;
  1578. } // end if
  1579. if ([NSString respondsToSelector:@selector(stringWithContentsOfFile:encoding:error:)]) {
  1580. // use newer method as of MacOS X 10.4
  1581. templateString = [NSString stringWithContentsOfFile:path encoding:enc error:&fileError];
  1582. if (fileErrorOcurred) {
  1583. desc = [fileError localizedDescription];
  1584. reason = [fileError localizedFailureReason];
  1585. // if an error in the Cocoa Error Domain ocurred ...
  1586. if ([[fileError domain] isEqualToString:@"NSCocoaErrorDomain"]) {
  1587. // get the error code and match it to a corresponding TEError
  1588. switch([fileError code]) {
  1589. case NSFileNoSuchFileError :
  1590. case NSFileReadNoSuchFileError :
  1591. case NSFileReadInvalidFileNameError :
  1592. // create error description - file not found
  1593. error = [TEError error:TE_FILE_NOT_FOUND_ERROR inLine:0 atToken:TE_PATH];
  1594. break;
  1595. case NSFileReadNoPermissionError :
  1596. // create error description - cannot read file
  1597. error = [TEError error:TE_UNABLE_TO_READ_FILE_ERROR inLine:0 atToken:TE_PATH];
  1598. break;
  1599. case NSFileReadCorruptFileError :
  1600. // create error description - invalid file format
  1601. error = [TEError error:TE_INVALID_FILE_FORMAT_ERROR inLine:0 atToken:TE_PATH];
  1602. break;
  1603. case NSFileReadInapplicableStringEncodingError :
  1604. // create error description - encoding error
  1605. error = [TEError error:TE_TEMPLATE_ENCODING_ERROR inLine:0 atToken:TE_PATH];
  1606. break;
  1607. default :
  1608. // create error description - generic file error
  1609. error = [TEError error:TE_GENERIC_ERROR inLine:0 atToken:TE_PATH];
  1610. // embed the Cocoa generated error description
  1611. [error setLiteral:[NSString stringWithFormat:
  1612. @"while trying to access file at path %@\n%@\n%@", path, desc, reason]];
  1613. break;
  1614. } // end switch
  1615. }
  1616. // if the error is outside of the Cocoa Error Domain ...
  1617. else {
  1618. // create error description - generic file error
  1619. error = [TEError error:TE_GENERIC_ERROR inLine:0 atToken:TE_PATH];
  1620. // embed the Cocoa generated error description
  1621. [error setLiteral:[NSString stringWithFormat:
  1622. @"while trying to access file at path %@\n%@\n%@", path, desc, reason]];
  1623. } // end if
  1624. if ([[error literal] length] == 0) {
  1625. [error setLiteral:path];
  1626. } // end if
  1627. // log this error to the console
  1628. [error logErrorMessageForTemplate:path];
  1629. // and add this error to the error log
  1630. *errorLog = [NSArray arrayWithObject:error];
  1631. // this error is not recoverable - abort
  1632. return nil;
  1633. } // end if
  1634. }
  1635. else {
  1636. // use alternative method before MacOS X 10.4
  1637. templateData = [NSData dataWithContentsOfFile:path]; // path must be absolute
  1638. templateString = [[[NSString alloc] initWithData:templateData encoding:enc] autorelease];
  1639. if (false) { // how the heck do we know there was no encoding error?
  1640. // create error description - encoding error
  1641. error = [TEError error:TE_TEMPLATE_ENCODING_ERROR inLine:0 atToken:TE_PATH];
  1642. [error setLiteral:path];
  1643. // log this error to the console
  1644. [error logErrorMessageForTemplate:path];
  1645. // and add this error to the error log
  1646. *errorLog = [NSArray arrayWithObject:error];
  1647. // this error is not recoverable - abort
  1648. return nil;
  1649. } // end if
  1650. } // end if
  1651. return [NSString stringByExpandingTemplate:templateString
  1652. withStartTag:startTag
  1653. andEndTag:endTag
  1654. usingDictionary:dictionary
  1655. errorsReturned:errorLog];
  1656. #undef fileErrorOcurred
  1657. } // end method
  1658. + (id)valueForDictionary:(id)dictionary key:(NSString *)key
  1659. {
  1660. // use placeholder as key for dictionary lookup
  1661. id currentPlace = dictionary;
  1662. NSArray *path = [key componentsSeparatedByString:@"."];
  1663. for(id component in path) {
  1664. if(currentPlace != nil && [currentPlace respondsToSelector:@selector(valueForKey:)]) currentPlace = [currentPlace valueForKey:component];
  1665. }
  1666. return currentPlace;
  1667. }
  1668. @end // STSTemplateEngine