aboutsummaryrefslogtreecommitdiffstats
path: root/Source/ICUTemplateMatcher.m
diff options
context:
space:
mode:
Diffstat (limited to 'Source/ICUTemplateMatcher.m')
-rw-r--r--Source/ICUTemplateMatcher.m192
1 files changed, 192 insertions, 0 deletions
diff --git a/Source/ICUTemplateMatcher.m b/Source/ICUTemplateMatcher.m
new file mode 100644
index 00000000..dbeb49aa
--- /dev/null
+++ b/Source/ICUTemplateMatcher.m
@@ -0,0 +1,192 @@
+//
+// ICUTemplateMatcher.m
+//
+// Created by Matt Gemmell on 19/05/2008.
+// Copyright 2008 Instinctive Code. All rights reserved.
+//
+
+#import "ICUTemplateMatcher.h"
+#import "RegexKitLite.h"
+
+
+@implementation ICUTemplateMatcher
+
+
++ (ICUTemplateMatcher *)matcherWithTemplateEngine:(MGTemplateEngine *)theEngine
+{
+ return [[[ICUTemplateMatcher alloc] initWithTemplateEngine:theEngine] autorelease];
+}
+
+
+- (id)initWithTemplateEngine:(MGTemplateEngine *)theEngine
+{
+ if (self = [super init]) {
+ self.engine = theEngine; // weak ref
+ }
+
+ return self;
+}
+
+
+- (void)dealloc
+{
+ self.engine = nil;
+ self.templateString = nil;
+ self.markerStart = nil;
+ self.markerEnd = nil;
+ self.exprStart = nil;
+ self.exprEnd = nil;
+ self.filterDelimiter = nil;
+ self.regex = nil;
+
+ [super dealloc];
+}
+
+
+- (void)engineSettingsChanged
+{
+ // This method is a good place to cache settings from the engine.
+ self.markerStart = engine.markerStartDelimiter;
+ self.markerEnd = engine.markerEndDelimiter;
+ self.exprStart = engine.expressionStartDelimiter;
+ self.exprEnd = engine.expressionEndDelimiter;
+ self.filterDelimiter = engine.filterDelimiter;
+ self.templateString = engine.templateContents;
+
+ // Note: the \Q ... \E syntax causes everything inside it to be treated as literals.
+ // This help us in the case where the marker/filter delimiters have special meaning
+ // in regular expressions; notably the "$" character in the default marker start-delimiter.
+ // Note: the (?m) syntax makes ICU enable multiline matching.
+ NSString *basePattern = @"(\\Q%@\\E)(?:\\s+)?(.*?)(?:(?:\\s+)?\\Q%@\\E(?:\\s+)?(.*?))?(?:\\s+)?\\Q%@\\E";
+ NSString *mrkrPattern = [NSString stringWithFormat:basePattern, self.markerStart, self.filterDelimiter, self.markerEnd];
+ NSString *exprPattern = [NSString stringWithFormat:basePattern, self.exprStart, self.filterDelimiter, self.exprEnd];
+ self.regex = [NSString stringWithFormat:@"(?m)(?:%@|%@)", mrkrPattern, exprPattern];
+}
+
+
+- (NSDictionary *)firstMarkerWithinRange:(NSRange)range
+{
+ NSRange matchRange = [self.templateString rangeOfRegex:self.regex options:RKLNoOptions inRange:range capture:0 error:NULL];
+ NSMutableDictionary *markerInfo = nil;
+ if (matchRange.length > 0) {
+ markerInfo = [NSMutableDictionary dictionary];
+ [markerInfo setObject:[NSValue valueWithRange:matchRange] forKey:MARKER_RANGE_KEY];
+
+ // Found a match. Obtain marker string.
+ NSString *matchString = [self.templateString substringWithRange:matchRange];
+ NSRange localRange = NSMakeRange(0, [matchString length]);
+ //NSLog(@"mtch: \"%@\"", matchString);
+
+ // Find type of match
+ NSString *matchType = nil;
+ NSRange mrkrSubRange = [matchString rangeOfRegex:regex options:RKLNoOptions inRange:localRange capture:1 error:NULL];
+ BOOL isMarker = (mrkrSubRange.length > 0); // only matches if match has marker-delimiters
+ int offset = 0;
+ if (isMarker) {
+ matchType = MARKER_TYPE_MARKER;
+ } else {
+ matchType = MARKER_TYPE_EXPRESSION;
+ offset = 3;
+ }
+ [markerInfo setObject:matchType forKey:MARKER_TYPE_KEY];
+
+ // Split marker string into marker-name and arguments.
+ NSRange markerRange = NSMakeRange(0, [matchString length]);
+ markerRange = [matchString rangeOfRegex:regex options:RKLNoOptions inRange:localRange capture:2 + offset error:NULL];
+
+ if (markerRange.length > 0) {
+ NSString *markerString = [matchString substringWithRange:markerRange];
+ NSArray *markerComponents = [self argumentsFromString:markerString];
+ if (markerComponents && [markerComponents count] > 0) {
+ [markerInfo setObject:[markerComponents objectAtIndex:0] forKey:MARKER_NAME_KEY];
+ int count = [markerComponents count];
+ if (count > 1) {
+ [markerInfo setObject:[markerComponents subarrayWithRange:NSMakeRange(1, count - 1)]
+ forKey:MARKER_ARGUMENTS_KEY];
+ }
+ }
+
+ // Check for filter.
+ NSRange filterRange = [matchString rangeOfRegex:regex options:RKLNoOptions inRange:localRange capture:3 + offset error:NULL];
+ if (filterRange.length > 0) {
+ // Found a filter. Obtain filter string.
+ NSString *filterString = [matchString substringWithRange:filterRange];
+
+ // Convert first : plus any immediately-following whitespace into a space.
+ localRange = NSMakeRange(0, [filterString length]);
+ NSString *space = @" ";
+ NSRange filterArgDelimRange = [filterString rangeOfRegex:@":(?:\\s+)?" options:RKLNoOptions inRange:localRange
+ capture:0 error:NULL];
+ if (filterArgDelimRange.length > 0) {
+ // Replace found text with space.
+ filterString = [NSString stringWithFormat:@"%@%@%@",
+ [filterString substringWithRange:NSMakeRange(0, filterArgDelimRange.location)],
+ space,
+ [filterString substringWithRange:NSMakeRange(NSMaxRange(filterArgDelimRange),
+ localRange.length - NSMaxRange(filterArgDelimRange))]];
+ }
+
+ // Split into filter-name and arguments.
+ NSArray *filterComponents = [self argumentsFromString:filterString];
+ if (filterComponents && [filterComponents count] > 0) {
+ [markerInfo setObject:[filterComponents objectAtIndex:0] forKey:MARKER_FILTER_KEY];
+ int count = [filterComponents count];
+ if (count > 1) {
+ [markerInfo setObject:[filterComponents subarrayWithRange:NSMakeRange(1, count - 1)]
+ forKey:MARKER_FILTER_ARGUMENTS_KEY];
+ }
+ }
+ }
+ }
+ }
+
+ return markerInfo;
+}
+
+
+- (NSArray *)argumentsFromString:(NSString *)argString
+{
+ // Extract arguments from argString, taking care not to break single- or double-quoted arguments,
+ // including those containing \-escaped quotes.
+ NSString *argsPattern = @"\"(.*?)(?<!\\\\)\"|'(.*?)(?<!\\\\)'|(\\S+)";
+ NSMutableArray *args = [NSMutableArray array];
+
+ int location = 0;
+ while (location != NSNotFound) {
+ NSRange searchRange = NSMakeRange(location, [argString length] - location);
+ NSRange entireRange = [argString rangeOfRegex:argsPattern options:RKLNoOptions
+ inRange:searchRange capture:0 error:NULL];
+ NSRange matchedRange = [argString rangeOfRegex:argsPattern options:RKLNoOptions
+ inRange:searchRange capture:1 error:NULL];
+ if (matchedRange.length == 0) {
+ matchedRange = [argString rangeOfRegex:argsPattern options:RKLNoOptions
+ inRange:searchRange capture:2 error:NULL];
+ if (matchedRange.length == 0) {
+ matchedRange = [argString rangeOfRegex:argsPattern options:RKLNoOptions
+ inRange:searchRange capture:3 error:NULL];
+ }
+ }
+
+ location = NSMaxRange(entireRange) + ((entireRange.length == 0) ? 1 : 0);
+ if (matchedRange.length > 0) {
+ [args addObject:[argString substringWithRange:matchedRange]];
+ } else {
+ location = NSNotFound;
+ }
+ }
+
+ return args;
+}
+
+
+@synthesize engine;
+@synthesize markerStart;
+@synthesize markerEnd;
+@synthesize exprStart;
+@synthesize exprEnd;
+@synthesize filterDelimiter;
+@synthesize templateString;
+@synthesize regex;
+
+
+@end