光学薄膜設計ソフトの設計 その36 - NSXMLDocumentの使い方 [考え中 - 光学薄膜設計]
Cocoa Break!さんのリンク集に取り上げてもらってうれしいので今回から何日かCocoaのコードについて書こう。単純。
ところでCocoa Break!さんにはずっとお世話になっている。Appleの抽象的な記述の多い、決して読みやすいとは言えないGuideは、Cocoa Break!さんの翻訳を読ませてもらって「こんなことを言ってたのか」と初めて理解できるということがよくある。
僕のブログにAppleのGuideやReferenceへのリンクを張ることがあるけど、これはやはり第1ソースを参照することにしているのでどうしても仕方がない(本やCDの評論のページのリンク先がAmazonになっているのとかは、僕はやりたくない)。日本語を母国語にしている人でGuideやReferenceへのリンクをたどって先を見る気がある人は、それに対応するCocoa Break!さんの翻訳を見た方が手っ取り早い。日本語を母国語にしていない人がこのブログを見ることはないだろうと思うけど。
ということで、お世話になってます。
XMLを解析するための方法
一般的にはXMLファイルの解析に二通りのやりかたが用意されている。それは
- 木構造ベース型
- イベント駆動型
当然、木構造ベース型はメモリを食うけど構造が出来上がってから中身を調べるので、編集してもう一度ファイルに書き出したりできる。イベント駆動型は軽いけど、パース動作をいわゆるコールバック関数(Javaならインベントハンドラ、Cocoaならdelegate)として実装する必要があって「あなたに合わせます」型に書かなければいけなくて面倒なのと、読みっぱなしで、後戻りできない。どちらかというとイベント駆動型のほうが多く使われている感じがするけど、その場合スタックを明示的に使ったりするテクニカルなコードが目につく気もする。
XMLを解析するCocoaクラス
Cocoaには両方の方式に対応するクラスがそれぞれFoundationにある。木構造ベース型はNSXMLDocumentで、イベント駆動型はNSXMLParserでそれぞれパースできる。Cocoaの場合、NSXMLDocumentそのものはNSXMLParserを使って作られているらしい。まあ、そらそうだわな。両方書けと言われてそれぞれ別物として書くプログラマがいたとしたら、そいつのコードは信用できんわ。
木構造ベース型のクラスはNSXMLDocumentだけでなく、いくつかのクラスからできていて
- NSXMLNode
- NSXMLElement
- NSXMLDocument
- NSXMLDTDNode
- NSXMLDTD
もう一方のイベント駆動型は
- NSXMLParser
NSXMLDocument
今回はそれほど大きなデータ構造にはならないので、NSXMLDocumentを使って読み込んでしまって、そのあとじっくり中身を解析する、と言う方法にする。NSXMLDocumentの初期化はおもに
- (id)initWithContentsOfURL:(NSURL *)url options:(NSUInteger)mask error:(NSError **)error; - (id)initWithData:(NSData *)data options:(NSUInteger)mask error:(NSError **)error; - (id)initWithXMLString:(NSString *)string options:(NSUInteger)mask error:(NSError **)error;のみっつがある(それ以外に既存のNSXMLElementからつくることもできる)。メソッド名を見ればわかるようにひとつ目はURLを指定してそこから、ふたつ目はNSDataから、みっつ目はNSStringからで、例えば
- (id)initWithContentsOfURL:(NSURL *)url options:(NSUInteger)mask error:(NSError **)error { NSData *xmlData = [NSData dataWithContentsOfURL:url options:NSUncachedRead error:error]; if (!(*error)) return nil; return [self initWithData:xmlData options:mask error:error]; }などとできるので実質的におなじである。
とりあえず、読み込みとは分けて、そのうえでエンコードの問題などを避けるためにふたつ目のNSDataから作るのが簡単。
初期化のときのmaskは、ドキュメントには
NSXMLDocumentTidyHTML = 1 << 9, NSXMLDocumentTidyXML = 1 << 10, NSXMLDocumentValidate = 1 << 13, NSXMLDocumentXInclude = 1 << 16, NSXMLDocumentIncludeContentTypeDeclaration = 1 << 18,となっている。今回関係するmaskは、まずNSXMLDocumentTidyXMLで、これは構文解析エラーが起こるような場合(タグが閉じてないとか)を回復しようと努力をした上で読み込む。それとNSXMLDocumentValidateはDTDか、XML Schemaが指定されていると(DTDの場合はファイル内部に書かれている場合と、外のファイルを参照する場合の両方で)XMLがValidかどうか検証する。DTD、XML Schemaの記述からはずれているとNSXMLDocumentのオブジェクトは作られず、errorに検証内容が入って返される。
今回、内部DTDの場合は動作が確認できたけど、XML SchemaはどうしてもSchemaファイルを読んでくれない。
AppleのTree-Based XML Programing Guide(Cocoa Break!さんのサイトに「Cocoa のための木構造ベースの XML プログラミングガイド」として翻訳あり)に「 A schema should be physically installed on a Mac OS X system at /System/Library/Schemas/elementName.xsd. 」とあって、Schemaファイルの位置が決めうちになっているように書いてあるけど、ここに置いてもやっぱり読んでくれない。
よくわからない。Schemaファイルの記述の仕方が間違っている可能性が高いけど、それがチェックできない。NSXMLDocumentもそれを報告してくれない。
今回の場合、データを記述するのでなるべく厳密にしておきたい。ということでNSXMLDocumentTidyXMLのフラグはオフにしてNSXMLDocumentValidateをオンにする。ただし、これでもDTDのレベルの検証なのでオブジェクトの中身の検証(例えば数字があるべきところに文字が入っているとか)は自前でやる必要がある。XML Schemaならこのレベルも検証可能だったはず。
NSXMLDocumentValidateの検証は、コンパイラのようにできるだけたくさんのエラーを報告してくれるわけではなくて、最初に出会った時点でパースを中止するみたい。複数個のエラーが含まれているときは、ひとつ修正しては読み込んで、という繰り返しが必要になる。
次回は読み込みが成功したNSXMLDocumentの中身をたどる方法について。
明快に解説していただき大変感謝しています。いろいろ不確かな認識があったのですが整理がつけられました。
NSXMLDocumentValidateに外部のXML Schema を参照させる方法ですが、
xmlns:xsi= と xsi:schemaLocation= アトリビュートをルートエレメントに付け加えたら、一応読みにいくようになりました。/System/...ではなく、xsi:schemaLocationにhttp://...でネット上のSchemaを指定してみました。
by a310 (2010-03-23 01:03)
コメントありがとうございます。
お役に立てたとしたら、僕としてもうれしいです。
Schemaの読み込みは今度試してみます。
なにしろ、すっかり忘れてしまっていて...
何のためのメモなんだか....
by decafish (2010-03-23 21:45)