googletastic 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/README.textile +47 -0
  2. data/Rakefile +103 -0
  3. data/lib/googletastic/access_rule.rb +45 -0
  4. data/lib/googletastic/album.rb +22 -0
  5. data/lib/googletastic/app_engine.rb +103 -0
  6. data/lib/googletastic/attendee.rb +7 -0
  7. data/lib/googletastic/base.rb +46 -0
  8. data/lib/googletastic/calendar.rb +60 -0
  9. data/lib/googletastic/comment.rb +3 -0
  10. data/lib/googletastic/document.rb +219 -0
  11. data/lib/googletastic/event.rb +149 -0
  12. data/lib/googletastic/ext/file.rb +219 -0
  13. data/lib/googletastic/ext/pretty_print.xsl +47 -0
  14. data/lib/googletastic/ext/xml.rb +10 -0
  15. data/lib/googletastic/ext.rb +1 -0
  16. data/lib/googletastic/form.rb +131 -0
  17. data/lib/googletastic/group.rb +44 -0
  18. data/lib/googletastic/helpers/document.rb +70 -0
  19. data/lib/googletastic/helpers/event.rb +26 -0
  20. data/lib/googletastic/helpers/form.rb +102 -0
  21. data/lib/googletastic/helpers.rb +18 -0
  22. data/lib/googletastic/image.rb +121 -0
  23. data/lib/googletastic/mixins/actions.rb +113 -0
  24. data/lib/googletastic/mixins/attributes.rb +27 -0
  25. data/lib/googletastic/mixins/finders.rb +78 -0
  26. data/lib/googletastic/mixins/namespaces.rb +42 -0
  27. data/lib/googletastic/mixins/parsing.rb +68 -0
  28. data/lib/googletastic/mixins/requesting.rb +84 -0
  29. data/lib/googletastic/mixins.rb +5 -0
  30. data/lib/googletastic/person.rb +41 -0
  31. data/lib/googletastic/spreadsheet.rb +35 -0
  32. data/lib/googletastic/sync/document.rb +120 -0
  33. data/lib/googletastic/sync/form.rb +106 -0
  34. data/lib/googletastic/sync.rb +46 -0
  35. data/lib/googletastic/thumbnail.rb +7 -0
  36. data/lib/googletastic/youtube.rb +25 -0
  37. data/lib/googletastic.rb +115 -0
  38. data/spec/benchmarks/document_benchmark.rb +40 -0
  39. data/spec/config.yml +6 -0
  40. data/spec/fixtures/data/Doing business in the eMarketPlace.doc +0 -0
  41. data/spec/fixtures/data/basic.txt +1 -0
  42. data/spec/fixtures/data/calendar.list.xml +64 -0
  43. data/spec/fixtures/data/doc_as_html.html +3097 -0
  44. data/spec/fixtures/data/doc_as_html_html.html +0 -0
  45. data/spec/fixtures/data/doclist.xml +76 -0
  46. data/spec/fixtures/data/document.single.xml +30 -0
  47. data/spec/fixtures/data/end.xml +446 -0
  48. data/spec/fixtures/data/event.list.xml +62 -0
  49. data/spec/fixtures/data/form.html +66 -0
  50. data/spec/fixtures/data/group.list.xml +58 -0
  51. data/spec/fixtures/data/person.list.xml +53 -0
  52. data/spec/fixtures/data/photo.list.xml +111 -0
  53. data/spec/fixtures/data/sample_upload.mp4 +0 -0
  54. data/spec/fixtures/data/spreadsheet.list.xml +43 -0
  55. data/spec/fixtures/models/document.rb +7 -0
  56. data/spec/fixtures/models/event.rb +3 -0
  57. data/spec/fixtures/models/form.rb +6 -0
  58. data/spec/fixtures/models/test_model.rb +3 -0
  59. data/spec/fixtures/results/test.txt +185 -0
  60. data/spec/googletastic/access_rule_spec.rb +13 -0
  61. data/spec/googletastic/album_spec.rb +9 -0
  62. data/spec/googletastic/app_engine_spec.rb +12 -0
  63. data/spec/googletastic/base_spec.rb +49 -0
  64. data/spec/googletastic/calendar_spec.rb +11 -0
  65. data/spec/googletastic/document_spec.rb +163 -0
  66. data/spec/googletastic/event_spec.rb +34 -0
  67. data/spec/googletastic/form_spec.rb +43 -0
  68. data/spec/googletastic/group_spec.rb +9 -0
  69. data/spec/googletastic/image_spec.rb +9 -0
  70. data/spec/googletastic/person_spec.rb +9 -0
  71. data/spec/googletastic/post_spec.rb +46 -0
  72. data/spec/googletastic/spreadsheet_spec.rb +9 -0
  73. data/spec/googletastic/youtube_spec.rb +58 -0
  74. data/spec/spec.opts +1 -0
  75. data/spec/spec_helper.rb +25 -0
  76. metadata +187 -0
@@ -0,0 +1,41 @@
1
+ class Googletastic::Person < Googletastic::Base
2
+
3
+ attr_accessor :name, :email
4
+
5
+ class << self
6
+
7
+ def index_url
8
+ "http://www.google.com/m8/feeds/contacts/default/full"
9
+ end
10
+
11
+ def client_class
12
+ "Contacts"
13
+ end
14
+
15
+ # http://code.google.com/apis/contacts/docs/2.0/reference.html#Parameters
16
+ def valid_queries
17
+ {
18
+ :order => "orderby",
19
+ :sort => "sortorder",
20
+ :group => "group"
21
+ }.merge(super)
22
+ end
23
+
24
+ def unmarshall(xml)
25
+ records = xml.xpath("//atom:entry", ns_tag("atom")).collect do |record|
26
+ id = record.xpath("atom:id", ns_tag("atom")).first.text
27
+ name = record.xpath("atom:title", ns_tag("atom")).first.text
28
+ email = record.xpath("gd:email", ns_tag("gd")).first["address"].to_s
29
+
30
+ Googletastic::Person.new(
31
+ :id => id,
32
+ :name => name,
33
+ :email => email
34
+ )
35
+ end
36
+ records
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,35 @@
1
+ class Googletastic::Spreadsheet < Googletastic::Base
2
+
3
+ attr_accessor :title, :content
4
+
5
+ # Time.now.xmlschema
6
+ class << self
7
+
8
+ def client_class
9
+ "Spreadsheets"
10
+ end
11
+
12
+ def index_url
13
+ "http://spreadsheets.google.com/feeds/spreadsheets/private/full"
14
+ end
15
+
16
+ def unmarshall(xml)
17
+ records = xml.xpath("//atom:entry", ns_tag("atom")).collect do |record|
18
+ id = record.xpath("atom:id", ns_tag("atom")).first.text.gsub("http://spreadsheets.google.com/feeds/spreadsheets/", "")
19
+ title = record.xpath("atom:title", ns_tag("atom")).first.text
20
+ content = record.xpath("atom:content", ns_tag("atom")).first.text
21
+ created_at = record.xpath("atom:published", ns_tag("atom")).text
22
+ updated_at = record.xpath("atom:updated", ns_tag("atom")).text
23
+
24
+ Googletastic::Spreadsheet.new(
25
+ :id => id,
26
+ :title => title,
27
+ :content => content,
28
+ :updated_at => DateTime.parse(updated_at)
29
+ )
30
+ end
31
+ records
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,120 @@
1
+ module Googletastic::Sync::Document
2
+
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+
9
+ def sync(documents, options, &block)
10
+ updated = process(documents, options)
11
+ key = options[:key].to_s
12
+ data = {key => []}
13
+ documents.each do |document|
14
+ data[key] << {
15
+ :id => document.remote.id
16
+ }
17
+ yield(data, key, document) if block_given?
18
+ end
19
+ response = Googletastic::Sync.post(
20
+ :url => options[:url],
21
+ :path => options[:path],
22
+ :format => :json,
23
+ :data => data
24
+ )
25
+ end
26
+
27
+ def process(documents, options = {})
28
+ options[:key] ||= "documents"
29
+ # defaults to a reasonable limit (3MB) for heroku
30
+ options[:max_size] ||= 3000000
31
+ # per max_size chunk
32
+ documents_processed = []
33
+ # total
34
+ updated_documents = []
35
+ counted_size = 0
36
+ documents.each_with_index do |document, index|
37
+ next if document.remote.kind != "document"
38
+ if document.synced_at and document.synced_at >= document.remote.updated_at
39
+ puts "Skipping Document... #{document.title}"
40
+ if documents_processed.length > 0 and index == documents.length - 1
41
+ Googletastic::Sync.push(options[:username], options[:password], options)
42
+ cleanup(documents_processed, options)
43
+ end
44
+ next
45
+ end
46
+ remote = document.remote
47
+ content = nil
48
+ title = remote.title
49
+ ext = remote.ext
50
+ puts "Processing Document... #{document.title}"
51
+ begin
52
+ if ext == ".textile"
53
+ # google is putting strange characters at beginning of downloaded files
54
+ content = remote.download("txt").body.gsub(/\357\273\277/, "")
55
+ content = RedCloth.new(content).to_html
56
+ elsif ext == ".markdown"
57
+ content = remote.download("txt").body.gsub(/\357\273\277/, "")
58
+ content = BlueCloth.new(content).to_html
59
+ elsif ext.nil? || ext.empty?
60
+ # just use the html we have already
61
+ content = remote.content
62
+ else
63
+ content = remote.download("txt").body.gsub(/\357\273\277/, "")
64
+ end
65
+
66
+ document.content = content
67
+ title = remote.id
68
+
69
+ tempfile = Tempfile.new("googltastic-tempfiles-#{title}-#{Time.now}-#{rand(10000)}")
70
+ tempfile.write(content)
71
+
72
+ path = File.join(options[:folder], options[:key])
73
+ Dir.mkdir(path) unless File.exists?(path)
74
+ path = File.join(path, title)
75
+
76
+ # if we have passed the 5MB (or our 3MB) limit,
77
+ # then push the files to GAE
78
+ if tempfile.size + counted_size >= options[:max_size] || index == documents.length - 1
79
+ Googletastic::Sync.push(options[:username], options[:password], options)
80
+ cleanup(documents_processed, options)
81
+ counted_size = 0
82
+ documents_processed = []
83
+ end
84
+
85
+ content = Googletastic::PrettyPrint.xml(content)
86
+
87
+ File.open(path, 'w') {|f| f.write(content) }
88
+ documents_processed << document
89
+ counted_size += tempfile.size
90
+ tempfile.close
91
+
92
+ updated_documents << document
93
+
94
+ rescue Exception => e
95
+ puts "Error... #{e.inspect}"
96
+ end
97
+ end
98
+
99
+ updated_documents
100
+ end
101
+
102
+ def cleanup(syncables, options)
103
+ syncables.each do |syncable|
104
+ syncable.synced_at = Time.now
105
+ syncable.save!
106
+ path = File.join(options[:folder], options[:key], syncable.remote.id)
107
+ path += options[:ext] if options.has_key?(:ext)
108
+ begin
109
+ File.delete(path)
110
+ rescue Exception => e
111
+
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ Googletastic::Document.class_eval do
119
+ include Googletastic::Sync::Document
120
+ end
@@ -0,0 +1,106 @@
1
+ module Googletastic::Sync::Form
2
+
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+
9
+ def sync(forms, options = {}, &block)
10
+ updated = process(forms, options)
11
+ key = options[:key].to_s
12
+ data = {key => []}
13
+ updated.each do |form|
14
+ data[key] << {
15
+ :id => form.remote.id,
16
+ :formkey => form.formkey
17
+ }
18
+ yield(data, key, form) if block_given?
19
+ end
20
+ response = Googletastic::Sync.post(
21
+ :url => options[:url],
22
+ :path => options[:path],
23
+ :format => :json,
24
+ :data => data
25
+ )
26
+ end
27
+
28
+ def process(forms, options = {})
29
+ options[:key] ||= "forms"
30
+ # defaults to a reasonable limit (3MB) for heroku
31
+ options[:max_size] ||= 3000000
32
+ # per max_size chunk
33
+ forms_processed = []
34
+ # total
35
+ updated_forms = []
36
+ counted_size = 0
37
+ forms.each_with_index do |form, index|
38
+ if form.formkey.nil?
39
+ puts "Missing formkey... #{form.title}"
40
+ if forms_processed.length > 0 and index == forms.length - 1
41
+ Googletastic::Sync.push(options[:username], options[:password], options)
42
+ cleanup(forms_processed, options.merge(:ext => ".html"))
43
+ end
44
+ next
45
+ end
46
+ remote = form.remote
47
+ content = nil
48
+ puts "Processing Form... #{form.title}"
49
+ begin
50
+ form.remote.form_key = form.formkey
51
+ title = form.formkey + ".html"
52
+ content = form.remote.body
53
+
54
+ tempfile = Tempfile.new("googltastic-form-tempfiles-#{title}-#{Time.now}-#{rand(10000)}")
55
+ tempfile.write(content)
56
+
57
+ path = File.join(options[:folder], options[:key])
58
+ Dir.mkdir(path) unless File.exists?(path)
59
+ path = File.join(path, title)
60
+
61
+ # if we have passed the 5MB (or our 3MB) limit,
62
+ # then push the files to GAE
63
+ if tempfile.size + counted_size >= options[:max_size] || index == forms.length - 1
64
+ Googletastic::Sync.push(options[:username], options[:password], options)
65
+ cleanup(forms_processed, options.merge(:ext => ".html"))
66
+ counted_size = 0
67
+ forms_processed = []
68
+ end
69
+
70
+ content = Googletastic::PrettyPrint.xml(content)
71
+
72
+ File.open(path, 'w') {|f| f.write(content) }
73
+ forms_processed << form
74
+ counted_size += tempfile.size
75
+ tempfile.close
76
+
77
+ updated_forms << form
78
+
79
+ rescue Exception => e
80
+ puts "Error... #{e.inspect}"
81
+ end
82
+ end
83
+
84
+ updated_forms
85
+ end
86
+
87
+ def cleanup(syncables, options)
88
+ syncables.each do |syncable|
89
+ syncable.synced_at = Time.now
90
+ syncable.save!
91
+ path = File.join(options[:folder], options[:key], syncable.formkey)
92
+ path += options[:ext] if options.has_key?(:ext)
93
+ begin
94
+ File.delete(path)
95
+ rescue Exception => e
96
+
97
+ end
98
+ end
99
+ end
100
+
101
+ end
102
+ end
103
+
104
+ Googletastic::Form.class_eval do
105
+ include Googletastic::Sync::Form
106
+ end
@@ -0,0 +1,46 @@
1
+ module Googletastic::Sync
2
+
3
+ class << self
4
+
5
+ def sync_documents(documents, options = {}, &block)
6
+ Googletastic::Document.sync(documents, options, &block)
7
+ end
8
+
9
+ def sync_forms(forms, options = {}, &block)
10
+ Googletastic::Form.sync(forms, options, &block)
11
+ end
12
+
13
+ def push(username, password, options = {})
14
+ Googletastic.client_for(:app_engine).push(username, password, options)
15
+ end
16
+
17
+ # POSTs to your registered application
18
+ def post(options = {})
19
+ url = URI.parse(options[:url])
20
+ # POST update to registered application
21
+ http = Net::HTTP.new(url.host, url.port)
22
+ header = options[:header] || {}
23
+ header.merge!('Content-Type' =>'application/json')
24
+ data = options[:data].to_json
25
+ response = http.post(options[:path], data, header)
26
+ end
27
+
28
+ def cleanup(syncables, options)
29
+ syncables.each do |syncable|
30
+ syncable.synced_at = Time.now
31
+ syncable.save!
32
+ path = File.join(options[:folder], syncable.title)
33
+ path += options[:ext] if options.has_key?(:ext)
34
+ begin
35
+ File.delete(path)
36
+ rescue Exception => e
37
+
38
+ end
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+
46
+ require_local "sync/*", __FILE__
@@ -0,0 +1,7 @@
1
+ class Googletastic::Thumbnail < Hash # simple value object
2
+
3
+ include Googletastic::Mixins::Attributes
4
+
5
+ attr_accessor :url, :width, :height
6
+
7
+ end
@@ -0,0 +1,25 @@
1
+ =begin
2
+ module Thing
3
+ module Google
4
+ class YouTube < CouchRest::ExtendedDocument
5
+
6
+ MEDIA_NS = "http://search.yahoo.com/mrss" unless defined?(MEDIA_NS)
7
+ YT_NS = "http://gdata.youtube.com/schemas/2007" unless defined?(YT_NS)
8
+
9
+ API = "http://gdata.youtube.com" unless defined?(API)
10
+ GET_UPLOAD_TOKEN = "action/GetUploadToken"
11
+ UPLOAD = "http://uploads.gdata.youtube.com/feeds/api/users"
12
+ MULTIPART = "multipart/related"
13
+
14
+ def self.unmarshall(object)
15
+
16
+ end
17
+
18
+ def self.marshall(xml)
19
+
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+ =end
@@ -0,0 +1,115 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'date'
4
+ require 'rake/clean'
5
+ #require 'rbconfig'
6
+ require 'open-uri'
7
+ require 'nokogiri'
8
+ require 'active_support'
9
+ require 'active_record'
10
+ require 'gdata'
11
+ require 'liquid'
12
+
13
+ GOOGLETASTIC_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(GOOGLETASTIC_ROOT)
14
+
15
+ class Module
16
+ def include_class_and_instance_modules
17
+ self.module_eval <<-eos
18
+ def self.included(base)
19
+ base.extend(ClassMethods)
20
+ base.class_eval do
21
+ include InstanceMethods
22
+ end
23
+ end
24
+ eos
25
+ end
26
+ end
27
+
28
+ def require_local(path, from = __FILE__)
29
+ files(path, from) {|file| require file if File.extname(file) == ".rb"}
30
+ end
31
+
32
+ def files(path, from = __FILE__, &block)
33
+ Dir.glob(File.expand_path(File.join(File.dirname(from), path))) {|file| yield file}
34
+ end
35
+
36
+ def require_spec
37
+ require File.join(GOOGLETASTIC_ROOT, "/spec/spec_helper")
38
+ end
39
+
40
+ class String
41
+ # active_support isn't letting me do this!
42
+ def parameterize(sep = '-')
43
+ # replace accented chars with ther ascii equivalents
44
+ # parameterized_string = transliterate(string)
45
+ parameterized_string = self.dup
46
+ # Turn unwanted chars into the seperator
47
+ parameterized_string.gsub!(/[^a-z0-9\-_\+]+/i, sep)
48
+ unless sep.blank?
49
+ re_sep = Regexp.escape(sep)
50
+ # No more than one of the separator in a row.
51
+ parameterized_string.gsub!(/#{re_sep}{2,}/, sep)
52
+ # Remove leading/trailing separator.
53
+ parameterized_string.gsub!(/^#{re_sep}|#{re_sep}$/i, '')
54
+ end
55
+ parameterized_string.downcase
56
+ end
57
+ end
58
+
59
+ module Googletastic
60
+ # :stopdoc:
61
+ VERSION = '0.0.1'
62
+ # :startdoc
63
+ class << self; attr_accessor :keys, :clients, :options; end
64
+
65
+ def self.credentials
66
+ return self.keys if self.keys
67
+
68
+ config_file = "#{RAILS_ROOT}/config/gdata.yml"
69
+ raise "Sorry, you must have #{config_file}" unless File.exists?(config_file)
70
+ self.keys = YAML.load_file(config_file).symbolize_keys
71
+ end
72
+
73
+ def self.client_for(model)
74
+ self.clients ||= {}
75
+ model = model.to_sym
76
+ return self.clients[model] if self.clients.has_key?(model)
77
+ client = ("GData::Client::#{model.to_s.camelize}").constantize.new(:source => credentials[:application])
78
+ client.clientlogin(credentials[:username], credentials[:password], nil, nil, nil, credentials[:account_type])
79
+ self.clients[model] = client
80
+ end
81
+
82
+ def self.options_for(klass, value = {})
83
+ self.options ||= {}
84
+ klass = klass.is_a?(Class) ? klass : klass.class
85
+ name = klass.to_s.underscore.downcase.to_sym
86
+ if value and !value.blank?
87
+ value.symbolize_keys!
88
+ self.options[name] = value
89
+ else
90
+ self.options[name] ||= {}
91
+ end
92
+ self.options[name]
93
+ end
94
+
95
+ def self.[](value)
96
+ self.options_for(value)
97
+ end
98
+ end
99
+
100
+ # main includes
101
+ require_local "googletastic/ext/*"
102
+
103
+ require File.dirname(__FILE__) + '/googletastic/mixins'
104
+ require File.dirname(__FILE__) + '/googletastic/base'
105
+
106
+ require_local "googletastic/*"
107
+
108
+ #files("googletastic/mixins/*") do |file|
109
+ # Googletastic::Base.send(:include, "Googletastic::Mixins::#{File.basename(file, '.rb').camelize}".constantize)
110
+ #end
111
+
112
+ if defined?(ActiveRecord::Base)
113
+ ActiveRecord::Base.send(:include, Googletastic::Helpers)
114
+ end
115
+ # EOF
@@ -0,0 +1,40 @@
1
+ require 'benchmark'
2
+ require 'rubygems'
3
+
4
+ $:.unshift(File.dirname(__FILE__) + '/..')
5
+ require 'spec_helper'
6
+
7
+ id = "0AT1-fvkm5vJPZGN2NzR3bmJfMGZiMnh3N2R0"
8
+ many = 1
9
+
10
+ Benchmark.bmbm(7) do |x|
11
+ x.report ("Find the Document") do
12
+ many.times do |i|
13
+ doc = Googletastic::Document.find(id)
14
+ end
15
+ end
16
+ x.report("View the Raw HTML: ") do
17
+ many.times do |i|
18
+ doc = Googletastic::Document.find(id)
19
+ result = doc.view
20
+ end
21
+ end
22
+ x.report("Download as HTML: ") do
23
+ many.times do |i|
24
+ doc = Googletastic::Document.find(id)
25
+ result = doc.download("html")
26
+ end
27
+ end
28
+ x.report("Download as TXT: ") do
29
+ many.times do |i|
30
+ doc = Googletastic::Document.find(id)
31
+ result = doc.download("txt")
32
+ end
33
+ end
34
+ x.report("Download as PDF: ") do
35
+ many.times do |i|
36
+ doc = Googletastic::Document.find(id)
37
+ result = doc.download("pdf")
38
+ end
39
+ end
40
+ end
data/spec/config.yml ADDED
@@ -0,0 +1,6 @@
1
+ username: "flexinstyle@gmail.com"
2
+ password: "flexyflexy"
3
+ application: "Googletastic Test"
4
+ account_type: "HOSTED_OR_GOOGLE"
5
+ youtube_login: "MrFlexinstyle"
6
+ youtube_dev_key: "AI39si7jkhs_ECjF4unOQz8gpWGSKXgq0KJpm8wywkvBSw4s8oJd5p5vkpvURHBNh-hiYJtoKwQqSfot7KoCkeCE32rNcZqMxA"
@@ -0,0 +1 @@
1
+ something
@@ -0,0 +1,64 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <feed xmlns="http://www.w3.org/2005/Atom" xmlns:app="http://www.w3.org/2007/app" xmlns:gCal="http://schemas.google.com/gCal/2005" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:gd="http://schemas.google.com/g/2005" gd:etag="W/&quot;AkACRXkyeSp7IWA9WxBaEk8.&quot;" gd:kind="calendar#calendarFeed">
3
+ <id>http://www.google.com/calendar/feeds/default/allcalendars/full</id>
4
+ <updated>2010-03-22T03:32:44.791Z</updated>
5
+ <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/gCal/2005#calendarmeta"/>
6
+ <title>flexinstyle@gmail.com's Calendar List</title>
7
+ <link rel="alternate" type="text/html" href="http://www.google.com/calendar/render"/>
8
+ <link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://www.google.com/calendar/feeds/default/allcalendars/full"/>
9
+ <link rel="http://schemas.google.com/g/2005#post" type="application/atom+xml" href="http://www.google.com/calendar/feeds/default/allcalendars/full"/>
10
+ <link rel="self" type="application/atom+xml" href="http://www.google.com/calendar/feeds/default/allcalendars/full"/>
11
+ <author>
12
+ <name>flexinstyle@gmail.com</name>
13
+ <email>flexinstyle@gmail.com</email>
14
+ </author>
15
+ <generator version="1.0" uri="http://www.google.com/calendar">Google Calendar</generator>
16
+ <openSearch:startIndex>1</openSearch:startIndex>
17
+ <entry gd:etag="W/&quot;A0cFSX47eCp7IWA9WxBaEU0.&quot;" gd:kind="calendar#calendar">
18
+ <id>http://www.google.com/calendar/feeds/default/calendars/flexinstyle%40gmail.com</id>
19
+ <published>2010-03-22T03:32:44.788Z</published>
20
+ <updated>2010-03-20T18:16:58.000Z</updated>
21
+ <app:edited>2010-03-20T18:16:58.000Z</app:edited>
22
+ <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/gCal/2005#calendarmeta"/>
23
+ <title type="text">flexinstyle@gmail.com</title>
24
+ <content type="application/atom+xml" src="http://www.google.com/calendar/feeds/flexinstyle%40gmail.com/private/full"/>
25
+ <link rel="alternate" type="application/atom+xml" href="http://www.google.com/calendar/feeds/flexinstyle%40gmail.com/private/full"/>
26
+ <link rel="http://schemas.google.com/gCal/2005#eventFeed" type="application/atom+xml" href="http://www.google.com/calendar/feeds/flexinstyle%40gmail.com/private/full"/>
27
+ <link rel="http://schemas.google.com/acl/2007#accessControlList" type="application/atom+xml" href="http://www.google.com/calendar/feeds/flexinstyle%40gmail.com/acl/full"/>
28
+ <link rel="self" type="application/atom+xml" href="http://www.google.com/calendar/feeds/default/allcalendars/full/flexinstyle%40gmail.com"/>
29
+ <link rel="edit" type="application/atom+xml" href="http://www.google.com/calendar/feeds/default/allcalendars/full/flexinstyle%40gmail.com"/>
30
+ <author>
31
+ <name>flexinstyle@gmail.com</name>
32
+ <email>flexinstyle@gmail.com</email>
33
+ </author>
34
+ <gCal:accesslevel value="owner"/>
35
+ <gCal:color value="#A32929"/>
36
+ <gCal:hidden value="false"/>
37
+ <gCal:selected value="true"/>
38
+ <gCal:timezone value="America/Phoenix"/>
39
+ <gCal:timesCleaned value="0"/>
40
+ </entry>
41
+ <entry gd:etag="W/&quot;A0QBRX47eCp7IWA9WxBaEk0.&quot;" gd:kind="calendar#calendar">
42
+ <id>http://www.google.com/calendar/feeds/default/calendars/en.usa%23holiday%40group.v.calendar.google.com</id>
43
+ <published>2010-03-22T03:32:44.788Z</published>
44
+ <updated>2010-03-21T22:09:14.000Z</updated>
45
+ <app:edited>2010-03-21T22:09:14.000Z</app:edited>
46
+ <category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/gCal/2005#calendarmeta"/>
47
+ <title type="text">US Holidays</title>
48
+ <summary type="text">US Holidays</summary>
49
+ <content type="application/atom+xml" src="http://www.google.com/calendar/feeds/en.usa%23holiday%40group.v.calendar.google.com/private/full"/>
50
+ <link rel="alternate" type="application/atom+xml" href="http://www.google.com/calendar/feeds/en.usa%23holiday%40group.v.calendar.google.com/private/full"/>
51
+ <link rel="http://schemas.google.com/gCal/2005#eventFeed" type="application/atom+xml" href="http://www.google.com/calendar/feeds/en.usa%23holiday%40group.v.calendar.google.com/private/full"/>
52
+ <link rel="self" type="application/atom+xml" href="http://www.google.com/calendar/feeds/default/allcalendars/full/en.usa%23holiday%40group.v.calendar.google.com"/>
53
+ <link rel="edit" type="application/atom+xml" href="http://www.google.com/calendar/feeds/default/allcalendars/full/en.usa%23holiday%40group.v.calendar.google.com"/>
54
+ <author>
55
+ <name>US Holidays</name>
56
+ </author>
57
+ <gCal:accesslevel value="read"/>
58
+ <gCal:color value="#2952A3"/>
59
+ <gCal:hidden value="false"/>
60
+ <gCal:selected value="true"/>
61
+ <gCal:timezone value="America/Phoenix"/>
62
+ <gCal:timesCleaned value="0"/>
63
+ </entry>
64
+ </feed>