diff --git a/CHCSV.h b/CHCSV.h new file mode 100644 index 0000000..c3e9471 --- /dev/null +++ b/CHCSV.h @@ -0,0 +1,12 @@ +// +// CHCSV.h +// CHCSVParser +// +// Created by Dave DeLong on 10/2/10. +// Copyright 2010 Home. All rights reserved. +// + +#import "CHCSVParser.h" +#import "CHCSVWriter.h" +#import "NSArray+CHCSVAdditions.h" +#import "NSString+CHCSVAdditions.h" \ No newline at end of file diff --git a/CHCSVParser.h b/CHCSVParser.h index 4205282..cffdac6 100644 --- a/CHCSVParser.h +++ b/CHCSVParser.h @@ -53,6 +53,9 @@ - (id) initWithContentsOfCSVFile:(NSString *)aCSVFile encoding:(NSStringEncoding)encoding error:(NSError **)anError; - (id) initWithContentsOfCSVFile:(NSString *)aCSVFile usedEncoding:(NSStringEncoding *)usedEncoding error:(NSError **)anError; + +- (id) initWithCSVString:(NSString *)csvString encoding:(NSStringEncoding)encoding error:(NSError **)anError; + - (void) parse; @end diff --git a/CHCSVParser.m b/CHCSVParser.m index 45732a3..7cb14bd 100644 --- a/CHCSVParser.m +++ b/CHCSVParser.m @@ -66,7 +66,7 @@ @interface CHCSVParser () @property (retain) NSString * currentChunk; -- (void) discoverTextEncoding; +- (NSStringEncoding) textEncodingForData:(NSData *)chunkToSniff offset:(NSUInteger *)offset; - (NSString *) nextCharacter; - (void) runParseLoop; @@ -90,6 +90,7 @@ - (id) initWithContentsOfCSVFile:(NSString *)aCSVFile encoding:(NSStringEncoding csvFileHandle = [[NSFileHandle fileHandleForReadingAtPath:csvFile] retain]; if (csvFileHandle == nil) { if (anError) { + NSLog(@"error for file: %@", csvFile); *anError = [NSError errorWithDomain:@"com.davedelong.csv" code:0 userInfo:[NSDictionary dictionaryWithObject:@"Unable to open file for reading" forKey:NSLocalizedDescriptionKey]]; } [self release]; @@ -113,7 +114,11 @@ - (id) initWithContentsOfCSVFile:(NSString *)aCSVFile encoding:(NSStringEncoding - (id) initWithContentsOfCSVFile:(NSString *)aCSVFile usedEncoding:(NSStringEncoding *)usedEncoding error:(NSError **)anError { if (self = [self initWithContentsOfCSVFile:aCSVFile encoding:NSUTF8StringEncoding error:anError]) { - [self discoverTextEncoding]; + + NSData * chunk = [csvFileHandle readDataOfLength:CHUNK_SIZE]; + NSUInteger seekOffset = 0; + fileEncoding = [self textEncodingForData:chunk offset:&seekOffset]; + [csvFileHandle seekToFileOffset:seekOffset]; if (usedEncoding) { *usedEncoding = fileEncoding; @@ -122,6 +127,26 @@ - (id) initWithContentsOfCSVFile:(NSString *)aCSVFile usedEncoding:(NSStringEnco return self; } +- (id) initWithCSVString:(NSString *)csvString encoding:(NSStringEncoding)encoding error:(NSError **)anError { + if (self = [super init]) { + csvFile = nil; + csvFileHandle = nil; + fileEncoding = encoding; + + balancedQuotes = YES; + balancedEscapes = YES; + + currentLine = 0; + currentField = [[NSMutableString alloc] init]; + + currentChunk = [csvString copy]; + chunkIndex = 0; + + state = CHCSVParserStateInsideFile; + } + return self; +} + - (void) dealloc { [csvFileHandle release]; [csvFile release]; @@ -132,51 +157,51 @@ - (void) dealloc { [super dealloc]; } -- (void) discoverTextEncoding { - NSData * chunkToSniff = [csvFileHandle readDataOfLength:CHUNK_SIZE]; +- (NSStringEncoding) textEncodingForData:(NSData *)chunkToSniff offset:(NSUInteger *)offset { NSUInteger length = [chunkToSniff length]; - NSUInteger offset = 0; + *offset = 0; + NSStringEncoding encoding = NSUTF8StringEncoding; if (length > 0) { UInt8* bytes = (UInt8*)[chunkToSniff bytes]; - fileEncoding = CFStringConvertEncodingToNSStringEncoding(CFStringGetSystemEncoding()); + encoding = CFStringConvertEncodingToNSStringEncoding(CFStringGetSystemEncoding()); switch (bytes[0]) { case 0x00: if (length>3 && bytes[1]==0x00 && bytes[2]==0xFE && bytes[3]==0xFF) { - fileEncoding = NSUTF32BigEndianStringEncoding; - offset = 4; + encoding = NSUTF32BigEndianStringEncoding; + *offset = 4; } break; case 0xEF: if (length>2 && bytes[1]==0xBB && bytes[2]==0xBF) { - fileEncoding = NSUTF8StringEncoding; - offset = 3; + encoding = NSUTF8StringEncoding; + *offset = 3; } break; case 0xFE: if (length>1 && bytes[1]==0xFF) { - fileEncoding = NSUTF16BigEndianStringEncoding; - offset = 2; + encoding = NSUTF16BigEndianStringEncoding; + *offset = 2; } break; case 0xFF: if (length>1 && bytes[1]==0xFE) { if (length>3 && bytes[2]==0x00 && bytes[3]==0x00) { - fileEncoding = NSUTF32LittleEndianStringEncoding; - offset = 4; + encoding = NSUTF32LittleEndianStringEncoding; + *offset = 4; } else { - fileEncoding = NSUTF16LittleEndianStringEncoding; - offset = 2; + encoding = NSUTF16LittleEndianStringEncoding; + *offset = 2; } } break; default: - fileEncoding = NSUTF8StringEncoding; // fall back on UTF8 + encoding = NSUTF8StringEncoding; // fall back on UTF8 break; } } - [csvFileHandle seekToFileOffset:offset]; - return; + + return encoding; } #pragma mark Parsing methods diff --git a/CHCSVParser.xcodeproj/project.pbxproj b/CHCSVParser.xcodeproj/project.pbxproj index aa81d10..3dacf01 100644 --- a/CHCSVParser.xcodeproj/project.pbxproj +++ b/CHCSVParser.xcodeproj/project.pbxproj @@ -7,6 +7,10 @@ objects = { /* Begin PBXBuildFile section */ + 5516BCB512578CFC0025F235 /* NSString+CHCSVAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 5516BCB412578CFC0025F235 /* NSString+CHCSVAdditions.m */; }; + 5516BCB912578D750025F235 /* CHCSVSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 5516BCB812578D750025F235 /* CHCSVSupport.m */; }; + 5516BCBB12578EA90025F235 /* NSString+CHCSVAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 5516BCB412578CFC0025F235 /* NSString+CHCSVAdditions.m */; }; + 5516BCBC12578EAD0025F235 /* CHCSVSupport.m in Sources */ = {isa = PBXBuildFile; fileRef = 5516BCB812578D750025F235 /* CHCSVSupport.m */; }; 551981D61203715400FBE033 /* CHCSVParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 551981D51203715400FBE033 /* CHCSVParser.m */; }; 557FCEB61203F938009FCDBA /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 557FCEB51203F938009FCDBA /* CoreServices.framework */; }; 557FD0431204A45D009FCDBA /* NSArray+CHCSVAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 557FD0421204A45D009FCDBA /* NSArray+CHCSVAdditions.m */; }; @@ -37,6 +41,11 @@ 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 = ""; }; 32A70AAB03705E1F00C91783 /* CHCSVParser_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CHCSVParser_Prefix.pch; sourceTree = ""; }; + 5516BCB312578CFC0025F235 /* NSString+CHCSVAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+CHCSVAdditions.h"; sourceTree = ""; }; + 5516BCB412578CFC0025F235 /* NSString+CHCSVAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+CHCSVAdditions.m"; sourceTree = ""; }; + 5516BCB612578D480025F235 /* CHCSV.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CHCSV.h; sourceTree = ""; }; + 5516BCB712578D750025F235 /* CHCSVSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CHCSVSupport.h; sourceTree = ""; }; + 5516BCB812578D750025F235 /* CHCSVSupport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CHCSVSupport.m; sourceTree = ""; }; 551981D41203715400FBE033 /* CHCSVParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CHCSVParser.h; sourceTree = ""; }; 551981D51203715400FBE033 /* CHCSVParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CHCSVParser.m; sourceTree = ""; }; 551981EE1203800300FBE033 /* Test.csv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Test.csv; sourceTree = ""; }; @@ -91,12 +100,7 @@ children = ( 32A70AAB03705E1F00C91783 /* CHCSVParser_Prefix.pch */, 08FB7796FE84155DC02AAC07 /* main.m */, - 551981D41203715400FBE033 /* CHCSVParser.h */, - 551981D51203715400FBE033 /* CHCSVParser.m */, - 55EFC7B71210608C0070B303 /* CHCSVWriter.h */, - 55EFC7B81210608C0070B303 /* CHCSVWriter.m */, - 557FD0411204A45D009FCDBA /* NSArray+CHCSVAdditions.h */, - 557FD0421204A45D009FCDBA /* NSArray+CHCSVAdditions.m */, + 5516BCBA12578DA90025F235 /* CHCSVParser */, 551981EE1203800300FBE033 /* Test.csv */, 557FD05A1204A72B009FCDBA /* UnitTests.h */, 557FD05B1204A72B009FCDBA /* UnitTests.m */, @@ -121,6 +125,24 @@ name = Products; sourceTree = ""; }; + 5516BCBA12578DA90025F235 /* CHCSVParser */ = { + isa = PBXGroup; + children = ( + 5516BCB612578D480025F235 /* CHCSV.h */, + 551981D41203715400FBE033 /* CHCSVParser.h */, + 551981D51203715400FBE033 /* CHCSVParser.m */, + 55EFC7B71210608C0070B303 /* CHCSVWriter.h */, + 55EFC7B81210608C0070B303 /* CHCSVWriter.m */, + 5516BCB712578D750025F235 /* CHCSVSupport.h */, + 5516BCB812578D750025F235 /* CHCSVSupport.m */, + 557FD0411204A45D009FCDBA /* NSArray+CHCSVAdditions.h */, + 557FD0421204A45D009FCDBA /* NSArray+CHCSVAdditions.m */, + 5516BCB312578CFC0025F235 /* NSString+CHCSVAdditions.h */, + 5516BCB412578CFC0025F235 /* NSString+CHCSVAdditions.m */, + ); + name = CHCSVParser; + sourceTree = ""; + }; C6859EA2029092E104C91782 /* Documentation */ = { isa = PBXGroup; children = ( @@ -175,7 +197,14 @@ isa = PBXProject; buildConfigurationList = 1DEB927808733DD40010E9CD /* Build configuration list for PBXProject "CHCSVParser" */; compatibilityVersion = "Xcode 3.1"; + developmentRegion = English; hasScannedForEncodings = 1; + knownRegions = ( + English, + Japanese, + French, + German, + ); mainGroup = 08FB7794FE84155DC02AAC07 /* CHCSVParser */; projectDirPath = ""; projectRoot = ""; @@ -222,6 +251,8 @@ 557FD0591204A71C009FCDBA /* NSArray+CHCSVAdditions.m in Sources */, 557FD05D1204A731009FCDBA /* UnitTests.m in Sources */, 55EFC7B91210608C0070B303 /* CHCSVWriter.m in Sources */, + 5516BCB512578CFC0025F235 /* NSString+CHCSVAdditions.m in Sources */, + 5516BCB912578D750025F235 /* CHCSVSupport.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -232,6 +263,8 @@ 8DD76F9A0486AA7600D96B5E /* main.m in Sources */, 551981D61203715400FBE033 /* CHCSVParser.m in Sources */, 557FD0431204A45D009FCDBA /* NSArray+CHCSVAdditions.m in Sources */, + 5516BCBB12578EA90025F235 /* NSString+CHCSVAdditions.m in Sources */, + 5516BCBC12578EAD0025F235 /* CHCSVSupport.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/CHCSVSupport.h b/CHCSVSupport.h new file mode 100644 index 0000000..79cae02 --- /dev/null +++ b/CHCSVSupport.h @@ -0,0 +1,21 @@ +// +// CHCSVSupport.h +// CHCSVParser +// +// Created by Dave DeLong on 10/2/10. +// Copyright 2010 Home. All rights reserved. +// + +#import +#import "CHCSVParser.h" + +@interface NSArrayCHCSVAggregator : NSObject { + NSMutableArray * lines; + NSMutableArray * currentLine; + NSError * error; +} + +@property (readonly) NSArray * lines; +@property (readonly) NSError * error; + +@end \ No newline at end of file diff --git a/CHCSVSupport.m b/CHCSVSupport.m new file mode 100644 index 0000000..e1a9be6 --- /dev/null +++ b/CHCSVSupport.m @@ -0,0 +1,47 @@ +// +// CHCSVSupport.m +// CHCSVParser +// +// Created by Dave DeLong on 10/2/10. +// Copyright 2010 Home. All rights reserved. +// + +#import "CHCSVSupport.h" + + +@implementation NSArrayCHCSVAggregator +@synthesize lines, error; + +- (void) dealloc { + [lines release]; + [currentLine release]; + [error release]; + [super dealloc]; +} + +- (void) parser:(CHCSVParser *)parser didStartDocument:(NSString *)csvFile { + lines = [[NSMutableArray alloc] init]; +} + +- (void) parser:(CHCSVParser *)parser didStartLine:(NSUInteger)lineNumber { + currentLine = [[NSMutableArray alloc] init]; +} + +- (void) parser:(CHCSVParser *)parser didEndLine:(NSUInteger)lineNumber { + [lines addObject:currentLine]; + [currentLine release], currentLine = nil; +} + +- (void) parser:(CHCSVParser *)parser didReadField:(NSString *)field { + [currentLine addObject:field]; +} + +- (void) parser:(CHCSVParser *)parser didEndDocument:(NSString *)csvFile { + +} + +- (void) parser:(CHCSVParser *)parser didFailWithError:(NSError *)anError { + error = [anError retain]; +} + +@end \ No newline at end of file diff --git a/CHCSVWriter.m b/CHCSVWriter.m index 6381022..ee39a60 100644 --- a/CHCSVWriter.m +++ b/CHCSVWriter.m @@ -105,18 +105,19 @@ - (void) closeFile { [outputHandle release], outputHandle = nil; if (atomically == YES && [handleFile isEqual:destinationFile] == NO) { + NSError *err = nil; if ([[NSFileManager defaultManager] fileExistsAtPath:destinationFile]) { - NSError *err = nil; [[NSFileManager defaultManager] removeItemAtPath:destinationFile error:&err]; if (err != nil) { error = [err retain]; return; } - [[NSFileManager defaultManager] moveItemAtPath:handleFile toPath:destinationFile error:&err]; - if (err != nil) { - error = [err retain]; - } } + [[NSFileManager defaultManager] moveItemAtPath:handleFile toPath:destinationFile error:&err]; + if (err != nil) { + error = [err retain]; + } + [[NSFileManager defaultManager] removeItemAtPath:handleFile error:nil]; } } } diff --git a/NSArray+CHCSVAdditions.m b/NSArray+CHCSVAdditions.m index b753a62..62a9de9 100644 --- a/NSArray+CHCSVAdditions.m +++ b/NSArray+CHCSVAdditions.m @@ -26,56 +26,7 @@ of this software and associated documentation files (the "Software"), to deal #import "NSArray+CHCSVAdditions.h" #import "CHCSVParser.h" #import "CHCSVWriter.h" - -@interface NSArrayCHCSVAggregator : NSObject { - NSMutableArray * lines; - NSMutableArray * currentLine; - NSError * error; -} - -@property (readonly) NSArray * lines; -@property (readonly) NSError * error; - -@end - -@implementation NSArrayCHCSVAggregator -@synthesize lines, error; - -- (void) dealloc { - [lines release]; - [currentLine release]; - [error release]; - [super dealloc]; -} - -- (void) parser:(CHCSVParser *)parser didStartDocument:(NSString *)csvFile { - lines = [[NSMutableArray alloc] init]; -} - -- (void) parser:(CHCSVParser *)parser didStartLine:(NSUInteger)lineNumber { - currentLine = [[NSMutableArray alloc] init]; -} - -- (void) parser:(CHCSVParser *)parser didEndLine:(NSUInteger)lineNumber { - [lines addObject:currentLine]; - [currentLine release], currentLine = nil; -} - -- (void) parser:(CHCSVParser *)parser didReadField:(NSString *)field { - [currentLine addObject:field]; -} - -- (void) parser:(CHCSVParser *)parser didEndDocument:(NSString *)csvFile { - -} - -- (void) parser:(CHCSVParser *)parser didFailWithError:(NSError *)anError { - error = [anError retain]; -} - -@end - - +#import "CHCSVSupport.h" @implementation NSArray (CHCSVAdditions) @@ -149,13 +100,14 @@ - (BOOL) writeToCSVFile:(NSString *)csvFile atomically:(BOOL)atomically { CHCSVWriter * writer = [[CHCSVWriter alloc] initWithCSVFile:csvFile atomic:atomically]; for (NSArray * row in self) { - for (NSArray * field in row) { - [writer writeField:field]; + for (id field in row) { + [writer writeField:[field description]]; } [writer writeLine]; } ok = ([writer error] == nil); + [writer closeFile]; [writer release]; return ok; diff --git a/NSString+CHCSVAdditions.h b/NSString+CHCSVAdditions.h new file mode 100644 index 0000000..222c72d --- /dev/null +++ b/NSString+CHCSVAdditions.h @@ -0,0 +1,16 @@ +// +// NSString+CHCSVAdditions.h +// CHCSVParser +// +// Created by Dave DeLong on 10/2/10. +// Copyright 2010 Home. All rights reserved. +// + +#import + + +@interface NSString (CHCSVAdditions) + +- (NSArray *) CSVComponents; + +@end diff --git a/NSString+CHCSVAdditions.m b/NSString+CHCSVAdditions.m new file mode 100644 index 0000000..36f0cb5 --- /dev/null +++ b/NSString+CHCSVAdditions.m @@ -0,0 +1,32 @@ +// +// NSString+CHCSVAdditions.m +// CHCSVParser +// +// Created by Dave DeLong on 10/2/10. +// Copyright 2010 Home. All rights reserved. +// + +#import "NSString+CHCSVAdditions.h" +#import "CHCSVParser.h" +#import "CHCSVSupport.h" + +@implementation NSString (CHCSVAdditions) + +- (NSArray *) CSVComponents { + + CHCSVParser * parser = [[CHCSVParser alloc] initWithCSVString:self encoding:[self fastestEncoding] error:nil]; + NSArrayCHCSVAggregator * delegate = [[NSArrayCHCSVAggregator alloc] init]; + [parser setParserDelegate:delegate]; + [parser parse]; + [parser release]; + + NSArray * results = nil; + if ([parser error] == nil) { + results = [[[delegate lines] retain] autorelease]; + } + [delegate release]; + + return results; +} + +@end diff --git a/README.markdown b/README.markdown index ed5b91e..7b30fe5 100644 --- a/README.markdown +++ b/README.markdown @@ -13,6 +13,8 @@ ###Parsing In order to parse CSV files, you'll need `CHCSVParser.h` and `CHCSVParser.m`. A `CHCSVParser` works very similarly to an `NSXMLParser`, in that it synchronously parses the data and invokes delegate callback methods to let you know that it has found a field, or has finished reading a line, or has encountered a syntax error. +A `CHCSVParser` can be created either with a path to a CSV file, or with an `NSString` of CSV data. + ###Writing In order to write data to a CSV file, you'll need `CHCSVWriter.h` and `CHCSVWriter.m`. A `CHCSVWriter` has 2 primary methods (beyond the designated initializer): `writeField:` and `writeLine`. @@ -36,6 +38,26 @@ All of the initializers (both class and instance versions) return an `NSArray` o The `writeToCSVFile:` method expects the same structure (an `NSArray` of `NSArray` objects). +There is also an `NSString` category to parse an `NSString` of CSV data into an `NSArray` of `NSArray` objects. This method is: + +- `- (NSArray *) CSVComponents;` + +Both the `NSArray` and `NSString` categories require including the `CHCSVSupport.h` and `CHCSVSupport.m` files in your project. + +###General Use + +The simplest use of `CHCSVParser` is to include all of the files in your project: + +- `CHCSV.h` +- `CHCSVParser.h` and `CHCSVParser.m` +- `CHCSVWriter.h` and `CHCSVWriter.m` +- `NSArray+CHCSVAdditions.h` and `NSArray+CHCSVAdditions.m` +- `NSString+CHCSVAdditions.h` and `NSString+CHCSVAdditions.m` +- `CHCSVSupport.h` and `CHCSVSupport.m` + +Then to use any of the CSV parsing or writing functionality, simply `#import "CHCSV.h"` and use any of the classes and categories as you'd like. + + ##Data Encoding `CHCSVParser` relies on knowing the encoding of the CSV file. It should work with pretty much any kind of file encoding, if you can provide what that encoding is. If you do not know the encoding of the file, then `CHCSVParser` can make a naïve guess. `CHCSVParser` will try to guess the encoding of the file from among these options: diff --git a/UnitTests.h b/UnitTests.h index cc6f422..e965544 100644 --- a/UnitTests.h +++ b/UnitTests.h @@ -27,7 +27,7 @@ @interface UnitTests : SenTestCase { - + NSAutoreleasePool * testPool; } @end diff --git a/UnitTests.m b/UnitTests.m index 846d2a6..3c9bec7 100644 --- a/UnitTests.m +++ b/UnitTests.m @@ -25,9 +25,37 @@ of this software and associated documentation files (the "Software"), to deal #import "UnitTests.h" #import "NSArray+CHCSVAdditions.h" +#import "NSString+CHCSVAdditions.h" @implementation UnitTests +- (void) setUp { + testPool = [[NSAutoreleasePool alloc] init]; +} + +- (void) tearDown { + [testPool drain], testPool = nil; +} + +- (NSArray *) expectedFields { + return [NSArray arrayWithObjects: + [NSArray arrayWithObjects:@"This",@"is",@"a",@"simple",@"line",nil], + [NSArray arrayWithObjects:@"This",@"is",@"a",@"quoted",@"line",nil], + [NSArray arrayWithObjects:@"This",@"is",@"a",@"mixed",@"line",nil], + [NSArray arrayWithObjects:@"This",@"has",@"a\nmultiline\nfield",nil], + [NSArray arrayWithObjects:@"#This",@"line",@"should",@"not",@"be",@"ignored",nil], + [NSArray arrayWithObjects:@"This",@"has",@"\"escaped\"",@"quotes",nil], + [NSArray arrayWithObjects:@"This",@"has",@"\"escaped\"",@"quotes",nil], + [NSArray arrayWithObjects:@"This",@"has",@"empty",@"fields",@"",@"",@"",nil], + [NSArray arrayWithObjects:@"This",@"has",@"escaped",@"escapes\\",nil], + [NSArray arrayWithObjects:@"This",@"has",@"escaped",@"commas,",nil], + [NSArray arrayWithObjects:@"This",@"has",@"quoted",@"commas,",nil], + [NSArray arrayWithObjects:@"This",@"has",@"empty",@"quoted",@"fields",@"",@"",nil], + [NSArray arrayWithObjects:@"This",@"has",@"mixed",@"\"escaped quotes\"", nil], + [NSArray arrayWithObjects:@"This",@"is",@"the",@"last",@"line",nil], + nil]; +} + - (void) testCSV { NSString * file = [[NSBundle bundleForClass:[self class]] pathForResource:@"Test" ofType:@"csv"]; @@ -38,22 +66,7 @@ - (void) testCSV { STAssertTrue(encoding == NSUTF8StringEncoding, @"Wrong encoding; given %@ (%lu)", CFStringGetNameOfEncoding(CFStringConvertNSStringEncodingToEncoding(encoding)), encoding); STAssertNil(error, @"Unexpected error: %@", error); - NSArray * expectedFields = [NSArray arrayWithObjects: - [NSArray arrayWithObjects:@"This",@"is",@"a",@"simple",@"line",nil], - [NSArray arrayWithObjects:@"This",@"is",@"a",@"quoted",@"line",nil], - [NSArray arrayWithObjects:@"This",@"is",@"a",@"mixed",@"line",nil], - [NSArray arrayWithObjects:@"This",@"has",@"a\nmultiline\nfield",nil], - [NSArray arrayWithObjects:@"#This",@"line",@"should",@"not",@"be",@"ignored",nil], - [NSArray arrayWithObjects:@"This",@"has",@"\"escaped\"",@"quotes",nil], - [NSArray arrayWithObjects:@"This",@"has",@"\"escaped\"",@"quotes",nil], - [NSArray arrayWithObjects:@"This",@"has",@"empty",@"fields",@"",@"",@"",nil], - [NSArray arrayWithObjects:@"This",@"has",@"escaped",@"escapes\\",nil], - [NSArray arrayWithObjects:@"This",@"has",@"escaped",@"commas,",nil], - [NSArray arrayWithObjects:@"This",@"has",@"quoted",@"commas,",nil], - [NSArray arrayWithObjects:@"This",@"has",@"empty",@"quoted",@"fields",@"",@"",nil], - [NSArray arrayWithObjects:@"This",@"has",@"mixed",@"\"escaped quotes\"", nil], - [NSArray arrayWithObjects:@"This",@"is",@"the",@"last",@"line",nil], - nil]; + NSArray * expectedFields = [self expectedFields]; NSUInteger expectedCount = [expectedFields count]; NSUInteger actualCount = [fields count]; @@ -65,7 +78,8 @@ - (void) testCSV { STAssertTrue([actualLine isEqualToArray:expectedLine], @"lines differ. Expected %@, given %@", expectedLine, actualLine); } - NSString * tempFile = [NSTemporaryDirectory() stringByAppendingPathComponent:@"test.csv"]; + NSString * tempFileName = [NSString stringWithFormat:@"%d-test.csv", arc4random()]; + NSString * tempFile = [NSTemporaryDirectory() stringByAppendingPathComponent:tempFileName]; NSLog(@"Writing to file: %@", tempFile); BOOL writtenToFile = [expectedFields writeToCSVFile:tempFile atomically:YES]; @@ -87,4 +101,25 @@ - (void) testCSV { } } +- (void) testCSVString { + NSString * file = [[NSBundle bundleForClass:[self class]] pathForResource:@"Test" ofType:@"csv"]; + + NSStringEncoding encoding = 0; + NSString * csv = [NSString stringWithContentsOfFile:file usedEncoding:&encoding error:nil]; + NSArray * fields = [csv CSVComponents]; + NSLog(@"fields: %@", fields); + + NSArray * expectedFields = [self expectedFields]; + + NSUInteger expectedCount = [expectedFields count]; + NSUInteger actualCount = [fields count]; + STAssertTrue(expectedCount == actualCount, @"incorrect number of lines parsed. expected %lu, given %lu", expectedCount, actualCount); + for (int i = 0; i < MIN(expectedCount, actualCount); ++i) { + NSArray * actualLine = [fields objectAtIndex:i]; + NSArray * expectedLine = [expectedFields objectAtIndex:i]; + + STAssertTrue([actualLine isEqualToArray:expectedLine], @"lines differ. Expected %@, given %@", expectedLine, actualLine); + } +} + @end