2010年10月5日火曜日

Objective-C + libxml2 を使ってフィードを取り込みたいんだ (4)

Xcode 3.2.4 and iOS SDK 4.1 で試してる。
古いバージョンとか新しいバージョンのこと考えてない。

さて、続きを書こう。少し間があいたので前回は何を書いてたのか忘れた。
軽く見なおしたら「C言語の関数からMethod呼び出せるって不思議」ってことみたいなのでその続きになるっぽい。

今回はそうすると、実際にパースするところを書いていけばいいか。

流れとしてはこんな感じ。

  1. [Objective-C] connection:didReceiveData: でデータを受信(分割して取得される)
  2. [C] startElementSAX でタグの始まりを判断
  3. [C] charactersFoundSAX でタグ間の文字データを取得(タグの終わりまで繰り返される)
  4. [C] endElementSAX でタグの終わりを判断
  5. [C] 2.に戻る
  6. [Objective-C] 1.に戻る
  7. [Objective-C] connectionDidFinishLoading: でデータ受信の完了


「結局どこで文字列を取得できてるんだよ!」ってことが知りたいわけだが、それは4.のタグの終わりを判断するところ。
ここでバッファに貯めたタグ間の文字列を取得するためにゴニョゴニョしてる。


// RSSImporter.h

#import <Foundation/Foundation.h>
#import <libxml/tree.h>

@interface RSSImporter : NSObject {
@private
xmlParserCtxtPtr context;
// ここ追加
BOOL isItem; // 記事情報か
BOOL isBuffering; // バッファー中か
NSMutableData *characterBuffer; // バッファする箱
}

// ここ追加
@property BOOL isItem;
@property BOOL isBuffering;

- (void)fromC;
- (void)fromC:(const char *)localname;

// ここ追加
- (void)appendCharacters:(const char *)characters length:(NSInteger)length;
- (void)finishAppendCharacters:(NSString *)element;

@end



// RSSImporter.m の該当箇所を編集すること

//
// 要素開始の通知を受けたときの処理
//
static void startElementSAX(
void *context,
const xmlChar *localname,
const xmlChar *prefix,
const xmlChar *URI,
int nb_namespaces,
const xmlChar **namespaces,
int nb_attributes, int nb_defaulted,
const xmlChar **atrributes) {
// void *context は Objective-C との橋渡し。
// xmlCreatePushParserCtxt()の第2引数でselfとして渡されたのが、contextとして渡ってきた。
// 型を合わせることで、Objective-Cのオブジェクトとして扱えるようにしている。
RSSImporter *paserImporter = (RSSImporter *)context;

// ここ追加
// タグの開始(要素の開始)を判断する。例えば<title>xxx</title>なら<title>を判断してくれる。
// バッファし始めることをここで宣言(paserImporter.isBuffering=YES)する。
if (paserImporter.isItem == YES) {
if (strncmp((const char*)localname, "title", sizeof("title")) == 0) {
paserImporter.isBuffering = YES;
}
else if (strncmp((const char*)localname, "link", sizeof("link")) == 0) {
paserImporter.isBuffering = YES;
}
else if (strncmp((const char*)localname, "description", sizeof("description")) == 0) {
//paserImporter.isBuffering = YES; // 今回はコメントにして記事本文は読み込まないようにしとく
}
else if (strncmp((const char*)localname, "pubDate", sizeof("pubDate")) == 0) {
paserImporter.isBuffering = YES;
}

} else {
if (strncmp((const char*)localname, "item", sizeof("item")) == 0) {
paserImporter.isItem = YES;
}
else {
paserImporter.isItem = NO;
}
}
}

//
// 要素終了の通知を受けたときの処理
//
static void endElementSAX(
void *context,
const xmlChar *localname,
const xmlChar *prefix,
const xmlChar *URI) {
RSSImporter *paserImporter = (RSSImporter *)context;

// ここ追加
// タグの終了(要素の終了)を判断する。例えば<title>xxx</title>なら</title>を判断してくれる。
// バッファを終了する。
// 例えば<title>あいうえお</title>なら、Objective-C側で溜めた文字列「あいうえお」
// を確定させて次のバッファを溜められるようにしている。
if (paserImporter.isBuffering == YES) {
if (strncmp((const char*)localname, "title", sizeof("title")) == 0) {
[paserImporter finishAppendCharacters:@"title"];
}
else if (strncmp((const char*)localname, "link", sizeof("link")) == 0) {
[paserImporter finishAppendCharacters:@"link"];
}
else if (strncmp((const char*)localname, "description", sizeof("description")) == 0) {
[paserImporter finishAppendCharacters:@"description"];
}
else if (strncmp((const char*)localname, "pubDate", sizeof("pubDate")) == 0) {
[paserImporter finishAppendCharacters:@"pubDate"];
}
}
paserImporter.isBuffering = NO;
}

//
// 要素間のテキストの通知を受けたときの処理
//
static void charactersFoundSAX(
void *context,
const xmlChar *characters,
int length) {
RSSImporter *paserImporter = (RSSImporter *)context;

// ここ追加
// 通知で受け取る文字列は、一回だけじゃなくて連続してくる。(短ければ1回で受け取れる)
// それらをその都度Objective-C側にバッファを溜めていく。
// 例えば<title>あいうえお</title>なら、startElementSAXからendElementSAXの間
// で断続して文字列「あいうえお」を取得することになる。
if (paserImporter.isBuffering == YES) {
[paserImporter appendCharacters:(const char *)characters length:length];
}
}




// RSSImporter.m の該当箇所を編集すること

//
// クラスエクステンションを利用することで、
// プロパティとメソッドへのアクセスをプライベートからに制限できる
//
@interface RSSImporter ()
// これ追加
@property (nonatomic, retain) NSMutableData *characterBuffer;
@end




// RSSImporter.m の該当箇所を編集すること

@implementation RSSImporter

// ここ追加
@synthesize isItem; // 記事情報か
@synthesize isBuffering; // バッファー中か
@synthesize characterBuffer; // バッファする箱

/**
*/
- (id)init {

self = [super init];
if (self != nil) {

// ここ追加
self.isBuffering = NO;
self.characterBuffer = [NSMutableData data];

// パーサ用のコンテクストを作る
if (!context) {
context = xmlCreatePushParserCtxt(&simpleSAXHandlerStruct, self, NULL, 0, NULL);
}

NSString *feedURL = @"http://eyesrobe.blogspot.com/feeds/posts/default?alt=rss";
NSURLRequest *requestURL = [NSURLRequest requestWithURL:[NSURL URLWithString:feedURL]];
NSURLConnection *urlConnection = [[[NSURLConnection alloc] initWithRequest:requestURL delegate:self] autorelease];
if (urlConnection == nil) {
NSLog(@"error");
}
}

return self;
}

/**
ここ追加
パースされて送られてきた文字を、バッファに溜める。
NSMutableDataなcharacterBufferに送られてきた文字をがんがん入れていく。
Cの関数から呼ばれる不思議
*/
- (void)appendCharacters:(const char *)characters length:(NSInteger)length {
[characterBuffer appendBytes:characters length:length];
}

/**
ここ追加
溜めたバッファを NSString に変換する
Cの関数から呼ばれる不思議
*/
- (void)finishAppendCharacters:(NSString *)element {

NSString *currentString = [[[NSString alloc] initWithData:self.characterBuffer encoding:NSUTF8StringEncoding] autorelease];
NSLog(@"#%@ : %@", element, currentString);
[characterBuffer setLength:0];
}

/**
メモリ解放
*/
- (void)dealloc {
[characterBuffer release]; // ここ追加
[super dealloc];
}



で実行すると、こんな感じのログが出ると。

2010-10-05 14:54:57.747 Foo[1285:207] 受信中(データは分割されて受信される)
2010-10-05 14:54:57.749 Foo[1285:207] #pubDate : Thu, 30 Sep 2010 09:27:00 +0000
2010-10-05 14:54:57.750 Foo[1285:207] #title : Objective-C + libxml2 を使ってフィードを取り込みたいんだ (3)
2010-10-05 14:54:57.752 Foo[1285:207] #link : http://eyesrobe.blogspot.com/2010/09/objective-c-libxml2-3.html
2010-10-05 14:54:57.753 Foo[1285:207] #pubDate : Thu, 30 Sep 2010 07:27:00 +0000
2010-10-05 14:54:57.754 Foo[1285:207] #title : Objective-C + libxml2 を使ってフィードを取り込みたいんだ (2)
2010-10-05 14:54:57.781 Foo[1285:207] 受信中(データは分割されて受信される)
2010-10-05 14:54:57.782 Foo[1285:207] #link : http://eyesrobe.blogspot.com/2010/09/objective-c-libxml2-2.html
2010-10-05 14:54:57.783 Foo[1285:207] #pubDate : Thu, 30 Sep 2010 02:58:00 +0000
2010-10-05 14:54:57.784 Foo[1285:207] #title : Objective-C + libxml2 を使ってフィードを取り込みたいんだ (1)
2010-10-05 14:54:57.786 Foo[1285:207] 受信中(データは分割されて受信される)
2010-10-05 14:54:57.787 Foo[1285:207] #link : http://eyesrobe.blogspot.com/2010/09/objective-c-libxml2-1.html
2010-10-05 14:54:57.846 Foo[1285:207] 受信完了

0 件のコメント:

コメントを投稿