Google Data API에서 Ruby 사용

Jochen Hartmann, Google 데이터 API팀
2008년 4월

소개

동적 스크립팅 언어인 Ruby는 최근 몇 년간 인기 있는 Ruby 웹 개발 프레임워크로 인해 많은 주목을 받았습니다. 이 도움말에서는 Ruby를 사용하여 Google Data API 서비스와 상호작용하는 방법을 설명합니다. 여기서는 레일을 중점적으로 다루지 않으며 기본 HTTP 명령어와 피드 구조를 설명하는 데 더 중점을 둡니다. 여기에 나와 있는 모든 예는 Ruby의 대화형 셸인 irb를 사용하여 명령줄에서 따라할 수 있습니다.

cURL 도움말에서 확인할 수 있듯이 Google Data API는 Atom 게시 프로토콜을 사용하여 웹 리소스를 표현, 생성, 업데이트합니다. 이 프로토콜의 장점은 표준 HTTP 동사를 사용하여 표준 HTTP 상태 코드로 응답하는 요청을 공식화한다는 것입니다.

이 도움말에서 사용할 동사는 GET(콘텐츠 검색), POST(새 콘텐츠 업로드), PUT(기존 콘텐츠 업데이트)입니다. Google Data API를 사용할 때 마주칠 수 있는 표준 코드 중 일부는 200으로 피드나 항목을 성공적으로 검색하거나 201로 리소스 생성이나 업데이트를 성공적으로 마쳤다는 의미입니다. 잘못된 요청 전송 등 문제가 발생하는 경우 '잘못된 요청'을 의미하는 400 코드가 다시 전송됩니다. 정확히 어떤 문제가 발생했는지 설명하는 자세한 메시지가 응답 본문에 제공됩니다.

Ruby는 'Net' 모듈의 일부로 유용한 디버깅 옵션을 제공합니다. 이러한 코드 샘플을 상당히 짧게 유지하기 위해 여기에서는 사용 설정하지 않았습니다.

Ruby 가져오기 및 설치

Linux를 사용하는 경우 대부분의 패키지 관리 시스템을 사용하여 Ruby를 설치할 수 있습니다. 다른 운영체제 및 전체 소스 코드를 확인하려면 http://www.ruby-lang.org/en/downloads/를 방문하세요. ERM에는 이 예시에 사용할 대화형 셸이 기본적으로 설치되어야 합니다. 여기에 나열된 코드 예시를 따르려면 XML을 Ruby 데이터 구조로 파싱하는 작은 라이브러리인 XmlSimple도 설치해야 합니다. XmlSimple을 설치/설치하려면 http://xml-simple.rubyforge.org/를 방문하세요.

머신에서 Ruby 사본을 실행한 후에는 Net:HTTP 패키지를 사용하여 Google 데이터 서비스에 대한 기본 요청을 수행할 수 있습니다. 아래 스니펫은 Ruby의 대화형 셸에서 필요한 가져오기를 수행하는 방법을 보여줍니다. 'net/http' 패키지를 요구하는 경우 YouTube에서 최고 평점 동영상 피드의 URL을 파싱한 다음 HTTP GET 요청을 수행합니다.

irb(main):001:0> require 'net/http'
=> true
irb(main):002:0> youtube_top_rated_videos_feed_uri = \
'http://gdata.youtube.com/feeds/api/standardfeeds/top_rated'
=> "http://gdata.youtube.com/feeds/api/standardfeeds/top_rated"
irb(main):003:0> uri = \
URI.parse(youtube_top_rated_videos_feed_uri)
=> #<URI::HTTP:0xfbf826e4 URL:http://gdata.youtube.com/feeds/api/standardfeeds/top_rated>

irb(main):004:0> uri.host
=> "gdata.youtube.com"
irb(main):005:0> Net::HTTP.start(uri.host, uri.port) do |http|
irb(main):006:1* puts http.get(uri.path)
irb(main):007:1> end
#<Net::HTTPOK:0xf7ef22cc>

이 요청은 명령줄에 XML을 상당 부분 반영했습니다. 모든 상품이 <feed> 요소 내에 포함되어 있으며 이를 <entry> 요소라고 부릅니다. 여기서는 XML 형식에 관해서는 걱정하지 않으셔도 됩니다. HTTP를 사용하여 기본적인 Google Data API 요청을 하는 방법을 설명하기 위해서입니다. 이제 API를 전환하고 스프레드시트에 중점을 두겠습니다. 전송 및 검색할 수 있는 정보는 '명령줄 친화적'이기 때문입니다.

인증 | Google Sheets API 사용

다시 항목 요소의 피드를 검색합니다. 이번에는 자체 스프레드시트를 사용해 보겠습니다. 이를 위해서는 먼저 Google 계정 서비스로 인증해야 합니다.

GData 인증에 관한 문서에서 설명했듯이 두 가지 방법으로 Google 서비스를 인증할 수 있습니다. AuthSub는 웹 기반 애플리케이션용이며 요약하면 토큰 교환 프로세스가 포함됩니다. AuthSub의 진정한 이점은 애플리케이션이 사용자 인증 정보를 저장할 필요가 없다는 것입니다. ClientLogin은 '설치된' 애플리케이션용입니다. ClientLogin 과정에서 사용자 이름과 비밀번호는 사용하려는 서비스를 식별하는 문자열과 함께 https를 통해 Google 서비스로 전송됩니다. Google Sheets API 서비스는 wise라는 문자열로 식별됩니다.

대화형 셸로 다시 전환하여 Google에 인증하겠습니다. https를 사용하여 인증 요청과 사용자 인증 정보를 전송하고 있습니다.

irb(main):008:0> require 'net/https'
=> true
irb(main):009:0> http = Net::HTTP.new('www.google.com', 443)
=> #<Net::HTTP www.google.com:443 open=false>
irb(main):010:0> http.use_ssl = true
=> true
irb(main):011:0> path = '/accounts/ClientLogin'
=> "/accounts/ClientLogin"

# Now we are passing in our actual authentication data. 
# Please visit OAuth For Installed Apps for more information 
# about the accountType parameter
irb(main):014:0> data = \
irb(main):015:0* 'accountType=HOSTED_OR_GOOGLE&Email=your email' \
irb(main):016:0* '&Passwd=your password' \
irb(main):017:0* '&service=wise'

=> accountType=HOSTED_OR_GOOGLE&Email=your email&Passwd=your password&service=wise"

# Set up a hash for the headers
irb(main):018:0> headers = \
irb(main):019:0* { 'Content-Type' => 'application/x-www-form-urlencoded'}
=> {"Content-Type"=>"application/x-www-form-urlencoded"}

# Post the request and print out the response to retrieve our authentication token
irb(main):020:0> resp, data = http.post(path, data, headers)
warning: peer certificate won't be verified in this SSL session
=> [#<Net::HTTPOK 200 OK readbody=true>, "SID=DQAAAIIAAADgV7j4F-QVQjnxdDRjpslHKC3M ... [ snipping out the rest of the authentication strings ]

# Strip out our actual token (Auth) and store it
irb(main):021:0> cl_string = data[/Auth=(.*)/, 1]
=> "DQAAAIUAAADzL... [ snip ]

# Build our headers hash and add the authorization token
irb(main):022:0> headers["Authorization"] = "GoogleLogin auth=#{cl_string}"
=> "GoogleLogin auth=DQAAAIUAAADzL... [ snip ]

정상입니다. 이제 인증을 마쳤으므로 요청을 사용하여 스프레드시트를 검색해 보겠습니다.

http://spreadsheets.google.com/feeds/spreadsheets/private/full

인증된 요청이므로 헤더도 전달하려고 합니다. 실제로 다양한 피드에 대해 여러 요청을 할 것이므로 이 기능을 get_feed로 부르는 간단한 함수로 래핑할 수도 있습니다.

# Store the URI to the feed since we may want to use it again
irb(main):023:0> spreadsheets_uri = \
irb(main):024:0* 'http://spreadsheets.google.com/feeds/spreadsheets/private/full'

# Create a simple method to obtain a feed
irb(main):025:0> def get_feed(uri, headers=nil)
irb(main):026:1> uri = URI.parse(uri)
irb(main):027:1> Net::HTTP.start(uri.host, uri.port) do |http|
irb(main):028:2* return http.get(uri.path, headers)
irb(main):029:2> end
irb(main):030:1> end
=> nil

# Lets make a request and store the response in 'my_spreadsheets'
irb(main):031:0> my_spreadsheets = get_feed(spreadsheets_uri, headers)
=> #<Net::HTTPOK 200 OK readbody=true>

irb(main):032:0> my_spreadsheets
=> #<Net::HTTPOK 200 OK readbody=true>

# Examine our XML (showing only an excerpt here...)
irb(main):033:0> my_spreadsheets.body
=> "<?xml version='1.0' encoding='UTF-8'?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/'>
<id>http://spreadsheets.google.com/feeds/spreadsheets/private/full</id><updated>2008-03-20T20:49:39.211Z</updated>
<category scheme='http://schemas.google.com/spreadsheets/2006' term='http://schemas.google.com/spreadsheets/2006#spreadsheet'/>
<title type='text'>Available Spreadsheets - test.api.jhartmann@gmail.com</title><link rel='alternate' type='text/html' href='http://docs.google.com'/>
<link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://spreadsheets.google.com/feeds/spreadsheets/private/full'/><link rel='self' type='application/atom+xml' href='http://spreadsheets.google.com/feeds/spreadsheets/private/full?tfe='/>
<openSearch:totalResults>6</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><entry>
<id>http://spreadsheets.google.com/feeds/spreadsheets/private/full/o04927555739056712307.4365563854844943790</id><updated>2008-03-19T20:44:41.055Z</updated><category scheme='http://schemas.google.com/spreadsheets/2006' term='http://schemas.google.com/spreadsheets/2006#spreadsheet'/><title type='text'>test02</title><content type='text'>test02</content><link rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed' type='application/atom+xml' href='http://spreadsheets.google.com/feeds/worksheets/o04927555739056712307.4365563854844943790/private/full'/><link rel='alternate' type='text/html' href='http://spreadsheets.google.com/ccc?key=o04927555739056712307.4365563854844943790'/><link rel='self' type='application/atom+xml' href='http://spreadsheets.google.com/feeds/spreadsheets/private/full/o04927555739056712307.4365563854844943790'/><author><name>test.api.jhartmann</name><email>test.api.jhartmann@gmail.com</email></author></entry><entry> ...

명령줄에서 많은 것을 해독할 필요가 없기 때문에 위에서 강조하지 않은 XML이 많이 표시됩니다. 보다 사용자 친화적인 환경을 만들기 위해 XmlSimple를 사용하여 이를 데이터 구조로 파싱해 보겠습니다.

# Perform imports
irb(main):034:0> require 'rubygems'
=> true
irb(main):035:0> require 'xmlsimple'
=> true
irb(main):036:0> doc = \
irb(main):037:0* XmlSimple.xml_in(my_spreadsheets.body, 'KeyAttr' => 'name')

# Import the 'pp' module for 'pretty printing'
irb(main):038:0> require 'pp'
=> true

# 'Pretty-print' our XML document
irb(main):039:0> pp doc
{"totalResults"=>["6"],
 "category"=>
  [{"term"=>"http://schemas.google.com/spreadsheets/2006#spreadsheet",
    "scheme"=>"http://schemas.google.com/spreadsheets/2006"}],
 "title"=>
  [{"type"=>"text",
    "content"=>"Available Spreadsheets - Test-account"}],
 "startIndex"=>["1"],
 "id"=>["http://spreadsheets.google.com/feeds/spreadsheets/private/full"],
 "entry"=>
  [{"category"=>
     [{"term"=>"http://schemas.google.com/spreadsheets/2006#spreadsheet",
       "scheme"=>"http://schemas.google.com/spreadsheets/2006"}],
    "title"=>[{"type"=>"text", "content"=>"blank"}],
    "author"=>
     [{"name"=>["Test-account"],
       "email"=>["my email"]}],
    "id"=>
     ["http://spreadsheets.google.com/feeds/spreadsheets/private/full/o04927555739056712307.3387874275736238738"],
    "content"=>{"type"=>"text", "content"=>"blank"},
    "link"=>
    [ snipping out the rest of the XML ]

워크시트 얻기

따라서 위의 출력에서 볼 수 있듯이 피드에 6개의 스프레드시트가 포함되어 있습니다. 이 글을 짧게 유지하기 위해 나머지 XML 출력뿐만 아니라 위의 XML 출력에서 나머지 부분을 잘라냈습니다. 이 스프레드시트를 자세히 살펴보려면 몇 가지 단계를 더 수행해야 합니다.

  1. 스프레드시트 키 가져오기
  2. 스프레드시트 키를 사용하여 워크시트 피드 가져오기
  3. 사용할 워크시트의 ID 가져오기
  4. 셀 피드 또는 listFeed를 요청하여 워크시트의 실제 콘텐츠에 액세스합니다.

복잡한 작업처럼 들릴 수 있지만 몇 가지 간단한 메서드를 작성하면 아주 간단하다는 것을 보여드리겠습니다. 셀 피드와 listFeed는 워크시트의 실제 셀 콘텐츠를 서로 다르게 표현합니다. listFeed는 정보의 전체 행을 나타내며 새 데이터를 POST하는 데 권장됩니다. 셀 피드는 개별 셀을 나타내며 PUT을 사용하여 개별 셀 업데이트 또는 여러 개별 셀의 일괄 업데이트에 사용됩니다. 자세한 내용은 Google Sheets API 문서를 참고하세요.

먼저 스프레드시트 키 (위의 XML 출력에 강조표시됨)를 추출한 다음 워크시트 피드를 가져와야 합니다.

# Extract the spreadsheet key from our datastructure
irb(main):040:0> spreadsheet_key = \ 
irb(main):041:0* doc["entry"][0]["id"][0][/full\/(.*)/, 1]
=> "o04927555739056712307.3387874275736238738"

# Using our get_feed method, let's obtain the worksheet feed
irb(main):042:0> worksheet_feed_uri = \ 
irb(main):043:0* "http://spreadsheets.google.com/feeds/worksheets/#{spreadsheet_key}/private/full"
=> "http://spreadsheets.google.com/feeds/worksheets/o04927555739056712307.3387874275736238738/private/full"

irb(main):044:0> worksheet_response = get_feed(worksheet_feed_uri, headers)
=> #<Net::HTTPOK 200 OK readbody=true>

# Parse the XML into a datastructure
irb(main):045:0> worksheet_data = \ 
irb(main):046:0* XmlSimple.xml_in(worksheet_response.body, 'KeyAttr' => 'name')
=> {"totalResults"=>["1"], "category"=>[{"term ... [ snip ]

# And pretty-print it
irb(main):047:0> pp worksheet_data
{"totalResults"=>["1"],
 "category"=>
  [{"term"=>"http://schemas.google.com/spreadsheets/2006#worksheet",
    "scheme"=>"http://schemas.google.com/spreadsheets/2006"}],
 "title"=>[{"type"=>"text", "content"=>"blank"}],
 "author"=>
  [{"name"=>["test.api.jhartmann"],
    "email"=>["test.api.jhartmann@gmail.com"]}],
 "startIndex"=>["1"],
 "id"=>
  ["http://spreadsheets.google.com/feeds/worksheets/o04927555739056712307.3387874275736238738/private/full"],
 "entry"=>
  [{"category"=>
     [{"term"=>"http://schemas.google.com/spreadsheets/2006#worksheet",
       "scheme"=>"http://schemas.google.com/spreadsheets/2006"}],
    "title"=>[{"type"=>"text", "content"=>"Sheet 1"}],
    "rowCount"=>["100"],
    "colCount"=>["20"],
    "id"=>
     ["http://spreadsheets.google.com/feeds/worksheets/o04927555739056712307.3387874275736238738/private/full/od6"],
    "content"=>{"type"=>"text", "content"=>"Sheet 1"},
    "link"=>
     [{"href"=>
        "http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full",
       "rel"=>"http://schemas.google.com/spreadsheets/2006#listfeed",
       "type"=>"application/atom+xml"},
      {"href"=>
        "http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full",
       "rel"=>"http://schemas.google.com/spreadsheets/2006#cellsfeed",
       "type"=>"application/atom+xml"},
    [ snip: cutting off the rest of the XML ]

여기서 볼 수 있듯이 이제 listFeed 및cellFeed에 액세스하는 링크(highlighted above)를 찾을 수 있습니다. listFeed를 살펴보기 전에 현재 샘플 스프레드시트에 어떤 데이터가 있는지 빠르게 설명하겠습니다. 그러면 찾고 있는 항목을 알 수 있습니다.

스프레드시트는 다음과 같이 매우 간단합니다.

language웹사이트
자바http://java.com
phphttp://php.net

이 데이터는 listFeed에서 다음과 같이 표시됩니다.

irb(main):048:0> listfeed_uri = \
irb(main):049:0* worksheet_data["entry"][0]["link"][0]["href"]
=> "http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full"

irb(main):050:0> response = get_feed(listfeed_uri, headers)
=> #<Net::HTTPOK 200 OK readbody=true>
irb(main):051:0> listfeed_doc = \ 
irb(main):052:0* XmlSimple.xml_in(response.body, 'KeyAttr' => 'name')
=> {"totalResults"=>["2"], "category"=>[{"term" ... [ snip ]

# Again we parse the XML and then pretty print it
irb(main):053:0> pp listfeed_doc
{"totalResults"=>["2"],
 "category"=>
  [{"term"=>"http://schemas.google.com/spreadsheets/2006#list",
    "scheme"=>"http://schemas.google.com/spreadsheets/2006"}],
 "title"=>[{"type"=>"text", "content"=>"Programming language links"}],
 "author"=>
  [{"name"=>["test.api.jhartmann"],
    "email"=>["test.api.jhartmann@gmail.com"]}],
 "startIndex"=>["1"],
 "id"=>
  ["http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full"],
 "entry"=>
  [{"category"=>
     [{"term"=>"http://schemas.google.com/spreadsheets/2006#list",
       "scheme"=>"http://schemas.google.com/spreadsheets/2006"}],
    "language"=>["java"],
    "title"=>[{"type"=>"text", "content"=>"ruby"}],
    "website"=>["http://java.com"],
    "id"=>
     ["http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full/cn6ca"],
    "content"=>
     {"type"=>"text", "content"=>"website: http://java.com"},
    "link"=>
     [{"href"=>
        "http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full/cn6ca",
       "rel"=>"self",
       "type"=>"application/atom+xml"},
      {"href"=>
        "http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full/cn6ca/1j81anl6096",
       "rel"=>"edit",
       "type"=>"application/atom+xml"}],
    "updated"=>["2008-03-20T22:19:51.739Z"]},
   {"category"=>
     [{"term"=>"http://schemas.google.com/spreadsheets/2006#list",
       "scheme"=>"http://schemas.google.com/spreadsheets/2006"}],
    "language"=>["php"],
    "title"=>[{"type"=>"text", "content"=>"php"}],
    "website"=>["http://php.net"],
    "id"=>
     ["http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full/cokwr"],
    "content"=>{"type"=>"text", "content"=>"website: http://php.net"},
    [ snip ]

보시다시피 listFeed는 각 행에 항목을 만들어 워크시트의 콘텐츠를 반환합니다. 스프레드시트의 첫 번째 행에 셀 헤더가 포함되어 있는 다음 해당 행의 데이터를 기반으로 XML 헤더가 동적으로 생성된다고 가정합니다. 실제 XML을 보면 자세히 알 수 있습니다.

<?xml version='1.0' encoding='UTF-8'?><feed [ snip namespaces ]>
<id>http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full</id>
<updated>2008-03-20T22:19:51.739Z</updated>
<category scheme='http://schemas.google.com/spreadsheets/2006' term='http://schemas.google.com/spreadsheets/2006#list'/>

<title type='text'>Programming language links</title>
[ snip: cutting out links and author information ]
<entry>
    <id>http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full/cn6ca</id>
    [ snip: updated and category ]
    <title type='text'>java</title>
    <content type='text'>website: http://java.com</content>
    <link rel='self' type='application/atom+xml' href='http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full/cn6ca'/>
    <link rel='edit' type='application/atom+xml' href='http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full/cn6ca/1j81anl6096'/>
    <gsx:language>java</gsx:language>
    <gsx:website>http://java.com</gsx:website>
</entry>
<entry>
    <id>http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full/cokwr</id>
    [ snip: updated and category ]
    <title type='text'>php</title>
    <content type='text'>website: http://php.net</content>
    <link rel='self' type='application/atom+xml' href='http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full/cokwr'/>
    <link rel='edit' type='application/atom+xml' href='http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full/cokwr/41677fi0nc'/>
    <gsx:language>php</gsx:language>
    <gsx:website>http://php.net</gsx:website>
</entry>
</feed>

빠른 비교를 위해 동일한 정보가 셀 피드에 어떻게 표시되는지 살펴보겠습니다.

# Extract the cellfeed link
irb(main):054:0> cellfeed_uri = \
irb(main):055:0* worksheet_data["entry"][0]["link"][1]["href"]
=> "http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full"
irb(main):056:0> response = \ 
irb(main):057:0* get_feed(cellfeed_uri, headers)
=> #<Net::HTTPOK 200 OK readbody=true>

# Parse into datastructure and print
irb(main):058:0> cellfeed_doc = \ 
irb(main):059:0* XmlSimple.xml_in(response.body, 'KeyAttr' => 'name')
=> {"totalResults"=>["6"], [ snip ]

irb(main):060:0> pp cellfeed_doc
{"totalResults"=>["6"],
 "category"=>
  [{"term"=>"http://schemas.google.com/spreadsheets/2006#cell",
    "scheme"=>"http://schemas.google.com/spreadsheets/2006"}],
 "title"=>[{"type"=>"text", "content"=>"Programming language links"}],
 "rowCount"=>["101"],
 "colCount"=>["20"],
 "author"=>
  [{"name"=>["test.api.jhartmann"],
    "email"=>["test.api.jhartmann@gmail.com"]}],
 "startIndex"=>["1"],
 "id"=>
  ["http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full"],
 "entry"=>
  [{"category"=>
     [{"term"=>"http://schemas.google.com/spreadsheets/2006#cell",
       "scheme"=>"http://schemas.google.com/spreadsheets/2006"}],
    "cell"=>
     [{"col"=>"1",
       "row"=>"1",
       "content"=>"language",
       "inputValue"=>"language"}],
    "title"=>[{"type"=>"text", "content"=>"A1"}],
    "id"=>
     ["http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full/R1C1"],
    "content"=>{"type"=>"text", "content"=>"language"},
    "link"=>
     [{"href"=>
        "http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full/R1C1",
       "rel"=>"self",
       "type"=>"application/atom+xml"},
      {"href"=>
        "http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full/R1C1/8srvbs",
       "rel"=>"edit",
       "type"=>"application/atom+xml"}],
    "updated"=>["2008-03-20T22:19:51.739Z"]},
    [ snip ]

보시다시피 각 셀마다 하나씩 6개의 항목이 반환됩니다. 'language'라는 단어를 포함하는 셀 A1의 값 외에 다른 모든 출력은 잘라냈습니다. 위에 표시된 수정 링크도 확인하세요. 이 링크에는 끝에 버전 문자열 (8srvbs)이 포함되어 있습니다. 버전 데이터는 셀 데이터의 업데이트 시 중요하며, 이 내용은 이 문서의 뒷부분에서 설명합니다. 업데이트를 덮어쓰지 않도록 해줍니다. 셀 데이터를 업데이트하기 위해 PUT 요청을 할 때마다 요청에 셀의 최신 버전 문자열을 포함해야 합니다. 각 업데이트 후에 새 버전 문자열이 반환됩니다.

listFeed에 콘텐츠 게시

콘텐츠를 게시하기 위해 가장 먼저 필요한 것은 listFeed의 POST 링크입니다. 이 링크는 목록 피드가 요청될 때 반환됩니다. URL http://schemas.google.com/g/2005#postrel 속성의 값으로 포함됩니다. 이 링크 요소를 파싱하고 href 속성을 추출해야 합니다. 먼저, 보다 쉽게 게시할 수 있도록 간단한 메서드를 만듭니다.

irb(main):061:0> def post(uri, data, headers)
irb(main):062:1> uri = URI.parse(uri)
irb(main):063:1> http = Net::HTTP.new(uri.host, uri.port)
irb(main):064:1> return http.post(uri.path, data, headers)
irb(main):065:1> end
=> nil
# Set up our POST url
irb(main):066:0> post_url = \ 
irb(main):067:0* "http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full"
=> "http://spreadsheets.google.com/feeds/list/o04927555739056712307.3387874275736238738/od6/private/full"

# We must use 'application/atom+xml' as MIME type so let's change our headers 
# which were still set to 'application/x-www-form-urlencoded' when we sent our 
# ClientLogin information over https
irb(main):068:0> headers["Content-Type"] = "application/atom+xml"
=> "application/atom+xml"

# Setting up our data to post, using proper namespaces
irb(main):069:0> new_row = \ 
irb(main):070:0* '<atom:entry xmlns:atom="http://www.w3.org/2005/Atom">' << 
irb(main):071:0* '<gsx:language xmlns:gsx="http://schemas.google.com/spreadsheets/2006/extended">' <<
irb(main):072:0* 'ruby</gsx:language>' << 
irb(main):073:0* '<gsx:website xmlns:gsx="http://schemas.google.com/spreadsheets/2006/extended">' <<
irb(main):074:0* 'http://ruby-lang.org</gsx:website>' << 
irb(main):075:0* '</atom:entry>'
=> "<atom:entry xmlns:atom=\"http://www.w3.org/2005/Atom\"><gsx:language ... [ snip ] 

# Performing the post
irb(main):076:0> post_response = post(post_url, new_row, headers) 
=> #<Net::HTTPCreated 201 Created readbody=true>

201 상태는 게시물이 성공했음을 나타냅니다.

셀 피드를 사용하여 콘텐츠 업데이트

문서에서 셀 피드는 기존 콘텐츠에 대한 PUT 요청을 선호합니다. 하지만 위의 셀 피드에서 실제 가져온 데이터는 이미 실제 스프레드시트에 있던 데이터만 새로운 정보를 추가하려면 어떻게 해야 할까요? 데이터를 입력하려는 빈 셀마다 요청하면 됩니다. 아래 스니펫은 Python 프로그래밍 언어에 관한 정보를 삽입하려는 빈 셀 R5C1 (행 5, 열 1)을 검색하는 방법을 보여줍니다.

원래 변수 cellfeed_uri에는 셀 피드 자체의 URI만 포함되었습니다. 이제 편집하려는 셀을 추가하고 셀 버전 문자열을 가져와 수정합니다.

# Set our query URI
irb(main):077:0> cellfeed_query = cellfeed_uri + '/R5C1'
=> "http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full/R5C1"

# Request the information to extract the edit link
irb(main):078:0> cellfeed_data = get_feed(cellfeed_query, headers)
=> #<Net::HTTPOK 200 OK readbody=true>
irb(main):079:0> cellfeed_data.body
=> "<?xml version='1.0' encoding='UTF-8'?>
<entry xmlns='http://www.w3.org/2005/Atom' xmlns:gs='http://schemas.google.com/spreadsheets/2006' xmlns:batch='http://schemas.google.com/gdata/batch'>
<id>http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full/R5C1</id>
<updated>2008-03-24T21:55:36.462Z</updated>
<category scheme='http://schemas.google.com/spreadsheets/2006' term='http://schemas.google.com/spreadsheets/2006#cell'/>
<title type='text'>A5</title>
<content type='text'>
</content>
<link rel='self' type='application/atom+xml' href='http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full/R5C1'/>
<link rel='edit' type='application/atom+xml' href='http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full/R5C1/47pc'/>
<gs:cell row='5' col='1' inputValue=''>
</gs:cell>
</entry>"

위의 코드 목록에서 확인할 수 있듯이 버전 문자열은 47pc입니다. (오른쪽 끝까지 스크롤해야 할 수도 있습니다.) 보다 쉽게 할 수 있도록 관심 있는 셀의 버전 문자열을 가져오는 편의 메서드를 만들어 보겠습니다.

irb(main):080:0> def get_version_string(uri, headers=nil)
irb(main):081:1> response = get_feed(uri, headers)
irb(main):082:1> require 'rexml/document'
irb(main):083:1> xml = REXML::Document.new response.body
irb(main):084:1> edit_link = REXML::XPath.first(xml, '//[@rel="edit"]')
irb(main):085:1> edit_link_href = edit_link.attribute('href').to_s
irb(main):086:1> return edit_link_href.split(/\//)[10]
irb(main):087:1> end
=> nil

# A quick test
irb(main):088:0> puts get_version_string(cellfeed_query, headers)
47pc
=> nil

PUT 요청을 처리하는 동안 PUT 요청을 수행하기 위한 메서드를 작성할 수도 있고, 전체 일괄 업데이트를 수행하기 위한 메서드를 작성해도 됩니다. 이 함수는 다음 변수가 포함된 해시 배열을 가져옵니다.

  • :batch_id - 일괄 요청의 각 부분에 대한 고유 식별자입니다.
  • :cell_id - R#C# 형식으로 업데이트할 셀의 ID이며 여기서 셀 A1은 R1C1로 표시됩니다.
  • :data - 삽입하려는 데이터입니다.

irb(main):088:0> def batch_update(batch_data, cellfeed_uri, headers)
irb(main):089:1> batch_uri = cellfeed_uri + '/batch'
irb(main):090:1> batch_request = <<FEED
irb(main):091:1" <?xml version="1.0" encoding="utf-8"?> \
irb(main):092:1" <feed xmlns="http://www.w3.org/2005/Atom" \
irb(main):093:1" xmlns:batch="http://schemas.google.com/gdata/batch" \
irb(main):094:1" xmlns:gs="http://schemas.google.com/spreadsheets/2006" \
irb(main):095:1" xmlns:gd="http://schemas.google.com/g/2005">
irb(main):096:1" <id>#{cellfeed_uri}</id>
irb(main):097:1" FEED
irb(main):098:1> batch_data.each do |batch_request_data|
irb(main):099:2* version_string = get_version_string(cellfeed_uri + '/' + batch_request_data[:cell_id], headers)
irb(main):100:2> data = batch_request_data[:data]
irb(main):101:2> batch_id = batch_request_data[:batch_id]
irb(main):102:2> cell_id = batch_request_data[:cell_id]
irb(main):103:2> row = batch_request_data[:cell_id][1,1]
irb(main):104:2> column = batch_request_data[:cell_id][3,1]
irb(main):105:2> edit_link = cellfeed_uri + '/' + cell_id + '/' + version_string
irb(main):106:2> batch_request<< <<ENTRY
irb(main):107:2" <entry>
irb(main):108:2" <gs:cell col="#{column}" inputValue="#{data}" row="#{row}"/>
irb(main):109:2" <batch:id>#{batch_id}</batch:id>
irb(main):110:2" <batch:operation type="update" />
irb(main):111:2" <id>#{cellfeed_uri}/#{cell_id}</id>
irb(main):112:2" <link href="#{edit_link}" rel="edit" type="application/atom+xml" />
irb(main):113:2" </entry>
irb(main):114:2" ENTRY
irb(main):115:2> end
irb(main):116:1> batch_request << '</feed>'
irb(main):117:1> return post(batch_uri, batch_request, headers)
irb(main):118:1> end
=> nil

# Our sample batch data to insert information about the Python programming language into our worksheet
irb(main):119:0> batch_data = [ \
irb(main):120:0* {:batch_id => 'A', :cell_id => 'R5C1', :data => 'Python'}, \ 
irb(main):121:0* {:batch_id => 'B', :cell_id => 'R5C2', :data => 'http://python.org' } ]
=> [{:cell_id=>"R5C1", :data=>"Python", :batch_id=>"A"}=>{:cell_id=>"R5C2", :data=>"http://python.org", :batch_id=>"B"}]

# Perform the update
irb(main):122:0> response = batch_update(batch_data, cellfeed_uri, headers)
=> #<Net::HTTPOK 200 OK readbody=true>

# Parse the response.body XML and print it
irb(main):123:0> response_xml = XmlSimple.xml_in(response.body, 'KeyAttr' => 'name')
=> [ snip ]

irb(main):124:0> pp response_xml
{"title"=>[{"type"=>"text", "content"=>"Batch Feed"}],
 "xmlns:atom"=>"http://www.w3.org/2005/Atom",
 "id"=>
  ["http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full"],
 "entry"=>
  [{"status"=>[{"code"=>"200", "reason"=>"Success"}],
    "category"=>
     [{"term"=>"http://schemas.google.com/spreadsheets/2006#cell",
       "scheme"=>"http://schemas.google.com/spreadsheets/2006"}],
    "cell"=>
     [{"col"=>"1", "row"=>"5", "content"=>"Python", "inputValue"=>"Python"}],
    "title"=>[{"type"=>"text", "content"=>"A5"}],
    "id"=>
     ["http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full/R5C1",
      "A"],
    "operation"=>[{"type"=>"update"}],
    "content"=>{"type"=>"text", "content"=>"Python"},
    "link"=>
     [{"href"=>
        "http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full/R5C1",
       "rel"=>"self",
       "type"=>"application/atom+xml"},
      {"href"=>
        "http://spreadsheets.google.com/feeds/cells/o04927555739056712307.3387874275736238738/od6/private/full/R5C1/49kwzg",
       "rel"=>"edit",
       "type"=>"application/atom+xml"}],
    "updated"=>["2008-03-27T15:48:48.470Z"]},
    [ snip ]

보시다시피 200 OK 응답 코드를 수신했기 때문에 일괄 요청에 성공했습니다. 응답 XML을 파싱하면 response_data 배열에서 설정한 각 개별 :batch_id에 대해 별도의 메시지가 반환됩니다. 일괄 처리에 대한 자세한 내용은 GData의 일괄 처리 문서를 참고하세요.

마무리

보시다시피 Ruby의 대화형 셸을 사용하여 Google 데이터 API를 사용하는 것은 매우 쉽습니다. listFeed와 셀 피드를 모두 사용하여 스프레드시트와 워크시트에 액세스할 수 있었습니다. 또한 POST 요청을 사용하여 새 데이터를 삽입한 후 약 120줄의 코드만으로 일괄 업데이트를 수행하는 메서드를 작성했습니다. 이 시점에서 이러한 간단한 메서드 중 일부를 클래스에 래핑하고 재사용 가능한 프레임워크를 빌드하는 것은 너무 어렵지 않습니다.

즐겨 사용하는 Google Data API에서 이러한 도구를 사용하는 방법에 관해 궁금한 점이 있다면 토론방에 참여해 주세요.

위에서 자세히 설명한 코드 샘플이 포함된 클래스 파일은 http://code.google.com/p/google-data-samples-ruby에서 찾을 수 있습니다.

이 도움말에 대해 토론합니다.