Geometry 関数
NSBundle
NSDictionary
NSFileManager
NSMutableAttributedString
NSNumber
NSScanner
NSString
NSTimer
Graphic 関数
NSApplication
NSBezierPath
NSCell
NSColor
NSCursor
NSCustomImageRep
NSDocument
NSDocumentController
NSDragging
NSEvent
NSFontManager
NSGraphicsContext
NSImage
NSMenu
NSOutlineView
NSPanel
NSPopUpButton
NSResponder
NSScrollView
NSString 追加
NSTableColumn
NSTableView
NSTextStorage
NSTextView
NSToolbar
NSView
NSWindow
NSWorkspace
その他
.nib ウィンドウ
Views パレット
クラス
インスタンス変数
メソッド
CFXML
-CFXML high level API を使う
-CFXML のノード
-CFXMLTreeCreateFromData() でエラーの詳細を受け取る
Carbon Event
Carbon Graphics
Cocoa で日本語
メソッド
その他

- 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



[Home] [Download] [Archives] [BBS] [Cocoa Programming Tips 1001] [Core Foundation の秘密] [Safari Developer Center] [はじめてのブラウザのつくり方] [Sketch BP] [スクリーンセイバーを作ろう] [Objective-C 最適化] [Authorization API 完全理解] [Mac OS X Programming Books Review] [オブジェクト指向の言語比較論] [panther-dev]

mailto: mkino@xd5.so-net.ne.jp