Home > コンピュータ | > Cocoa

Cocoa Archive

CocoaでHTMLを扱う

Mac OS X 10.4以降ではNSXMLDocumentでHTMLを扱える

比較的簡単にHTMLを扱うことができる。比較的ぶっ壊れたHTMLでもOKだそうだ。

#import 

int main (int argc, const char * argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    // insert code here...

    NSURL *url = [NSURL URLWithString:@"http://mixi.jp/"];

	NSData *data = [NSData dataWithContentsOfURL:url];
	NSXMLDocument *document = [[[NSXMLDocument alloc] initWithData:data options:NSXMLDocumentTidyHTML error:NULL] autorelease];

	NSArray* nodes;
	NSError* error;	

	nodes = [document nodesForXPath:@"/" error:&error];

	NSLog(@"%@",nodes);

	[pool drain];
    return 0;
}

NSAttributedString

[NSAttributedString initWithHTML:documentAttributes:]

HMDTより

崩れるのでblockquoteしないけど、以下はコピペです。

Cocoaを使って、HTMLをパースするには、2つの方法がある。1つは、WebViewを使う方法。WebViewにHTMLを読み込ませて、WebFrameからDOMDocumentを取得する。これだと、Webページの表示とまったく同じDOMが得られる。JavaScriptも実行される。しかし、WebViewを作成して、HTMLレンダリングまでしてしまうので、単にパースをしたいだけのときは、ちと大げさ。

もう1つの方法は、NSXMLDocumentを使う事。NSXMLDocumentを作成するときのオプションに、NSXMLDocumentTidyHTMLを指定すれば、HTMLもパースできる。おぉ、便利だ。しかし、NSXMLDocumentが生成するツリーと、WebViewが作るものは、若干違うときがある。これは、パースのライブラリに、WebViewがkhtmlを使っているのに対して、NSXMLDocumentはlibxml2を使っているため。libxml2は、HTMLをパースするときに、XHTMLへの変換を試みるため(?)、閉じられていないタグの取り扱いなどに、少し解釈に差が出るみたい。そこだけ注意。

HMDTより、その2

崩れるのでblockquoteしないけど、以下はコピペです。

IE のお気に入りを読み込むための、長い道のり。

いまさらながら、Internet Explorer のお気に入りファイルを読み込むプログラムを書こうとしている。すでに多くの方がご存知のように、IE のお気に入りは、~/Library/Preferences/Explorer/Favorites.html に書き出されているんだ。これは Netsacpe Bookmark File Format っていうのと互換で書かれているらしい。簡単な説明がここにある。一言で言うと、HTML ファイルとして保存してある訳だ。ガッデム。再利用するときにめんどくさいじゃないか。XML にしろよ。

文句をいってもしかたがないので、どうにかすることを考える。まず、HTML ファイルをパースしないといけない。これは Cocoa ではできない。Web Kit を使うか?WebView を作成して、HTML ファイルを読み込ませて、DOM ツリーを取得。これならできそうだけど、たかがお気に入りファイルを読むために、そこまでやるのはおおげさすぎる。Web Kit の HTML パーサに直接アクセスできればいいんだけど。で、検討の結果、libxml2 を使ってみることにした。

libxml2 は、XML を取り扱うためのライブラリ。Panther あたりから、Mac OS X に標準で付いてくる(これ重要)。HTML のパーサもついている。次のようなコードで、HTML をパースしてツリーを得ることができる。

Sample


#include <libxml/HTMLParser.h>
#include <libxml/HTMLTree.h>

int main(int argc, char** argv)
{
    htmlDocPtr htmlDoc;
    htmlDoc = htmlParseFile(
        "/Users/mkino/Library/Preferences/Explorer/Favorites.html", NULL);
}

おぉ、簡単だ。htmlParseFile を呼べばよろし。どのくらいの HTML ファイルに対応しているかは分からないけど、IE のお気に入りはとりあえず読み込めるようだ。
あと、エンコーディングも取得できる。HTML ファイルに、charset でエンコーディングが指定してある場合は、 htmlGetMetaEncoding で取得できる。

次に、このツリー構造を解析する。解析の仕方は 2 つ。1 つは、一個ずつ手でたぐっていく方法。もう 1 つは XPath を使う方法だ。XPath を使えば、ダイレクトに目的のノードにアクセスできる。たとえば、IE のお気に入りだと、トップ階層にあるフォルダは、HTML の body 要素の下の、dl 要素の下の、dt 要素の下の、h3 タグとして表現されている。これにアクセスするコードは、次のような感じになる。

void getFolder(htmlDocPtr htmlDoc)
{
    xmlXPathContextPtr  context;
    context = xmlXPathNewContext(htmlDoc);

    xmlXPathObjectPtr   result;
    result = xmlXPathEvalExpression(
            (xmlChar*)"/html/body/dl/dt/h3", context);
    xmlXPathFreeContext(context);

    xmlNodeSetPtr   nodeSet;
    nodeSet = result->nodesetval;
    if (!xmlXPathNodeSetIsEmpty(nodeSet)) {
        int i;
        for (i = 0; i < nodeSet->nodeNr; i++) {
            xmlNodePtr  nodePtr;
            nodePtr = nodeSet->nodeTab[i];

            printf("name %s\n", nodePtr->name);
            printf("content %s\n", xmlNodeListGetString(
                    htmlDoc, nodePtr->xmlChildrenNode, 1));
        }
    }
    xmlXPathFreeObject(result);
}

HTML ツリーに対して、/html/body/dl/dt/h3 っていう XPath を評価する。これで h3 タグの一覧が得られる訳だ。
XPath は便利だけど、今回のお気に入りの読み込みではフォルダをたぐるため、再帰的にアクセスする必要があるので、手でやった方が効率がいいかもしれない。これで、どうにかタイトルとアドレスは得ることができそうだ。

libxml2サーチパス

libxml2を使いたくて、でもosxに既に入っているような気がした。

/usr/include/のところにlibxml2の文字を発見、これだ。

でも、ソースをみると、
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
と書いてある。

#include <libxml2/libxml/parser.h>
とか置き換えても、内部のパスがおかしいみたいになってしまう。

うーむ。

少し考えて、プロジェクトのサーチパスに
HEADER_SEARCH_PATHS = /usr/include/libxml2/
と加えてみたら、通った。

The Debugger has exited due to signal 5 (SIGTRAP).

ずっと困っている問題がある。LeopardのXcode 3.0で開発をしていると最初はいいのだが、すぐに

2008-04-26 17:42:29.343 Xcode[246:203] *** NSTask: Task create for path ‘/Users/私/Desktop/Cocoa SQLite Browser/build/Debug/Cocoa SQLite Browser.app’ failed: 13, “Permission denied”. Terminating temporary process.

The Debugger has exited due to signal 5 (SIGTRAP).The Debugger has exited due to signal 5 (SIGTRAP).

となって止まってしまう問題がある。やはりちゃんとマニュアル類に目を通した方がいいのか。

さて、定番のGoogle検索で何とかしようと思うのだけど、例えば“has exited due to signal 5″をキーに探しても結果は324件しかなく、色々読んでも悩んでいる人はいることは分かるけど、効果的な解答が示されているものは見つからなかった。“NSTask: Task create for” “Permission denied”で探すとさらに結果は少なく78件しかない。

これはなんとかならないのか。とりあえず、Terminalで「バンドル/Contents/MacOS/バイナリ」を直接起動すると起動はするのでテストは可能。しかしFinderのアイコンには禁止マーク(?)がついているし、ダブルクリックしても「このアーキテクチャでは使用できないため、開くことができません」と言われる。これでは配布できないよ。。。

Document-based Applicationで起動時に自動的に新規書類を作らない方法

Cocoa Document-based Applicationのプロジェクトを作って「ビルドして進行」を押すとウィンドウが現れる。これはMyDocument(クラスとnib)のためのウィンドウである。最近のアプリケーションは起動するとウィンドウを自動的に開くものが多い(テキストエディタとか)けれど、そうでない方がいいものもある(例えば画像ビューア)。さて、どうすればいいのだろう?

ポイントを先に書いてしまうと

- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender {
return NO;
}

をdelegateしてしまればOKである。

しかし、Interface Builder 3.0ではインターフェースがずいぶん変わっていてやり方がわからないので試行錯誤してみた。IB 2.0までならIBでNSObjectのサブクラスを作ってインスタンス化してファイルとして書き出してコードを編集する。IB 3.0ではどうするのだろう?

どうもIB 3.0では先にXcodeでクラスを作る必要があるようだ。まずXcodeのファイルメニューから「新規ファイル」を選び「Cocoa→Objective-C class」を選ぶ。とりあえず名前は「ApplicationController」とでもしておく。ApplicationController.mには上記のapplicationShouldOpenUntitledFileメソッドを書いておく。

新規Objective-Cクラス

次にIBでMainMenu.nib(メインのnibファイル)を開いて、パレットからNSObjectをドラッグする。

Interface Builder 3.0ライブラリ

適当な名前(SQLiteDBController)をつけてInspectorからIdentity Inspectorを開いてClassに先ほど作ったクラス名「ApplicationController」を指定する。

Interface Builder 3.0インスペクタ

最後にFile’s Ownerから線を伸ばしてさきほど作ったクラス(SQLiteDBController)に接続し、delegateとして指定する。

Interface Builder 3.0でdelegate設定

以上で自動的に新しいファイルを作成しなくなる。

Home > Cocoa

Search
Feeds
Meta

Page Top