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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +8 -1
- data/.standard.yml +1 -0
- data/Gemfile +5 -5
- data/README.md +1 -1
- data/Rakefile +4 -2
- data/VERSION +1 -1
- data/bin/standardrb +16 -0
- data/iiif-presentation.gemspec +23 -23
- data/lib/iiif/hash_behaviours.rb +29 -33
- data/lib/iiif/ordered_hash.rb +33 -33
- data/lib/iiif/presentation/abstract_resource.rb +17 -22
- data/lib/iiif/presentation/annotation.rb +9 -11
- data/lib/iiif/presentation/annotation_list.rb +8 -10
- data/lib/iiif/presentation/canvas.rb +11 -13
- data/lib/iiif/presentation/collection.rb +7 -9
- data/lib/iiif/presentation/image_resource.rb +33 -35
- data/lib/iiif/presentation/layer.rb +8 -10
- data/lib/iiif/presentation/manifest.rb +9 -12
- data/lib/iiif/presentation/range.rb +8 -10
- data/lib/iiif/presentation/resource.rb +5 -7
- data/lib/iiif/presentation/sequence.rb +8 -11
- data/lib/iiif/presentation/service.rb +1 -2
- data/lib/iiif/presentation.rb +7 -7
- data/lib/iiif/service.rb +121 -102
- data/lib/iiif/v3/abstract_resource.rb +132 -144
- data/lib/iiif/v3/presentation/annotation.rb +20 -21
- data/lib/iiif/v3/presentation/annotation_collection.rb +7 -9
- data/lib/iiif/v3/presentation/annotation_page.rb +14 -15
- data/lib/iiif/v3/presentation/canvas.rb +21 -22
- data/lib/iiif/v3/presentation/choice.rb +14 -15
- data/lib/iiif/v3/presentation/collection.rb +7 -8
- data/lib/iiif/v3/presentation/image_resource.rb +21 -22
- data/lib/iiif/v3/presentation/manifest.rb +18 -19
- data/lib/iiif/v3/presentation/nav_place.rb +125 -29
- data/lib/iiif/v3/presentation/range.rb +10 -10
- data/lib/iiif/v3/presentation/resource.rb +4 -5
- data/lib/iiif/v3/presentation/sequence.rb +10 -11
- data/lib/iiif/v3/presentation/service.rb +12 -13
- data/lib/iiif/v3/presentation.rb +8 -8
- data/spec/integration/iiif/presentation/image_resource_spec.rb +54 -58
- data/spec/integration/iiif/service_spec.rb +76 -77
- data/spec/integration/iiif/v3/abstract_resource_spec.rb +73 -73
- data/spec/integration/iiif/v3/presentation/image_resource_spec.rb +53 -56
- data/spec/spec_helper.rb +56 -64
- data/spec/unit/iiif/hash_behaviours_spec.rb +384 -387
- data/spec/unit/iiif/ordered_hash_spec.rb +86 -91
- data/spec/unit/iiif/presentation/abstract_resource_spec.rb +64 -70
- data/spec/unit/iiif/presentation/annotation_list_spec.rb +1 -3
- data/spec/unit/iiif/presentation/annotation_spec.rb +1 -3
- data/spec/unit/iiif/presentation/canvas_spec.rb +11 -14
- data/spec/unit/iiif/presentation/collection_spec.rb +26 -33
- data/spec/unit/iiif/presentation/image_resource_spec.rb +4 -6
- data/spec/unit/iiif/presentation/layer_spec.rb +16 -20
- data/spec/unit/iiif/presentation/manifest_spec.rb +45 -48
- data/spec/unit/iiif/presentation/range_spec.rb +18 -22
- data/spec/unit/iiif/presentation/resource_spec.rb +3 -6
- data/spec/unit/iiif/presentation/sequence_spec.rb +58 -63
- data/spec/unit/iiif/presentation/shared_examples/abstract_resource_only_keys.rb +4 -10
- data/spec/unit/iiif/presentation/shared_examples/any_type_keys.rb +5 -9
- data/spec/unit/iiif/presentation/shared_examples/array_only_keys.rb +9 -17
- data/spec/unit/iiif/presentation/shared_examples/int_only_keys.rb +6 -14
- data/spec/unit/iiif/presentation/shared_examples/string_only_keys.rb +6 -13
- data/spec/unit/iiif/service_spec.rb +8 -13
- data/spec/unit/iiif/v3/abstract_resource_define_methods_for_spec.rb +39 -37
- data/spec/unit/iiif/v3/abstract_resource_spec.rb +128 -143
- data/spec/unit/iiif/v3/presentation/annotation_collection_spec.rb +14 -17
- data/spec/unit/iiif/v3/presentation/annotation_page_spec.rb +71 -69
- data/spec/unit/iiif/v3/presentation/annotation_spec.rb +249 -233
- data/spec/unit/iiif/v3/presentation/canvas_spec.rb +211 -193
- data/spec/unit/iiif/v3/presentation/choice_spec.rb +81 -77
- data/spec/unit/iiif/v3/presentation/collection_spec.rb +24 -29
- data/spec/unit/iiif/v3/presentation/image_resource_spec.rb +70 -71
- data/spec/unit/iiif/v3/presentation/manifest_spec.rb +248 -237
- data/spec/unit/iiif/v3/presentation/nav_place_spec.rb +249 -49
- data/spec/unit/iiif/v3/presentation/range_spec.rb +28 -29
- data/spec/unit/iiif/v3/presentation/resource_spec.rb +83 -82
- data/spec/unit/iiif/v3/presentation/sequence_spec.rb +126 -120
- data/spec/unit/iiif/v3/presentation/service_spec.rb +123 -124
- data/spec/unit/iiif/v3/presentation/shared_examples/abstract_resource_only_keys.rb +4 -8
- data/spec/unit/iiif/v3/presentation/shared_examples/any_type_keys.rb +5 -7
- data/spec/unit/iiif/v3/presentation/shared_examples/array_only_keys.rb +9 -13
- data/spec/unit/iiif/v3/presentation/shared_examples/hash_only_keys.rb +9 -13
- data/spec/unit/iiif/v3/presentation/shared_examples/int_only_keys.rb +6 -10
- data/spec/unit/iiif/v3/presentation/shared_examples/numeric_only_keys.rb +6 -10
- data/spec/unit/iiif/v3/presentation/shared_examples/string_only_keys.rb +6 -10
- data/spec/unit/iiif/v3/presentation/shared_examples/uri_only_keys.rb +9 -13
- metadata +27 -69
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require_relative
|
|
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
|
|
9
|
+
CONTENT_RESOURCE_PROPERTIES = %w[format height width duration]
|
|
10
10
|
|
|
11
11
|
# used by Collection, AnnotationCollection
|
|
12
|
-
PAGING_PROPERTIES = %w
|
|
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
|
|
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
|
|
30
|
+
%w[id logo viewing_hint related see_also within]
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def string_only_keys
|
|
34
|
-
%w
|
|
34
|
+
%w[nav_date type format rights viewing_direction start_canvas]
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def array_only_keys
|
|
38
|
-
%w
|
|
38
|
+
%w[metadata thumbnail rendering first last next prev items service]
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def hash_only_keys
|
|
42
|
-
%w
|
|
42
|
+
%w[label requiredStatement summary]
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def int_only_keys
|
|
46
|
-
%w
|
|
46
|
+
%w[height width total start_index]
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def numeric_only_keys
|
|
50
|
-
%w
|
|
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
|
|
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
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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.
|
|
92
|
+
if s.is_a?(String) && File.exist?(s)
|
|
93
93
|
ordered_hash = IIIF::OrderedHash[JSON.parse(IO.read(s))]
|
|
94
|
-
elsif s.
|
|
94
|
+
elsif s.is_a?(String) && !File.exist?(s)
|
|
95
95
|
ordered_hash = IIIF::OrderedHash[JSON.parse(s)]
|
|
96
|
-
elsif s.
|
|
96
|
+
elsif s.is_a?(Hash)
|
|
97
97
|
ordered_hash = IIIF::OrderedHash[s]
|
|
98
98
|
else
|
|
99
|
-
m =
|
|
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.
|
|
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
|
-
|
|
106
|
+
IIIF::V3::Presentation::Service.from_ordered_hash(ordered_hash)
|
|
107
107
|
end
|
|
108
108
|
end
|
|
109
109
|
|
|
110
110
|
def validate
|
|
111
|
-
|
|
112
|
-
unless
|
|
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
|
-
|
|
119
|
-
if
|
|
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
|
-
|
|
125
|
+
uri_only_keys.each do |k|
|
|
126
126
|
if self[k]
|
|
127
|
-
|
|
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
|
|
138
|
-
unless
|
|
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
|
|
145
|
-
viewing_hint_val = self[
|
|
143
|
+
if has_key?("viewing_hint")
|
|
144
|
+
viewing_hint_val = self["viewing_hint"]
|
|
146
145
|
[*viewing_hint_val].each { |vh_val|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
|
155
|
-
unless self[
|
|
156
|
-
m =
|
|
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[
|
|
159
|
+
self["metadata"].each do |entry|
|
|
160
160
|
md_keys = entry.keys
|
|
161
|
-
unless md_keys.size == 2 && md_keys.include?(
|
|
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
|
|
169
|
-
unless self[
|
|
170
|
-
m =
|
|
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[
|
|
173
|
+
self["thumbnail"].each do |entry|
|
|
174
174
|
thumb_keys = entry.keys
|
|
175
|
-
unless thumb_keys.include?(
|
|
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
|
|
182
|
+
if has_key?("nav_date")
|
|
183
183
|
begin
|
|
184
|
-
Date.strptime(self[
|
|
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
|
-
|
|
191
|
-
if self.
|
|
192
|
-
|
|
193
|
-
|
|
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
|
|
206
|
-
unless self[
|
|
207
|
-
m =
|
|
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[
|
|
202
|
+
self["rendering"].each do |entry|
|
|
211
203
|
rendering_keys = entry.keys
|
|
212
|
-
unless rendering_keys.include?(
|
|
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
|
|
220
|
-
validate_uri(self[
|
|
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 =
|
|
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 && !
|
|
245
|
-
self[
|
|
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
|
-
|
|
243
|
+
validate
|
|
252
244
|
end
|
|
253
245
|
|
|
254
246
|
export_hash = IIIF::OrderedHash.new
|
|
255
247
|
|
|
256
248
|
if sort_json_ld_keys
|
|
257
|
-
|
|
258
|
-
export_hash[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
|
-
|
|
268
|
-
unless sort_json_ld_keys && k.start_with?(
|
|
269
|
-
if
|
|
270
|
-
export_hash[k] =
|
|
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
|
|
264
|
+
elsif data[k].is_a?(Hash)
|
|
273
265
|
export_hash[k] = IIIF::OrderedHash.new
|
|
274
|
-
|
|
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.
|
|
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
|
-
|
|
273
|
+
export_hash[k][sub_k] << if member.respond_to?(:to_ordered_hash)
|
|
274
|
+
member.to_ordered_hash(sub_opts)
|
|
284
275
|
else
|
|
285
|
-
|
|
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
|
|
284
|
+
elsif data[k].is_a?(Array)
|
|
294
285
|
export_hash[k] = []
|
|
295
286
|
|
|
296
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
302
|
+
hsh[sub_k] << if sub_member.respond_to?(:to_ordered_hash)
|
|
303
|
+
sub_member.to_ordered_hash(sub_opts)
|
|
314
304
|
else
|
|
315
|
-
|
|
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] =
|
|
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?(
|
|
343
|
-
type = IIIF::V3::AbstractResource.get_descendant_class_by_jld_type(hsh[
|
|
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 ==
|
|
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 ==
|
|
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].
|
|
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].
|
|
347
|
+
elsif hsh[key].is_a?(Array)
|
|
358
348
|
new_object[new_key] = []
|
|
359
349
|
hsh[key].each do |member|
|
|
360
|
-
if new_key ==
|
|
361
|
-
|
|
362
|
-
elsif member.
|
|
363
|
-
|
|
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
|
-
|
|
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
|
-
|
|
389
|
-
@data = hsh
|
|
390
|
-
end
|
|
376
|
+
protected
|
|
391
377
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
450
|
+
send(:[]=, key, val)
|
|
463
451
|
end
|
|
464
|
-
if
|
|
465
|
-
define_singleton_method("#{
|
|
452
|
+
if camelized_key != key
|
|
453
|
+
define_singleton_method("#{camelized_key}=") do |val|
|
|
466
454
|
validation.call(key, val) if block_given?
|
|
467
|
-
|
|
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
|
|
476
|
-
define_singleton_method(
|
|
477
|
-
|
|
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
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|