CFXML


HOME > TIPS > Cocoa Programming Tips 1001 > Core Foundation > CFXML

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










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() は属性情報ね。それで、問題は何が取れるのか?だ。それは、実はノードによって違う。それを示したのが下のテーブルなんだ。

スクリーンショット(2011-06-09 17.45.22).png

たとえば、ドキュメントノードは、dataTypeCode に kCFXMLNodeTypeDocument が返ってくることで判別できる。文字列表現はなしで、属性として 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




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