ld4l-open_annotation_rdf 0.0.4

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.
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