שימוש ב-Ruby עם Google Data APIs

יוכן הרטמן, צוות Google Data APIs
אפריל 2008

מבוא

‫Ruby היא שפת סקריפטים דינמית שזכתה לתשומת לב רבה בשנים האחרונות, בגלל מסגרת הפיתוח הפופולרית של Rails לאתרי אינטרנט. במאמר הזה מוסבר איך להשתמש ב-Ruby כדי ליצור אינטראקציה עם שירותי Google Data API. לא נתמקד ב-Rails, אלא נסביר את פקודות ה-HTTP הבסיסיות ואת המבנה של הפידים שלנו. אפשר לפעול לפי כל הדוגמאות שמוצגות כאן משורת הפקודה באמצעות irb, המעטפת האינטראקטיבית של Ruby.

כפי שזוכרים מהמאמר על cURL, Google Data APIs משתמשים בפרוטוקול Atom Publishing כדי לייצג, ליצור ולעדכן משאבי אינטרנט. היתרון בפרוטוקול הזה הוא שמשתמשים בפעלים רגילים של HTTP כדי ליצור בקשות, והתשובות מתקבלות באמצעות קודי מצב רגילים של HTTP.

הפעלים שבהם נשתמש במאמר הזה הם GET לאחזור תוכן, POST להעלאת תוכן חדש ו-PUT לעדכון תוכן קיים. חלק מהקודים הסטנדרטיים שאולי תיתקלו בהם כשאתם משתמשים בממשקי Google Data API הם 200, שמייצג שליפה מוצלחת של פיד או רשומה, או 201, שמייצג יצירה או עדכון מוצלחים של משאב. אם משהו ישתבש, למשל אם תישלח בקשה פגומה, יישלח בחזרה קוד 400 (שמשמעותו 'בקשה פגומה'). גוף התשובה יכלול הודעה מפורטת יותר עם הסבר על מה בדיוק השתבש.

‫Ruby מספקת אפשרות טובה לניפוי באגים כחלק מהמודול Net. כדי שדוגמאות הקוד האלה לא יהיו ארוכות מדי, לא הפעלתי את האפשרות הזו כאן.

השגת Ruby והתקנה שלה

אם אתם משתמשים ב-Linux, אפשר להתקין את Ruby באמצעות רוב מערכות ניהול החבילות. למערכות הפעלה אחרות ולקבלת קוד המקור המלא, אפשר להיכנס לכתובת http://www.ruby-lang.org/en/downloads/. Irb, המעטפת האינטראקטיבית שבה נשתמש בדוגמאות האלה, אמורה להיות מותקנת כברירת מחדל. כדי להשתמש בדוגמאות הקוד שמופיעות כאן, תצטרכו גם להתקין את XmlSimple, ספרייה קטנה לניתוח XML למבני נתונים של Ruby. כדי לקבל או להתקין את XmlSimple, אפשר להיכנס אל http://xml-simple.rubyforge.org/

אחרי שמתקינים עותק של Ruby במחשב, אפשר להשתמש בחבילה Net:HTTP כדי לשלוח בקשות בסיסיות לשירותי הנתונים של Google. קטע הקוד הבא מראה איך לבצע את הייבוא הנדרש מהמעטפת האינטראקטיבית של Ruby. אנחנו דורשים את החבילה net/http, מנתחים את כתובת ה-URL של פיד הסרטונים עם הדירוג הכי גבוה מ-YouTube ואז מבצעים בקשת 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, רק רציתי להסביר איך לשלוח בקשת Google Data API בסיסית באמצעות HTTP. אנחנו נעבור עכשיו לממשקי API ונתמקד ב-Spreadsheets, כי המידע שאנחנו יכולים לשלוח ולאחזר הוא יותר 'ידידותי לשורת הפקודה'.

אימות | שימוש ב-Google Spreadsheets API

שוב נתחיל באחזור פיד של רכיבי הזנה. אבל הפעם אנחנו רוצים לעבוד עם גיליונות אלקטרוניים משלנו. כדי לעשות זאת, קודם צריך לבצע אימות באמצעות שירות חשבונות Google.

כפי שאתם בוודאי זוכרים מהמסמכים בנושא אימות GData, יש שתי דרכים לאימות בשירותים של Google. ‫AuthSub מיועד לאפליקציות מבוססות-אינטרנט, ובקיצור, הוא כולל תהליך של החלפת אסימונים. היתרון האמיתי של AuthSub הוא שהאפליקציה לא צריכה לאחסן את פרטי הכניסה של המשתמש. ‫ClientLogin מיועד לאפליקציות 'מותקנות'. בתהליך ClientLogin, שם המשתמש והסיסמה נשלחים לשירותי Google באמצעות HTTPS, יחד עם מחרוזת שמזהה את השירות שבו רוצים להשתמש. שירות Google Spreadsheets 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 שלמעלה (וגם ברוב הרשימות האחרות). כדי לבדוק את הגיליון האלקטרוני הזה לעומק, נצטרך לבצע עוד כמה שלבים:

  1. קבלת המפתח של הגיליון האלקטרוני
  2. שימוש במפתח הגיליון האלקטרוני כדי לקבל את פיד גיליון העבודה
  3. קבלת המזהה של הגיליון האלקטרוני שבו רוצים להשתמש
  4. שליחת בקשה ל-cellsFeed או ל-listFeed כדי לגשת לתוכן בפועל של גליון העבודה

יכול להיות שזה נשמע כמו הרבה עבודה, אבל אראה לך שזה די פשוט אם נכתוב כמה שיטות פשוטות. הפידים cellsFeed ו-listFeed הם שני ייצוגים שונים של התוכן בפועל של התאים בגיליון עבודה. רכיב listFeed מייצג שורה שלמה של מידע, ומומלץ להשתמש בו כדי לפרסם נתונים חדשים. ‫cellFeed מייצג תאים בודדים ומשמש לעדכונים של תאים בודדים או לעדכונים של קבוצות תאים (בשני המקרים נעשה שימוש ב-PUT). פרטים נוספים זמינים במסמכי התיעוד של Google Spreadsheets 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 ]

כפי שאפשר לראות כאן, עכשיו אפשר למצוא את הקישורים (highlighted above) לגישה אל listFeed ואל cellsFeed. לפני שנצלול ל-listFeed, אציג בקצרה את הנתונים שקיימים כרגע בגיליון האלקטרוני לדוגמה, כדי שתדעו מה אנחנו מחפשים:

הגיליון האלקטרוני שלנו פשוט מאוד ונראה כך:

languageאתר
javahttp://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>

כדי להשוות במהירות, נראה איך אותו מידע מוצג ב-cellsFeed:

# 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 רשומות, אחת לכל תא. השמטתי את כל הפלט האחר מלבד הערך של תא A1, שמכיל את המילה 'language'. אפשר גם לראות את הקישור עריכה שמופיע למעלה. הקישור הזה מכיל מחרוזת גרסה (8srvbs) בסוף. מחרוזת הגרסה חשובה כשמעדכנים נתונים בתאים, כמו שנעשה בסוף המאמר הזה. כך מוודאים שעדכונים לא יימחקו. בכל פעם ששולחים בקשת PUT לעדכון נתונים בתא, צריך לכלול בבקשה את מחרוזת הגרסה האחרונה של התא. מחרוזת גרסה חדשה תוחזר אחרי כל עדכון.

פרסום תוכן ב-listFeed

הדבר הראשון שאנחנו צריכים כדי לפרסם תוכן הוא קישור POST לרשימת הפידים. הקישור הזה יוחזר כשמתבצעת בקשה לפיד הרשימה. הוא יכיל את כתובת ה-URL http://schemas.google.com/g/2005#post כערך של המאפיין rel. תצטרכו לנתח את רכיב הקישור הזה ולחלץ ממנו את מאפיין 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 מציין שהבקשה שלנו לפרסום בוצעה בהצלחה.

שימוש ב-cellsFeed לעדכון תוכן

מהתיעוד אפשר לראות שפיד התאים מעדיף בקשות PUT על תוכן קיים. אבל מכיוון שהמידע שאחזרנו מה-cellsFeed קודם לכן היה רק הנתונים שכבר היו בגיליון האלקטרוני שלנו, איך אפשר להוסיף מידע חדש? פשוט צריך לשלוח בקשה לכל תא ריק שרוצים להזין בו נתונים. קטע הקוד שלמטה מראה איך לאחזר את התא הריק R5C1 (שורה 5, עמודה 1) שבו רוצים להוסיף מידע על שפת התכנות Python.

המשתנה המקורי cellfeed_uri הכיל רק את ה-URI של cellfeed עצמו. עכשיו אנחנו רוצים לצרף את התא שאנחנו רוצים לערוך ולקבל את מחרוזת הגרסה של התא הזה כדי לבצע את העריכה:

# 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, או אפילו יותר טוב, לכתוב שיטה לביצוע העדכון כולו. הפונקציה שלנו תקבל מערך של גיבובים שמכילים את המשתנים הבאים:

  • :batch_id – מזהה ייחודי לכל חלק בבקשה של קבוצת הפעולות.
  • :cell_id – מזהה התא לעדכון בפורמט R#C#, כאשר התא 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 של התשובה, אפשר לראות שמוחזרת הודעה נפרדת לכל :batch_id שהגדרנו במערך response_data. מידע נוסף על עיבוד באצווה זמין במסמך עיבוד באצווה ב-GData.

סיכום

כפי שראיתם, קל מאוד להשתמש במעטפת האינטראקטיבית של Ruby כדי להתנסות עם Google Data APIs. הצלחנו לגשת לגיליונות האלקטרוניים ולגליונות העבודה שלנו באמצעות listFeed ו-cellsFeed. בנוסף, הוספנו נתונים חדשים באמצעות בקשת POST, ואז כתבנו שיטות לביצוע עדכון באצווה עם כ-120 שורות קוד בלבד. מכאן, לא אמורה להיות בעיה גדולה לעטוף חלק מהשיטות הפשוטות האלה במחלקות ולבנות מסגרת שניתן לעשות בה שימוש חוזר.

אם יש לכם שאלות לגבי השימוש בכלים האלה עם Google Data API המועדף עליכם, אתם מוזמנים להצטרף לקבוצות הדיון.

קובץ כיתה עם דוגמאות הקוד שמתוארות למעלה זמין בכתובת http://code.google.com/p/google-data-samples-ruby

אפשר להצטרף לדיון על המאמר הזה