It's straightforward to build a named substitution system that can be easily applied to strings loaded either in code or in files. The following NSString
category allows you to specify named substitutions in a dictionary, such that any occurrence of the string ${toSubstitute}
are replaced by the object corresponding to toSubstitue
's object in a dictionary.
NSString+CVTemplateAdditions.h:
#import <Foundation/Foundation.h>
@interface NSString (CVTemplateAdditions)
- (NSString *)cvStringByApplyingSubstitutions:(NSDictionary *)substitutions;
@end
NSString+CVTemplateAdditions.m:
#import "NSString+CVTemplateAdditions.h"
@implementation NSString (CVTemplateAdditions)
- (NSString *)cvStringByApplyingSubstitutions:(NSDictionary *)substitutions
{
NSMutableString *toReturn = [self mutableCopy];
[substitutions enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *obj, BOOL *stop) {
NSString *stringToSub = [NSString stringWithFormat:@"${%@}",key];
[toReturn replaceOccurrencesOfString:stringToSub withString:obj options:NSLiteralSearch range:NSMakeRange(0, [toReturn length])];
}];
return [toReturn copy];
}
@end
We could load your text into a file, like so:
Template.txt:
${detailsText}
${nameText}
${addressLineOneText}
${addressLineTwoText}
${cityAddressText}, Il
${zipAddressText}
Phone: ${phoneText}
Email: ${emailText}
And then apply substitutions like:
NSDictionary *substitutions = @{
@"detailsText": @"details text here",
@"nameText": @"name text here",
@"addressLineOneText": @"address line one text here",
@"addressLineTwoText": @"address line two text here",
@"cityAddressText": @"city address text here",
@"zipAddressText": @"zip address text here",
@"phoneText": @"phone text here",
@"emailText": @"email text here"
};
NSString *actual = [input cvStringByApplyingSubstitutions:substitutions];
In the above code, actual
evaluates to the following string:
details text here
name text here
address line one text here
address line two text here
city address text here, Il
zip address text here
Phone: phone text here
Email: email text here
The following SenTestCase
subclass and input files demonstrate the functionality further:
TemplatingEngineTests.h:
#import <SenTestingKit/SenTestingKit.h>
@interface TemplatingEngineTests : SenTestCase
@end
TemplatingEngineTests.m:
#import "TemplatingEngineTests.h"
#import "NSString+CVTemplateAdditions.h"
@implementation TemplatingEngineTests
- (void)setUp
{
[super setUp];
// Set-up code here.
}
- (void)tearDown
{
// Tear-down code here.
[super tearDown];
}
- (void)testApplyingSubstitutionsResultsInExpectedString
{
NSString *expected = [self expectedString];
NSString *input = [self templateString];
NSDictionary *substitutions = @{
@"detailsText": @"details text here",
@"nameText": @"name text here",
@"addressLineOneText": @"address line one text here",
@"addressLineTwoText": @"address line two text here",
@"cityAddressText": @"city address text here",
@"zipAddressText": @"zip address text here",
@"phoneText": @"phone text here",
@"emailText": @"email text here"
};
NSString *actual = [input cvStringByApplyingSubstitutions:substitutions];
STAssertEqualObjects(actual, expected, nil);
}
- (void)testApplyingNilSubstitutionsReturnsStringEqualToItself
{
NSString *testString = [self templateString];
NSString *result = [testString cvStringByApplyingSubstitutions:nil];
STAssertEqualObjects(result, testString, nil);
}
- (void)testNonTemplatedStringReturnsStringEqualToItself
{
NSString *testString = [self nonTemplateString];
NSString *result = [testString cvStringByApplyingSubstitutions:@{@"key": @"sub"}];
STAssertEqualObjects(result, testString, nil);
}
- (NSString *)nonTemplateString
{
return [self stringFromFile:@"NoTemplate"];
}
- (NSString *)templateString
{
return [self stringFromFile:@"Template"];
}
- (NSString *)expectedString
{
return [self stringFromFile:@"TemplateExpected"];
}
- (NSString *)stringFromFile:(NSString *)name
{
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:name ofType:@"txt"];
NSString *testString = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
return testString;
}
@end
Template.txt:
${detailsText}
${nameText}
${addressLineOneText}
${addressLineTwoText}
${cityAddressText}, Il
${zipAddressText}
Phone: ${phoneText}
Email: ${emailText}
TemplateExpected.txt:
details text here
name text here
address line one text here
address line two text here
city address text here, Il
zip address text here
Phone: phone text here
Email: email text here
NoTemplate.txt:
This is an email with no template parameters.
It's quite boring.
Sigh.
TELL ALL YOUR FRIENDS HOW AWESOME OUR APP IS!!!
Hopefully you agree that it becomes much more manageable and readable when you can load complex formats from text files and refer to them by name.