triannon 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9936254da42efbcc7d7225b4f684097a2ac84f0f
4
- data.tar.gz: b0d8e6bcba4019fc9cef4300f484f7a2d11774db
3
+ metadata.gz: 98b04d335bf8e2b24385cfeb4088d9c8db62c5c8
4
+ data.tar.gz: e10c1dc27cd105c7acb4fee6de77f8f51dddac9e
5
5
  SHA512:
6
- metadata.gz: 90a6538ea8c10582a4e06f1e159947b1013784d7f2b7c8474811f13534f3c4806ed559b2456b304c5d51ce688d0b5f2ff44b2a43b518a181827fd3a32b3276bc
7
- data.tar.gz: efda4aab06e7a5939d44e61ab6a860be835b20301c2f699291b1b246ae59e3820563720860c0ef307fbb1b9d4447b695ed911fbd7534df4f16419f77a2dce349
6
+ metadata.gz: 6f46652fb5c45b9b47be3374919eb4fd9bcf79ae50bf69059ee3896640990cedd11c499684185ea85d9befb6820c17ec8e6e84fc6425fbd7cef979dc57b6ef31
7
+ data.tar.gz: e3967493035299c17a6e7235f288e0b212738e94b9817806869fb4fb737bb6508e72c05607372718e6e84738ba85cca2d5597143ca723cf148457e2a003a6976
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Build Status](https://travis-ci.org/sul-dlss/triannon.svg?branch=master)](https://travis-ci.org/sul-dlss/triannon) [![Dependency Status](https://gemnasium.com/sul-dlss/triannon.svg)](https://gemnasium.com/sul-dlss/triannon) [![Gem Version](https://badge.fury.io/rb/triannon.svg)](http://badge.fury.io/rb/triannon)
1
+ [![Build Status](https://travis-ci.org/sul-dlss/triannon.svg?branch=master)](https://travis-ci.org/sul-dlss/triannon) [![Coverage Status](https://coveralls.io/repos/sul-dlss/triannon/badge.png)](https://coveralls.io/r/sul-dlss/triannon) [![Dependency Status](https://gemnasium.com/sul-dlss/triannon.svg)](https://gemnasium.com/sul-dlss/triannon) [![Gem Version](https://badge.fury.io/rb/triannon.svg)](http://badge.fury.io/rb/triannon)
2
2
 
3
3
  # Triannon
4
4
 
@@ -4,6 +4,7 @@ module Triannon
4
4
  class AnnotationsController < ApplicationController
5
5
  before_action :default_format_jsonld, only: [:show]
6
6
  before_action :set_annotation, only: [:show, :edit, :update, :destroy]
7
+ rescue_from Triannon::ExternalReferenceError, with: :ext_ref_error
7
8
 
8
9
  # GET /annotations/annotations
9
10
  def index
@@ -95,5 +96,10 @@ module Triannon
95
96
  end
96
97
  end
97
98
  end
99
+
100
+ # handle Triannon::ExternalReferenceError
101
+ def ext_ref_error(exception)
102
+ render plain: exception.message, status: 403
103
+ end
98
104
  end
99
105
  end
@@ -178,7 +178,12 @@ private
178
178
  end
179
179
 
180
180
  def json_oa_context
181
- @json_oa_context ||= File.read("lib/triannon/oa_context_20130208.json")
181
+ # FIXME: this is a terrible place to do this!!
182
+ if Rails.root.to_s.match(/internal/) # testing via engine_cart
183
+ @json_oa_context ||= File.read(Rails.root.join("..", "..", "lib", "triannon", "oa_context_20130208.json"))
184
+ else
185
+ @json_oa_context ||= File.read(Rails.root.join("lib", "triannon", "oa_context_20130208.json"))
186
+ end
182
187
  end
183
188
 
184
189
  def graph_exists?
@@ -1,4 +1,6 @@
1
1
  module Triannon
2
+ # an LDP aware model of an Annotation -- basically, a shim between the OA notion of an annotation
3
+ # and the LDP storage.
2
4
  class AnnotationLdp
3
5
 
4
6
  # RDF::Graph object with all triples, including back end (e.g. LDP, Fedora)
@@ -8,9 +10,7 @@ module Triannon
8
10
 
9
11
  # RDF::Graph without any back end (e.g. LDP, Fedora) triples
10
12
  def stripped_graph
11
- @stripped_graph ||= begin
12
- new_graph = RDF::LDP.remove_ldp_triples (RDF::FCRepo4.remove_fedora_triples(graph))
13
- end
13
+ RDF::LDP.remove_ldp_triples (RDF::FCRepo4.remove_fedora_triples(graph))
14
14
  end
15
15
 
16
16
  def base_uri
@@ -39,9 +39,11 @@ module Triannon
39
39
  }
40
40
  result
41
41
  end
42
-
43
- def load_data_into_graph ttl
44
- graph.from_ttl ttl
42
+
43
+ # add the passed statements to #graph
44
+ # @param [Array<RDF::Statement>] statements an array of RDF statements to be loaded into the graph
45
+ def load_statements_into_graph statements
46
+ graph.insert(statements) if statements && statements.size > 0
45
47
  end
46
48
 
47
49
  private
@@ -0,0 +1,33 @@
1
+ module Triannon
2
+ class GraphValidator < ActiveModel::Validator
3
+
4
+ def validate(anno)
5
+ graph_exists? anno
6
+ graph = anno.graph
7
+ basic_solution = graph.query(self.basic_query)
8
+ unless basic_solution && basic_solution.size == 1
9
+ anno.errors[:data] << 'The oa:Annotation class MUST be associated with each Annotation.'
10
+ anno.errors[:data] << 'each Annotation MUST have at least one target'
11
+ end
12
+ end
13
+
14
+ def graph_exists?(anno)
15
+ graph = anno.graph
16
+ unless graph && graph.size > 0
17
+ anno.errors[:data] << 'Unable to create non-null graph'
18
+ end
19
+ end
20
+
21
+ # query for a subject with
22
+ # predicate RDF::OpenAnnotation.hasTarget
23
+ # type of RDF::OpenAnnotation.Annotation
24
+ def self.basic_query
25
+ @@basic_query ||= begin
26
+ RDF::Query.new
27
+ basic_query << [:s, RDF.type, RDF::URI("http://www.w3.org/ns/oa#Annotation")]
28
+ basic_query << [:s, RDF::OpenAnnotation.hasTarget, nil]
29
+ end
30
+ end
31
+
32
+ end
33
+ end
@@ -7,24 +7,27 @@ module Triannon
7
7
  # use LDP protocol to create the OpenAnnotation.Annotation in an RDF store
8
8
  # @param [Triannon::Annotation] anno a Triannon::Annotation object, from which we use the graph
9
9
  def self.create(anno)
10
- # TODO: we should not get here if the Annotation object already has an id
11
- result = Triannon::LdpCreator.new anno
12
- result.create_base
10
+ if anno && anno.graph
11
+ # TODO: special case if the Annotation object already has an id --
12
+ # see https://github.com/sul-dlss/triannon/issues/84
13
+ result = Triannon::LdpCreator.new anno
14
+ result.create_base
13
15
 
14
- bodies_solns = anno.graph.query([nil, RDF::OpenAnnotation.hasBody, nil])
15
- if bodies_solns.size > 0
16
- result.create_body_container
17
- result.create_body_resources
18
- end
16
+ bodies_solns = anno.graph.query([nil, RDF::OpenAnnotation.hasBody, nil])
17
+ if bodies_solns.size > 0
18
+ result.create_body_container
19
+ result.create_body_resources
20
+ end
19
21
 
20
- targets_solns = anno.graph.query([nil, RDF::OpenAnnotation.hasTarget, nil])
21
- # NOTE: Annotation is invalid if there are no target statements
22
- if targets_solns.size > 0
23
- result.create_target_container
24
- result.create_target_resources
25
- end
22
+ targets_solns = anno.graph.query([nil, RDF::OpenAnnotation.hasTarget, nil])
23
+ # NOTE: Annotation is invalid if there are no target statements
24
+ if targets_solns.size > 0
25
+ result.create_target_container
26
+ result.create_target_resources
27
+ end
26
28
 
27
- result.id
29
+ result.id
30
+ end
28
31
  end
29
32
 
30
33
  # given an RDF::Resource (an RDF::Node or RDF::URI), look for all the statements with that object
@@ -51,16 +54,20 @@ module Triannon
51
54
 
52
55
  # POSTS a ttl representation of the LDP Annotation container to the LDP store
53
56
  def create_base
54
- # TODO: we should error if the Annotation object already has an id
55
-
57
+ if @anno.graph.query([nil, RDF::Triannon.externalReference, nil]).count > 0
58
+ raise Triannon::ExternalReferenceError, "Incoming annotations may not have http://triannon.stanford.edu/ns/externalReference as a predicate."
59
+ end
60
+
61
+ # TODO: special case if the Annotation object already has an id --
62
+ # see https://github.com/sul-dlss/triannon/issues/84
56
63
  g = RDF::Graph.new
57
64
  @anno.graph.each { |s|
58
65
  g << s
59
66
  }
60
67
 
61
- # remove the hasBody statements and any other statements associated with them
68
+ # don't include the hasBody statements and any other statements associated with them
62
69
  bodies_stmts = g.query([nil, RDF::OpenAnnotation.hasBody, nil])
63
- bodies_stmts.each { |has_body_stmt |
70
+ bodies_stmts.each { |has_body_stmt|
64
71
  g.delete has_body_stmt
65
72
  body_obj = has_body_stmt.object
66
73
  Triannon::LdpCreator.subject_statements(body_obj, g).each { |s|
@@ -68,9 +75,9 @@ module Triannon
68
75
  }
69
76
  }
70
77
 
71
- # remove the hasTarget statements and any other statements associated with them
78
+ # don't include the hasTarget statements and any other statements associated with them
72
79
  targets_stmts = g.query([nil, RDF::OpenAnnotation.hasTarget, nil])
73
- targets_stmts.each { |has_target_stmt |
80
+ targets_stmts.each { |has_target_stmt|
74
81
  g.delete has_target_stmt
75
82
  target_obj = has_target_stmt.object
76
83
  Triannon::LdpCreator.subject_statements(target_obj, g).each { |s|
@@ -111,120 +118,12 @@ module Triannon
111
118
 
112
119
  # create the body resources inside the (already created) body container
113
120
  def create_body_resources
114
- bodies_solns = @anno.graph.query([nil, RDF::OpenAnnotation.hasBody, nil])
115
- body_ids = []
116
- bodies_solns.each { |has_body_stmt |
117
- graph_for_resource = RDF::Graph.new
118
- body_obj = has_body_stmt.object
119
- if body_obj.is_a?(RDF::Node)
120
- # we need to use the null relative URI representation of blank nodes to write to LDP
121
- body_subject = RDF::URI.new
122
- else
123
- # it's already a URI, but we need to use the null relative URI representation so we can
124
- # write out as a Triannon:externalRef property with the URL, and any addl props too.
125
- if body_obj.to_str
126
- body_subject = RDF::URI.new
127
- graph_for_resource << RDF::Statement({:subject => body_subject,
128
- :predicate => RDF::Triannon.externalReference,
129
- :object => RDF::URI.new(body_obj.to_str)})
130
- addl_stmts = @anno.graph.query([body_obj, nil, nil])
131
- addl_stmts.each { |s|
132
- graph_for_resource << RDF::Statement({:subject => body_subject,
133
- :predicate => s.predicate,
134
- :object => s.object})
135
- }
136
- else # it's already a null relative URI
137
- body_subject = body_obj
138
- end
139
- end
140
- # add statements with body_obj as the subject
141
- Triannon::LdpCreator.subject_statements(body_obj, @anno.graph).each { |s|
142
- if s.subject == body_obj
143
- graph_for_resource << RDF::Statement({:subject => body_subject,
144
- :predicate => s.predicate,
145
- :object => s.object})
146
- else
147
- graph_for_resource << s
148
- end
149
- }
150
- body_ids << create_resource(graph_for_resource.to_ttl, "#{@id}/b")
151
- }
152
- body_ids
121
+ create_resources_in_container RDF::OpenAnnotation.hasBody
153
122
  end
154
-
123
+
155
124
  # create the target resources inside the (already created) target container
156
125
  def create_target_resources
157
- target_solns = @anno.graph.query([nil, RDF::OpenAnnotation.hasTarget, nil])
158
- target_ids = []
159
- target_solns.each { |has_target_stmt |
160
- graph_for_resource = RDF::Graph.new
161
- target_obj = has_target_stmt.object
162
- if target_obj.is_a?(RDF::Node)
163
- # we need to use the null relative URI representation of blank nodes to write to LDP
164
- target_subject = RDF::URI.new
165
- else
166
- # it's already a URI, but we need to use the null relative URI representation so we can
167
- # write out as a Triannon:externalRef property with the URL, and any addl props too.
168
- if target_obj.to_str
169
- target_subject = RDF::URI.new
170
- graph_for_resource << RDF::Statement({:subject => target_subject,
171
- :predicate => RDF::Triannon.externalReference,
172
- :object => RDF::URI.new(target_obj.to_str)})
173
- addl_stmts = @anno.graph.query([target_obj, nil, nil])
174
- addl_stmts.each { |s|
175
- graph_for_resource << RDF::Statement({:subject => target_subject,
176
- :predicate => s.predicate,
177
- :object => s.object})
178
- }
179
- else # it's already a null relative URI
180
- target_subject = target_obj
181
- end
182
- end
183
- # add statements with target_obj as the subject
184
- orig_source_objects = [] # the object URI nodes from targetObject, OA.hasSource, (uri) statements
185
- Triannon::LdpCreator.subject_statements(target_obj, @anno.graph).each { |s|
186
- if s.subject == target_obj
187
- # deal with external references in hasSource statements (i.e. targetObject, OA.hasSource, (url) )
188
- if s.predicate == RDF::OpenAnnotation.hasSource && s.object.is_a?(RDF::URI) && s.object.to_str
189
- # we need to represent the source URL as an externalReference - use a hash URI
190
- source_object = RDF::URI.new("#source")
191
- orig_source_objects << s.object
192
- graph_for_resource << RDF::Statement({:subject => source_object,
193
- :predicate => RDF::Triannon.externalReference,
194
- :object => RDF::URI.new(s.object.to_str)})
195
- # and all of the source URL's addl props
196
- Triannon::LdpCreator.subject_statements(s.object, @anno.graph).each { |ss|
197
- if ss.subject == s.object
198
- graph_for_resource << RDF::Statement({:subject => source_object,
199
- :predicate => ss.predicate,
200
- :object => ss.object})
201
- else
202
- graph_for_resource << ss
203
- end
204
- }
205
- # add the targetObj, OA.hasSource, (hash URI representation) to graph
206
- graph_for_resource << RDF::Statement({:subject => target_subject,
207
- :predicate => s.predicate,
208
- :object => source_object})
209
- else
210
- graph_for_resource << RDF::Statement({:subject => target_subject,
211
- :predicate => s.predicate,
212
- :object => s.object})
213
- end
214
- else
215
- graph_for_resource << s
216
- end
217
- }
218
- # make sure the graph we will write contains no extraneous statements about source URI
219
- # now represented as hash URI (#source)
220
- orig_source_objects.each { |rdf_uri_node|
221
- Triannon::LdpCreator.subject_statements(rdf_uri_node, graph_for_resource).each { |s|
222
- graph_for_resource.delete(s)
223
- }
224
- }
225
- target_ids << create_resource(graph_for_resource.to_ttl, "#{@id}/t")
226
- }
227
- target_ids
126
+ create_resources_in_container RDF::OpenAnnotation.hasTarget
228
127
  end
229
128
 
230
129
  def conn
@@ -261,5 +160,112 @@ module Triannon
261
160
  end
262
161
  end
263
162
 
163
+ # create the target/body resources inside the (already created) target/body container
164
+ # @param [RDF::URI] predicate either RDF::OpenAnnotation.hasTarget or RDF::OpenAnnotation.hasBody
165
+ def create_resources_in_container(predicate)
166
+ predicate_solns = @anno.graph.query([nil, predicate, nil])
167
+ resource_ids = []
168
+ predicate_solns.each { |predicate_stmt |
169
+ graph_for_resource = RDF::Graph.new
170
+ predicate_obj = predicate_stmt.object
171
+ if predicate_obj.is_a?(RDF::Node)
172
+ # we need to use the null relative URI representation of blank nodes to write to LDP
173
+ predicate_subject = RDF::URI.new
174
+ else
175
+ # it's already a URI, but we need to use the null relative URI representation so we can
176
+ # write out as a Triannon:externalRef property with the URL, and any addl props too.
177
+ if predicate_obj.to_str
178
+ predicate_subject = RDF::URI.new
179
+ graph_for_resource << RDF::Statement({:subject => predicate_subject,
180
+ :predicate => RDF::Triannon.externalReference,
181
+ :object => RDF::URI.new(predicate_obj.to_str)})
182
+ addl_stmts = @anno.graph.query([predicate_obj, nil, nil])
183
+ addl_stmts.each { |s|
184
+ graph_for_resource << RDF::Statement({:subject => predicate_subject,
185
+ :predicate => s.predicate,
186
+ :object => s.object})
187
+ }
188
+ else # it's already a null relative URI
189
+ predicate_subject = predicate_obj
190
+ end
191
+ end
192
+
193
+ # add statements with predicate_obj as the subject
194
+ orig_hash_uri_objs = [] # the orig URI objects from [targetObject, OA.hasSource/.default/.item, (uri)] statements
195
+ hash_uri_counter = 1
196
+ Triannon::LdpCreator.subject_statements(predicate_obj, @anno.graph).each { |s|
197
+ if s.subject == predicate_obj
198
+ # deal with any external URI references which may occur in:
199
+ # OA.hasSource (from SpecificResource), OA.default or OA.item (from Choice, Composite, List)
200
+ if s.object.is_a?(RDF::URI) && s.object.to_s
201
+ # do we need to represent the URL as an externalReference with hash URI
202
+ if s.predicate == RDF::OpenAnnotation.hasSource
203
+ hash_uri_str = "#source"
204
+ elsif s.predicate == RDF::OpenAnnotation.default
205
+ hash_uri_str = "#default"
206
+ elsif s.predicate == RDF::OpenAnnotation.item
207
+ hash_uri_str = "#item#{hash_uri_counter}"
208
+ hash_uri_counter = hash_uri_counter + 1
209
+ else
210
+ # we don't need to represent the object URI as an external ref
211
+ hash_uri_str = nil
212
+ graph_for_resource << RDF::Statement({:subject => predicate_subject,
213
+ :predicate => s.predicate,
214
+ :object => s.object})
215
+ end
216
+
217
+ if hash_uri_str
218
+ # represent the object URL as an external ref
219
+ new_hash_uri_obj = RDF::URI.new(hash_uri_str)
220
+ orig_hash_uri_obj = s.object
221
+ orig_hash_uri_objs << orig_hash_uri_obj
222
+ # add [targetObj, OA.hasSource/.default/.item, (hash URI)] triple to graph
223
+ graph_for_resource << RDF::Statement({:subject => predicate_subject,
224
+ :predicate => s.predicate,
225
+ :object => new_hash_uri_obj})
226
+
227
+ # add externalReference triple to graph
228
+ graph_for_resource << RDF::Statement({:subject => new_hash_uri_obj,
229
+ :predicate => RDF::Triannon.externalReference,
230
+ :object => RDF::URI.new(orig_hash_uri_obj.to_s)})
231
+ # and all of the orig URL's addl props
232
+ Triannon::LdpCreator.subject_statements(orig_hash_uri_obj, @anno.graph).each { |ss|
233
+ if ss.subject == orig_hash_uri_obj
234
+ graph_for_resource << RDF::Statement({:subject => new_hash_uri_obj,
235
+ :predicate => ss.predicate,
236
+ :object => ss.object})
237
+ else
238
+ graph_for_resource << ss
239
+ end
240
+ }
241
+ end
242
+ # NOTE: already dealt with case where there is no hash uri above
243
+ else
244
+ # s.object is not a URI, and subject = predicate_subject -- it may be a blank node
245
+ graph_for_resource << RDF::Statement({:subject => predicate_subject,
246
+ :predicate => s.predicate,
247
+ :object => s.object})
248
+ end
249
+ else
250
+ # s.subject != predicate_obj
251
+ graph_for_resource << s
252
+ end
253
+ }
254
+ # make sure the graph we will write contains no extraneous statements about URIs
255
+ # now represented as hash URIs
256
+ orig_hash_uri_objs.each { |uri_node|
257
+ Triannon::LdpCreator.subject_statements(uri_node, graph_for_resource).each { |s|
258
+ graph_for_resource.delete(s)
259
+ }
260
+ }
261
+ if (predicate == RDF::OpenAnnotation.hasTarget)
262
+ resource_ids << create_resource(graph_for_resource.to_ttl, "#{@id}/t")
263
+ else
264
+ resource_ids << create_resource(graph_for_resource.to_ttl, "#{@id}/b")
265
+ end
266
+ }
267
+ resource_ids
268
+ end
269
+
264
270
  end
265
271
  end
@@ -6,11 +6,11 @@ module Triannon
6
6
 
7
7
  def self.load key
8
8
  l = Triannon::LdpLoader.new key
9
- l.load_annotation
10
- l.load_body
11
- l.load_target
9
+ l.load_anno_container
10
+ l.load_bodies
11
+ l.load_targets
12
12
 
13
- oa_graph = Triannon::LdpToOaMapper.ldp_to_oa l.annotation
13
+ oa_graph = Triannon::LdpToOaMapper.ldp_to_oa l.ldp_annotation
14
14
  oa_graph
15
15
  end
16
16
 
@@ -19,29 +19,32 @@ module Triannon
19
19
  l.find_all
20
20
  end
21
21
 
22
- attr_accessor :annotation
22
+ attr_accessor :ldp_annotation
23
23
 
24
24
  def initialize key = nil
25
25
  @key = key
26
26
  @base_uri = Triannon.config[:ldp_url]
27
- @annotation = Triannon::AnnotationLdp.new
27
+ @ldp_annotation = Triannon::AnnotationLdp.new
28
28
  end
29
29
 
30
- def load_annotation
31
- @annotation.load_data_into_graph get_ttl @key
30
+ # load annotation container object into @ldp_annotation's (our Triannon::AnnotationLdp object) graph
31
+ def load_anno_container
32
+ load_object_into_annotation_graph(@key)
32
33
  end
33
34
 
34
- def load_body
35
- @annotation.body_uris.each { |body_uri|
36
- sub_path = body_uri.to_s.split(@base_uri + '/').last
37
- @annotation.load_data_into_graph get_ttl sub_path
35
+ # load body objects into @ldp_annotation's (our Triannon::AnnotationLdp object) graph
36
+ def load_bodies
37
+ @ldp_annotation.body_uris.each { |body_uri|
38
+ body_obj_path = body_uri.to_s.split(@base_uri + '/').last
39
+ load_object_into_annotation_graph(body_obj_path)
38
40
  }
39
41
  end
40
42
 
41
- def load_target
42
- @annotation.target_uris.each { |target_uri|
43
- sub_path = target_uri.to_s.split(@base_uri + '/').last
44
- @annotation.load_data_into_graph get_ttl sub_path
43
+ # load target objects into @ldp_annotation's (our Triannon::AnnotationLdp object) graph
44
+ def load_targets
45
+ @ldp_annotation.target_uris.each { |target_uri|
46
+ target_obj_path = target_uri.to_s.split(@base_uri + '/').last
47
+ load_object_into_annotation_graph(target_obj_path)
45
48
  }
46
49
  end
47
50
 
@@ -64,19 +67,36 @@ module Triannon
64
67
 
65
68
  protected
66
69
 
70
+ # given a path to the back end storage url, retrieve the object from storage and load
71
+ # the triples (except storage specific triples) into the graph for @ldp_annotation, our Triannon::AnnotationLdp object
72
+ # @param [String] path the path to the object, e.g. the pid, or pid/t/target_pid
73
+ def load_object_into_annotation_graph(path)
74
+ @ldp_annotation.load_statements_into_graph(statements_from_ttl_minus_fedora(get_ttl path))
75
+ end
76
+
77
+ # gets object from back end storage as turtle serialization
67
78
  def get_ttl sub_path = nil
68
79
  resp = conn.get do |req|
69
80
  req.url "#{sub_path}" if sub_path
70
- req.headers['Accept'] = 'text/turtle'
81
+ req.headers['Accept'] = 'application/x-turtle'
71
82
  end
72
83
  resp.body
73
84
  end
74
85
 
86
+ # turns turtle serialization into Array of RDF::Statements, removing fedora-specific triples
87
+ # (leaving LDP and OA triples)
88
+ # @param [String] ttl a String containing RDF serialized as turtle
89
+ # @return [Array<RDF::Statements>] the RDF statements represented in the ttl
90
+ def statements_from_ttl_minus_fedora ttl
91
+ # RDF::Turtle::Reader.new(ttl).statements.to_a
92
+ g = RDF::Graph.new.from_ttl(ttl)
93
+ RDF::FCRepo4.remove_fedora_triples(g).statements
94
+ end
95
+
75
96
  def conn
76
97
  @c ||= Faraday.new @base_uri
77
98
  end
78
99
 
79
100
  end
80
101
 
81
-
82
102
  end
@@ -5,20 +5,21 @@ module Triannon
5
5
  def self.ldp_to_oa ldp_anno
6
6
  mapper = Triannon::LdpToOaMapper.new ldp_anno
7
7
  mapper.extract_base
8
- mapper.extract_body
9
- mapper.extract_target
8
+ mapper.extract_bodies
9
+ mapper.extract_targets
10
10
  mapper.oa_graph
11
11
  end
12
12
 
13
13
  attr_accessor :id, :oa_graph
14
14
 
15
15
  def initialize ldp_anno
16
- @ldp = ldp_anno
16
+ @ldp_anno = ldp_anno
17
+ @ldp_anno_graph = ldp_anno.stripped_graph
17
18
  @oa_graph = RDF::Graph.new
18
19
  end
19
20
 
20
21
  def extract_base
21
- @ldp.stripped_graph.each_statement do |stmnt|
22
+ @ldp_anno_graph.each_statement do |stmnt|
22
23
  if stmnt.predicate == RDF.type && stmnt.object == RDF::OpenAnnotation.Annotation
23
24
  @id = stmnt.subject.to_s.split('/').last
24
25
  @root_uri = RDF::URI.new(Triannon.config[:triannon_base_url] + "/#{@id}")
@@ -30,31 +31,175 @@ module Triannon
30
31
  end
31
32
  end
32
33
 
33
- def extract_body
34
- @ldp.body_uris.each { |body_uri|
35
- res = @ldp.stripped_graph.query [body_uri, RDF.type, RDF::Content.ContentAsText]
36
- if res.count > 0 # TODO raise if this fails?
37
- body_node = RDF::Node.new
38
- @oa_graph << [@root_uri, RDF::OpenAnnotation.hasBody, body_node]
39
- @oa_graph << [body_node, RDF.type, RDF::Content.ContentAsText]
40
- @oa_graph << [body_node, RDF.type, RDF::DCMIType.Text]
41
- res_chars = @ldp.stripped_graph.query [body_uri, RDF::Content.chars, nil]
42
- if res_chars.count > 0
43
- @oa_graph << [body_node, RDF::Content.chars, res_chars.first.object]
44
- end
34
+ def extract_bodies
35
+ @ldp_anno.body_uris.each { |body_uri|
36
+ if !map_external_ref(body_uri, RDF::OpenAnnotation.hasBody) &&
37
+ !map_content_as_text(body_uri, RDF::OpenAnnotation.hasBody) &&
38
+ !map_specific_resource(body_uri, RDF::OpenAnnotation.hasBody)
39
+ map_choice(body_uri, RDF::OpenAnnotation.hasBody)
45
40
  end
46
41
  }
47
42
  end
48
43
 
49
- def extract_target
50
- @ldp.target_uris.each { |target_uri|
51
- res = @ldp.stripped_graph.query [target_uri, RDF::Triannon.externalReference, nil]
52
- if res.count > 0
53
- ext_uri = res.first.object
54
- @oa_graph << [@root_uri, RDF::OpenAnnotation.hasTarget, ext_uri]
44
+ def extract_targets
45
+ @ldp_anno.target_uris.each { |target_uri|
46
+ if !map_external_ref(target_uri, RDF::OpenAnnotation.hasTarget) &&
47
+ !map_specific_resource(target_uri, RDF::OpenAnnotation.hasTarget)
48
+ map_choice(target_uri, RDF::OpenAnnotation.hasTarget)
55
49
  end
56
50
  }
57
51
  end
52
+
53
+ # if uri_obj is the subject of a Triannon.externalReference then add appropriate
54
+ # statements to @oa_graph and return true
55
+ # @param [RDF::URI] uri_obj the object that may have RDF::Triannon.externalReference
56
+ # @param [RDF::URI] predicate the predicate for [subject_obj, predicate, (ext_url)] statement
57
+ # to be added to @oa_graph, e.g. RDF::OpenAnnotation.hasTarget
58
+ # @param [RDF::URI] the subject object to get the predicate statement; defaults to @root_uri
59
+ # @returns [Boolean] true if it adds statements to @oa_graph, false otherwise
60
+ def map_external_ref uri_obj, predicate, subject_obj = @root_uri
61
+ solns = @ldp_anno_graph.query [uri_obj, RDF::Triannon.externalReference, nil]
62
+ if solns.count > 0
63
+ external_uri = solns.first.object
64
+ @oa_graph << [subject_obj, predicate, external_uri]
65
+
66
+ Triannon::LdpCreator.subject_statements(uri_obj, @ldp_anno_graph).each { |stmt|
67
+ if stmt.subject == uri_obj && stmt.predicate != RDF::Triannon.externalReference
68
+ @oa_graph << [external_uri, stmt.predicate, stmt.object]
69
+ else
70
+ # we should never get here for external references ...
71
+ end
72
+ }
73
+ true
74
+ else
75
+ false
76
+ end
77
+ end
78
+
79
+ # if uri_obj has a type of RDF::Content.ContentAsText, then this is a skolemized blank node;
80
+ # add appropriate statements to @oa_graph to represent the blank node and its contents and return true
81
+ # @param [RDF::URI] uri_obj the object that may type RDF::Content.ContentAsText
82
+ # @param [RDF::URI] predicate the predicate for [subject_obj, predicate, (ext_url)] statement
83
+ # to be added to @oa_graph, e.g. RDF::OpenAnnotation.hasTarget
84
+ # @param [RDF::URI] the subject object to get the predicate statement; defaults to @root_uri
85
+ # @returns [Boolean] true if it adds statements to @oa_graph, false otherwise
86
+ def map_content_as_text uri_obj, predicate, subject_obj = @root_uri
87
+ solns = @ldp_anno_graph.query [uri_obj, RDF.type, RDF::Content.ContentAsText]
88
+ if solns.count > 0
89
+ blank_node = RDF::Node.new
90
+ @oa_graph << [subject_obj, predicate, blank_node]
91
+
92
+ Triannon::LdpCreator.subject_statements(uri_obj, @ldp_anno_graph).each { |stmt|
93
+ if stmt.subject == uri_obj
94
+ @oa_graph << [blank_node, stmt.predicate, stmt.object]
95
+ else
96
+ # it is a descendant statment - take as is
97
+ @oa_graph << stmt
98
+ end
99
+ }
100
+ true
101
+ else
102
+ false
103
+ end
104
+ end
105
+
106
+ # if uri_obj has a type of RDF::OpenAnnotation.SpecificResource, then this is a skolemized blank node;
107
+ # add appropriate statements to @oa_graph to represent the blank node and its contents and return true
108
+ # @param [RDF::URI] uri_obj the object that may have type RDF::OpenAnnotation.SpecificResource
109
+ # @param [RDF::URI] predicate the predicate for [@root_uri, predicate, (sel_res)] statement
110
+ # to be added to @oa_graph, e.g. RDF::OpenAnnotation.hasTarget
111
+ # @returns [Boolean] true if it adds statements to @oa_graph, false otherwise
112
+ def map_specific_resource uri_obj, predicate
113
+ solns = @ldp_anno_graph.query [uri_obj, RDF.type, RDF::OpenAnnotation.SpecificResource]
114
+ if solns.count > 0
115
+ blank_node = RDF::Node.new
116
+ @oa_graph << [@root_uri, predicate, blank_node]
117
+
118
+ source_obj = nil
119
+ selector_obj = nil
120
+ selector_blank_node = nil
121
+ specific_res_stmts = Triannon::LdpCreator.subject_statements(uri_obj, @ldp_anno_graph)
122
+ specific_res_stmts.each { |stmt|
123
+ if stmt.predicate == RDF::OpenAnnotation.hasSource
124
+ # expecting a hash URI
125
+ source_obj = stmt.object
126
+ if source_obj.to_s.match("#{uri_obj.to_s}#source")
127
+ source_has_ext_uri = map_external_ref source_obj, RDF::OpenAnnotation.hasSource, blank_node
128
+ end
129
+ elsif stmt.predicate == RDF::OpenAnnotation.hasSelector
130
+ # this becomes a blank node. Per http://www.openannotation.org/spec/core/specific.html#Selectors
131
+ # "Typically if all of the information needed to resolve the Selector (or other Specifier)
132
+ # is present within the graph, such as is the case for the
133
+ # FragmentSelector, TextQuoteSelector, TextPositionSelector and DataPositionSelector classes,
134
+ # then there is no need to have a resolvable resource that provides the same information."
135
+ selector_obj = stmt.object
136
+ selector_blank_node = RDF::Node.new
137
+ @oa_graph << [blank_node, RDF::OpenAnnotation.hasSelector, selector_blank_node]
138
+ end
139
+ }
140
+
141
+ # We can't know we'll hit hasSource and hasSelector statements in graph first,
142
+ # so we must do another pass through the statements to get that information
143
+ specific_res_stmts.each { |stmt|
144
+ if stmt.subject == uri_obj && stmt.object != source_obj && stmt.object != selector_obj
145
+ @oa_graph << [blank_node, stmt.predicate, stmt.object]
146
+ elsif stmt.subject != source_obj
147
+ if selector_blank_node && stmt.subject == selector_obj
148
+ @oa_graph << [selector_blank_node, stmt.predicate, stmt.object]
149
+ end
150
+ # there shouldn't be any other statements present
151
+ end
152
+ }
153
+ true
154
+ else
155
+ false
156
+ end
157
+ end
158
+
159
+ # if uri_obj has a type of RDF::OpenAnnotation.Choice, then this is a skolemized blank node;
160
+ # add appropriate statements to @oa_graph to represent the blank node and its contents and return true
161
+ # @param [RDF::URI] uri_obj the object that may have type RDF::OpenAnnotation.Choice
162
+ # @param [RDF::URI] predicate the predicate for [@root_uri, predicate, (choice)] statement
163
+ # to be added to @oa_graph, e.g. RDF::OpenAnnotation.hasTarget
164
+ # @returns [Boolean] true if it adds statements to @oa_graph, false otherwise
165
+ def map_choice uri_obj, predicate
166
+ solns = @ldp_anno_graph.query [uri_obj, RDF.type, RDF::OpenAnnotation.Choice]
167
+ if solns.count > 0
168
+ blank_node = RDF::Node.new
169
+ @oa_graph << [@root_uri, predicate, blank_node]
170
+
171
+ default_obj = nil
172
+ item_objs = []
173
+ choice_stmts = Triannon::LdpCreator.subject_statements(uri_obj, @ldp_anno_graph)
174
+ choice_stmts.each { |stmt|
175
+ if stmt.predicate == RDF::OpenAnnotation.default
176
+ default_obj = stmt.object
177
+ # assume it is either ContentAsText or external ref
178
+ if !map_content_as_text(default_obj, RDF::OpenAnnotation.default, blank_node)
179
+ map_external_ref(default_obj, RDF::OpenAnnotation.default, blank_node)
180
+ end
181
+ elsif stmt.predicate == RDF::OpenAnnotation.item
182
+ item_objs << stmt.object
183
+ # assume it is either ContentAsText or external ref
184
+ if !map_content_as_text(stmt.object, RDF::OpenAnnotation.item, blank_node)
185
+ map_external_ref(stmt.object, RDF::OpenAnnotation.item, blank_node)
186
+ end
187
+ end
188
+ }
189
+
190
+ # We can't know we'll hit item and default statements in graph first,
191
+ # so we must do another pass through the statements to get that information
192
+ choice_stmts.each { |stmt|
193
+ if stmt.subject == uri_obj && stmt.object != default_obj && !item_objs.include?(stmt.object)
194
+ @oa_graph << [blank_node, stmt.predicate, stmt.object]
195
+ # there shouldn't be any other unmapped statements present
196
+ end
197
+ }
198
+ true
199
+ else
200
+ false
201
+ end
202
+ end
58
203
 
59
204
  end
60
205
 
@@ -13,7 +13,7 @@
13
13
 
14
14
  <div class="field">
15
15
  <%= f.label 'data (as json-ld or turtle)' %><br>
16
- <%= f.text_area :data %>
16
+ <%= f.text_area :data, size: "80x20" %>
17
17
  </div>
18
18
  <div class="actions">
19
19
  <%= f.submit %>
data/lib/triannon.rb CHANGED
@@ -8,6 +8,7 @@ require 'faraday'
8
8
 
9
9
  module Triannon
10
10
  require "triannon/engine"
11
+ require "triannon/error"
11
12
 
12
13
  class << self
13
14
  attr_accessor :config
@@ -0,0 +1,9 @@
1
+ module Triannon
2
+ # generic Triannon error allowing rescue to catch all Triannon exceptions
3
+ class Error < RuntimeError
4
+ end
5
+
6
+ class ExternalReferenceError < Triannon::Error
7
+ end
8
+
9
+ end
@@ -1,3 +1,3 @@
1
1
  module Triannon
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: triannon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Beer
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-10-29 00:00:00.000000000 Z
12
+ date: 2014-11-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -221,6 +221,20 @@ dependencies:
221
221
  - - ">="
222
222
  - !ruby/object:Gem::Version
223
223
  version: '0'
224
+ - !ruby/object:Gem::Dependency
225
+ name: coveralls
226
+ requirement: !ruby/object:Gem::Requirement
227
+ requirements:
228
+ - - ">="
229
+ - !ruby/object:Gem::Version
230
+ version: '0'
231
+ type: :development
232
+ prerelease: false
233
+ version_requirements: !ruby/object:Gem::Requirement
234
+ requirements:
235
+ - - ">="
236
+ - !ruby/object:Gem::Version
237
+ version: '0'
224
238
  description:
225
239
  email:
226
240
  - cabeer@stanford.edu
@@ -241,6 +255,7 @@ files:
241
255
  - app/helpers/triannon/application_helper.rb
242
256
  - app/models/triannon/annotation.rb
243
257
  - app/models/triannon/annotation_ldp.rb
258
+ - app/models/triannon/graph_validator.rb
244
259
  - app/services/triannon/ldp_creator.rb
245
260
  - app/services/triannon/ldp_destroyer.rb
246
261
  - app/services/triannon/ldp_loader.rb
@@ -261,6 +276,7 @@ files:
261
276
  - lib/tasks/triannon_tasks.rake
262
277
  - lib/triannon.rb
263
278
  - lib/triannon/engine.rb
279
+ - lib/triannon/error.rb
264
280
  - lib/triannon/oa_context_20130208.json
265
281
  - lib/triannon/version.rb
266
282
  homepage:
@@ -283,7 +299,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
283
299
  version: '0'
284
300
  requirements: []
285
301
  rubyforge_project:
286
- rubygems_version: 2.2.2
302
+ rubygems_version: 2.4.3
287
303
  signing_key:
288
304
  specification_version: 4
289
305
  summary: Rails engine for working with storage of OpenAnnotations stored in Fedora4