bento_search 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +6 -5
- data/app/assets/javascripts/bento_search/ajax_load.js +42 -16
- data/app/assets/stylesheets/bento_search/suggested_styles.css +9 -0
- data/app/controllers/bento_search/search_controller.rb +15 -6
- data/app/helpers/bento_search_helper.rb +24 -8
- data/app/item_decorators/bento_search/no_links.rb +13 -0
- data/app/models/bento_search/openurl_creator.rb +18 -8
- data/app/models/bento_search/registrar.rb +2 -6
- data/app/models/bento_search/result_item.rb +43 -3
- data/app/models/bento_search/results.rb +4 -0
- data/app/models/bento_search/search_engine.rb +25 -23
- data/app/search_engines/bento_search/ebsco_host_engine.rb +42 -17
- data/app/search_engines/bento_search/google_books_engine.rb +2 -0
- data/app/search_engines/bento_search/google_site_search_engine.rb +177 -0
- data/app/search_engines/bento_search/mock_engine.rb +5 -0
- data/app/search_engines/bento_search/primo_engine.rb +23 -2
- data/app/search_engines/bento_search/scopus_engine.rb +4 -1
- data/app/search_engines/bento_search/summon_engine.rb +4 -14
- data/app/search_engines/bento_search/worldcat_sru_dc_engine.rb +293 -0
- data/app/views/bento_search/_std_item.html.erb +4 -5
- data/app/views/bento_search/_wrap_with_count.html.erb +20 -0
- data/app/views/bento_search/search/search.html.erb +15 -1
- data/config/locales/en.yml +6 -4
- data/lib/bento_search/util.rb +13 -0
- data/lib/bento_search/version.rb +1 -1
- data/test/dummy/log/development.log +1 -0
- data/test/dummy/log/test.log +24357 -0
- data/test/functional/bento_search/search_controller_test.rb +39 -0
- data/test/helper/bento_search_helper_test.rb +47 -5
- data/test/unit/ebsco_host_engine_test.rb +15 -0
- data/test/unit/google_books_engine_test.rb +1 -0
- data/test/unit/google_site_search_test.rb +122 -0
- data/test/unit/item_decorators_test.rb +12 -1
- data/test/unit/openurl_creator_test.rb +19 -3
- data/test/unit/primo_engine_test.rb +5 -3
- data/test/unit/result_item_test.rb +36 -1
- data/test/unit/search_engine_test.rb +27 -4
- data/test/unit/worldcat_sru_dc_engine_test.rb +120 -0
- data/test/vcr_cassettes/google_site/basic_smoke_test.yml +254 -0
- data/test/vcr_cassettes/google_site/empty_result_set.yml +53 -0
- data/test/vcr_cassettes/google_site/pagination_object_is_correct_for_actual_page_when_you_ask_for_too_far.yml +260 -0
- data/test/vcr_cassettes/google_site/with_highlighting.yml +265 -0
- data/test/vcr_cassettes/google_site/without_highlighting.yml +267 -0
- data/test/vcr_cassettes/primo/proper_tags_for_snippets.yml +517 -502
- data/test/vcr_cassettes/primo/search_smoke_test.yml +1 -1
- data/test/vcr_cassettes/worldcat_sru_dc/smoke_test.yml +628 -0
- metadata +40 -4
@@ -61,10 +61,10 @@ require 'httpclient'
|
|
61
61
|
# Hard to find docs page on embedding EBSCO limiters (like peer reviewed only "RV Y") in search query:
|
62
62
|
# http://eit.ebscohost.com/Pages/MethodDescription.aspx?service=~/Services/SearchService.asmx&method=Info
|
63
63
|
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
64
|
+
# == Limitations
|
65
|
+
# We do set language of ResultItems based on what ebsco tells us, but ebsoc
|
66
|
+
# seems to tell us 'english' for everything (maybe cause abstract is in
|
67
|
+
# English?). Config variable to tell us to ignore language?
|
68
68
|
class BentoSearch::EbscoHostEngine
|
69
69
|
include BentoSearch::SearchEngine
|
70
70
|
|
@@ -85,7 +85,7 @@ class BentoSearch::EbscoHostEngine
|
|
85
85
|
|
86
86
|
results = BentoSearch::Results.new
|
87
87
|
xml, response, exception = nil, nil, nil
|
88
|
-
|
88
|
+
|
89
89
|
begin
|
90
90
|
response = http_client.get(url)
|
91
91
|
xml = Nokogiri::XML(response.body)
|
@@ -144,12 +144,13 @@ class BentoSearch::EbscoHostEngine
|
|
144
144
|
def sniff_format(xml_node)
|
145
145
|
return nil if xml_node.nil?
|
146
146
|
|
147
|
-
|
147
|
+
|
148
|
+
if xml_node.at_xpath("./jinfo/*") && xml_node.at_xpath("./artinfo/*")
|
149
|
+
"Article"
|
150
|
+
elsif xml_node.at_xpath("./bkinfo/*")
|
148
151
|
"Book"
|
149
152
|
elsif xml_node.at_xpath("./dissinfo/*")
|
150
153
|
:dissertation
|
151
|
-
elsif xml_node.at_xpath("./jinfo/*") && xml_node.at_xpath("./artinfo/*")
|
152
|
-
"Article"
|
153
154
|
elsif xml_node.at_xpath("./jinfo/*")
|
154
155
|
:serial
|
155
156
|
else
|
@@ -172,12 +173,19 @@ class BentoSearch::EbscoHostEngine
|
|
172
173
|
|
173
174
|
components = components.collect {|a| a.titlecase if a}
|
174
175
|
components.uniq! # no need to have the same thing twice
|
175
|
-
|
176
|
-
# some hard-coded cases for better user-displayable string
|
176
|
+
|
177
|
+
# some hard-coded cases for better user-displayable string, and other
|
178
|
+
# normalization.
|
177
179
|
if ["Academic Journal", "Journal"].include?(components.first) && ["Article", "Journal Article"].include?(components.last)
|
178
180
|
return "Journal Article"
|
179
181
|
elsif components.first == "Periodical" && components.length > 1
|
180
182
|
return components.last
|
183
|
+
elsif components.size == 2 && components.first.include?(components.last)
|
184
|
+
# last is strict substring, don't need it
|
185
|
+
return components.first
|
186
|
+
elsif components.size == 2 && components.last.include?(components.first)
|
187
|
+
# first is strict substring, don't need it
|
188
|
+
return components.last
|
181
189
|
end
|
182
190
|
|
183
191
|
|
@@ -191,11 +199,15 @@ class BentoSearch::EbscoHostEngine
|
|
191
199
|
end
|
192
200
|
|
193
201
|
|
194
|
-
#
|
195
|
-
# or what the special chars are. But we know parens are special, can't
|
196
|
-
# escape em, we'll just remove em (should not effect search).
|
202
|
+
# escape or replace special chars to ebsco
|
197
203
|
def ebsco_query_escape(txt)
|
198
|
-
|
204
|
+
# it's unclear if ebsco API actually allows escaping of special chars,
|
205
|
+
# or what the special chars are. But we know parens are special, can't
|
206
|
+
# escape em, we'll just remove em (should not effect search).
|
207
|
+
|
208
|
+
# undocumented but question mark seems to cause a problem for ebsco,
|
209
|
+
# even inside quoted phrases, not sure why.
|
210
|
+
txt.gsub(/[)(\?]/, ' ')
|
199
211
|
end
|
200
212
|
|
201
213
|
# Actually turn the user's query into an EBSCO "AND" boolean query,
|
@@ -208,7 +220,7 @@ class BentoSearch::EbscoHostEngine
|
|
208
220
|
|
209
221
|
# Remove parens in non-phrase-quoted terms
|
210
222
|
terms = terms.collect do |t|
|
211
|
-
|
223
|
+
ebsco_query_escape(t)
|
212
224
|
end
|
213
225
|
|
214
226
|
# Remove boolean operators if they are bare not in a phrase, they'll
|
@@ -233,6 +245,7 @@ class BentoSearch::EbscoHostEngine
|
|
233
245
|
|
234
246
|
query = ebsco_query_prepare args[:query]
|
235
247
|
|
248
|
+
|
236
249
|
# wrap in (FI $query) if fielded search
|
237
250
|
if args[:search_field]
|
238
251
|
query = "(#{args[:search_field]} #{query})"
|
@@ -253,7 +266,7 @@ class BentoSearch::EbscoHostEngine
|
|
253
266
|
configuration.databases.each do |db|
|
254
267
|
url += "&db=#{db}"
|
255
268
|
end
|
256
|
-
|
269
|
+
|
257
270
|
return url
|
258
271
|
end
|
259
272
|
|
@@ -267,8 +280,16 @@ class BentoSearch::EbscoHostEngine
|
|
267
280
|
item.link = get_link(xml_rec)
|
268
281
|
|
269
282
|
item.issn = text_if_present info.at_xpath("./jinfo/issn")
|
270
|
-
|
283
|
+
|
284
|
+
item.journal_title = text_if_present(info.at_xpath("./jinfo/jtl"))
|
271
285
|
item.publisher = text_if_present info.at_xpath("./pubinfo/pub")
|
286
|
+
# if no publisher, but a dissertation institution, use that
|
287
|
+
# as publisher.
|
288
|
+
unless item.publisher
|
289
|
+
item.publisher = text_if_present info.at_xpath("./dissinfo/dissinst")
|
290
|
+
end
|
291
|
+
|
292
|
+
|
272
293
|
# Might have multiple ISBN's in record, just take first for now
|
273
294
|
item.isbn = text_if_present info.at_xpath("./bkinfo/isbn")
|
274
295
|
|
@@ -298,6 +319,10 @@ class BentoSearch::EbscoHostEngine
|
|
298
319
|
item.format = sniff_format info
|
299
320
|
item.format_str = sniff_format_str info
|
300
321
|
|
322
|
+
# Totally unreliable, seems to report english for everything? Maybe
|
323
|
+
# because abstracts are in english? Nevertheless we include for now.
|
324
|
+
item.language_code = text_if_present info.at_xpath("./language/@code")
|
325
|
+
|
301
326
|
|
302
327
|
return item
|
303
328
|
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'multi_json'
|
3
|
+
|
4
|
+
require 'http_client_patch/include_client'
|
5
|
+
require 'httpclient'
|
6
|
+
|
7
|
+
#
|
8
|
+
# An adapter for Google Site Search/Google Custom Search
|
9
|
+
#
|
10
|
+
# I think those are the same thing now, but may get differnet names
|
11
|
+
# depending on whether you are paying for getting for free. The free
|
12
|
+
# version only gives you 100 requests/day courtesy limit for testing.
|
13
|
+
#
|
14
|
+
# Create a custom/site search: http://www.google.com/cse
|
15
|
+
# API docs: https://developers.google.com/custom-search/v1/overview
|
16
|
+
# API console to get API key? https://code.google.com/apis/console/?pli=1#project:183362013039
|
17
|
+
#
|
18
|
+
# == Limitations
|
19
|
+
#
|
20
|
+
# * per-page is max 10, which makes it not too too useful. If you ask for more, you'll get an exception.
|
21
|
+
# * Google only lets you look at first 10 pages. If you ask for more, it won't raise,
|
22
|
+
# it'll just give you the last page google will let you have. pagintion object
|
23
|
+
# in result set will be appropriate for page you actually got though.
|
24
|
+
# * 'abstract' field always filled out with relevant snippets from google api.
|
25
|
+
# * Google API supports custom 'structured data' in your web pages (from microdata and meta tags?)
|
26
|
+
# for custom sorting and limiting and maybe field searching -- but this code
|
27
|
+
# does not currently support that. it could be added as custom config in some way.
|
28
|
+
# * The URL in display form is put in ResultItem#source_title
|
29
|
+
# That should result in it rendering in a reasonable place with standard display
|
30
|
+
# templates.
|
31
|
+
# * Sort: only relevance and date_desc. Custom sorts based on structured data not supported.
|
32
|
+
# * no search fields supported at present. may possibly add later after more
|
33
|
+
# investigation, google api may support both standard intitle etc, as well
|
34
|
+
# as custom attributes added in microdata to your pages.
|
35
|
+
# * ResultItem's will be set to have no OpenURLs, since no useful ones can be constructed.
|
36
|
+
#
|
37
|
+
# == Required config params
|
38
|
+
# [:api_key] api_key from google, get from Google API Console
|
39
|
+
# [:cx] identifier for specific google CSE, get from "Search engine unique ID" in CSE "Control Panel"
|
40
|
+
#
|
41
|
+
# == Optional config params
|
42
|
+
#
|
43
|
+
# [:highlighting] default false. if true, then title, display url, and snippets will
|
44
|
+
# have HTML <b> tags in them, and be html_safe. If false, plain
|
45
|
+
# ascii, but you'll still get snippets.
|
46
|
+
class BentoSearch::GoogleSiteSearchEngine
|
47
|
+
include BentoSearch::SearchEngine
|
48
|
+
|
49
|
+
extend HTTPClientPatch::IncludeClient
|
50
|
+
include_http_client
|
51
|
+
|
52
|
+
def search_implementation(args)
|
53
|
+
results = BentoSearch::Results.new
|
54
|
+
|
55
|
+
url = construct_query(args)
|
56
|
+
|
57
|
+
response = http_client.get(url)
|
58
|
+
|
59
|
+
if response.status != 200
|
60
|
+
results.error ||= {}
|
61
|
+
results.error[:status] = response.status
|
62
|
+
results.error[:response] = response.body
|
63
|
+
return results
|
64
|
+
end
|
65
|
+
|
66
|
+
json = MultiJson.load(response.body)
|
67
|
+
|
68
|
+
results.total_items = json["searchInformation"]["totalResults"].to_i
|
69
|
+
|
70
|
+
(json["items"] || []).each do |json_item|
|
71
|
+
item = BentoSearch::ResultItem.new
|
72
|
+
|
73
|
+
if configuration.highlighting
|
74
|
+
item.title = highlight_normalize json_item["htmlTitle"]
|
75
|
+
item.abstract = highlight_normalize json_item["htmlSnippet"]
|
76
|
+
item.source_title = highlight_normalize json_item["htmlFormattedUrl"]
|
77
|
+
else
|
78
|
+
item.title = json_item["title"]
|
79
|
+
item.abstract = json_item["snippet"]
|
80
|
+
item.source_title = json_item["formattedUrl"]
|
81
|
+
end
|
82
|
+
|
83
|
+
item.link = json_item["link"]
|
84
|
+
|
85
|
+
# we won't bother generating openurls for google hits, not useful
|
86
|
+
item.openurl_disabled = true
|
87
|
+
|
88
|
+
results << item
|
89
|
+
end
|
90
|
+
|
91
|
+
return results
|
92
|
+
end
|
93
|
+
|
94
|
+
# yep, google gives us a 10 max per page.
|
95
|
+
# also only lets us look at first 10 pages, sorry.
|
96
|
+
def max_per_page
|
97
|
+
10
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.required_configuation
|
101
|
+
[:api_key, :cx]
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.default_configuration
|
105
|
+
{
|
106
|
+
:base_url => 'https://www.googleapis.com/customsearch/v1?',
|
107
|
+
:highlighting => true
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
# Google supports relevance, and date sorting. Other kinds of
|
112
|
+
# sorts not generally present. Can be with custom structured data,
|
113
|
+
# but we don't support that. We currently do date sorts as hard sorts,
|
114
|
+
# but could be changed to be biases instead. See:
|
115
|
+
# https://developers.google.com/custom-search/docs/structured_data#page_dates
|
116
|
+
def sort_definitions
|
117
|
+
{
|
118
|
+
"relevance" => {},
|
119
|
+
"date_desc" => {:implementation => "date"},
|
120
|
+
"date_asc" => {:implementation => "date:a"}
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
protected
|
125
|
+
|
126
|
+
# create the URL to the google API based on normalized search args
|
127
|
+
#
|
128
|
+
# If you ask for pagination beyond what google will provide, it
|
129
|
+
# will give you the last page google will allow AND mutate the
|
130
|
+
# args hash passed in to match what you actually got!
|
131
|
+
def construct_query(args)
|
132
|
+
url = "#{configuration.base_url}key=#{CGI.escape configuration.api_key}&cx=#{CGI.escape configuration.cx}"
|
133
|
+
url += "&q=#{CGI.escape args[:query]}"
|
134
|
+
|
135
|
+
|
136
|
+
url += "&num=#{args[:per_page]}" if args[:per_page]
|
137
|
+
|
138
|
+
# google 'start' is 1-based. Google won't let you paginate
|
139
|
+
# past ~10 pages (101 - num). We silently max out there without
|
140
|
+
# raising.
|
141
|
+
if start = args[:start]
|
142
|
+
num = args[:per_page] || 10
|
143
|
+
start = start + 1
|
144
|
+
|
145
|
+
if start > (101 - num)
|
146
|
+
# illegal! fix.
|
147
|
+
start = (101 - num)
|
148
|
+
args[:start] = (start - 1) # ours is zero based
|
149
|
+
args[:page] = (args[:start] / num) + 1
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
url += "&start=#{start}"
|
154
|
+
end
|
155
|
+
|
156
|
+
if (sort = args[:sort]) && (value = sort_definitions[sort].try {|h| h[:implementation]})
|
157
|
+
url += "&sort=#{CGI.escape value}"
|
158
|
+
end
|
159
|
+
|
160
|
+
return url
|
161
|
+
end
|
162
|
+
|
163
|
+
# normalization for strings returned by google as 'html' with query
|
164
|
+
# in context highlighting.
|
165
|
+
#
|
166
|
+
# * change straight <b></b> tags given by google for highlighting
|
167
|
+
# to <b class="bento_search_highight">.
|
168
|
+
# * remove <br> tags that google annoyingly puts in; we'll handle
|
169
|
+
# line wrapping ourselves thanks.
|
170
|
+
# * and mark html_safe
|
171
|
+
def highlight_normalize(str)
|
172
|
+
str.gsub("<b>", '<b class="bento_search_highlight">').
|
173
|
+
gsub("<br>", "").
|
174
|
+
html_safe
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
@@ -15,7 +15,12 @@
|
|
15
15
|
class BentoSearch::MockEngine
|
16
16
|
include BentoSearch::SearchEngine
|
17
17
|
|
18
|
+
# used for testing what the engine received as args
|
19
|
+
attr_accessor :last_args
|
20
|
+
|
18
21
|
def search_implementation(args)
|
22
|
+
self.last_args = args
|
23
|
+
|
19
24
|
results = BentoSearch::Results.new
|
20
25
|
|
21
26
|
if configuration.error
|
@@ -53,7 +53,10 @@ require 'httpclient'
|
|
53
53
|
# == Vendor docs
|
54
54
|
#
|
55
55
|
# http://www.exlibrisgroup.org/display/PrimoOI/Brief+Search
|
56
|
-
|
56
|
+
#
|
57
|
+
# == Notes
|
58
|
+
#
|
59
|
+
# Some but not all hits have language_codes provided by api.
|
57
60
|
class BentoSearch::PrimoEngine
|
58
61
|
include BentoSearch::SearchEngine
|
59
62
|
|
@@ -66,13 +69,29 @@ class BentoSearch::PrimoEngine
|
|
66
69
|
def search_implementation(args)
|
67
70
|
|
68
71
|
url = construct_query(args)
|
72
|
+
|
73
|
+
results = BentoSearch::Results.new
|
69
74
|
|
70
75
|
response = http_client.get(url)
|
76
|
+
if response.status != 200
|
77
|
+
results.error ||= {}
|
78
|
+
results.error[:status] = response.status
|
79
|
+
results.error[:body] = response.body
|
80
|
+
return results
|
81
|
+
end
|
82
|
+
|
83
|
+
|
71
84
|
response_xml = Nokogiri::XML response.body
|
72
85
|
# namespaces really do nobody any good
|
73
86
|
response_xml.remove_namespaces!
|
74
87
|
|
75
|
-
|
88
|
+
|
89
|
+
if error = response_xml.at_xpath("./SEGMENTS/JAGROOT/RESULT/ERROR")
|
90
|
+
results.error ||= {}
|
91
|
+
results.error[:code] = error["CODE"]
|
92
|
+
results.error[:message] = error["MESSAGE"]
|
93
|
+
return results
|
94
|
+
end
|
76
95
|
|
77
96
|
results.total_items = response_xml.at_xpath("./SEGMENTS/JAGROOT/RESULT/DOCSET")["TOTALHITS"].to_i
|
78
97
|
|
@@ -119,6 +138,8 @@ class BentoSearch::PrimoEngine
|
|
119
138
|
item.issn = text_at_xpath doc_xml, "./PrimoNMBib/record/addata/issn"
|
120
139
|
item.isbn = text_at_xpath doc_xml, "./PrimoNMBib/record/addata/isbn"
|
121
140
|
|
141
|
+
item.language_code = text_at_xpath doc_xml, "./PrimoNMBib/record/display/language"
|
142
|
+
|
122
143
|
if (date = text_at_xpath doc_xml, "./PrimoNMBib/record/search/creationdate")
|
123
144
|
item.year = date[0,4] # first four chars
|
124
145
|
end
|
@@ -42,6 +42,8 @@ module BentoSearch
|
|
42
42
|
# TODO: Mention to Scopus: Only one author?
|
43
43
|
# Paging of 50 gets an error, but docs say I should be able to request 200. q
|
44
44
|
#
|
45
|
+
# Scopus response does not seem to include language of hit, even though
|
46
|
+
# api allows you to restrict by language. ask scopus if we're missing something?
|
45
47
|
class ScopusEngine
|
46
48
|
include BentoSearch::SearchEngine
|
47
49
|
|
@@ -62,6 +64,7 @@ module BentoSearch
|
|
62
64
|
"X-ELS-ResourceVersion" => "XOCS",
|
63
65
|
"Accept" => "application/atom+xml"}
|
64
66
|
)
|
67
|
+
|
65
68
|
xml = Nokogiri::XML(response.body)
|
66
69
|
rescue TimeoutError, HTTPClient::ConfigurationError, HTTPClient::BadResponseError, Nokogiri::SyntaxError => e
|
67
70
|
exception = e
|
@@ -81,7 +84,7 @@ module BentoSearch
|
|
81
84
|
xml &&
|
82
85
|
(error_xml = xml.at_xpath("./service-error/status")) &&
|
83
86
|
(node_text(error_xml.at_xpath("./statusCode")) == "INVALID_INPUT") &&
|
84
|
-
(node_text(error_xml.at_xpath("./statusText"))
|
87
|
+
(node_text(error_xml.at_xpath("./statusText")).starts_with? "Result set was empty")
|
85
88
|
)
|
86
89
|
# PROBABLY 0 hit count, although could be something else I'm afraid.
|
87
90
|
results.total_items = 0
|
@@ -79,6 +79,8 @@ require 'summon/transport/headers'
|
|
79
79
|
# headers how summon wants it, see class at
|
80
80
|
# https://github.com/summon/summon.rb/blob/master/lib/summon/transport/headers.rb
|
81
81
|
#
|
82
|
+
# Language provided only in language_str not language_code, all that API gives
|
83
|
+
# us. We could try to reverse lookup from ISO code labels later if we want.
|
82
84
|
class BentoSearch::SummonEngine
|
83
85
|
include BentoSearch::SearchEngine
|
84
86
|
|
@@ -169,6 +171,8 @@ class BentoSearch::SummonEngine
|
|
169
171
|
item.format_str = doc_hash["ContentType"].join(", ")
|
170
172
|
end
|
171
173
|
|
174
|
+
item.language_str = first_if_present doc_hash["Language"]
|
175
|
+
|
172
176
|
if ( configuration.highlighting && configuration.snippets_as_abstract &&
|
173
177
|
doc_hash["Snippet"] && doc_hash["Snippet"].length > 0 )
|
174
178
|
|
@@ -177,8 +181,6 @@ class BentoSearch::SummonEngine
|
|
177
181
|
item.abstract = first_if_present doc_hash["Abstract"]
|
178
182
|
end
|
179
183
|
|
180
|
-
item.extend( SummonOpenurlOverride )
|
181
|
-
|
182
184
|
results << item
|
183
185
|
end
|
184
186
|
|
@@ -381,18 +383,6 @@ class BentoSearch::SummonEngine
|
|
381
383
|
}
|
382
384
|
end
|
383
385
|
|
384
|
-
# Module that we extend our ResultItems with, to over-ride
|
385
|
-
# to_openurl to use a dup of ourselves with title/subtitle
|
386
|
-
# set to raw ones without highlighting markup.
|
387
|
-
module SummonOpenurlOverride
|
388
|
-
def to_openurl
|
389
|
-
dup = self.dup
|
390
|
-
dup.title = self.custom_data["raw_title"]
|
391
|
-
dup.subtitle = self.custom_data["raw_subtitle"]
|
392
|
-
|
393
|
-
dup.to_openurl
|
394
|
-
end
|
395
|
-
end
|
396
386
|
|
397
387
|
|
398
388
|
end
|