استخدام Ruby مع واجهات برمجة التطبيقات لبيانات Google

جوشين هارتمان، فريق Google Data APIs
نيسان (أبريل) 2008

المقدمة

لغة Ruby هي لغة برمجة نصية تحظى بقدر كبير من الاهتمام في السنوات الأخيرة بسبب إطار عمل تطوير السكك الحديدية الشائعة على الويب. ستوضّح هذه المقالة كيفية استخدام Ruby للتفاعل مع خدمات Google Data API. لن نركز على السكك الحديدية، ولكننا نهتم أكثر بشرح أوامر HTTP الأساسية وبنية خلاصاتنا. يمكن اتّباع جميع الأمثلة المقدَّمة هنا من سطر الأوامر باستخدام irb، وهي واجهة أوامر تفاعلية من Ruby.

ربما تتذكر من مقالة cURL أن واجهات برمجة التطبيقات لبيانات Google تستخدم بروتوكول نشر Atom لتمثيل موارد الويب وإنشاؤها وتحديثها. ويكمن جمال هذا البروتوكول في أنه يتم استخدام أفعال HTTP القياسية لصياغة الطلبات التي يتم الرد عليها باستخدام رموز حالة HTTP العادية.

الأفعال التي سنستخدمها في هذه المقالة هي GET لاسترداد المحتوى، وPOST لتحميل محتوى جديد، ووضع لتحديث المحتوى الحالي. بعض الرموز القياسية التي قد تصادفها عند استخدام واجهات برمجة التطبيقات لبيانات Google هي 200 والتي تمثل النجاح في استرداد خلاصة أو إدخالاً، أو 201 لتمثيل إنشاء أو تحديث مورد بنجاح. في حال حدوث خطأ ما، مثل إرسال طلب غير صحيح، سيتم إرسال رمز 400 (أي "طلب غير صحيح") مرة أخرى. سيتم توفير رسالة أكثر تفصيلاً في نص الاستجابة، توضح الخطأ الذي حدث بالضبط.

يقدم روبي خيارًا رائعًا لتصحيح الأخطاء كجزء من وحدة 'Net'. ومع ذلك، فمن أجل الاحتفاظ بنماذج الرموز هذه قصيرة بشكل معقول، لم أُمكّنها هنا.

الحصول على Ruby وتثبيته

يمكن تثبيت Ruby باستخدام معظم أنظمة إدارة الحزم إذا كنت تستخدم نظام التشغيل Linux. وبالنسبة إلى أنظمة التشغيل الأخرى وللحصول على شفرة المصدر الكاملة، يُرجى الانتقال إلى http://www.ruby-lang.org/en/downloads/. Irb، سيتم تلقائيًا تثبيت الواجهة التفاعلية التي سنستخدمها لهذه الأمثلة. لاتباع أمثلة الرموز المدرجة هنا، سيلزمك أيضًا تثبيت XmlSimple، وهي مكتبة صغيرة لتحليل XML في بنى بيانات Ruby. للحصول على/تثبيت XmlSimple، يُرجى الانتقال إلى http://xml-simple.rubyforge.org/

بعد أن تكون لديك نسخة من Ruby قيد التشغيل على جهازك، يمكنك استخدام حزمة Net:HTTP لإجراء طلبات أساسية في خدمات البيانات من Google. يوضح المقتطف أدناه كيفية إجراء عمليات الاستيراد الضرورية من واجهة المستخدم التفاعلية لروبي. إن ما نفعله يتطلب حزمة "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. نحن بصدد تبديل واجهات برمجة التطبيقات الآن والتركيز على جداول البيانات، نظرًا لأن المعلومات التي يمكننا إرسالها واستردادها تكون "مناسبة لسطر الأوامر".

المصادقة | باستخدام واجهة برمجة تطبيقات جداول بيانات Google

سنبدأ مجددًا باسترداد خلاصة عناصر الإدخال. ولكن في هذه المرة سنحتاج إلى التعامل مع جداول البيانات التابعة لنا. ولإجراء ذلك، يجب أولاً المصادقة باستخدام خدمة حسابات Google.

كما تعلم من الوثائق بشأن مصادقة GData، هناك طريقتان للمصادقة مع خدمات Google. يُستخدَم AuthSub للتطبيقات المستندة إلى الويب وباختصار يتضمّن عملية تبادل الرموز المميّزة. الفائدة الحقيقية من AuthSub هي أن تطبيقك لا يحتاج إلى تخزين بيانات اعتماد المستخدم. ClientLogin هو للتطبيقات "المثبتة". في عملية ClientLogin، يتم إرسال اسم المستخدم وكلمة المرور إلى خدمات Google عبر https مع سلسلة تحدد الخدمة التي تريد استخدامها. يتم تعريف خدمة واجهة برمجة تطبيقات جداول بيانات Google من خلال السلسلة حكمة.

لننتقل مرة أخرى إلى واجهة الاستخدام التفاعلية لنجري المصادقة مع 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. اطلب من feedFeed أو listFeed الدخول إلى المحتوى الفعلي في ورقة العمل

قد يبدو هذا عملاً صعبًا، لكنني سأوضح أنه من السهل جدًا كتابة بعض الطرق البسيطة. تعتبر كل من FeedFeed وlistFeed تمثيلين مختلفين لمحتوى الخلية الفعلي لأي ورقة عمل. تمثل خلاصة feed صفًا كاملاً من المعلومات ويُنصح بنشر هذه البيانات باستخدامها. وتمثِّل خلية Feed الخلايا الفردية وتُستخدَم إما لتعديلات الخلايا الفردية أو لتعديلات مجمَّعة في العديد من الخلايا الفردية (باستخدام الصيغة PUT). يُرجى الرجوع إلى وثائق واجهة برمجة تطبيقات جداول بيانات Google لمزيد من التفاصيل.

نحتاج أولاً إلى استخراج مفتاح جدول البيانات (المظلل في ناتج 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 وcellFeed. قبل أن نتعمق في قائمة الخلاصات، دعنا نوضح بسرعة البيانات الحالية في نموذج جدول البيانات بحيث يمكنك معرفة ما نبحث عنه:

جدول البيانات بسيط للغاية ويبدو كالتالي:

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>

لإجراء مقارنة سريعة، دعنا نلق نظرة على كيفية تمثيل المعلومات نفسها في cellfeed:

# 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 لخلاصة list. وسيتم عرض هذا الرابط عند طلب خلاصة القائمة. وستحتوي على عنوان 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 إلى أن مقالتنا تمت بنجاح.

استخدام cellfeed لتحديث المحتوى

من المستندات، نرى أن خلاصة الخلايا تفضِّل طلبات PUT على المحتوى الحالي. ولكن نظرًا لأن المعلومات التي تم استردادها من cellFeed في وقت سابق كانت عبارة عن بيانات موجودة فعلاً في جدول البيانات الفعلي، فكيف يمكننا إضافة معلومات جديدة؟ سنحتاج ببساطة إلى تقديم طلب لكل خلية فارغة نريد إدخال البيانات فيها. يوضح المقتطف أدناه كيفية استرداد الخلية الفارغة R5C1 (الصف 5، العمود 1) والتي نريد إدراج بعض المعلومات حول لغة برمجة Python بها.

يحتوي المتغير الأصلي 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، والأفضل من ذلك، دعنا نكتب أسلوبًا لإجراء التحديث المجمّع بأكمله. ستأخذ الدالة صفيفًا من علامات التجزئة التي تحتوي على المتغيرات التالية:

  • :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. لقد تمكنا من الوصول إلى جداول البيانات وأوراق العمل الخاصة بنا باستخدام كل من listFeed وcellFeed. بالإضافة إلى ذلك، تم إدراج بعض البيانات الجديدة باستخدام طلب POST ثم كتابة طرق لإجراء تحديث مجمّع يحتوي على حوالي 120 سطرًا تقريبًا من الشفرة. من الآن فصاعدًا، لن يكون من الصعب جدًا دمج بعض هذه الطرق البسيطة في صفوف وإنشاء إطار عمل يمكن إعادة استخدامه.

الرجاء الانضمام إلينا في مجموعات المناقشة إذا كانت لديك أي أسئلة حول استخدام هذه الأدوات مع Google Data API المفضل.

يمكن العثور على ملف الفئة الذي يحتوي على نماذج التعليمات البرمجية الموضحة أعلاه على http://code.google.com/p/google-data-samples-ruby

مناقشة هذه المقالة