google-search 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.rdoc +8 -0
- data/Manifest +70 -0
- data/README.rdoc +95 -0
- data/Rakefile +16 -0
- data/examples/image.rb +21 -0
- data/examples/images.html +1 -0
- data/examples/web.rb +28 -0
- data/google-search.gemspec +33 -0
- data/lib/google-search.rb +29 -0
- data/lib/google-search/item.rb +10 -0
- data/lib/google-search/item/base.rb +78 -0
- data/lib/google-search/item/blog.rb +34 -0
- data/lib/google-search/item/book.rb +40 -0
- data/lib/google-search/item/image.rb +40 -0
- data/lib/google-search/item/local.rb +107 -0
- data/lib/google-search/item/news.rb +46 -0
- data/lib/google-search/item/patent.rb +40 -0
- data/lib/google-search/item/video.rb +46 -0
- data/lib/google-search/item/web.rb +22 -0
- data/lib/google-search/response.rb +92 -0
- data/lib/google-search/search.rb +11 -0
- data/lib/google-search/search/base.rb +212 -0
- data/lib/google-search/search/blog.rb +14 -0
- data/lib/google-search/search/book.rb +8 -0
- data/lib/google-search/search/image.rb +93 -0
- data/lib/google-search/search/local.rb +8 -0
- data/lib/google-search/search/mixins.rb +4 -0
- data/lib/google-search/search/mixins/filter.rb +26 -0
- data/lib/google-search/search/mixins/order_by.rb +36 -0
- data/lib/google-search/search/mixins/safety_level.rb +40 -0
- data/lib/google-search/search/news.rb +66 -0
- data/lib/google-search/search/patent.rb +37 -0
- data/lib/google-search/search/video.rb +15 -0
- data/lib/google-search/search/web.rb +15 -0
- data/lib/google-search/version.rb +6 -0
- data/spec/fixtures/400-response.json +1 -0
- data/spec/fixtures/blog-response.json +1 -0
- data/spec/fixtures/book-response.json +1 -0
- data/spec/fixtures/image-response.json +1 -0
- data/spec/fixtures/invalid-response.json +1 -0
- data/spec/fixtures/local-response.json +1 -0
- data/spec/fixtures/news-response.json +1 -0
- data/spec/fixtures/patent-response.json +1 -0
- data/spec/fixtures/video-response.json +1 -0
- data/spec/fixtures/web-2-response.json +1 -0
- data/spec/fixtures/web-response.json +1 -0
- data/spec/item_blog_spec.rb +26 -0
- data/spec/item_book_spec.rb +18 -0
- data/spec/item_image_spec.rb +21 -0
- data/spec/item_local_spec.rb +30 -0
- data/spec/item_news_spec.rb +19 -0
- data/spec/item_patent_spec.rb +19 -0
- data/spec/item_spec.rb +10 -0
- data/spec/item_video_spec.rb +20 -0
- data/spec/item_web_spec.rb +18 -0
- data/spec/response_spec.rb +63 -0
- data/spec/search_blog_spec.rb +0 -0
- data/spec/search_book_spec.rb +0 -0
- data/spec/search_image_spec.rb +71 -0
- data/spec/search_local_spec.rb +0 -0
- data/spec/search_news_spec.rb +33 -0
- data/spec/search_patent_spec.rb +27 -0
- data/spec/search_spec.rb +89 -0
- data/spec/search_video_spec.rb +42 -0
- data/spec/search_web_spec.rb +42 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +13 -0
- data/tasks/docs.rake +18 -0
- data/tasks/gemspec.rake +3 -0
- data/tasks/spec.rake +25 -0
- metadata +168 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
|
2
|
+
module Google
|
3
|
+
class Search
|
4
|
+
class Item
|
5
|
+
class Image < self
|
6
|
+
|
7
|
+
##
|
8
|
+
# Image id.
|
9
|
+
|
10
|
+
attr_reader :id
|
11
|
+
|
12
|
+
##
|
13
|
+
# Context uri.
|
14
|
+
|
15
|
+
attr_reader :context_uri
|
16
|
+
|
17
|
+
##
|
18
|
+
# Image width in pixels.
|
19
|
+
|
20
|
+
attr_reader :width
|
21
|
+
|
22
|
+
##
|
23
|
+
# Image height in pixels.
|
24
|
+
|
25
|
+
attr_reader :height
|
26
|
+
|
27
|
+
##
|
28
|
+
# Initialize with _hash_.
|
29
|
+
|
30
|
+
def initialize hash
|
31
|
+
super
|
32
|
+
@id = hash['imageId']
|
33
|
+
@context_uri = hash['originalContextUrl']
|
34
|
+
@width = hash['width'].to_i
|
35
|
+
@height = hash['height'].to_i
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
|
2
|
+
module Google
|
3
|
+
class Search
|
4
|
+
class Item
|
5
|
+
class Local < self
|
6
|
+
|
7
|
+
##
|
8
|
+
# Country.
|
9
|
+
|
10
|
+
attr_reader :country
|
11
|
+
|
12
|
+
##
|
13
|
+
# Region.
|
14
|
+
|
15
|
+
attr_reader :region
|
16
|
+
|
17
|
+
##
|
18
|
+
# City.
|
19
|
+
|
20
|
+
attr_reader :city
|
21
|
+
|
22
|
+
##
|
23
|
+
# Type.
|
24
|
+
|
25
|
+
attr_reader :type
|
26
|
+
|
27
|
+
##
|
28
|
+
# Accuracy.
|
29
|
+
|
30
|
+
attr_reader :accuracy
|
31
|
+
|
32
|
+
##
|
33
|
+
# Max age in seconds.
|
34
|
+
|
35
|
+
attr_reader :max_age
|
36
|
+
|
37
|
+
##
|
38
|
+
# Google maps directions uri.
|
39
|
+
|
40
|
+
attr_reader :directions_uri
|
41
|
+
|
42
|
+
##
|
43
|
+
# Google maps directions to here uri.
|
44
|
+
|
45
|
+
attr_reader :directions_to_here_uri
|
46
|
+
|
47
|
+
##
|
48
|
+
# Google maps directions from here uri.
|
49
|
+
|
50
|
+
attr_reader :directions_from_here_uri
|
51
|
+
|
52
|
+
##
|
53
|
+
# Longitude float.
|
54
|
+
|
55
|
+
attr_reader :long
|
56
|
+
|
57
|
+
##
|
58
|
+
# Latitude float.
|
59
|
+
|
60
|
+
attr_reader :lat
|
61
|
+
|
62
|
+
##
|
63
|
+
# Viewport mode.
|
64
|
+
|
65
|
+
attr_reader :viewport_mode
|
66
|
+
|
67
|
+
##
|
68
|
+
# Phone numbers array.
|
69
|
+
|
70
|
+
attr_reader :phone_numbers
|
71
|
+
|
72
|
+
##
|
73
|
+
# Street address.
|
74
|
+
|
75
|
+
attr_reader :street_address
|
76
|
+
|
77
|
+
##
|
78
|
+
# Address lines array.
|
79
|
+
|
80
|
+
attr_reader :address_lines
|
81
|
+
|
82
|
+
##
|
83
|
+
# Initialize with _hash_.
|
84
|
+
|
85
|
+
def initialize hash
|
86
|
+
super
|
87
|
+
@country = hash['country']
|
88
|
+
@region = hash['region']
|
89
|
+
@city = hash['city']
|
90
|
+
@type = hash['listingType']
|
91
|
+
@accuracy = hash['accuracy'].to_i
|
92
|
+
@max_age = hash['maxAge']
|
93
|
+
@directions_uri = hash['ddUrl']
|
94
|
+
@directions_to_here_uri = hash['ddUrlToHere']
|
95
|
+
@directions_from_here_uri = hash['ddUrlFromHere']
|
96
|
+
@thumbnail_uri = hash['staticMapUrl']
|
97
|
+
@long = hash['lng'].to_f
|
98
|
+
@lat = hash['lat'].to_f
|
99
|
+
@viewport_mode = hash['viewportmode']
|
100
|
+
@phone_numbers = hash['phoneNumbers'].map { |phone| phone['number'] }
|
101
|
+
@street_address = hash['streetAddress']
|
102
|
+
@address_lines = hash['addressLines']
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
module Google
|
3
|
+
class Search
|
4
|
+
class Item
|
5
|
+
class News < self
|
6
|
+
|
7
|
+
##
|
8
|
+
# Published DateTime.
|
9
|
+
|
10
|
+
attr_reader :published
|
11
|
+
|
12
|
+
##
|
13
|
+
# Publisher.
|
14
|
+
|
15
|
+
attr_reader :publisher
|
16
|
+
|
17
|
+
##
|
18
|
+
# Location.
|
19
|
+
|
20
|
+
attr_reader :location
|
21
|
+
|
22
|
+
##
|
23
|
+
# Language.
|
24
|
+
|
25
|
+
attr_reader :language
|
26
|
+
|
27
|
+
##
|
28
|
+
# Redirect uri.
|
29
|
+
|
30
|
+
attr_reader :redirect_uri
|
31
|
+
|
32
|
+
##
|
33
|
+
# Initialize with _hash_.
|
34
|
+
|
35
|
+
def initialize hash
|
36
|
+
super
|
37
|
+
@location = hash['location']
|
38
|
+
@published = DateTime.parse hash['publishedDate']
|
39
|
+
@language = hash['language']
|
40
|
+
@publisher = hash['publisher']
|
41
|
+
@redirect_uri = hash['signedRedirectUrl']
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
|
2
|
+
module Google
|
3
|
+
class Search
|
4
|
+
class Item
|
5
|
+
class Patent < self
|
6
|
+
|
7
|
+
##
|
8
|
+
# Patent id.
|
9
|
+
|
10
|
+
attr_reader :id
|
11
|
+
|
12
|
+
##
|
13
|
+
# Patent status.
|
14
|
+
|
15
|
+
attr_reader :status
|
16
|
+
|
17
|
+
##
|
18
|
+
# Assignee.
|
19
|
+
|
20
|
+
attr_reader :assignee
|
21
|
+
|
22
|
+
##
|
23
|
+
# Application DateTime.
|
24
|
+
|
25
|
+
attr_reader :application_date
|
26
|
+
|
27
|
+
##
|
28
|
+
# Initialize with _hash_.
|
29
|
+
|
30
|
+
def initialize hash
|
31
|
+
super
|
32
|
+
@id = hash['patentNumber'].to_i
|
33
|
+
@application_date = DateTime.parse hash['applicationDate']
|
34
|
+
@assignee = hash['assignee']
|
35
|
+
@status = hash['patentStatus']
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
module Google
|
3
|
+
class Search
|
4
|
+
class Item
|
5
|
+
class Video < self
|
6
|
+
|
7
|
+
##
|
8
|
+
# Rating float.
|
9
|
+
|
10
|
+
attr_reader :rating
|
11
|
+
|
12
|
+
##
|
13
|
+
# Video type.
|
14
|
+
|
15
|
+
attr_reader :type
|
16
|
+
|
17
|
+
##
|
18
|
+
# Publisher.
|
19
|
+
|
20
|
+
attr_reader :publisher
|
21
|
+
|
22
|
+
##
|
23
|
+
# Published DateTime.
|
24
|
+
|
25
|
+
attr_reader :published
|
26
|
+
|
27
|
+
##
|
28
|
+
# Duration in seconds.
|
29
|
+
|
30
|
+
attr_reader :duration
|
31
|
+
|
32
|
+
##
|
33
|
+
# Initialize with _hash_.
|
34
|
+
|
35
|
+
def initialize hash
|
36
|
+
super
|
37
|
+
@rating = hash['rating'].to_f
|
38
|
+
@type = hash['videoType']
|
39
|
+
@publisher = hash['publisher']
|
40
|
+
@published = DateTime.parse hash['published']
|
41
|
+
@duration = hash['duration'].to_i
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
module Google
|
3
|
+
class Search
|
4
|
+
class Item
|
5
|
+
class Web < self
|
6
|
+
|
7
|
+
##
|
8
|
+
# Cached uri.
|
9
|
+
|
10
|
+
attr_reader :cache_uri
|
11
|
+
|
12
|
+
##
|
13
|
+
# Initialize with _hash_.
|
14
|
+
|
15
|
+
def initialize hash
|
16
|
+
super
|
17
|
+
@cache_uri = hash['cacheUrl']
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
|
2
|
+
module Google
|
3
|
+
class Search
|
4
|
+
class Response
|
5
|
+
|
6
|
+
#--
|
7
|
+
# Mixins
|
8
|
+
#++
|
9
|
+
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
##
|
13
|
+
# Response status code.
|
14
|
+
|
15
|
+
attr_reader :status
|
16
|
+
|
17
|
+
##
|
18
|
+
# Response details.
|
19
|
+
|
20
|
+
attr_reader :details
|
21
|
+
|
22
|
+
##
|
23
|
+
# Raw JSON string.
|
24
|
+
|
25
|
+
attr_accessor :raw
|
26
|
+
|
27
|
+
##
|
28
|
+
# Hash parsed from raw JSON string.
|
29
|
+
|
30
|
+
attr_reader :hash
|
31
|
+
|
32
|
+
##
|
33
|
+
# Items populated by the JSON hash.
|
34
|
+
|
35
|
+
attr_reader :items
|
36
|
+
|
37
|
+
##
|
38
|
+
# Estimated number of results.
|
39
|
+
|
40
|
+
attr_reader :estimated_count
|
41
|
+
|
42
|
+
##
|
43
|
+
# Current page index.
|
44
|
+
|
45
|
+
attr_reader :page
|
46
|
+
|
47
|
+
##
|
48
|
+
# Size of response.
|
49
|
+
|
50
|
+
attr_reader :size
|
51
|
+
|
52
|
+
##
|
53
|
+
# Initialize with _hash_.
|
54
|
+
|
55
|
+
def initialize hash
|
56
|
+
@page = 0
|
57
|
+
@hash = hash
|
58
|
+
@size = (hash['responseSize'] || :large).to_sym
|
59
|
+
@items = []
|
60
|
+
@status = hash['responseStatus']
|
61
|
+
@details = hash['responseDetails']
|
62
|
+
if valid?
|
63
|
+
if hash['responseData'].include? 'cursor'
|
64
|
+
@estimated_count = hash['responseData']['cursor']['estimatedResultCount'].to_i
|
65
|
+
@page = hash['responseData']['cursor']['currentPageIndex'].to_i
|
66
|
+
end
|
67
|
+
@hash['responseData']['results'].each_with_index do |result, i|
|
68
|
+
item_class = Google::Search::Item.class_for result['GsearchResultClass']
|
69
|
+
result['index'] = i + Google::Search.size_for(size) * page
|
70
|
+
items << item_class.new(result)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# Iterate each item with _block_.
|
77
|
+
|
78
|
+
def each_item &block
|
79
|
+
items.each { |item| yield item }
|
80
|
+
end
|
81
|
+
alias :each :each_item
|
82
|
+
|
83
|
+
##
|
84
|
+
# Check if the response is valid.
|
85
|
+
|
86
|
+
def valid?
|
87
|
+
hash['responseStatus'] == 200
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
|
2
|
+
require 'google-search/search/mixins'
|
3
|
+
require 'google-search/search/base'
|
4
|
+
require 'google-search/search/web'
|
5
|
+
require 'google-search/search/blog'
|
6
|
+
require 'google-search/search/image'
|
7
|
+
require 'google-search/search/local'
|
8
|
+
require 'google-search/search/news'
|
9
|
+
require 'google-search/search/video'
|
10
|
+
require 'google-search/search/book'
|
11
|
+
require 'google-search/search/patent'
|
@@ -0,0 +1,212 @@
|
|
1
|
+
|
2
|
+
module Google
|
3
|
+
class Search
|
4
|
+
|
5
|
+
#--
|
6
|
+
# Mixins
|
7
|
+
#++
|
8
|
+
|
9
|
+
include Enumerable
|
10
|
+
|
11
|
+
#--
|
12
|
+
# Constants
|
13
|
+
#++
|
14
|
+
|
15
|
+
URI = 'http://www.google.com/uds'
|
16
|
+
|
17
|
+
#--
|
18
|
+
# Exceptions
|
19
|
+
#++
|
20
|
+
|
21
|
+
class Error < StandardError; end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Version. Defaults to 1.0
|
25
|
+
|
26
|
+
attr_accessor :version
|
27
|
+
|
28
|
+
##
|
29
|
+
# Search type symbol.
|
30
|
+
|
31
|
+
attr_accessor :type
|
32
|
+
|
33
|
+
##
|
34
|
+
# Offset. Defaults to 0
|
35
|
+
|
36
|
+
attr_accessor :offset
|
37
|
+
|
38
|
+
##
|
39
|
+
# Language. Defaults to :en
|
40
|
+
|
41
|
+
attr_accessor :language
|
42
|
+
|
43
|
+
##
|
44
|
+
# Weither or not a search request has been sent.
|
45
|
+
|
46
|
+
attr_accessor :sent
|
47
|
+
|
48
|
+
##
|
49
|
+
# Query. Defaults to nil
|
50
|
+
|
51
|
+
attr_accessor :query
|
52
|
+
|
53
|
+
##
|
54
|
+
# API Key. Defaults to :notsupplied
|
55
|
+
|
56
|
+
attr_accessor :api_key
|
57
|
+
|
58
|
+
##
|
59
|
+
# Size. Defaults to :large
|
60
|
+
#
|
61
|
+
# - :small = 4
|
62
|
+
# - :large = 8
|
63
|
+
#
|
64
|
+
|
65
|
+
attr_accessor :size
|
66
|
+
|
67
|
+
##
|
68
|
+
# Additional options. All those listed above
|
69
|
+
# are deleted. The remaining represent query
|
70
|
+
# string key / value pairs.
|
71
|
+
|
72
|
+
attr_reader :options
|
73
|
+
|
74
|
+
##
|
75
|
+
# Initialize search _type_ with _options_. Optionally
|
76
|
+
# a block may be passed, and the Search instance will
|
77
|
+
# be yielded to it.
|
78
|
+
|
79
|
+
def initialize options = {}, &block
|
80
|
+
@type = self.class.to_s.split('::').last.downcase.to_sym
|
81
|
+
@version = options.delete(:version) || 1.0
|
82
|
+
@offset = options.delete(:offset) || 0
|
83
|
+
@size = options.delete(:size) || :large
|
84
|
+
@language = options.delete(:language) || :en
|
85
|
+
@query = options.delete(:query)
|
86
|
+
@api_key = options.delete(:api_key) || :notsupplied
|
87
|
+
@options = options
|
88
|
+
raise Error, 'Do not initialize Google::Search; Use a subclass such as Google::Search::Web' if @type == :search
|
89
|
+
yield self if block
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# Set a response _block_ which is called every time
|
94
|
+
# #get_response is called. Useful for reporting etc.
|
95
|
+
|
96
|
+
def each_response &block
|
97
|
+
@each_response = block
|
98
|
+
end
|
99
|
+
|
100
|
+
##
|
101
|
+
# Iterate each item with _block_.
|
102
|
+
|
103
|
+
def each_item &block
|
104
|
+
response = self.next.response
|
105
|
+
if response.valid?
|
106
|
+
response.each { |item| yield item }
|
107
|
+
each_item &block
|
108
|
+
end
|
109
|
+
end
|
110
|
+
alias :each :each_item
|
111
|
+
|
112
|
+
##
|
113
|
+
# Return all items.
|
114
|
+
|
115
|
+
def all_items
|
116
|
+
select { true }
|
117
|
+
end
|
118
|
+
alias :all :all_items
|
119
|
+
|
120
|
+
##
|
121
|
+
# Return uri.
|
122
|
+
|
123
|
+
def get_uri
|
124
|
+
URI + "/G#{@type}Search?" +
|
125
|
+
(get_uri_params + options.to_a).
|
126
|
+
map { |key, value| "#{key}=#{Search.url_encode(value)}" unless value.nil? }.compact.join('&')
|
127
|
+
end
|
128
|
+
|
129
|
+
#:nodoc:
|
130
|
+
|
131
|
+
def get_uri_params
|
132
|
+
validate(:query) { |query| query.respond_to?(:to_str) && !query.to_str.empty? }
|
133
|
+
validate(:version) { |version| Numeric === version }
|
134
|
+
[[:start, offset],
|
135
|
+
[:rsz, size],
|
136
|
+
[:hl, language],
|
137
|
+
[:key, api_key],
|
138
|
+
[:v, version],
|
139
|
+
[:q, query]]
|
140
|
+
end
|
141
|
+
|
142
|
+
##
|
143
|
+
# Prepare for next request.
|
144
|
+
|
145
|
+
def next
|
146
|
+
@offset += Search.size_for(size) if sent
|
147
|
+
self
|
148
|
+
end
|
149
|
+
|
150
|
+
##
|
151
|
+
# Return raw JSON response string.
|
152
|
+
|
153
|
+
def get_raw
|
154
|
+
@sent = true
|
155
|
+
open(get_uri).read
|
156
|
+
end
|
157
|
+
|
158
|
+
##
|
159
|
+
# Return hash parsed from the raw JSON response.
|
160
|
+
|
161
|
+
def get_hash
|
162
|
+
Search.json_decode get_raw
|
163
|
+
end
|
164
|
+
|
165
|
+
##
|
166
|
+
# Return Response object wrapping the JSON
|
167
|
+
# response hash.
|
168
|
+
|
169
|
+
def get_response
|
170
|
+
raw = get_raw
|
171
|
+
hash = Search.json_decode raw
|
172
|
+
hash['responseSize'] = size
|
173
|
+
response = Response.new hash
|
174
|
+
response.raw = raw
|
175
|
+
@each_response.call response if @each_response
|
176
|
+
response
|
177
|
+
end
|
178
|
+
alias :response :get_response
|
179
|
+
|
180
|
+
##
|
181
|
+
# Return int for size _sym_.
|
182
|
+
|
183
|
+
def self.size_for sym
|
184
|
+
{ :small => 4,
|
185
|
+
:large => 8 }[sym]
|
186
|
+
end
|
187
|
+
|
188
|
+
#:nodoc:
|
189
|
+
|
190
|
+
def validate meth, &block
|
191
|
+
value = send meth
|
192
|
+
raise Error, "invalid #{type} #{meth} #{value.inspect}", caller unless yield value
|
193
|
+
end
|
194
|
+
|
195
|
+
##
|
196
|
+
# Decode JSON _string_.
|
197
|
+
|
198
|
+
def self.json_decode string
|
199
|
+
JSON.parse string
|
200
|
+
end
|
201
|
+
|
202
|
+
##
|
203
|
+
# Url encode _string_.
|
204
|
+
|
205
|
+
def self.url_encode string
|
206
|
+
string.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
|
207
|
+
'%' + $1.unpack('H2' * $1.size).join('%').upcase
|
208
|
+
}.tr(' ', '+')
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
212
|
+
end
|