iPhone SDK 3.0 XML Parsing with NSXMLParser

iphone sdk 3.0으로 XML데이터를 파싱하다가 몇가지 사실을 알게됬다 iphone sdk 2.x 대로 개발된 XMLParser Class를 사용하여 빌드를 해 보려는데 에러가 발생해 빌드되지 않았다.

- (void)parseContents:(NSURL *)url  {
  NSXMLDocument *xmlDoc = [[NSXMLDocument alloc] initWithContentsOfURL:url options:1 << 10 error:nil];
  [self parseXMLData:[xmlDoc XMLData]];
  [xmlDoc release];
}

간단한 위 코드로 파싱하려고 하면 NSXMLDocument undeclared … 에러 발생

SDK API를 찾아보아도 NSXMLDocument Class는 찾을 수 가 없었다.

sdk 버전을 2.0으로 하면 에러없이 잘 됬다. 하지만 sdk3.0 에만 있는 기능을 사용하기 위해서는 에러 발생을 어떻게든 없애야 해서 다시 시도.

-(void)parseXMLUrl:(NSURL *)url {
  NSXMLParser*p =[NSXMLParser alloc];
  [p initWithContentsOfURL:url];
  [p setShouldProcessNamespaces:YES];
  [p setDelegate:self];
  [p parse];
  [p release];
}

다시 시도해도 실패. 이유는 XML 데이터가 규격을 완전하게 지키지 않았다. 받아온 XML 자체 문제가 있으니 교정을 해야 한다.

XML 태그 안에 특수문자가 있어 CDATA로 감싸야 되는 상황

NSString *xmlString = [NSString stringWithContentsOfURL:url];
NSData *xmlData = [[self sanitizeXmlString:xmlString] dataUsingEncoding:NSUTF8StringEncoding];

목표 위 코드를 작동하도록 만들기.

  • 교정함수를 만들어서 잘못된 태그 문자열 데이터를 교정해 XML 을 파싱할 수 있게 전처리 한다.
  • NSData 의 dataUsingEncoding 함수를 사용해서 NSString 을 NSData 로 변환 (Charset에 맞게)
  • NSString 의 stringWithContentsOfURL 로 파일 내용을 읽어오기

결국 안을 CDATA로 감싸는 함수

stringWithContentsOfURL 는 동기함수. 실행시 쓰레드로 잡고 실행완료후 다음코드 진행, 비동기로 Delegate 를 지정하지 않는다. 파일이 크거나, storage interface IO 느릴때, 외부 파일등 로딩이 느리면 블럭자체를 멀티스레드 비동기 처리해야함. 일단은 간단하게 구현만 하자.

교정 함수

-(NSString*) sanitizeLinkTag:(NSString*)string {
  NSString* sanitizeLinkTag =[string stringByReplacingOccurrencesOfString:(NSString*)@"</link>" withString:(NSString*)@"]]></link>"];
  sanitizeLinkTag = [sanitizeLinkTag stringByReplacingOccurrencesOfString:(NSString*)@"<link>" withString:(NSString*)@"<link><![CDATA["];
  return sanitizedString;
}

NSString 의 strignByReplacingOccurencesOfString 은 첫번째 파라메터가 target, 두번째 withString이 replace될 String

프로시져 형식의 메쏘드가 아니라 새 String을 리턴한다. NSString을 인스턴스화 해서 해보진 않았다. API상에 그냥 return new … 이렇게 써있어서. 어찌되었든 이런 특별한 경우가 아닌 경우 - 정확히 작성된 XML을 가져올 경우- 에는 XML파싱을 위해 XML의 구조를 “먼저” 알고 있어야 한다. URL일 경우에는 브라우져로, 파일인 경우에는 읽어서 판단.

NSXMLParser에서 중요한 4개의 메쏘드가 parsing작업을 담당하는데, 이는 트리구조를 직접 만들어 주지 않는다. AS 3.0이나 뭐 여타 라이브러리들의 TREE구조 혹은 Hash Object형식으로 만들어 주지 않는다. 어떻게 해주는지는 밑에 설명

NSXMLParser 의 delegate 메쏘드들 중에 가장 중요한 네 가지

-(void)parser:(NSXMLParser*)parser parseErrorOccurred:(NSError*)parseError
-(void)parser:(NSXMLParser*)parser didStartElement:(NSString*)elementName namespaceURI:(NSString*)namespaceURI qualifiedName:(NSString*)qualifiedName attributes:(NSDictionary*)attributeDict
-(void)parser:(NSXMLParser*)parser didEndElement:(NSString*)elementName namespaceURI:(NSString*)namespaceURI qualifiedName:(NSString*)qName
-(void)parser:(NSXMLParser*)parser foundCharacters:(NSString*)string

첫번째 parseErrorOccured 는 파싱중 에러발생시에, 두번째 didStartElement는 <>로 감싼 태그를 만날 때 세번째 didEndElement는 </>로 감싼 태그를 만날 때 네번째 foundCharacters 는 didStartElement 와 didEndElement 후 에 String을 반환한다.

중요한 것은 네번째 foundCharacters인데 이것은 didStartElement 이후, didEndElement 이후에 delegate된다 이것이 무엇이냐 didStartElement 이후 delegate되는 경우에는 파라메터로 엘리먼트안의 데이터가 넘어온다. 그러나 didEndElement 이후 delegate되는 경우에는 파라메터로 무언가 (== nil 혹은 == @“” 가 아님.) 넘어오는데 여튼 쓸수 없는 값이다 (\n인가)

그래서 delegate method를 작성할때 많은 예제들에서 사용하는 방식이 xmlValue를 전역 변수 선언해 놓고 didStartElement안에서 xmlValue를 빈 값으로 리셋 xmlValue = @“”; 이런식 그리고 foundCharacters 에서 xmlValue = string 으로 할당 didEndElement에서 xmlValue를 알맞은 곳에 사용 하는 형식으로 작성해야 한다.

foundCharacters안에 string을 직접 알맞은 곳에 사용하다간 didEndParsing 이후 delegate되는 foundCharacters의 이상한 string값이 잘못 사용되어 지기 때문이다.

이것은 SDK 2.x대의 NSXMLDocument를 사용한 구조에서는 발생하지 않는다고 한다. 2.x에서 NSXMLDocument를 사용한 경우에는 foundCharacters 가 한번만 호출.

XML데이터가 잘못되어 SDK3.0에서도 한번만 호출되더라도 이런 경우가 발생 할 수 있기 때문에

전역 xmlValue를 update하는 방식으로 XML data를 관리하는것이 낫겟다는 생각이다.

그리고 Tree구조가 아니기 때문에

didStartElement에서 elementName으로 얻어오는 tag name을 가지고 판단하여

알맞은 NSMutableDictionary나 array나 뭐 구조체처럼 쓰이는 Class등에 데이터를 migration할수 있겟다.

NSMutableDictionary 구조가

car

  • brand - model - color - grade

이런식이라면

XML데이터가 트리구조로 되어있던 뭔구조로 되어있던간에

tag name이 model이면 didEndParsing 에서 dictionary의 model key값에다가 직접 set을 해줘야된다.

color든 grade 키값이던간에 평면적으로 넘어오는 데이터를 트리구조로 변환해야되는 것.