diff --git a/lib/ld4l/open_annotation_rdf/annotation.rb b/lib/ld4l/open_annotation_rdf/annotation.rb index 9477b47..ca0dbda 100644 --- a/lib/ld4l/open_annotation_rdf/annotation.rb +++ b/lib/ld4l/open_annotation_rdf/annotation.rb @@ -23,7 +23,9 @@ def self.resume(uri_or_str) # get motivatedBy m = a.get_values(:motivatedBy) - return a unless m.kind_of?(Array) && m.size > 0 && m.first.kind_of?(RDF::Vocabulary::Term) + # TODO: Should m's class be validated? I've seen it be RDF::Vocabulary::Term and RDF::URI. For now, removing the validation. + return a unless m.kind_of?(Array) && m.size > 0 + # return a unless m.kind_of?(Array) && m.size > 0 && (m.first.kind_of?(RDF::Vocabulary::Term) || m.first.kind_of?(RDF::URI) # motivatedBy is set m_uri = m.first diff --git a/lib/ld4l/open_annotation_rdf/semantic_tag_annotation.rb b/lib/ld4l/open_annotation_rdf/semantic_tag_annotation.rb index 0fb012b..7d3b9be 100644 --- a/lib/ld4l/open_annotation_rdf/semantic_tag_annotation.rb +++ b/lib/ld4l/open_annotation_rdf/semantic_tag_annotation.rb @@ -4,10 +4,14 @@ class SemanticTagAnnotation < LD4L::OpenAnnotationRDF::Annotation @localname_prefix = "sta" + property :hasBody, :predicate => RDFVocabularies::OA.hasBody, :class_name => LD4L::OpenAnnotationRDF::SemanticTagBody + + # USAGE: Use setTerm to set the hasBody property to be the URI of the controlled vocabulary term that # is the annotation. # TODO: Should a semantic tag be destroyed when the last annotation referencing the term is destroyed? + # TODO: What if other triples have been attached beyond the type? ## # Set the hasBody property to the URI of the controlled vocabulary term that is the annotation and diff --git a/lib/ld4l/open_annotation_rdf/version.rb b/lib/ld4l/open_annotation_rdf/version.rb index a9e4f3d..8a38d7f 100644 --- a/lib/ld4l/open_annotation_rdf/version.rb +++ b/lib/ld4l/open_annotation_rdf/version.rb @@ -1,5 +1,5 @@ module LD4L module OpenAnnotationRDF - VERSION = "0.0.9" + VERSION = "0.0.10" end end diff --git a/spec/ld4l/open_annotation_rdf/annotation_spec.rb b/spec/ld4l/open_annotation_rdf/annotation_spec.rb index e8c50fc..57bb7eb 100644 --- a/spec/ld4l/open_annotation_rdf/annotation_spec.rb +++ b/spec/ld4l/open_annotation_rdf/annotation_spec.rb @@ -173,79 +173,285 @@ LD4L::FoafRDF::Person.new('p4') end - it "should resume an instance of CommentAnnotation" do - a_time = Time::now.strftime("%Y-%m-%dT%H:%M:%S.%L%z") - ca = LD4L::OpenAnnotationRDF::CommentAnnotation.new('ca_1') - ca.hasTarget = RDF::URI("http://example.org/bibref/br3") - ca.setComment("This is a comment.") - ca.annotatedBy = a_person - ca.annotatedAt = a_time - ca.persist! - expect(ca).to be_persisted - uri = ca.rdf_subject - - a = LD4L::OpenAnnotationRDF::Annotation.resume(uri) - expect(a).to be_a_kind_of(LD4L::OpenAnnotationRDF::CommentAnnotation) - expect(a.hasTarget.first.rdf_subject.to_s).to eq "http://example.org/bibref/br3" - expect(a.annotatedBy.first).to eq a_person - expect(a.annotatedAt.first).to eq a_time - expect(a.motivatedBy.first).to eq RDFVocabularies::OA.commenting - - b = a.getBody - expect(b).to be_a_kind_of(LD4L::OpenAnnotationRDF::CommentBody) - expect(b.type).to include RDFVocabularies::DCTYPES.Text - expect(b.type).to include RDFVocabularies::CNT.ContentAsText - expect(b.content).to eq ["This is a comment."] - expect(b.format.first).to eq "text/plain" - end - - it "should resume an instance of TagAnnotation" do - a_time = Time::now.strftime("%Y-%m-%dT%H:%M:%S.%L%z") - ta = LD4L::OpenAnnotationRDF::TagAnnotation.new('ta_2') - ta.hasTarget = RDF::URI("http://example.org/bibref/br3") - ta.setTag("good") - ta.annotatedBy = a_person - ta.annotatedAt = a_time - ta.persist! - expect(ta).to be_persisted - uri = ta.rdf_subject - - a = LD4L::OpenAnnotationRDF::Annotation.resume(uri) - expect(a).to be_a_kind_of(LD4L::OpenAnnotationRDF::TagAnnotation) - expect(a.hasTarget.first.rdf_subject.to_s).to eq "http://example.org/bibref/br3" - expect(a.annotatedBy.first).to eq a_person - expect(a.annotatedAt.first).to eq a_time - expect(a.motivatedBy.first).to eq RDFVocabularies::OA.tagging - - b = a.getBody - expect(b).to be_a_kind_of(LD4L::OpenAnnotationRDF::TagBody) - expect(b.type).to include RDFVocabularies::OA.Tag - expect(b.type).to include RDFVocabularies::CNT.ContentAsText - expect(b.tag).to eq ["good"] - end - - it "should resume an instance of SemanticTagAnnotation" do - a_time = Time::now.strftime("%Y-%m-%dT%H:%M:%S.%L%z") - sta = LD4L::OpenAnnotationRDF::SemanticTagAnnotation.new('sta_3') - sta.hasTarget = RDF::URI("http://example.org/bibref/br3") - sta.setTerm('http://example.org/term/3') - sta.annotatedBy = a_person - sta.annotatedAt = a_time - sta.persist! - expect(sta).to be_persisted - uri = sta.rdf_subject - - a = LD4L::OpenAnnotationRDF::Annotation.resume(uri) - expect(a).to be_a_kind_of(LD4L::OpenAnnotationRDF::SemanticTagAnnotation) - expect(a.hasTarget.first.rdf_subject.to_s).to eq "http://example.org/bibref/br3" - expect(a.annotatedBy.first).to eq a_person - expect(a.annotatedAt.first).to eq a_time - expect(a.motivatedBy.first).to eq RDFVocabularies::OA.tagging - - b = a.getBody - expect(b).to be_a_kind_of(LD4L::OpenAnnotationRDF::SemanticTagBody) - expect(b.type).to include RDFVocabularies::OA.SemanticTag - expect(b.rdf_subject.to_s).to eq('http://example.org/term/3') + context "when annotation body is persisted" do + it "should resume an instance of CommentAnnotation and its body" do + a_time = Time::now.strftime("%Y-%m-%dT%H:%M:%S.%L%z") + ca = LD4L::OpenAnnotationRDF::CommentAnnotation.new('ca_1') + ca.hasTarget = RDF::URI("http://example.org/bibref/br3") + ca.setComment("This is a comment.") + ca.annotatedBy = a_person + ca.annotatedAt = a_time + ca.persist! + expect(ca).to be_persisted + uri = ca.rdf_subject + + a = LD4L::OpenAnnotationRDF::Annotation.resume(uri) + expect(a).to be_a_kind_of(LD4L::OpenAnnotationRDF::CommentAnnotation) + expect(a.hasTarget.first.rdf_subject.to_s).to eq "http://example.org/bibref/br3" + expect(a.annotatedBy.first).to eq a_person + expect(a.annotatedAt.first).to eq a_time + expect(a.motivatedBy.first).to eq RDFVocabularies::OA.commenting + + b = a.getBody + expect(b).to be_a_kind_of(LD4L::OpenAnnotationRDF::CommentBody) + expect(b.type).to include RDFVocabularies::DCTYPES.Text + expect(b.type).to include RDFVocabularies::CNT.ContentAsText + expect(b.content).to eq ["This is a comment."] + expect(b.format.first).to eq "text/plain" + end + + it "should resume an instance of TagAnnotation and its body" do + a_time = Time::now.strftime("%Y-%m-%dT%H:%M:%S.%L%z") + ta = LD4L::OpenAnnotationRDF::TagAnnotation.new('ta_2') + ta.hasTarget = RDF::URI("http://example.org/bibref/br3") + ta.setTag("good") + ta.annotatedBy = a_person + ta.annotatedAt = a_time + ta.persist! + expect(ta).to be_persisted + uri = ta.rdf_subject + + a = LD4L::OpenAnnotationRDF::Annotation.resume(uri) + expect(a).to be_a_kind_of(LD4L::OpenAnnotationRDF::TagAnnotation) + expect(a.hasTarget.first.rdf_subject.to_s).to eq "http://example.org/bibref/br3" + expect(a.annotatedBy.first).to eq a_person + expect(a.annotatedAt.first).to eq a_time + expect(a.motivatedBy.first).to eq RDFVocabularies::OA.tagging + + b = a.getBody + expect(b).to be_a_kind_of(LD4L::OpenAnnotationRDF::TagBody) + expect(b.type).to include RDFVocabularies::OA.Tag + expect(b.type).to include RDFVocabularies::CNT.ContentAsText + expect(b.tag).to eq ["good"] + end + + it "should resume an instance of SemanticTagAnnotation and its body" do + a_time = Time::now.strftime("%Y-%m-%dT%H:%M:%S.%L%z") + sta = LD4L::OpenAnnotationRDF::SemanticTagAnnotation.new('sta_3') + sta.hasTarget = RDF::URI("http://example.org/bibref/br3") + sta.setTerm('http://example.org/term/3') + sta.annotatedBy = a_person + sta.annotatedAt = a_time + sta.persist! + expect(sta).to be_persisted + uri = sta.rdf_subject + + a = LD4L::OpenAnnotationRDF::Annotation.resume(uri) + expect(a).to be_a_kind_of(LD4L::OpenAnnotationRDF::SemanticTagAnnotation) + expect(a.hasTarget.first.rdf_subject.to_s).to eq "http://example.org/bibref/br3" + expect(a.annotatedBy.first).to eq a_person + expect(a.annotatedAt.first).to eq a_time + expect(a.motivatedBy.first).to eq RDFVocabularies::OA.tagging + + b = a.getBody + expect(b).to be_a_kind_of(LD4L::OpenAnnotationRDF::SemanticTagBody) + expect(b.type).to include RDFVocabularies::OA.SemanticTag + expect(b.rdf_subject.to_s).to eq('http://example.org/term/3') + end + end + + context "when annotation body is a blank node" do + it "should resume an instance of CommentAnnotation and a blank node body" do + a_time = Time::now.strftime("%Y-%m-%dT%H:%M:%S.%L%z") + ca = LD4L::OpenAnnotationRDF::CommentAnnotation.new('ca_1') + ca.hasTarget = RDF::URI("http://example.org/bibref/br3") + + cb = LD4L::OpenAnnotationRDF::CommentBody.new # create body as blank node + cb.content = "BLANK NODE COMMENT" + cb.format = "text/plain" + cb.persist! + expect(cb).to be_persisted + + ca.hasBody = cb + ca.annotatedBy = a_person + ca.annotatedAt = a_time + ca.persist! + expect(ca).to be_persisted + uri = ca.rdf_subject + + a = LD4L::OpenAnnotationRDF::Annotation.resume(uri) + expect(a).to be_a_kind_of(LD4L::OpenAnnotationRDF::CommentAnnotation) + expect(a.hasTarget.first.rdf_subject.to_s).to eq "http://example.org/bibref/br3" + expect(a.annotatedBy.first).to eq a_person + expect(a.annotatedAt.first).to eq a_time + expect(a.motivatedBy.first).to eq RDFVocabularies::OA.commenting + + b = a.getBody + expect(b).to be_a_kind_of(LD4L::OpenAnnotationRDF::CommentBody) + expect(b.type).to include RDFVocabularies::DCTYPES.Text + expect(b.type).to include RDFVocabularies::CNT.ContentAsText + expect(b.content).to eq ["BLANK NODE COMMENT"] + expect(b.format.first).to eq "text/plain" + end + + it "should resume an instance of TagAnnotation and a blank node body" do + a_time = Time::now.strftime("%Y-%m-%dT%H:%M:%S.%L%z") + ta = LD4L::OpenAnnotationRDF::TagAnnotation.new('ta_4') + ta.hasTarget = RDF::URI("http://example.org/bibref/br3") + ta.annotatedBy = a_person + ta.annotatedAt = a_time + + tb = LD4L::OpenAnnotationRDF::TagBody.new # create body as blank node + tb.tag = "BLANK_NODE_TAG" + tb.persist! + expect(tb).to be_persisted + + ta.hasBody = tb + ta.persist! + expect(ta).to be_persisted + uri = ta.rdf_subject + + a = LD4L::OpenAnnotationRDF::Annotation.resume(uri) + expect(a).to be_a_kind_of(LD4L::OpenAnnotationRDF::TagAnnotation) + expect(a.hasTarget.first.rdf_subject.to_s).to eq "http://example.org/bibref/br3" + expect(a.annotatedBy.first).to eq a_person + expect(a.annotatedAt.first).to eq a_time + expect(a.motivatedBy.first).to eq RDFVocabularies::OA.tagging + + b = a.getBody + expect(b).to be_a_kind_of(LD4L::OpenAnnotationRDF::TagBody) + expect(b.type).to include RDFVocabularies::OA.Tag + expect(b.type).to include RDFVocabularies::CNT.ContentAsText + expect(b.tag).to eq ["BLANK_NODE_TAG"] + end + + it "should resume an instance of SemanticTagAnnotation and body object is missing" do + + + # SKIPPED FOR REWRITE FOR BLANK NODE + # pending ("rewrite for blank node") + + + a_time = Time::now.strftime("%Y-%m-%dT%H:%M:%S.%L%z") + sta = LD4L::OpenAnnotationRDF::SemanticTagAnnotation.new('sta_3') + sta.hasTarget = RDF::URI("http://example.org/bibref/br3") + sta.hasBody = RDF::URI('http://example.org/term/3') # don't create a cooresponding body object + sta.annotatedBy = a_person + sta.annotatedAt = a_time + sta.persist! + expect(sta).to be_persisted + uri = sta.rdf_subject + + a = LD4L::OpenAnnotationRDF::Annotation.resume(uri) + expect(a).to be_a_kind_of(LD4L::OpenAnnotationRDF::SemanticTagAnnotation) + expect(a.hasTarget.first.rdf_subject.to_s).to eq "http://example.org/bibref/br3" + expect(a.annotatedBy.first).to eq a_person + expect(a.annotatedAt.first).to eq a_time + expect(a.motivatedBy.first).to eq RDFVocabularies::OA.tagging + + b = a.getBody + expect(b).to be_a_kind_of(LD4L::OpenAnnotationRDF::SemanticTagBody) + expect(b.type).to include RDFVocabularies::OA.SemanticTag + expect(b.rdf_subject.to_s).to eq('http://example.org/term/3') + end + end + + context "when loading from graph" do + context "and has blank node for body" do + context "and triples represent a comment annotation" do + before(:each) do + @anno_url = "http://my_oa_store/COMMENT_ANNO" + @comment_value = "This is a comment." + ttl = "<#{@anno_url}> a ; + [ + a , + ; + \"#{@comment_value}\" ; + \"text/plain\" + ]; + ; + ." + + anno_graph = RDF::Graph.new.from_ttl ttl + r = ActiveTriples::Repositories.repositories[:default] + r << anno_graph + anno_uri = RDF::URI.new(@anno_url) + @comment_anno = LD4L::OpenAnnotationRDF::Annotation.resume(anno_uri) + end + it "populates LD4L::OpenAnnotationRDF::TagAnnotation properly" do + expect(@comment_anno.rdf_subject.to_s).to eq @anno_url + expect(@comment_anno).to be_a LD4L::OpenAnnotationRDF::CommentAnnotation + expect(@comment_anno.type).to include(RDFVocabularies::OA.Annotation) + expect(@comment_anno.motivatedBy).to include(RDFVocabularies::OA.commenting) + expect(@comment_anno.hasTarget.first.rdf_subject).to eq RDF::URI.new("http://searchworks.stanford.edu/view/666") + end + it "populates Tag bodies properly" do + body = @comment_anno.hasBody.first + expect(body).to be_a LD4L::OpenAnnotationRDF::CommentBody + expect(body.content.first).to eq @comment_value + expect(body.type).to include(RDFVocabularies::CNT.ContentAsText) + expect(body.type).to include(RDFVocabularies::DCTYPES.Text) + end + end + + context "and triples represent a tag annotation" do + before(:each) do + @anno_url = "http://my_oa_store/TAG_ANNO" + @tag_value = "blue" + ttl = "<#{@anno_url}> a ; + [ + a , + , + ; + \"#{@tag_value}\" ; + \"text/plain\" + ]; + ; + ." + + anno_graph = RDF::Graph.new.from_ttl ttl + r = ActiveTriples::Repositories.repositories[:default] + r << anno_graph + anno_uri = RDF::URI.new(@anno_url) + @tag_anno = LD4L::OpenAnnotationRDF::Annotation.resume(anno_uri) + end + it "populates LD4L::OpenAnnotationRDF::TagAnnotation properly" do + expect(@tag_anno.rdf_subject.to_s).to eq @anno_url + expect(@tag_anno).to be_a LD4L::OpenAnnotationRDF::TagAnnotation + expect(@tag_anno.type).to include(RDFVocabularies::OA.Annotation) + expect(@tag_anno.motivatedBy).to include(RDFVocabularies::OA.tagging) + expect(@tag_anno.hasTarget.first.rdf_subject).to eq RDF::URI.new("http://searchworks.stanford.edu/view/666") + end + it "populates Tag bodies properly" do + body = @tag_anno.hasBody.first + expect(body).to be_a LD4L::OpenAnnotationRDF::TagBody + expect(body.tag.first).to eq @tag_value + expect(body.type).to include(RDFVocabularies::OA.Tag) + expect(body.type).to include(RDFVocabularies::CNT.ContentAsText) + expect(body.type).to include(RDFVocabularies::DCTYPES.Text) + end + end + + context "and triples represent a semantic tag annotation" do + before(:each) do + @anno_url = "http://my_oa_store/SEMANTIC_TAG_ANNO" + @term_url = "http://example.org/terms/foo" + ttl = "<#{@anno_url}> a ; + <#{@term_url}>; + ; + ." + + anno_graph = RDF::Graph.new.from_ttl ttl + r = ActiveTriples::Repositories.repositories[:default] + r << anno_graph + anno_uri = RDF::URI.new(@anno_url) + @semantic_tag_anno = LD4L::OpenAnnotationRDF::Annotation.resume(anno_uri) + end + it "populates LD4L::OpenAnnotationRDF::TagAnnotation properly" do + expect(@semantic_tag_anno.rdf_subject.to_s).to eq @anno_url + expect(@semantic_tag_anno).to be_a LD4L::OpenAnnotationRDF::SemanticTagAnnotation + expect(@semantic_tag_anno.type).to include(RDFVocabularies::OA.Annotation) + expect(@semantic_tag_anno.motivatedBy).to include(RDFVocabularies::OA.tagging) + expect(@semantic_tag_anno.hasTarget.first.rdf_subject).to eq RDF::URI.new("http://searchworks.stanford.edu/view/666") + end + it "populates Tag bodies properly" do + body = @semantic_tag_anno.hasBody.first + expect(body.rdf_subject.to_s).to eq @term_url + expect(body).to be_a LD4L::OpenAnnotationRDF::SemanticTagBody + expect(body.type).to include(RDFVocabularies::OA.SemanticTag) + end + end + end end end