CHCSVParser / Objective-C のCSV パーサー

davedelong/CHCSVParser - GitHub
https://github.com/davedelong/CHCSVParser

CHCSVParser はCocoa 向けにMIT ライセンスで公開されているObjective-CCSV パーサー。アップルのiOS Frameworks Developer であるDave DeLong によって開発されている。

使ってみよう

    git clone https://github.com/davedelong/CHCSVParser.git

ソースコードを取得し、プロジェクトに追加します。main.m, UnitTests.m などは不要です。ARC には対応していないので追加したソースに-fno-objc-arc フラグなどをつけます。

テスト用のデータは以下を利用しました。
http://www.tepco.co.jp/forecast/html/images/juyo-j.csv

ARC が有効になっているプロジェクトからCHCSVParser.h をインクルードした時点でエラーがでました。

CHCSVParser.h:58:1: error: property attributes 'assign' and 'weak' are mutually exclusive [3]

@propert 要素にassign と weak は併用できない、という内容です。
正直ARC の仕組みもよくわかっていないんですが、おそらくコンパイラによって

@property (assign) __weak id parserDelegate;

@property (assign, weak) id parserDelegate;

に変換されている?(ツッコミ待ち) 動作される為には以下のように表記を変えます。

これでやっとビルドが通るようになりました

さらに

では基本的な動作確認をします。

そういえば、いつも解説の度にコピペしたりプロジェクト作成したりでめんどくさかったので、このダイアリー用のリポジトリgithub 上に置きました

https://github.com/laiso/iOSSamples/tree/master/CHCSVParserSample

記事ごとに新規リポジトリをつくるのもめんどくさかったんで、ルートにXcode ワークスペースをつくって子供のプロジェクトを記事毎につくっていく戦略。できるだけ外部ライブラリソースを含まずビルドスクリプトやgit submodules で動的に参照する。

で、サンプルコードのコメントもめんどくさいのでユニットテストコードをそのまま解説にします。こんなかんじ

https://github.com/laiso/iOSSamples/blob/master/CHCSVParserSample/CHCSVParserSampleTests/CHCSVParserSampleTests.m

//
//  CHCSVParserSampleTests.m
//  CHCSVParserSampleTests
//
//  Created by  on 12/1/11.

#import "CHCSVParserSampleTests.h"

#import "CHCSV.h"

@interface CHCSVParserSampleTests(){
  NSString* csvPath;
  NSString* documentsDir;
}
@end

@implementation CHCSVParserSampleTests

- (void)setUp
{
  [super setUp];
  csvPath = [[NSBundle mainBundle] pathForResource:@"tepco.csv" ofType:nil];
  documentsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}

- (void)tearDown
{
  [super tearDown];
  
  /** テスト用に書き出したCSVファイルを削除 */
  NSFileManager* fm = [NSFileManager defaultManager];
  [[fm contentsOfDirectoryAtPath:documentsDir error:nil] enumerateObjectsUsingBlock:^(NSString* path, NSUInteger idx, BOOL *stop) {
    [fm removeItemAtPath:[documentsDir stringByAppendingPathComponent:path] error:nil];
  }];
}

- (void)testRead
{
  NSError* err;
  NSArray* values = [NSArray arrayWithContentsOfCSVFile:csvPath encoding:NSUTF8StringEncoding error:&err];
  
  STAssertTrue([values count] > 0, @"読み込めたかどうか確認");
  STAssertTrue([[values objectAtIndex:0] isKindOfClass:[NSArray class]], nil);
  NSLog(@"%@", values);
}

- (void)testWrite
{
  NSError* err;
  NSArray* values = [NSArray arrayWithContentsOfCSVFile:csvPath encoding:NSUTF8StringEncoding error:&err];
  NSMutableArray* newValues = [values mutableCopy];
  [newValues addObject:[NSArray arrayWithObjects:@"2011/12/1", @"10:00", @"4043", @"0", nil]];
  NSString* savePath = [documentsDir stringByAppendingPathComponent:@"testWrite.csv"];
  BOOL success = [newValues writeToCSVFile:savePath atomically:YES error:&err];
  STAssertTrue(success, @"追記して保存できるかどうか確認");  
}

- (void)testCheck
{
  NSError* err;
  NSArray* values = [NSArray arrayWithContentsOfCSVFile:csvPath encoding:NSUTF8StringEncoding error:&err];
  NSMutableArray* newValues = [values mutableCopy];
  [newValues addObject:[NSArray arrayWithObjects:@"2011/12/1", @"10:00", @"4043", @"0", nil]];
  NSString* savePath = [documentsDir stringByAppendingPathComponent:@"testCheck.csv"];
  BOOL success = [newValues writeToCSVFile:savePath atomically:YES error:&err];
  STAssertTrue(success, nil);
  
  NSArray* restoreValues = [NSArray arrayWithContentsOfCSVFile:savePath encoding:NSUTF8StringEncoding error:&err];
  STAssertTrue([restoreValues count] > [values count], @"保存したデータの件数がちゃんと増加しているか確認");
}

@end

あっ、Delegate のへんわざわざ修正したのに試してなかった…… (あとでやる)