Sử dụng Ruby với API Dữ liệu của Google

Jochen Hartmann, Nhóm Google Data API
Tháng 4 năm 2008

Giới thiệu

Ruby là một ngôn ngữ kịch bản động được nhiều người chú ý trong những năm gần đây do khung phát triển web phổ biến của Rails. Bài viết này sẽ giải thích cách sử dụng Ruby để tương tác với các dịch vụ Google Data API. Chúng tôi sẽ không tập trung vào Rails, thay vào đó chúng tôi quan tâm hơn đến việc giải thích các lệnh HTTP cơ bản và cấu trúc của các nguồn cấp dữ liệu của mình. Bạn có thể theo dõi tất cả ví dụ trình bày ở đây từ dòng lệnh bằng cách sử dụng irb, môi trường shell tương tác của Ruby.

Như bạn có thể đã nhớ từ bài viết cc, API dữ liệu của Google sử dụng Giao thức xuất bản Atom để thể hiện, tạo và cập nhật tài nguyên web. Ưu điểm của giao thức này là động từ HTTP chuẩn được dùng để xây dựng yêu cầu được trả lời bằng mã trạng thái HTTP chuẩn.

Các động từ mà chúng ta sẽ sử dụng trong bài viết này là GET để truy xuất nội dung, POST để tải nội dung mới lên và PUT để cập nhật nội dung hiện có. Một số mã tiêu chuẩn mà bạn có thể gặp khi sử dụng Google Data API là 200 để thể hiện sự thành công trong việc truy xuất nguồn cấp dữ liệu hoặc mục nhập, hoặc 201 để thể hiện việc tạo hoặc cập nhật tài nguyên thành công. Nếu xảy ra lỗi, chẳng hạn như khi bạn gửi yêu cầu không đúng định dạng, thì mã 400 (có nghĩa là "Yêu cầu không hợp lệ") sẽ được gửi lại. Thông báo chi tiết hơn sẽ được cung cấp trong nội dung phản hồi, giải thích chính xác vấn đề xảy ra.

Ruby cung cấp một tùy chọn gỡ lỗi đẹp như một phần của mô-đun "Net". Tuy nhiên, để đảm bảo mẫu mã ngắn gọn này, tôi chưa bật tính năng này tại đây.

Nhận và cài đặt Ruby

Bạn có thể cài đặt Ruby bằng hầu hết các hệ thống quản lý gói nếu bạn đang sử dụng Linux. Đối với các hệ điều hành khác và để lấy mã nguồn đầy đủ, vui lòng truy cập vào http://www.ruby-lang.org/en/downloads/. Irb, shell tương tác mà chúng ta sẽ sử dụng cho các ví dụ này phải được cài đặt theo mặc định. Để làm theo các ví dụ về mã được liệt kê ở đây, bạn cũng cần cài đặt XmlSimple, một thư viện nhỏ để phân tích cú pháp XML thành cấu trúc dữ liệu Ruby. Để có được/cài đặt XmlSimple, vui lòng truy cập vào http://xml-simple.rubyforge.org/

Sau khi có một bản sao Ruby đang chạy trên máy của mình, bạn có thể sử dụng gói Net:HTTP để thực hiện các yêu cầu cơ bản cho Dịch vụ dữ liệu của Google. Đoạn mã dưới đây cho biết cách thực hiện các thao tác nhập cần thiết từ shell tương tác của Ruby. Việc chúng tôi đang làm là yêu cầu gói "net/http", phân tích cú pháp URL cho nguồn cấp dữ liệu video được xếp hạng cao nhất trên YouTube rồi thực hiện yêu cầu 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>

Yêu cầu đó phải lặp lại khá nhiều XML với dòng lệnh. Bạn có thể nhận thấy rằng tất cả các mặt hàng đều nằm trong phần tử <feed> và được gọi là phần tử <entry>. Chúng ta hãy chưa lo lắng về định dạng XML. Tôi chỉ muốn giải thích cách tạo một yêu cầu API Dữ liệu cơ bản của Google bằng HTTP. Chúng ta sẽ chuyển API ngay bây giờ và tập trung vào Bảng tính vì thông tin chúng ta có thể gửi và truy xuất trở nên "phù hợp hơn với dòng lệnh".

Xác thực | Sử dụng API bảng tính Google

Chúng ta sẽ bắt đầu lại bằng cách truy xuất nguồn cấp dữ liệu các phần tử mục nhập. Tuy nhiên, lần này chúng ta sẽ làm việc với bảng tính của riêng mình. Để làm được việc đó, trước tiên, chúng ta phải xác thực bằng dịch vụ Tài khoản Google.

Như bạn có thể nhớ lại từ tài liệu về Xác thực GData, có hai cách để xác thực với các dịch vụ của Google. AuthSub là dành cho các ứng dụng dựa trên nền tảng web và tóm tắt là có quy trình trao đổi mã thông báo. Lợi ích thực sự của AuthSub là ứng dụng của bạn không cần lưu trữ thông tin xác thực của người dùng. ClientLogin dành cho các ứng dụng "đã cài đặt". Trong quá trình ClientLogin, tên người dùng và mật khẩu sẽ được gửi tới các dịch vụ của Google qua https cùng với chuỗi xác định dịch vụ mà bạn đang muốn sử dụng. Dịch vụ API bảng tính của Google được xác định bằng chuỗi wise.

Hãy chuyển về giao diện tương tác của chúng ta, hãy xác thực bằng Google. Xin lưu ý rằng chúng tôi đang sử dụng https để gửi yêu cầu xác thực và thông tin xác thực:

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 ]

OK. Bây giờ, chúng ta đã được xác thực, hãy thử truy xuất bảng tính của riêng mình bằng cách gửi yêu cầu

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

Vì đây là yêu cầu được xác thực nên chúng ta cũng muốn truyền tiêu đề. Trên thực tế, vì chúng ta sẽ tạo nhiều yêu cầu cho nhiều nguồn cấp dữ liệu nên chúng ta cũng có thể gói chức năng này vào một hàm đơn giản để gọi 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> ...

Một lần nữa, chúng tôi thấy nhiều XML mà tôi đã nhấn mạnh ở trên vì bạn không cần phải lo lắng về việc giải mã từ dòng lệnh. Để thân thiện hơn với người dùng, hãy để chúng tôi phân tích cú pháp của cấu trúc đó thành một cấu trúc dữ liệu sử dụng 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 ]

Nhận trang tính

Như bạn có thể thấy trong dữ liệu đầu ra ở trên, nguồn cấp dữ liệu của tôi chứa 6 bảng tính. Để ngắn gọn bài viết này, tôi đã cắt bỏ phần đầu ra XML ở trên (cũng như trong hầu hết các danh sách khác). Để tìm hiểu sâu hơn về bảng tính này, chúng tôi sẽ cần thực hiện thêm một vài bước:

  1. Lấy khóa bảng tính
  2. Dùng khóa bảng tính để lấy nguồn cấp dữ liệu trang tính của chúng tôi
  3. Lấy mã nhận dạng cho trang tính mà chúng ta muốn dùng
  4. Yêu cầu ô dataFeed hoặc listFeed để truy cập vào nội dung thực tế của trang tính

Nghe có vẻ khá nhiều việc phải làm nhưng tôi sẽ cho bạn thấy rằng mọi việc đều dễ dàng nếu chúng ta viết một số phương pháp đơn giản. cellFeed và listFeed là hai đại diện khác nhau cho nội dung ô thực tế của một trang tính. listFeed đại diện cho toàn bộ hàng thông tin và được đề xuất để POST dữ liệu mới. Ô nguồn cấp dữ liệu đại diện cho từng ô riêng lẻ và được dùng để cập nhật hàng loạt hoặc cập nhật hàng loạt cho nhiều ô riêng lẻ (cả hai đều sử dụng PUT). Vui lòng tham khảo tài liệu về API Google Bảng tính để biết thêm thông tin chi tiết.

Trước tiên, chúng ta cần trích xuất khóa bảng tính (như được đánh dấu trong đầu ra XML ở trên) để lấy nguồn cấp dữ liệu trang tính:

# 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 ]

Như bạn có thể thấy ở đây, giờ đây chúng ta có thể tìm thấy các đường liên kết (highlighted above) để truy cập vào listFeed vàcellFeed. Trước khi tìm hiểu về danh sách nguồn cấp dữ liệu list, tôi xin được giải thích nhanh về dữ liệu đang có trong bảng tính mẫu để bạn có thể biết được dữ liệu chúng tôi đang tìm:

Bảng tính của chúng tôi rất đơn giản và chỉ có dạng như sau:

languagetrang web
javahttp://java.com
phphttp://php.net

Đây là giao diện của dữ liệu này trong 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 ]

Như bạn có thể thấy, listFeed trả về nội dung của trang tính bằng cách tạo một mục nhập cho mỗi hàng. Trường này giả định rằng hàng đầu tiên của bảng tính chứa tiêu đề ô của bạn rồi tạo tiêu đề XML một cách linh động dựa trên dữ liệu trong hàng đó. Việc xem xét XML thực tế sẽ giúp giải thích rõ hơn về điều này:

<?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>

Để so sánh nhanh, hãy để chúng tôi xem xét cách thể hiện cùng một thông tin trong ô 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 ]

Như bạn có thể thấy ở đây, 6 mục nhập sẽ được trả về, mỗi mục cho một ô. Tôi đã cắt bỏ tất cả kết quả khác ngoài giá trị của ô A1, chứa từ 'ngôn ngữ'. Ngoài ra, hãy lưu ý đường liên kết edit (chỉnh sửa) xuất hiện ở trên. Đường liên kết này chứa một chuỗi phiên bản (8srvbs) ở cuối. Chuỗi phiên bản quan trọng khi cập nhật dữ liệu ô, như chúng ta sẽ thực hiện ở cuối bài viết này. Đảm bảo rằng các bản cập nhật sẽ không bị ghi đè. Bất cứ khi nào thực hiện yêu cầu PUT để cập nhật dữ liệu ô, bạn phải bao gồm chuỗi phiên bản mới nhất của ô trong yêu cầu. Hệ thống sẽ trả về một chuỗi phiên bản mới sau mỗi lần cập nhật.

Đăng nội dung lên listFeed

Việc đầu tiên chúng ta cần làm để đăng nội dung là đường liên kết POST cho nguồn cấp dữ liệu listFeed. Đường liên kết này sẽ được trả về khi bạn yêu cầu nguồn cấp dữ liệu danh sách. Tham số này sẽ chứa URL http://schemas.google.com/g/2005#post làm giá trị cho thuộc tính rel. Bạn cần phân tích cú pháp thành phần liên kết này và trích xuất thuộc tính href của thành phần đó. Đầu tiên, chúng ta sẽ tạo một phương thức nhỏ để dễ dàng đăng bài hơn:

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>

Trạng thái 201 cho biết bài đăng của chúng ta đã thành công.

Sử dụngcellFeed để cập nhật nội dung

Trong tài liệu này, chúng ta có thể thấy nguồn cấp dữ liệu di động ưu tiên yêu cầu PUT trên nội dung hiện có. Nhưng vì thông tin mà chúng tôi truy xuất từ các ô dataFeed ở trên chỉ là dữ liệu đã có trong bảng tính thực tế, làm cách nào để thêm thông tin mới? Chúng ta chỉ cần tạo một yêu cầu cho từng ô trống mà chúng ta muốn nhập dữ liệu vào. Đoạn mã dưới đây cho biết cách truy xuất ô trống R5C1 (Hàng 5, Cột 1) trong đó chúng ta muốn chèn một số thông tin về ngôn ngữ lập trình Python.

Biến ban đầu của chúng tôi cellfeed_uri chỉ chứa URI cho chính nguồn cấp dữ liệu. Bây giờ, chúng ta muốn thêm ô mà chúng ta muốn chỉnh sửa và lấy chuỗi phiên bản ô đó để chỉnh sửa:

# 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>"

Như bạn có thể thấy trong trang thông tin mã ở trên, chuỗi phiên bản là 47pc. (Có thể bạn phải cuộn hết cỡ sang bên phải.) Để giúp mọi việc trở nên dễ dàng hơn, hãy để chúng ta tạo một phương thức thuận tiện giúp chúng ta có chuỗi phiên bản cho bất kỳ ô nào mà chúng ta quan tâm:

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

Khi đó, chúng ta cũng có thể viết một phương thức để thực hiện yêu cầu PUT, hoặc tốt hơn là chúng ta viết một phương thức để thực hiện toàn bộ quá trình cập nhật theo lô. Hàm của chúng ta sẽ lấy một loạt hàm băm chứa các biến sau:

  • :batch_id – Giá trị nhận dạng duy nhất cho mỗi phần của yêu cầu hàng loạt.
  • :cell_id – Mã nhận dạng của ô cần cập nhật ở định dạng R#C#, trong đó ô A1 sẽ được biểu thị dưới dạng R1C1.
  • :data – Dữ liệu mà chúng ta muốn chèn.

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 ]

Như bạn có thể thấy, yêu cầu xử lý hàng loạt của chúng tôi đã thành công vì chúng tôi đã nhận được mã phản hồi 200 OK. Khi phân tích cú pháp XML phản hồi, chúng ta có thể thấy rằng một thông báo riêng biệt được trả về cho từng :batch_id riêng lẻ mà chúng ta đã đặt trong mảng response_data. Để biết thêm thông tin về quá trình xử lý hàng loạt, vui lòng tham khảo tài liệu Xử lý hàng loạt trong GData.

Kết luận

Như bạn đã thấy, rất dễ dàng sử dụng shell tương tác của Ruby để chơi với Google Data API. Chúng tôi có thể truy cập vào bảng tính và trang tính của mình bằng cách sử dụng cả listFeed vàcellFeed. Ngoài ra, chúng tôi đã chèn một số dữ liệu mới bằng cách sử dụng yêu cầu POST, sau đó viết các phương thức để thực hiện cập nhật theo lô chỉ với khoảng 120 dòng mã. Từ thời điểm này, không quá khó để đưa một số phương thức đơn giản này vào các lớp và tự tạo cho bạn một khung có thể sử dụng lại.

Vui lòng tham gia với chúng tôi trong các nhóm thảo luận nếu bạn có thắc mắc về việc sử dụng các công cụ này với Google Data API mà mình yêu thích.

Bạn có thể tìm thấy một tệp lớp có các mã mẫu nêu trên tại http://code.google.com/p/google-data-samples-ruby

Thảo luận về bài viết này!