ld4l-open_annotation_rdf 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +11 -0
  6. data/LICENSE.txt +14 -0
  7. data/README.md +231 -0
  8. data/Rakefile +2 -0
  9. data/ld4l-open_annotation_rdf.gemspec +46 -0
  10. data/lib/ld4l/open_annotation_rdf.rb +66 -0
  11. data/lib/ld4l/open_annotation_rdf/annotation.rb +82 -0
  12. data/lib/ld4l/open_annotation_rdf/comment_annotation.rb +47 -0
  13. data/lib/ld4l/open_annotation_rdf/comment_body.rb +24 -0
  14. data/lib/ld4l/open_annotation_rdf/configuration.rb +127 -0
  15. data/lib/ld4l/open_annotation_rdf/semantic_tag_annotation.rb +66 -0
  16. data/lib/ld4l/open_annotation_rdf/semantic_tag_body.rb +70 -0
  17. data/lib/ld4l/open_annotation_rdf/tag_annotation.rb +98 -0
  18. data/lib/ld4l/open_annotation_rdf/tag_body.rb +83 -0
  19. data/lib/ld4l/open_annotation_rdf/version.rb +5 -0
  20. data/lib/ld4l/open_annotation_rdf/vocab/cnt.rb +6 -0
  21. data/lib/ld4l/open_annotation_rdf/vocab/dctypes.rb +5 -0
  22. data/lib/ld4l/open_annotation_rdf/vocab/oa.rb +23 -0
  23. data/spec/ld4l/open_annotation_rdf/annotation_spec.rb +603 -0
  24. data/spec/ld4l/open_annotation_rdf/comment_annotation_spec.rb +559 -0
  25. data/spec/ld4l/open_annotation_rdf/comment_body_spec.rb +371 -0
  26. data/spec/ld4l/open_annotation_rdf/configuration_spec.rb +194 -0
  27. data/spec/ld4l/open_annotation_rdf/semantic_tag_annotation_spec.rb +619 -0
  28. data/spec/ld4l/open_annotation_rdf/semantic_tag_body_spec.rb +412 -0
  29. data/spec/ld4l/open_annotation_rdf/tag_annotation_spec.rb +672 -0
  30. data/spec/ld4l/open_annotation_rdf/tag_body_spec.rb +430 -0
  31. data/spec/ld4l/open_annotation_rdf_spec.rb +57 -0
  32. data/spec/spec_helper.rb +21 -0
  33. metadata +201 -0
@@ -0,0 +1,47 @@
1
+ module LD4L
2
+ module OpenAnnotationRDF
3
+ class CommentAnnotation < LD4L::OpenAnnotationRDF::Annotation
4
+
5
+ @localname_prefix="ca"
6
+
7
+ property :hasBody, :predicate => RDFVocabularies::OA.hasBody, :class_name => LD4L::OpenAnnotationRDF::CommentBody
8
+
9
+
10
+ ##
11
+ # Create a comment annotation body and set the hasBody property to it.
12
+ #
13
+ # @param [String]
14
+ #
15
+ # @return instance of SemanticTagBody
16
+ def setComment(comment)
17
+ @body = LD4L::OpenAnnotationRDF::CommentBody.new(
18
+ ActiveTriples::LocalName::Minter.generate_local_name(
19
+ LD4L::OpenAnnotationRDF::CommentBody, 10, @localname_prefix,
20
+ LD4L::OpenAnnotationRDF.configuration.localname_minter ))
21
+ @body.content = comment
22
+ @body.format = "text/plain"
23
+ set_value(:hasBody, @body)
24
+ @body
25
+ end
26
+
27
+ ##
28
+ # Special processing for new and resumed CommentAnnotations
29
+ #
30
+ def initialize(*args)
31
+ super(*args)
32
+
33
+ # set motivatedBy
34
+ m = get_values(:motivatedBy)
35
+ set_value(:motivatedBy, RDFVocabularies::OA.commenting) unless m.kind_of?(Array) && m.size > 0
36
+
37
+ # resume CommentBody if it exists
38
+ comment_uri = get_values(:hasBody).first
39
+ if( comment_uri )
40
+ comment_uri = comment_uri.rdf_subject if comment_uri.kind_of?(ActiveTriples::Resource)
41
+ @body = LD4L::OpenAnnotationRDF::CommentBody.new(comment_uri)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,24 @@
1
+ module LD4L
2
+ module OpenAnnotationRDF
3
+ class CommentBody < ActiveTriples::Resource
4
+
5
+ class << self; attr_reader :localname_prefix end
6
+ @localname_prefix="cb"
7
+
8
+ configure :type => RDFVocabularies::CNT.ContentAsText,
9
+ :base_uri => LD4L::OpenAnnotationRDF.configuration.base_uri,
10
+ :repository => :default
11
+
12
+ property :content, :predicate => RDFVocabularies::CNT.chars # :type => XSD.string
13
+ property :format, :predicate => RDF::DC.format # :type => XSD.string
14
+
15
+ def initialize(*args)
16
+ super(*args)
17
+
18
+ t = get_values(:type)
19
+ t << RDFVocabularies::DCTYPES.Text
20
+ set_value(:type,t)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,127 @@
1
+ module LD4L
2
+ module OpenAnnotationRDF
3
+
4
+ # Used by LD4L::OpenAnnotationRDF class to configure...
5
+ # * base_uri
6
+ # * local_minter
7
+ # * unique_tags
8
+ #
9
+ # @example Configure all configurable properties
10
+ # LD4L::OpenAnnotationRDF.configure do |config|
11
+ # config.base_uri = "http://www.example.org/annotations/"
12
+ # config.localname_minter = lambda { |prefix=""| prefix+SecureRandom.uuid }
13
+ # config.unique_tags = true
14
+ # end
15
+ #
16
+ # @example Usage of base uri and local name
17
+ # # uri = base_uri + localname"
18
+ # annotation = LD4L::OpenAnnotationRDF::CommentAnnotation.new(
19
+ # ActiveTriples::LocalName::Minter.generate_local_name(
20
+ # LD4L::OpenAnnotationRDF::CommentBody, 10, 'a',
21
+ # LD4L::OpenAnnotationRDF.configuration.localname_minter ))
22
+ # annotation.rdf_subject
23
+ # # => "http://www.example.org/annotations/a9f85752c-9c2c-4a65-997a-68482895a656"
24
+ #
25
+ # @note Use LD4L::OpenAnnotationRDF.configure to call the methods in this class. See 'Configure all configurable
26
+ # properties' example for most common approach to configuration.
27
+ class Configuration
28
+
29
+ ##
30
+ # @overload base_uri
31
+ # Get the base_uri to be used when generating rdf_subjects for new objects. See example configuration and usage in class documentation examples.
32
+ # @return the configured base_uri
33
+ # @overload base_uri=(new_base_uri)
34
+ # Set the base_uri to be used when generating rdf_subjects for new objects. See example configuration and usage in class documentation examples.
35
+ # @param [String] new value for the base_uri
36
+ # @note base_uri will only take effect when a model class is first created. Once the model class is created, the base_uri is bound to the class.
37
+ attr_reader :base_uri
38
+
39
+ ##
40
+ # @overload localname_minter
41
+ # Get the localname_minter to be used when generating rdf_subjects for new objects. See example configuration and usage in class documentation examples.
42
+ # @return the configured localname_minter
43
+ # @overload localname_minter=(new_localname_minter)
44
+ # Set the localname_minter to be used when generating rdf_subjects for new objects. See example configuration and usage in class documentation examples.
45
+ # @param [String] new value for the localname_minter
46
+ attr_reader :localname_minter
47
+
48
+ ##
49
+ # @overload unique_tags
50
+ # Get whether the GEM should enforce uniqueness of user generated tags when using
51
+ # the TagAnnotation::setTag method. See example configuration and usage in class documentation examples.
52
+ # @return the configured unique_tags
53
+ # @overload unique_tags=(new_unique_tags)
54
+ # Set whether the GEM should enforce uniqueness of user generated tags when using
55
+ # the TagAnnotation::setTag method.
56
+ # @param [Boolean] new value for the unique_tags
57
+ #
58
+ # true - enforce uniqueness (default)
59
+ # - annotations share TagBodys
60
+ # - setTag method looks for an existing TagBody with the tag value and reuses the existing TagBody
61
+ # - setTag to change a tag's value will create/reuse a different TagBody for the new tag value
62
+ # false - do not enforce uniqueness
63
+ # - annotation owns its TagBody
64
+ # - setTag first time call creates a TagBody with the tag value
65
+ # - setTag to change the tag's value will modify the tag value in this annotation's TagBody
66
+ attr_reader :unique_tags
67
+
68
+ def self.default_base_uri
69
+ @default_base_uri = "http://localhost/".freeze
70
+ end
71
+ private_class_method :default_base_uri
72
+
73
+ def self.default_localname_minter
74
+ # by setting to nil, it will use the default minter in the minter gem
75
+ @default_localname_minter = nil
76
+ end
77
+ private_class_method :default_localname_minter
78
+
79
+ def self.default_unique_tags
80
+ @default_unique_tags = true
81
+ end
82
+ private_class_method :default_unique_tags
83
+
84
+ def initialize
85
+ @base_uri = self.class.send(:default_base_uri)
86
+ @localname_minter = self.class.send(:default_localname_minter)
87
+ @unique_tags = self.class.send(:default_unique_tags)
88
+ end
89
+
90
+ def base_uri=(new_base_uri)
91
+ @base_uri = new_base_uri
92
+ end
93
+
94
+ ##
95
+ # Reset the base_uri to be used when generating rdf_subject for new objects back to the default configuration.
96
+ #
97
+ def reset_base_uri
98
+ @base_uri = self.class.send(:default_base_uri)
99
+ end
100
+
101
+ def localname_minter=(new_minter)
102
+ @localname_minter = new_minter
103
+ end
104
+
105
+ ##
106
+ # Reset the minter to be used to generate the local name portion of the rdf_subject for new objects to the
107
+ # default minter.
108
+ #
109
+ def reset_localname_minter
110
+ @localname_minter = self.class.send(:default_localname_minter)
111
+ end
112
+
113
+ def unique_tags=(new_unique_indicator)
114
+ @unique_tags = new_unique_indicator
115
+ end
116
+
117
+ ##
118
+ # Reset whether the GEM should enforce uniqueness of user generated tags, when using
119
+ # the TagAnnotation::setTag method, to the default configuration (true).
120
+ #
121
+ # @see unique_tags=
122
+ def reset_unique_tags
123
+ @unique_tags = self.class.send(:default_unique_tags)
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,66 @@
1
+ module LD4L
2
+ module OpenAnnotationRDF
3
+ class SemanticTagAnnotation < LD4L::OpenAnnotationRDF::Annotation
4
+
5
+ @localname_prefix = "sta"
6
+
7
+ # USAGE: Use setTerm to set the hasBody property to be the URI of the controlled vocabulary term that
8
+ # is the annotation.
9
+
10
+ # TODO: Should a semantic tag be destroyed when the last annotation referencing the term is destroyed?
11
+
12
+ ##
13
+ # Set the hasBody property to the URI of the controlled vocabulary term that is the annotation and
14
+ # create the semantic tag body instance identifying the term as a semantic tag annotation.
15
+ #
16
+ # @param [String] controlled vocabulary uri for the term
17
+ #
18
+ # @return instance of SemanticTagBody
19
+ def setTerm(term_uri)
20
+ raise ArgumentError, 'Argument must be a uri string or an instance of RDF::URI' unless
21
+ term_uri.kind_of?(String) && term_uri.size > 0 || term_uri.kind_of?(RDF::URI)
22
+
23
+ # return existing body if term is unchanged
24
+ old_term_uri = @body ? @body.rdf_subject.to_s : nil
25
+ term_uri = RDF::URI(term_uri) unless term_uri.kind_of?(RDF::URI)
26
+ return @body if old_term_uri && old_term_uri == term_uri.to_s
27
+
28
+ @body = LD4L::OpenAnnotationRDF::SemanticTagBody.new(term_uri)
29
+ set_value(:hasBody, @body)
30
+ @body
31
+ end
32
+
33
+ ##
34
+ # Special processing for new and resumed SemanticTagAnnotations
35
+ #
36
+ def initialize(*args)
37
+ super(*args)
38
+
39
+ # set motivatedBy
40
+ m = get_values(:motivatedBy)
41
+ set_value(:motivatedBy, RDFVocabularies::OA.tagging) unless m.kind_of?(Array) && m.size > 0
42
+
43
+ # resume SemanticTagBody if it exists
44
+ term_uri = get_values(:hasBody).first
45
+ if( term_uri )
46
+ term_uri = term_uri.rdf_subject if term_uri.kind_of?(ActiveTriples::Resource)
47
+ @body = LD4L::OpenAnnotationRDF::SemanticTagBody.new(term_uri)
48
+ end
49
+ end
50
+
51
+ def destroy
52
+ # TODO Determine behavior of destroy
53
+ # Behaviour Options
54
+ # * Always destroy SemanticTagAnnotation
55
+ # * Handling of SemanticTagBody
56
+ # ** If SemanticTagBody is used only by this SemanticTagAnnotation, destroy it.
57
+ # ** Otherwise, do not destroy it.
58
+ # TODO Write tests for this behaviour.
59
+ # TODO Write code here to enforce.
60
+ super
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+
@@ -0,0 +1,70 @@
1
+ module LD4L
2
+ module OpenAnnotationRDF
3
+ class SemanticTagBody < ActiveTriples::Resource
4
+
5
+ class << self; attr_reader :localname_prefix end
6
+ @localname_prefix="stb"
7
+
8
+ # USAGE: When creating a semantic tag body, use the URI of the controlled vocabulary term as the RDF Subject URI
9
+ # for an instance of this class.
10
+
11
+ configure :type => RDFVocabularies::OA.SemanticTag,
12
+ :base_uri => LD4L::OpenAnnotationRDF.configuration.base_uri,
13
+ :repository => :default
14
+
15
+ ##
16
+ # Get a list of annotations using a term.
17
+ #
18
+ # @param [String] controlled vocabulary uri for the term
19
+ #
20
+ # @return array of annotation URIs
21
+ #
22
+ # NOTE: This method returns only persisted annotations.
23
+ def self::annotations_using( term_uri )
24
+ raise ArgumentError, 'Argument must be a uri string or an instance of RDF::URI' unless
25
+ term_uri.kind_of?(String) && term_uri.size > 0 || term_uri.kind_of?(RDF::URI)
26
+
27
+ term_uri = RDF::URI(term_uri) unless term_uri.kind_of?(RDF::URI)
28
+
29
+ # find usage by Annotations
30
+ graph = ActiveTriples::Repositories.repositories[repository]
31
+ query = RDF::Query.new({
32
+ :annotation => {
33
+ RDF.type => RDFVocabularies::OA.Annotation,
34
+ RDFVocabularies::OA.hasBody => term_uri,
35
+ }
36
+ })
37
+ annotations = []
38
+ results = query.execute(graph)
39
+ results.each { |r| annotations << r.to_hash[:annotation] }
40
+ annotations
41
+ end
42
+
43
+
44
+ ##
45
+ # Destroy the SemanticTagBody only if the term is not used by another SemanticAnnotation
46
+ #
47
+ # @param [String] controlled vocabulary uri for the term
48
+ #
49
+ # @return true if destroyed; otherwise, false if used by other annotations
50
+ #
51
+ # NOTE: Use this method after changing the term AND persisting the annotation on which the term was changed.
52
+ def self::destroy_if_unused( term_uri )
53
+ raise ArgumentError, 'Argument must be a uri string or an instance of RDF::URI' unless
54
+ term_uri.kind_of?(String) && term_uri.size > 0 || term_uri.kind_of?(RDF::URI)
55
+
56
+ term_uri = RDF::URI(term_uri) unless term_uri.kind_of?(RDF::URI)
57
+
58
+ # find usage by Annotations
59
+ annotations = self::annotations_using( term_uri )
60
+ destroyed = false
61
+ if( annotations.empty? )
62
+ stb = LD4L::OpenAnnotationRDF::SemanticTagBody.new(term_uri)
63
+ destroyed = stb.destroy!
64
+ end
65
+ destroyed
66
+ end
67
+
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,98 @@
1
+ module LD4L
2
+ module OpenAnnotationRDF
3
+ class TagAnnotation < LD4L::OpenAnnotationRDF::Annotation
4
+
5
+ @localname_prefix="ta"
6
+
7
+ property :hasBody, :predicate => RDFVocabularies::OA.hasBody, :class_name => LD4L::OpenAnnotationRDF::TagBody
8
+
9
+ # TODO: Should a tag be destroyed when the last annotation referencing the tag is destroyed?
10
+
11
+ ##
12
+ # Set the hasBody property to the URI of the one and only TagBody holding the tag value. Create a new TagBody
13
+ # if one doesn't exist with this value.
14
+ #
15
+ # @param [String] tag value
16
+ #
17
+ # @return instance of TagBody
18
+ def setTag(tag)
19
+ raise ArgumentError, 'Argument must be a string with at least one character' unless tag.kind_of?(String) && tag.size > 0
20
+
21
+ # return existing body if tag value is unchanged
22
+ old_tag = @body ? @body.tag : nil
23
+ return @body if old_tag && old_tag.include?(tag)
24
+
25
+ if LD4L::OpenAnnotationRDF.configuration.unique_tags
26
+ # when unique_tags = true, try to find an existing TagBody with the tag value before creating a new TagBody
27
+ # TODO Determine behavior of setTag when unique_tags=true
28
+ # Behaviour Options:
29
+ # * Look for an existing TagBody with this value.
30
+ # ** If none found, create a new TagBody.
31
+ # ** If one found, set @body to this TagBody
32
+ # ** If multiple found, use the first one found
33
+ # ### the same one may not be the first one found each time the query executes
34
+ @body = LD4L::OpenAnnotationRDF.configuration.unique_tags ? LD4L::OpenAnnotationRDF::TagBody.fetch_by_tag_value(tag) : nil
35
+ if @body == nil
36
+ @body = LD4L::OpenAnnotationRDF::TagBody.new(
37
+ ActiveTriples::LocalName::Minter.generate_local_name(
38
+ LD4L::OpenAnnotationRDF::TagBody, 10, @localname_prefix,
39
+ LD4L::OpenAnnotationRDF.configuration.localname_minter ))
40
+ @body.tag = tag
41
+ end
42
+ else
43
+ # when unique_tags = false, ??? (see TODO)
44
+ # TODO Determine behavior of setTag when unique_tags=false
45
+ # Behaviour Options:
46
+ # * If this TagAnnotation does not have a TagBody (@body) set, then create a new TagBody.
47
+ # * If this TagBody is used only by this TagAnnotation, then change the value in the TagBody.
48
+ # * If this TagBody is used by multiple TagAnnotations,
49
+ # ** EITHER change the value in the TagBody which changes it for all the TagAnnotations.
50
+ # ### Likely an undesirable side effect having the value change for all TagAnnotations
51
+ # ** OR create a new TagBody and update @body to that TagBody
52
+ # OR
53
+ # * [CURRENT] Always create a new TagBody each time setTag is called and update @body
54
+ # ### This last options has the potential for orphaned TagBodys that no TagAnnotation references.
55
+ # TODO Rethink the current behavior which is always to create a new TagBody potentially leaving around orphans.
56
+ @body = LD4L::OpenAnnotationRDF::TagBody.new(
57
+ ActiveTriples::LocalName::Minter.generate_local_name(
58
+ LD4L::OpenAnnotationRDF::TagBody, 10, @localname_prefix,
59
+ LD4L::OpenAnnotationRDF.configuration.localname_minter ))
60
+ @body.tag = tag
61
+ end
62
+ set_value(:hasBody, @body)
63
+ @body
64
+ end
65
+
66
+ ##
67
+ # Special processing for new and resumed TagAnnotations
68
+ #
69
+ def initialize(*args)
70
+ super(*args)
71
+
72
+ # set motivatedBy
73
+ m = get_values(:motivatedBy)
74
+ set_value(:motivatedBy, RDFVocabularies::OA.tagging) unless m.kind_of?(Array) && m.size > 0
75
+
76
+ # resume TagBody if it exists
77
+ tag_uri = get_values(:hasBody).first
78
+ if( tag_uri )
79
+ tag_uri = tag_uri.rdf_subject if tag_uri.kind_of?(ActiveTriples::Resource)
80
+ @body = LD4L::OpenAnnotationRDF::TagBody.new(tag_uri)
81
+ end
82
+ end
83
+
84
+ def destroy
85
+ # TODO Determine behavior of destroy
86
+ # Behaviour Options
87
+ # * Always destroy TagAnnotation
88
+ # * Handling of TagBody
89
+ # ** If TagBody is used only by this TagAnnotation, destroy it.
90
+ # ** Otherwise, do not destroy it.
91
+ # TODO Write tests for this behaviour.
92
+ # TODO Write code here to enforce.
93
+ super
94
+ end
95
+ end
96
+ end
97
+ end
98
+
@@ -0,0 +1,83 @@
1
+ module LD4L
2
+ module OpenAnnotationRDF
3
+ class TagBody < ActiveTriples::Resource
4
+
5
+ class << self; attr_reader :localname_prefix end
6
+ @localname_prefix="tb"
7
+
8
+ configure :type => RDFVocabularies::OA.Tag,
9
+ :base_uri => LD4L::OpenAnnotationRDF.configuration.base_uri,
10
+ :repository => :default
11
+
12
+ property :tag, :predicate => RDFVocabularies::CNT.chars # :type => XSD.string
13
+
14
+ ##
15
+ # Get a list of annotations using the tag value.
16
+ #
17
+ # @param [String] tag value
18
+ #
19
+ # @return array of annotation URIs
20
+ #
21
+ # NOTE: This method returns only persisted annotations.
22
+ def self::annotations_using( tag_value )
23
+ raise ArgumentError, 'Argument must be a string with at least one character' unless
24
+ tag_value.kind_of?(String) && tag_value.size > 0
25
+
26
+ tb = self::fetch_by_tag_value( tag_value )
27
+ return [] unless tb
28
+ tag_uri = tb.rdf_subject
29
+
30
+ # find usage by Annotations
31
+ graph = ActiveTriples::Repositories.repositories[repository]
32
+ query = RDF::Query.new({
33
+ :annotation => {
34
+ RDF.type => RDFVocabularies::OA.Annotation,
35
+ RDFVocabularies::OA.hasBody => tag_uri,
36
+ }
37
+ })
38
+ results = query.execute(graph)
39
+
40
+ # process results
41
+ annotations = []
42
+ results.each { |r| annotations << r.to_hash[:annotation] }
43
+ annotations
44
+ end
45
+
46
+
47
+ ##
48
+ # Search the configured repository for a TagBody triple that has the tag value for the tag property.
49
+ #
50
+ # @param [String] tag value
51
+ #
52
+ # @return instance of TagBody if found; otherwise, nil
53
+ def self::fetch_by_tag_value( tag_value )
54
+ raise ArgumentError, 'Argument must be a string with at least one character' unless
55
+ tag_value.kind_of?(String) && tag_value.size > 0
56
+
57
+ graph = ActiveTriples::Repositories.repositories[repository]
58
+ query = RDF::Query.new({
59
+ :tagbody => {
60
+ RDF.type => RDFVocabularies::OA.Tag,
61
+ RDFVocabularies::CNT.chars => tag_value,
62
+ }
63
+ })
64
+
65
+ tagbody = nil
66
+ results = query.execute(graph)
67
+ unless( results.empty? )
68
+ tagbody_uri = results[0].to_hash[:tagbody]
69
+ tagbody = LD4L::OpenAnnotationRDF::TagBody.new(tagbody_uri)
70
+ end
71
+ tagbody
72
+ end
73
+
74
+ def initialize(*args)
75
+ super(*args)
76
+
77
+ t = get_values(:type)
78
+ t << RDFVocabularies::CNT.ContentAsText
79
+ set_value(:type,t)
80
+ end
81
+ end
82
+ end
83
+ end