iiif-presentation 1.4.2 → 1.5.0

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.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +8 -1
  3. data/.standard.yml +1 -0
  4. data/Gemfile +5 -5
  5. data/README.md +1 -1
  6. data/Rakefile +4 -2
  7. data/VERSION +1 -1
  8. data/bin/standardrb +16 -0
  9. data/iiif-presentation.gemspec +23 -23
  10. data/lib/iiif/hash_behaviours.rb +29 -33
  11. data/lib/iiif/ordered_hash.rb +33 -33
  12. data/lib/iiif/presentation/abstract_resource.rb +17 -22
  13. data/lib/iiif/presentation/annotation.rb +9 -11
  14. data/lib/iiif/presentation/annotation_list.rb +8 -10
  15. data/lib/iiif/presentation/canvas.rb +11 -13
  16. data/lib/iiif/presentation/collection.rb +7 -9
  17. data/lib/iiif/presentation/image_resource.rb +33 -35
  18. data/lib/iiif/presentation/layer.rb +8 -10
  19. data/lib/iiif/presentation/manifest.rb +9 -12
  20. data/lib/iiif/presentation/range.rb +8 -10
  21. data/lib/iiif/presentation/resource.rb +5 -7
  22. data/lib/iiif/presentation/sequence.rb +8 -11
  23. data/lib/iiif/presentation/service.rb +1 -2
  24. data/lib/iiif/presentation.rb +7 -7
  25. data/lib/iiif/service.rb +121 -102
  26. data/lib/iiif/v3/abstract_resource.rb +127 -130
  27. data/lib/iiif/v3/presentation/annotation.rb +20 -21
  28. data/lib/iiif/v3/presentation/annotation_collection.rb +7 -9
  29. data/lib/iiif/v3/presentation/annotation_page.rb +14 -15
  30. data/lib/iiif/v3/presentation/canvas.rb +21 -22
  31. data/lib/iiif/v3/presentation/choice.rb +14 -15
  32. data/lib/iiif/v3/presentation/collection.rb +7 -8
  33. data/lib/iiif/v3/presentation/image_resource.rb +21 -22
  34. data/lib/iiif/v3/presentation/manifest.rb +18 -19
  35. data/lib/iiif/v3/presentation/nav_place.rb +125 -29
  36. data/lib/iiif/v3/presentation/range.rb +10 -10
  37. data/lib/iiif/v3/presentation/resource.rb +4 -5
  38. data/lib/iiif/v3/presentation/sequence.rb +10 -11
  39. data/lib/iiif/v3/presentation/service.rb +12 -13
  40. data/lib/iiif/v3/presentation.rb +8 -8
  41. data/spec/integration/iiif/presentation/image_resource_spec.rb +54 -58
  42. data/spec/integration/iiif/service_spec.rb +76 -77
  43. data/spec/integration/iiif/v3/abstract_resource_spec.rb +72 -73
  44. data/spec/integration/iiif/v3/presentation/image_resource_spec.rb +53 -56
  45. data/spec/spec_helper.rb +56 -64
  46. data/spec/unit/iiif/hash_behaviours_spec.rb +384 -387
  47. data/spec/unit/iiif/ordered_hash_spec.rb +86 -91
  48. data/spec/unit/iiif/presentation/abstract_resource_spec.rb +64 -70
  49. data/spec/unit/iiif/presentation/annotation_list_spec.rb +1 -3
  50. data/spec/unit/iiif/presentation/annotation_spec.rb +1 -3
  51. data/spec/unit/iiif/presentation/canvas_spec.rb +11 -14
  52. data/spec/unit/iiif/presentation/collection_spec.rb +26 -33
  53. data/spec/unit/iiif/presentation/image_resource_spec.rb +4 -6
  54. data/spec/unit/iiif/presentation/layer_spec.rb +16 -20
  55. data/spec/unit/iiif/presentation/manifest_spec.rb +45 -48
  56. data/spec/unit/iiif/presentation/range_spec.rb +18 -22
  57. data/spec/unit/iiif/presentation/resource_spec.rb +3 -6
  58. data/spec/unit/iiif/presentation/sequence_spec.rb +58 -63
  59. data/spec/unit/iiif/presentation/shared_examples/abstract_resource_only_keys.rb +4 -10
  60. data/spec/unit/iiif/presentation/shared_examples/any_type_keys.rb +5 -9
  61. data/spec/unit/iiif/presentation/shared_examples/array_only_keys.rb +9 -17
  62. data/spec/unit/iiif/presentation/shared_examples/int_only_keys.rb +6 -14
  63. data/spec/unit/iiif/presentation/shared_examples/string_only_keys.rb +6 -13
  64. data/spec/unit/iiif/service_spec.rb +8 -13
  65. data/spec/unit/iiif/v3/abstract_resource_define_methods_for_spec.rb +39 -37
  66. data/spec/unit/iiif/v3/abstract_resource_spec.rb +124 -131
  67. data/spec/unit/iiif/v3/presentation/annotation_collection_spec.rb +14 -17
  68. data/spec/unit/iiif/v3/presentation/annotation_page_spec.rb +71 -69
  69. data/spec/unit/iiif/v3/presentation/annotation_spec.rb +249 -233
  70. data/spec/unit/iiif/v3/presentation/canvas_spec.rb +211 -193
  71. data/spec/unit/iiif/v3/presentation/choice_spec.rb +81 -77
  72. data/spec/unit/iiif/v3/presentation/collection_spec.rb +24 -29
  73. data/spec/unit/iiif/v3/presentation/image_resource_spec.rb +70 -71
  74. data/spec/unit/iiif/v3/presentation/manifest_spec.rb +248 -235
  75. data/spec/unit/iiif/v3/presentation/nav_place_spec.rb +249 -49
  76. data/spec/unit/iiif/v3/presentation/range_spec.rb +28 -29
  77. data/spec/unit/iiif/v3/presentation/resource_spec.rb +83 -82
  78. data/spec/unit/iiif/v3/presentation/sequence_spec.rb +125 -119
  79. data/spec/unit/iiif/v3/presentation/service_spec.rb +123 -124
  80. data/spec/unit/iiif/v3/presentation/shared_examples/abstract_resource_only_keys.rb +4 -8
  81. data/spec/unit/iiif/v3/presentation/shared_examples/any_type_keys.rb +5 -7
  82. data/spec/unit/iiif/v3/presentation/shared_examples/array_only_keys.rb +9 -13
  83. data/spec/unit/iiif/v3/presentation/shared_examples/hash_only_keys.rb +9 -13
  84. data/spec/unit/iiif/v3/presentation/shared_examples/int_only_keys.rb +6 -10
  85. data/spec/unit/iiif/v3/presentation/shared_examples/numeric_only_keys.rb +6 -10
  86. data/spec/unit/iiif/v3/presentation/shared_examples/string_only_keys.rb +6 -10
  87. data/spec/unit/iiif/v3/presentation/shared_examples/uri_only_keys.rb +9 -13
  88. metadata +26 -65
@@ -1,4 +1,4 @@
1
- require_relative '../hash_behaviours'
1
+ require_relative "../hash_behaviours"
2
2
 
3
3
  module IIIF
4
4
  module V3
@@ -6,19 +6,19 @@ module IIIF
6
6
  include IIIF::HashBehaviours
7
7
 
8
8
  # properties used by content resources only
9
- CONTENT_RESOURCE_PROPERTIES = %w{ format height width duration }
9
+ CONTENT_RESOURCE_PROPERTIES = %w[format height width duration]
10
10
 
11
11
  # used by Collection, AnnotationCollection
12
- PAGING_PROPERTIES = %w{ first last next prev total start_index }
12
+ PAGING_PROPERTIES = %w[first last next prev total start_index]
13
13
 
14
14
  # subclasses should override required_keys as appropriate, e.g. super + %w{ id }
15
15
  def required_keys
16
- %w{ type }
16
+ %w[type]
17
17
  end
18
18
 
19
19
  # subclasses should override prohibited_keys as appropriate, e.g. super + PAGING_PROPERTIES
20
20
  def prohibited_keys
21
- %w{ }
21
+ %w[]
22
22
  end
23
23
 
24
24
  # NOTE: keys associated with a single resource type are not included below in xxx_keys methods:
@@ -27,37 +27,37 @@ module IIIF
27
27
  def any_type_keys
28
28
  # values *may* be multivalued
29
29
  # NOTE: for id: "Resources that do not require URIs [for ids] may be assigned blank node identifiers"
30
- %w{ id logo viewing_hint related see_also within }
30
+ %w[id logo viewing_hint related see_also within]
31
31
  end
32
32
 
33
33
  def string_only_keys
34
- %w{ nav_date type format rights viewing_direction start_canvas }
34
+ %w[nav_date type format rights viewing_direction start_canvas]
35
35
  end
36
36
 
37
37
  def array_only_keys
38
- %w{ metadata thumbnail rendering first last next prev items service }
38
+ %w[metadata thumbnail rendering first last next prev items service]
39
39
  end
40
40
 
41
41
  def hash_only_keys
42
- %w{ label requiredStatement summary }
42
+ %w[label requiredStatement summary]
43
43
  end
44
44
 
45
45
  def int_only_keys
46
- %w{ height width total start_index }
46
+ %w[height width total start_index]
47
47
  end
48
48
 
49
49
  def numeric_only_keys
50
- %w{ duration }
50
+ %w[duration]
51
51
  end
52
52
 
53
53
  def uri_only_keys
54
- %w{ }
54
+ %w[]
55
55
  end
56
56
 
57
57
  # Not every subclass is allowed to have viewingDirect, but when it is,
58
58
  # it must be one of these values
59
59
  def legal_viewing_direction_values
60
- %w{ left-to-right right-to-left top-to-bottom bottom-to-top }
60
+ %w[left-to-right right-to-left top-to-bottom bottom-to-top]
61
61
  end
62
62
 
63
63
  def legal_viewing_hint_values
@@ -69,19 +69,19 @@ module IIIF
69
69
  # Order is only guaranteed if an ActiveSupport::OrderedHash is passed.
70
70
  # @param [boolean] include_context (default: false). Pass true if the
71
71
  # context should be included.
72
- def initialize(hsh={})
73
- if self.class == IIIF::V3::AbstractResource
72
+ def initialize(hsh = {})
73
+ if instance_of?(IIIF::V3::AbstractResource)
74
74
  raise "#{self.class} is an abstract class. Please use one of its subclasses."
75
75
  end
76
76
  @data = IIIF::OrderedHash[hsh]
77
- self.define_methods_for_any_type_keys
78
- self.define_methods_for_string_only_keys
79
- self.define_methods_for_array_only_keys
80
- self.define_methods_for_hash_only_keys
81
- self.define_methods_for_int_only_keys
82
- self.define_methods_for_numeric_only_keys
83
- self.define_methods_for_uri_only_keys
84
- self.snakeize_keys
77
+ define_methods_for_any_type_keys
78
+ define_methods_for_string_only_keys
79
+ define_methods_for_array_only_keys
80
+ define_methods_for_hash_only_keys
81
+ define_methods_for_int_only_keys
82
+ define_methods_for_numeric_only_keys
83
+ define_methods_for_uri_only_keys
84
+ snakeize_keys
85
85
  end
86
86
 
87
87
  # Static methods / alternative constructors
@@ -89,34 +89,34 @@ module IIIF
89
89
  # Parse from a file path, string, or existing hash
90
90
  def parse(s)
91
91
  ordered_hash = nil
92
- if s.kind_of?(String) && File.exist?(s)
92
+ if s.is_a?(String) && File.exist?(s)
93
93
  ordered_hash = IIIF::OrderedHash[JSON.parse(IO.read(s))]
94
- elsif s.kind_of?(String) && !File.exist?(s)
94
+ elsif s.is_a?(String) && !File.exist?(s)
95
95
  ordered_hash = IIIF::OrderedHash[JSON.parse(s)]
96
- elsif s.kind_of?(Hash)
96
+ elsif s.is_a?(Hash)
97
97
  ordered_hash = IIIF::OrderedHash[s]
98
98
  else
99
- m = '#parse takes a path to a file, a JSON String, or a Hash, '
99
+ m = "#parse takes a path to a file, a JSON String, or a Hash, "
100
100
  m += "argument was a #{s.class}."
101
- if s.kind_of?(String)
102
- m+= "If you were trying to point to a file, does it exist?"
101
+ if s.is_a?(String)
102
+ m += "If you were trying to point to a file, does it exist?"
103
103
  end
104
104
  raise ArgumentError, m
105
105
  end
106
- return IIIF::V3::Presentation::Service.from_ordered_hash(ordered_hash)
106
+ IIIF::V3::Presentation::Service.from_ordered_hash(ordered_hash)
107
107
  end
108
108
  end
109
109
 
110
110
  def validate
111
- self.required_keys.each do |k|
112
- unless self.has_key?(k)
111
+ required_keys.each do |k|
112
+ unless has_key?(k)
113
113
  m = "A(n) #{k} is required for each #{self.class}"
114
114
  raise IIIF::V3::Presentation::MissingRequiredKeyError, m
115
115
  end
116
116
  end
117
117
 
118
- self.prohibited_keys.each do |k|
119
- if self.has_key?(k)
118
+ prohibited_keys.each do |k|
119
+ if has_key?(k)
120
120
  m = "#{k} is a prohibited key in #{self.class}"
121
121
  raise IIIF::V3::Presentation::ProhibitedKeyError, m
122
122
  end
@@ -133,90 +133,91 @@ module IIIF
133
133
  # e.g. my_image_resource['format'] = 'image/jpeg'
134
134
 
135
135
  # Viewing Direction values
136
- if self.has_key?('viewing_direction')
137
- unless self.legal_viewing_direction_values.include?(self['viewing_direction'])
136
+ if has_key?("viewing_direction")
137
+ unless legal_viewing_direction_values.include?(self["viewing_direction"])
138
138
  m = "viewingDirection must be one of #{legal_viewing_direction_values}"
139
139
  raise IIIF::V3::Presentation::IllegalValueError, m
140
140
  end
141
141
  end
142
142
  # Viewing Hint can be an Array ("Any resource type may have one or more viewing hints")
143
- if self.has_key?('viewing_hint')
144
- viewing_hint_val = self['viewing_hint']
143
+ if has_key?("viewing_hint")
144
+ viewing_hint_val = self["viewing_hint"]
145
145
  [*viewing_hint_val].each { |vh_val|
146
- unless self.legal_viewing_hint_values.include?(vh_val) || (vh_val.kind_of?(String) && vh_val =~ URI::regexp)
147
- m = "viewingHint for #{self.class} must be one or more of #{self.legal_viewing_hint_values} or a URI"
148
- raise IIIF::V3::Presentation::IllegalValueError, m
146
+ valid = legal_viewing_hint_values.include?(vh_val) || (vh_val.is_a?(String) && vh_val =~ URI::RFC2396_PARSER.make_regexp)
147
+ unless valid
148
+ m = "viewingHint for #{self.class} must be one or more of #{legal_viewing_hint_values} or a URI"
149
+ raise IIIF::V3::Presentation::IllegalValueError, m
149
150
  end
150
151
  }
151
152
  end
152
153
  # Metadata is Array; each entry is a Hash containing (only) 'label' and 'value' properties
153
- if self.has_key?('metadata') && self['metadata']
154
- unless self['metadata'].all? { |entry| entry.kind_of?(Hash) }
155
- m = 'metadata must be an Array with Hash members'
154
+ if has_key?("metadata") && self["metadata"]
155
+ unless self["metadata"].all? { |entry| entry.is_a?(Hash) }
156
+ m = "metadata must be an Array with Hash members"
156
157
  raise IIIF::V3::Presentation::IllegalValueError, m
157
158
  end
158
- self['metadata'].each do |entry|
159
+ self["metadata"].each do |entry|
159
160
  md_keys = entry.keys
160
- unless md_keys.size == 2 && md_keys.include?('label') && md_keys.include?('value')
161
+ unless md_keys.size == 2 && md_keys.include?("label") && md_keys.include?("value")
161
162
  m = "metadata members must be a Hash of keys 'label' and 'value'"
162
163
  raise IIIF::V3::Presentation::IllegalValueError, m
163
164
  end
164
165
  end
165
166
  end
166
167
  # Thumbnail is Array; each entry is a Hash or ImageResource containing (at least) 'id' and 'type' keys
167
- if self.has_key?('thumbnail') && self['thumbnail']
168
- unless self['thumbnail'].all? { |entry| entry.kind_of?(IIIF::V3::Presentation::ImageResource) || entry.kind_of?(Hash) }
169
- m = 'thumbnail must be an Array with Hash or ImageResource members'
168
+ if has_key?("thumbnail") && self["thumbnail"]
169
+ unless self["thumbnail"].all? { |entry| entry.is_a?(IIIF::V3::Presentation::ImageResource) || entry.is_a?(Hash) }
170
+ m = "thumbnail must be an Array with Hash or ImageResource members"
170
171
  raise IIIF::V3::Presentation::IllegalValueError, m
171
172
  end
172
- self['thumbnail'].each do |entry|
173
+ self["thumbnail"].each do |entry|
173
174
  thumb_keys = entry.keys
174
- unless thumb_keys.include?('id') && thumb_keys.include?('type')
175
+ unless thumb_keys.include?("id") && thumb_keys.include?("type")
175
176
  m = 'thumbnail members must include keys "id" and "type"'
176
177
  raise IIIF::V3::Presentation::IllegalValueError, m
177
178
  end
178
179
  end
179
180
  end
180
181
  # NavDate (navigation date)
181
- if self.has_key?('nav_date')
182
+ if has_key?("nav_date")
182
183
  begin
183
- Date.strptime(self['nav_date'], '%Y-%m-%dT%H:%M:%SZ')
184
+ Date.strptime(self["nav_date"], "%Y-%m-%dT%H:%M:%SZ")
184
185
  rescue ArgumentError
185
186
  m = "nav_date must be of form YYYY-MM-DDThh:mm:ssZ"
186
187
  raise IIIF::V3::Presentation::IllegalValueError, m
187
188
  end
188
189
  end
189
190
 
190
- if key?('rights') && !self['rights'].kind_of?(String)
191
- m = 'rights must be a String'
191
+ if key?("rights") && !self["rights"].is_a?(String)
192
+ m = "rights must be a String"
192
193
  raise IIIF::V3::Presentation::IllegalValueError, m
193
194
  end
194
195
 
195
196
  # rendering is Array; each entry is a Hash containing 'label' and 'format' keys
196
- if self.has_key?('rendering') && self['rendering']
197
- unless self['rendering'].all? { |entry| entry.kind_of?(Hash) }
198
- m = 'rendering must be an Array with Hash members'
197
+ if has_key?("rendering") && self["rendering"]
198
+ unless self["rendering"].all? { |entry| entry.is_a?(Hash) }
199
+ m = "rendering must be an Array with Hash members"
199
200
  raise IIIF::V3::Presentation::IllegalValueError, m
200
201
  end
201
- self['rendering'].each do |entry|
202
+ self["rendering"].each do |entry|
202
203
  rendering_keys = entry.keys
203
- unless rendering_keys.include?('label') && rendering_keys.include?('format')
204
+ unless rendering_keys.include?("label") && rendering_keys.include?("format")
204
205
  m = 'rendering members must be a Hash including keys "label" and "format"'
205
206
  raise IIIF::V3::Presentation::IllegalValueError, m
206
207
  end
207
208
  end
208
209
  end
209
210
  # startCanvas is a String with a URI value
210
- if self.has_key?('start_canvas') && self['start_canvas'].kind_of?(String)
211
- validate_uri(self['start_canvas'], 'startCanvas') # raises IllegalValueError
211
+ if has_key?("start_canvas") && self["start_canvas"].is_a?(String)
212
+ validate_uri(self["start_canvas"], "startCanvas") # raises IllegalValueError
212
213
  end
213
214
  end
214
215
 
215
216
  # Options
216
217
  # * pretty: (true|false). Should the JSON be pretty-printed? (default: false)
217
218
  # * All options available in #to_ordered_hash
218
- def to_json(opts={})
219
- hsh = self.to_ordered_hash(opts)
219
+ def to_json(opts = {})
220
+ hsh = to_ordered_hash(opts)
220
221
  if opts.fetch(:pretty, false)
221
222
  JSON.pretty_generate(hsh)
222
223
  else
@@ -230,23 +231,23 @@ module IIIF
230
231
  # document if it doesn't exist. Default: true.
231
232
  # * sort_json_ld_keys: (true|false). Brings all properties starting with
232
233
  # '@'. Default: true. to the top of the document and sorts them.
233
- def to_ordered_hash(opts={})
234
+ def to_ordered_hash(opts = {})
234
235
  include_context = opts.fetch(:include_context, true)
235
- if include_context && !self.has_key?('@context')
236
- self['@context'] = IIIF::V3::Presentation::CONTEXT
236
+ if include_context && !has_key?("@context")
237
+ self["@context"] = IIIF::V3::Presentation::CONTEXT
237
238
  end
238
239
  force = opts.fetch(:force, false)
239
240
  sort_json_ld_keys = opts.fetch(:sort_json_ld_keys, true)
240
241
 
241
242
  unless force
242
- self.validate
243
+ validate
243
244
  end
244
245
 
245
246
  export_hash = IIIF::OrderedHash.new
246
247
 
247
248
  if sort_json_ld_keys
248
- self.keys.select { |k| k.start_with?('@') }.sort!.each do |k|
249
- export_hash[k] = self.data[k]
249
+ keys.select { |k| k.start_with?("@") }.sort!.each do |k|
250
+ export_hash[k] = data[k]
250
251
  end
251
252
  end
252
253
 
@@ -255,25 +256,24 @@ module IIIF
255
256
  sort_json_ld_keys: sort_json_ld_keys,
256
257
  force: force
257
258
  }
258
- self.keys.each do |k|
259
- unless sort_json_ld_keys && k.start_with?('@')
260
- if self.data[k].respond_to?(:to_ordered_hash) #.respond_to?(:to_ordered_hash)
261
- export_hash[k] = self.data[k].to_ordered_hash(sub_opts)
259
+ keys.each do |k|
260
+ unless sort_json_ld_keys && k.start_with?("@")
261
+ if data[k].respond_to?(:to_ordered_hash) # .respond_to?(:to_ordered_hash)
262
+ export_hash[k] = data[k].to_ordered_hash(sub_opts)
262
263
 
263
- elsif self.data[k].kind_of?(Hash)
264
+ elsif data[k].is_a?(Hash)
264
265
  export_hash[k] = IIIF::OrderedHash.new
265
- self.data[k].each do |sub_k, v|
266
-
266
+ data[k].each do |sub_k, v|
267
267
  if v.respond_to?(:to_ordered_hash)
268
268
  export_hash[k][sub_k] = v.to_ordered_hash(sub_opts)
269
269
 
270
- elsif v.kind_of?(Array)
270
+ elsif v.is_a?(Array)
271
271
  export_hash[k][sub_k] = []
272
272
  v.each do |member|
273
- if member.respond_to?(:to_ordered_hash)
274
- export_hash[k][sub_k] << member.to_ordered_hash(sub_opts)
273
+ export_hash[k][sub_k] << if member.respond_to?(:to_ordered_hash)
274
+ member.to_ordered_hash(sub_opts)
275
275
  else
276
- export_hash[k][sub_k] << member
276
+ member
277
277
  end
278
278
  end
279
279
  else
@@ -281,29 +281,28 @@ module IIIF
281
281
  end
282
282
  end
283
283
 
284
- elsif self.data[k].kind_of?(Array)
284
+ elsif data[k].is_a?(Array)
285
285
  export_hash[k] = []
286
286
 
287
- self.data[k].each do |member|
287
+ data[k].each do |member|
288
288
  if member.respond_to?(:to_ordered_hash)
289
289
  export_hash[k] << member.to_ordered_hash(sub_opts)
290
290
 
291
- elsif member.kind_of?(Hash)
291
+ elsif member.is_a?(Hash)
292
292
  hsh = IIIF::OrderedHash.new
293
293
  export_hash[k] << hsh
294
- member.each do |sub_k,v|
295
-
294
+ member.each do |sub_k, v|
296
295
  if v.respond_to?(:to_ordered_hash)
297
296
  hsh[sub_k] = v.to_ordered_hash(sub_opts)
298
297
 
299
- elsif v.kind_of?(Array)
298
+ elsif v.is_a?(Array)
300
299
  hsh[sub_k] = []
301
300
 
302
301
  v.each do |sub_member|
303
- if sub_member.respond_to?(:to_ordered_hash)
304
- hsh[sub_k] << sub_member.to_ordered_hash(sub_opts)
302
+ hsh[sub_k] << if sub_member.respond_to?(:to_ordered_hash)
303
+ sub_member.to_ordered_hash(sub_opts)
305
304
  else
306
- hsh[sub_k] << sub_member
305
+ sub_member
307
306
  end
308
307
  end
309
308
  else
@@ -317,7 +316,7 @@ module IIIF
317
316
  end
318
317
  end
319
318
  else
320
- export_hash[k] = self.data[k]
319
+ export_hash[k] = data[k]
321
320
  end
322
321
 
323
322
  end
@@ -327,33 +326,33 @@ module IIIF
327
326
  export_hash
328
327
  end
329
328
 
330
- def self.from_ordered_hash(hsh, default_klass=IIIF::OrderedHash)
329
+ def self.from_ordered_hash(hsh, default_klass = IIIF::OrderedHash)
331
330
  # Create a new object (new_object)
332
331
  type = nil
333
- if hsh.has_key?('type')
334
- type = IIIF::V3::AbstractResource.get_descendant_class_by_jld_type(hsh['type'])
332
+ if hsh.has_key?("type")
333
+ type = IIIF::V3::AbstractResource.get_descendant_class_by_jld_type(hsh["type"])
335
334
  end
336
335
  new_object = type.nil? ? default_klass.new : type.new
337
336
 
338
337
  hsh.keys.each do |key|
339
- new_key = key.underscore == key ? key : key.underscore
340
- if new_key == 'service'
338
+ new_key = (key.underscore == key) ? key : key.underscore
339
+ if new_key == "service"
341
340
  new_object[new_key] = hsh[key].map do |entry|
342
341
  IIIF::V3::AbstractResource.from_ordered_hash(entry, IIIF::V3::Presentation::Service)
343
342
  end
344
- elsif new_key == 'body'
343
+ elsif new_key == "body"
345
344
  new_object[new_key] = IIIF::V3::AbstractResource.from_ordered_hash(hsh[key], IIIF::V3::Presentation::Resource)
346
- elsif hsh[key].kind_of?(Hash)
345
+ elsif hsh[key].is_a?(Hash)
347
346
  new_object[new_key] = IIIF::V3::AbstractResource.from_ordered_hash(hsh[key])
348
- elsif hsh[key].kind_of?(Array)
347
+ elsif hsh[key].is_a?(Array)
349
348
  new_object[new_key] = []
350
349
  hsh[key].each do |member|
351
- if new_key == 'service'
352
- new_object[new_key] << IIIF::V3::AbstractResource.from_ordered_hash(member, IIIF::V3::Presentation::Service)
353
- elsif member.kind_of?(Hash)
354
- new_object[new_key] << IIIF::V3::AbstractResource.from_ordered_hash(member)
350
+ new_object[new_key] << if new_key == "service"
351
+ IIIF::V3::AbstractResource.from_ordered_hash(member, IIIF::V3::Presentation::Service)
352
+ elsif member.is_a?(Hash)
353
+ IIIF::V3::AbstractResource.from_ordered_hash(member)
355
354
  else
356
- new_object[new_key] << member
355
+ member
357
356
  # Again, no nested arrays, right?
358
357
  end
359
358
  end
@@ -364,8 +363,6 @@ module IIIF
364
363
  new_object
365
364
  end
366
365
 
367
- protected
368
-
369
366
  def self.get_descendant_class_by_jld_type(type)
370
367
  IIIF::V3::AbstractResource.all_known_subclasses.find do |klass|
371
368
  klass.const_defined?(:TYPE) && klass.const_get(:TYPE) == type
@@ -376,13 +373,11 @@ module IIIF
376
373
  @all_known_subclasses ||= IIIF::V3::AbstractResource.descendants.reject(&:singleton_class?)
377
374
  end
378
375
 
379
- def data=(hsh)
380
- @data = hsh
381
- end
376
+ protected
382
377
 
383
- def data
384
- @data
385
- end
378
+ attr_writer :data
379
+
380
+ attr_reader :data
386
381
 
387
382
  def define_methods_for_any_type_keys
388
383
  define_accessor_methods(*any_type_keys)
@@ -391,14 +386,14 @@ module IIIF
391
386
  # an array for empty values.
392
387
  any_type_keys.each do |key|
393
388
  define_singleton_method(key) do
394
- self.send('[]', key)
389
+ send(:[], key)
395
390
  end
396
391
  end
397
392
  end
398
393
 
399
394
  def define_methods_for_array_only_keys
400
395
  define_accessor_methods(*array_only_keys) do |key, val|
401
- unless val.kind_of?(Array)
396
+ unless val.is_a?(Array)
402
397
  m = "#{key} must be an Array."
403
398
  raise IIIF::V3::Presentation::IllegalValueError, m
404
399
  end
@@ -407,7 +402,7 @@ module IIIF
407
402
 
408
403
  def define_methods_for_hash_only_keys
409
404
  define_accessor_methods(*hash_only_keys) do |key, val|
410
- unless val.kind_of?(Hash)
405
+ unless val.is_a?(Hash)
411
406
  m = "#{key} must be a Hash."
412
407
  raise IIIF::V3::Presentation::IllegalValueError, m
413
408
  end
@@ -416,7 +411,7 @@ module IIIF
416
411
 
417
412
  def define_methods_for_string_only_keys
418
413
  define_accessor_methods(*string_only_keys) do |key, val|
419
- unless val.kind_of?(String)
414
+ unless val.is_a?(String)
420
415
  m = "#{key} must be a String."
421
416
  raise IIIF::V3::Presentation::IllegalValueError, m
422
417
  end
@@ -425,7 +420,7 @@ module IIIF
425
420
 
426
421
  def define_methods_for_int_only_keys
427
422
  define_accessor_methods(*int_only_keys) do |key, val|
428
- unless val.kind_of?(Integer) && val > 0
423
+ unless val.is_a?(Integer) && val > 0
429
424
  m = "#{key} must be a positive Integer."
430
425
  raise IIIF::V3::Presentation::IllegalValueError, m
431
426
  end
@@ -434,7 +429,7 @@ module IIIF
434
429
 
435
430
  def define_methods_for_numeric_only_keys
436
431
  define_accessor_methods(*numeric_only_keys) do |key, val|
437
- unless val.kind_of?(Numeric) && val > 0
432
+ unless val.is_a?(Numeric) && val > 0
438
433
  m = "#{key} must be a positive Integer or Float."
439
434
  raise IIIF::V3::Presentation::IllegalValueError, m
440
435
  end
@@ -447,15 +442,17 @@ module IIIF
447
442
 
448
443
  def define_accessor_methods(*keys, &validation)
449
444
  keys.each do |key|
445
+ camelized_key = key.camelize(:lower)
446
+
450
447
  # Setter
451
448
  define_singleton_method("#{key}=") do |val|
452
449
  validation.call(key, val) if block_given?
453
- self.send('[]=', key, val)
450
+ send(:[]=, key, val)
454
451
  end
455
- if key.camelize(:lower) != key
456
- define_singleton_method("#{key.camelize(:lower)}=") do |val|
452
+ if camelized_key != key
453
+ define_singleton_method("#{camelized_key}=") do |val|
457
454
  validation.call(key, val) if block_given?
458
- self.send('[]=', key, val)
455
+ send(:[]=, key, val)
459
456
  end
460
457
  end
461
458
  # Getter
@@ -463,22 +460,22 @@ module IIIF
463
460
  self[key] ||= []
464
461
  self[key]
465
462
  end
466
- if key.camelize(:lower) != key
467
- define_singleton_method(key.camelize(:lower)) do
468
- self.send('[]', key)
463
+ if camelized_key != key
464
+ define_singleton_method(camelized_key) do
465
+ send(:[], key)
469
466
  end
470
467
  end
471
468
  end
472
469
  end
473
470
 
474
471
  private
472
+
475
473
  def validate_uri(val, key)
476
- unless val.kind_of?(String) && val =~ /\A#{URI::regexp}\z/
477
- m = "#{key} value must be a String containing a URI for #{self.class}"
478
- raise IIIF::V3::Presentation::IllegalValueError, m
479
- end
474
+ raise ArgumentError if URI(val).scheme.nil?
475
+ rescue URI::Error, ArgumentError
476
+ m = "#{key} value must be a String containing a URI for #{self.class}"
477
+ raise IIIF::V3::Presentation::IllegalValueError, m
480
478
  end
481
-
482
479
  end
483
480
  end
484
481
  end
@@ -2,68 +2,67 @@ module IIIF
2
2
  module V3
3
3
  module Presentation
4
4
  class Annotation < IIIF::V3::AbstractResource
5
-
6
- TYPE = 'Annotation'.freeze
5
+ TYPE = "Annotation".freeze
7
6
 
8
7
  def required_keys
9
- super + %w{ id motivation target }
8
+ super + %w[id motivation target]
10
9
  end
11
10
 
12
11
  def prohibited_keys
13
12
  super + CONTENT_RESOURCE_PROPERTIES + PAGING_PROPERTIES +
14
- %w{ nav_date viewing_direction start_canvas content_annotations }
13
+ %w[nav_date viewing_direction start_canvas content_annotations]
15
14
  end
16
15
 
17
16
  def any_type_keys
18
- super + %w{ body target }
17
+ super + %w[body target]
19
18
  end
20
19
 
21
20
  def uri_only_keys
22
- super + %w{ id }
21
+ super + %w[id]
23
22
  end
24
23
 
25
24
  def string_only_keys
26
- super + %w{ motivation time_mode }
25
+ super + %w[motivation time_mode]
27
26
  end
28
27
 
29
28
  def legal_time_mode_values
30
- %w{ trim scale loop }.freeze
29
+ %w[trim scale loop].freeze
31
30
  end
32
31
 
33
32
  def legal_viewing_hint_values
34
- super + %w{ none }
33
+ super + %w[none]
35
34
  end
36
35
 
37
- def initialize(hsh={})
38
- hsh['type'] = TYPE unless hsh.has_key? 'type'
39
- hsh['motivation'] = 'painting' unless hsh.has_key? 'motivation'
40
- super(hsh)
36
+ def initialize(hsh = {})
37
+ hsh["type"] = TYPE unless hsh.has_key? "type"
38
+ hsh["motivation"] = "painting" unless hsh.has_key? "motivation"
39
+ super
41
40
  end
42
41
 
43
42
  def validate
44
43
  super
45
44
 
46
- if self.has_key?('body') && self['body'].kind_of?(IIIF::V3::Presentation::ImageResource)
45
+ if has_key?("body") && self["body"].is_a?(IIIF::V3::Presentation::ImageResource)
47
46
  img_res_class_str = "IIIF::V3::Presentation::ImageResource"
48
47
 
49
- unless self.motivation == 'painting'
48
+ unless motivation == "painting"
50
49
  m = "#{self.class} motivation must be 'painting' when body is a kind of #{img_res_class_str}"
51
50
  raise IIIF::V3::Presentation::IllegalValueError, m
52
51
  end
53
52
 
54
- body_resource = self['body']
55
- body_id = body_resource['id']
53
+ body_resource = self["body"]
54
+ body_id = body_resource["id"]
56
55
  if body_id && body_id =~ /^https?:/
57
- validate_uri(body_id, 'anno body ImageResource id') # can raise IllegalValueError
56
+ validate_uri(body_id, "anno body ImageResource id") # can raise IllegalValueError
58
57
  else
59
58
  m = "when #{self.class} body is a kind of #{img_res_class_str}, ImageResource id must be an http(s) URI"
60
59
  raise IIIF::V3::Presentation::IllegalValueError, m
61
60
  end
62
61
  end
63
62
 
64
- if self.has_key?('time_mode')
65
- unless self.legal_time_mode_values.include?(self['time_mode'])
66
- m = "timeMode for #{self.class} must be one of #{self.legal_time_mode_values}."
63
+ if has_key?("time_mode")
64
+ unless legal_time_mode_values.include?(self["time_mode"])
65
+ m = "timeMode for #{self.class} must be one of #{legal_time_mode_values}."
67
66
  raise IIIF::V3::Presentation::IllegalValueError, m
68
67
  end
69
68
  end