From 3d469b459b464899c3c8578e240e0d9dc0fca6f3 Mon Sep 17 00:00:00 2001 From: Naomi Dushay Date: Mon, 17 Jul 2017 23:41:53 -0700 Subject: [PATCH 1/4] v3 canvas: improve specificity, validation and tests (WIP) --- lib/iiif/v3/presentation/canvas.rb | 46 ++- spec/unit/iiif/v3/presentation/canvas_spec.rb | 314 ++++++++++++++++-- 2 files changed, 319 insertions(+), 41 deletions(-) diff --git a/lib/iiif/v3/presentation/canvas.rb b/lib/iiif/v3/presentation/canvas.rb index 75b9e1d..d14f6bf 100644 --- a/lib/iiif/v3/presentation/canvas.rb +++ b/lib/iiif/v3/presentation/canvas.rb @@ -3,25 +3,22 @@ module V3 module Presentation class Canvas < IIIF::V3::AbstractResource - # TODO (?) a simple 'Image Canvas' constructor. - - TYPE = 'Canvas' + TYPE = 'Canvas'.freeze def required_keys super + %w{ id label } end - def array_only_keys - super + %w{ content } + def prohibited_keys + super + PAGING_PROPERTIES + %w{ viewing_direction format nav_date start_canvas content_annotations } end - # TODO: test and validate - def int_only_keys - super + %w{ width height } + def array_only_keys + super + %w{ content } end def legal_viewing_hint_values - super + %w{ non-paged } + super + %w{ paged continuous non-paged facing-pages auto-advance } end def initialize(hsh={}) @@ -31,7 +28,36 @@ def initialize(hsh={}) def validate super - # TODO: all members of content are of type AnnotationPage + + id_uri = URI.parse(self['id']) + unless self['id'] =~ /^https?:/ && id_uri.fragment.nil? + err_msg = "id must be an http(s) URI without a fragment for #{self.class}" + raise IIIF::V3::Presentation::IllegalValueError, err_msg + end + + content = self['content'] + if content && content.any? + unless content.all? { |entry| entry.instance_of?(IIIF::V3::Presentation::AnnotationPage) } + err_msg = 'All entries in the content list must be a IIIF::V3::Presentation::AnnotationPage' + raise IIIF::V3::Presentation::IllegalValueError, err_msg + end + end + + # "A canvas MUST have exactly one width and one height, or exactly one duration. + # It may have width, height and duration."" + height = self['height'] + width = self['width'] + extent_err_msg = "#{self.class} must have (a height and a width) and/or a duration" + if (!!height ^ !!width) # this is an exclusive or: forces height and width to boolean + raise IIIF::V3::Presentation::IllegalValueError, extent_err_msg + end + duration = self['duration'] + unless (height && width) || duration + raise IIIF::V3::Presentation::IllegalValueError, extent_err_msg + end + + # TODO: Content must not be associated with space or time outside of the Canvas’s dimensions, + # such as at coordinates below 0,0, greater than the height or width, before 0 seconds, or after the duration. end end end diff --git a/spec/unit/iiif/v3/presentation/canvas_spec.rb b/spec/unit/iiif/v3/presentation/canvas_spec.rb index 056f05a..e7d5b08 100644 --- a/spec/unit/iiif/v3/presentation/canvas_spec.rb +++ b/spec/unit/iiif/v3/presentation/canvas_spec.rb @@ -1,51 +1,303 @@ describe IIIF::V3::Presentation::Canvas do - let(:fixed_values) do - { - "@context" => [ - "http://iiif.io/api/presentation/3/context.json", - "http://www.w3.org/ns/anno.jsonld" - ], - "id" => "http://www.example.org/iiif/book1/canvas/p1", - "type" => "Canvas", - "label" => "p. 1", - "height" => 1000, - "width" => 750, - "content" => [ ] - } + describe '#required_keys' do + %w{ type id label }.each do |k| + it k do + expect(subject.required_keys).to include(k) + end + end + end + + describe '#prohibited_keys' do + it 'contains the expected key names' do + keys = described_class::PAGING_PROPERTIES + + %w{ + viewing_direction + format + nav_date + start_canvas + content_annotations + } + expect(subject.prohibited_keys).to include(*keys) + end + end + + describe '#array_only_keys' do + it 'content' do + expect(subject.array_only_keys).to include('content') + end end + describe '#legal_viewing_hint_values' do + it 'contains the expected values' do + expect(subject.legal_viewing_hint_values).to contain_exactly('paged', 'continuous', 'non-paged', 'facing-pages', 'auto-advance') + end + end describe '#initialize' do - it 'sets type' do + it 'sets type to Canvas by default' do expect(subject['type']).to eq 'Canvas' end + it 'allows subclasses to override type' do + subclass = Class.new(described_class) do + def initialize(hsh={}) + hsh = { 'type' => 'a:SubClass' } + super(hsh) + end + end + sub = subclass.new + expect(sub['type']).to eq 'a:SubClass' + end end - describe "#{described_class}.int_only_keys" do - it_behaves_like 'it has the appropriate methods for integer-only keys v3' - end + describe '#validate' do + let(:exp_id_err_msg) { "id must be an http(s) URI without a fragment for #{described_class}" } + before(:each) do + subject['id'] = 'http://www.example.org/my_canvas' + subject['label'] = 'foo' + end + it 'raises an IllegalValueError if id is not URI' do + subject['id'] = 'foo' + expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_id_err_msg) + end + it 'raises an IllegalValueError if id is not http(s)' do + subject['id'] = 'ftp://www.example.org' + expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_id_err_msg) + end + it 'raises an IllegalValueError if id has a fragment' do + subject['id'] = 'http://www.example.org#foo' + expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_id_err_msg) + end - describe "#{described_class}.numeric_only_keys" do - it_behaves_like 'it has the appropriate methods for numeric-only keys v3' - end + let(:exp_extent_err_msg) { "#{described_class} must have (a height and a width) and/or a duration" } + it 'raises an IllegalValueError if height is a string' do + subject['height'] = 'foo' + expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_extent_err_msg) + end + it 'raises an IllegalValueError if height but no width' do + subject['height'] = 666 + expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_extent_err_msg) + end + it 'raises an IllegalValueError if width but no height' do + subject['width'] = 666 + subject['duration'] = 66.6 + expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_extent_err_msg) + end + it 'raises an IllegalValueError if no width, height or duration' do + expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_extent_err_msg) + end + it 'allows width, height and duration' do + subject['width'] = 666 + subject['height'] = 666 + subject['duration'] = 66.6 + expect { subject.validate }.not_to raise_error + end - describe "#{described_class}.define_methods_for_string_only_keys" do - it_behaves_like 'it has the appropriate methods for string-only keys v3' + it 'raises IllegalValueError for content entry that is not an AnnotationPage' do + subject['content'] = [IIIF::V3::Presentation::AnnotationPage.new, IIIF::V3::Presentation::Annotation.new] + exp_err_msg = "All entries in the content list must be a IIIF::V3::Presentation::AnnotationPage" + expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_err_msg) + end end - describe "#{described_class}.define_methods_for_array_only_keys" do - it_behaves_like 'it has the appropriate methods for array-only keys v3' - end + describe 'realistic examples' do + let(:minimal_canvas_object) { described_class.new({ + "id" => "http://example.org/iiif/book1/canvas/c1", + 'label' => "so minimal it's not here", + "height" => 1000, + "width" => 1000 + })} + describe 'minimal canvas' do + it 'validates' do + expect{minimal_canvas_object.validate}.not_to raise_error + end + it 'has expected required values' do + expect(minimal_canvas_object.type).to eq described_class::TYPE + expect(minimal_canvas_object.id).to eq "http://example.org/iiif/book1/canvas/c1" + expect(minimal_canvas_object.label).to eq "so minimal it's not here" + expect(minimal_canvas_object.height).to eq 1000 + expect(minimal_canvas_object.width).to eq 1000 + end + end + describe 'minimal with empty content' do + let(:canvas_object) { + minimal_canvas_object['content'] = [] + minimal_canvas_object + } + it 'validates' do + expect{canvas_object.validate}.not_to raise_error + end + it 'has additional values' do + expect(canvas_object.content).to eq [] + end + end + let(:anno_page) { IIIF::V3::Presentation::AnnotationPage.new( + "id" => "http://example.org/iiif/book1/page/p1/1", + 'items' => [] + ) } + describe 'minimal with content' do + let(:canvas_object) { + minimal_canvas_object['content'] = [anno_page, anno_page] + minimal_canvas_object + } + it 'validates' do + expect{canvas_object.validate}.not_to raise_error + end + it 'has content value' do + expect(canvas_object.content.size).to eq 2 + expect(canvas_object.content).to eq [anno_page, anno_page] + end + end - describe "#{described_class}.define_methods_for_any_type_keys" do - it_behaves_like 'it has the appropriate methods for any-type keys v3' - end + ex5 = { + "id" => "http://example.org/iiif/book1/canvas/p2", + "type" => "Canvas", + "label" => "p. 2", + "height" =>1000, + "width" =>750, + "images" => [ + { + "type" => "Annotation", + "motivation" => "painting", + "resource" =>{ + "id" => "http://example.org/images/book1-page2/full/1500,2000/0/default.jpg", + "type" => "dctypes:Image", + "format" => "image/jpeg", + "height" =>2000, + "width" =>1500, + "service" => { + "@context" => "http://iiif.io/api/image/2/context.json", + "id" => "http://example.org/images/book1-page2", + "profile" => "http://iiif.io/api/image/2/level1.json", + "height" =>8000, + "width" =>6000, + "tiles" => [{"width" => 512, "scaleFactors" => [1,2,4,8,16]}] + } + }, + "on" => "http://example.org/iiif/book1/canvas/p2" + } + ], + "otherContent" => [ + { + "id" => "http://example.org/iiif/book1/list/p2", + "type" => "AnnotationList", + "within" => "http://example.org/iiif/book1/layer/l1" + } + ] + } + + describe 'video object' do + let(:canvas_for_video) { described_class.new({ + "id" => "http://tomcrane.github.io/scratch/manifests/3/canvas/1", + "label" => "Associate multiple Video representations as Choice", + "height" => 1000, + "width" => 1000, + "duration" => 100, + "content" => [anno_page] + }) } + it 'validates' do + expect{canvas_for_video.validate}.not_to raise_error + end + it 'height, width, duration' do + expect(canvas_for_video.height).to eq 1000 + expect(canvas_for_video.width).to eq 1000 + expect(canvas_for_video.duration).to eq 100 + end + end - describe "#legal_viewing_hint_values" do - it "should not error" do - expect{subject.legal_viewing_hint_values}.not_to raise_error + describe 'audio object' do + let(:canvas_for_audio) { described_class.new({ + "id" => "http://tomcrane.github.io/scratch/manifests/3/canvas/2", + "label" => "Track 2", + "description" => "foo", + "duration" => 45, + "content" => [anno_page] + })} + it 'validates' do + expect{canvas_for_audio.validate}.not_to raise_error + end + it 'duration' do + expect(canvas_for_audio.duration).to eq 45 + end + it 'description' do + expect(canvas_for_audio.description).to eq 'foo' + end end + + ex_3d_tom_crane = { + "id" =>"http://tomcrane.github.io/scratch/manifests/3/canvas/3d", + "type" =>"Canvas", + "thumbnail" =>"http://files.universalviewer.io/manifests/nelis/animal-skull/thumb.jpg", + "width" =>10000, + "height" =>10000, + "depth" =>10000, + "label" =>"A stage for an object", + "content" =>[ + { + "id" =>"...", + "type" =>"AnnotationPage", + "items" =>[ + { + "id" =>"http://tomcrane.github.io/scratch/manifests/3/3d/anno1", + "type" =>"Annotation", + "motivation" =>"painting", + "body" =>{ + "id" =>"http://files.universalviewer.io/manifests/nelis/animal-skull/animal-skull.json", + "type" =>"PhysicalObject", + "format" =>"application/vnd.threejs+json", + "label" =>"Animal Skull" + }, + "target" =>"http://tomcrane.github.io/scratch/manifests/3/canvas/3d" + } + ] + } + ] +} + + # file + # Audio + # video + # 3d object + # document + # citation + # image + # book + + stanford_1 = { + "type" => "Canvas", + "id" => "https://purl.stanford.edu/bc592pz8308/iiif3/canvas/0001", + "label" => "image", + "height" => 7579, + "width" => 10108, + "content" => [ + { + "type" => "AnnotationPage", + "id" => "https://purl.stanford.edu/bc592pz8308/iiif3/annotation_page/0001", + "items" => [ + { + "type" => "Annotation", + "motivation" => "painting", + "id" => "https://purl.stanford.edu/bc592pz8308/iiif3/annotation/0001", + "body" => { + "type" => "Image", + "id" => "https://stacks.stanford.edu/image/iiif/bc592pz8308%2Fbc592pz8308_05_0001/full/full/0/default.jpg", + "format" => "image/jpeg", + "height" => 7579, + "width" => 10108, + "service" => { + "@context" => "http://iiif.io/api/image/2/context.json", + "@id" => "https://stacks.stanford.edu/image/iiif/bc592pz8308%2Fbc592pz8308_05_0001", + "id" => "https://stacks.stanford.edu/image/iiif/bc592pz8308%2Fbc592pz8308_05_0001", + "profile" => "http://iiif.io/api/image/2/level1.json" + } + }, + "target" => "https://purl.stanford.edu/bc592pz8308/iiif3/canvas/0001" + } + ] + } + ] + } + end end From 9db8119b236f47a0000169b67d1f92ce644d9864 Mon Sep 17 00:00:00 2001 From: Naomi Dushay Date: Tue, 18 Jul 2017 18:50:06 -0700 Subject: [PATCH 2/4] v3 canvas: add depth property for 3d objects --- lib/iiif/v3/presentation/canvas.rb | 4 + spec/unit/iiif/v3/presentation/canvas_spec.rb | 93 ++++++------------- 2 files changed, 32 insertions(+), 65 deletions(-) diff --git a/lib/iiif/v3/presentation/canvas.rb b/lib/iiif/v3/presentation/canvas.rb index d14f6bf..7ab6f83 100644 --- a/lib/iiif/v3/presentation/canvas.rb +++ b/lib/iiif/v3/presentation/canvas.rb @@ -13,6 +13,10 @@ def prohibited_keys super + PAGING_PROPERTIES + %w{ viewing_direction format nav_date start_canvas content_annotations } end + def int_only_keys + super + %w{ depth } + end + def array_only_keys super + %w{ content } end diff --git a/spec/unit/iiif/v3/presentation/canvas_spec.rb b/spec/unit/iiif/v3/presentation/canvas_spec.rb index e7d5b08..ad68f3a 100644 --- a/spec/unit/iiif/v3/presentation/canvas_spec.rb +++ b/spec/unit/iiif/v3/presentation/canvas_spec.rb @@ -22,6 +22,12 @@ end end + describe '#int_only_keys' do + it 'depth (for 3d objects)' do + expect(subject.int_only_keys).to include('depth') + end + end + describe '#array_only_keys' do it 'content' do expect(subject.array_only_keys).to include('content') @@ -149,42 +155,27 @@ def initialize(hsh={}) end end - ex5 = { - "id" => "http://example.org/iiif/book1/canvas/p2", - "type" => "Canvas", - "label" => "p. 2", - "height" =>1000, - "width" =>750, - "images" => [ - { - "type" => "Annotation", - "motivation" => "painting", - "resource" =>{ - "id" => "http://example.org/images/book1-page2/full/1500,2000/0/default.jpg", - "type" => "dctypes:Image", - "format" => "image/jpeg", - "height" =>2000, - "width" =>1500, - "service" => { - "@context" => "http://iiif.io/api/image/2/context.json", - "id" => "http://example.org/images/book1-page2", - "profile" => "http://iiif.io/api/image/2/level1.json", - "height" =>8000, - "width" =>6000, - "tiles" => [{"width" => 512, "scaleFactors" => [1,2,4,8,16]}] - } - }, - "on" => "http://example.org/iiif/book1/canvas/p2" - } - ], - "otherContent" => [ - { - "id" => "http://example.org/iiif/book1/list/p2", - "type" => "AnnotationList", - "within" => "http://example.org/iiif/book1/layer/l1" - } - ] - } + describe '3d object' do + let(:canvas_3d_object) { described_class.new({ + "id" => "http://tomcrane.github.io/scratch/manifests/3/canvas/3d", + "thumbnail" => [{'id' => "http://files.universalviewer.io/manifests/nelis/animal-skull/thumb.jpg", + 'type' => 'Image'}], + "width" => 10000, + "height" => 10000, + "depth" => 10000, + "label" => "A stage for an object", + "content" => [anno_page] + })} + it 'validates' do + expect{canvas_3d_object.validate}.not_to raise_error + end + it 'thumbnail' do + expect(canvas_3d_object.thumbnail).to eq [{'id' => "http://files.universalviewer.io/manifests/nelis/animal-skull/thumb.jpg", 'type' => 'Image'}] + end + it 'depth' do + expect(canvas_3d_object.depth).to eq 10000 + end + end describe 'video object' do let(:canvas_for_video) { described_class.new({ @@ -224,35 +215,7 @@ def initialize(hsh={}) end end - ex_3d_tom_crane = { - "id" =>"http://tomcrane.github.io/scratch/manifests/3/canvas/3d", - "type" =>"Canvas", - "thumbnail" =>"http://files.universalviewer.io/manifests/nelis/animal-skull/thumb.jpg", - "width" =>10000, - "height" =>10000, - "depth" =>10000, - "label" =>"A stage for an object", - "content" =>[ - { - "id" =>"...", - "type" =>"AnnotationPage", - "items" =>[ - { - "id" =>"http://tomcrane.github.io/scratch/manifests/3/3d/anno1", - "type" =>"Annotation", - "motivation" =>"painting", - "body" =>{ - "id" =>"http://files.universalviewer.io/manifests/nelis/animal-skull/animal-skull.json", - "type" =>"PhysicalObject", - "format" =>"application/vnd.threejs+json", - "label" =>"Animal Skull" - }, - "target" =>"http://tomcrane.github.io/scratch/manifests/3/canvas/3d" - } - ] - } - ] -} + # TODO: still need Stanford examples # file # Audio From f4359134c38f51290dd13a02c7112f636a30c4c8 Mon Sep 17 00:00:00 2001 From: Naomi Dushay Date: Wed, 19 Jul 2017 00:05:38 -0700 Subject: [PATCH 3/4] v3 canvas spec: add realistic examples from stanford --- spec/unit/iiif/v3/presentation/canvas_spec.rb | 167 +++++++++--------- 1 file changed, 86 insertions(+), 81 deletions(-) diff --git a/spec/unit/iiif/v3/presentation/canvas_spec.rb b/spec/unit/iiif/v3/presentation/canvas_spec.rb index ad68f3a..c657ac8 100644 --- a/spec/unit/iiif/v3/presentation/canvas_spec.rb +++ b/spec/unit/iiif/v3/presentation/canvas_spec.rb @@ -155,6 +155,75 @@ def initialize(hsh={}) end end + describe 'file object' do + describe 'without extent info' do + let(:file_object) { described_class.new({ + "id" => "https://example.org/bd742gh0511/iiif3/canvas/bd742gh0511_1", + "label" => "File 1", + "content" => [anno_page] + })} + it 'validates' do + expect{file_object.validate}.not_to raise_error + end + end + end + + describe 'image object' do + describe 'without extent info' do + let(:image_object) { described_class.new({ + "id" => "https://example.org/yv090xk3108/iiif3/canvas/yv090xk3108_1", + "label" => "image", + "content" => [anno_page] + })} + it 'validates' do + expect{image_object.validate}.not_to raise_error + end + end + describe 'with extent given' do + let(:image_object) { described_class.new({ + "id" => "https://example.org/yy816tv6021/iiif3/canvas/yy816tv6021_3", + "label" => "Image of media (1 of 2)", + "height" => 3456, + "width" => 5184, + "content" => [anno_page] + })} + it 'validates' do + expect{image_object.validate}.not_to raise_error + end + end + end + + describe 'audio object' do + describe 'without duration' do + let(:canvas_for_audio) { described_class.new({ + "id" => "https://example.org/xk681bt2506/iiif3/canvas/xk681bt2506_1", + "label" => "Audio file 1", + "content" => [anno_page] + })} + it 'validates' do + expect{canvas_for_audio.validate}.not_to raise_error + end + end + describe 'digerati example' do + let(:canvas_for_audio) { described_class.new({ + "id" => "http://tomcrane.github.io/scratch/manifests/3/canvas/2", + "label" => "Track 2", + "description" => "foo", + "duration" => 45, + "content" => [anno_page] + })} + it 'validates' do + expect{canvas_for_audio.validate}.not_to raise_error + end + it 'duration' do + expect(canvas_for_audio.duration).to eq 45 + end + it 'description' do + expect(canvas_for_audio.description).to eq 'foo' + end + end + end + describe '3d object' do let(:canvas_3d_object) { described_class.new({ "id" => "http://tomcrane.github.io/scratch/manifests/3/canvas/3d", @@ -178,89 +247,25 @@ def initialize(hsh={}) end describe 'video object' do - let(:canvas_for_video) { described_class.new({ - "id" => "http://tomcrane.github.io/scratch/manifests/3/canvas/1", - "label" => "Associate multiple Video representations as Choice", - "height" => 1000, - "width" => 1000, - "duration" => 100, - "content" => [anno_page] - }) } - it 'validates' do - expect{canvas_for_video.validate}.not_to raise_error - end - it 'height, width, duration' do - expect(canvas_for_video.height).to eq 1000 - expect(canvas_for_video.width).to eq 1000 - expect(canvas_for_video.duration).to eq 100 - end - end - - describe 'audio object' do - let(:canvas_for_audio) { described_class.new({ - "id" => "http://tomcrane.github.io/scratch/manifests/3/canvas/2", - "label" => "Track 2", - "description" => "foo", - "duration" => 45, - "content" => [anno_page] - })} - it 'validates' do - expect{canvas_for_audio.validate}.not_to raise_error - end - it 'duration' do - expect(canvas_for_audio.duration).to eq 45 - end - it 'description' do - expect(canvas_for_audio.description).to eq 'foo' + describe 'with extent info' do + let(:canvas_for_video) { described_class.new({ + "id" => "http://tomcrane.github.io/scratch/manifests/3/canvas/1", + "label" => "Associate multiple Video representations as Choice", + "height" => 1000, + "width" => 1000, + "duration" => 100, + "content" => [anno_page] + }) } + it 'validates' do + expect{canvas_for_video.validate}.not_to raise_error + end + it 'height, width, duration' do + expect(canvas_for_video.height).to eq 1000 + expect(canvas_for_video.width).to eq 1000 + expect(canvas_for_video.duration).to eq 100 + end end end - - # TODO: still need Stanford examples - - # file - # Audio - # video - # 3d object - # document - # citation - # image - # book - - stanford_1 = { - "type" => "Canvas", - "id" => "https://purl.stanford.edu/bc592pz8308/iiif3/canvas/0001", - "label" => "image", - "height" => 7579, - "width" => 10108, - "content" => [ - { - "type" => "AnnotationPage", - "id" => "https://purl.stanford.edu/bc592pz8308/iiif3/annotation_page/0001", - "items" => [ - { - "type" => "Annotation", - "motivation" => "painting", - "id" => "https://purl.stanford.edu/bc592pz8308/iiif3/annotation/0001", - "body" => { - "type" => "Image", - "id" => "https://stacks.stanford.edu/image/iiif/bc592pz8308%2Fbc592pz8308_05_0001/full/full/0/default.jpg", - "format" => "image/jpeg", - "height" => 7579, - "width" => 10108, - "service" => { - "@context" => "http://iiif.io/api/image/2/context.json", - "@id" => "https://stacks.stanford.edu/image/iiif/bc592pz8308%2Fbc592pz8308_05_0001", - "id" => "https://stacks.stanford.edu/image/iiif/bc592pz8308%2Fbc592pz8308_05_0001", - "profile" => "http://iiif.io/api/image/2/level1.json" - } - }, - "target" => "https://purl.stanford.edu/bc592pz8308/iiif3/canvas/0001" - } - ] - } - ] - } - end end From ef2adb82b9251bd2d0932b3fbd2240c96d94b471 Mon Sep 17 00:00:00 2001 From: Naomi Dushay Date: Wed, 19 Jul 2017 00:14:30 -0700 Subject: [PATCH 4/4] v3 canvas: relax requirement for extent info --- lib/iiif/v3/presentation/canvas.rb | 14 +++++++++----- spec/unit/iiif/v3/presentation/canvas_spec.rb | 6 +++++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/iiif/v3/presentation/canvas.rb b/lib/iiif/v3/presentation/canvas.rb index 7ab6f83..f2d2f1b 100644 --- a/lib/iiif/v3/presentation/canvas.rb +++ b/lib/iiif/v3/presentation/canvas.rb @@ -51,14 +51,18 @@ def validate # It may have width, height and duration."" height = self['height'] width = self['width'] - extent_err_msg = "#{self.class} must have (a height and a width) and/or a duration" if (!!height ^ !!width) # this is an exclusive or: forces height and width to boolean + extent_err_msg = "#{self.class} requires both height and width or neither" raise IIIF::V3::Presentation::IllegalValueError, extent_err_msg end - duration = self['duration'] - unless (height && width) || duration - raise IIIF::V3::Presentation::IllegalValueError, extent_err_msg - end + # NOTE: relaxing requirement for (exactly one width and one height, and/or exactly one duration) + # as Stanford has objects (such as txt files) for which this makes no sense + # (see sul-dlss/purl/issues/169) + # duration = self['duration'] + # unless (height && width) || duration + # extent_err_msg = "#{self.class} must have (a height and a width) and/or a duration" + # raise IIIF::V3::Presentation::IllegalValueError, extent_err_msg + # end # TODO: Content must not be associated with space or time outside of the Canvas’s dimensions, # such as at coordinates below 0,0, greater than the height or width, before 0 seconds, or after the duration. diff --git a/spec/unit/iiif/v3/presentation/canvas_spec.rb b/spec/unit/iiif/v3/presentation/canvas_spec.rb index c657ac8..7a0b066 100644 --- a/spec/unit/iiif/v3/presentation/canvas_spec.rb +++ b/spec/unit/iiif/v3/presentation/canvas_spec.rb @@ -75,7 +75,9 @@ def initialize(hsh={}) expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_id_err_msg) end - let(:exp_extent_err_msg) { "#{described_class} must have (a height and a width) and/or a duration" } + # let(:exp_extent_err_msg) { "#{described_class} must have (a height and a width) and/or a duration" } + # (see sul-dlss/purl/issues/169) + let(:exp_extent_err_msg) { "#{described_class} requires both height and width or neither" } it 'raises an IllegalValueError if height is a string' do subject['height'] = 'foo' expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_extent_err_msg) @@ -90,6 +92,8 @@ def initialize(hsh={}) expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_extent_err_msg) end it 'raises an IllegalValueError if no width, height or duration' do + # (see sul-dlss/purl/issues/169) + skip('while this is in the current v3 spec, it does not make sense for some content (e.g. txt files)') expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_extent_err_msg) end it 'allows width, height and duration' do