気楽なソフト工房

プログラミングについていろいろな記事を書いています。



mykonos2008

Author:mykonos2008
システムエンジニアとして働いている30代の会社員です。
仕事や趣味でプログラムを書いている方の役に立つ記事を書いていきたいと思っています。
ご意見、ご感想はこちらまで
If you are an english speaker,Please visit my english blog.

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
XMLからオブジェクトのデシリアライズをするiOS用のライブラリ「ChimeraXML(キメラXML)」をgithubに公開しました。
iOSのXMLデシリアライズライブラリ「ChimeraXML」

前回の記事ではシンプルなXMLをデシリアライズする記事を紹介しました。

今回は、もう少し複雑なXMLを扱えるようにしたコードを紹介します。

<User>
  <Id>1</Id>
  <Name>山田太郎</Name>
  <Family>
    <User>
       <Name>山田次郎</Name>
     </User>
  </Family>
  <Email>xxxxx@xxxx.com</Email>
</User>

前回のXMLとの違いはFamilyという要素が加わっていることです。
今回の記事のテーマは、このFamily要素を処理することではなく、無視をすることです。

実際の開発の現場でも、APIのXMLに含まれる要素の一部が不要なケースは多々あると思います。
そこで、今回はUserクラスのpropertyInfoForElement:element:には手を加えず、
(つまり、処理対象として、Familyを追加せず)、その他の要素が正しく処理されるように
コードを変更します。ちなみに前回のコードで、今回のXMLを処理すると一部の要素が正しく
取得できません。

「ParserDelegateImpl.m」
#import "ParserDelegateImpl.h"
#import "ElementInfo.h"
#import "XmlEntity.h"
#import "PropertyInfo.h"

#import <objc/runtime.h>

@implementation ParserDelegateImpl{
    
    NSMutableArray *_elementStack;
    id _rootObject; //デシリアライズ結果のオブジェクト
    int _depth; //現在処理しているXMLの階層を示す変数
}

-(id)initWithTargetClass:(Class)targetClass
{
    self = [super init];
    if(self){
        _elementStack = [NSMutableArray array];
        _rootObject = [[targetClass alloc] init];
    }
    return self;
}

-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI
    qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
    if(_depth > [_elementStack count]){
        _depth++;
        return;
    }
    
    _depth++; 
    
    //ルート要素の場合
    if([_elementStack count] == 0){
        //最上位階層の情報を管理するElementInfoを生成し、Stackにつめる
        ElementInfo *info = [[ElementInfo alloc] init];
        info.elementName = elementName;
        info.target = _rootObject;

        [_elementStack addObject:info];
    }
    //ルート要素以外の場合
    else{
        //親要素の情報を取り出す
        ElementInfo *parent = [_elementStack lastObject];
        //Userクラスから要素に対応するプロパティの情報を取得する
        PropertyInfo *propInfo =[[parent.target class] propertyInfoForElement:elementName];
        if(!propInfo){
            return;
        }
        //プロパティが存在しているかチェックする
        objc_property_t prop =  class_getProperty([parent.target class], [propInfo.name UTF8String]);
        if(prop){
            //NSString *propAttr = [NSString stringWithUTF8String:property_getAttributes(prop)];
            //NSLog(@"propAttr = %@",propAttr);
            
            //要素を管理するElementInfoを生成し、Stackにつめる
            ElementInfo *info = [[ElementInfo alloc] init];
            info.elementName = elementName;
            info.propInfo = propInfo;
            
            //プロパティの型に応じて処理を分ける
            //NSStringの場合
            if(propInfo.type == [NSString class]){
                info.target = [[NSMutableString alloc] init];
            }
            
            [_elementStack addObject:info];
        }
    }     
}

-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName 
     namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
    if(_depth == [_elementStack count]){
        //スタックにつめられている最後のオブジェクトを取得し、その要素名が引数のelementNameと一致するか判定する
        ElementInfo *lastElement = [_elementStack lastObject];
        if([lastElement.elementName isEqualToString:elementName]){
            //targetオブジェクトがNSMutableStringの場合(テキストノードの場合)
            if([lastElement.target isKindOfClass:[NSMutableString class]]){
                //もう一つ上の階層のオブジェクトのプロパティにテキストノードの値を設定する
                ElementInfo *parentElement = [_elementStack objectAtIndex:[_elementStack count] -2];
                [parentElement.target setValue:lastElement.target forKey:lastElement.propInfo.name];
            }
        }
        
        [_elementStack removeLastObject];
        
    }
    _depth--;
}

-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
    //stackの最後のオブジェクトがNSMutableStringの場合、テキストノードの値をそれにつめる
    ElementInfo *lastElement = [_elementStack lastObject];
    if([lastElement.target isKindOfClass:[NSMutableString class]]){
        [((NSMutableString *)lastElement.target) appendString:string];
    }
}

//解析結果のオブジェクトを返却する
-(id)resultObject
{
    return _rootObject;
}

@end

変更点はほんの少しです。

まず、現在、処理をしている階層を管理するdepthという変数を追加しています。
そして、depthをdidStartElementの最初で1プラスし、didEndElementの終わりで1マイナスしています。

たとえば、Idタグに対応するdidStartElementがコールされたタイミングでは値は1になっています。
Userタグは開始していますが、終了はしていませんので。

そして、前回少し解説しましたが、このコードではデシリアライズをする際、NSMutableArrayをスタックとして使用し、
XMLを上から下の要素に移動する毎に、通過した要素の情報(ElementInfo)をNSMutableArrayに
追加していく処理をしています。逆に、要素の終了タグが見つかったタイミングで、スタックの最後に登録されているElementInfoを
削除しています。

このロジックですと、例えば、Idタグが発見され、didStartElementがコールされたタイミングでは、
Userタグ(ルート)に対応するElementInfo1つがスタックに登録されていることになります。
従って、depthとスタックのサイズは一致していることになります。

もう一点重要なポイントとして、処理の対象としない要素を発見した際は、スタックには登録しないロジック(下記の部分)になっています。

-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI
    qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
・・・・・・・・・・・・・・・・・
       PropertyInfo *propInfo =[[parent.target class] propertyInfoForElement:elementName];
        if(!propInfo){
            return;
        }
・・・・・・・・・・・・・・・・・
}

つまり、不要なタグを発見した以降は、depthがスタックのサイズより大きくなってしまいます。
これを利用して、didStartElement、didEndElementで処理を開始する前に
処理対象かどうかの判定を行っています。
次回でデシリアライズシリーズの最終回にしようと思いますが、 コレクションタイプの要素を扱えるようにしてみたいと思います。


コメント

コメントの投稿

管理者にだけ表示を許可する

トラックバック

http://csfun.blog49.fc2.com/tb.php/117-c8e5f98c

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。