IBM®
메인 컨텐츠로 가기
    Korea [국가변경]    이용약관
 
 
   
        제품    서비스 & 솔루션    고객지원 & 다운로드    회원 서비스    
메인 컨텐츠로 가기

한국 developerWorks  >  오픈 소스 | XML  >

XPath를 사용하여 PHP 웹 사이트에 구글 캘린더 이벤트 넣기

XML 파싱 API로 XPath와 SimpleXML는 가독성과 상세함을 균형있게 제공한다

developerWorks
문서 옵션

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.

샘플 코드

영어원문

영어원문


제안 및 의견
피드백

난이도 : 중급

PJ Cabrera, 소프트웨어 엔지니어, 자유기고가

옮긴이: 박재호 이해영 dwkorea@kr.ibm.com

2008 년 10 월 07 일

구글 캘린더를 비롯하여 여러 온라인 캘린더 프로그램은 간단하면서도 중앙집중화된 시스템을 제공합니다. 다시 말하면, 캘린더 프로그램이 제공하는 웹 사이트에서 온라인 공동체는 이벤트 캘린더를 관리하고 공동체 구성원은 다가올 이벤트 정보를 얻을 수 있습니다. 하지만 많은 공동체가 이벤트 달력을 자기네 웹 사이트나 포럼이나 블로그에 표시하는 쪽을 선호합니다. 그래서 온라인 캘린더 프로그램에서 이벤트 정보를 복사해다 자기네 웹 사이트에 붙여넣습니다. 결국 같은 내용을 여기저기 복사함으로써 온라인에서 이벤트를 관리하는 효율성이 사라집니다. 구글 캘린더는 이런 문제를 멋지게 해결하는 통합 API(Application Program Interface)를 제공합니다. 이 기사에서는 XPath를 사용하여 PHP 웹 사이트에 구글 캘린더 자료를 가져와 표시하는 방법을 소개합니다.

여러 해 전 웹 개발자 프리랜서로 일하면서 나는 PHP 웹 포털 사이트를 하나 만들었다. (당시 내가 살던 동네에서 아주 흔했던) 특정 자동차 모델 소유자와 팬들을 위한 공동체 사이트였다(흔히 자동차 클럽이라고도 한다). 내게 연락하기 몇 해 전에 클럽 운영진 중 몇 명이 기초적인 웹 기술을 익혀 나름대로 사이트를 만들었다. 그런데 사이트가 점차 커지면서 페이지 수가 늘어났고 슬슬 낡아가기 시작했으며, 이벤트 캘린더 정보도 여기저기 흩어졌다. 원래 사이트를 만든 이유가 일반인들과 공동체 구성원들에게 클럽 활동을 알리려는 목적이었므로 이벤트 캘린더 정보를 이 페이지 저 페이지에다 어지럽게 복사해 넣었다.

시간이 지나면서 각 페이지에 복사한 이벤트 캘린더가 서로 어긋나기 시작했다. 심지어 이미 끝난 이벤트를 공지하는 페이지도 있었다. 웹 사이트를 효율적으로 설계하고 올바로 관리한다 치더라도, 페이지마다 이벤트 캘린더 정보를 복사해 넣는다면 시간을 낭비할 뿐더러 실수를 저지르기 십상이다. 결국 웹 사이트가 클럽 발전을 돕지 못하고 클럽이 웹 사이트 관리에 진을 빼는 상태가 되었다. 그래서 그 사람들은 좀 더 나은 해결책을 찾으려고 내게 연락했다.

그들은 1) 클럽 구성원들이 좋아하는 자동차를 토론하는 포럼, 2) 최신 이벤트와 공지 사항을 보여주는 뉴스 페이지가 있는 사이트를 원했다. 뉴스 페이지는 (친교 모임, 명소와 이곳저곳을 누비고 다니는 주행 모임, 예산이나 회비 등을 의논하는 일반 모임 등) 점점 늘어나는 클럽 이벤트를 구성원에게 알리려는 목적이었다. 좀 더 중요하게, 그들은 이벤트 캘린더를 여러 벌 관리하는 번거로움을 없애고 싶어했다. 한 곳에서만 관리하되 사이트 내에서 어디서나 접근할 수 있는 방식을 원했다.

구글 캘린더와 구글 데이터 API

구글 캘린더와 같은 온라인 캘린더 프로그램은 이런 문제를 해결한다. 한 곳에서 이벤트를 관리하고 표시하므로 캘린더 사용자들이 이벤트 정보를 공유하고 유지하기가 쉽다. 따라서 이벤트를 진행하는 과정에서 발생하는 오류가 줄어든다. 클럽 구성원들은 온라인 캘린더에서 다가올 행사와 활동을 확인하므로 개별 페이지에 있는 옛날 이벤트 정보로 혼란을 겪을 필요가 없다. 이러한 장점을 고려하면 온라인 캘린더 프로그램이 이상적인 해결책으로 보였다.

CMS(Content Management System) 중 하나인 Drupal(참고자료 참조)로 클럽 사이트를 개발하던 중 나는 클럽 운영진에게 구글 캘린더를 써서 클럽 이벤트를 관리하자고 제안했다. 처음에는 성공적이었다. Drupal 관리 페이지에서 내가 만들어 준 이벤트 사이드바를 갱신하기 쉬웠으므로 클럽은 생산성이 크게 높아졌다고 알려왔다. 하지만 클럽이 번성하면서, 즉 공지할 이벤트 수가 늘어나면서, 매번 이벤트 사이드바를 갱신하기가 짜증나고 번거로워졌다.

결국 나는 구글 캘린더와 구글 데이터 API를 사용해 클럽을 곤경에서 구해냈다. 구글 데이터 API는 다양한 문서와 정보를 읽고 갱신하는 웹 서비스 API인 APP(Atom Publishing Protocol)를 구현한다. 구글은 마이크로소프트(Microsoft®) .NET, 자바(Java™), 파이썬, PHP 등에서 사용할 API도 제공하는데, 이 API들은 구글 데이터 API 기능을 감싸는 객체 지향 래퍼 클래스에 불과하다.

잠시 시간을 들여 조사한 끝에 나는 구글 캘린더 계정에서 이벤트 정보를 가져와 항상 '최신 이벤트' 사이드바에 밀어넣도록 클럽 사이트를 개선할 수 있었다.




위로


구글 캘린더 피드

구글 데이터 API는 구글이 제공하는 다양한 웹 서비스를 문서와 정보를 포함하는 여러 아톰 피드 형식으로 제공한다. 구글 캘린더도 예외는 아니며, 구글 캘린더 정보 대다수를 다양한 피드로 제공한다. 피드 유형은 두 가지다. 하나는 HTTP 인증 피드고, 다른 하나는 공개 피드다. 인증 피드를 가져오려면 HTTP 클라이언트가 HTTP GET 요청으로 인증 정보를 전송해야 한다. 인증된 피드는 HTTP POST 요청으로 구글 캘린더 계정을 갱신하는 기능도 제공한다. 인증 피드를 사용하는 HTTP 클라이언트는 이벤트를 추가하거나 삭제하고, 캘린더에서 이벤트 알림을 요청하거나 해지하고, 구글 계정으로 캘린더를 생성하거나 삭제할 수 있다.

사용자가 구글 캘린더 GUI에서 볼 수 있는 모든 개별 캘린더는 구글 캘린더 API로 접근할 수 있다. 여기에는 사용자가 소유한 캘린더, 다른 사람이 소유하며 사용자와 공유한 캘린더, 사용자가 읽기 전용으로 가져온 캘린더가 모두 포함된다. 각 캘린더는 인증된 비공개 이벤트 피드와 공개 이벤트 피드가 있으며, 이벤트 피드는 캘린더 이벤트 목록을 포함한다. 이 기사에서는 공개 이벤트 피드에 초점을 맞춘다.

구글 캘린더 피드 공개하기

구글 캘린더의 공개 이벤트 피드를 사용하려면 먼저 구글 캘린더 프로그램으로 로그인한다. 한 계정에서 캘린더를 여러 개 사용해도 괜찮다. 구글 캘린더 프로그램으로 로그인한 후에는 공개할 캘린더를 선택한다. 다음으로 캘린더 이름 옆에 있는 작은 화살표를 클릭한다. 그러면 피드 이름 옆에 드롭다운 메뉴가 뜨는데, 여기에서 캘린더 설정(Calendar Settings)을 선택한다. 자세한 내용은 그림 1을 참조한다.


그림 1. 선택한 캘린더에 대한 구글 캘린더 드롭다운 메뉴


그러면 캘린더 설정 페이지가 열린다. 여기에서 캘린더 이름이나 이벤트 시간대를 설정한다. 한 가지 중요한 점이라면, 공개 피드로 캘린더 정보를 가져오려면, 캘린더 자체를 공개 캘린더나 공유 캘린더로 설정해야 한다는 사실이다. 캘린더를 공개 캘린더나 공유 캘린더로 설정하려면, 그림 2에서 보듯이, 공유 설정 변경(Change sharing settings)을 클릭한다.


그림 2. 캘린더 보여주기 속성을 공개 또는 공유로 변경하기


그러면 페이지가 이 캘린더 공유하기(Share this calendar) 탭으로 이동하며, 여기서 캘린더를 공개로 설정(Share all information on this calendar with everyone)을 선택한다. 변경한 설정을 저장(Save)하면 "캘린더를 공개하면 Google 검색 등을 통해 누구나 캘린더의 모든 일정을 볼 수 있습니다. 공개하시겠습니까?"라는 확인 창이 열리고 여기서 예(Yes)를 선택하면 된다. 마지막으로 (필요하다면) 캘린더로 돌아가기(Back to Calendar)를 클릭하여 캘린더 페이지로 돌아간다.

구글 캘린더 피드 살펴보기

구글 캘린더 피드 예제는 다운로드 절에서 제공하는 코드 예제 중 full.xml 파일을 참조한다. 이 기사에서 사용하는 예제 이벤트 피드는 참고자료에서 제공한다.

이벤트 피드는 이벤트를 기술하는 다양한 엘리먼트를 포함한다. 예를 들면 이벤트 제목, 설명, 이벤트 장소와 시간 등이다. 구글 캘린더는 이벤트에 초청 받은 사람들의 전자편지 주소도 관리하면서 이벤트가 변경되면 관련자들에게 전자편지를 발송한다. 전자편지 주소가 구글 캘린더 사용자 계정이라면, 수신자는 구글 캘린더 프로그램에서 곧바로 초청에 응답할 수 있으며, 구글 캘린더는 참가자의 참석 여부를 추적해 준다. 참석 여부를 추적하는 기능은 이 기사 범위를 벗어나므로 자세히 설명하지 않는다. 여기서는 이벤트 제목, 날짜, 장소 등 기본적인 이벤트 정보를 중점적으로 다룬다. Listing 1은 예제 피드에 들어 있는 이벤트 항목이다.


Listing 1. 예제 구글 캘린더 이벤트 피드 항목: ID와 타임스탬프
                
    <entry>
	    <id>
            http://www.google.com/calendar/feeds/foss.sanjuan%40gmail.com/public/full/
            s19o15ve3nn209gv5qf6c43ao4
        </id>
        <published>2007-08-12T15:45:40.000Z</published>
        <updated>2007-08-12T15:53:37.000Z</updated>
        ...
        ...

id 엘리먼트는 고유한 URI(Uniform Resource Identifier)를 포함한다. 구체적으로는 구글 캘린더 시스템 내에서 이벤트를 식별하는 식별자로, 고유한 숫자만이 아니라 정보를 가져온 피드도 포함한다. published 엘리먼트와 updated 엘리먼트는 RFC 3339 타임스탬프 형식을 따른다. updated 엘리먼트는 이벤트가 최종적으로 갱신된 시간을 나타낸다. 새 이벤트인 경우는 이벤트가 생성된 시간을 뜻한다.

id, published, updated 엘리먼트 다음에는 사람 눈으로 이해하기가 훨씬 쉬운 Listing 2와 같은 항목이 뒤따른다. 바로 이 정보를 사이드바나 이벤트 페이지에 표시한다.


Listing 2. 예제 구글 캘린더 이벤트 피드 항목: 제목, 작성자, 상태
                
        ...
        ...

        <title type="text">Linux Install Fest</title>
        ...
        ...

        <author>
            <name>Open Source San Juan</name>
            <email>foss.sanjuan@gmail.com</email>
        </author>
        ...
        ...

        <gd:eventStatus value="http://schemas.google.com/g/2005#event.confirmed"/>
        ...
        ...

title 엘리먼트는 이벤트 제목을 표현하는 간단한 문자열이다. 고유할 필요는 없다. author 엘리먼트는 name 엘리먼트와 email 엘리먼트로 이루어진다. 이벤트 작성자는 캘린더에 이벤트를 입력한 구글 캘린더 사용자다. 이벤트를 생성한 사용자가 반드시 캘린더를 소유한 사용자는 아니다. 쓰기 권한만 올바로 설정한다면, 인증 피드를 사용하여 구글 캘린더 사용자가 다른 사용자의 캘린더에 이벤트를 생성할 수도 있다. 표 1은 status 엘리먼트에 가능한 값을 열거한다.


표 1. gd:eventStatus 엘리먼트에 가능한 값
설명
http://schemas.google.com/g/2005#event.cancelled취소된 이벤트
http://schemas.google.com/g/2005#event.confirmed확인된 이벤트
http://schemas.google.com/g/2005#event.tentative미정인 이벤트

다음으로 이벤트 장소와 시간을 기술하는 엘리먼트가 온다. 자세한 내용은 Listing 3을 참조한다.


Listing 3. 예제 구글 캘린더 이벤트 피드 항목: 언제, 어디서
                
        ...
        ...

        <gd:when startTime="2007-08-03T16:00:00.000-04:00" 
            endTime="2007-08-03T19:00:00.000-04:00"/>
        <gd:where 
            valueString="Guaynabo Public High School Auditorium, Guaynabo, PR"/>
    </entry>

when 엘리먼트는 속성이 두 개다. 하나는 이벤트 시작 시간(start)이고, 다른 하나는 이벤트 종료 시간(end)이다. 둘 다 RFC 3339 타임스탬프 형식을 따른다. where 엘리먼트의 valueString 속성은 구글 캘린더 프로그램과 구글 데이터 API를 통해 검색할 수 있다. 구글 캘린더 프로그램과 구글 데이터 API를 통해 개별 엘리먼트는 검색하지 못한다. 대신, title, author, description, where 엘리먼트의 valueString 속성 값 등 string을 전문 검색한다. 잠시 후에 보겠지만, 구글 데이터 API를 통해 질의 결과에 포함될 이벤트를 필터링하기 위해 이벤트 시작 날짜 범위를 지정할 수도 있다.

구글 캘린더 피드 내용 제한하기

정확한 정보를 가져오기 위해 구글 데이터 API는 HTTP GET 요청에서 질의 매개변수라는 개념을 지원한다. 이런 매개변수를 사용하여 구글 데이터 API 클라이언트는 가져올 항목 최대 개수(max-results), 피드 항목에 사용할 정렬 기준(orderby), 반환할 항목을 제한할 시작 시간 범위(start-minstart-max) 등을 지정할 수 있다. 마지막 두 매개변수 start-minstart-max는 이벤트 시작 시간 범위를 가리킨다. start-min은 범위 시작 시간이고, start-max는 범위 끝 시간이다. 둘 다 RFC 3339 타임스탬프 형식을 따른다.

마지막으로, 질의 문자열에서 singleevents 매개변수는 반복 이벤트를 가져오는 방식을 좀 더 쉽게 제어한다. singleevents 매개변수를 true로 설정하면 반복 이벤트는 개별 이벤트로 피드에 포함된다. singleevents 매개변수를 false로 설정하면 반복 이벤트는 <gd:recurrence> 엘리먼트를 포함한다. 이 때 <gd:recurrence> 엘리먼트는 반복 규칙을 iCal 형식으로 포함한다. 구체적인 iCal 형식과 iCal 형식을 분석하는 방법은 이 기사 범위를 벗어난다.

Listing 4는 질의 매개변수를 모두 추가한 이벤트 피드 URL이다. 원래는 한 줄이나 가독성을 높이고자 긴 URL을 여러 행으로 나누었다.


Listing 4. 질의 매개변수를 추가한 예제 구글 캘린더 피드 URl
                
    http://www.google.com/calendar/feeds/foss.sanjuan%40gmail.com/public/full?
    max-results=25&
    singleevents=true&
    orderby=starttime&
    start-min=2007-05-22T09%3A58%3A47-04%3A00&
    start-max=2007-11-06T09%3A58%3A47-04%3A00




위로


PHP로 구글 캘린더 피드 분석하기

지금까지 구글 캘린더 이벤트 피드를 구성하는 엘리먼트를 소개하고, 매개변수를 지정하여 원하는 정보를 가져오는 방법을 설명했다. 이제는 피드를 분석하여 페이지에 표시하는 방법을 살펴볼 차례다. 피드에서 이벤트 목록과 각 이벤트의 제목, 시간, 장소를 추출하려면 몇 가지 XML API가 필요하다. PHP는 여러 가지 XML API를 제공하는데, 가장 먼저 DOM(Document Object Model)을 살펴보겠다.

DOM으로 피드 분석하기

XML DOM API는 표준 XML 구문분석 API로, XML 응용 프로그램에서 가장 흔히 사용된다. 구체적으로 DOM API를 사용하는 방법은 이 기사 범위를 벗어나지만, 간단한 예제 프로그램으로 DOM API로 구글 캘린더 이벤트 피드를 분석하는 방법을 설명한 후 DOM API가 제공하는 장점과 단점을 살펴보겠다.

XML DOM API가 제공하는 장점 중 하나는 속력이다. XML 문서 전체를 메모리로 읽어들인 후 메모리에서 XML 문서에 포함된 엘리먼트를 가져온다. XML 파일이 아주 크다면 비효율적이지만, 2MB에서 3MB 정도라면 괜찮은 속력을 얻는다.

XML DOM API가 제공하는 또 다른 장점은 가독성이다. DOM으로 XML을 분석하는 단계는 거의 영어와 비슷하다. 먼저 XML 문서를 연 다음에 계층적인 방식으로 문서 내 엘리먼트를 가져온다. 마치 PHP에게 "태그 이름이 'entry'인 엘리먼트를 가져오시오"라고 말하는 식이다. Listing 5는 DOM으로 구글 캘린더 피드를 분석하는 예제 코드다.


Listing 5. DOM API로 구글 캘린더 이벤트 피드 분석하기
                
<?php 
    $confirmed = 'http://schemas.google.com/g/2005#event.confirmed';

    $three_months_in_seconds = 60 * 60 * 24 * 28 * 3;
    $three_months_ago = date("Y-m-d\Th:i:sP", time() - $three_months_in_seconds);
    $three_months_from_today = date("Y-m-d\Th:i:sP", time() + $three_months_in_seconds);

    $feed = "http://www.google.com/calendar/feeds/foss.sanjuan%40gmail.com/" . 
        "public/full?orderby=starttime&singleevents=true&" . 
        "start-min=" . $three_months_ago . "&" .
        "start-max=" . $three_months_from_today;

    $doc = new DOMDocument(); 
    $doc->load( $feed );

    $entries = $doc->getElementsByTagName( "entry" ); 

    foreach ( $entries as $entry ) { 

        $status = $entry->getElementsByTagName( "eventStatus" ); 
        $eventStatus = $status->item(0)->getAttributeNode("value")->value;

        if ($eventStatus == $confirmed) {
            $titles = $entry->getElementsByTagName( "title" ); 
            $title = $titles->item(0)->nodeValue;

            $times = $entry->getElementsByTagName( "when" ); 
            $startTime = $times->item(0)->getAttributeNode("startTime")->value;
            $when = date( "l jS \o\f F Y - h:i A", strtotime( $startTime ) );

            $places = $entry->getElementsByTagName( "where" ); 
            $where = $places->item(0)->getAttributeNode("valueString")->value;

            print $title . "\n"; 
            print $when . " AST\n"; 
            print $where . "\n"; 
            print "\n"; 
        }
    }
?>

위 코드 예제에서는 필요한 매개변수를 추가하여 피드 URL을 설정한다. 다음으로 피드를 열어서 모든 이벤트 항목을 DOMNodeList로 가져온 후 foreach로 루프를 돌면서 각 이벤트 항목을 추출한다. 루프 내에서 각 이벤트마다 gd:eventStatus 엘리먼트의 value 속성을 살펴서 이벤트 확인 상태를 파악한다. 여기서 이벤트 태그에 gd: 접두어를 지정할 필요가 없다는 사실에 주목한다. PHP DOM API는 이름 공간을 이해한다. 위 코드에서 gd: 접두어를 붙이면 구문분석기가 원하는 엘리먼트를 찾아내지 못한다.

확인된 이벤트라면 (즉, 이벤트 상태가 event.confirmed라면) title, gd:when, gd:where 엘리먼트를 추출한다. gd:when 엘리먼트와 gd:where 엘리먼트에서는 구체적인 속성을 추출한다. 즉, startTimevalueString을 추출한다. 이벤트 시간을 이해하기 쉬운 형식으로 표현하려면 gd:when 엘리먼트의 startTime 속성 값을 적절히 변환해야 한다. 즉, 먼저 strtotime 함수로 문자열 형태의 시간을 long 정수 형태로 변환한 후 date 함수를 호출하여 일반적인 형식을 얻는다.

DOM API는 다소 장황하다는 게 단점이다. 프로그램에서 각 코드가 의미하는 바는 명백하다. 알고리즘을 말로 설명할 때와 별반 다르지 않다. 하지만 간결하고 깔끔한 코드라 부르기는 어렵다. DOMNode 클래스와 DOMDocument 클래스에 속한 getElementsByTagName 메서드나 getAttributeNode 같은 메서드는 이름 자체만도 상당히 길다.

SAX로 피드 분석하기

PHP에서 사용할 수 있는 또 다른 XML API가 SAX(Simple API for XML) API다. DOM API는 읽기 쉬우면서 장황하지만, SAX는 간결하면서 이해하기 어렵다. 심지어 숙련된 개발자도 코드를 판독하는 데 어려움을 겪는다. DOM과 달리, SAX는 XML 문서 전체를 메모리로 읽어들이지 않는다. 그래서 SAX API를 사용하면 번개처럼 빠른 코드, 메모리를 최소로 사용하는 코드, 프로그램 목적에 딱 맞는 코드를 작성하기가 쉽다. DOM API를 사용할 때는 최소한 XML 문서 크기만큼 메모리가 필요하다.

Listing 6은 SAX API로 구글 캘린더 이벤트 피드를 처리하는 코드 예제 중 일부다. 전체 코드는 다운로드 절에서 제공하는 예제 스크립트 중 sax_sample.php를 참고한다.


Listing 6. SAX API로 구글 캘린더 이벤트 피드 분석하기
                
    function startElement( $parser, $tagName, $attr )
    {
        global $g_entries, $g_tagName, $g_confirmed, $g_is_confirmed, 
            $g_in_entry, $g_in_originalevent;

        if ( $tagName == 'ENTRY' ) {
            if ($g_is_confirmed || count( $g_entries ) == 0) {
                $g_entries []= array();
            }
            $g_is_confirmed = false;
            $g_in_entry = true;
        }
        else if ($tagName == 'GD:EVENTSTATUS')
        {
            if ($attr['VALUE'] == $g_confirmed) {
                $g_is_confirmed = true;
            }
        }
        else if ($tagName == 'GD:WHEN' && $g_is_confirmed && 
            $g_in_originalevent == false)
        {
            $startTime = date( "l jS \o\f F Y - h:i A", strtotime($attr['STARTTIME']) );
            $g_entries[ count( $g_entries ) - 1 ]['when'] = $startTime;
        }
        else if ($tagName == 'GD:WHERE' && $g_is_confirmed)
        {
            $g_entries[ count( $g_entries ) - 1 ]['where'] = $attr['VALUESTRING'];
        }
        else if ( $tagName == 'GD:ORIGINALEVENT' ) {
            $g_in_originalevent = true;
        }
        $g_tagName = $tagName;
    }

    function endElement( $parser, $tagName ) 
    {
        global $g_tagName, $g_in_entry, $g_in_originalevent;

        if ( $tagName == 'ENTRY' ) {
            $g_in_entry = false;
        }
        else if ( $tagName == 'GD:ORIGINALEVENT' ) {
            $g_in_originalevent = false;
        }
        $g_tagName = null;
    }

    function textData( $parser, $text )
    {
        global $g_entries, $g_tagName, $g_in_entry;
        if ($g_tagName == 'TITLE' && $g_in_entry) {
            $g_entries[ count( $g_entries ) - 1 ]['title'] = $text;
        }
    }

위 코드는 startElement, endElement, textData 함수를 보여준다. 세 함수는 SAX 예제 스크립트에서 SAX 구문분석 엔진에 등록한다. 위 코드는 XML 문서에서 필요한 엘리먼트를 찾아내는 방법을 보여준다. 코드가 상당히 복잡한데, 이는 SAX가 XML 문서를 처음부터 순서대로 읽어서 XML 엘리먼트를 찾기 때문이다. 현재 이벤트 항목이 확인(confirmed) 상태인지 확인하려면 다른 엘리먼트부터 읽어야 한다. gd:eventStatus 엘리먼트가 title 엘리먼트 뒤에 오기 때문이다. 코드가 복잡한 또 다른 이유는 피드 자체에 title 엘리먼트가 있는데 각 이벤트 항목 내에도 title 엘리먼트가 있어서다. 그래서 title 엘리먼트가 이벤트 항목 내에 있음을 나타내는 플래그가 필요하다. 또한 반복 이벤트는 gd:originalEvent 엘리먼트 안에 gd:when 엘리먼트가 존재한다. 그래서 이런 gd:when 엘리먼트를 감지하는 플래그도 필요하다. 만약 플래그를 확인하지 않고 단순히 엘리먼트 이름만 찾으면 잘못된 결과를 얻는다.

위 예제에서 보듯이, SAX API는 두 가지 단점이 있다. 하나는 DOM API보다 훨씬 장황하고 길다는 점이고, 다른 하나는 DOM 예제 코드보다 몇 배나 복잡하다는 점이다.

젠드 구글 API 래퍼로 피드 분석하기

Zend Technologies 사는 구글과 협력하여 구글 API를 지원하도록 젠드 프레임워크를 확장했다. 구체적으로 말하면, 젠드 프레임워크가 구글 API의 아톰 피드와 관련한 세부 사항을 감추는 객체 지향 PHP 클래스를 제공한다는 말이다. 이러한 객체 지향 클래스는 장점이 많다. 다양한 구글 API 문서를 추상 객체와 메서드와 간단한 매개변수로 표현하며, 사용자가 (GET, PUT, POST와 같은) HTTP 프로토콜과 아톰 피드를 상세히 알 필요가 없다. 또한 번거로운 사용자 인증 단계를 모두 처리하는 클래스도 제공한다. 따라서 클라이언트에서 이벤트를 생성하거나 갱신하는 등 구글 데이터 API 기능을 모두 지원하는 외부 프로그램을 구현하기가 아주 간편하다.

젠드 프레임워크가 제공하는 구글 데이터 API 클래스는 XML 구문분석과 HTTP 프로토콜에 익숙하지 않은 개발자에게는 상당한 편의를 제공한다. 하지만 그렇다고 단점이 없지는 않다. Listing 7에서 보듯이, 코드가 길고 장황하다. 많은 객체 생성자가 매개변수를 취하지 않는 탓에 객체를 생성한 다음에 객체 속성을 일일이 설정하기 때문이다.


Listing 7. 젠드 PHP 클래스를 사용하여 구글 캘린더 이벤트 피드를 가져와 처리하기
                
<?php
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Gdata');
Zend_Loader::loadClass('Zend_Gdata_Calendar');

$three_months_in_seconds = 60 * 60 * 24 * 28 * 3;
$three_months_ago = date("Y-m-d\Th:i:sP", time() - $three_months_in_seconds);
$three_months_from_today = date("Y-m-d\Th:i:sP", time() + $three_months_in_seconds);

// 인증 없이 캘린더 서비스 인스턴스를 생성한다.
// 공유 피드를 위한 읽기 전용 목적
$service = new Zend_Gdata_Calendar();

$query = $service->newEventQuery();
$query->setUser('foss.sanjuan%40gmail.com');
$query->setVisibility('public');
$query->setProjection('full');
$query->setOrderby('starttime');
$query->setStartMin($three_months_ago);
$query->setStartMax($three_months_from_today);

// 캘린더 서버에서 이벤트 목록을 가져온다.
try {
    $eventFeed = $service->getCalendarEventFeed($query);
} catch (Zend_Gdata_App_Exception $e) {
    echo "Error: " . $e->getResponse();
}

// 이벤트 목록을 순회하면서 HTML list로 출력한다.
print "<ul>\n";
foreach ($eventFeed as $event) {
    print "<li>" . $event->title . "</li>\n";

	$startTime = $event->when->startTime;
    print "<li>" . date("l jS \o\f F Y - h:i A", strtotime( $startTime ) );

	// 구글 캘린더 API에서 시간대 지원은 버그가 있다.
    print " AST</li>\n";

    print "<li>" . $event->where->valueString . "</li>\n";
}
print "</ul>\n";

젠드 구글 데이터 API 클래스가 제공하는 장점 중 하나라면, eventFeed 객체를 가져온 후 피드의 엘리먼트와 속성을 eventFeed 객체의 속성으로 계층적인 참조가 가능하다는 점이다. 즉, 계층적으로 엘리먼트를 참조하는 방식이 XML 문서 구조와 비슷하므로 그만큼 코드를 읽기가 쉽다. DOMNode에서 getAttributeNode("attributeName")->value 메서드로 읽어들이던 속성 값은 부모 엘리먼트에서 계층적으로 읽으면 된다.




위로


XPath와 SimpleXML로 분석하기

SimpleXML API와 XPath는 개인적으로 가장 사용하기 쉬운 XML API라고 생각한다. SimpleXML API는 메모리에 올라온 XML 문서를 객체 속성의 계층적인 집합으로 표현한다. DOM API가 반환하는 DOMNode 객체 인스턴스를 계층적으로 참조하는 방식과 비슷하다. DOM의 DOMNode 객체와 마찬가지로, Zend 클래스와는 달리, 엘리먼트 속성을 가져오려면 attribute() 메서드를 호출한다. 엘리먼트 속성은 attributes() 메서드가 반환하는 객체에 객체 속성으로 들어 있다. Listing 8은 SimpleXML API로 구글 캘린더 피드를 분석한 후 필요한 정보를 추출하는 코드다.


Listing 8. XPath와 SimpleXML로 구글 캘린더 이벤트 피드 분석하기
                
    $s = simplexml_load_file($feed); 

    foreach ($s->entry as $item) {
        $gd = $item->children('http://schemas.google.com/g/2005');

        if ($gd->eventStatus->attributes()->value == $confirmed) {
?>
            <font size=+1><b>
                <?php print $item->title; ?>
            </b></font><br>

<?php 
            $startTime = '';
            if ( $gd->when ) {
                $startTime = $gd->when->attributes()->startTime;
            } elseif ( $gd->recurrence ) {
                $startTime = $gd->recurrence->when->attributes()->startTime; 
            } 

            print date("l jS \o\f F Y - h:i A", strtotime( $startTime ) );
            // Google Calendar API's support of timezones is buggy
            print " AST<br>";
?>
            <?php print $gd->where->attributes()->valueString; ?>
            <br><br>

<?php
        }
    } ?>

SimpleXML API가 지니는 한 가지 단점이라면, DOM이나 SAX보다 4배까지 느리다는 점이다. 이는 SimpleXML이 PHP의 동적 속성을 이용하여 즉석에서 엘리먼트와 속성 노드를 생성하기 때문이다. 반면, 젠드 구글 데이터 API 클래스는 클래스 코드에 엘리먼트와 속성 노드를 정의한다. 즉, 노드를 계층적으로 참조하는 속력은 Zend 클래스가 SimpleXML보다 빠르다.

또 다른 단점이라면, XML 이름 공간을 지원하는 방식이다. 특정한 이름 공간에 속하는 자식 엘리먼트를 가져오려면 부모 엘리먼트에서 children() 메서드를 호출한다. 이 때 children() 메서드 인수로는 이름 공간 URI를 지정한다. 그러면 지정한 이름 공간에 속하는 엘리먼트만 반환된다. 엄밀하게는 엘리먼트를 속성으로 가지는 객체가 반환된다. 반면, DOM과 Zend 클래스는 모든 이름 공간의 모든 엘리먼트를 계층적으로 참조가 가능한 개별 노드로 반환한다.




위로


성능과 캐시

앞서 XML API를 소개하면서 SimpleXML이 DOM이나 SAX보다 몇 배까지 느리다고 언급했다. 내가 SimpleXML을 사용하는 이유는 SAX보다 사용하기 쉽고 DOM보다 가독성이 높아서다. SimpleXML은 즉석에서 엘리먼트와 속성 노드를 생성하므로 노드를 탐색하는 코드가 젠드 구글 데이터 API 클래스만큼이나 이해하기 쉽다.

그러나 이 기사에서 소개한 예제 코드에는 또 다른 문제가 있다. 예제 코드는 사용자가 페이지를 방문할 때마다 구글 캘린더 피드를 다시 가져온다. 예제 코드를 PHP 사이트의 사이드바에 넣는다면, 방문자가 페이지를 표시할 때마다, 피드를 다시 내려받아 분석한다는 뜻이다. 캘린더 내용이 몇 시간에 한 번 혹은 며칠에 한 번 바뀔 가능성이 크므로 확실히 낭비다. 예를 들어, 사이트가 인기를 끌어서 하루 페이지 조회수가 1000회에 이르렀다고 가정하자. (인기 있는 포럼이라면 사용자가 20-30명만 넘어서도 페이지 조회수는 1000회를 쉽게 넘긴다.) 이 때 사용자가 페이지를 조회할 때마다 피드를 가져다 분석한다면? 구글 API는 사용 규정으로 이러한 남용을 금지한다. 사용 규정을 준수하지 않으면 서버 IP를 차단하기도 한다. 심하면 구글 계정도 취소한다. 따라서 좀 더 영리한 방법이 필요하다.

구글 API 사용 규정을 위반하지 않도록 현명하게 예제 코드를 고치는 방법은 여러 가지다. 모두가 기본적으로는 한 번 가져온 정보를 캐시에 저장했다가 캘린더 내용이 바뀔 때만 다시 가져오는 방식이다. 정보를 캐시에 저장하는 세부 내역은 이 기사 범위를 벗어나지만, 스크립트를 개선하는 방식에 대한 이해를 돕고자 간략히 설명하겠다.

한 가지 방법으로는 HTTP 요청 헤더를 사용하여 구글 데이터 API 피드가 변경되었는지 여부를 확인하는 방법이 있다. 구글 서비스는 반환 정보에 들어있는 <atom:updated> 엘리먼트 값에 따라 최종 수정일(Last-Modified) 응답 헤더를 설정한다. 첫 호출에서 스크립트는 피드를 분석해 얻은 결과를 HTML 파일로 저장한다. 이 때 <atom:updated> 타임스탬프 값도 데이터베이스 레코드에 저장한다. 다음 번에 사용자가 이벤트 캘린더 정보를 요청하면 변경되지 않았을 경우 다시 한번 피드를 인출하지 않도록 클라이언트는 타임스탬프를 가져와 If-Modified-Since 요청 헤더에 넣어 구글 서비스로 요청을 보낸다.

If-Modified-Since 헤더에 들어 있는 시간 이후로 피드 내용이 변하지 않았다면 구글 서비스는 304 (Not Modified) HTTP 응답을 보낸다. 이벤트 정보가 변하지 않았다는 뜻이므로 스크립트는 앞서 생성한 HTML 파일을 include 함수로 표시한다. 피드 내용이 변했다면 구글 서비스는 200 (OK) 응답을 반환한다. 그러면 스크립트는 피드를 분석하여 새 HTML 파일을 생성하고, <atom:updated> 엘리먼트 값을 새 타임스탬프로 저장한 후, 새로 생성한 HTML 파일을 include 함수로 표시한다.

타임스탬프 값 하나를 저장하려고 데이터베이스를 사용하기가 다소 부담스러울지도 모르겠다. 하지만 PHP는 (마이크로소프트 ASP, 마이크로소프트 APS.NET, 자바 서블릿, JSP와는 달리) 응용 프로그램 수준에서 변수를 지원하지 않는다. 데이터베이스 대신 공유 메모리를 사용하는 방법도 있다. 그런데 모든 PHP 버전이 공유 메모리를 지원하지는 않는다. 공유 메모리는 보안 위험이 존재하기 때문이다. 또 다른 캐시 대안으로, 특히 PHP 프로그램이 웹 서버 클러스터에서 돌아간다면, memcached도 있다(참고로, memcached는 원래 LiveJournal 제작자가 만들었고 FaceBook 개발자가 크게 확장한 클러스터 메모리 캐시 서비스다).




위로


결론

구글 캘린더는 중앙집중화된 웹 응용 프로그램 앞단을 제공한다. 구글 캘린더를 통해 조직과 리더는 구성원과 일반인을 위해 이벤트 캘린더를 공유하고 공개할 수 있다. 구글 데이터 API는 구글 캘린더를 비롯하여 거의 모든 구글 서비스에서 정보를 가져오고, 조회하고, 갱신하는 기능을 아톰 피드와 아톰 퍼블리싱 프로토콜 형태로 제공한다.

XPath와 SimpleXML을 사용하면 구글 캘린더에서 이벤트 피드를 가져와 유효한 세부 엘리먼트 내용을 해석한 다음에 웹 사이트에 최신 이벤트를 자동으로 표시할 수 있다. XPath는 PHP 툴킷 중에서 가장 빠른 XML API는 아니지만, 잘 구성된 XML 문서를 처리한다면, 가장 사용하기 쉬운 XML API 중 하나임에는 분명하다. 여기에 캐시 기법을 더하면 XPath가 상대적으로 느리다는 단점도 보완할 수 있다.





위로


다운로드 하십시오

설명이름크기다운로드 방식
예제 PHP 코드os-php-xpath.google-calendar-api.zip4KBHTTP
다운로드 방식에 대한 정보


참고자료

교육
  • Drupal: 인기 있는 CMS(Content Management System) 중 하나다.

  • Google Calendar APIs and Tools Overview: 구글 캘린더 API를 사용하는 방법을 개략적으로 소개한다. 상세한 API 문서를 가리키는 링크도 제공한다.

  • PHP.net: PHP와 관련한 자료를 상세히 제공한다.

  • "Recommended PHP reading list."(Daniel Krook과 Carlos Hoyos, 2006년 3월): PHP 개발자가 읽어야 할 자료 목록이다.

  • developerWorks에서 PHP 기사를 살펴본다.

  • IBM developerWorks에 실린 PHP project resources를 참조해 프로젝트 관련 정보를 살펴본다.

  • developerWorks 포드캐스트: 소프트웨어 개발자들이 나누는 토론과 흥미로운 인터뷰 자료를 제공한다.

  • PHP와 데이터베이스를 연동한다면, Zend Core for IBM을 권장한다. IBM Zend Core는 설치하기 쉽고 사용하기 간편한 PHP 개발 환경으로, IBM DB2 V9을 지원한다.

  • developerWorks에서 제공하는 기술 행사와 웹 캐스트를 놓치지 말자.

  • IBM 오픈 소스 개발자들이 흥미를 가지는 컨퍼런스, 전시회, 웹 캐스트 등 전세계적으로 벌어지는 행사 목록도 참고한다.

  • developerWorks 오픈 소스 영역은 오픈 소스 개발과 구현에 필요한 자원을 모아놓은 사이트다. 오픈 소스를 개발하는 방법, 오픈 소스 도구, 오픈 소스 프로젝트, 오픈 소스에서 IBM 제품을 사용하는 방법 등을 상세히 설명한다.

  • developerWorks On demand demos에서 IBM 제품과 기술, 오픈 소스 제품과 기술을 무료로 시연해볼 수 있다.


제품 및 기술 얻기

토론


필자소개

P.J. Cabrera

P.J. Cabrera는 Ruby on Rails 전자 상거래와 CMS를 전문으로 개발하는 소프트웨어 개발자다. 주된 관심사는 Ruby on Rails, 오픈 소스 스크립트 언어, 프레임워크, 애자일 개발 기법, 메시 네트워크(Mesh network), 클라우드 컴퓨팅(Cloud Computing), XML 구문분석과 처리 기법, 시맨틱 웹 콘텐츠와 관련한 마이크로포맷, 베이즈 필터링과 심볼릭 프로세싱을 사용하여 정보를 좀 더 효율적으로 가져오고 답하고 분류하고 추출하는 방법 등이다. 자세한 내용은 그의 블로그(pjtrix.com/blawg/)를 살펴본다.




기사에 대한 평가


보다 나은 서비스를 제공하기 위함이오니 잠시 짬을 내어 이 양식을 제출하여 주십시오.



 


 


 


이 문서 북마킹 하기

mar.gar.in mar.gar.in naver naver eolin eolin del.icio.us del.icio.us





위로


developerWorks 콘텐트를 다른 사이트에 전재하기:
developerWorks 콘텐트에 대한 저작권은 IBM에 있습니다. IBM의 서면 허가나 원본 저자의 허락이 없이는 전재를 금합니다. 저희 콘텐트를 전재하시려면 IBM developerWorks 담당자 에게 문의하십시오.
    IBM 소개 개인정보 보호정책 문의