気楽なソフト工房

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



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は以下です。

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

子の要素として、複数のUser要素を持つFriends要素を追加しました。

まず、デシリアライズ先のUserクラスにプロパティを追加して、propertyInfoForElement:element:にも処理を追加します。

[User.h]

#import <Foundation/Foundation.h>
#import "XmlEntity.h"

@interface User : XmlEntity

@property(strong,nonatomic) NSString *userId;
@property(strong,nonatomic) NSString *name;
@property(strong,nonatomic) NSArray *friends;
@property(strong,nonatomic) NSString *email;

@end

[User.m]
#import "User.h"

@implementation User

+(PropertyInfo *)propertyInfoForElement:(NSString *)element
{
    static NSDictionary *_propDic;
    
    if(!_propDic){
        
        PropertyInfo *userId = [[PropertyInfo alloc] init];
        userId.name = @"userId";
        userId.type = [NSString class];
        
        PropertyInfo *name = [[PropertyInfo alloc] init];
        name.name = @"name";
        name.type = [NSString class];
        
        PropertyInfo *friends = [[PropertyInfo alloc] init];
        friends.name = @"friends";
        friends.type = [NSArray class];
        friends.subType = [User class];
        
        PropertyInfo *email = [[PropertyInfo alloc] init];
        email.name = @"email";
        email.type = [NSString class];
        
        _propDic = @{
                     @"Id": userId,
                     @"Name": name,
                     @"Friends":friends,
                     @"Email" :email
                     };
    }
    
    return _propDic[element];
}
@end

上記のコードでPropertyInfoクラスのsubTypeというプロパティにUserクラスを設定しています。
これは今回、PropertyInfoクラスに追加したプロパティで、Objective-cにはジェネリックコレクションの仕組みが無いため、
NSArray型のプロパティの要素として、何のクラスのオブジェクトが登録されるかを指定するためのものです。

「PropertyInfo.h」
#import <Foundation/Foundation.h>

@interface PropertyInfo : NSObject

@property(strong,nonatomic) NSString *name;
@property(strong,nonatomic) Class type;
@property(strong,nonatomic) Class subType;

@end

そして、最後にコレクションを扱う処理を追加したパース処理クラスです。

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

#import 

@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];
        
        //親要素がコレクションの場合
        if([parent.target isKindOfClass:[NSArray class]]){
            ElementInfo *info = [[ElementInfo alloc] init];
            info.elementName = elementName;
            info.target = [[parent.propInfo.subType alloc] init];
            [parent.target addObject:info.target];
            [_elementStack addObject:info];
        }
        else{
            //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){
               
                //要素を管理するElementInfoを生成し、Stackにつめる
                ElementInfo *info = [[ElementInfo alloc] init];
                info.elementName = elementName;
                info.propInfo = propInfo;
                
                //プロパティの型に応じて処理を分ける
                //NSStringの場合
                if(propInfo.type == [NSString class]){
                    info.target = [[NSMutableString alloc] init];
                }
                //コレクションの場合
                else if(propInfo.type == [NSArray class] || propInfo.type == [NSMutableArray class]){
                    info.target = [NSMutableArray array];
                    [parent.target setValue:info.target forKey:propInfo.name];
                }
                
                [_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


これで一般的な形のXMLを扱うことが出来るものになったと思います。
実際にこれをライブラリとして使用する場合は、もう少し必要になることがあると思います。

今回のコードでは、デシリアライズ先のクラスのプロパティの型が、
NSStringとNSArray型のみでしたが、その他の型を扱えるようにする必要があると思います。

あとは、XMLが崩れていたり、デシリアライズとして、想定していないクラスが
渡されたりした場合などの例外処理と、チェック処理あたりになると思います。


コメント

コメントの投稿

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

トラックバック

http://csfun.blog49.fc2.com/tb.php/118-fb7bcbda

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