umlaut 3.0.0beta4 → 3.0.0beta5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ /* simple_visible_toggle.js. Used for toggling visibility of error information. Can possibly be combined with more powerful expand_contract_toggle.js */
2
+ jQuery(document).ready(function($) {
3
+
4
+ $("a.simple_visible_toggle").live("click", function() {
5
+ $(this).next().toggle();
6
+ });
7
+
8
+ });
@@ -0,0 +1,181 @@
1
+ /* update_html.js: Provide functions to update content on page with background responses from Umlaut. Used by Umlaut itself, as well as by third party callers.*/
2
+ (function($) {
3
+
4
+ function SectionTarget(config) {
5
+ //Add properties from config to ourself
6
+ $.extend(this, config);
7
+
8
+ //Defaults
9
+ if (this.selector == undefined)
10
+ this.selector = "#" + this.umlaut_section_id;
11
+ if (this.position == undefined)
12
+ this.position = "html";
13
+
14
+ }
15
+ //Callback default to no-op function please.
16
+ var noop = function() {};
17
+ SectionTarget.prototype.before_update = noop;
18
+ SectionTarget.prototype.after_update = noop;
19
+ SectionTarget.prototype.complete = noop;
20
+
21
+ SectionTarget.prototype.ensure_placement_destination = function() {
22
+ if ( this.selector == undefined) {
23
+ return null;
24
+ }
25
+
26
+ //Already have it cached?
27
+ if ( this.host_div_element ) {
28
+ return this.host_div_element;
29
+ }
30
+
31
+ var new_div = $('<div class="umlaut" style="display:none"></div>');
32
+ // Find the first thing matched by selector, and call the
33
+ // method specified in "action" string on it, giving it our
34
+ // HTML to replace. This works because our actions are
35
+ // all arguments that will take one method: html, before, after, append,
36
+ // prepend.
37
+ $(this.selector).eq(0)[ this.position ]( new_div );
38
+
39
+ //Cache for later
40
+ this.host_div_element = new_div;
41
+ return this.host_div_element;
42
+ };
43
+
44
+
45
+ // Define an object constructor on the global window object
46
+ // For our UmlautHtmlUpdater object.
47
+ function HtmlUpdater(umlaut_base, context_object) {
48
+ if (context_object == undefined)
49
+ context_object = "";
50
+
51
+ umlaut_base = umlaut_base.replace(/\/$/,'');
52
+ this.umlaut_uri = umlaut_base + '/resolve/partial_html_sections?umlaut.response_format=json&' + context_object;
53
+
54
+ this.section_targets = [];
55
+
56
+ this.add_section_target = function(config) {
57
+ this.section_targets.push( new SectionTarget(config) );
58
+ };
59
+
60
+ //default no-op call-backs
61
+ this.complete = noop;
62
+ this.before_update = noop;
63
+ this.after_update = noop;
64
+
65
+
66
+ //Code for seeing if a URI is same origin or not borrowed from jQuery
67
+ this.is_remote_url = function(url) {
68
+ var regexp = /^(\w+:)?\/\/([^\/?#]+)/;
69
+ var parts = regexp.exec( url );
70
+ return (parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host));
71
+ }
72
+
73
+ this.update = function() {
74
+ // Need to capture because we won't have 'this' inside the ajax
75
+ // success handler.
76
+ var myself = this;
77
+ var dataType = this.is_remote_url( this.umlaut_uri ) ? "jsonp" : "json";
78
+ $.ajax({
79
+ url: myself.umlaut_uri,
80
+ dataType: dataType,
81
+ jsonp: "umlaut.jsonp",
82
+ error: function() {
83
+ $.error("Problem loading background elements.");
84
+ },
85
+ success: function(umlaut_response) {
86
+ for (var i = 0; i < myself.section_targets.length; i++) {
87
+ var section_target = myself.section_targets[i];
88
+
89
+ var umlaut_html_section = myself.find_umlaut_response_section(umlaut_response, section_target.umlaut_section_id);
90
+
91
+ if (umlaut_html_section == undefined) {
92
+ continue;
93
+ }
94
+ var count = null;
95
+ if (typeof umlaut_html_section.response_count != "undefined") {
96
+ count = parseInt(umlaut_html_section.response_count.value);
97
+ }
98
+ var existing_element = section_target.ensure_placement_destination();
99
+ var new_element = $('<div class="umlaut" style="display:none" class="' + section_target.umlaut_section_id +'"></div>');
100
+ new_element.html(umlaut_html_section.html_content);
101
+
102
+
103
+ var should_continue = section_target.before_update(new_element, count, section_target);
104
+ if (should_continue != false) {
105
+ should_continue = myself.before_update(new_element, count, section_target);
106
+ }
107
+
108
+ if (should_continue != false) {
109
+ existing_element.replaceWith(new_element);
110
+
111
+ section_target.host_div_element = new_element;
112
+
113
+ new_element.show();
114
+
115
+ section_target.after_update(new_element, count, section_target);
116
+ myself.after_update(new_element, count, section_target);
117
+
118
+ }
119
+ }
120
+
121
+ //Do we need to update again?
122
+ if (umlaut_response.partial_html_sections.in_progress) {
123
+ //Fix our update URI to be the one umlaut suggests
124
+ //Except strip out the umlaut.jsonp parameter, jquery is
125
+ //going to add that back in as desired.
126
+ myself.umlaut_uri =
127
+ umlaut_response.partial_html_sections.in_progress.refresh_url.replace(/[?;&]umlaut\.jsonp=[^;&]+/, '');
128
+
129
+
130
+ var refresh_seconds =
131
+ umlaut_response.partial_html_sections.in_progress.requested_wait_seconds;
132
+ window.setTimeout(function() { myself.update(); }, refresh_seconds * 1000);
133
+
134
+ } else {
135
+ myself.complete();
136
+ for (var i = 0; i < myself.section_targets.length; i++) {
137
+ var section_target = myself.section_targets[i];
138
+ section_target.complete(section_target);
139
+ }
140
+ }
141
+
142
+ }
143
+ });
144
+ };
145
+ this.find_umlaut_response_section = function(response, id) {
146
+ return $.grep(response.partial_html_sections.html_section, function(section) {
147
+ return section.id == id;
148
+ })[0];
149
+ };
150
+
151
+ };
152
+
153
+ //Put it in a global object, leave space for other things in "Umlaut" later.
154
+ if (window.Umlaut == undefined)
155
+ window.Umlaut = new Object();
156
+ window.Umlaut.HtmlUpdater = HtmlUpdater;
157
+
158
+ /* LEGACY Loader was recommended for loading Umlaut JS behaviors
159
+ in an external page, for JQuery Content Utility.
160
+
161
+ var loader = new Umlaut.Loader();
162
+ loader.load();
163
+
164
+ We will provide just enough code to keep that from
165
+ error'ing (and halting js execution), although at present it does not
166
+ actually load the JS behaviors using new style, app wont' have
167
+ JS behaviors. */
168
+
169
+ window.Umlaut.Loader = function() {
170
+ this.load = function(option_list) {
171
+ // log problem in browsers that support it.
172
+ if (typeof console != "undefined" && typeof console.log != "undefined") {
173
+ console.log("WARN: Umlaut.Loader no longer supported in Umlaut 3.x, you may have not loaded Umlaut JS Behaviors as desired. See Umlaut documentation for new way.");
174
+
175
+ }
176
+ }
177
+ }
178
+
179
+
180
+ })(jQuery);
181
+
@@ -1,19 +1,27 @@
1
- /* expand_contract_toggle.js: Support for show more/hide more in lists of umlaut content. */
1
+ /* expand_contract_toggle.js: Support for show more/hide more in lists of umlaut content.
2
+
3
+ The JS needs to swap out the image for expand/contract toggle. AND we need
4
+ the URL it swaps in to be an ABSOLUTE url when we're using partial html
5
+ widget.
6
+
7
+ So we swap in a non-fingerprinted URL, even if the original was asset
8
+ pipline fingerprinted. sorry, best way to make it work!
9
+ */
2
10
  jQuery(document).ready(function($) {
3
11
 
4
12
  $(".expand_contract_toggle").live("click", function() {
5
13
  var content = $(this).next(".expand_contract_content");
6
14
  var icon = $(this).parent().find('img.toggle_icon');
7
15
 
8
- if (content.is(":visible")) {
9
- icon.attr("src", '<%= image_path("list_closed.png") %>');
16
+ if (content.is(":visible")) {
17
+ icon.attr("src", icon.attr("src").replace(/list_open[^.]*\.png/, "list_closed.png"));
10
18
  $(this).find(".expand_contract_action_label").text("Show ");
11
19
 
12
20
  content.hide();
13
21
 
14
22
  }
15
23
  else {
16
- icon.attr("src", '<%= image_path("list_open.png") %>');
24
+ icon.attr("src", icon.attr("src").replace(/list_closed[^.]*\.png/, "list_open.png"));
17
25
  $(this).find(".expand_contract_action_label").text("Hide ");
18
26
  content.show();
19
27
  }
@@ -58,7 +58,10 @@
58
58
  };
59
59
 
60
60
  //default no-op call-backs
61
- this.complete = function() {};
61
+ this.complete = noop;
62
+ this.before_update = noop;
63
+ this.after_update = noop;
64
+
62
65
 
63
66
  //Code for seeing if a URI is same origin or not borrowed from jQuery
64
67
  this.is_remote_url = function(url) {
@@ -98,6 +101,9 @@
98
101
 
99
102
 
100
103
  var should_continue = section_target.before_update(new_element, count, section_target);
104
+ if (should_continue != false) {
105
+ should_continue = myself.before_update(new_element, count, section_target);
106
+ }
101
107
 
102
108
  if (should_continue != false) {
103
109
  existing_element.replaceWith(new_element);
@@ -106,7 +112,8 @@
106
112
 
107
113
  new_element.show();
108
114
 
109
- section_target.after_update(new_element, count, section_target)
115
+ section_target.after_update(new_element, count, section_target);
116
+ myself.after_update(new_element, count, section_target);
110
117
 
111
118
  }
112
119
  }
@@ -163,7 +170,8 @@
163
170
  this.load = function(option_list) {
164
171
  // log problem in browsers that support it.
165
172
  if (typeof console != "undefined" && typeof console.log != "undefined") {
166
- console.log("WARN: Umlaut.Loader no longer supported in Umlaut 3.x, you have not loaded Umlaut JS Behaviors. See Umlaut documentation for new way.");
173
+ console.log("WARN: Umlaut.Loader no longer supported in Umlaut 3.x, you may have not loaded Umlaut JS Behaviors as desired. See Umlaut documentation for new way.");
174
+
167
175
  }
168
176
  }
169
177
  }
@@ -158,9 +158,7 @@ module ResolveHelper
158
158
  # <li>Item Number: <%= index %>: <%= item.title %></li>
159
159
  # <% end %>
160
160
  def list_with_limit(id, list, options = {}, &block)
161
-
162
-
163
-
161
+
164
162
  # backwards compatible to when third argument was just a number
165
163
  # for limit.
166
164
  options = {:limit => options} unless options.kind_of?(Hash)
@@ -172,17 +170,17 @@ module ResolveHelper
172
170
  content = "".html_safe
173
171
  content <<
174
172
  content_tag(:ul, :class => options[:ul_class]) do
175
- list.enum_for(:each_with_index).collect do |item, index|
176
- capture(item, index, &block) unless list.length > options[:limit] && index >= options[:limit]-2
173
+ list.slice(0, options[:limit]).enum_for(:each_with_index).collect do |item, index|
174
+ yield(item, index)
177
175
  end.join(" \n ").html_safe
178
176
  end
179
177
 
180
178
  if (list.length > options[:limit] )
181
179
  content <<
182
- expand_contract_section("#{list.length - options[:limit] + 1} more", id) do
180
+ expand_contract_section("#{list.length - options[:limit] } more", id) do
183
181
  content_tag(:ul, :class=>options[:ul_class]) do
184
- list.slice(options[:limit]-1..list.length-1).enum_for(:each_with_index).each do |item, index|
185
- yield(item, index)
182
+ list.slice(options[:limit]..list.length-1).enum_for(:each_with_index).each do |item, index|
183
+ yield(item, index + options[:limit])
186
184
  end.join(" \n ").html_safe
187
185
  end
188
186
  end
@@ -1,8 +1,8 @@
1
1
  <%
2
2
 
3
- #debugger
4
3
  all_responses = fulltext
5
-
4
+
5
+
6
6
  (title_level_fulltxt, fulltxt) =
7
7
  all_responses.partition {|i| i.view_data[:coverage_checked] == false || i.view_data[:can_link_to_article] == false}
8
8
  # order em with full fulltxt before title_level_fulltxt
@@ -15,7 +15,6 @@
15
15
 
16
16
 
17
17
  <div class="umlaut_section_content">
18
-
19
18
  <%= list_with_limit('umlaut_fulltext',
20
19
  all_responses,
21
20
  :ul_class => "response_list",
@@ -36,7 +35,6 @@
36
35
  </div>
37
36
  <ul class="response_list">
38
37
  <% end %>
39
-
40
38
  <%= render(:partial => "standard_response_item", :object=> item) %>
41
39
  <% end %>
42
40
 
@@ -0,0 +1,327 @@
1
+ require 'nokogiri'
2
+ require 'open-uri'
3
+ require 'base64'
4
+ require 'marc'
5
+
6
+ # Searches a Blacklight with the cql extension installed.
7
+ #
8
+ #
9
+ # Params include:
10
+ # [base_url]
11
+ # required. Complete URL to catalog.atom action. Eg "https://blacklight.mse.jhu.edu/catalog.atom"
12
+ # [bl_fields]
13
+ # required with at least some entries if you want this to do anything. Describe the names of given semantic fields in your BL instance.
14
+ # * issn
15
+ # * isbn
16
+ # * lccn
17
+ # * oclcnum
18
+ # * id (defaults to 'id')
19
+ # * title
20
+ # * author
21
+ # * serials_limit_clause => not an index name, full URL clause for a limit to apply to known serials searches, for instance "f[format][]=Serial"
22
+ # [identifier_search]
23
+ # Do catalog search on issn/isbn/oclcnum/lccn/bibId. Default true.
24
+ # [keyword_search]
25
+ # Do catalog search on title/author keywords where applicable. Generally only used when identifier_search finds no hits, if identifier_search is on. Default true.
26
+ # [keyword_per_page]
27
+ # How many records to fetch from blacklight when doing keyword searches.
28
+ # [exclude_holdings]
29
+ # Can be used to exclude certain 'dummy' holdings that have certain collection, location, or other values. Eg:
30
+ # exclude_holdings:
31
+ # collection_str:
32
+ # - World Wide Web
33
+ # - Internet
34
+ # [rft_id_bibnum_prefixes]
35
+ # Array of URI prefixes in an rft_id that indicate that the actual solr id comes next. For instance, if your blacklight will send "http://blacklight.com/catalog/some_id" in an rft_id, then include "http://blacklight.com/catalog/". Optional.
36
+ class Blacklight < Service
37
+ required_config_params :base_url, :display_name
38
+ attr_reader :base_url, :cql_search_field
39
+ attr_reader :bl_fields, :issn
40
+
41
+ include UmlautHttp
42
+ include MetadataHelper
43
+ include MarcHelper
44
+ include XmlSchemaHelper
45
+
46
+ def initialize(config)
47
+ # defaults
48
+ # If you are sending an OpenURL from a library service, you may
49
+ # have the HIP bibnum, and include it in the OpenURL as, eg.
50
+ # rft_id=http://catalog.library.jhu.edu/bib/343434 (except URL-encoded)
51
+ # Then you'd set rft_id_bibnum_prefix to http://catalog.library.jhu.edu/bib/
52
+ @rft_id_bibnum_prefixes = []
53
+ @cql_search_field = "cql"
54
+ @keyword_per_page = 10
55
+ @identifier_search = true
56
+ @keyword_search = true
57
+ @link_to_search = true
58
+ super(config)
59
+ @bl_fields = { "id" => "id "}.merge(@bl_fields)
60
+ end
61
+
62
+ # Standard method, used by background service updater. See Service docs.
63
+ def service_types_generated
64
+ types = [ ServiceTypeValue[:fulltext], ServiceTypeValue[:holding], ServiceTypeValue[:table_of_contents], ServiceTypeValue[:relevant_link] ]
65
+
66
+ return types
67
+ end
68
+
69
+
70
+ def handle(request)
71
+ ids_processed = []
72
+ holdings_added = 0
73
+
74
+ if (@identifier_search && url = blacklight_precise_search_url(request) )
75
+ doc = Nokogiri::XML( http_fetch(url).body )
76
+
77
+ ids_processed.concat( bib_ids_from_atom_entries( doc.xpath("atom:feed/atom:entry", xml_ns) ) )
78
+
79
+ # namespaces make xpath harder than it should be, but css
80
+ # selector still easy, thanks nokogiri! Grab the marc from our
81
+ # results.
82
+ marc_matches = doc.xpath("atom:feed/atom:entry/atom:content[@type='application/marc']", xml_ns).collect do |encoded_marc21|
83
+ MARC::Reader.decode( Base64.decode64(encoded_marc21.text) )
84
+ end
85
+
86
+ add_856_links(request, marc_matches )
87
+
88
+ # Got to make a second fetch for dlf_expanded info, cause BL doens't
89
+ # (yet) let us ask for more than one at once
90
+ holdings_url = blacklight_precise_search_url( request, "dlf_expanded" )
91
+ holdings_added += add_holdings( holdings_url ) if holdings_url
92
+ end
93
+ #keyword search.
94
+ if (@keyword_search &&
95
+ url = blacklight_keyword_search_url(request))
96
+
97
+ doc = Nokogiri::XML( http_fetch(url).body )
98
+ # filter out matches whose titles don't really match at all, or
99
+ # which have already been seen in identifier search.
100
+ entries = filter_keyword_entries( doc.xpath("atom:feed/atom:entry", xml_ns) , :exclude_ids => ids_processed, :remove_subtitle => (! title_is_serial?(request.referent)) )
101
+
102
+ marc_by_atom_id = {}
103
+
104
+ # Grab the marc from our entries. Important not to do a // xpath
105
+ # search, or we'll wind up matching parent elements not actually
106
+ # included in our 'entries' list.
107
+ marc_matches = entries.xpath("atom:content[@type='application/marc']", xml_ns).collect do |encoded_marc21|
108
+ marc = MARC::Reader.decode( Base64.decode64(encoded_marc21.text) )
109
+
110
+ marc_by_atom_id[ encoded_marc21.at_xpath("ancestor::atom:entry/atom:id/text()", xml_ns).to_s ] = marc
111
+
112
+ marc
113
+ end
114
+
115
+ # We've filtered out those we consider just plain bad
116
+ # matches, everything else we're going to call
117
+ # an approximate match. Sort so that those with
118
+ # a date close to our request date are first.
119
+ if ( year = get_year(request.referent))
120
+ marc_matches = marc_matches.partition {|marc| get_years(marc).include?( year )}.flatten
121
+ end
122
+ # And add in the 856's
123
+ add_856_links(request, marc_matches, :match_reliability => ServiceResponse::MatchUnsure)
124
+
125
+ # Fetch and add in the holdings
126
+ url = blacklight_url_for_ids(bib_ids_from_atom_entries(entries))
127
+
128
+ holdings_added += add_holdings( url, :match_reliability => ServiceResponse::MatchUnsure, :marc_data => marc_by_atom_id ) if url
129
+
130
+ if (@link_to_search && holdings_added ==0)
131
+ hit_count = doc.at_xpath("atom:feed/opensearch:totalResults/text()", xml_ns).to_s.to_i
132
+ html_result_url = doc.at_xpath("atom:feed/atom:link[@rel='alternate'][@type='text/html']/attribute::href", xml_ns).to_s
133
+
134
+ if hit_count > 0
135
+ request.add_service_response(
136
+ :service => self,
137
+ :source_name => @display_name,
138
+ :count => hit_count,
139
+ :display_text => "#{hit_count} possible #{case; when hit_count > 1 ; 'matches' ; else; 'match' ; end} in #{@display_name}",
140
+ :url => html_result_url,
141
+ :service_type_value => :holding_search )
142
+ end
143
+ end
144
+ end
145
+
146
+
147
+
148
+
149
+ return request.dispatched(self, true)
150
+
151
+
152
+ end
153
+
154
+ # Send a CQL request for any identifiers present.
155
+ # Ask for for an atom response with embedded marc21 back.
156
+ def blacklight_precise_search_url(request, format = "marc")
157
+ # Add search clauses for our identifiers, if we have them and have a configured search field for them.
158
+ clauses = []
159
+ added = []
160
+ ["lccn", "isbn", "oclcnum"].each do |key|
161
+ if bl_fields[key] && request.referent.send(key)
162
+ clauses.push( "#{bl_fields[key]} = \"#{request.referent.send(key)}\"")
163
+ added << key
164
+ end
165
+ end
166
+ # Only add ISSN if we don't have an ISBN, reduces false matches
167
+ if ( !added.include?("isbn") &&
168
+ bl_fields["issn"] &&
169
+ request.referent.issn)
170
+ clauses.push("#{bl_fields["issn"]} = \"#{request.referent.issn}\"")
171
+ end
172
+
173
+
174
+ # Add Solr document identifier if we can get one from the URL
175
+
176
+ if (id = get_solr_id(request.referent))
177
+ clauses.push("#{bl_fields['id']} = \"#{id}\"")
178
+ end
179
+
180
+ # if we have nothing, we can do no search.
181
+ return nil if clauses.length == 0
182
+
183
+ cql = clauses.join(" OR ")
184
+
185
+ return base_url + "?search_field=#{@cql_search_field}&content_format=#{format}&q=#{CGI.escape(cql)}"
186
+ end
187
+
188
+ # Construct a CQL search against blacklight for author and title,
189
+ # possibly with serial limit. Ask for Atom with embedded MARC back.
190
+ def blacklight_keyword_search_url(request, options = {})
191
+ options[:format] ||= "atom"
192
+ options[:content_format] ||= "marc"
193
+
194
+ clauses = []
195
+
196
+ # We need both title and author to search keyword style, or
197
+ # we get too many false positives. Except serials we'll do
198
+ # title only. sigh, logic tree.
199
+ title = get_search_title(request.referent)
200
+ author = get_top_level_creator(request.referent)
201
+ return nil unless title && (author || (@bl_fields["serials_limit_clause"] && title_is_serial?(request.referent)))
202
+ # phrase search for title, just raw dismax for author
203
+ # Embed quotes inside the quoted value, need to backslash-quote for CQL,
204
+ # and backslash the backslashes for ruby literal.
205
+ clauses.push("#{@bl_fields["title"]} = \"\\\"#{title}\\\"\"")
206
+ clauses.push("#{@bl_fields["author"]} = \"#{author}\"") if author
207
+
208
+
209
+
210
+ url = base_url + "?search_field=#{@cql_search_field}&content_format=#{options[:content_format]}&q=#{CGI.escape(clauses.join(" AND "))}"
211
+
212
+ if (@bl_fields["serials_limit_clause"] &&
213
+ title_is_serial?(request.referent))
214
+ url += "&" + @bl_fields["serials_limit_clause"]
215
+ end
216
+
217
+ return url
218
+ end
219
+
220
+ # Takes a url that will return atom response of dlf_expanded content.
221
+ # Adds Umlaut "holding" ServiceResponses for dlf_expanded, as appropriate.
222
+ # Returns number of holdings added.
223
+ def add_holdings(holdings_url, options = {})
224
+ options[:match_reliability] ||= ServiceResponse::MatchExact
225
+ options[:marc_data] ||= {}
226
+
227
+ atom = Nokogiri::XML( http_fetch(holdings_url).body )
228
+ content_entries = atom.search("/atom:feed/atom:entry/atom:content", xml_ns)
229
+
230
+ # For each atom entry, find the dlf_expanded record. For each dlf_expanded
231
+ # record, take all of it's holdingsrec's if it has them, or all of it's
232
+ # items if it doesn't, and add them to list. We wind up with a list
233
+ # of mixed holdingsrec's and items.
234
+ holdings_xml = content_entries.collect do |dlf_expanded|
235
+ copies = dlf_expanded.xpath("dlf:record/dlf:holdings/dlf:holdingset/dlf:holdingsrec", xml_ns)
236
+ copies.length > 0 ? copies : dlf_expanded.xpath("dlf:record/dlf:items/dlf:item", xml_ns)
237
+ end.flatten
238
+
239
+ service_data = holdings_xml.collect do | xml_metadata |
240
+ atom_entry = xml_metadata.at_xpath("ancestor::atom:entry", xml_ns)
241
+ atom_id = atom_entry.at_xpath("atom:id/text()", xml_ns).to_s
242
+
243
+ edition_str = edition_statement(options[:marc_data][atom_id])
244
+ url = atom_entry.at_xpath("atom:link[@rel='alternate'][@type='text/html']/attribute::href", xml_ns).to_s
245
+
246
+ xml_to_holdings( xml_metadata ).merge(
247
+ :service => self,
248
+ :match_reliability => options[:match_reliability],
249
+ :edition_str => edition_str,
250
+ :url => url
251
+ )
252
+ end
253
+
254
+ # strip out holdings that aren't really holdings
255
+ service_data.delete_if do |data|
256
+ @exclude_holdings.collect do |key, values|
257
+ values.include?(data[key.to_sym])
258
+ end.include?(true)
259
+ end
260
+
261
+ # Sort by "collection"
262
+ service_data.sort do |a, b|
263
+ a[:collection_str] <=> b[:collection_str]
264
+ end
265
+
266
+ service_data.each do |data|
267
+ request.add_service_response(data.merge(:service => self, :service_type_value =>"holding"))
268
+ end
269
+
270
+ return service_data.length
271
+ end
272
+
273
+ def filter_keyword_entries(atom_entries, options = {})
274
+ options[:exclude_ids] ||= []
275
+ options[:remove_subtitle] ||= true
276
+ request_title_forms = [
277
+ raw_search_title(request.referent).downcase,
278
+ normalize_title( raw_search_title(request.referent) )
279
+ ]
280
+ request_title_forms << normalize_title( raw_search_title(request.referent), :remove_subtitle => true) if options[:remove_subtitle]
281
+ request_title_forms.compact
282
+
283
+ # Only keep entries with title match, and that aren't in the
284
+ # exclude_ids list.
285
+ good_entries = atom_entries.find_all do |atom_entry|
286
+ title = atom_entry.xpath("atom:title/text()", xml_ns).to_s
287
+
288
+ entry_title_forms = [
289
+ title.downcase,
290
+ normalize_title(title)
291
+ ]
292
+ entry_title_forms << normalize_title(title, :remove_subtitle=>true) if options[:remove_subtitle]
293
+ entry_title_forms.compact
294
+
295
+ ((entry_title_forms & request_title_forms).length > 0 &&
296
+ (bib_ids_from_atom_entries(atom_entry) & options[:exclude_ids]).length == 0)
297
+ end
298
+ return Nokogiri::XML::NodeSet.new( atom_entries.document, good_entries)
299
+ end
300
+
301
+ def bib_ids_from_atom_entries(entries)
302
+ entries.xpath("atom:id/text()", xml_ns).to_a.collect do |atom_id|
303
+ atom_id.to_s =~ /([^\/]+)$/
304
+ $1
305
+ end.compact
306
+ end
307
+
308
+ def blacklight_url_for_ids(ids, format="dlf_expanded")
309
+ return nil unless ids.length > 0
310
+
311
+ return base_url + "?search_field=#{@cql_search_field}&content_format=#{format}&q=" + CGI.escape("#{@bl_fields["id"]} any \"#{ids.join(" ")}\"")
312
+ end
313
+
314
+
315
+ def get_solr_id(rft)
316
+ rft.identifiers.each do |id|
317
+ @rft_id_bibnum_prefixes.each do |prefix|
318
+ if id[0, prefix.length] == prefix
319
+ return id[prefix.length, id.length]
320
+ end
321
+ end
322
+ end
323
+
324
+ return nil
325
+ end
326
+
327
+ end
@@ -33,6 +33,9 @@
33
33
  class Scopus < Service
34
34
  require 'open-uri'
35
35
  require 'multi_json'
36
+
37
+ include ActionView::Helpers::SanitizeHelper
38
+
36
39
  include MetadataHelper
37
40
  include UmlautHttp
38
41
 
@@ -237,7 +240,8 @@ class Scopus < Service
237
240
  request.add_service_response(
238
241
  :service=>self,
239
242
  :display_text => "Abstract from #{@display_name}",
240
- :content => first_hit["abstract"],
243
+ :content => sanitize(first_hit["abstract"]),
244
+ :content_html_safe => true,
241
245
  :url => detail_url(first_hit),
242
246
  :service_type_value => :abstract)
243
247
  end
@@ -0,0 +1,164 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Umlaut
3
+ # Class to inject Umlaut routes, design copied from Blacklight project.
4
+ # you would do a:
5
+ # Umlaut::Routes.new(self, optional_args).draw
6
+ # in local
7
+ # app routes.rb, that line is generated into local app by Umlaut generator.
8
+ # options include :only and :except to limit what route groups are generated.
9
+ class Routes
10
+
11
+ def initialize(router, options ={})
12
+ @router = router
13
+ @options = options
14
+ end
15
+
16
+ def draw
17
+ route_sets.each do |r|
18
+ self.send(r)
19
+ end
20
+ end
21
+
22
+ protected
23
+
24
+ def add_routes &blk
25
+ @router.instance_exec(@options, &blk)
26
+ end
27
+
28
+ def route_sets
29
+ # :admin is not included by default, needs to be turned on.
30
+ (@options[:only] || default_route_sets) - (@options[:except] || []) + (@options[:admin] == true ? [:admin] : [])
31
+ end
32
+
33
+ def default_route_sets
34
+ [:root, :permalinks, :a_z, :resolve, :open_search, :link_router, :export_email, :resources, :search, :javascript]
35
+ end
36
+
37
+ module RouteSets
38
+ # for now include root generation in Umlaut auto-generation
39
+ def root
40
+ add_routes do |options|
41
+ root :to => "search#index"
42
+ end
43
+ end
44
+
45
+ def permalinks
46
+ add_routes do |options|
47
+ match 'go/:id' => 'store#index'
48
+ end
49
+ end
50
+
51
+ # some special direct links to A-Z type searches, including
52
+ # legacy redirects for SFX-style urls, to catch any bookmarks.
53
+ def a_z
54
+ add_routes do |options|
55
+ # Special one for alpha list
56
+ match 'journal_list/:id/:page' => 'search#journal_list', :defaults => { :page => 1, :id => 'A' }
57
+
58
+
59
+ # Catch redirected from SFX A-Z and citation linker urls
60
+ # v2 A-Z links redirected to umlaut, point to journal_list
61
+ # code in journal_list filter picks out SFX URL vars for
62
+ # letter.
63
+ match '/resolve/azlist/default' => 'search#journal_list', :page => 1, :id => 'A'
64
+
65
+ # SFX v3 A-Z list url format
66
+ match 'resolve/az' => 'search#journal_list', :page => 1, :id => 'A'
67
+ end
68
+ end
69
+
70
+ # This is a legacy wild controller route that's not recommended for RESTful applications.
71
+ # Note: This route will make all actions in every controller accessible via GET requests.
72
+ # match ':controller(/:action(/:id(.:format)))'
73
+
74
+ def resolve
75
+ add_routes do |options|
76
+ # ResolveController still uses rails 2.0 style 'wildcard' routes,
77
+ # TODO tighten this up to only match what oughta be matched.
78
+ # Note: This route will make all actions in this controller accessible via GET requests.
79
+
80
+ match 'resolve(/:action(/:id(.:format)))' => "resolve"
81
+ end
82
+ end
83
+
84
+ def open_search
85
+ add_routes do |options|
86
+ # OpenSearchController still uses rails 2.0 style 'wildcard' routes,
87
+ # TODO tighten this up to only match what oughta be matched.
88
+ # Note: This route will make all actions in this controller accessible via GET requests.
89
+
90
+ match 'open_search(/:action(/:id(.:format)))' => "open_search"
91
+ end
92
+ end
93
+
94
+ def link_router
95
+ add_routes do |options|
96
+ # LinkRouterController still uses rails 2.0 style 'wildcard' routes,
97
+ # TODO tighten this up to only match what oughta be matched.
98
+ # Note: This route will make all actions in this controller accessible via GET requests.
99
+
100
+ match 'link_router(/:action(/:id(.:format)))' => "link_router"
101
+ end
102
+ end
103
+
104
+ def export_email
105
+ add_routes do |options|
106
+ # ExportEmailController still uses rails 2.0 style 'wildcard' routes,
107
+ # TODO tighten this up to only match what oughta be matched.
108
+ # Note: This route will make all actions in this controller accessible via GET requests.
109
+
110
+ match 'export_email(/:action(/:id(.:format)))' => "export_email"
111
+ end
112
+ end
113
+
114
+ def resources
115
+ add_routes do |options|
116
+ # ResourceController still uses rails 2.0 style 'wildcard' routes,
117
+ # TODO tighten this up to only match what oughta be matched.
118
+ # Note: This route will make all actions in this controller accessible via GET requests.
119
+
120
+ match 'resource(/:action(/:id(.:format)))' => "resource"
121
+ end
122
+ end
123
+
124
+ def search
125
+ add_routes do |options|
126
+ # SearchController still uses rails 2.0 style 'wildcard' routes,
127
+ # TODO tighten this up to only match what oughta be matched.
128
+ # Note: This route will make all actions in this controller accessible via GET requests.
129
+
130
+ match 'search(/:action(/:id(.:format)))' => "search"
131
+ end
132
+ end
133
+
134
+ def javascript
135
+ add_routes do |options|
136
+ # Legacy location for update_html.js used by JQuery Content Utility
137
+ # to embed JS on external sites. Redirect to new location.
138
+ # Intentionally non-fingerprinted, most efficient thing
139
+ # we can do in this case is let the web server take care
140
+ # of Last-modified-by etc headers.
141
+ match 'javascripts/jquery/umlaut/update_html.js' => redirect("/assets/umlaut/update_html.js", :status => 301)
142
+
143
+ # The loader doens't work _exactly_ like the new umlaut-ui.js, but
144
+ # it's close enough that it'll work better redirecting than just
145
+ # 404'ing.
146
+ match 'js_helper/loader' => redirect("/assets/umlaut_ui.js")
147
+
148
+
149
+ match 'images/spinner.gif' => redirect("/assets/spinner.gif")
150
+ end
151
+ end
152
+
153
+ def admin
154
+ add_routes do |options|
155
+ namespace "admin" do
156
+ match 'service_errors(/:service_id)' => "service_errors#index"
157
+ end
158
+ end
159
+ end
160
+
161
+ end
162
+ include RouteSets
163
+ end
164
+ end
data/lib/umlaut/routes.rb CHANGED
@@ -140,6 +140,12 @@ module Umlaut
140
140
  # of Last-modified-by etc headers.
141
141
  match 'javascripts/jquery/umlaut/update_html.js' => redirect("/assets/umlaut/update_html.js", :status => 301)
142
142
 
143
+ # The loader doens't work _exactly_ like the new umlaut-ui.js, but
144
+ # it's close enough that it'll work better redirecting than just
145
+ # 404'ing.
146
+ match 'js_helper/loader' => redirect("/assets/umlaut_ui.js")
147
+
148
+
143
149
  match 'images/spinner.gif' => redirect("/assets/spinner.gif")
144
150
  end
145
151
  end
@@ -1,3 +1,3 @@
1
1
  module Umlaut
2
- VERSION = "3.0.0beta4"
2
+ VERSION = "3.0.0beta5"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: umlaut
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0beta4
4
+ version: 3.0.0beta5
5
5
  prerelease: 5
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-02 00:00:00.000000000 Z
12
+ date: 2012-04-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
16
- requirement: &223216140 !ruby/object:Gem::Requirement
16
+ requirement: &17186100 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 3.2.2
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *223216140
24
+ version_requirements: *17186100
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: jquery-rails
27
- requirement: &223215720 !ruby/object:Gem::Requirement
27
+ requirement: &17185560 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *223215720
35
+ version_requirements: *17185560
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: nokogiri
38
- requirement: &224032220 !ruby/object:Gem::Requirement
38
+ requirement: &17184800 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - =
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 1.5.0
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *224032220
46
+ version_requirements: *17184800
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: openurl
49
- requirement: &224031720 !ruby/object:Gem::Requirement
49
+ requirement: &17184180 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: 0.3.0
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *224031720
57
+ version_requirements: *17184180
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: marc
60
- requirement: &224031240 !ruby/object:Gem::Requirement
60
+ requirement: &17183420 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ~>
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: 0.4.3
66
66
  type: :runtime
67
67
  prerelease: false
68
- version_requirements: *224031240
68
+ version_requirements: *17183420
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: isbn
71
- requirement: &224030800 !ruby/object:Gem::Requirement
71
+ requirement: &17182120 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '0'
77
77
  type: :runtime
78
78
  prerelease: false
79
- version_requirements: *224030800
79
+ version_requirements: *17182120
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: htmlentities
82
- requirement: &224030200 !ruby/object:Gem::Requirement
82
+ requirement: &17180260 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,10 +87,10 @@ dependencies:
87
87
  version: '0'
88
88
  type: :runtime
89
89
  prerelease: false
90
- version_requirements: *224030200
90
+ version_requirements: *17180260
91
91
  - !ruby/object:Gem::Dependency
92
92
  name: multi_json
93
- requirement: &224029580 !ruby/object:Gem::Requirement
93
+ requirement: &17179300 !ruby/object:Gem::Requirement
94
94
  none: false
95
95
  requirements:
96
96
  - - ! '>='
@@ -98,10 +98,10 @@ dependencies:
98
98
  version: '0'
99
99
  type: :runtime
100
100
  prerelease: false
101
- version_requirements: *224029580
101
+ version_requirements: *17179300
102
102
  - !ruby/object:Gem::Dependency
103
103
  name: confstruct
104
- requirement: &224028780 !ruby/object:Gem::Requirement
104
+ requirement: &17132240 !ruby/object:Gem::Requirement
105
105
  none: false
106
106
  requirements:
107
107
  - - ~>
@@ -109,10 +109,10 @@ dependencies:
109
109
  version: '0.2'
110
110
  type: :runtime
111
111
  prerelease: false
112
- version_requirements: *224028780
112
+ version_requirements: *17132240
113
113
  - !ruby/object:Gem::Dependency
114
114
  name: exlibris-primo
115
- requirement: &224028280 !ruby/object:Gem::Requirement
115
+ requirement: &17130980 !ruby/object:Gem::Requirement
116
116
  none: false
117
117
  requirements:
118
118
  - - ~>
@@ -120,10 +120,10 @@ dependencies:
120
120
  version: 0.1.0
121
121
  type: :runtime
122
122
  prerelease: false
123
- version_requirements: *224028280
123
+ version_requirements: *17130980
124
124
  - !ruby/object:Gem::Dependency
125
125
  name: single_test
126
- requirement: &224027780 !ruby/object:Gem::Requirement
126
+ requirement: &17129720 !ruby/object:Gem::Requirement
127
127
  none: false
128
128
  requirements:
129
129
  - - ~>
@@ -131,10 +131,10 @@ dependencies:
131
131
  version: 0.5.1
132
132
  type: :development
133
133
  prerelease: false
134
- version_requirements: *224027780
134
+ version_requirements: *17129720
135
135
  - !ruby/object:Gem::Dependency
136
136
  name: uglifier
137
- requirement: &224027200 !ruby/object:Gem::Requirement
137
+ requirement: &17128300 !ruby/object:Gem::Requirement
138
138
  none: false
139
139
  requirements:
140
140
  - - ! '>='
@@ -142,10 +142,10 @@ dependencies:
142
142
  version: '0'
143
143
  type: :development
144
144
  prerelease: false
145
- version_requirements: *224027200
145
+ version_requirements: *17128300
146
146
  - !ruby/object:Gem::Dependency
147
147
  name: therubyracer
148
- requirement: &224026480 !ruby/object:Gem::Requirement
148
+ requirement: &17126180 !ruby/object:Gem::Requirement
149
149
  none: false
150
150
  requirements:
151
151
  - - ! '>='
@@ -153,10 +153,10 @@ dependencies:
153
153
  version: '0'
154
154
  type: :development
155
155
  prerelease: false
156
- version_requirements: *224026480
156
+ version_requirements: *17126180
157
157
  - !ruby/object:Gem::Dependency
158
158
  name: ruby-prof
159
- requirement: &224025900 !ruby/object:Gem::Requirement
159
+ requirement: &17523620 !ruby/object:Gem::Requirement
160
160
  none: false
161
161
  requirements:
162
162
  - - ! '>='
@@ -164,7 +164,7 @@ dependencies:
164
164
  version: '0'
165
165
  type: :development
166
166
  prerelease: false
167
- version_requirements: *224025900
167
+ version_requirements: *17523620
168
168
  description:
169
169
  email:
170
170
  - umlaut-general@rubyforge.org
@@ -213,11 +213,13 @@ files:
213
213
  - app/assets/javascripts/umlaut_ui.js
214
214
  - app/assets/javascripts/umlaut.js
215
215
  - app/assets/javascripts/umlaut/update_html.js
216
+ - app/assets/javascripts/umlaut/#simple_visible_toggle.js#
216
217
  - app/assets/javascripts/umlaut/ajax_windows.js
217
218
  - app/assets/javascripts/umlaut/simple_visible_toggle.js
218
219
  - app/assets/javascripts/umlaut/ensure_window_size.js.erb
220
+ - app/assets/javascripts/umlaut/expand_contract_toggle.js
219
221
  - app/assets/javascripts/umlaut/search_autocomplete.js
220
- - app/assets/javascripts/umlaut/expand_contract_toggle.js.erb
222
+ - app/assets/javascripts/umlaut/#update_html.js#
221
223
  - app/assets/stylesheets/umlaut.css
222
224
  - app/models/sfx_url.rb
223
225
  - app/models/crossref_lookup.rb
@@ -321,6 +323,7 @@ files:
321
323
  - lib/opensearch_feed.rb
322
324
  - lib/holding.rb
323
325
  - lib/umlaut_configurable.rb
326
+ - lib/umlaut/#routes.rb#
324
327
  - lib/umlaut/routes.rb
325
328
  - lib/umlaut/version.rb
326
329
  - lib/umlaut/default_configuration.rb
@@ -333,6 +336,7 @@ files:
333
336
  - lib/service_adaptors/primo_service.rb
334
337
  - lib/service_adaptors/open_library.rb
335
338
  - lib/service_adaptors/elsevier_cover.rb
339
+ - lib/service_adaptors/#blacklight.rb#
336
340
  - lib/service_adaptors/ulrichs_link.rb
337
341
  - lib/service_adaptors/ulrichs_cover.rb
338
342
  - lib/service_adaptors/scopus.rb
@@ -481,7 +485,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
481
485
  version: '0'
482
486
  segments:
483
487
  - 0
484
- hash: -556718949677620916
488
+ hash: -986297622485492728
485
489
  required_rubygems_version: !ruby/object:Gem::Requirement
486
490
  none: false
487
491
  requirements: