SSブログ

光学薄膜設計ソフトの設計 その38 - NSXMLDocumentその3 [考え中 - 光学薄膜設計]

前回木構造ベースでXMLを解析するクラスであるNSXMLDocumentをおさらいした。今日はNSXMLDocumentを使った具体的なObjective-C+Foundationフレームワークのコードの例として、XMLのファイルを読み込んで、ちょっとだけ短く表示するための簡単なコマンドラインアプリ「showXMLTree」を書いてみる。僕はこれを使ってDTDの記述に問題が無いかチェックした。

よく考えたら、なんか、光学薄膜と全然関係ないな。

これはターミナルからXMLファイルを引数にして起動すると、XMLファイルを読み込んで要素の名前、属性とその値、要素の内容を木構造のレベルごとにインデントして出力する。 例えば

$  ./showXMLTree ExampleWithDTD.xml 
dotfData
    filmComposition
        baseMedium
            [BK7]
(以下略)
というようなもの。要素のタグはそのまま出力して、属性は
(attribute->value)
のように、要素の内容はそのまま文字列として
[value]
のように出力する。

まずmain関数は

#import <Foundation/Foundation.h>
@interface NSXMLNode (OTFXMLDTDTest) //(1)
- (NSString *)indentedString;
@end

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    if (argc <= 1) {
        NSLog(@"\nSpecify XML file path as an argument.");
        exit(-1);
    }
    NSString        *arg = [NSString stringWithCString:argv[argc - 1]];
    NSString        *path = [arg stringByExpandingTildeInPath]; //(2)
    NSData          *xmldata = [NSData dataWithContentsOfFile:path];  //(3)
    NSError         *error;
    NSXMLDocument   *xmldoc = [[NSXMLDocument alloc] 
                               initWithData:xmldata
                                    options:NSXMLDocumentValidate
                                      error:&error]; //(4)
    if (error)
        NSLog(@"%@", error);
    NSXMLElement    *rel = [xmldoc rootElement];  // (5)
    NSString        *str = [rel indentedString];  // (6)
    NSLog(@"%@", str);  // (7)
    [xmldoc release];
    [pool drain];
    return 0;
}
いきなり(1)でへんなことをしているけど、これはわざわざ新しいクラスを作るのが面倒なのでNSXMLNodeのカテゴリを作っている(カテゴリって便利。javaはなんでカテゴリを真似しなかったんだろ)。あまり行儀いい作法ではないので良い子は真似をしないように。この中身はあとで示す。

呼ばれたときの引数の数をチェックした後、それがパスだと見なして(2)で、それがチルダ(~)で始まってたら絶対パスに展開する。その中身を(3)でNSDataとして読み込む。

そのNSDataからNSXMLDocumentのインスタンスを(4)で作って、そのルート要素を(5)で取り出している。(6)でカテゴリで定義するindentedStringメソッドを投げて文字列を受けて、(7)でNSLogで書き出している。それだけ。

ではそのカテゴリ。

@implementation NSXMLNode (OTFXMLDTDTest)
- (NSString *)elementString  // (14)
{
    NSMutableString *str = [NSMutableString string];
    [str appendString:[self name]];  // (15)
    NSArray   *attr = [(NSXMLElement *)self attributes];
    if ([attr count] > 0) {  // (16)
        NSEnumerator    *attren = [attr objectEnumerator];
        id              obj;
        while (obj = [attren nextObject])
            [str appendString:[obj indentedString]];
    }
    return str;
}

- (NSString *)indentedString
{
    NSMutableString *str = [NSMutableString string];  // (8)
    indent(str, [self level]);  // (9)
    switch ([self kind]) {  // (10)
        case NSXMLElementKind:
            [str appendString:[self elementString]];  // (11)
            break;
        case NSXMLAttributeKind:
            [str appendFormat:@"(%@->%@)", [self name], [self objectValue]];
            break;
        case NSXMLTextKind:
            [str appendFormat:@"[%@]", [self objectValue]];
            break;
    }
    if ([self childCount] > 0) {  // (12)
        NSArray         *children = [self children];
        NSEnumerator    *chen = [children objectEnumerator];
        id              child;
        while (child = [chen nextObject])
            [str appendString:[child indentedString]];  // (13)
    }
    return str;
}
@end
最初のelementStringメソッドは後回しにしてもらって、indentedStringメソッドの(8)でまず書き込むためのNSMutableStringを作って、インデント用のスペースを追加するindentという関数を呼んでいる。

(10)でkindメソッドを使って種類別に分岐している。(11)はノードが要素だった場合、elementStringというメソッドを呼ぶ。それ以外は書いてある通り。

さらに子要素を持っていた場合(12)で子要素ごとにindentedStringメソッドを投げて帰ってきたNSStringを(13)でアペンドしてその溜まった文字列を返している。

ノードが要素だった場合のelementStringは(14)にある。まず(15)で名前を文字列に追加して、(16)で属性を持っていたら、またそれぞれにindentedStringメソッドを投げている。

(9)の関数は

static void indent(NSMutableString *str, int level)
{
    [str appendString:@"\n"];
    while (--level > 0)
        [str appendString:@"\t"];
}
というもので、渡されてきたlevelの値に従ってタブを挿入するだけ。

これで全部。

(4)でNSXMLDocumentValidateを指定しているのでDTDに合っていないとエラーを返す。たとえば以前、例として上げたDTD付きのXMLファイルのデータの先頭に

<dotfData>
    <unknownTag />
    <filmComposition>
    (以下略)
というふうにunknownTagという要素を入れてみると
Error Domain=NSXMLParserErrorDomain Code=534
UserInfo=0x10a7e0
"Line 63: No declaration for element unknownTag"
という、けっこうちゃんとしたエラーを報告してNSXMLDocumentは作られない(NSLogの出力書式がうるさいのでちょっと読みやすくしてある)。

NSXMLDocumentは属性に関して省略された場合に、デフォルト値がDTDにあってもattributesメソッドでは補われない。attributeForName:でも同様。DTDにデフォルト値が書いてあってもそれは要素をたどることでは絶対に行き着かない。

NSXMLDTDNodeというDTDを表すオブジェクトもあるけど、属性のデフォルト値を返すようなメソッドを持っていない。今のところNSXMLDTDNodeのobjectValueメソッドから得られる文字列を自分で読むしかないらしい。部分的とはいえ自分でパーサを書くのと同じことになる。これは最悪。本来なら、DTDに属性のデフォルトがあれば、その要素の属性を問い合わせたときに反映されなければおかしい。一般のDOMはそうなっている(はず)。

今回のは木構造に従って出力するだけなので再帰呼び出しで簡単になってしまったが、実際に使う場合は要素の名前をチェックして、意味を解釈して、その内容に従って処理を進めなければならないので、再帰呼び出しで簡単にというわけにはいかない。実はそれこそがXMLを使う上で一番難しいところ。しかもきれいに簡単に書く、というのがなかなかうまくいかなくてなにをやってるのかすぐわからなくなるコードになりやすい。


nice!(0)  コメント(0)  トラックバック(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

献立03/28献立03/29 ブログトップ

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。