Class: IIIF::V3::AbstractResource

Inherits:
Object
  • Object
show all
Includes:
HashBehaviours
Defined in:
lib/iiif/v3/abstract_resource.rb

Constant Summary collapse

CONTENT_RESOURCE_PROPERTIES =

properties used by content resources only

%w{ format height width duration }
PAGING_PROPERTIES =

used by Collection, AnnotationCollection

%w{ first last next prev total start_index }

Constants included from HashBehaviours

HashBehaviours::SIMPLE_SELF_RETURNERS

Class Method Summary collapse

Instance Method Summary collapse

Methods included from HashBehaviours

#clear, #merge, #merge!, #reject!, #select, #select!

Constructor Details

#initialize(hsh = {}) ⇒ AbstractResource

Initialize a Presentation node

Parameters:

  • hsh (Hash) (defaults to: {})
    • Anything in this hash will be added to the Object. Order is only guaranteed if an ActiveSupport::OrderedHash is passed.
  • include_context (boolean)

    (default: false). Pass true if the context should be included.



72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/iiif/v3/abstract_resource.rb', line 72

def initialize(hsh={})
  if self.class == IIIF::V3::AbstractResource
    raise "#{self.class} is an abstract class. Please use one of its subclasses."
  end
  @data = IIIF::OrderedHash[hsh]
  self.define_methods_for_any_type_keys
  self.define_methods_for_string_only_keys
  self.define_methods_for_array_only_keys
  self.define_methods_for_hash_only_keys
  self.define_methods_for_int_only_keys
  self.define_methods_for_numeric_only_keys
  self.define_methods_for_uri_only_keys
  self.snakeize_keys
end

Class Method Details

.from_ordered_hash(hsh, default_klass = IIIF::OrderedHash) ⇒ Object



330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/iiif/v3/abstract_resource.rb', line 330

def self.from_ordered_hash(hsh, default_klass=IIIF::OrderedHash)
  # Create a new object (new_object)
  type = nil
  if hsh.has_key?('type')
    type = IIIF::V3::AbstractResource.get_descendant_class_by_jld_type(hsh['type'])
  end
  new_object = type.nil? ? default_klass.new : type.new

  hsh.keys.each do |key|
    new_key = key.underscore == key ? key : key.underscore
    if new_key == 'service'
      new_object[new_key] = hsh[key].map do |entry|
        IIIF::V3::AbstractResource.from_ordered_hash(entry, IIIF::V3::Presentation::Service)
      end
    elsif new_key == 'body'
      new_object[new_key] = IIIF::V3::AbstractResource.from_ordered_hash(hsh[key], IIIF::V3::Presentation::Resource)
    elsif hsh[key].kind_of?(Hash)
      new_object[new_key] = IIIF::V3::AbstractResource.from_ordered_hash(hsh[key])
    elsif hsh[key].kind_of?(Array)
      new_object[new_key] = []
      hsh[key].each do |member|
        if new_key == 'service'
          new_object[new_key] << IIIF::V3::AbstractResource.from_ordered_hash(member, IIIF::V3::Presentation::Service)
        elsif member.kind_of?(Hash)
          new_object[new_key] << IIIF::V3::AbstractResource.from_ordered_hash(member)
        else
          new_object[new_key] << member
          # Again, no nested arrays, right?
        end
      end
    else
      new_object[new_key] = hsh[key]
    end
  end
  new_object
end

.parse(s) ⇒ Object

Parse from a file path, string, or existing hash



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/iiif/v3/abstract_resource.rb', line 90

def parse(s)
  ordered_hash = nil
  if s.kind_of?(String) && File.exist?(s)
    ordered_hash = IIIF::OrderedHash[JSON.parse(IO.read(s))]
  elsif s.kind_of?(String) && !File.exist?(s)
    ordered_hash = IIIF::OrderedHash[JSON.parse(s)]
  elsif s.kind_of?(Hash)
    ordered_hash = IIIF::OrderedHash[s]
  else
    m = '#parse takes a path to a file, a JSON String, or a Hash, '
    m += "argument was a #{s.class}."
    if s.kind_of?(String)
      m+= "If you were trying to point to a file, does it exist?"
    end
    raise ArgumentError, m
  end
  return IIIF::V3::Presentation::Service.from_ordered_hash(ordered_hash)
end

Instance Method Details

#any_type_keysObject

NOTE: keys associated with a single resource type are not included below in xxx_keys methods: those single resource types should include additional keys by overriding xxx_keys as appropriate



27
28
29
30
31
# File 'lib/iiif/v3/abstract_resource.rb', line 27

def any_type_keys
  # values *may* be multivalued
  # NOTE: for id: "Resources that do not require URIs [for ids] may be assigned blank node identifiers"
  %w{ id logo viewing_hint related see_also within }
end

#array_only_keysObject



37
38
39
# File 'lib/iiif/v3/abstract_resource.rb', line 37

def array_only_keys
  %w{ metadata thumbnail rendering first last next prev items service }
end

#hash_only_keysObject



41
42
43
# File 'lib/iiif/v3/abstract_resource.rb', line 41

def hash_only_keys
  %w{ label requiredStatement summary }
end

#int_only_keysObject



45
46
47
# File 'lib/iiif/v3/abstract_resource.rb', line 45

def int_only_keys
  %w{ height width total start_index }
end

Not every subclass is allowed to have viewingDirect, but when it is, it must be one of these values



59
60
61
# File 'lib/iiif/v3/abstract_resource.rb', line 59

def legal_viewing_direction_values
  %w{ left-to-right right-to-left top-to-bottom bottom-to-top }
end


63
64
65
# File 'lib/iiif/v3/abstract_resource.rb', line 63

def legal_viewing_hint_values
  []
end

#numeric_only_keysObject



49
50
51
# File 'lib/iiif/v3/abstract_resource.rb', line 49

def numeric_only_keys
  %w{ duration }
end

#prohibited_keysObject

subclasses should override prohibited_keys as appropriate, e.g. super + PAGING_PROPERTIES



20
21
22
# File 'lib/iiif/v3/abstract_resource.rb', line 20

def prohibited_keys
  %w{ }
end

#required_keysObject

subclasses should override required_keys as appropriate, e.g. super + %w{ id }



15
16
17
# File 'lib/iiif/v3/abstract_resource.rb', line 15

def required_keys
  %w{ type }
end

#string_only_keysObject



33
34
35
# File 'lib/iiif/v3/abstract_resource.rb', line 33

def string_only_keys
  %w{ nav_date type format rights viewing_direction start_canvas }
end

#to_json(opts = {}) ⇒ Object

Options

  • pretty: (true|false). Should the JSON be pretty-printed? (default: false)
  • All options available in #to_ordered_hash


218
219
220
221
222
223
224
225
# File 'lib/iiif/v3/abstract_resource.rb', line 218

def to_json(opts={})
  hsh = self.to_ordered_hash(opts)
  if opts.fetch(:pretty, false)
    JSON.pretty_generate(hsh)
  else
    hsh.to_json
  end
end

#to_ordered_hash(opts = {}) ⇒ Object

Options:

  • force: (true|false). Skips validations.
  • include_context: (true|false). Adds the @context to the top of the document if it doesn't exist. Default: true.
  • sort_json_ld_keys: (true|false). Brings all properties starting with '@'. Default: true. to the top of the document and sorts them.


233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/iiif/v3/abstract_resource.rb', line 233

def to_ordered_hash(opts={})
  include_context = opts.fetch(:include_context, true)
  if include_context && !self.has_key?('@context')
    self['@context'] = IIIF::V3::Presentation::CONTEXT
  end
  force = opts.fetch(:force, false)
  sort_json_ld_keys = opts.fetch(:sort_json_ld_keys, true)

  unless force
    self.validate
  end

  export_hash = IIIF::OrderedHash.new

  if sort_json_ld_keys
    self.keys.select { |k| k.start_with?('@') }.sort!.each do |k|
      export_hash[k] = self.data[k]
    end
  end

  sub_opts = {
    include_context: false,
    sort_json_ld_keys: sort_json_ld_keys,
    force: force
  }
  self.keys.each do |k|
    unless sort_json_ld_keys && k.start_with?('@')
      if self.data[k].respond_to?(:to_ordered_hash) #.respond_to?(:to_ordered_hash)
        export_hash[k] = self.data[k].to_ordered_hash(sub_opts)

      elsif self.data[k].kind_of?(Hash)
        export_hash[k] = IIIF::OrderedHash.new
        self.data[k].each do |sub_k, v|

          if v.respond_to?(:to_ordered_hash)
            export_hash[k][sub_k] = v.to_ordered_hash(sub_opts)

          elsif v.kind_of?(Array)
            export_hash[k][sub_k] = []
            v.each do |member|
              if member.respond_to?(:to_ordered_hash)
                export_hash[k][sub_k] << member.to_ordered_hash(sub_opts)
              else
                export_hash[k][sub_k] << member
              end
            end
          else
            export_hash[k][sub_k] = v
          end
        end

      elsif self.data[k].kind_of?(Array)
        export_hash[k] = []

        self.data[k].each do |member|
          if member.respond_to?(:to_ordered_hash)
            export_hash[k] << member.to_ordered_hash(sub_opts)

          elsif member.kind_of?(Hash)
            hsh = IIIF::OrderedHash.new
            export_hash[k] << hsh
            member.each do |sub_k,v|

              if v.respond_to?(:to_ordered_hash)
                hsh[sub_k] = v.to_ordered_hash(sub_opts)

              elsif v.kind_of?(Array)
                hsh[sub_k] = []

                v.each do |sub_member|
                  if sub_member.respond_to?(:to_ordered_hash)
                    hsh[sub_k] << sub_member.to_ordered_hash(sub_opts)
                  else
                    hsh[sub_k] << sub_member
                  end
                end
              else
                hsh[sub_k] = v
              end
            end

          else
            export_hash[k] << member
            # there are no nested arrays, right?
          end
        end
      else
        export_hash[k] = self.data[k]
      end

    end
  end
  export_hash.remove_empties
  export_hash.camelize_keys
  export_hash
end

#uri_only_keysObject



53
54
55
# File 'lib/iiif/v3/abstract_resource.rb', line 53

def uri_only_keys
  %w{ }
end

#validateObject



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/iiif/v3/abstract_resource.rb', line 110

def validate
  self.required_keys.each do |k|
    unless self.has_key?(k)
      m = "A(n) #{k} is required for each #{self.class}"
      raise IIIF::V3::Presentation::MissingRequiredKeyError, m
    end
  end

  self.prohibited_keys.each do |k|
    if self.has_key?(k)
      m = "#{k} is a prohibited key in #{self.class}"
      raise IIIF::V3::Presentation::ProhibitedKeyError, m
    end
  end

  uri_only_keys.each do |k|
    if self[k]
      Array(self[k]).each { |val| validate_uri(val, k) }
    end
  end

  # Note:  self.define_methods_for_xxx_only_keys provides some validation at assignment time
  #  currently, there is NO validation when key values are assigned directly with hash syntax,
  #  e.g. my_image_resource['format'] = 'image/jpeg'

  # Viewing Direction values
  if self.has_key?('viewing_direction')
    unless self.legal_viewing_direction_values.include?(self['viewing_direction'])
      m = "viewingDirection must be one of #{legal_viewing_direction_values}"
      raise IIIF::V3::Presentation::IllegalValueError, m
    end
  end
  # Viewing Hint can be an Array ("Any resource type may have one or more viewing hints")
  if self.has_key?('viewing_hint')
    viewing_hint_val = self['viewing_hint']
    [*viewing_hint_val].each { |vh_val|
      unless self.legal_viewing_hint_values.include?(vh_val) || (vh_val.kind_of?(String) && vh_val =~ URI::regexp)
          m = "viewingHint for #{self.class} must be one or more of #{self.legal_viewing_hint_values} or a URI"
          raise IIIF::V3::Presentation::IllegalValueError, m
      end
    }
  end
  # Metadata is Array; each entry is a Hash containing (only) 'label' and 'value' properties
  if self.has_key?('metadata') && self['metadata']
    unless self['metadata'].all? { |entry| entry.kind_of?(Hash) }
      m = 'metadata must be an Array with Hash members'
      raise IIIF::V3::Presentation::IllegalValueError, m
    end
    self['metadata'].each do |entry|
      md_keys = entry.keys
      unless md_keys.size == 2 && md_keys.include?('label') && md_keys.include?('value')
        m = "metadata members must be a Hash of keys 'label' and 'value'"
        raise IIIF::V3::Presentation::IllegalValueError, m
      end
    end
  end
  # Thumbnail is Array; each entry is a Hash or ImageResource containing (at least) 'id' and 'type' keys
  if self.has_key?('thumbnail') && self['thumbnail']
    unless self['thumbnail'].all? { |entry| entry.kind_of?(IIIF::V3::Presentation::ImageResource) || entry.kind_of?(Hash) }
      m = 'thumbnail must be an Array with Hash or ImageResource members'
      raise IIIF::V3::Presentation::IllegalValueError, m
    end
    self['thumbnail'].each do |entry|
      thumb_keys = entry.keys
      unless thumb_keys.include?('id') && thumb_keys.include?('type')
        m = 'thumbnail members must include keys "id" and "type"'
        raise IIIF::V3::Presentation::IllegalValueError, m
      end
    end
  end
  # NavDate (navigation date)
  if self.has_key?('nav_date')
    begin
      Date.strptime(self['nav_date'], '%Y-%m-%dT%H:%M:%SZ')
    rescue ArgumentError
      m = "nav_date must be of form YYYY-MM-DDThh:mm:ssZ"
      raise IIIF::V3::Presentation::IllegalValueError, m
    end
  end

  if key?('rights') && !self['rights'].kind_of?(String)
    m = 'rights must be a String'
    raise IIIF::V3::Presentation::IllegalValueError, m
  end

  # rendering is Array; each entry is a Hash containing 'label' and 'format' keys
  if self.has_key?('rendering') && self['rendering']
    unless self['rendering'].all? { |entry| entry.kind_of?(Hash) }
      m = 'rendering must be an Array with Hash members'
      raise IIIF::V3::Presentation::IllegalValueError, m
    end
    self['rendering'].each do |entry|
      rendering_keys = entry.keys
      unless rendering_keys.include?('label') && rendering_keys.include?('format')
        m = 'rendering members must be a Hash including keys "label" and "format"'
        raise IIIF::V3::Presentation::IllegalValueError, m
      end
    end
  end
  # startCanvas is a String with a URI value
  if self.has_key?('start_canvas') && self['start_canvas'].kind_of?(String)
    validate_uri(self['start_canvas'], 'startCanvas') # raises IllegalValueError
  end
end