triannon 0.6.0 → 0.7.0
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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/app/controllers/concerns/rdf_response_formats.rb +72 -0
- data/app/controllers/triannon/annotations_controller.rb +6 -69
- data/app/controllers/triannon/search_controller.rb +46 -0
- data/app/models/triannon/annotation.rb +1 -1
- data/app/services/triannon/solr_searcher.rb +164 -0
- data/app/views/triannon/search/find.html.erb +3 -0
- data/config/routes.rb +19 -14
- data/lib/generators/triannon/install_generator.rb +1 -1
- data/lib/triannon.rb +1 -0
- data/lib/triannon/iiif_anno_list.rb +59 -0
- data/lib/triannon/version.rb +1 -1
- metadata +9 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d962055a1eab01f2506013e1b964c52c4e8f53b8
|
4
|
+
data.tar.gz: 2015fcbfba7f07fed353a59250a044376863aad0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a267c5b3a4c2fd76e386ca1b9f6e196dd5905a94c9590c9698ec906add9614df8c512d6382ce2c17f7aad619dc608435904dfe28fbd22748b2df87532310340e
|
7
|
+
data.tar.gz: c22b8ddb6e7b659598f879aceefa008345b35995b8945b8aa57a218162f5b29a5cc2e2bc420034727e02af14624fc113949165acfb11ab7331d7c9f6b7073d03
|
data/README.md
CHANGED
@@ -50,7 +50,7 @@ Set up caching for jsonld context documents:
|
|
50
50
|
** add to Gemfile:
|
51
51
|
|
52
52
|
```ruby
|
53
|
-
gem 'rest-client', '~> 1.7.
|
53
|
+
gem 'rest-client', '~> 1.7.3' # problem with rest-client 1.8.0 and rest-client-components
|
54
54
|
gem 'rack-cache'
|
55
55
|
gem 'rest-client-components'
|
56
56
|
```
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# methods to support RDF response formats
|
2
|
+
module RdfResponseFormats
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
# find first mime type from request.accept that matches return mime type
|
6
|
+
def mime_type_from_accept(return_mime_types)
|
7
|
+
@mime_type_from_accept ||= begin
|
8
|
+
if request.accept && request.accept.is_a?(String)
|
9
|
+
accept_mime_types = request.accept.split(',')
|
10
|
+
accept_mime_types.each { |mime_type|
|
11
|
+
mime_str = mime_type.split("; profile=").first.strip
|
12
|
+
if return_mime_types.include? mime_str
|
13
|
+
return mime_str
|
14
|
+
end
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# set format to jsonld if it isn't already set
|
21
|
+
def default_format_jsonld
|
22
|
+
if ((!request.accept || request.accept.empty?) && (!params[:format] || params[:format].empty?))
|
23
|
+
request.format = "jsonld"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# parse the Accept HTTP header for the value of profile if it is a request for jsonld or json
|
28
|
+
# e.g. Accept: application/ld+json; profile="http://www.w3.org/ns/oa-context-20130208.json"
|
29
|
+
# @return [String] url for jsonld @context or nil if missing or non-jsonld/json format
|
30
|
+
def context_url_from_accept
|
31
|
+
if request.format == "jsonld" || request.format == "json"
|
32
|
+
accept_str = request.accept
|
33
|
+
if accept_str && accept_str.split("profile=") && accept_str.split("profile=").last
|
34
|
+
context_url = accept_str.split("profile=").last.strip
|
35
|
+
context_url = context_url[1, context_url.size] if context_url.start_with?('"')
|
36
|
+
context_url = context_url[0, context_url.size-1] if context_url.end_with?('"')
|
37
|
+
case context_url
|
38
|
+
when Triannon::JsonldContext::OA_DATED_CONTEXT_URL,
|
39
|
+
Triannon::JsonldContext::OA_CONTEXT_URL,
|
40
|
+
Triannon::JsonldContext::IIIF_CONTEXT_URL
|
41
|
+
context_url
|
42
|
+
else
|
43
|
+
nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# parse the Accept HTTP Link for the value of rel if it is a request for jsonld or json
|
50
|
+
# e.g. Link: http://www.w3.org/ns/oa.json; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"
|
51
|
+
# note that the "type" part is optional
|
52
|
+
# @return [String] url for jsonld @context or nil if missing or non-jsonld/json format
|
53
|
+
def context_url_from_link
|
54
|
+
if request.format == "jsonld" || request.format == "json"
|
55
|
+
link_str = request.headers["Link"]
|
56
|
+
if link_str && link_str.split("; rel=") && link_str.split("; rel=").first
|
57
|
+
context_url = link_str.split("; rel=").first.strip
|
58
|
+
case context_url
|
59
|
+
when Triannon::JsonldContext::OA_DATED_CONTEXT_URL,
|
60
|
+
Triannon::JsonldContext::OA_CONTEXT_URL,
|
61
|
+
Triannon::JsonldContext::IIIF_CONTEXT_URL
|
62
|
+
context_url
|
63
|
+
else
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
end
|
@@ -2,6 +2,8 @@ require_dependency "triannon/application_controller"
|
|
2
2
|
|
3
3
|
module Triannon
|
4
4
|
class AnnotationsController < ApplicationController
|
5
|
+
include RdfResponseFormats
|
6
|
+
|
5
7
|
before_action :default_format_jsonld, only: [:show]
|
6
8
|
before_action :set_annotation, only: [:show, :update, :destroy]
|
7
9
|
rescue_from Triannon::ExternalReferenceError, with: :ext_ref_error
|
@@ -51,7 +53,7 @@ module Triannon
|
|
51
53
|
end
|
52
54
|
|
53
55
|
# NOT YET IMPLEMENTED
|
54
|
-
# GET /annotations/1/edit
|
56
|
+
# GET /annotations/1/edit
|
55
57
|
# def edit
|
56
58
|
# end
|
57
59
|
|
@@ -71,7 +73,7 @@ module Triannon
|
|
71
73
|
content_type = request.headers["Content-Type"]
|
72
74
|
@annotation = Annotation.new({:data => request.body.read, :expected_content_type => content_type})
|
73
75
|
end
|
74
|
-
|
76
|
+
|
75
77
|
if @annotation.save
|
76
78
|
default_format_jsonld # NOTE: this must be here and not in before_filter or we get Missing template errors
|
77
79
|
flash[:notice] = "Annotation #{@annotation.id} was successfully created."
|
@@ -124,83 +126,18 @@ module Triannon
|
|
124
126
|
@annotation.destroy
|
125
127
|
redirect_to annotations_url, status: 204, notice: 'Annotation was successfully destroyed.'
|
126
128
|
end
|
127
|
-
|
129
|
+
|
128
130
|
private
|
129
131
|
|
130
132
|
def set_annotation
|
131
133
|
@annotation = Annotation.find(params[:id])
|
132
134
|
end
|
133
|
-
|
134
|
-
# set format to jsonld if it isn't already set
|
135
|
-
def default_format_jsonld
|
136
|
-
if ((!request.accept || request.accept.empty?) && (!params[:format] || params[:format].empty?))
|
137
|
-
request.format = "jsonld"
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
# find first mime type from request.accept that matches return mime type
|
142
|
-
def mime_type_from_accept(return_mime_types)
|
143
|
-
@mime_type_from_accept ||= begin
|
144
|
-
if request.accept && request.accept.is_a?(String)
|
145
|
-
accept_mime_types = request.accept.split(',')
|
146
|
-
accept_mime_types.each { |mime_type|
|
147
|
-
mime_str = mime_type.split("; profile=").first.strip
|
148
|
-
if return_mime_types.include? mime_str
|
149
|
-
return mime_str
|
150
|
-
end
|
151
|
-
}
|
152
|
-
end
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
# parse the Accept HTTP header for the value of profile if it is a request for jsonld or json
|
157
|
-
# e.g. Accept: application/ld+json; profile="http://www.w3.org/ns/oa-context-20130208.json"
|
158
|
-
# @return [String] url for jsonld @context or nil if missing or non-jsonld/json format
|
159
|
-
def context_url_from_accept
|
160
|
-
if request.format == "jsonld" || request.format == "json"
|
161
|
-
accept_str = request.accept
|
162
|
-
if accept_str && accept_str.split("profile=") && accept_str.split("profile=").last
|
163
|
-
context_url = accept_str.split("profile=").last.strip
|
164
|
-
context_url = context_url[1, context_url.size] if context_url.start_with?('"')
|
165
|
-
context_url = context_url[0, context_url.size-1] if context_url.end_with?('"')
|
166
|
-
case context_url
|
167
|
-
when Triannon::JsonldContext::OA_DATED_CONTEXT_URL,
|
168
|
-
Triannon::JsonldContext::OA_CONTEXT_URL,
|
169
|
-
Triannon::JsonldContext::IIIF_CONTEXT_URL
|
170
|
-
context_url
|
171
|
-
else
|
172
|
-
nil
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
# parse the Accept HTTP Link for the value of rel if it is a request for jsonld or json
|
179
|
-
# e.g. Link: http://www.w3.org/ns/oa.json; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"
|
180
|
-
# note that the "type" part is optional
|
181
|
-
# @return [String] url for jsonld @context or nil if missing or non-jsonld/json format
|
182
|
-
def context_url_from_link
|
183
|
-
if request.format == "jsonld" || request.format == "json"
|
184
|
-
link_str = request.headers["Link"]
|
185
|
-
if link_str && link_str.split("; rel=") && link_str.split("; rel=").first
|
186
|
-
context_url = link_str.split("; rel=").first.strip
|
187
|
-
case context_url
|
188
|
-
when Triannon::JsonldContext::OA_DATED_CONTEXT_URL,
|
189
|
-
Triannon::JsonldContext::OA_CONTEXT_URL,
|
190
|
-
Triannon::JsonldContext::IIIF_CONTEXT_URL
|
191
|
-
context_url
|
192
|
-
else
|
193
|
-
nil
|
194
|
-
end
|
195
|
-
end
|
196
|
-
end
|
197
|
-
end
|
198
135
|
|
199
136
|
# handle Triannon::ExternalReferenceError
|
200
137
|
def ext_ref_error(exception)
|
201
138
|
render plain: exception.message, status: 403
|
202
139
|
end
|
203
|
-
|
140
|
+
|
204
141
|
# render json_ld respecting requested context
|
205
142
|
# @param [String] req_context set to "iiif" or "oa". Default is oa
|
206
143
|
# @param [String] mime_type the mime type to be set in the Content-Type header of the HTTP response
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require_dependency "triannon/application_controller"
|
2
|
+
|
3
|
+
module Triannon
|
4
|
+
class SearchController < ApplicationController
|
5
|
+
include RdfResponseFormats
|
6
|
+
|
7
|
+
before_action :default_format_jsonld, only: [:find]
|
8
|
+
|
9
|
+
def find
|
10
|
+
anno_graphs_array = solr_searcher.find(params)
|
11
|
+
|
12
|
+
# add id to iiif_anno_list
|
13
|
+
@list_hash = Triannon::IIIFAnnoList.anno_list(anno_graphs_array)
|
14
|
+
@list_hash["@id"] = request.original_url if @list_hash
|
15
|
+
|
16
|
+
respond_to do |format|
|
17
|
+
format.jsonld { render :json => @list_hash.to_json, content_type: "application/ld+json" }
|
18
|
+
format.ttl {
|
19
|
+
accept_return_type = mime_type_from_accept(["application/x-turtle", "text/turtle"])
|
20
|
+
render :body => RDF::Graph.new.from_jsonld(@list_hash.to_json).to_ttl, content_type: accept_return_type if accept_return_type
|
21
|
+
}
|
22
|
+
format.rdfxml {
|
23
|
+
accept_return_type = mime_type_from_accept(["application/rdf+xml", "text/rdf+xml", "text/rdf"])
|
24
|
+
render :body => RDF::Graph.new.from_jsonld(@list_hash.to_json).to_rdfxml, content_type: accept_return_type if accept_return_type }
|
25
|
+
format.json {
|
26
|
+
accept_return_type = mime_type_from_accept(["application/json", "text/x-json", "application/jsonrequest"])
|
27
|
+
render :json => @list_hash.to_json, content_type: accept_return_type
|
28
|
+
}
|
29
|
+
format.xml {
|
30
|
+
accept_return_type = mime_type_from_accept(["application/xml", "text/xml", "application/x-xml"])
|
31
|
+
render :xml => RDF::Graph.new.from_jsonld(@list_hash.to_json).to_rdfxml, content_type: accept_return_type if accept_return_type }
|
32
|
+
format.html { render :find }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
def solr_searcher
|
40
|
+
@ss ||= Triannon::SolrSearcher.new
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
end # SearchController
|
45
|
+
|
46
|
+
end # Triannon
|
@@ -102,7 +102,7 @@ protected
|
|
102
102
|
|
103
103
|
# Add annotation to Solr as a Solr document
|
104
104
|
def solr_save
|
105
|
-
solr_writer.write(graph) if
|
105
|
+
solr_writer.write(graph) if id_as_url && !id_as_url.empty?
|
106
106
|
end
|
107
107
|
|
108
108
|
# Delete annotation from Solr
|
@@ -0,0 +1,164 @@
|
|
1
|
+
module Triannon
|
2
|
+
class SolrSearcher
|
3
|
+
|
4
|
+
# convert RSolr::Response object into an array of Triannon::Graph objects,
|
5
|
+
# where each graph object contains a single annotation returned in the response docs
|
6
|
+
# @param [Hash] rsolr_response an RSolr response to a query. It's actually an
|
7
|
+
# RSolr::HashWithResponse but let's not quibble
|
8
|
+
# @return [Array<Triannon::Graph>]
|
9
|
+
def self.anno_graphs_array(rsolr_response)
|
10
|
+
result = []
|
11
|
+
# TODO: deal with Solr pagination
|
12
|
+
rsolr_response['response']['docs'].each { |solr_doc_hash|
|
13
|
+
result << Triannon::Graph.new(RDF::Graph.new.from_jsonld(solr_doc_hash['anno_jsonld']))
|
14
|
+
}
|
15
|
+
result
|
16
|
+
end
|
17
|
+
|
18
|
+
# @note hardcoded Solr search service expectation in generated search params
|
19
|
+
# @note hardcoded mapping of REST params for /search to Solr params
|
20
|
+
#
|
21
|
+
# Convert action request params to appropriate params
|
22
|
+
# to be sent to the search service as part of a search request
|
23
|
+
#
|
24
|
+
# request params are given in "Annotation Lists in Triannon" by Robert Sanderson
|
25
|
+
# in Google Docs:
|
26
|
+
#
|
27
|
+
# - targetUri, value is a URI
|
28
|
+
# - bodyUri, value is a URI
|
29
|
+
# - bodyExact, value is a string
|
30
|
+
# - bodyKeyword, value is a string
|
31
|
+
# - bodyType, value is a URI
|
32
|
+
# - motivatedBy, value is a URI (or just the fragment portion)
|
33
|
+
# - annotatedBy, value is a URI
|
34
|
+
# - annotatedAt, value is a datetime
|
35
|
+
#
|
36
|
+
# @param [Hash<String => String>] controller_params params from Controller
|
37
|
+
# @return [Hash] params to send to Solr as a Hash
|
38
|
+
def self.solr_params(controller_params)
|
39
|
+
solr_params_hash = {}
|
40
|
+
q_terms_array = []
|
41
|
+
fq_terms_array = []
|
42
|
+
|
43
|
+
controller_params.each_pair { |k, v|
|
44
|
+
case k.downcase
|
45
|
+
when 'targeturi'
|
46
|
+
q_terms_array << q_terms_for_url("target_url", v)
|
47
|
+
when 'bodyuri'
|
48
|
+
q_terms_array << q_terms_for_url("body_url", v)
|
49
|
+
when 'bodyexact'
|
50
|
+
# no need to Solr escape value because it's in quotes
|
51
|
+
q_terms_array << "body_chars_exact:\"#{v}\""
|
52
|
+
when 'motivatedby'
|
53
|
+
case
|
54
|
+
when v.include?('#')
|
55
|
+
# we want fragment portion of URL value only, as that
|
56
|
+
# is what is in Solr
|
57
|
+
fq_terms_array << "motivation:#{RSolr.solr_escape(v.sub(/^.*#/, ''))}"
|
58
|
+
when v == "http://www.shared-canvas.org/ns/painting", v == "sc:painting"
|
59
|
+
fq_terms_array << "motivation:painting"
|
60
|
+
else
|
61
|
+
fq_terms_array << "motivation:#{RSolr.solr_escape(v)}"
|
62
|
+
end
|
63
|
+
when 'bodykeyword'
|
64
|
+
solr_params_hash[:kqf] = 'body_chars_exact^3 body_chars_unstem^2 body_chars_stem'
|
65
|
+
solr_params_hash[:kpf] = 'body_chars_exact^15 body_chars_unstem^10 body_chars_stem^5'
|
66
|
+
solr_params_hash[:kpf3] = 'body_chars_exact^9 body_chars_unstem^6 body_chars_stem^3'
|
67
|
+
solr_params_hash[:kpf2] = 'body_chars_exact^6 body_chars_unstem^4 body_chars_stem^2'
|
68
|
+
q_terms_array << '_query_:"{!dismax qf=$kqf pf=$kpf pf3=$kpf3 pf2=$kpf2}' + RSolr.solr_escape(v) + '"'
|
69
|
+
|
70
|
+
# TODO: add'l params to implement:
|
71
|
+
# targetType - fq
|
72
|
+
# bodyType - fq
|
73
|
+
# annotatedAt - fq (deal with time format and wildcard for specificity)
|
74
|
+
# annotatedBy - q (may be incomplete string)
|
75
|
+
end
|
76
|
+
}
|
77
|
+
|
78
|
+
q_terms_array.flatten
|
79
|
+
if q_terms_array.size > 0
|
80
|
+
solr_params_hash[:q] = q_terms_array.join(' AND ')
|
81
|
+
solr_params_hash[:defType] = "lucene"
|
82
|
+
end
|
83
|
+
if fq_terms_array.size > 0
|
84
|
+
solr_params_hash[:fq] = fq_terms_array
|
85
|
+
end
|
86
|
+
|
87
|
+
solr_params_hash
|
88
|
+
|
89
|
+
# TODO: integration tests for
|
90
|
+
# target_url with and without the scheme prefix
|
91
|
+
# target_url with and without fragment
|
92
|
+
# bodykeyword single terms, multiple terms, quoted strings ...
|
93
|
+
|
94
|
+
end # solr_params
|
95
|
+
|
96
|
+
|
97
|
+
# If the url contains a fragment, query terms should only match the exact
|
98
|
+
# url given (with the specific fragment). (i.e. foo.org#bar does not
|
99
|
+
# match foo.org)
|
100
|
+
# If the url does NOT contain a fragment, query terms should match the
|
101
|
+
# url given (no fragment) AND any urls that are the same with a fragment
|
102
|
+
# added. (i.e. foo.org matches foo.org#bar)
|
103
|
+
# @param [String] fieldname the name of the Solr field to be searched with url as a value
|
104
|
+
# @param [String] url the url value sought in the Solr field
|
105
|
+
# @return [Array<String>] an array of query terms to be added to the Solr q argument
|
106
|
+
def self.q_terms_for_url(fieldname, url)
|
107
|
+
q_terms = []
|
108
|
+
q_terms << "#{fieldname}:#{RSolr.solr_escape(url)}"
|
109
|
+
if !url.include? '#'
|
110
|
+
# Note: do NOT Solr escape the # (unnec) or the * (want Solr to view it as wildcard)
|
111
|
+
q_terms << "#{fieldname}:#{RSolr.solr_escape(url)}#*"
|
112
|
+
end
|
113
|
+
q_terms
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
def initialize
|
118
|
+
@rsolr_client = RSolr.connect :url => Triannon.config[:solr_url]
|
119
|
+
@logger = Rails.logger
|
120
|
+
@max_retries = Triannon.config[:max_solr_retries] || 5
|
121
|
+
@base_sleep_seconds = Triannon.config[:base_sleep_seconds] || 1
|
122
|
+
@max_sleep_seconds = Triannon.config[:max_sleep_seconds] || 5
|
123
|
+
end
|
124
|
+
|
125
|
+
# to be called from controller:
|
126
|
+
# 1. converts controller params to solr params
|
127
|
+
# 2. sends request to Solr
|
128
|
+
# 3. converts Solr response object to array of anno graphs
|
129
|
+
# @param [Hash<String => String>] controller_params params from Controller
|
130
|
+
# @return [Array<Triannon::Graph>] array of Triannon::Graph objects,
|
131
|
+
# where each graph object contains a single annotation returned in the response docs
|
132
|
+
def find(controller_params)
|
133
|
+
solr_params = self.class.solr_params(controller_params)
|
134
|
+
solr_response = search(solr_params)
|
135
|
+
anno_graphs_array = self.class.anno_graphs_array(solr_response)
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
protected
|
140
|
+
|
141
|
+
# send params to Solr 'select' with POST, retrying if an error occurs.
|
142
|
+
# See https://github.com/ooyala/retries for info on with_retries.
|
143
|
+
# @param [Hash] solr_params the params to send to Solr
|
144
|
+
# @return RSolr::Response object
|
145
|
+
def search(solr_params = {})
|
146
|
+
handler = Proc.new do |exception, attempt_cnt, total_delay|
|
147
|
+
@logger.debug "#{exception.inspect} on Solr search attempt #{attempt_cnt} for #{solr_params.inspect}"
|
148
|
+
end
|
149
|
+
|
150
|
+
response = nil
|
151
|
+
with_retries(:handler => handler,
|
152
|
+
:max_tries => @max_retries,
|
153
|
+
:base_sleep_seconds => @base_sleep_seconds,
|
154
|
+
:max_sleep_seconds => @max_sleep_seconds) do |attempt|
|
155
|
+
@logger.debug "Solr search attempt #{attempt} for #{solr_params.inspect}"
|
156
|
+
# use POST in case of long params
|
157
|
+
response = @rsolr_client.post 'select', :params => solr_params
|
158
|
+
@logger.info "Successfully searched Solr on attempt #{attempt}"
|
159
|
+
end
|
160
|
+
response
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
end
|
data/config/routes.rb
CHANGED
@@ -1,26 +1,31 @@
|
|
1
1
|
Triannon::Engine.routes.draw do
|
2
|
+
|
2
3
|
root to: 'annotations#index'
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
constraints: lambda { |request|
|
5
|
+
resources :annotations, except: [:update, :edit],
|
6
|
+
# show action must explicitly forbid "new", "iiif" and "oa" as id values; couldn't
|
7
|
+
# figure out how to do it with regexp constraint since beginning and end regex
|
8
|
+
# matchers aren't allowed when enforcing formats for segment (e.g. :id)
|
9
|
+
constraints: lambda { |request|
|
9
10
|
id = request.env["action_dispatch.request.path_parameters"][:id]
|
10
|
-
id !~ /^
|
11
|
-
}
|
12
|
-
|
13
|
-
|
11
|
+
id !~ /^iiif$/ && id !~ /^oa$/ && id !~ /^search$/
|
12
|
+
} do
|
13
|
+
collection do
|
14
|
+
get 'search', to: 'search#find'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
get '/search', to: 'search#find'
|
14
19
|
|
15
20
|
# allow jsonld context in path (only allow iiif or oa as values)
|
16
21
|
# must explicitly forbid "new" as id values; couldn't
|
17
|
-
#
|
18
|
-
#
|
19
|
-
get '/annotations/:jsonld_context/:id(.:format)', to: 'annotations#show',
|
20
|
-
constraints: lambda { |request|
|
22
|
+
# figure out how to do it with regexp constraint since beginning and end regex
|
23
|
+
# matchers aren't allowed when enforcing formats for segment (e.g. :id)
|
24
|
+
get '/annotations/:jsonld_context/:id(.:format)', to: 'annotations#show',
|
25
|
+
constraints: lambda { |request|
|
21
26
|
jsonld_context = request.env["action_dispatch.request.path_parameters"][:jsonld_context]
|
22
27
|
id = request.env["action_dispatch.request.path_parameters"][:id]
|
23
28
|
(jsonld_context =~ /^iiif$/ || jsonld_context =~ /^oa$/ ) && id !~ /^new$/
|
24
29
|
}
|
25
|
-
|
30
|
+
|
26
31
|
end
|
data/lib/triannon.rb
CHANGED
@@ -0,0 +1,59 @@
|
|
1
|
+
module Triannon
|
2
|
+
|
3
|
+
class IIIFAnnoList
|
4
|
+
|
5
|
+
# from http://iiif.io/api/presentation/2/annotationList_frame.json
|
6
|
+
ANNO_LIST_FRAME = JSON.parse('
|
7
|
+
{
|
8
|
+
"@context" : "http://iiif.io/api/presentation/2/context.json",
|
9
|
+
"@type": "sc:AnnotationList",
|
10
|
+
"resources": [{
|
11
|
+
"@type": "oa:Annotation",
|
12
|
+
"on" : [{
|
13
|
+
"@embed" : false
|
14
|
+
}]
|
15
|
+
}]
|
16
|
+
}')
|
17
|
+
|
18
|
+
# from http://iiif.io/api/presentation/2/annotation_frame.json
|
19
|
+
ANNO_FRAME = JSON.parse('
|
20
|
+
{
|
21
|
+
"@context" : "http://iiif.io/api/presentation/2/context.json",
|
22
|
+
"@type": "oa:Annotation",
|
23
|
+
"on" : [{
|
24
|
+
"@embed" : false
|
25
|
+
}]
|
26
|
+
}')
|
27
|
+
|
28
|
+
# Class Methods ----------------------------------------------------------------
|
29
|
+
|
30
|
+
# take an Array of annos as Triannon::Graph objects and return a Hash representation
|
31
|
+
# of IIIF Annotation List
|
32
|
+
# @param [Array<Triannon::Graph>] tgraph_array annotations as Triannon::Graph objects
|
33
|
+
# @return [Hash] IIIF Annotation List as a Hash, containing the annotations in the array
|
34
|
+
def self.anno_list(tgraph_array)
|
35
|
+
if tgraph_array
|
36
|
+
result = {
|
37
|
+
"@context" => Triannon::JsonldContext::IIIF_CONTEXT_URL,
|
38
|
+
"@type" => "sc:AnnotationList",
|
39
|
+
"within" => {"@type" => "sc:Layer", "total" => tgraph_array.size },
|
40
|
+
"resources" => tgraph_array.map { |g|
|
41
|
+
embedded_body = JSON::LD::API.frame(JSON.parse(g.jsonld_iiif), ANNO_FRAME)
|
42
|
+
resource = embedded_body["@graph"]
|
43
|
+
if resource.is_a?(Array) && resource.size == 1
|
44
|
+
resource = resource.first
|
45
|
+
end
|
46
|
+
resource
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
# remove context from each anno as it is redundant
|
51
|
+
result["resources"].each { |anno_hash|
|
52
|
+
anno_hash.delete("@context")
|
53
|
+
}
|
54
|
+
result
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
data/lib/triannon/version.rb
CHANGED
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.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Beer
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2015-
|
13
|
+
date: 2015-04-06 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rails
|
@@ -284,14 +284,14 @@ dependencies:
|
|
284
284
|
requirements:
|
285
285
|
- - "~>"
|
286
286
|
- !ruby/object:Gem::Version
|
287
|
-
version: 1.7.
|
287
|
+
version: 1.7.3
|
288
288
|
type: :development
|
289
289
|
prerelease: false
|
290
290
|
version_requirements: !ruby/object:Gem::Requirement
|
291
291
|
requirements:
|
292
292
|
- - "~>"
|
293
293
|
- !ruby/object:Gem::Version
|
294
|
-
version: 1.7.
|
294
|
+
version: 1.7.3
|
295
295
|
- !ruby/object:Gem::Dependency
|
296
296
|
name: rest-client-components
|
297
297
|
requirement: !ruby/object:Gem::Requirement
|
@@ -335,8 +335,10 @@ files:
|
|
335
335
|
- app/assets/javascripts/triannon/application.js
|
336
336
|
- app/assets/stylesheets/triannon/annotations.css
|
337
337
|
- app/assets/stylesheets/triannon/application.css.scss
|
338
|
+
- app/controllers/concerns/rdf_response_formats.rb
|
338
339
|
- app/controllers/triannon/annotations_controller.rb
|
339
340
|
- app/controllers/triannon/application_controller.rb
|
341
|
+
- app/controllers/triannon/search_controller.rb
|
340
342
|
- app/helpers/triannon/application_helper.rb
|
341
343
|
- app/models/triannon/annotation.rb
|
342
344
|
- app/models/triannon/annotation_ldp.rb
|
@@ -344,6 +346,7 @@ files:
|
|
344
346
|
- app/services/triannon/ldp_to_oa_mapper.rb
|
345
347
|
- app/services/triannon/ldp_writer.rb
|
346
348
|
- app/services/triannon/root_annotation_creator.rb
|
349
|
+
- app/services/triannon/solr_searcher.rb
|
347
350
|
- app/services/triannon/solr_writer.rb
|
348
351
|
- app/views/layouts/triannon/application.html.erb
|
349
352
|
- app/views/triannon/annotations/_form.html.erb
|
@@ -351,6 +354,7 @@ files:
|
|
351
354
|
- app/views/triannon/annotations/index.html.erb
|
352
355
|
- app/views/triannon/annotations/new.html.erb
|
353
356
|
- app/views/triannon/annotations/show.html.erb
|
357
|
+
- app/views/triannon/search/find.html.erb
|
354
358
|
- config/initializers/mime_types.rb
|
355
359
|
- config/routes.rb
|
356
360
|
- config/solr/solr.xml
|
@@ -365,6 +369,7 @@ files:
|
|
365
369
|
- lib/triannon/engine.rb
|
366
370
|
- lib/triannon/error.rb
|
367
371
|
- lib/triannon/graph.rb
|
372
|
+
- lib/triannon/iiif_anno_list.rb
|
368
373
|
- lib/triannon/jsonld_context.rb
|
369
374
|
- lib/triannon/version.rb
|
370
375
|
homepage:
|