From 11ec389473e72e0a5f3b90c4a14c770e78f3254a Mon Sep 17 00:00:00 2001 From: Bogdan Iusco Date: Wed, 22 Apr 2015 11:25:34 +0300 Subject: [PATCH 1/4] Add code blocks to the CSV parser --- CHCSVParser.xcodeproj/project.pbxproj | 12 ++ CHCSVParser/CHCSVParser/CHCSVParserBlocks.h | 84 +++++++++++ CHCSVParser/CHCSVParser/CHCSVParserBlocks.m | 84 +++++++++++ Unit Tests/CHCSVParserBlocksTestCase.m | 153 ++++++++++++++++++++ 4 files changed, 333 insertions(+) create mode 100644 CHCSVParser/CHCSVParser/CHCSVParserBlocks.h create mode 100644 CHCSVParser/CHCSVParser/CHCSVParserBlocks.m create mode 100644 Unit Tests/CHCSVParserBlocksTestCase.m diff --git a/CHCSVParser.xcodeproj/project.pbxproj b/CHCSVParser.xcodeproj/project.pbxproj index 8a7cab7..1a5504d 100644 --- a/CHCSVParser.xcodeproj/project.pbxproj +++ b/CHCSVParser.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 432CA0F61AE6998500EF86FC /* CHCSVParserBlocks.m in Sources */ = {isa = PBXBuildFile; fileRef = 432CA0F51AE6998500EF86FC /* CHCSVParserBlocks.m */; }; + 432CA0F81AE69E6100EF86FC /* CHCSVParserBlocksTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 432CA0F71AE69E6100EF86FC /* CHCSVParserBlocksTestCase.m */; }; + 432CA0F91AE6B2C300EF86FC /* CHCSVParserBlocks.m in Sources */ = {isa = PBXBuildFile; fileRef = 432CA0F51AE6998500EF86FC /* CHCSVParserBlocks.m */; }; 550ED4281973491500E7D173 /* Issue64.csv in Resources */ = {isa = PBXBuildFile; fileRef = 550ED4271973491500E7D173 /* Issue64.csv */; }; 5515D4461AAB72FF00DC1D5B /* Issue79.csv in Resources */ = {isa = PBXBuildFile; fileRef = 5515D4451AAB72FF00DC1D5B /* Issue79.csv */; }; 557FCEB61203F938009FCDBA /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 557FCEB51203F938009FCDBA /* CoreServices.framework */; }; @@ -22,6 +25,9 @@ /* Begin PBXFileReference section */ 08FB7796FE84155DC02AAC07 /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 08FB779EFE84155DC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; + 432CA0F41AE6998500EF86FC /* CHCSVParserBlocks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CHCSVParserBlocks.h; sourceTree = ""; }; + 432CA0F51AE6998500EF86FC /* CHCSVParserBlocks.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CHCSVParserBlocks.m; sourceTree = ""; }; + 432CA0F71AE69E6100EF86FC /* CHCSVParserBlocksTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CHCSVParserBlocksTestCase.m; sourceTree = ""; }; 550ED4271973491500E7D173 /* Issue64.csv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Issue64.csv; sourceTree = ""; }; 5515D4451AAB72FF00DC1D5B /* Issue79.csv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Issue79.csv; sourceTree = ""; }; 5560A19E173743A300DDD1D3 /* UnitTestContent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UnitTestContent.h; sourceTree = ""; }; @@ -103,6 +109,8 @@ children = ( 55B9931F160E376100D6548A /* CHCSVParser.h */, 55B99320160E376100D6548A /* CHCSVParser.m */, + 432CA0F41AE6998500EF86FC /* CHCSVParserBlocks.h */, + 432CA0F51AE6998500EF86FC /* CHCSVParserBlocks.m */, ); path = CHCSVParser; sourceTree = ""; @@ -114,6 +122,7 @@ 5560A19E173743A300DDD1D3 /* UnitTestContent.h */, 557FD05A1204A72B009FCDBA /* UnitTests.h */, 557FD05B1204A72B009FCDBA /* UnitTests.m */, + 432CA0F71AE69E6100EF86FC /* CHCSVParserBlocksTestCase.m */, 550ED4271973491500E7D173 /* Issue64.csv */, 55C61C60199C0B4600770AA2 /* Issue65.csv */, 5515D4451AAB72FF00DC1D5B /* Issue79.csv */, @@ -221,7 +230,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 432CA0F91AE6B2C300EF86FC /* CHCSVParserBlocks.m in Sources */, 557FD05D1204A731009FCDBA /* UnitTests.m in Sources */, + 432CA0F81AE69E6100EF86FC /* CHCSVParserBlocksTestCase.m in Sources */, 55F79EE01682D6B60095E9D5 /* CHCSVParser.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -230,6 +241,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 432CA0F61AE6998500EF86FC /* CHCSVParserBlocks.m in Sources */, 8DD76F9A0486AA7600D96B5E /* main.m in Sources */, 55B99321160E376100D6548A /* CHCSVParser.m in Sources */, ); diff --git a/CHCSVParser/CHCSVParser/CHCSVParserBlocks.h b/CHCSVParser/CHCSVParser/CHCSVParserBlocks.h new file mode 100644 index 0000000..787fd24 --- /dev/null +++ b/CHCSVParser/CHCSVParser/CHCSVParserBlocks.h @@ -0,0 +1,84 @@ +// +// CHCSVParserBlocks.h +// CHCSVParser +/** + Copyright (c) 2014 Dave DeLong + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + **/ + +#import "CHCSVParser.h" + +/** + * CSV parser with code blocks. + * Do not set the delegate for this class. Instead use the provied callbacks. + */ +@interface CHCSVParserBlocks : CHCSVParser + +/** + * Indicates that the parser has started parsing the stream + */ +@property (nonatomic, copy) void(^didBeginDocumentCallback)(); + +/** + * Indicates that the parser has successfully finished parsing the stream + * + * This callback is not invoked if any error is encountered + */ +@property (nonatomic, copy) void(^didEndDocumentCallback)(); + +/** + * Indicates the parser has started parsing a line + * + * @param recordNumber The 1-based number of the record + */ +@property (nonatomic, copy) void(^didBeginLineCallback)(NSUInteger recordNumber); + +/** + * Indicates the parser has finished parsing a line + * + * @param recordNumber The 1-based number of the record + */ +@property (nonatomic, copy) void(^didEndLineCallback)(NSUInteger recordNumber); + +/** + * Indicates the parser has parsed a field on the current line + * + * @param field The parsed string. If configured to do so, this string may be sanitized and trimmed + * @param fieldIndex The 0-based index of the field within the current record + */ +@property (nonatomic, copy) void(^didReadFieldCallback)(NSString *field, NSInteger fieldIndex); + +/** + * Indicates the parser has encountered a comment + * + * This method is only invoked if @c CHCSVParser.recognizesComments is @c YES + * + * @param comment The parsed comment + */ +@property (nonatomic, copy) void(^didReadCommentCallback)(NSString *comment); + +/** + * Indicates the parser encounter an error while parsing + * + * @param error The @c NSError instance + */ +@property (nonatomic, copy) void(^didFailWithErrorCallback)(NSError *error); + +@end diff --git a/CHCSVParser/CHCSVParser/CHCSVParserBlocks.m b/CHCSVParser/CHCSVParser/CHCSVParserBlocks.m new file mode 100644 index 0000000..881891d --- /dev/null +++ b/CHCSVParser/CHCSVParser/CHCSVParserBlocks.m @@ -0,0 +1,84 @@ +// +// CHCSVParserBlocks.m +// CHCSVParser +/** + Copyright (c) 2014 Dave DeLong + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + **/ + +#import "CHCSVParserBlocks.h" + +@implementation CHCSVParserBlocks + +#pragma mark - CHCSVParser methods + +- (instancetype)initWithInputStream:(NSInputStream *)stream usedEncoding:(NSStringEncoding *)encoding delimiter:(unichar)delimiter { + self = [super initWithInputStream:stream usedEncoding:encoding delimiter:delimiter]; + if (self) { + self.delegate = self; + } + return self; +} + +#pragma mark - CHCSVParserDelegate methods + +- (void)parserDidBeginDocument:(CHCSVParser *)parser { + if (self.didBeginDocumentCallback) { + self.didBeginDocumentCallback(); + } +} + +- (void)parserDidEndDocument:(CHCSVParser *)parser { + if (self.didEndDocumentCallback) { + self.didEndDocumentCallback(); + } +} + +- (void)parser:(CHCSVParser *)parser didBeginLine:(NSUInteger)recordNumber { + if (self.didBeginLineCallback) { + self.didBeginLineCallback(recordNumber); + } +} + +- (void)parser:(CHCSVParser *)parser didEndLine:(NSUInteger)recordNumber { + if (self.didEndLineCallback) { + self.didEndLineCallback(recordNumber); + } +} + +- (void)parser:(CHCSVParser *)parser didReadField:(NSString *)field atIndex:(NSInteger)fieldIndex { + if (self.didReadFieldCallback) { + self.didReadFieldCallback(field, fieldIndex); + } +} + +- (void)parser:(CHCSVParser *)parser didReadComment:(NSString *)comment { + if (self.didReadCommentCallback) { + self.didReadCommentCallback(comment); + } +} + +- (void)parser:(CHCSVParser *)parser didFailWithError:(NSError *)error { + if (self.didFailWithErrorCallback) { + self.didFailWithErrorCallback(error); + } +} + +@end diff --git a/Unit Tests/CHCSVParserBlocksTestCase.m b/Unit Tests/CHCSVParserBlocksTestCase.m new file mode 100644 index 0000000..7df4ad0 --- /dev/null +++ b/Unit Tests/CHCSVParserBlocksTestCase.m @@ -0,0 +1,153 @@ +// +// CHCSVParserBlocksTestCase.m +// CHCSVParser +/** + Copyright (c) 2014 Dave DeLong + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + **/ + + +#import + +#import "CHCSVParserBlocks.h" + +@interface CHCSVParserBlocksTestCase : XCTestCase + +@property (nonatomic, strong) CHCSVParserBlocks *parser; + +@end + +@implementation CHCSVParserBlocksTestCase + +#pragma mark - Setup methods + +- (void)setUp { + [super setUp]; + NSString *csv = @"1,2,3,4,5"; + self.parser = [[CHCSVParserBlocks alloc] initWithCSVString:csv]; +} + +- (void)tearDown { + self.parser = nil; + [super tearDown]; +} + +#pragma mark - Test callbacks + +- (void)testCallbacksNotSet { + XCTAssertNoThrow([self.parser parse]); +} + +- (void)testValidateDidBeginCallback { + __block BOOL wasCalled = NO; + self.parser.didBeginDocumentCallback = ^{ + wasCalled = YES; + }; + [self.parser.delegate parserDidBeginDocument:self.parser]; + XCTAssertTrue(wasCalled, @"didBeginLineCallback not called!"); +} + +- (void)testValidateDidEndDocumentCallback { + __block BOOL wasCalled = NO; + self.parser.didEndDocumentCallback = ^ { + wasCalled = YES; + }; + [self.parser.delegate parserDidEndDocument:self.parser]; + XCTAssertTrue(wasCalled, @"parserDidEndDocument not called!"); +} + +- (void)testValidateDidBeginLineCallback { + __block BOOL wasCalled = NO; + __block NSUInteger receivedRecordNumber; + NSUInteger recordNumber = arc4random(); + self.parser.didBeginLineCallback = ^(NSUInteger aRecordNumber) { + wasCalled = YES; + receivedRecordNumber = aRecordNumber; + }; + [self.parser.delegate parser:self.parser didBeginLine:recordNumber]; + XCTAssertTrue(wasCalled, @"didBeginLineCallback not called!"); + XCTAssertEqual(recordNumber, receivedRecordNumber); +} + +- (void)testValidateDidEndLineCallback { + __block BOOL wasCalled = NO; + __block NSUInteger receivedRecordNumber; + NSUInteger recordNumber = arc4random(); + self.parser.didEndLineCallback = ^(NSUInteger aRecordNumber) { + wasCalled = YES; + receivedRecordNumber = aRecordNumber; + }; + [self.parser.delegate parser:self.parser didEndLine:recordNumber]; + XCTAssertTrue(wasCalled, @"didEndLineCallback not called!"); + XCTAssertEqual(recordNumber, receivedRecordNumber); +} + +- (void)testValidateDidReadFieldCallback { + __block BOOL wasCalled = NO; + __block NSString *receivedField; + __block NSInteger receivedLine; + NSString *field = [[self class] randomString]; + NSInteger line = arc4random(); + self.parser.didReadFieldCallback = ^(NSString *aField, NSInteger aLine) { + wasCalled = YES; + receivedField = aField; + receivedLine = aLine; + }; + [self.parser.delegate parser:self.parser didReadField:field atIndex:line]; + XCTAssertTrue(wasCalled, @"didReadFieldCallback not called!"); + XCTAssertEqualObjects(field, receivedField); + XCTAssertEqual(line, receivedLine); +} + +- (void)testValidateDidReadCommentCallback { + __block BOOL wasCalled = NO; + __block NSString *receivedComment; + NSString *comment = [[self class] randomString]; + self.parser.didReadCommentCallback = ^(NSString *aComment) { + wasCalled = YES; + receivedComment = aComment; + }; + [self.parser.delegate parser:self.parser didReadComment:comment]; + XCTAssertTrue(wasCalled, @"didReadCommentCallback not called!"); + XCTAssertEqualObjects(comment, receivedComment); +} + +- (void)testValidateDidFailedWithErrorCallback { + __block BOOL wasCalled = NO; + __block NSError *receivedError; + NSString *randomDomain = [[self class] randomString]; + NSUInteger randomCode = arc4random(); + NSError *error = [NSError errorWithDomain:randomDomain code:randomCode userInfo:nil]; + self.parser.didFailWithErrorCallback = ^(NSError *aError) { + wasCalled = YES; + receivedError = aError; + }; + [self.parser.delegate parser:self.parser didFailWithError:error]; + XCTAssertTrue(wasCalled, @"didFailWithErrorCallback not called!"); + XCTAssertEqualObjects(error, receivedError); +} + +#pragma mark - Factory methods + ++ (NSString *)randomString { + return [NSString stringWithFormat:@"%c", arc4random_uniform(26) + 'a']; +} + +@end From 0647183151c37556609871733fa55758349d4da8 Mon Sep 17 00:00:00 2001 From: Bogdan Iusco Date: Wed, 22 Apr 2015 11:30:38 +0300 Subject: [PATCH 2/4] Update podspec to contain the new files and bump up version --- CHCSVParser.podspec | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHCSVParser.podspec b/CHCSVParser.podspec index 93690c3..19fca47 100644 --- a/CHCSVParser.podspec +++ b/CHCSVParser.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = "CHCSVParser" - spec.version = "2.1.0" + spec.version = "2.1.1" spec.summary = "A proper CSV parser for Objective-C" spec.description = <<-DESC A robust class for reading and writing delimited files in Cocoa. @@ -11,7 +11,7 @@ Pod::Spec.new do |spec| spec.social_media_url = "http://twitter.com/davedelong" spec.ios.deployment_target = "6.0" spec.osx.deployment_target = "10.7" - spec.source = { :git => "https://github.com/davedelong/CHCSVParser.git", :tag => "2.1.0" } - spec.source_files = "CHCSVParser/CHCSVParser/CHCSVParser.{h,m}" + spec.source = { :git => "https://github.com/davedelong/CHCSVParser.git", :tag => "2.1.1" } + spec.source_files = "CHCSVParser/CHCSVParser/CHCSVParser.{h,m}","CHCSVParser/CHCSVParser/CHCSVParserBlocks.{h,m}" spec.requires_arc = true end From d29b937dbf7c317e7c3b80c809bfca0452f9c12c Mon Sep 17 00:00:00 2001 From: Bogdan Iusco Date: Wed, 22 Apr 2015 11:35:31 +0300 Subject: [PATCH 3/4] Update podspec so cocoapods users can clone from this repo. --- CHCSVParser.podspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHCSVParser.podspec b/CHCSVParser.podspec index 19fca47..ec8f5af 100644 --- a/CHCSVParser.podspec +++ b/CHCSVParser.podspec @@ -5,13 +5,13 @@ Pod::Spec.new do |spec| spec.description = <<-DESC A robust class for reading and writing delimited files in Cocoa. DESC - spec.homepage = "https://github.com/davedelong/CHCSVParser" + spec.homepage = "https://github.com/grigaci/CHCSVParser" spec.license = { :type => 'MIT', :file => 'LICENSE.txt' } spec.author = "Dave DeLong" spec.social_media_url = "http://twitter.com/davedelong" spec.ios.deployment_target = "6.0" spec.osx.deployment_target = "10.7" - spec.source = { :git => "https://github.com/davedelong/CHCSVParser.git", :tag => "2.1.1" } + spec.source = { :git => "https://github.com/grigaci/CHCSVParser.git", :tag => "2.1.1" } spec.source_files = "CHCSVParser/CHCSVParser/CHCSVParser.{h,m}","CHCSVParser/CHCSVParser/CHCSVParserBlocks.{h,m}" spec.requires_arc = true end From 6ad5a44e5360bf7dd59f13433784452b6677c8e2 Mon Sep 17 00:00:00 2001 From: Bogdan Iusco Date: Wed, 22 Apr 2015 11:36:01 +0300 Subject: [PATCH 4/4] Update readme file. --- README.markdown | 1 + 1 file changed, 1 insertion(+) diff --git a/README.markdown b/README.markdown index 17f4e97..160661e 100644 --- a/README.markdown +++ b/README.markdown @@ -1,6 +1,7 @@ #CHCSVParser `CHCSVParser` is an Objective-C parser for CSV files. +Instead of using the delegate methods, now you can use code blocks. ##Supported Platforms