diff options
author | openthread <openfibers@gmail.com> | 2014-04-07 16:33:57 +0400 |
---|---|---|
committer | openthread <openfibers@gmail.com> | 2014-04-07 16:33:57 +0400 |
commit | 2b7f7adff4adff672fbb079aafdbe36bb093545c (patch) | |
tree | 96387588fe45584c8be2dece0707bc77674cb411 | |
parent | c41fe67736a6b826c727e85ee86b207f8ac0fff5 (diff) |
added batch sql splitter
-rw-r--r-- | fmdb.xcodeproj/project.pbxproj | 55 | ||||
-rw-r--r-- | src/fmdb/FMStatementSplitter/FMSQLStatementSplitter.h | 21 | ||||
-rw-r--r-- | src/fmdb/FMStatementSplitter/FMSQLStatementSplitter.m | 186 | ||||
-rw-r--r-- | src/fmdb/FMStatementSplitter/FMStatementKeywordRecogniser.h | 97 | ||||
-rw-r--r-- | src/fmdb/FMStatementSplitter/FMStatementKeywordRecogniser.m | 118 | ||||
-rw-r--r-- | src/fmdb/FMStatementSplitter/FMStatementQuotedRecogniser.h | 130 | ||||
-rw-r--r-- | src/fmdb/FMStatementSplitter/FMStatementQuotedRecogniser.m | 180 | ||||
-rw-r--r-- | src/fmdb/FMStatementSplitter/FMStatementTokenRecogniser.h | 16 |
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:¤tTokenOffset]; + 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, "edPosition); + 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 |