gist

2012年1月7日土曜日

iOSでSOAPをJSONに変換する

SOAPで取得したデータをJSON形式に変換します。エスケープ文字、空オブジェクト、空の配列には未対応です。コードも怪しいです。

以下のようなファイルを用意します。

  1. soap.xml ... 取得したSOAP形式のデータ(今回はファイルで)
  2. LHParser.h/m ... XMLのパースとJSONへの変換
  3. NSMutableArray+StackAdditions.h/m ... NSMutableArrayにスタックの機能を追加したカテゴリ
  4. libxml2.dylib ... XMLパース用のライブラリ。事前に追加。

soap.xml

<soapenv:Envelope
    xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <soapenv:Body>
    <ns1:getHTTMembersResponse
        soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
        xmlns:ns1="http://k-on.luckyandhappy.com">
        <response xsi:type="ns2:GetHTTMembersResponse"
            xmlns:ns2="k-on">
            <members
                soapenc:arrayType="ns2:Member[1]"
                xsi:type="soapenc:Array" 
                xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
                <member xsi:type="ns2:Member">
                    <name xsi:type="xsd:string">平沢 唯</name>
                    <gender xsi:type="ns2:Gender">
                        <id xsi:type="xsd:int">2</id>
                        <title xsi:type="xsd:string">女性</title>
                    </gender>
                    <birthday xsi:type="xsd:dateTime">1991-11-27T00:00:00.000Z</birthday>
                    <height xsi:type="xsd:float">156.2</height>
                    <weight xsi:type="xsd:float">50.5</weight>
                    <abo xsi:type="xsd:ABO">
                        <id xsi:type="xsd:int">3</id>
                        <title xsi:type="xsd:string">O</title>
                    </abo>
                    <isVocal xsi:type="xsd:boolean">true</isVocal>
                </member>
            </members>
        </response>
    </ns1:getHTTMembersResponse>
</soapenv:Body>
</soapenv:Envelope>

LHParser.h

#import <Foundation/Foundation.h>
#import <libxml/tree.h>
#import "NSMutableArray StackAdditions.h"

@interface LHParser : NSObject {
    
    NSDictionary *_openers;
    NSDictionary *_closures;
    NSMutableArray *_closuresStack;
    NSDictionary *_charactersFoundTable;
    BOOL _isCharactersFound;
    
    xmlParserCtxtPtr _ctxt;
    NSMutableString *_json;
}
-(void)parse;
-(void)didParse;

-(void)startElement:(NSString*)localname type:(NSString*)type;
-(void)endElement:(NSString*)localname;
-(void)foundCharacter:(NSString*)characters;

@end


static void startElementSAX(void *context,
                            const xmlChar *localname,
                            const xmlChar *prefix,
                            const xmlChar *URI,
                            int nb_namespaces,
                            const xmlChar **namespaces,
                            int nb_attributes,
                            int nb_defaulted,
                            const xmlChar **atrributes);

static void endElementSAX(void *context,
                          const xmlChar *localname,
                          const xmlChar *prefix,
                          const xmlChar *URI);

static void charactersFoundSAX(void *context,
                               const xmlChar *characters,
                               int lenght);

LHParser.m

#import "LHParser.h"

static xmlSAXHandler simpleSAXHandlerStruct;


@implementation LHParser

-(id)init
{
    self = [super init];
    if(self) {
        _openers = [NSDictionary dictionaryWithObjectsAndKeys:
                                 @"\"", @"string",
                                 @"\"", @"dateTime",
                                 @"",  @"int",
                                 @"",  @"float",
                                 @"",  @"boolean",
                                 @"[",  @"Array",
                                 @"{",  @"object",
                                 nil];
        _closures = [NSDictionary dictionaryWithObjectsAndKeys:
                                  @"\"", @"\"",
                                  @"}", @"{",
                                  @"]",  @"[",
                                  @"", @"",
                                  nil];
        
        _charactersFoundTable = [NSDictionary dictionaryWithObjectsAndKeys:
                            [NSNumber numberWithBool:YES], @"string",
                            [NSNumber numberWithBool:YES], @"dateTime",
                            [NSNumber numberWithBool:YES],  @"int",
                            [NSNumber numberWithBool:YES],  @"float",
                            [NSNumber numberWithBool:YES],  @"boolean",
                            [NSNumber numberWithBool:NO],  @"Array",
                            nil];

    }
    return self;
}

-(void)parse 
{
    _isCharactersFound = NO;
    _closuresStack = [[NSMutableArray alloc] init];
    _json = [[NSMutableString alloc] initWithString:@"{"];
    
    
    xmlDefaultSAXHandlerInit();
    _ctxt = xmlCreatePushParserCtxt(&simpleSAXHandlerStruct,self,NULL,0,NULL);
    
    NSString *path = [[NSBundle mainBundle] pathForResource:@"soap" ofType:@"xml"];
    NSData *data = [NSData dataWithContentsOfFile:path];
    xmlParseChunk(_ctxt, (const char*)[data bytes], [data length], 0);
    xmlParseChunk(_ctxt,NULL, 0, 1);
    xmlFreeParserCtxt(_ctxt);
}

-(void)startElement:(NSString*)localname type:(NSString*)type 
{
    NSLog(@"localname=%@", localname);
    NSLog(@"type=%@", type);
    
    NSString *opener = [_openers valueForKey:type];
    if(opener==nil) {
        opener = [_openers valueForKey:@"object"];
    }
    
    NSString *parent = [_closuresStack lastObject];
    BOOL inArray = NO;
    if(parent){
        inArray = [parent isEqualToString:@"]"];
    }
    if(!inArray) {
        [_json appendString:@"\""];
        [_json appendString:localname];
        [_json appendString:@"\""];
        [_json appendString:@":"];
    }
    [_json appendString:opener];
    
    NSString *closure = [_closures valueForKey:opener];
    [_closuresStack push:closure];
    
    NSNumber *boolean = [_charactersFoundTable valueForKey:type];
    if(boolean!=nil) {
        _isCharactersFound = [boolean boolValue];
    }
    else {
        _isCharactersFound = NO;
    }

}

-(void)endElement:(NSString*)localname 
{
    _isCharactersFound = NO;
    
    NSString *closure = [_closuresStack pop];
    
    if(   [closure isEqualToString:@"}"] 
       || [closure isEqualToString:@"]"]) {
        [_json deleteCharactersInRange:NSMakeRange([_json length]-1, 1)];
    }

    [_json appendString:closure];
    [_json appendString:@","];
    
}

-(void)foundCharacter:(NSString*)characters 
{
    if(_isCharactersFound) {
        NSLog(@"%@", characters);
        [_json appendString:characters];
    }
}


-(void)didParse 
{
    [_json deleteCharactersInRange:NSMakeRange([_json length]-1, 1)];
    [_json appendString:@"}"];
    NSLog(@"didParse");
    NSLog(@"%@", _json);
}

-(void)dealloc 
{
    [_openers release];
    [_closures release];
    [_closuresStack release];
    [_charactersFoundTable release];
    [_json release];
}

@end



#pragma mark SAX Parsing Callbacks

bool isEnvelope = false;
bool isBody = false;
bool isContent = false;

static const char* ENVELOPE = "Envelope";
static const char* ENVELOPE_URI = "http://schemas.xmlsoap.org/soap/envelope/";
static const char* BODY = "Body";
static const char* CONTENT_URI = "http://k-on.luckyandhappy.com";

static void startElementSAX(void *context,
                            const xmlChar *localname,
                            const xmlChar *prefix,
                            const xmlChar *URI,
                            int nb_namespaces,
                            const xmlChar **namespaces,
                            int nb_attributes,
                            int nb_defaulted,
                            const xmlChar **attributes)
{
    NSLog(@"\n");
    
    if(   0==strncmp((const char*)localname, ENVELOPE, strlen(ENVELOPE)-1)
       && 0==strncmp((const char*)URI, ENVELOPE_URI, strlen(ENVELOPE_URI)-1)
       ){
        NSLog(@"Envelope start");
        isEnvelope = true;
    }
    if(   0==strncmp((const char*)localname, BODY, strlen(BODY)-1)
       && 0==strncmp((const char*)URI, ENVELOPE_URI, strlen(ENVELOPE_URI)-1)
       ){
        NSLog(@"Body start");
        isBody = true;
    }
    if(   NULL != URI
       && 0==strncmp((const char*)URI, CONTENT_URI, strlen(CONTENT_URI)-1)
       ){
        NSLog(@"Content start");
        isContent = true;
    }
    if(isContent){
        
        for(int i=0; i<nb_attributes;   i) {
            if(0==strncmp((const char*)attributes[0], "type", strlen("type")-1)){

                char type[128];
                int len = attributes[4] - attributes[3];
                memcpy( type, attributes[3], len );
                type[len] = '\0';
                const char *sp = ":";
                char *type1;
                char *type2;
                type1 = strtok( type, sp );
                type2 = strtok( NULL, sp );
                
                NSString *nsLocalname = [NSString stringWithCString:(const char*)localname
                                                           encoding:NSUTF8StringEncoding];
                NSString *nsType = [NSString stringWithCString:(const char*)type2
                                                      encoding:NSUTF8StringEncoding];
                
                [(LHParser*)context startElement:nsLocalname type:nsType];
                
                break;
            }
            
            attributes  = 5;
        }
    }
}

static void endElementSAX(void *context,
                          const xmlChar *localname,
                          const xmlChar *prefix,
                          const xmlChar *URI)
{
    if(   0==strncmp((const char*)localname, ENVELOPE, strlen(ENVELOPE)-1)
       && 0==strncmp((const char*)URI, ENVELOPE_URI, strlen(ENVELOPE_URI)-1)
       ){
        isEnvelope = false;
    }
    if(   0==strncmp((const char*)localname, BODY, strlen(BODY)-1)
       && 0==strncmp((const char*)URI, ENVELOPE_URI, strlen(ENVELOPE_URI)-1)
       ){
        isBody = false;
    }
    if(   NULL != URI
       && 0==strncmp((const char*)URI, CONTENT_URI, strlen(CONTENT_URI)-1)
       ){
        isContent = false;
        
        [(LHParser*)context didParse];
    }
    if(isContent){
        NSString *nsLocalname = [NSString stringWithCString:(const char*)localname 
                                                   encoding:NSUTF8StringEncoding];
        [(LHParser*)context endElement:nsLocalname];
    }
}

static void charactersFoundSAX(void *context,
                               const xmlChar *characters,
                               int lenght)
{
    NSString *nsCharacters = [[NSString alloc] initWithBytes:(const char*)characters
                                                      length:lenght
                                                    encoding:NSUTF8StringEncoding];
    [(LHParser*)context foundCharacter:nsCharacters];
    [nsCharacters release];
}

static void errorEncounteredSAX(void *ctx, 
                                const char *msg, 
                                ...) 
{
    NSCAssert(NO, @"Unhandled error encountered during SAX parse.");
}


static xmlSAXHandler simpleSAXHandlerStruct = {
    NULL,                       /* internalSubset */
    NULL,                       /* isStandalone   */
    NULL,                       /* hasInternalSubset */
    NULL,                       /* hasExternalSubset */
    NULL,                       /* resolveEntity */
    NULL,                       /* getEntity */
    NULL,                       /* entityDecl */
    NULL,                       /* notationDecl */
    NULL,                       /* attributeDecl */
    NULL,                       /* elementDecl */
    NULL,                       /* unparsedEntityDecl */
    NULL,                       /* setDocumentLocator */
    NULL,                       /* startDocument */
    NULL,                       /* endDocument */
    NULL,                       /* startElement*/
    NULL,                       /* endElement */
    NULL,                       /* reference */
    charactersFoundSAX,         /* characters */
    NULL,                       /* ignorableWhitespace */
    NULL,                       /* processingInstruction */
    NULL,                       /* comment */
    NULL,                       /* warning */
    errorEncounteredSAX,        /* error */
    NULL,                       /* fatalError //: unused error() get all the errors */
    NULL,                       /* getParameterEntity */
    NULL,                       /* cdataBlock */
    NULL,                       /* externalSubset */
    XML_SAX2_MAGIC,             //
    NULL,
    startElementSAX,            /* startElementNs */
    endElementSAX,              /* endElementNs */
    NULL,                       /* serror */
};

NSMutableArray+StackAdditions.h

#import <Foundation/Foundation.h>

@interface NSMutableArray (StackAdditions)
- (id)pop;
- (void)push:(id)obj;
@end

NSMutableArray+StackAdditions.m

#import "NSMutableArray StackAdditions.h"

@implementation NSMutableArray (StackAdditions)
- (id)pop
{
    id lastObject = [[[self lastObject] retain] autorelease];
    if (lastObject)
        [self removeLastObject];
    return lastObject;
}

- (void)push:(id)obj
{
    [self addObject: obj];
}
@end

呼び出しコード

    LHParser *parser = [[LHParser alloc] init];
    [parser parse];

出力結果(実際は1行です)

{"response":
  {
    "members":
    [
      {
       "name":"平沢 唯",
       "gender":{"id":2,"title":"女性"},
       "birthday":"1991-11-27T00:00:00.000Z",
       "height":156.2,
       "weight":50.5,
       "abo":{"id":3,"title":"O"},
       "isVocal":true
      }
    ]
  }
}

0 件のコメント: