HMDT - Logic and Intuition -

about HMDT

Cocoa Programming Tips 1001

Core Foundation

CFXML

Core Foundation - CFXML

CFXML high level API を使う

Keywords: CFXMLTreeCreateFromData()

Core Foundation は XML パーサ機能を提供しているんだ。XML パーサとは、XML ドキュメントを適当に切り分けるもののこと。そんなパーサは世の中にいくらでもあるよ。CFXML の利点を上げるとしたら、Mac OS X システムに標準でついてくることと、Cocoa からもシームレスに使えることかな。欠点は機能が少ないこと。でも単純なパースしかしないなら、実用に耐える。

CFXML には high level API と low level API がある。high level API は木構造にパースしてくれて、low level API はイベントを上げながらパースする。DOM と SAX の違いみたいなもんだ。ここでは high level API を使ってみよう。

CFXMl は XML ドキュメントを木構造である CFXMLTree にパースするための API、CFXMLTreeCreateFromData() を提供しているんだ。

CoreFoundation/CFXMLParser.h

CF_EXPORT
CFXMLTreeRef CFXMLTreeCreateFromData(
        CFAllocatorRef allocator, 
        CFDataRef xmlData, 
        CFURLRef dataSource, 
        CFOptionFlags parseOptions, 
        CFIndex versionOfNodes);

パースされるデータは xmlData だ。CFData で渡す。CFString ではないので注意。dataSource は XML データの URL を示す。これはパース時の外部参照の解決に使われるんで、ない場合は NULL でもいいよ。parseOptions に指定できるのは以下のオプションだ。

  • kCFXMLParserValidateDocument
    DTD を使って検証する。現在サポートされてない
  • kCFXMLParserSkipMetaData
    メタデータ(DTD とコメント)をスキップする
  • kCFXMLParserReplacePhysicalEntities
    宣言されたエンティティを置き換える。現在サポートされていない
  • kCFXMLParserSkipWhitespace
    空白文字をスキップする
  • kCFXMLParserResolveExternalEntities
    外部エンティティを解決する
  • kCFXMLParserAddImpliedAttributes
    DTD が特定している属性を加える。現在サポートされていない

おいおい、DTD に関するオプションはサポートされてないのかよ。XML パーサとしては、片手落ちじゃねーの?あと、関数の返り値としては、CFXMLTree 型で返る。

ま、とりあえず使ってみよう。使い方はこんな感じで。

TreeViewDocument.m (sample)

- (BOOL)loadDataRepresentation:(NSData*)data 
        ofType:(NSString*)type
{
    if ([type isEqualToString:@"XMLDocumentType"]) {
        // Parse the XML and get the CFXMLTree
        _xmlTree = CFXMLTreeCreateFromData(
                kCFAllocatorDefault,
                (CFDataRef)data, 
                NULL, 
                kCFXMLParserSkipWhitespace, 
                kCFXMLNodeCurrentVersion);
        
        ...
        
        return YES;
    }
    
    return NO;
}

NSDocument の loadDataRepresentation:ofType: でパースしてみた。読み込む XML ファイルは、IANA でエンコーディングを指定している必要がある。これで CFXMLTree 型にパースされる。

取り出された CFXMLTree 型の使い方は、長くなったので次のコラムで。

■サンプルダウンロード:
XMLTreeViewer.tar.gz

Core Foundation - CFXML

CFXML のノード

Keywords: CFXMLNode

XML ドキュメントのノードを表すのが CFXMLNode オブジェクト。たとえば、ドキュメントノードとか、エレメントノードとか、テキストノードとかね。この CFXMLNode は、次の情報を持っているはずなんだ。

  • ノードのタイプ
  • 文字列表現
  • 追加の属性

ノードのタイプは、そのノードが何であるかを表すもの。文字列表現は、そのノードのテキストタイプの情報。たとえば、エレメントノードならタグ名、テキストノードならそのテキストの内容。追加の属性は、それ以外の属性ね。

これらを取得するには、次の API を使うんだ。

TreeViewDocument.m (sample)

CF_EXPORT
CFXMLNodeTypeCode CFXMLNodeGetTypeCode(CFXMLNodeRef node);

CF_EXPORT
CFStringRef CFXMLNodeGetString(CFXMLNodeRef node);

CF_EXPORT
const void *CFXMLNodeGetInfoPtr(CFXMLNodeRef node);

CFXMLNodeGetString() が文字列用で、CFXMLNodeGetInfoPtr() は属性情報ね。それで、問題は何が取れるのか?だ。それは、実はノードによって違う。それを示したのが下のテーブルなんだ。

ノードタイプ 文字列表現 属性情報
kCFXMLNodeTypeDocument (未使用) CFXMLDocumentInfo *
kCFXMLNodeTypeElement タグ名 CFXMLElementInfo*
kCFXMLNodeTypeAttribute (未使用) (未使用)
kCFXMLNodeTypeProcessInstruction ターゲット名 CFXMLProcessingInstructionInfo*
kCFXMLNodeTypeComment コメント NULL
kCFXMLNodeTypeText テキスト NULL
kCFXMLNodeTypeCDATASection CDATA NULL
kCFXMLNodeTypeDocumentFragment (未使用) (未使用)
kCFXMLNodeTypeEntity エンティティ名 CFXMLEntityInfo*
kCFXMLNodeTypeEntityReference エンティティリファレンス CFXMLEntityReferenceInfo*
kCFXMLNodeTypeDocumentType トップレベルのエレメント名 CFXMLDocumentTypeInfo*
kCFXMLNodeTypeWhitespace 空白文字 NULL
kCFXMLNodeTypeNotation ノテーション名 CFXMLNotationInfo*
kCFXMLNodeTypeElementTypeDeclaration タグ名 CFXMLElementTypeDeclarationInfo*
kCFXMLNodeTypeAttributeListDeclaration タグ名 CFXMLAttributeListDeclarationInfo*

たとえば、ドキュメントノードは、dataTypeCodekCFXMLNodeTypeDocument が返ってくることで判別できる。文字列表現はなしで、属性として CFXMLDocumentInfo 型が取れる。こいつは、ソースの URL とエンコーディングを持っているんだ。

と、いう感じでノードを取り扱うことができる。じゃ、サンプル。

TreeViewDocument.m (sample)

- (id)outlineView:(NSOutlineView*)outlineView 
        objectValueForTableColumn:(NSTableColumn*)tableColumn 
        byItem:(id)item
{
    CFXMLNodeRef	node = CFXMLTreeGetNode((CFXMLTreeRef)item);

    ...

    switch (CFXMLNodeGetTypeCode(node)) {
    ...

    case kCFXMLNodeTypeElement: {
        const CFXMLElementInfo*	element = 
                (CFXMLElementInfo*)CFXMLNodeGetInfoPtr(node);
        NSArray*		attributeOrder = 
                (NSArray*)element->attributeOrder;
        NSDictionary*	attributes = 
                (NSDictionary*)element->attributes;
        
        string = [NSMutableString string];
        [string appendFormat:@"<%@", CFXMLNodeGetString(node)];
        for (i = 0; i < [attributeOrder count]; i++) {
            id	attr = [attributeOrder objectAtIndex:i];
            [string appendFormat:@" %@="%@"", 
                    attr, [attributes objectForKey:attr]];
        }
        [string appendFormat:@">"];
        
        return string;
    }
    ...

    }
    
    return (id)CFXMLNodeGetString(node);
}

サンプルでは、まず CFXMLNode を取り出して、CFXMLNodeGetTypeCode() を使ってノードタイプを判別している。それに対して swtich をかけるんだ。それがエレメントノードだったら、CFXMLNodeGetInfoPtr() を使って属性を取り出す。そうすると、タグの属性が入った NSDictionary にアクセスできる、ってわけだ。

とまぁ、こんな感じでノードを使おう。

■サンプルダウンロード:
XMLTreeViewer.tar.gz

Core Foundation - CFXML

CFXMLTreeCreateFromData() でエラーの詳細を受け取る

Keywords: CFXMLTreeCreateFromData

CFXMLTreeCreateFromData() は、XML データを指定して、パースされた木構造を受け取るための API。成功したときは問題ないんだけど、失敗したときは NULL が返ってくるだけなんだ。いったいどこで XML パースが失敗したのか分からないんだ。これは悲しい。失敗した原因と行数ぐらいは知りたいよな。

エラーの原因をつかむには、CFXMLParser を使う必要がある。じつは、CFXMLTreeCreateFromData() の中で、CFXMLParser を作成してパースしているんだ。だから、パースが終わった後、その CFXMLParser を返してやる API に変更してやればいい。

というわけで、作ってみた。CFXMLTreeCreateFromDataWithParser() っていう API だ。

CFXMLEx.h (sample)

CFXMLTreeRef CFXMLTreeCreateFromDataWithParser(
        CFAllocatorRef allocator, 
        CFDataRef xmlData, 
        CFURLRef dataSource, 
        UInt32 parseOptions, 
        CFIndex parserVersion, 
        CFXMLParserRef* parserOut);

CFXMLTreeCreateFromData() API の後ろに CFXMLParserRef へのポインタをくっつけた。パースが終わったら、ここにパーサを設定するんだ。

で、これが実装の一部。Darwing で公開されている CFXMLParser.c を参考にした。

CFXMLEx.c (sample)

CFXMLTreeRef CFXMLTreeCreateFromDataWithParser(
        CFAllocatorRef allocator, 
        CFDataRef xmlData, 
        CFURLRef dataSource, 
        UInt32 parseOptions, 
        CFIndex parserVersion, 
        CFXMLParserRef* parserOut)
{
    CFXMLParserRef			parser;
    CFXMLParserCallBacks	callbacks;
    CFXMLTreeRef			result;
    
    ...
    
    // Create parser
    parser = CFXMLParserCreate(
                    allocator, 
                    xmlData, 
                    dataSource, 
                    parseOptions, 
                    parserVersion, 
                    &callbacks, 
                    NULL);
    
    // Parse
    if (CFXMLParserParse(parser)) {
        result = (CFXMLTreeRef)CFXMLParserGetDocument(parser);
    }
    else {
        ...
    }
    
    if (parserOut) {
        // Set parser output
        *parserOut = parser;
    }
    else {
        // Release parser
        CFRelease(parser);
    }
    
    return result;
}

CFXMLParserCreate() でパーサを作って、CFXMLParserParse() でパースをしてやる。そのパーサを、引数 parserOut に設定しているんだ。これで、呼び出し側でパーサを使えるぜ。ただし、そのパーサを release しておく必要があるんで注意。

この API の使い方は、こんな感じになる。

TreeViewDocument.m (sample)

- (BOOL)loadDataRepresentation:(NSData*)data 
        ofType:(NSString*)type
{
    CFXMLParserRef				parser;
    CFXMLNodeRef				document;
    const CFXMLDocumentInfo*	docInfo;
    NSStringEncoding			encoding;
    
    if ([type isEqualToString:@"XMLDocumentType"]) {
        // Parse the XML and get the CFXMLTree
        _xmlTree = CFXMLTreeCreateFromDataWithParser(
                        kCFAllocatorDefault,
                        (CFDataRef)data, 
                        NULL, 
                        kCFXMLParserSkipWhitespace, 
                        kCFXMLNodeCurrentVersion, 
                        &parser);
        if (!_xmlTree && parser) {
            // Parse error
            NSString*	message;
            message = [NSString stringWithFormat:@"Line %d: %@", 
                            CFXMLParserGetLineNumber(parser), 
                            (NSString*)CFXMLParserCopyErrorDescription(parser)];
            _errMessage = [message retain];
            CFRelease(parser);
            
            return YES;
        }
        CFRelease(parser);
        
        ...
    }
    
    return NO;
}

CFXMLTreeCreateFromDataWithParser() を使って XML ドキュメントをパースする。返り値が NULL だったらパースは失敗していて、パーサから CFXMLParserGetLineNumber()CFXMLParserCopyErrorDescription を使うことで、エラーの原因を取り出すことができるんだ。これで XML パースが使いやすくなるよ。

■サンプルダウンロード:
XMLTreeViewer.tar.gz

back to top content

Copyright © 2002-2006 HMDT. All rights reserved.