気楽なソフト工房

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



mykonos2008

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

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
XMLデシリアライズを行うiOS用ライブラリ「ChimeraXML」をgithubに公開しました。
「ChimeraXML」はJavaの「Simple」ライブラリのようにXMLをオブジェクトに変換するためのObjective-Cのライブラリです。
解析対象のXMLに対応するクラスを定義し、そのプロパティのメタ情報を解析エンジンに提供するためのメソッドを実装するだけで、
XMLの内容が、定義したクラスのオブジェクトの各プロパティに設定されます。

「ChimeraXML」は「NSXMLParser」を使用し、要素を上から順にオブジェクトのプロパティにマッピングしながら、 プロパティに値を設定していきます。

「ChimeraXML」を使用すると、XMLを解析するためのコーディング量を大幅に削減することが可能になります。
開発者が行う必要がある作業は、ほぼ、XMLに対応するクラスを定義し、そのクラスで「propertyInfoForElement:」メソッドを実装するだけになります。

「ChimeraXML」の取得


以下のURLから「ChimeraXML」のプロジェクト一式を取得してください。
https://github.com/mykonos2008/ChimeraXML

「ChimeraXML」の利用方法


Workspaceを作って、「ChimeraXML」をプロジェクトとして追加する方法と、
ソースファイル一式(ChimeraXML-Prefix.pchをのぞく)をそのまま、アプリのプロジェクトに追加する方法があります。
前者は少し面倒なので、後者をおすすめします。

コードの書き方


以下のような商品の一覧XMLをデシリアライズするとします。

<?xml version="1.0" encoding="UTF-8" ?>
<result>
        <item-count>2</item-count>
        <ignore>test</ignore>
        <items>
            <item>
                <item-name>商品1</item-name>
                <item-desc>商品1の説明</item-desc>
                <item-price>2500</item-price>
                <item-size>
                    <width>10.5</width>
                    <height>15.0</height>
                </item-size>
                <tel>03-1111-2222</tel>
                <tel>03-3333-4444</tel>
            </item>
            <item>
                <item-name>商品2</item-name>
                <item-desc>商品2の説明</item-desc>
                <item-price>3200</item-price>
                <item-size>
                    <width>13.5</width>
                    <height>18.2</height>
                </item-size>
                <shop>
                    <shop-code>S01</shop-code>
                    <shop-name>新宿南口店</shop-name>
                </shop>
                <shop>
                    <shop-code>S02</shop-code>
                    <shop-name>渋谷店</shop-name>
                </shop>
            </item>
        </items>
        <categories>
            <category>1</category>
            <category>2</category>
        </categories>
</result>

まず、XMLのデシリアライズ先となるオブジェクトのクラスを定義します。
サンプルでは、大元のクラスであるResultEntityと各商品の情報を管理するItemEntity、
商品のサイズを管理するItemSizeEntity、取り扱い店舗の情報を管理するShopEntityの
4つのクラスを定義します。

デシリアライズ先となるオブジェクトは、CPXmlEntityクラスを継承している必要があります。

「ResultEntity.h」
#import "CPXmlEntity.h"

@interface ResultEntity : CPXmlEntity

@property(strong,nonatomic) NSNumber *itemCount;
@property(strong,nonatomic) NSArray *items;
@property(strong,nonatomic) NSArray *categories;

@end

そして、XMLの要素に対応するプロパティを定義します。上記のサンプルでは、
item-count要素に対応するitemCountプロパティ、items要素に対応するitemプロパティ、
categories要素に対応するcategoriesプロパティを定義しています。
プロパティの型として使用できるのは、「NSArray」、「NSString」、「NSNumber」、「CPXmlEntity」を 継承するクラスの4つです。
クラス名やプロパティ名は要素名と一致させる必要はありません。

「ResultEntity.m」
#import "ResultEntity.h"
#import "ItemEntity.h"

@implementation ResultEntity

+ (id)propertyInfoForElement:(NSString *)element
{
    static NSDictionary *propDic;
    
    if(!propDic){
  
        CPPropertyInfo *items = [[CPPropertyInfo alloc] init];
        items.name = @"items";
        items.childType = [ItemEntity class];
        
        CPPropertyInfo *categories = [[CPPropertyInfo alloc] init];
        categories.name = @"categories";
        categories.childType = [NSNumber class];
        
        propDic = @{
                     @"item-count": @"itemCount",
                     @"items":items,
                     @"categories": categories
                    };
    }
    
    return propDic[element];
}

@end

次に実装ファイルです。実装ファイルでは、CPXmlEntityで定義されているクラスメソッド「propertyInfoForElement:」を
オーバーライドして実装する必要があります。

Objective-cではJavaのようなアノテーションの仕組みが無いため、その代わりとして、このメソッドで
プロパティのメタ情報を提供するようにします。引数で指定されたXMLの要素名に対応するプロパティの情報を
NSString、またはCCPropertyInfoクラスのオブジェクトとして返却します。

NSArray型のプロパティ以外は対応するプロパティ名をNSString型で返却するだけです。
NSArray型の場合、格納されるオブジェクトの型が提供される必要があるため、CCPropertyInfo型を返すようにします。
CCPropertyInfoは、name、type、childType、implicitArrayの4つのプロパティを持っています。
nameにはプロパティ名を設定します。typeには値を設定する必要はありません。implicitArrayは後述します。
childTypeは、NSArrayの中に格納するオブジェクトの型を設定します。

このメソッドをどのように実装するかについてはルールは有りませんが、上記のサンプルコードのようにstatic指定子を使用した
NSDictionaryを使用すると効率が良いと思います。NSDictionaryのキーに設定しているのは、XMLの要素名です。

解析対象のXMLが持っている全ての要素について、対応するプロパティを定義しておく必要は有りません。
アプリケーションが必要とする項目についてのみ、プロパティを定義し、propertyInfoForElement:で値を
返却するようにしておけば問題ないです。

「ItemEntity.m」
#import "CPXmlEntity.h"

@interface ItemEntity : CPXmlEntity

@property(strong,nonatomic) NSString *itemName;
@property(strong,nonatomic) NSString *itemDesc;
@property(strong,nonatomic) NSNumber *itemPrice;
@property(strong,nonatomic) ItemSizeEntity *itemSize;
@property(strong,nonatomic) NSArray *tels;
@property(strong,nonatomic) NSArray *shops;

@end

「ItemEntity.m」
#import "ItemEntity.h"

@implementation ItemEntity

+ (id)propertyInfoForElement:(NSString *)element
{
    static NSDictionary *propDic;
    
    if(!propDic){
        
        CPPropertyInfo *tels = [[CPPropertyInfo alloc] init];
        tels.name = @"tels";
        tels.childType = [NSString class];
        tels.implicitArray = YES;
        
        CPPropertyInfo *shops = [[CPPropertyInfo alloc] init];
        shops.name = @"shops";
        shops.childType = [ShopEntity class];
        shops.implicitArray = YES;
  
        propDic = @{
                     @"item-name": @"itemName",
                     @"item-desc": @"itemDesc",
                     @"item-price": @"itemPrice",
                     @"item-size": @"itemSize",
                     @"tel" : tels,
                     @"shop" : shops,
                    };
    }
    
    return propDic[element];
}

@end

CPPropertyInfoの[implicitArray]プロパティは、サンプルのshop要素のように同じ名前を持つ要素がそれら一括りにする親要素を持たずに
複数回出現する場合、[YES]を設定する必要があります。category要素のようにそれらを一括りにする親要素(categories)を持つ場合は、
デフォルト値である、Noを設定しておいてください。

「ItemSizeEntity.h」
@interface ItemSizeEntity : CPXmlEntity

@property(strong,nonatomic) NSNumber *width;
@property(strong,nonatomic) NSNumber *height;

@end

「ItemSizeEntity.m」
#import "ItemSizeEntity.h"

@implementation ItemSizeEntity

+ (id)propertyInfoForElement:(NSString *)element
{
    static NSDictionary *propDic;
    
    if(!propDic){
        
        propDic = @{
                    @"width": @"width",
                    @"height": @"height"
                    };
    }
    
    return propDic[element];
}

@end

「ShopEntity.h」
#import "CPXmlEntity.h"

@interface ShopEntity : CPXmlEntity

@property(nonatomic,strong) NSString *shopCode;
@property(nonatomic,strong) NSString *shopName;

@end

「ShopEntity.m」
#import "ShopEntity.h"

@implementation ShopEntity

+ (id)propertyInfoForElement:(NSString *)element
{
    static NSDictionary *propDic;
    
    if(!propDic){
        
        propDic = @{
                    @"shop-code": @"shopCode",
                    @"shop-name": @"shopName"
                    };
    }
    
    return propDic[element];
}

@end

parseをする部分の処理はとてもシンプルです。_targetXMLは解析対象のXMLを保持するNSString型の変数です。
デシリアライズ処理を行うクラスは「ChimeraParser」というクラスで、ルート要素に対応するエンティティクラスの型で初期化します。

デシリアライズは、parseメソッドで行います。プロパティのメタ情報(CCPropertyInfo)に不備が有る場合など、
例外をスローするようになっていますので、開発段階では、try、catchでparseメソッドを囲っておいた方が良いと思います。

    NSData *data = [_targetXML dataUsingEncoding:NSUTF8StringEncoding];
    ChimeraParser *chimeraParser = [[ChimeraParser alloc] initWithTargetClass:[ResultEntity class]];    
    ResultEntity *result = [chimeraParser parse:data];


コメント

コメントの投稿

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

トラックバック

http://csfun.blog49.fc2.com/tb.php/123-597a0fd3

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