気楽なソフト工房

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



mykonos2008

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

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
前回の続きで、今回はいよいよ、デシリアライズする部分の処理を紹介します。

デシリアライズする対象のXMLは以下です。

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

今回は解説するより、ソースをみていただく方が早いかと思います。ですので、ここから一気にソースを紹介します。

まずはデシリアライズを起動する部分の処理。
XMLの解析にはNSXMLParserを使用します。

    NSData *xmlData = [_xml dataUsingEncoding:NSUTF8StringEncoding];
    ParserDelegateImpl *delegateImpl = [[ParserDelegateImpl alloc] initWithTargetClass:[User class]];
    NSXMLParser *parser = [[NSXMLParser alloc] initWithData:xmlData];
    parser.delegate = delegateImpl;
    [parser parse];
    
    id user = [delegateImpl resultObject];
    NSLog(@"name = %@",[user name]);

次に、処理の途中で必要となる情報を管理しておくためのクラスです。

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

@interface ElementInfo : NSObject

//一つ下の階層の要素の値をセットする先となるオブジェクト
@property(strong,nonatomic) id target;

//要素名
@property(strong,nonatomic) NSString *elementName;

//要素に対応するプロパティの情報を管理するオブジェクト
@property(strong,nonatomic) PropertyInfo *propInfo;

@end

そして、次がデシリアライズ処理を行っている部分のソースです。

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

#import >objc/runtime.h<

@implementation ParserDelegateImpl{
    
    NSMutableArray *_elementStack;
    id _rootObject; //デシリアライズ結果のオブジェクト
}

-(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([_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){
            //要素を管理する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
{
    //スタックにつめられている最後のオブジェクトを取得し、その要素名が引数の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];
}

-(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の要素の開始を発見する毎に、その要素に対応するElementInfoクラスのインスタンスを生成し、
NSMutableArrayに追加していきます。要素の終了タグを発見すると、NSMutableArrayの最後のオブジェクトを
削除します。

これによって、NSMutableArrayには常に、現在処理中の要素の上位要素が管理されていることになります。
要するにNSMutableArrayをスタックとして使用しているわけです。

デシリアライズ処理の過程では常に現在の要素の上位要素に対応するオブジェクトへのアクセスが必要になります。
今回のサンプルでは、NameタグのテキストをUserタグに対応するUserクラスのインスタンスのプロパティに設定します。
これをスタックの仕組みを使って実現しているのです。

今回のソースではまだとてもシンプルな形をしたXMLしか扱うことができません。
そして、XMLに処理対象ではないXMLがあった場合の挙動も考慮出来ていません。
ですから、まだ実用レベルには達していないかと思います。
次回はもう少しその辺りを突き詰めてみます。


コメント

コメントの投稿

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

トラックバック

http://csfun.blog49.fc2.com/tb.php/116-8b535f78

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