iPhone App2011.09.21 19:33

일단 이전에 만든 XML은 진짜 기본적인 Parser인거 같다... 내가 실제로 XML Parser를 사용함으로써 수많게 유용하고 유연하게

바꾸었다. 어떤 XML이 들어와도 읽을수 있게(이건 오버인가?..)

일단 발전된 상황이라면 모든 XML을 Dictionary로 관리하기 때문에 유연하게 바뀌었다.

XML Parser에서 initWithContentsOfURL을 이용하면 네트워크가 안될시나 URL이 잘못되면 어떠한 예외를 던지지 못하고 멈추는 현상이 있어서

일단 나는 파싱하기전에 그 URL이 XML을 정상적으로 리턴하는지 검증하는것이 필요 하였다.

원리는 이렇다.

일단 XML받는부분을 Background에서 실행한다.

[self performSelector:@selector(getTime) withObject:nil];


그리고 아래 예제처럼하였다.


- (void) getTime

{

NSString *serverIP = [[NSUserDefaults standardUserDefaults] objectForKey:@"ServerIP"];

NSURL* url =[NSURL URLWithString:[[NSString stringWithFormat:TimeElementsURL,serverIP]stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];

  

XmlManager* xmlManager= [[XmlManager alloc] init];

  

NSData* xmlData = [xmlManager GetXmlData:url isPostData:NO];

  

if(xmlData != nil) {

NSMutableDictionary *xmlDict= [xmlManager GetXmlDictDataByData:xmlData elementFile:@"TimeElements.plist"];

NSUserDefaults *timeData = [NSUserDefaults standardUserDefaults];

[timeData setObject:xmlDict forKey:@"TimeData"];

[timeData synchronize];

  

//시간 설정

currentTime = [[xmlDict objectForKey:@"TimeSec"] intValue];

int secs = currentTime % 60;

int mins = (currentTime % 3600) / 60;

int hours = currentTime / 3600;

timeRemainLabel.text = [NSString stringWithFormat:@"%.2d:%.2d:%.2d",hours, mins, secs];

  

}

else {

NSLog(@"time data 얻기 실패");

}

  

  

[xmlManager release];

}

소스는 거창하지만 실제로 보면

NSData* xmlData = [xmlManager GetXmlData:url isPostData:NO];  


이부분을 보면 된다. 내가 구현한 xmlManager는 파일첨부 할것이다.

저렇게 일단 XML을 파싱하기전에 파싱데이터를 가져온다.

만약 저기서 data를 못받으면(data == nil) 실패했다고 말해준다.

이것이 바로 검증인것이다.
실패했는지 성공했는지 결과를 알수 있다는것이다.

성공하면 이렇게 파싱한다.
NSMutableDictionary *xmlDict= [xmlManager GetXmlDictDataByData:xmlData elementFile:@"TimeElements.plist"];
 
그리고 elementFile은 plist를 만들어서



 
이렇게 만든다. 

NSManager에 GetXmlData 소스는 이렇다.
-(NSData*)GetXmlData:(NSURL*) url isPostData:(BOOL) isPost

{

    NSMutableURLRequest* feedRequest = [NSMutableURLRequest requestWithURL:url cachePolicy: NSURLRequestUseProtocolCachePolicy timeoutInterval:10];

    if(isPost == YES)

    {

        [feedRequest setHTTPMethod:@"POST"];

    }

    NSError* theError = nil;

NSData* xmlData = [NSURLConnection sendSynchronousRequest:feedRequest returningResponse:nil error:&theError];     

//    NSString *str = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];     //확인용    


    if(theError != nil || xmlData ==nil)

    {        

        //네트워크가 잘못됬거나 data 가져 오지 못했을 경우 nil return

        return nil

    }

    else

    {

        return xmlData;

    }


2011년 10월 12일  추가

NSXmlparser버그로 인해서 첫글자가 숫자일경우 짤려서 foundCharacters 두번 호출되는 버그가 있었다.

Xml에 들어온글중에 원래 xml의 element안에 있는글이

"
1004 story 들어오기. 나도 글을 쓰고 싶을 뿐이다 ㅠㅠ"

라는 글이였는데 
 

"에 들어오기. 나도 글을 쓰고 싶을 뿐이다 ㅠㅠ"

만 나오는것이였다 보니까 1004 story라는 글이 짤려 있었다.

그래서 보니 첫글자가 숫자가 들어가니 아래 로그 처럼 나뉘어서 두번에 걸쳐서 foundCharacters delegate가 호출 되었다.

 

2011-10-12 11:09:01.447 나도 작가다[1151:207] CmtContent

2011-10-12 11:09:01.447 나도 작가다[1151:207] 1004 story  , CmtContent

2011-10-12 11:09:01.448 나도 작가다[1151:207] count = 2, index= 0 

2011-10-12 11:09:01.448 나도 작가다[1151:207] 들어오기. 나도 글을 쓰고 싶을 뿐이다 ㅠㅠ , CmtContent

그래서 아래처럼 수정하였다.

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string

    //trim 쓰자면 이걸 쓰고 안하면 그냥 string 값을 넣어도 된다.

    NSCharacterSet *characterSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];

    NSString *trimmedValue = [string stringByTrimmingCharactersInSet:characterSet];

    

     if([trimmedValue length] == 0)

    {

        return;    

    }

    

    id element = [elementStack lastObject];    

    

    if([element isKindOfClass:[NSString class]])

    {

        NSUInteger count = [elementStack count];

        NSUInteger index = count -2;

        id parentElement = [elementStack objectAtIndex:index];                               

        

        NSMutableDictionary *dataInfoDict = (NSMutableDictionary*)parentElement;        

        

        //NSXmlparser버그로 인해서 첫글자가 숫자일경우 짤려서 foundCharacters 두번 호출된다.

        if([element isEqualToString:@"CmtContent"])

        {

            if([[dataInfoDict allKeys] containsObject:element])

            {                

                NSString* previousString = (NSString*)[dataInfoDict objectForKey:element];

                trimmedValue =[previousString stringByAppendingString:trimmedValue]; 

            }

        }

        

        [dataInfoDict setObject:trimmedValue forKey:element]; 

    }    

}


요약하자면 이전에 dictionary를 검색해서 이미 값이 있으면 이전 값과 현재 값을 합쳐서
저장한다.

만약 이전값이 없으면 무시되니깐 정상적으로 작동한다.
 
 
최신 xmlParser는 올려놓겠다

 

Posted by 동동(이재동)
iPhone App2011.09.19 11:01

보안이 필요한 데이터를 받고 줘야 할때 (로그인 인증같은거)

GET으로 하게 되면 보안이 치명적일수가 있다.

그래서 Post로 데이터를 받아서 XML을 파싱할려고 했지만

기본 NSXMLParser에는 Get방식으로는 받아서 바로 파싱할수 있으나 Post는 없었다.

그래서 할수 없이 NSURLConnectino을 이용하여 일단 url로부터 Data를 받은후 그 데이터를 NSXMLParser에 넣는 방식으로 처리해야 한다.


//url encoding

NSString *urlString =[NSString stringWithFormat:@"http://주소/MemberSelect?id=%@&pw=%@",_userID.text,_userPW.text];

NSString *escapedUrl = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

NSURL *xmlUrl =[NSURL URLWithString:escapedUrl];


이렇게 url을 세팅하고


  NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:xmlUrl cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];

[request setHTTPMethod:@"POST"];

[NSURLConnection connectionWithRequest:request delegate:self];

post로 보내면


- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data

{

NSString *returnString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

  

NSLog(returnString);


XmlManager* xmlManager= [[XmlManager alloc] init];

NSMutableDictionary *loginInfoData= [xmlManager GetXmlDictDataByData:data elementFile:@"LogInElements.plist"];

}


이 delegate를 통해서 post로 보낸 데이터의 return값을 받을수 있다.


그래서 로그인이 성공하면 성공이라는 메세지를 xml로 보내주게 되고 그밑에 xml을 파싱했다.




Posted by 동동(이재동)
iPhone App2011.07.27 11:19

저번에 XML에 파싱 부분을 올렸었다 소스를 올렸지만 그렇게 되면 메모리 관리가 제대로 안돼서 다시 만들었다.

일단 지난 번 코드를 보자

-(NSMutableArray*) parseContent:(NSURL*) url

{

NSXMLParser* xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:url];

[xmlParser setDelegate:self];

  

_hugeBoardDataArray =[NSMutableArray arrayWithCapacity:1024];

_elementStack = [NSMutableArray arrayWithCapacity:1024];

[xmlParser parse];

return _hugeBoardDataArray;

}

@end

보면 엉망이다 일단 arrayWithCapacity를 마땅히 쓸필요가 없고 _hugeBoardDataArray와 ElementStack을

- (id) init

{

if((self = [super init]) != nil)

{

_hugeBoardDataArray = [[NSMutableArray alloc] init];

_elementStack = [[NSMutableArray alloc] init];

}

  

return self;

}

생성자에서 이렇게 처리 하였다.


그리고 이부분은

-(NSMutableArray*) parseContent:(NSURL*) url

{

NSXMLParser* xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:url];

[xmlParser setDelegate:self];

[xmlParser parse];

[xmlParser release];

  

return [NSMutableArray arrayWithArray:_hugeBoardDataArray];

}

이렇게 고쳤다.

return 부분이 포인트다 무언가를 리턴할때 꼭 autoRelase로 만들어서 리턴해야 한다는 것을 깨달았다.

arraywitharray를 쓴이유다 만약 그냥 _hugeBoardDataArray를 리턴하게 되면 release를 해줘야 하는 타입임으로

저렇게 넣어서 했다.


그리고 data쪽도 new로 받았었는데

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict

{

if([elementName isEqualToString:@”HugeBoardInfo”] == TRUE)

{

HugeBoardData *data = [[HugeBoardData alloc] init];

[_elementStack addObject:data];

[data release];

}

else if([elementName isEqualToString:@”idx”] || [elementName isEqualToString:@”Name”] || [elementName isEqualToString:@”Title”] || [elementName isEqualToString:@”Description” ] || [elementName isEqualToString:@”Date”])

{

NSString *element = [NSString stringWithString:elementName];

[_elementStack addObject:element];

}

}

저렇게 alloc로 받도록 수정하였다.


마지막으로

-(void) dealloc

{

[super dealloc];

[_hugeBoardDataArray release];

[_elementStack release];

}

이렇게 메모리 해제는 꼭 해줘야 한다.




Posted by 동동(이재동)
iPhone App2011.07.21 18:15

아이폰에서 XML을 파싱하기 위해서는 여러가지 방법이 있다.

라이브러리를 이용하는 방법(touchXML,KISSXML등등)과 NSXMLParser를 이용하는 방법이 있다.

아이폰이 아닌 그냥 맥개발에서는 NSXMLDocument가 있어서 편하게 쓸수 있다고 하는데 아무튼.. 그냥 힘든방법으로 가자.

c#으로 개발했을때는 시리얼라이즈가 있어서 클래스만 만들면 자동으로 xml을 만들고 파싱하고 아주 편했다.

하지만 Iphone Objective-C는........ㅠ.ㅠ

일단 WCF Rest서비스를 만들어서 DB내용을 XML로 동적으로 뿌려주기 위한 서비스를 만들었다.

그래서 파싱할 xml은 이렇다.

<ArrayOfHugeBoardInfo xmlns="http://schemas.datacontract.org/2004/07/HugeBoardService" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">

<HugeBoardInfo>

<Date>2010-02-02T00:00:00</Date>

<Description>hi</Description>

<Name>leejaedong</Name>

<Title>hi</Title>

<idx>1</idx>

</HugeBoardInfo>

일단 이 데이터를 저장할 클래스부터 만들자 C#이랑 비슷하다 여기까지는

@interface HugeBoardData : NSObject {

NSString* _idx;

NSString* _name;

NSString* _title;

NSString* _description;

NSString* _date;

}


@property(copy) NSString* idx;

@property(copy) NSString* name;

@property(copy) NSString* title;

@property(copy) NSString* description;

@property(copy) NSString* date;



@end

헤더에 이렇게 넣고

@implementation HugeBoardData

@synthesize idx= _idx;

@synthesize name= _name;

@synthesize title= _title;

@synthesize description = _description;

@synthesize date= _date;

@end


구현부에 구현을 하자

그다음 일단 Xml을 파싱을 전문적으로 하는 class를 만들자.

@interface HugeBoardXmlParser : NSObject<NSXMLParserDelegate> {

NSXMLParser *parser;

NSMutableArray *_hugeBoardDataArray;

NSMutableArray *_elementStack;

}


@property (nonatomic,retain) NSXMLParser *parser;

-(NSMutableArray*)parseContent: (NSURL*) url;

@end


이름을 HugeBoardXmlParser라 만들고 NSXMLParser 델리게이트를 참조 하였다.그리고 XML을 parser할 NSXMLParser와 xml파일의 모든 데이터를 저장할 hugeboardDataArray그리고 elementStack이라고 해서 한줄한줄 읽을때마다 저장해서 이게 어떤 노드에 있고 어떤값지를 임시로 저장해놓는 array이다

#import "HugeBoardXmlParser.h"

#import "HugeBoardData.h"


@implementation HugeBoardXmlParser


@synthesize parser;


- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict

{

if([elementName isEqualToString:@"HugeBoardInfo"] == TRUE)

{

HugeBoardData *data = [HugeBoardData new];

[_elementStack addObject:data];

}

else if([elementName isEqualToString:@"idx"] || [elementName isEqualToString:@"Name"] || [elementName isEqualToString:@"Title"] || [elementName isEqualToString:@"Description" ] || [elementName isEqualToString:@"Date"])

{

NSString *element = [NSString stringWithString:elementName];

[_elementStack addObject:element];

}

}


- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName

{

if([elementName isEqualToString:@"HugeBoardInfo"] == TRUE)

{

HugeBoardData *data =(HugeBoardData*)[_elementStack lastObject];

[_hugeBoardDataArray addObject:data];

[_elementStack removeLastObject];

}

else if([elementName isEqualToString:@"idx"] || [elementName isEqualToString:@"Name"] || [elementName isEqualToString:@"Title"] || [elementName isEqualToString:@"Description" ] || [elementName isEqualToString:@"Date"])

{

[_elementStack removeLastObject];

}

  

}


- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string

{

//trim 쓰자면 이걸 쓰고 안하면 그냥 string 값을 넣어도 된다.

NSCharacterSet *characterSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];

NSString *trimmedValue = [string stringByTrimmingCharactersInSet:characterSet];

if([trimmedValue length] == 0)

{

return;

}

  

id element = [_elementStack lastObject];

if([element isKindOfClass:[NSString class]])

{

NSUInteger count = [_elementStack count];

NSUInteger index = count -2;

id parentElement = [_elementStack objectAtIndex:index];


HugeBoardData *data = (HugeBoardData*)parentElement;

if([element isEqualToString:@"idx"])

{

data.idx = trimmedValue;

}

else if([element isEqualToString:@"Name"])

{

data.name = trimmedValue;

}

else if([element isEqualToString:@"Title"])

{

data.title = trimmedValue;

}

else if([element isEqualToString:@"Description"])

{

data.description = trimmedValue;

}

else if([element isEqualToString:@"Date"])

{

data.date = trimmedValue;

}

}

}

-(NSMutableArray*) parseContent:(NSURL*) url

{

NSXMLParser* xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:url];

[xmlParser setDelegate:self];

  

_hugeBoardDataArray =[NSMutableArray arrayWithCapacity:1024];

_elementStack = [NSMutableArray arrayWithCapacity:1024];

[xmlParser parse];

return _hugeBoardDataArray;

}

@end


이것은 구현부이다.
하나씩 알아보자.일단 파싱을 하기 위해서 필요한 3가지 델리게이트가 있다.첫째 이부분이다.

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict

{

if([elementName isEqualToString:@"HugeBoardInfo"] == TRUE)

{

HugeBoardData *data = [HugeBoardData new];

[_elementStack addObject:data];

}

else if([elementName isEqualToString:@"idx"] || [elementName isEqualToString:@"Name"] || [elementName isEqualToString:@"Title"] || [elementName isEqualToString:@"Description" ] || [elementName isEqualToString:@"Date"])

{

NSString *element = [NSString stringWithString:elementName];

[_elementStack addObject:element];

}

}

초반 엘리먼트를 읽는다. 우린 이것을 일단 모두 저장한다. 어디에? elementStack에

그다음 호출되는것은 이거다

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string

{

//trim 쓰자면 이걸 쓰고 안하면 그냥 string 값을 넣어도 된다.

NSCharacterSet *characterSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];

NSString *trimmedValue = [string stringByTrimmingCharactersInSet:characterSet];

if([trimmedValue length] == 0)

{

return;

}

  

id element = [_elementStack lastObject];

if([element isKindOfClass:[NSString class]])

{

NSUInteger count = [_elementStack count];

NSUInteger index = count -2;

id parentElement = [_elementStack objectAtIndex:index];


HugeBoardData *data = (HugeBoardData*)parentElement;

if([element isEqualToString:@"idx"])

{

data.idx = trimmedValue;

}

else if([element isEqualToString:@"Name"])

{

data.name = trimmedValue;

}

else if([element isEqualToString:@"Title"])

{

data.title = trimmedValue;

}

else if([element isEqualToString:@"Description"])

{

data.description = trimmedValue;

}

else if([element isEqualToString:@"Date"])

{

data.date = trimmedValue;

}

}

}


실제 Value값을 여기서 얻을수가 있는데 아까 처음에 _elementStack에 저장했던 값을 분석해서 HugeBoardData라는 아까 만든 저장형 클래스에 담는다. trimmedValiue대신에 string을 넣어도 된다.
마지막으로 호출되는것은 이것이다.

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName

{

if([elementName isEqualToString:@"HugeBoardInfo"] == TRUE)

{

HugeBoardData *data =(HugeBoardData*)[_elementStack lastObject];

[_hugeBoardDataArray addObject:data];

[_elementStack removeLastObject];

}

else if([elementName isEqualToString:@"idx"] || [elementName isEqualToString:@"Name"] || [elementName isEqualToString:@"Title"] || [elementName isEqualToString:@"Description" ] || [elementName isEqualToString:@"Date"])

{

[_elementStack removeLastObject];

}

}


여기서하는것은 아까 foundCharacters에서 저장한 HugeBoardData를 _HugeBoardDataArray에 저장한다. 여기까지가 한텀이 끝난것이다. 이제 아마 같은 방식으로 계속 한텀한텀씩 저장해서 모든 xml데이터를 _hugeBoardDataArray에 저장할것이다.한텀이 끝났으면 다음텀을 위해서 _elementStack을 삭제한다. 이렇게 3개의 델리게이트 메소드가 계속 호출되면서 반복된다.
이제 이 모든기능을 작동되게 만드는 외부에서 호출 당하는 메소드를 보자

-(NSMutableArray*) parseContent:(NSURL*) url

{

NSXMLParser* xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:url];

[xmlParser setDelegate:self];

  

_hugeBoardDataArray =[NSMutableArray arrayWithCapacity:1024];

_elementStack = [NSMutableArray arrayWithCapacity:1024];

[xmlParser parse];

return _hugeBoardDataArray;

}


여기보면 xmlParser를 작동되게 하고 모든 xmlData를 포함하는 _hugeBoardDataArray를 리턴한다.
그래서 외부에서 사용할려면

NSURL *XMLURL =[NSURL URLWithString:@"http://192.168.10.3:9090/hugeboardService/getboardinfo"];

HugeBoardXmlParser* parser = [[HugeBoardXmlParser alloc] init];

NSMutableArray *hugeboardArr = [parser parseContent:XMLURL];


이렇게 하면 된다. 그럼 hugeBoardArr에 모든 데이터가 저장될것이다.



참고 한 사이트 : http://kiipos.delimount.net/1084

Posted by 동동(이재동)
Windows Phone 72011.02.18 17:04

이건 wp7쪽에 써야 되는지 모르겠다.

 

html이나 여러 쪽에도 적용되는것인데

 

Name    Character    Code
quot    "                  &quot;
amp    &                  &amp;
apos    '                &apos;
lt    <                     &lt;
gt    >                   &gt;

 

이렇게 쓰인다

 

근데 엔터키를 의미하는 \n을 xml에 입력하면 string값으로 \\n이 넘어오는데

 

이건 어떻게 해야 할지 몰라서

 

DescriptionTextBlock.Text = param.Replace("\\n", "\n");
 
그냥 이렇게 강제로 바꾸었다.
 
나중에 알면 수정해야겠다.

 

참조 : http://kimchulgoon.blog.me/30044443181

Posted by 동동(이재동)