iiif-presentation 1.4.1 → 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 +132 -144
  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 +73 -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 +128 -143
  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 -237
  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 +126 -120
  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 +27 -69
@@ -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 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 rights 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,43 +89,42 @@ 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
123
123
  end
124
124
 
125
- self.uri_only_keys.each do |k|
125
+ uri_only_keys.each do |k|
126
126
  if self[k]
127
- vals = *self[k]
128
- vals.each { |val| validate_uri(val, k) }
127
+ Array(self[k]).each { |val| validate_uri(val, k) }
129
128
  end
130
129
  end
131
130
 
@@ -134,98 +133,91 @@ module IIIF
134
133
  # e.g. my_image_resource['format'] = 'image/jpeg'
135
134
 
136
135
  # Viewing Direction values
137
- if self.has_key?('viewing_direction')
138
- 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"])
139
138
  m = "viewingDirection must be one of #{legal_viewing_direction_values}"
140
139
  raise IIIF::V3::Presentation::IllegalValueError, m
141
140
  end
142
141
  end
143
142
  # Viewing Hint can be an Array ("Any resource type may have one or more viewing hints")
144
- if self.has_key?('viewing_hint')
145
- viewing_hint_val = self['viewing_hint']
143
+ if has_key?("viewing_hint")
144
+ viewing_hint_val = self["viewing_hint"]
146
145
  [*viewing_hint_val].each { |vh_val|
147
- unless self.legal_viewing_hint_values.include?(vh_val) || (vh_val.kind_of?(String) && vh_val =~ URI::regexp)
148
- m = "viewingHint for #{self.class} must be one or more of #{self.legal_viewing_hint_values} or a URI"
149
- 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
150
150
  end
151
151
  }
152
152
  end
153
153
  # Metadata is Array; each entry is a Hash containing (only) 'label' and 'value' properties
154
- if self.has_key?('metadata') && self['metadata']
155
- unless self['metadata'].all? { |entry| entry.kind_of?(Hash) }
156
- 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"
157
157
  raise IIIF::V3::Presentation::IllegalValueError, m
158
158
  end
159
- self['metadata'].each do |entry|
159
+ self["metadata"].each do |entry|
160
160
  md_keys = entry.keys
161
- 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")
162
162
  m = "metadata members must be a Hash of keys 'label' and 'value'"
163
163
  raise IIIF::V3::Presentation::IllegalValueError, m
164
164
  end
165
165
  end
166
166
  end
167
167
  # Thumbnail is Array; each entry is a Hash or ImageResource containing (at least) 'id' and 'type' keys
168
- if self.has_key?('thumbnail') && self['thumbnail']
169
- unless self['thumbnail'].all? { |entry| entry.kind_of?(IIIF::V3::Presentation::ImageResource) || entry.kind_of?(Hash) }
170
- 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"
171
171
  raise IIIF::V3::Presentation::IllegalValueError, m
172
172
  end
173
- self['thumbnail'].each do |entry|
173
+ self["thumbnail"].each do |entry|
174
174
  thumb_keys = entry.keys
175
- unless thumb_keys.include?('id') && thumb_keys.include?('type')
175
+ unless thumb_keys.include?("id") && thumb_keys.include?("type")
176
176
  m = 'thumbnail members must include keys "id" and "type"'
177
177
  raise IIIF::V3::Presentation::IllegalValueError, m
178
178
  end
179
179
  end
180
180
  end
181
181
  # NavDate (navigation date)
182
- if self.has_key?('nav_date')
182
+ if has_key?("nav_date")
183
183
  begin
184
- Date.strptime(self['nav_date'], '%Y-%m-%dT%H:%M:%SZ')
184
+ Date.strptime(self["nav_date"], "%Y-%m-%dT%H:%M:%SZ")
185
185
  rescue ArgumentError
186
186
  m = "nav_date must be of form YYYY-MM-DDThh:mm:ssZ"
187
187
  raise IIIF::V3::Presentation::IllegalValueError, m
188
188
  end
189
189
  end
190
- # rights is Array; each entry is a Hash containing 'id' with a URI value
191
- if self.has_key?('rights')
192
- unless self['rights'].all? { |entry| entry.kind_of?(Hash) }
193
- m = 'rights must be an Array with Hash members'
194
- raise IIIF::V3::Presentation::IllegalValueError, m
195
- end
196
- self['rights'].each do |entry|
197
- unless entry.keys.include?('id')
198
- m = 'rights members must be a Hash including "id"'
199
- raise IIIF::V3::Presentation::IllegalValueError, m
200
- end
201
- validate_uri(entry['id'], 'id') # raises IllegalValueError
202
- end
190
+
191
+ if key?("rights") && !self["rights"].is_a?(String)
192
+ m = "rights must be a String"
193
+ raise IIIF::V3::Presentation::IllegalValueError, m
203
194
  end
195
+
204
196
  # rendering is Array; each entry is a Hash containing 'label' and 'format' keys
205
- if self.has_key?('rendering') && self['rendering']
206
- unless self['rendering'].all? { |entry| entry.kind_of?(Hash) }
207
- 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"
208
200
  raise IIIF::V3::Presentation::IllegalValueError, m
209
201
  end
210
- self['rendering'].each do |entry|
202
+ self["rendering"].each do |entry|
211
203
  rendering_keys = entry.keys
212
- unless rendering_keys.include?('label') && rendering_keys.include?('format')
204
+ unless rendering_keys.include?("label") && rendering_keys.include?("format")
213
205
  m = 'rendering members must be a Hash including keys "label" and "format"'
214
206
  raise IIIF::V3::Presentation::IllegalValueError, m
215
207
  end
216
208
  end
217
209
  end
218
210
  # startCanvas is a String with a URI value
219
- if self.has_key?('start_canvas') && self['start_canvas'].kind_of?(String)
220
- 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
221
213
  end
222
214
  end
223
215
 
224
216
  # Options
225
217
  # * pretty: (true|false). Should the JSON be pretty-printed? (default: false)
226
218
  # * All options available in #to_ordered_hash
227
- def to_json(opts={})
228
- hsh = self.to_ordered_hash(opts)
219
+ def to_json(opts = {})
220
+ hsh = to_ordered_hash(opts)
229
221
  if opts.fetch(:pretty, false)
230
222
  JSON.pretty_generate(hsh)
231
223
  else
@@ -239,23 +231,23 @@ module IIIF
239
231
  # document if it doesn't exist. Default: true.
240
232
  # * sort_json_ld_keys: (true|false). Brings all properties starting with
241
233
  # '@'. Default: true. to the top of the document and sorts them.
242
- def to_ordered_hash(opts={})
234
+ def to_ordered_hash(opts = {})
243
235
  include_context = opts.fetch(:include_context, true)
244
- if include_context && !self.has_key?('@context')
245
- self['@context'] = IIIF::V3::Presentation::CONTEXT
236
+ if include_context && !has_key?("@context")
237
+ self["@context"] = IIIF::V3::Presentation::CONTEXT
246
238
  end
247
239
  force = opts.fetch(:force, false)
248
240
  sort_json_ld_keys = opts.fetch(:sort_json_ld_keys, true)
249
241
 
250
242
  unless force
251
- self.validate
243
+ validate
252
244
  end
253
245
 
254
246
  export_hash = IIIF::OrderedHash.new
255
247
 
256
248
  if sort_json_ld_keys
257
- self.keys.select { |k| k.start_with?('@') }.sort!.each do |k|
258
- export_hash[k] = self.data[k]
249
+ keys.select { |k| k.start_with?("@") }.sort!.each do |k|
250
+ export_hash[k] = data[k]
259
251
  end
260
252
  end
261
253
 
@@ -264,25 +256,24 @@ module IIIF
264
256
  sort_json_ld_keys: sort_json_ld_keys,
265
257
  force: force
266
258
  }
267
- self.keys.each do |k|
268
- unless sort_json_ld_keys && k.start_with?('@')
269
- if self.data[k].respond_to?(:to_ordered_hash) #.respond_to?(:to_ordered_hash)
270
- 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)
271
263
 
272
- elsif self.data[k].kind_of?(Hash)
264
+ elsif data[k].is_a?(Hash)
273
265
  export_hash[k] = IIIF::OrderedHash.new
274
- self.data[k].each do |sub_k, v|
275
-
266
+ data[k].each do |sub_k, v|
276
267
  if v.respond_to?(:to_ordered_hash)
277
268
  export_hash[k][sub_k] = v.to_ordered_hash(sub_opts)
278
269
 
279
- elsif v.kind_of?(Array)
270
+ elsif v.is_a?(Array)
280
271
  export_hash[k][sub_k] = []
281
272
  v.each do |member|
282
- if member.respond_to?(:to_ordered_hash)
283
- 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)
284
275
  else
285
- export_hash[k][sub_k] << member
276
+ member
286
277
  end
287
278
  end
288
279
  else
@@ -290,29 +281,28 @@ module IIIF
290
281
  end
291
282
  end
292
283
 
293
- elsif self.data[k].kind_of?(Array)
284
+ elsif data[k].is_a?(Array)
294
285
  export_hash[k] = []
295
286
 
296
- self.data[k].each do |member|
287
+ data[k].each do |member|
297
288
  if member.respond_to?(:to_ordered_hash)
298
289
  export_hash[k] << member.to_ordered_hash(sub_opts)
299
290
 
300
- elsif member.kind_of?(Hash)
291
+ elsif member.is_a?(Hash)
301
292
  hsh = IIIF::OrderedHash.new
302
293
  export_hash[k] << hsh
303
- member.each do |sub_k,v|
304
-
294
+ member.each do |sub_k, v|
305
295
  if v.respond_to?(:to_ordered_hash)
306
296
  hsh[sub_k] = v.to_ordered_hash(sub_opts)
307
297
 
308
- elsif v.kind_of?(Array)
298
+ elsif v.is_a?(Array)
309
299
  hsh[sub_k] = []
310
300
 
311
301
  v.each do |sub_member|
312
- if sub_member.respond_to?(:to_ordered_hash)
313
- 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)
314
304
  else
315
- hsh[sub_k] << sub_member
305
+ sub_member
316
306
  end
317
307
  end
318
308
  else
@@ -326,7 +316,7 @@ module IIIF
326
316
  end
327
317
  end
328
318
  else
329
- export_hash[k] = self.data[k]
319
+ export_hash[k] = data[k]
330
320
  end
331
321
 
332
322
  end
@@ -336,33 +326,33 @@ module IIIF
336
326
  export_hash
337
327
  end
338
328
 
339
- def self.from_ordered_hash(hsh, default_klass=IIIF::OrderedHash)
329
+ def self.from_ordered_hash(hsh, default_klass = IIIF::OrderedHash)
340
330
  # Create a new object (new_object)
341
331
  type = nil
342
- if hsh.has_key?('type')
343
- 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"])
344
334
  end
345
335
  new_object = type.nil? ? default_klass.new : type.new
346
336
 
347
337
  hsh.keys.each do |key|
348
- new_key = key.underscore == key ? key : key.underscore
349
- if new_key == 'service'
338
+ new_key = (key.underscore == key) ? key : key.underscore
339
+ if new_key == "service"
350
340
  new_object[new_key] = hsh[key].map do |entry|
351
341
  IIIF::V3::AbstractResource.from_ordered_hash(entry, IIIF::V3::Presentation::Service)
352
342
  end
353
- elsif new_key == 'body'
343
+ elsif new_key == "body"
354
344
  new_object[new_key] = IIIF::V3::AbstractResource.from_ordered_hash(hsh[key], IIIF::V3::Presentation::Resource)
355
- elsif hsh[key].kind_of?(Hash)
345
+ elsif hsh[key].is_a?(Hash)
356
346
  new_object[new_key] = IIIF::V3::AbstractResource.from_ordered_hash(hsh[key])
357
- elsif hsh[key].kind_of?(Array)
347
+ elsif hsh[key].is_a?(Array)
358
348
  new_object[new_key] = []
359
349
  hsh[key].each do |member|
360
- if new_key == 'service'
361
- new_object[new_key] << IIIF::V3::AbstractResource.from_ordered_hash(member, IIIF::V3::Presentation::Service)
362
- elsif member.kind_of?(Hash)
363
- 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)
364
354
  else
365
- new_object[new_key] << member
355
+ member
366
356
  # Again, no nested arrays, right?
367
357
  end
368
358
  end
@@ -373,8 +363,6 @@ module IIIF
373
363
  new_object
374
364
  end
375
365
 
376
- protected
377
-
378
366
  def self.get_descendant_class_by_jld_type(type)
379
367
  IIIF::V3::AbstractResource.all_known_subclasses.find do |klass|
380
368
  klass.const_defined?(:TYPE) && klass.const_get(:TYPE) == type
@@ -385,13 +373,11 @@ module IIIF
385
373
  @all_known_subclasses ||= IIIF::V3::AbstractResource.descendants.reject(&:singleton_class?)
386
374
  end
387
375
 
388
- def data=(hsh)
389
- @data = hsh
390
- end
376
+ protected
391
377
 
392
- def data
393
- @data
394
- end
378
+ attr_writer :data
379
+
380
+ attr_reader :data
395
381
 
396
382
  def define_methods_for_any_type_keys
397
383
  define_accessor_methods(*any_type_keys)
@@ -400,14 +386,14 @@ module IIIF
400
386
  # an array for empty values.
401
387
  any_type_keys.each do |key|
402
388
  define_singleton_method(key) do
403
- self.send('[]', key)
389
+ send(:[], key)
404
390
  end
405
391
  end
406
392
  end
407
393
 
408
394
  def define_methods_for_array_only_keys
409
395
  define_accessor_methods(*array_only_keys) do |key, val|
410
- unless val.kind_of?(Array)
396
+ unless val.is_a?(Array)
411
397
  m = "#{key} must be an Array."
412
398
  raise IIIF::V3::Presentation::IllegalValueError, m
413
399
  end
@@ -416,7 +402,7 @@ module IIIF
416
402
 
417
403
  def define_methods_for_hash_only_keys
418
404
  define_accessor_methods(*hash_only_keys) do |key, val|
419
- unless val.kind_of?(Hash)
405
+ unless val.is_a?(Hash)
420
406
  m = "#{key} must be a Hash."
421
407
  raise IIIF::V3::Presentation::IllegalValueError, m
422
408
  end
@@ -425,7 +411,7 @@ module IIIF
425
411
 
426
412
  def define_methods_for_string_only_keys
427
413
  define_accessor_methods(*string_only_keys) do |key, val|
428
- unless val.kind_of?(String)
414
+ unless val.is_a?(String)
429
415
  m = "#{key} must be a String."
430
416
  raise IIIF::V3::Presentation::IllegalValueError, m
431
417
  end
@@ -434,7 +420,7 @@ module IIIF
434
420
 
435
421
  def define_methods_for_int_only_keys
436
422
  define_accessor_methods(*int_only_keys) do |key, val|
437
- unless val.kind_of?(Integer) && val > 0
423
+ unless val.is_a?(Integer) && val > 0
438
424
  m = "#{key} must be a positive Integer."
439
425
  raise IIIF::V3::Presentation::IllegalValueError, m
440
426
  end
@@ -443,7 +429,7 @@ module IIIF
443
429
 
444
430
  def define_methods_for_numeric_only_keys
445
431
  define_accessor_methods(*numeric_only_keys) do |key, val|
446
- unless val.kind_of?(Numeric) && val > 0
432
+ unless val.is_a?(Numeric) && val > 0
447
433
  m = "#{key} must be a positive Integer or Float."
448
434
  raise IIIF::V3::Presentation::IllegalValueError, m
449
435
  end
@@ -456,15 +442,17 @@ module IIIF
456
442
 
457
443
  def define_accessor_methods(*keys, &validation)
458
444
  keys.each do |key|
445
+ camelized_key = key.camelize(:lower)
446
+
459
447
  # Setter
460
448
  define_singleton_method("#{key}=") do |val|
461
449
  validation.call(key, val) if block_given?
462
- self.send('[]=', key, val)
450
+ send(:[]=, key, val)
463
451
  end
464
- if key.camelize(:lower) != key
465
- define_singleton_method("#{key.camelize(:lower)}=") do |val|
452
+ if camelized_key != key
453
+ define_singleton_method("#{camelized_key}=") do |val|
466
454
  validation.call(key, val) if block_given?
467
- self.send('[]=', key, val)
455
+ send(:[]=, key, val)
468
456
  end
469
457
  end
470
458
  # Getter
@@ -472,22 +460,22 @@ module IIIF
472
460
  self[key] ||= []
473
461
  self[key]
474
462
  end
475
- if key.camelize(:lower) != key
476
- define_singleton_method(key.camelize(:lower)) do
477
- self.send('[]', key)
463
+ if camelized_key != key
464
+ define_singleton_method(camelized_key) do
465
+ send(:[], key)
478
466
  end
479
467
  end
480
468
  end
481
469
  end
482
470
 
483
471
  private
472
+
484
473
  def validate_uri(val, key)
485
- unless val.kind_of?(String) && val =~ /\A#{URI::regexp}\z/
486
- m = "#{key} value must be a String containing a URI for #{self.class}"
487
- raise IIIF::V3::Presentation::IllegalValueError, m
488
- 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
489
478
  end
490
-
491
479
  end
492
480
  end
493
481
  end