이전에 포스팅한 "NSXMLParser로 RSS 읽어오기"와 유사한 방법으로 구글 날씨 RSS를 가져오는 것을 만들어 보았습니다. 그런데 한글이 깨져나와 확인해 보니 문자셋이 euc-kr이었습니다. 문자셋을 확인하는 방법은 URLConnection의 델리게이트 메소드에서 확인할 수 있습니다.
  1. - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
  2.     NSLog(@"Encoding: %@", [response textEncodingName]);
  3. }

전송이 끝난 후에 아래와 같이 NSData를 euc-kr을 utf-8로 변환하여 사용할 수 있습니다. 변경된 data를 NSXMLParser의 initWithData의 인자로 사용하면 됩니다.
  1. - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
  2.     NSString *str = [[NSString alloc] initWithData:receiveData encoding:0x80000000 + kCFStringEncodingDOSKorean];
  3.     NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
  4.    
  5.     NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
  6. .
  7. .
  8. .
  9. }

한가지 이상한 점은 웹브라우저에서 확인하면 같은 URL이지만 utf-8로 넘어 옵니다. 아마 서버에서 헤더를 검사에서 각각 다른 인코딩으로 넘겨주는 것이 아닌가 하는 생각이 듭니다. 헤더의 항목들을 변경해서 보았는데 User-Agent를 설정해서 보내보니 euc-kr이 아닌 utf-8로 넘어 왔습니다.
  1.     NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.google.com/ig/api?weather=seoul"]];
  2.      
  3.     [request addValue:@"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ko; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2" forHTTPHeaderField:@"User-Agent"];
  4.  
  5.     xmlConnection = [[NSURLConnection alloc]
  6.                      initWithRequest:request
  7.                      delegate:self];


구글의 날씨 API에서는 이와 같이 User-Agent를 보내면 utf-8로 보내기때문에 위와같이 인코딩의 변환이 필요하지 않습니다. 아마 예측가능한 User-Agent는 utf-8로 보내고 그외에는 euc-kr로 보내는 것 같습니다. 이는 영문도 마찬가지이며 http://www.google.com/ig/api?weather=seoul와 같이 co.kr에서 com으로 변경하면 문자셋이 iso-8859-1로 넘어 옵니다. User-Agent를 추가하면 역시 utf-8로 넘어 옵니다.



이전부터 그냥 복사해서 올렸는데 오늘 보니 아래와 같이 나오는 건 너무 보기가 힘든 것 같아서, 예제코드를  Quick Highlighter를 사용해서 정리해 보았습니다.
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.google.com/ig/api?weather=seoul"]];
     
    [request addValue:@"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ko; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2" forHTTPHeaderField:@"User-Agent"];

    xmlConnection = [[NSURLConnection alloc]
                     initWithRequest:request
                     delegate:self];

보기도 조금 나아지지만 해당 클래스에 대한 애플의 문서로 바로 링크가 되는 것도 좋은 것 같습니다.


RSS를 읽어 오는 간단한 리더기를 만들어 보겠습니다. 맥에서는 NSXMLDocument란 편리한 클래스가 있지만 아이폰 SDK에는 포함되어 있지 않습니다. 그렇기 때문에 NSXMLParser를 사용해서 RSS xml을 읽어오는 간단한 샘플을 만들어 보겠습니다.

인터넷을 통해 데이터를 가져오는 부분은 이전  "NSURLConnection으로 웹페이지 내용 가져오기"란 포스팅을 참고 하시기 바랍니다. 여기서는 파싱하는 부분만 간단히 살펴보겠습니다.


1. NSXMLParser 생성
xml 데이터 파싱은 네트워크로 데이터 수신이 완료된 후 불려지는 connectionDidFinishLoading 메소드에서 아래와 같이 처리합니다.

NSXMLParser *parser = [[NSXMLParser alloc] initWithData:receiveData];

[parser setDelegate:self];
[parser parse];
[parser release];

NSXMLParser 오브젝트를 수신된 데이터가 저장된 NSData 타입의 receiveData를 인자로 초기화를 합니다. setDelegate 메소드로 현재 오브젝트를 NSXMLParser의 딜리케이트로 지정합니다. 지정된 오브젝트는 요소별로 파싱의 시작/종료와 파싱된 스트링을 받을 수 있는 메소드를 구현해야 합니다.

parse 메소드로 파싱이 시작됩니다. 파싱은 자동으로 처리되지 않으며, 각 단계별로 딜리게이트된 메소드를 구현하여 필요와 형식에 맞게 직접 처리해야 합니다.

2. Delegate 메소드 구현
NSXMLParser에는 많은 딜리게이트 메소드가 있지만 가장 중요하고 거의 반드시 구현해야될 메소드는 parser:didStartElement, parser:foundCharacters, parser:didEndElement입니다.

parser:didStartElement로 한 요소의 파싱이 시작됨을 알수 있습니다. parser:foundCharacters로 해당 문자열들이 넘어 옵니다. 토큰 단위로 넘어 오기 때문에 넘어 오는 문자열들을 계속 저장해야 합니다. parser:didEndElement가 실행되면 비로소 한 요소의 파싱이 끝난 것을 알 수 있습니다. 이 메소드에서 해당 요소에 따른 필요한 처리를 합니다.

1) 시작 메소드 구현
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
    if ([elementName isEqualToString:@"item"])
        elementType = etItem;
   
    [xmlValue setString:@""];
}

두번째 인자인 elementName으로 해당요소의 이름이 전달됩니다. 세번째와 네번째 인자는 네임스페이스와 관련된 uri와 전체이름이 전달됩니다. 만약 해당 xml이 네임스페이스를 사용한다면 이전에 [parser setShouldProcessNamespaces:YES];로 네임스페이스를 처리하도록 설정해야 합니다. NSXMLParser의 shouldProcessANamespace의 기본값은 NO 입니다.

마지막 인자인 attributeDict에는 해당 요소의 속성들이 전달됩니다. 만약 <item lang="ko"> 와 같이 되어 있다면 attributeDict 딕셔너리에 key가 'lang', value가 'ko'로 저장되어 전달됩니다.

여기서는 다른 인자들은 무시하고 item이란 이름의 요소가 시작될때 부터 데이터들을 저장하도록 요소이름이 item인지만 확인합니다. 그리고 xmlValue에 새로운 데이터를 저장하기 위해 이전에 저장된 값들을 초기화합니다.

2) 데이터 저장 메소드 구현
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    if (elementType == etItem) {
        [xmlValue appendString:string];
    }
}
토큰별로 넘어오는 문자열을 xmlValue에 저장합니다.

3) 종료 메소드 구현
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
    if (elementType != etItem)
        return;

    if ([elementName isEqualToString:@"title"]) {
        [currectItem setValue:[NSString stringWithString:xmlValue] forKey:elementName];
    } else if ([elementName isEqualToString:@"link"]) {
        [currectItem setValue:[NSString stringWithString:xmlValue] forKey:elementName];
    } else if ([elementName isEqualToString:@"description"]) {
        [currectItem setValue:[NSString stringWithString:xmlValue] forKey:elementName];
    } else if ([elementName isEqualToString:@"category"]) {
        [currectItem setValue:[NSString stringWithString:xmlValue] forKey:elementName];
    } else if ([elementName isEqualToString:@"pubDate"]) {
        [currectItem setValue:[NSString stringWithString:xmlValue] forKey:elementName];
    } else if ([elementName isEqualToString:@"item"]) {
        [xmlParseData addObject:[NSDictionary dictionaryWithDictionary:currectItem]];
    }
}

한 요소가 끝날때 호출됩니다. 여기서는 RSS의 title, link, description, category, pubData 항목들만 currentItem 딕셔너리에 저장합니다. 한 포스팅의 마지막 요소인 </item>일 경우에는 xmlParseData에 현재 딕셔너리를 추가합니다.

3. 테이블뷰 출력
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [xmlParseData count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
   
    static NSString *CellIdentifier = @"Cell";
   
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    }
   
    NSDictionary *dict = [xmlParseData objectAtIndex:indexPath.row];
    [[cell textLabel] setText:[dict objectForKey:@"title"]];
   
    return cell;
}

여기서 테이블뷰는 아무 동작을 하지않으며 xmlParseData에 저장된 해당 title만 출력합니다. 빌드 후 실행하면 아래와 같이 해당 RSS의 제목이 출력되는 것을 확인할 수 있습니다.

간단한 RSS 리더기를 구현해 보았습니다. 전체 소스는 아래의 압축파일을 다운로드 받아 확인하실 수 있습니다. 정확하고 자세한 내용은 아이폰 개발자 센터에서 제공하는 Introduction to Event-Driven XML Programming Guide for Cocoa 문서 또는 SeismicXML 샘플코드를 확인하시기 바랍니다.

'iOS' 카테고리의 다른 글

아이폰 OS 4  (8) 2010.04.09
NSXMLParser로 RSS 읽어오기  (21) 2009.08.05
인터페이스빌더 Table View Cell 사용하기  (0) 2009.06.25
cocos2d 개발환경 설정  (24) 2009.04.13
iPhone SDK 3.0 beta 2  (4) 2009.04.05
UITableView의 메모리 누수 현상  (4) 2009.02.05

이전에 올블로그 위젯을 올린 적이 있는데 RSS 리더기나 다른 좋은 관련 툴들이 많이 있어 업그레이드를 하지 않았습니다. 후에 Dashcode를 이용해 다시 만들어 볼려고 했는데 데모 사용기간이 지나 한동안 잊고 있다가 레오퍼드를 사용하면서 다시 Dashcode를 쓸 수 있게 되었습니다.
 
그래서 Dashcode를 사용하여 다시 만들어 볼려고 했는데, RSS 템플릿을 쓰니 다운로드 받고 설치하는 것 보다 그냥 만드는게 빠를 정도로 너무 쉽기 때문에 RSS 템플릿을 사용하여 위젯을 만든는 방법만 간단하게 설명할려고 합니다.

1. 프로젝트 생성
Dashcode를 처음 사용하시는 분들은 이전 관련 포스팅을 참조 하시면 도움이 되실 것 입니다. 먼저 Dashcode를 실행합니다. 아래와 같이 템플릿 선택화면이 나오면 'RSS 템플릿'을 선택하고 우측 하단의 'Choose' 버튼을 클릭합니다.

사용자 삽입 이미지


2. 기본 이미지/아이콘 변경
아래의 이미지를 다운로드 하여 사용하시거나 원하는 디자인으로 직접 배경과 아이콘 이미지를 사용하셔도 됩니다.

아래와 같이 좌측의 Default Image를 선택한 후에 배경파일(all.png)을 드래그 하여 가져다 놓습니다. 동일하게 하단의 Widget Icon을 선택한 후에 Icon.png 파일을 드래그 하여 놓습니다.

사용자 삽입 이미지


3. 전면(front) 설정
사용자 삽입 이미지

상단에서 front를 선택하면 좌측과 같이 front를 구성하고 있는 목록들을 볼 수 있습니다.

이중에서 fontImg, topRectangleShape, bottomRectangleShape, feedTitle을 선택한 후에 delete 키를 눌러 삭제합니다.

 







다시 배경 이미지 파일을 마우스로 드래그 해서 아래와 같이 위치 시킵니다.
사용자 삽입 이미지


4. RSS URL 설정
사용자 삽입 이미지
이제는 RSS를 가져올 주소를 설정해 보겠습니다. 위의 과정은 생략하시고 이 부분만 설정하시면 3초만에 자신만의 RSS 리더기를 만들 수 있습니다.

설정을 위해서 좌측과 같이 Provide RSS feed에서 RSS Properties 앞의 삼각형 버튼을 클릭합니다.



화면에서 Properties 하단의 Feed URL 입력 부분에 아래와 같이 올블로그의 '가장 많이 추천 받은 글'의 RSS주소를 입력합니다.
사용자 삽입 이미지


5. 실행
메뉴에서 'File/Save'를 클릭하여 프로젝트를 저장하고 'File/DeployWidget to Dashboard'를 클릭하여 제작한 위젯을 대쉬보드로 보냅니다. 이제 대쉬보드에서 아래와 같이 올블로그 위젯을 보실 수 있습니다.
사용자 삽입 이미지

사용자 삽입 이미지
하단의 설정버튼(i)을 클릭하면 좌측과 같이 출력될 내용의 길이를 설정 할 수 있습니다.


사용자 삽입 이미지

녹스퀘스트님의 '위젯이 동작하지 않는다'는 댓글을 보고 확인해 보니,
이전에 올린 위젯이 현재 맥 OS X에서 동작을 하지 않아 수정하여 다시 올립니다. 대쉬보드를 거의 사용하지 않았더니 언제부터 동작하지 않았는지는 잘 모르겠습니다.

올블로그의 RSS는 변경된 것 같지 않고 확실하지는 않지만 아마 OS X에서 보안 관련 패치가 되면서 동작하지 않은 것 같습니다. 방식을 변경해서 동작하도록 만들어서 다시 업로드 합니다. 필요하신 분들은 다시 설치 하셔야 할 것 같습니다. 아래의 파일을 다운로드 받으신 후에 압축을 푸시고 위젯 아이콘을 더블클릭하시면 다시 설치됩니다. 불편을 드려 죄송합니다.

'습작 소프트웨어' 카테고리의 다른 글

광고 차단툴 - AntiAD  (7) 2008.04.18
파일명 일괄 변환 툴  (2) 2008.04.11
맥 OS X용 올블로그 실시간 인기글 위젯 수정본  (2) 2008.02.26
티돌이(티스토리 알리미) 윈도우 버젼  (4) 2008.02.06
티돌이 1.0B  (24) 2008.01.07
티돌이 0.7B  (2) 2007.11.08

> OS: Unix 계열 (확인:Linux, OS X)
> Lang: C++
> 마지막 변경: 2007-12-14

블로그에서 RSS를 가져오는 간단한 클래스입니다. 공개되어 있는 가벼운 xml 라이브러리를 사용할려고 했는데, 데이터가 크면 오류가 나서 간단하게 만들어 보았습니다. 소스파일을 하나로 만들려다 보니 다른 라이브러리를 쓰지 않았습니다. 사용 가능한 socket, string, list 클래스들을 활용하시면 간단해 질 것입니다.

티스토리, 다음, 네이버에서 몇 개의 블로그들을 테스트를 해 보았습니다. 우선 아래의 파일을 다운로드 받으신 후에 압축을 풀면 소스 파일(brss.cpp, brss.h), makefile, 테스트 용 test_brss.cpp 네개의 파일이 있습니다.

헤더파일 보기


make를 하시면 테스트 파일이 컴파일 되며, test_brss.cpp는 아래와 같습니다.

#include <stdio.h>
#include "brss.h"

int main(int argc, char* argv[])
{
    if(argc != 3)
    {  
        printf("\nUSAGE: test_brss [url] [domain]\n");
        return 0;
    }  

    char aszBuff[128];
    CBlogRss* pRSS = new CBlogRss;

    if(pRSS->GetData(argv[1], argv[2]) == false)
    {  
        printf("Fail to get data[%d]\n", pRSS->GetErrorLine());
    }  
    else
    {  
        T_BlogInfo* pBlog = pRSS->GetBlogInfo();

        printf("Blog %s, %s, %s, %d [E:%d]\n",
            pBlog->aszTitle, pBlog->aszLink, pBlog->aszDesc,
            pRSS->GetPostCount(), pRSS->GetErrorLine());
    }  

    for(int i = 0; i < pRSS->GetPostCount(); i++)
    {  
        T_BlogPost* pPost = pRSS->GetBlogPost(i);

        sprintf(aszBuff, "%d-%02d-%02d %02d:%02d",
            pPost->tTime.nYear, pPost->tTime.nMonth, pPost->tTime.nDay,
            pPost->tTime.nHour, pPost->tTime.nMin);

        printf(" > post %d: %s (%s) @ %s\n", i+1, pPost->aszTitle,
            pPost->aszLink, aszBuff);
        //printf(" > %s\n", pPost->pszDescription);
    }  

    delete pRSS;

    return 0;
}

테스트를 실행 하실려면 사용법은 아래와 같습니다.
> ./test_brss [blog_url] [path]

[blog_url] 에서 접속할 블로그 도메인을 'http://'나 주소 맨뒤에 '/'를 생략하고 입력합니다. [path]는 도메인을 제외한 주소입니다. 아래의 예를 보시면 쉽게 이해할 수 있을 것입니다.
사용자 삽입 이미지


소스를 보시면 아시겠지만 제가 만드는 것들이 다 그렇듯이 아주 아껴서 주석을 넣고, 필요한 만큼 대충 파싱을 하고, 대충 오류를 확인합니다. 아직  몇 개의 블로그 밖에 테스트를 하지 못했습니다. 버그나 사용시 오류가 있으면 댓글로 알려 주시면 감사하겠습니다.

가끔 뭘 올리기는 하는데 도움을 드릴려고 올리는 것인지, 테스트를 부탁 드리는 것인지 저도 혼동이 오네요. 당연한 이야기겠지만 사용하는데는 어떠한 제한도 없습니다.

'기타' 카테고리의 다른 글

프로그래밍 언어 인기 순위  (21) 2008.01.09
OS X APM 설치툴 - MAMP  (6) 2007.12.27
블로그 RSS 읽어오는 C++ class  (7) 2007.12.14
OS X용 정규 표현식 테스트 어플리케이션 - Reggy  (1) 2007.12.12
cocoa 이름의 유래  (6) 2007.11.30
맥용게임 Doukutsu  (3) 2007.11.21

요새 저희 가족 홈페이지를 만들고 있습니다. 한가한 토요일 오후라 블로그 RSS를 가져 오는 루틴을 만들고 있었는데, 실컨 하다가 php 홈페이지에서 문서를 보니 SimpleXMLElement라는 방법이 있었습니다. PHP5부터 지원한다고 하네요.

<?
$rss = file_get_contents("http://www.cocoadev.co.kr/rss");
$xml = new SimpleXMLElement($rss);

print "Blog: <a href='".$xml->channel[0]->link."'>";
print $xml->channel[0]->title."</a><br>";

print "<ul>";
foreach($xml->channel[0]->item as $item)
{
    print "<li><a href='".$item->guid."'>".$item->title."</a> ";
print "(".$item->pubDate.")</li>";
}
print "</ul>";
?>

사용자 삽입 이미지

편한 방법을 찾은 건 다행인데, 왠지 허무하기도 하고 만들 의욕이 그냥 팍 다운되네요. 컴퓨터는 그만 끄고 아들녀석이랑 놀아 줘야 겠습니다.