Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/ccgus/fmdb.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoropenthread <openfibers@gmail.com>2014-04-07 16:33:57 +0400
committeropenthread <openfibers@gmail.com>2014-04-07 16:33:57 +0400
commit2b7f7adff4adff672fbb079aafdbe36bb093545c (patch)
tree96387588fe45584c8be2dece0707bc77674cb411
parentc41fe67736a6b826c727e85ee86b207f8ac0fff5 (diff)
added batch sql splitter
-rw-r--r--fmdb.xcodeproj/project.pbxproj55
-rw-r--r--src/fmdb/FMStatementSplitter/FMSQLStatementSplitter.h21
-rw-r--r--src/fmdb/FMStatementSplitter/FMSQLStatementSplitter.m186
-rw-r--r--src/fmdb/FMStatementSplitter/FMStatementKeywordRecogniser.h97
-rw-r--r--src/fmdb/FMStatementSplitter/FMStatementKeywordRecogniser.m118
-rw-r--r--src/fmdb/FMStatementSplitter/FMStatementQuotedRecogniser.h130
-rw-r--r--src/fmdb/FMStatementSplitter/FMStatementQuotedRecogniser.m180
-rw-r--r--src/fmdb/FMStatementSplitter/FMStatementTokenRecogniser.h16
8 files changed, 803 insertions, 0 deletions
diff --git a/fmdb.xcodeproj/project.pbxproj b/fmdb.xcodeproj/project.pbxproj
index 5c759fe..eec72f4 100644
--- a/fmdb.xcodeproj/project.pbxproj
+++ b/fmdb.xcodeproj/project.pbxproj
@@ -7,6 +7,22 @@
objects = {
/* Begin PBXBuildFile section */
+ 42C753F518F2D2A400F79E14 /* FMSQLStatementSplitter.h in Headers */ = {isa = PBXBuildFile; fileRef = 42C753EE18F2D2A400F79E14 /* FMSQLStatementSplitter.h */; };
+ 42C753F618F2D2A400F79E14 /* FMSQLStatementSplitter.m in Sources */ = {isa = PBXBuildFile; fileRef = 42C753EF18F2D2A400F79E14 /* FMSQLStatementSplitter.m */; };
+ 42C753F718F2D2A400F79E14 /* FMSQLStatementSplitter.m in Sources */ = {isa = PBXBuildFile; fileRef = 42C753EF18F2D2A400F79E14 /* FMSQLStatementSplitter.m */; };
+ 42C753F818F2D2A400F79E14 /* FMSQLStatementSplitter.m in Sources */ = {isa = PBXBuildFile; fileRef = 42C753EF18F2D2A400F79E14 /* FMSQLStatementSplitter.m */; };
+ 42C753F918F2D2A400F79E14 /* FMSQLStatementSplitter.m in Sources */ = {isa = PBXBuildFile; fileRef = 42C753EF18F2D2A400F79E14 /* FMSQLStatementSplitter.m */; };
+ 42C753FA18F2D2A400F79E14 /* FMStatementKeywordRecogniser.h in Headers */ = {isa = PBXBuildFile; fileRef = 42C753F018F2D2A400F79E14 /* FMStatementKeywordRecogniser.h */; };
+ 42C753FB18F2D2A400F79E14 /* FMStatementKeywordRecogniser.m in Sources */ = {isa = PBXBuildFile; fileRef = 42C753F118F2D2A400F79E14 /* FMStatementKeywordRecogniser.m */; };
+ 42C753FC18F2D2A400F79E14 /* FMStatementKeywordRecogniser.m in Sources */ = {isa = PBXBuildFile; fileRef = 42C753F118F2D2A400F79E14 /* FMStatementKeywordRecogniser.m */; };
+ 42C753FD18F2D2A400F79E14 /* FMStatementKeywordRecogniser.m in Sources */ = {isa = PBXBuildFile; fileRef = 42C753F118F2D2A400F79E14 /* FMStatementKeywordRecogniser.m */; };
+ 42C753FE18F2D2A400F79E14 /* FMStatementKeywordRecogniser.m in Sources */ = {isa = PBXBuildFile; fileRef = 42C753F118F2D2A400F79E14 /* FMStatementKeywordRecogniser.m */; };
+ 42C753FF18F2D2A400F79E14 /* FMStatementQuotedRecogniser.h in Headers */ = {isa = PBXBuildFile; fileRef = 42C753F218F2D2A400F79E14 /* FMStatementQuotedRecogniser.h */; };
+ 42C7540018F2D2A400F79E14 /* FMStatementQuotedRecogniser.m in Sources */ = {isa = PBXBuildFile; fileRef = 42C753F318F2D2A400F79E14 /* FMStatementQuotedRecogniser.m */; };
+ 42C7540118F2D2A400F79E14 /* FMStatementQuotedRecogniser.m in Sources */ = {isa = PBXBuildFile; fileRef = 42C753F318F2D2A400F79E14 /* FMStatementQuotedRecogniser.m */; };
+ 42C7540218F2D2A400F79E14 /* FMStatementQuotedRecogniser.m in Sources */ = {isa = PBXBuildFile; fileRef = 42C753F318F2D2A400F79E14 /* FMStatementQuotedRecogniser.m */; };
+ 42C7540318F2D2A400F79E14 /* FMStatementQuotedRecogniser.m in Sources */ = {isa = PBXBuildFile; fileRef = 42C753F318F2D2A400F79E14 /* FMStatementQuotedRecogniser.m */; };
+ 42C7540418F2D2A400F79E14 /* FMStatementTokenRecogniser.h in Headers */ = {isa = PBXBuildFile; fileRef = 42C753F418F2D2A400F79E14 /* FMStatementTokenRecogniser.h */; };
621721B21892BFE30006691F /* FMDatabase.m in Sources */ = {isa = PBXBuildFile; fileRef = CCC24EBB0A13E34D00A6D3E3 /* FMDatabase.m */; };
621721B31892BFE30006691F /* FMResultSet.m in Sources */ = {isa = PBXBuildFile; fileRef = CCC24EC00A13E34D00A6D3E3 /* FMResultSet.m */; };
621721B41892BFE30006691F /* FMDatabaseQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = CC47A00E148581E9002CCDAB /* FMDatabaseQueue.m */; };
@@ -82,6 +98,13 @@
/* Begin PBXFileReference section */
08FB779EFE84155DC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
32A70AAB03705E1F00C91783 /* fmdb_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fmdb_Prefix.pch; path = src/sample/fmdb_Prefix.pch; sourceTree = SOURCE_ROOT; };
+ 42C753EE18F2D2A400F79E14 /* FMSQLStatementSplitter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMSQLStatementSplitter.h; sourceTree = "<group>"; };
+ 42C753EF18F2D2A400F79E14 /* FMSQLStatementSplitter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMSQLStatementSplitter.m; sourceTree = "<group>"; };
+ 42C753F018F2D2A400F79E14 /* FMStatementKeywordRecogniser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMStatementKeywordRecogniser.h; sourceTree = "<group>"; };
+ 42C753F118F2D2A400F79E14 /* FMStatementKeywordRecogniser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMStatementKeywordRecogniser.m; sourceTree = "<group>"; };
+ 42C753F218F2D2A400F79E14 /* FMStatementQuotedRecogniser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMStatementQuotedRecogniser.h; sourceTree = "<group>"; };
+ 42C753F318F2D2A400F79E14 /* FMStatementQuotedRecogniser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMStatementQuotedRecogniser.m; sourceTree = "<group>"; };
+ 42C753F418F2D2A400F79E14 /* FMStatementTokenRecogniser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMStatementTokenRecogniser.h; sourceTree = "<group>"; };
6290CBB5188FE836009790F8 /* libFMDB-IOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libFMDB-IOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
6290CBB6188FE836009790F8 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
6290CBC6188FE837009790F8 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; };
@@ -208,6 +231,21 @@
name = Products;
sourceTree = "<group>";
};
+ 42C753ED18F2D2A400F79E14 /* FMStatementSplitter */ = {
+ isa = PBXGroup;
+ children = (
+ 42C753EE18F2D2A400F79E14 /* FMSQLStatementSplitter.h */,
+ 42C753EF18F2D2A400F79E14 /* FMSQLStatementSplitter.m */,
+ 42C753F018F2D2A400F79E14 /* FMStatementKeywordRecogniser.h */,
+ 42C753F118F2D2A400F79E14 /* FMStatementKeywordRecogniser.m */,
+ 42C753F218F2D2A400F79E14 /* FMStatementQuotedRecogniser.h */,
+ 42C753F318F2D2A400F79E14 /* FMStatementQuotedRecogniser.m */,
+ 42C753F418F2D2A400F79E14 /* FMStatementTokenRecogniser.h */,
+ );
+ name = FMStatementSplitter;
+ path = src/fmdb/FMStatementSplitter;
+ sourceTree = "<group>";
+ };
8314AF3018CD737D00EC0E25 /* fmdb */ = {
isa = PBXGroup;
children = (
@@ -222,6 +260,7 @@
CC50F2CB0DF9183600E4AAAE /* FMDatabaseAdditions.m */,
CC9E4EB713B31188005F9210 /* FMDatabasePool.h */,
CC9E4EB813B31188005F9210 /* FMDatabasePool.m */,
+ 42C753ED18F2D2A400F79E14 /* FMStatementSplitter */,
);
name = fmdb;
sourceTree = "<group>";
@@ -284,11 +323,15 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
+ 42C753FF18F2D2A400F79E14 /* FMStatementQuotedRecogniser.h in Headers */,
EE42910712B42FC90088BD94 /* FMDatabase.h in Headers */,
+ 42C753F518F2D2A400F79E14 /* FMSQLStatementSplitter.h in Headers */,
+ 42C753FA18F2D2A400F79E14 /* FMStatementKeywordRecogniser.h in Headers */,
EE42910612B42FC30088BD94 /* FMDatabaseAdditions.h in Headers */,
EE42910912B42FD00088BD94 /* FMResultSet.h in Headers */,
8314AF3318CD73D600EC0E25 /* FMDB.h in Headers */,
CC9E4EBA13B31188005F9210 /* FMDatabasePool.h in Headers */,
+ 42C7540418F2D2A400F79E14 /* FMStatementTokenRecogniser.h in Headers */,
CC47A00F148581E9002CCDAB /* FMDatabaseQueue.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -420,8 +463,11 @@
files = (
621721B31892BFE30006691F /* FMResultSet.m in Sources */,
621721B21892BFE30006691F /* FMDatabase.m in Sources */,
+ 42C753FE18F2D2A400F79E14 /* FMStatementKeywordRecogniser.m in Sources */,
+ 42C7540318F2D2A400F79E14 /* FMStatementQuotedRecogniser.m in Sources */,
621721B61892BFE30006691F /* FMDatabasePool.m in Sources */,
621721B41892BFE30006691F /* FMDatabaseQueue.m in Sources */,
+ 42C753F918F2D2A400F79E14 /* FMSQLStatementSplitter.m in Sources */,
621721B51892BFE30006691F /* FMDatabaseAdditions.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -433,9 +479,12 @@
CCC24EC20A13E34D00A6D3E3 /* FMDatabase.m in Sources */,
CCC24EC50A13E34D00A6D3E3 /* main.m in Sources */,
CCC24EC70A13E34D00A6D3E3 /* FMResultSet.m in Sources */,
+ 42C7540018F2D2A400F79E14 /* FMStatementQuotedRecogniser.m in Sources */,
CC50F2CD0DF9183600E4AAAE /* FMDatabaseAdditions.m in Sources */,
+ 42C753F618F2D2A400F79E14 /* FMSQLStatementSplitter.m in Sources */,
CC9E4EB913B31188005F9210 /* FMDatabasePool.m in Sources */,
CC47A010148581E9002CCDAB /* FMDatabaseQueue.m in Sources */,
+ 42C753FB18F2D2A400F79E14 /* FMStatementKeywordRecogniser.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -446,9 +495,12 @@
BFC152B118417F0D00605DF7 /* FMDatabaseAdditions.m in Sources */,
BF940F5C18417D490001E077 /* FMDBTempDBTests.m in Sources */,
BF940F5E18417DEA0001E077 /* FMDatabaseAdditionsTests.m in Sources */,
+ 42C7540218F2D2A400F79E14 /* FMStatementQuotedRecogniser.m in Sources */,
BF5D042118416BB2008C5AA9 /* FMDatabaseTests.m in Sources */,
+ 42C753F818F2D2A400F79E14 /* FMSQLStatementSplitter.m in Sources */,
BFE55E131841C9A000CB3A63 /* FMDatabasePoolTests.m in Sources */,
BFE55E151841D38800CB3A63 /* FMDatabaseQueueTests.m in Sources */,
+ 42C753FD18F2D2A400F79E14 /* FMStatementKeywordRecogniser.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -458,8 +510,11 @@
files = (
EE42910812B42FCC0088BD94 /* FMDatabase.m in Sources */,
EE42910512B42FBC0088BD94 /* FMDatabaseAdditions.m in Sources */,
+ 42C753FC18F2D2A400F79E14 /* FMStatementKeywordRecogniser.m in Sources */,
+ 42C7540118F2D2A400F79E14 /* FMStatementQuotedRecogniser.m in Sources */,
EE42910A12B42FD20088BD94 /* FMResultSet.m in Sources */,
CC9E4EBB13B31188005F9210 /* FMDatabasePool.m in Sources */,
+ 42C753F718F2D2A400F79E14 /* FMSQLStatementSplitter.m in Sources */,
CC47A011148581E9002CCDAB /* FMDatabaseQueue.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/src/fmdb/FMStatementSplitter/FMSQLStatementSplitter.h b/src/fmdb/FMStatementSplitter/FMSQLStatementSplitter.h
new file mode 100644
index 0000000..d9beba1
--- /dev/null
+++ b/src/fmdb/FMStatementSplitter/FMSQLStatementSplitter.h
@@ -0,0 +1,21 @@
+//
+// FMSQLStatementSplitter.h
+// FMDB
+//
+// Created by openthread on 3/5/14.
+// Copyright (c) 2014 openthread. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface FMSplittedStatement : NSObject
+@property (nonatomic, retain) NSString *statementString;//statement string
+@end
+
+@interface FMSQLStatementSplitter : NSObject
+
++ (instancetype)sharedInstance;
+
+- (NSArray *)statementsFromBatchSqlStatement:(NSString *)batchStatement;
+
+@end
diff --git a/src/fmdb/FMStatementSplitter/FMSQLStatementSplitter.m b/src/fmdb/FMStatementSplitter/FMSQLStatementSplitter.m
new file mode 100644
index 0000000..71b1253
--- /dev/null
+++ b/src/fmdb/FMStatementSplitter/FMSQLStatementSplitter.m
@@ -0,0 +1,186 @@
+//
+// FMSQLStatementSplitter.m
+// FMDB
+//
+// Created by openthread on 3/5/14.
+// Copyright (c) 2014 openthread. All rights reserved.
+//
+
+#import "FMSQLStatementSplitter.h"
+#import "FMStatementKeywordRecogniser.h"
+#import "FMStatementQuotedRecogniser.h"
+#import "FMDatabase.h"
+
+@implementation FMSplittedStatement
+
+- (NSString *)description
+{
+ NSString *description = [super description];
+ description = [description stringByAppendingFormat:@" %@", self.statementString];
+ return description;
+}
+
+@end
+
+@implementation FMSQLStatementSplitter
+{
+ NSMutableArray *_tokenRecognisers;
+}
+
+- (id)init
+{
+ self = [super init];
+ if (self)
+ {
+ _tokenRecognisers = [NSMutableArray array];
+ FMDBRetain(_tokenRecognisers);
+
+ //' quote
+ FMStatementQuotedRecogniser *singleQuoteRecogniser = nil;
+ singleQuoteRecogniser = [FMStatementQuotedRecogniser quotedRecogniserWithStartQuote:@"'"
+ endQuote:@"'"
+ escapeSequence:@"\\"
+ name:@"SingleQuote"];
+ singleQuoteRecogniser.shouldQuoteEscapeSequence = YES;
+ [_tokenRecognisers addObject:singleQuoteRecogniser];
+
+ //" quote
+ FMStatementQuotedRecogniser *doubleQuoteRecogniser = nil;
+ doubleQuoteRecogniser = [FMStatementQuotedRecogniser quotedRecogniserWithStartQuote:@"\""
+ endQuote:@"\""
+ escapeSequence:@"\\"
+ name:@"DoubleQuote"];
+
+ doubleQuoteRecogniser.shouldQuoteEscapeSequence = NO;
+ [_tokenRecognisers addObject:doubleQuoteRecogniser];
+
+ //` quote
+ FMStatementQuotedRecogniser *sqlashQuoteRecogniser = nil;
+ sqlashQuoteRecogniser = [FMStatementQuotedRecogniser quotedRecogniserWithStartQuote:@"`"
+ endQuote:@"`"
+ escapeSequence:@"\\"
+ name:@"SqlashQuote"];
+ sqlashQuoteRecogniser.shouldQuoteEscapeSequence = NO;
+ [_tokenRecognisers addObject:sqlashQuoteRecogniser];
+
+ //; recognizer
+ NSArray *operatorKeywords = @[@";"];
+ [_tokenRecognisers addObject:[FMStatementKeywordRecogniser recogniserForKeywords:operatorKeywords]];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ FMDBRelease(_tokenRecognisers);
+#if ! __has_feature(objc_arc)
+ [super dealloc];
+#endif
+}
+
++ (instancetype)sharedInstance
+{
+ static id instance;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ instance = [[self alloc] init];
+ });
+ return instance;
+}
+
+
+- (NSArray *)statementsFromBatchSqlStatement:(NSString *)input;
+{
+ NSUInteger currentTokenOffset = 0;
+ NSUInteger inputLength = [input length];
+ NSArray *recs = _tokenRecognisers;
+ NSScanner *scanner = [NSScanner scannerWithString:input];
+
+ NSMutableArray *resultArray = [NSMutableArray array];
+ NSUInteger lastSplitterLocation = 0;
+
+ while (currentTokenOffset < inputLength)
+ {
+ @autoreleasepool
+ {
+ BOOL recognised = NO;
+ for (NSUInteger i = 0; i < recs.count; i++)
+ {
+ id<FMStatementTokenRecogniser> recogniser = recs[i];
+ NSRange range = [recogniser recogniseRangeWithScanner:scanner currentTokenPosition:&currentTokenOffset];
+ if (NSNotFound != range.location)
+ {
+ if (i == 3)//Recognised ; keyword
+ {
+ FMSplittedStatement *statement = [[FMSplittedStatement alloc] init];
+ statement.statementString = [input substringWithRange:NSMakeRange(lastSplitterLocation, currentTokenOffset - lastSplitterLocation)];
+ lastSplitterLocation = currentTokenOffset;
+ [resultArray addObject:statement];
+ FMDBRelease(statement);
+ }
+ recognised = YES;
+ break;
+ }
+ }
+
+ if (!recognised)
+ {
+ currentTokenOffset ++;
+ }
+
+ if (currentTokenOffset == inputLength && lastSplitterLocation != inputLength)
+ //input comes to end, put all string remaining to the last statement
+ {
+ FMSplittedStatement *statement = [[FMSplittedStatement alloc] init];
+ statement.statementString = [input substringWithRange:NSMakeRange(lastSplitterLocation, currentTokenOffset - lastSplitterLocation)];
+ lastSplitterLocation = currentTokenOffset;
+ [resultArray addObject:statement];
+ FMDBRelease(statement);
+ }
+ }
+ }
+
+ return [NSArray arrayWithArray:resultArray];
+}
+
++ (void)test
+{
+ NSArray *statementStringArray = @[
+ @"SELECT TABLE IF EXISTS ';' `web_offline_track`;",
+ @"select TABLE IF NOT EXISTS \";\" `web_offline_track` (`id` VARCHAR(40) NOT NULL, `type` INT NULL, `type_extra` BIGINT NULL, `track_id` BIGINT NULL, `detail` TEXT NULL, `size` INT NULL, `dfsid` BIGINT NULL, `bitrate` INT NULL, `state` INT NULL, `download_time` INT NULL, `complete_time` INT NULL, `sou;rce_href` TEXT NULL, `source_text` TEXT NULL, `source_extra` TEXT NULL, `album_id` VARCHAR(40) NULL, `relative_path` TEXT NULL, `track_name` TEXT NULL, `artist_name` TEXT NULL, `album_name` TEXT NULL, PRIMARY KEY (`id`));",
+ @"create TABLE select IF EXISTS `web_playl;ist_order`;",
+ @"select TABLE IF NOT EXISTS `web_playlist_order` (`playlist_id` BIGINT NOT NULL, `field` VARCHAR(40) NULL, `order` VARCHAR(40) NULL, PRIMARY KEY select (`playlist_SELECTid`));",
+ @"'\\\\';",
+ @"'blah blah"];
+
+ NSMutableString *batchStatement = [NSMutableString string];
+ for (NSString *str in statementStringArray)
+ {
+ [batchStatement appendString:str];
+ }
+
+ //Result
+ NSArray *statements = [[FMSQLStatementSplitter sharedInstance] statementsFromBatchSqlStatement:batchStatement];
+ NSLog(@"%@ test with parsed result: %@",[super description], statements);
+
+ //counts
+ NSLog(@"statement count :%lu expected %lu.",
+ (unsigned long)statements.count,
+ (unsigned long)statementStringArray.count);
+
+ //single statement
+ for (NSUInteger i = 0; i<statementStringArray.count && i<statements.count; i++)
+ {
+ NSString *originalString = statementStringArray[i];
+ FMSplittedStatement *statement = statements[i];
+ NSLog(@"statement check successed : %d", [originalString isEqualToString:statement.statementString]);
+ }
+}
+
+//+ (void)load
+//{
+// [super load];
+// [self test];
+//}
+
+@end
diff --git a/src/fmdb/FMStatementSplitter/FMStatementKeywordRecogniser.h b/src/fmdb/FMStatementSplitter/FMStatementKeywordRecogniser.h
new file mode 100644
index 0000000..2c636ee
--- /dev/null
+++ b/src/fmdb/FMStatementSplitter/FMStatementKeywordRecogniser.h
@@ -0,0 +1,97 @@
+//
+// FMStatementKeywordRecogniser.h
+// FMDB
+//
+// Created by openthread on 3/5/14.
+// Copyright (c) 2014 openthread. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+#import "FMStatementTokenRecogniser.h"
+
+/**
+ * The FMStatementKeywordRecogniser class attempts to recognise a specific keyword in a token stream.
+ *
+ * A keyword recogniser attempts to recognise a specific word or set of symbols.
+ * Keyword recognisers can also check that the keyword is not followed by specific characters in order to stop it recognising the beginnings of words.
+ */
+@interface FMStatementKeywordRecogniser : NSObject <FMStatementTokenRecogniser>
+
+///---------------------------------------------------------------------------------------
+/// @name Creating and Initialising a Keyword Recogniser
+///---------------------------------------------------------------------------------------
+
+/**
+ * Creates a Keyword Recogniser for a specific keyword.
+ *
+ * @param keyword The keyword to recognise.
+ *
+ * @return Returns a keyword recogniser for the passed keyword.
+ *
+ * @see initWithKeyword:
+ * @see recogniserForKeyword:invalidFollowingCharacters:
+ */
++ (id)recogniserForKeyword:(NSString *)keyword;
+
++ (id)recogniserForKeywords:(NSArray *)keywords;
+
+/**
+ * Creates a Keyword Recogniser for a specific keyword.
+ *
+ * @param keyword The keyword to recognise.
+ * @param invalidFollowingCharacters A set of characters that may not follow the keyword in the string being tokenised.
+ *
+ * @return Returns a keyword recogniser for the passed keyword.
+ *
+ * @see recogniserForKeyword:
+ * @see initWithKeyword:invalidFollowingCharacters:
+ */
++ (id)recogniserForKeyword:(NSString *)keyword invalidFollowingCharacters:(NSCharacterSet *)invalidFollowingCharacters;
+
++ (id)recogniserForKeywords:(NSArray *)keywords invalidFollowingCharacters:(NSCharacterSet *)invalidFollowingCharacters;
+
+/**
+ * Initialises a Keyword Recogniser to recognise a specific keyword.
+ *
+ * @param keyword The keyword to recognise.
+ *
+ * @return Returns the keyword recogniser initialised to recognise the passed keyword.
+ *
+ * @see recogniserForKeyword:
+ * @see initWithKeyword:invalidFollowingCharacters:
+ */
+- (id)initWithKeyword:(NSString *)keyword;
+
+- (id)initWithKeywords:(NSArray *)keywords;
+
+/**
+ * Initialises a Keyword Recogniser to recognise a specific keyword.
+ *
+ * @param keyword The keyword to recognise.
+ * @param invalidFollowingCharacters A set of characters that may not follow the keyword in the string being tokenised.
+ *
+ * @return Returns the keyword recogniser initialised to recognise the passed keyword.
+ *
+ * @see initWithKeyword:
+ * @see recogniserForKeyword:invalidFollowingCharacters:
+ */
+- (id)initWithKeyword:(NSString *)keyword invalidFollowingCharacters:(NSCharacterSet *)invalidFollowingCharacters;
+
+- (id)initWithKeywords:(NSArray *)keywords invalidFollowingCharacters:(NSCharacterSet *)invalidFollowingCharacters;
+
+///---------------------------------------------------------------------------------------
+/// @name Configuring a Keyword Recogniser
+///---------------------------------------------------------------------------------------
+
+/**
+ * The keyword that the recogniser should attempt to recognise.
+ */
+@property (readwrite,retain,nonatomic) NSArray *keywords;
+
+/**
+ * A set of characters that may not follow the keyword.
+ */
+@property (readwrite,retain,nonatomic) NSCharacterSet *invalidFollowingCharacters;
+
+@end
diff --git a/src/fmdb/FMStatementSplitter/FMStatementKeywordRecogniser.m b/src/fmdb/FMStatementSplitter/FMStatementKeywordRecogniser.m
new file mode 100644
index 0000000..3ca3c27
--- /dev/null
+++ b/src/fmdb/FMStatementSplitter/FMStatementKeywordRecogniser.m
@@ -0,0 +1,118 @@
+//
+// FMStatementKeywordRecogniser.m
+// FMDB
+//
+// Created by openthread on 3/5/14.
+// Copyright (c) 2014 openthread. All rights reserved.
+//
+
+#import "FMStatementKeywordRecogniser.h"
+#import "FMDatabase.h"
+
+@implementation FMStatementKeywordRecogniser
+
+@synthesize keywords = _keywords;
+@synthesize invalidFollowingCharacters = _invalidFollowingCharacters;
+
++ (id)recogniserForKeyword:(NSString *)keyword
+{
+ return [self recogniserForKeywords:@[keyword]];
+}
+
++ (id)recogniserForKeywords:(NSArray *)keywords
+{
+ return FMDBReturnAutoreleased([[self alloc] initWithKeywords:keywords]);
+}
+
+- (id)initWithKeyword:(NSString *)keyword
+{
+ return [self initWithKeywords:@[keyword]];
+}
+
+- (id)initWithKeywords:(NSArray *)keywords
+{
+ return [self initWithKeywords:keywords invalidFollowingCharacters:nil];
+}
+
++ (id)recogniserForKeyword:(NSString *)keyword invalidFollowingCharacters:(NSCharacterSet *)invalidFollowingCharacters
+{
+ return [self recogniserForKeywords:@[keyword]
+ invalidFollowingCharacters:invalidFollowingCharacters];
+}
+
++ (id)recogniserForKeywords:(NSArray *)keywords invalidFollowingCharacters:(NSCharacterSet *)invalidFollowingCharacters
+{
+ return FMDBReturnAutoreleased([[self alloc] initWithKeywords:keywords invalidFollowingCharacters:invalidFollowingCharacters]);
+}
+
+- (id)initWithKeyword:(NSString *)keyword invalidFollowingCharacters:(NSCharacterSet *)invalidFollowingCharacters
+{
+ return [self initWithKeywords:@[keyword] invalidFollowingCharacters:invalidFollowingCharacters];
+}
+
+- (id)initWithKeywords:(NSArray *)keywords invalidFollowingCharacters:(NSCharacterSet *)invalidFollowingCharacters
+{
+ self = [super init];
+
+ if (nil != self)
+ {
+ self.keywords = keywords;
+ [self setInvalidFollowingCharacters:invalidFollowingCharacters];
+ }
+
+ return self;
+}
+
+- (id)init
+{
+ return [self initWithKeyword:@" "];
+}
+
+#define CPKeywordRecogniserKeywordKey @"K.k"
+#define CPKeywordRecogniserInvalidFollowingCharactersKey @"K.f"
+
+- (id)initWithCoder:(NSCoder *)aDecoder
+{
+ self = [super init];
+
+ if (nil != self)
+ {
+ [self setKeywords:[aDecoder decodeObjectForKey:CPKeywordRecogniserKeywordKey]];
+ [self setInvalidFollowingCharacters:[aDecoder decodeObjectForKey:CPKeywordRecogniserInvalidFollowingCharactersKey]];
+ }
+
+ return self;
+}
+
+- (void)encodeWithCoder:(NSCoder *)aCoder
+{
+ [aCoder encodeObject:[self keywords] forKey:CPKeywordRecogniserKeywordKey];
+ [aCoder encodeObject:[self invalidFollowingCharacters] forKey:CPKeywordRecogniserInvalidFollowingCharactersKey];
+}
+
+- (NSRange)recogniseRangeWithScanner:(NSScanner *)scanner currentTokenPosition:(NSUInteger *)tokenPosition
+{
+ for (NSString *keyword in self.keywords)
+ {
+ NSUInteger kwLength = [keyword length];
+ NSUInteger remainingChars = [[scanner string] length] - *tokenPosition;
+ if (remainingChars >= kwLength)
+ {
+ if (CFStringFindWithOptions((CFStringRef)[scanner string], (CFStringRef)keyword, CFRangeMake(*tokenPosition, kwLength), kCFCompareAnchored | kCFCompareCaseInsensitive, NULL))
+ {
+ if (remainingChars == kwLength ||
+ nil == self.invalidFollowingCharacters ||
+ !CFStringFindCharacterFromSet((CFStringRef)[scanner string], (CFCharacterSetRef)self.invalidFollowingCharacters, CFRangeMake(*tokenPosition + kwLength, 1), kCFCompareAnchored, NULL))
+ {
+ NSRange result = NSMakeRange(*tokenPosition, kwLength);
+ *tokenPosition = *tokenPosition + kwLength;
+ return result;
+ }
+ }
+ }
+ }
+
+ return NSMakeRange(NSNotFound, 0);
+}
+
+@end
diff --git a/src/fmdb/FMStatementSplitter/FMStatementQuotedRecogniser.h b/src/fmdb/FMStatementSplitter/FMStatementQuotedRecogniser.h
new file mode 100644
index 0000000..567e251
--- /dev/null
+++ b/src/fmdb/FMStatementSplitter/FMStatementQuotedRecogniser.h
@@ -0,0 +1,130 @@
+//
+// FMStatementQuotedRecogniser.h
+// FMDB
+//
+// Created by openthread on 3/5/14.
+// Copyright (c) 2014 openthread. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+#import "FMStatementTokenRecogniser.h"
+
+/**
+ * The FMStatementQuotedRecogniser class is used to recognise quoted literals in the input string. This can be used for quoted strings, characters, comments and many other things.
+ *
+ * Quoted tokens are recognised via a start string and end string. You may optionally add an escape sequence string that stops the end quote being recognised at that point in the input.
+ * You may optionally provide a block used to replace escape sequences with their actual meaning. If you don't provide an escape replcement block it is assumed that the character
+ * following the escape sequence replaces the whole sequence.
+ *
+ * Finally, you may also provide a maximum length for the quoted sequence to recognise. If you want to recognise strings of any length, pass NSNotFound.
+ */
+@interface FMStatementQuotedRecogniser : NSObject <FMStatementTokenRecogniser>
+
+///---------------------------------------------------------------------------------------
+/// @name Creating and Initialising a Quoted Recogniser
+///---------------------------------------------------------------------------------------
+
+/**
+ * Creates a quoted recogniser that recognises quoted litterals starting with startQuote and ending with endQuote.
+ *
+ * @param startQuote A string that indicates the beginning of a quoted literal.
+ * @param endQuote A string that indicates the end of the quoted literal.
+ * @param name The name to attach to recognised tokens.
+ * @return Returns a FMStatementQuotedRecogniser that recognises C like identifiers.
+ *
+ * @see quotedRecogniserWithStartQuote:endQuote:escapeSequence:name:
+ * @see quotedRecogniserWithStartQuote:endQuote:escapeSequence:maximumLength:name:
+ */
++ (id)quotedRecogniserWithStartQuote:(NSString *)startQuote endQuote:(NSString *)endQuote name:(NSString *)name;
+
+/**
+ * Creates a quoted recogniser that recognises quoted litterals starting with startQuote and ending with endQuote. Escaped sequences are recognised by the escapeSequence string.
+ *
+ * @param startQuote A string that indicates the beginning of a quoted literal.
+ * @param endQuote A string that indicates the end of the quoted literal.
+ * @param escapeSequence A string that indicates an escaped character.
+ * @param name The name to attach to recognised tokens.
+ * @return Returns a FMStatementQuotedRecogniser that recognises C like identifiers.
+ *
+ * @see quotedRecogniserWithStartQuote:endQuote:name:
+ * @see quotedRecogniserWithStartQuote:endQuote:escapeSequence:maximumLength:name:
+ */
++ (id)quotedRecogniserWithStartQuote:(NSString *)startQuote endQuote:(NSString *)endQuote escapeSequence:(NSString *)escapeSequence name:(NSString *)name;
+
+/**
+ * Creates a quoted recogniser that recognises quoted litterals starting with startQuote and ending with endQuote. Escaped sequences are recognised by the escapeSequence string. Quoted strings have a maximum length.
+ *
+ * @param startQuote A string that indicates the beginning of a quoted literal.
+ * @param endQuote A string that indicates the end of the quoted literal.
+ * @param escapeSequence A string that indicates an escaped character.
+ * @param maximumLength The maximum length of the resulting string.
+ * @param name The name to attach to recognised tokens.
+ * @return Returns a FMStatementQuotedRecogniser that recognises C like identifiers.
+ *
+ * @see quotedRecogniserWithStartQuote:endQuote:name:
+ * @see quotedRecogniserWithStartQuote:endQuote:escapeSequence:name:
+ * @see initWithStartQuote:endQuote:escapeSequence:maximumLength:name:
+ */
++ (id)quotedRecogniserWithStartQuote:(NSString *)startQuote endQuote:(NSString *)endQuote escapeSequence:(NSString *)escapeSequence maximumLength:(NSUInteger)maximumLength name:(NSString *)name;
+
+/**
+ * Initialises a quoted recogniser that recognises quoted litterals starting with startQuote and ending with endQuote. Escaped sequences are recognised by the escapeSequence string. Quoted strings have a maximum length.
+ *
+ * @param startQuote A string that indicates the beginning of a quoted literal.
+ * @param endQuote A string that indicates the end of the quoted literal.
+ * @param escapeSequence A string that indicates an escaped character.
+ * @param maximumLength The maximum length of the resulting string.
+ * @param name The name to attach to recognised tokens.
+ * @return Returns a FMStatementQuotedRecogniser that recognises C like identifiers.
+ *
+ * @see quotedRecogniserWithStartQuote:endQuote:escapeSequence:maximumLength:name:
+ */
+- (id)initWithStartQuote:(NSString *)startQuote endQuote:(NSString *)endQuote escapeSequence:(NSString *)escapeSequence maximumLength:(NSUInteger)maximumLength name:(NSString *)name;
+
+///---------------------------------------------------------------------------------------
+/// @name Configuring a Quoted Recogniser
+///---------------------------------------------------------------------------------------
+
+/**
+ * Determines the string used to indicate the start of the quoted literal.
+ *
+ * @see endQuote
+ */
+@property (readwrite,copy) NSString *startQuote;
+
+/**
+ * Determines the string used to indicate the end of the quoted literal.
+ *
+ * @see startQuote
+ */
+@property (readwrite,copy) NSString *endQuote;
+
+/**
+ * Determines the string used to indicate an escaped character in the quoted literal.
+ */
+@property (readwrite,copy) NSString *escapeSequence;
+
+/**
+ * If `YES`, quoted string will contains `escapeSequence`.
+ * If `NO`, quoted string will not contains `escapeSequence`.
+ * Default is `NO`.
+ */
+@property (nonatomic, assign) BOOL shouldQuoteEscapeSequence;
+
+/**
+ * Determines how much of the input string to consume when an escaped literal is found, and what to replace it with.
+ */
+@property (readwrite,copy) NSString *(^escapeReplacer)(NSString *tokenStream, NSUInteger *quotePosition);
+
+/**
+ * Determines the maximum length of the quoted literal not including quotes. To indicate the literal can be any length specify NSNotFound.
+ */
+@property (readwrite,assign) NSUInteger maximumLength;
+
+/**
+ * Determines the name of the token produced.
+ */
+@property (readwrite,copy) NSString *name;
+
+@end
diff --git a/src/fmdb/FMStatementSplitter/FMStatementQuotedRecogniser.m b/src/fmdb/FMStatementSplitter/FMStatementQuotedRecogniser.m
new file mode 100644
index 0000000..3db01d6
--- /dev/null
+++ b/src/fmdb/FMStatementSplitter/FMStatementQuotedRecogniser.m
@@ -0,0 +1,180 @@
+//
+// FMStatementQuotedRecogniser.m
+// FMDB
+//
+// Created by openthread on 3/5/14.
+// Copyright (c) 2014 openthread. All rights reserved.
+//
+
+#import "FMStatementQuotedRecogniser.h"
+#import "FMDatabase.h"
+
+@implementation FMStatementQuotedRecogniser
+
+@synthesize startQuote = _startQuote;
+@synthesize endQuote = _endQuote;
+@synthesize escapeSequence = _escapeSequence;
+@synthesize escapeReplacer = _escapeReplacer;
+@synthesize maximumLength = _maximumLength;
+@synthesize name = _name;
+
++ (NSUInteger)minWithLeftParam:(NSUInteger)left rightParam:(NSUInteger)right
+{
+ return (left < right ? left : right);
+}
+
++ (id)quotedRecogniserWithStartQuote:(NSString *)startQuote endQuote:(NSString *)endQuote name:(NSString *)name
+{
+ return [FMStatementQuotedRecogniser quotedRecogniserWithStartQuote:startQuote endQuote:endQuote escapeSequence:nil name:name];
+}
+
++ (id)quotedRecogniserWithStartQuote:(NSString *)startQuote endQuote:(NSString *)endQuote escapeSequence:(NSString *)escapeSequence name:(NSString *)name
+{
+ return [FMStatementQuotedRecogniser quotedRecogniserWithStartQuote:startQuote endQuote:endQuote escapeSequence:escapeSequence maximumLength:NSNotFound name:name];
+}
+
++ (id)quotedRecogniserWithStartQuote:(NSString *)startQuote endQuote:(NSString *)endQuote escapeSequence:(NSString *)escapeSequence maximumLength:(NSUInteger)maximumLength name:(NSString *)name
+{
+ return FMDBReturnAutoreleased([[FMStatementQuotedRecogniser alloc] initWithStartQuote:startQuote endQuote:endQuote escapeSequence:escapeSequence maximumLength:maximumLength name:name]);
+}
+
+- (id)initWithStartQuote:(NSString *)initStartQuote endQuote:(NSString *)initEndQuote escapeSequence:(NSString *)initEscapeSequence maximumLength:(NSUInteger)initMaximumLength name:(NSString *)initName
+{
+ self = [super init];
+
+ if (nil != self)
+ {
+ [self setStartQuote:initStartQuote];
+ [self setEndQuote:initEndQuote];
+ [self setEscapeSequence:initEscapeSequence];
+ [self setMaximumLength:initMaximumLength];
+ [self setName:initName];
+ }
+
+ return self;
+}
+
+#define CPQuotedRecogniserStartQuoteKey @"Q.s"
+#define CPQuotedRecogniserEndQuoteKey @"Q.e"
+#define CPQuotedRecogniserEscapeSequenceKey @"Q.es"
+#define CPQuotedRecogniserMaximumLengthKey @"Q.m"
+#define CPQuotedRecogniserNameKey @"Q.n"
+
+- (id)initWithCoder:(NSCoder *)aDecoder
+{
+ self = [super init];
+
+ if (nil != self)
+ {
+ [self setStartQuote:[aDecoder decodeObjectForKey:CPQuotedRecogniserStartQuoteKey]];
+ [self setEndQuote:[aDecoder decodeObjectForKey:CPQuotedRecogniserEndQuoteKey]];
+ [self setEscapeSequence:[aDecoder decodeObjectForKey:CPQuotedRecogniserEscapeSequenceKey]];
+ @try
+ {
+ [self setMaximumLength:[aDecoder decodeIntegerForKey:CPQuotedRecogniserMaximumLengthKey]];
+ }
+ @catch (NSException *exception)
+ {
+ NSLog(@"Warning, value for maximum length too long for this platform, allowing infinite lengths");
+ [self setMaximumLength:NSNotFound];
+ }
+ [self setName:[aDecoder decodeObjectForKey:CPQuotedRecogniserNameKey]];
+ }
+
+ return self;
+}
+
+- (void)encodeWithCoder:(NSCoder *)aCoder
+{
+ if (nil != [self escapeReplacer])
+ {
+ NSLog(@"Warning: encoding CPQuoteRecogniser with an escapeReplacer set. This will not be recreated when decoded.");
+ }
+ [aCoder encodeObject:[self startQuote] forKey:CPQuotedRecogniserStartQuoteKey];
+ [aCoder encodeObject:[self endQuote] forKey:CPQuotedRecogniserEndQuoteKey];
+ [aCoder encodeObject:[self escapeSequence] forKey:CPQuotedRecogniserEscapeSequenceKey];
+ [aCoder encodeInteger:[self maximumLength] forKey:CPQuotedRecogniserMaximumLengthKey];
+ [aCoder encodeObject:[self name] forKey:CPQuotedRecogniserNameKey];
+}
+
+- (NSRange)recogniseRangeWithScanner:(NSScanner *)scanner currentTokenPosition:(NSUInteger *)tokenPosition
+{
+ NSString *(^er)(NSString *tokenStream, NSUInteger *quotePosition) = [self escapeReplacer];
+ NSUInteger startQuoteLength = [self.startQuote length];
+ NSUInteger endQuoteLength = [self.endQuote length];
+ NSString *tokenString = [scanner string];
+
+ long inputLength = [tokenString length];
+ NSUInteger rangeLength = [FMStatementQuotedRecogniser minWithLeftParam:inputLength - *tokenPosition
+ rightParam:startQuoteLength + endQuoteLength + self.maximumLength];
+ CFRange searchRange = CFRangeMake(*tokenPosition, rangeLength);
+ CFRange range;
+ BOOL matched = CFStringFindWithOptions((CFStringRef)tokenString, (CFStringRef)self.startQuote, searchRange, kCFCompareAnchored, &range);
+
+ CFMutableStringRef outputString = CFStringCreateMutable(kCFAllocatorDefault, 0);
+
+ if (matched)
+ {
+ searchRange.location = searchRange.location + range.length;
+ searchRange.length = searchRange.length - range.length;
+
+ CFRange endRange;
+ CFRange escapeRange;
+ BOOL matchedEndSequence = CFStringFindWithOptions((CFStringRef)tokenString, (CFStringRef)self.endQuote, searchRange, 0L, &endRange);
+ BOOL matchedEscapeSequence = nil == self.escapeSequence ? NO : CFStringFindWithOptions((CFStringRef)tokenString, (CFStringRef)self.escapeSequence, searchRange, 0L, &escapeRange);
+
+ while (matchedEndSequence && searchRange.location < inputLength)
+ {
+ if (!matchedEscapeSequence || endRange.location < escapeRange.location)//End quote is not escaped by escape sequence.
+ {
+ NSUInteger resultRangeBegin = *tokenPosition;
+ *tokenPosition = endRange.location + endRange.length;
+ NSUInteger resultRangeLength = *tokenPosition - resultRangeBegin;
+ CFRelease(outputString);
+ return NSMakeRange(resultRangeBegin, resultRangeLength);
+ }
+ else//End quote is escaped by escape sequence
+ {
+ NSUInteger quotedPosition = escapeRange.location + escapeRange.length;
+ CFRange subStrRange = CFRangeMake(searchRange.location,
+ escapeRange.location + (self.shouldQuoteEscapeSequence ? escapeRange.length : 0) - searchRange.location);
+ CFStringRef substr = CFStringCreateWithSubstring(kCFAllocatorDefault, (CFStringRef)tokenString, subStrRange);
+ CFStringAppend(outputString, substr);
+ CFRelease(substr);
+ BOOL appended = NO;
+ if (nil != er)
+ {
+ NSString *s = er(tokenString, &quotedPosition);
+ if (nil != s)
+ {
+ appended = YES;
+ CFStringAppend(outputString, (CFStringRef)s);
+ }
+ }
+ if (!appended)
+ {
+ substr = CFStringCreateWithSubstring(kCFAllocatorDefault, (CFStringRef)tokenString, CFRangeMake(escapeRange.location + escapeRange.length, 1));
+ CFStringAppend(outputString, substr);
+ CFRelease(substr);
+ quotedPosition += 1;
+ }
+ searchRange.length = searchRange.location + searchRange.length - quotedPosition;
+ searchRange.location = quotedPosition;
+
+ if (endRange.location < searchRange.location)
+ {
+ matchedEndSequence = CFStringFindWithOptions((CFStringRef)tokenString, (CFStringRef)self.endQuote, searchRange, 0L, &endRange);
+ }
+ if (escapeRange.location < searchRange.location)
+ {
+ matchedEscapeSequence = CFStringFindWithOptions((CFStringRef)tokenString, (CFStringRef)self.escapeSequence, searchRange, 0L, &escapeRange);
+ }
+ }
+ }
+ }
+
+ CFRelease(outputString);
+ return NSMakeRange(NSNotFound, 0);
+}
+
+@end
diff --git a/src/fmdb/FMStatementSplitter/FMStatementTokenRecogniser.h b/src/fmdb/FMStatementSplitter/FMStatementTokenRecogniser.h
new file mode 100644
index 0000000..be86796
--- /dev/null
+++ b/src/fmdb/FMStatementSplitter/FMStatementTokenRecogniser.h
@@ -0,0 +1,16 @@
+//
+// FMStatementTokenRecogniser.h
+// FMDB
+//
+// Created by openthread on 3/5/14.
+// Copyright (c) 2014 openthread. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@protocol FMStatementTokenRecogniser <NSObject, NSCoding>
+
+@required
+- (NSRange)recogniseRangeWithScanner:(NSScanner *)scanner currentTokenPosition:(NSUInteger *)tokenPosition;
+
+@end