umlaut 3.0.0alpha1
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.
- data/LICENSE +7 -0
- data/README.md +49 -0
- data/Rakefile +37 -0
- data/app/assets/images/error.gif +0 -0
- data/app/assets/images/export_bg_bot.gif +0 -0
- data/app/assets/images/export_bg_mid.gif +0 -0
- data/app/assets/images/export_bg_top.gif +0 -0
- data/app/assets/images/famfamfam/book_open.png +0 -0
- data/app/assets/images/famfamfam/cross.png +0 -0
- data/app/assets/images/famfamfam/page_sound.gif +0 -0
- data/app/assets/images/famfamfam/page_text.gif +0 -0
- data/app/assets/images/famfamfam/page_up.gif +0 -0
- data/app/assets/images/famfamfam/page_white.png +0 -0
- data/app/assets/images/famfamfam/readme.html +1495 -0
- data/app/assets/images/famfamfam/tiny_cross.png +0 -0
- data/app/assets/images/frame_remove.gif +0 -0
- data/app/assets/images/ico_go.gif +0 -0
- data/app/assets/images/jhu_findit.gif +0 -0
- data/app/assets/images/list_closed.png +0 -0
- data/app/assets/images/list_open.png +0 -0
- data/app/assets/images/more_info.gif +0 -0
- data/app/assets/images/rails.png +0 -0
- data/app/assets/images/request.gif +0 -0
- data/app/assets/images/spinner.gif +0 -0
- data/app/assets/javascripts/umlaut/ajax_windows.js +35 -0
- data/app/assets/javascripts/umlaut/ensure_window_size.js.erb +34 -0
- data/app/assets/javascripts/umlaut/expand_contract_toggle.js +25 -0
- data/app/assets/javascripts/umlaut/search_autocomplete.js +46 -0
- data/app/assets/javascripts/umlaut/simple_visible_toggle.js +8 -0
- data/app/assets/javascripts/umlaut/update_html.js +152 -0
- data/app/assets/javascripts/umlaut.js +17 -0
- data/app/assets/stylesheets/umlaut.css +857 -0
- data/app/controllers/application_controller.rb +14 -0
- data/app/controllers/export_email_controller.rb +123 -0
- data/app/controllers/js_helper_controller.rb +10 -0
- data/app/controllers/link_router_controller.rb +87 -0
- data/app/controllers/open_search_controller.rb +9 -0
- data/app/controllers/resolve_controller.rb +288 -0
- data/app/controllers/resource_controller.rb +83 -0
- data/app/controllers/search_controller.rb +328 -0
- data/app/controllers/search_methods/sfx3.rb +148 -0
- data/app/controllers/search_methods/sfx4.rb +257 -0
- data/app/controllers/search_methods/sfx_api.rb +47 -0
- data/app/controllers/store_controller.rb +64 -0
- data/app/controllers/umlaut/controller_behavior.rb +20 -0
- data/app/controllers/umlaut/controller_logic.rb +96 -0
- data/app/controllers/umlaut/error_handling.rb +48 -0
- data/app/controllers/umlaut_controller.rb +112 -0
- data/app/helpers/application_helper.rb +4 -0
- data/app/helpers/emailer_helper.rb +43 -0
- data/app/helpers/export_email_helper.rb +34 -0
- data/app/helpers/open_search_helper.rb +7 -0
- data/app/helpers/resolve_helper.rb +225 -0
- data/app/helpers/search_helper.rb +50 -0
- data/app/helpers/umlaut/footer_helper.rb +64 -0
- data/app/helpers/umlaut/helper.rb +62 -0
- data/app/helpers/umlaut/html_head_helper.rb +37 -0
- data/app/helpers/umlaut/url_generation.rb +77 -0
- data/app/mailers/emailer.rb +48 -0
- data/app/models/clickthrough.rb +2 -0
- data/app/models/collection.rb +259 -0
- data/app/models/crossref_lookup.rb +2 -0
- data/app/models/dispatched_service.rb +58 -0
- data/app/models/permalink.rb +29 -0
- data/app/models/referent.rb +473 -0
- data/app/models/referent_value.rb +14 -0
- data/app/models/request.rb +449 -0
- data/app/models/service_response.rb +179 -0
- data/app/models/service_store.rb +59 -0
- data/app/models/service_type_value.rb +58 -0
- data/app/models/service_wave.rb +150 -0
- data/app/models/sfx_db/az_additional_title.rb +11 -0
- data/app/models/sfx_db/az_letter_group.rb +11 -0
- data/app/models/sfx_db/az_title.rb +38 -0
- data/app/models/sfx_db/az_title_v2.rb +34 -0
- data/app/models/sfx_db/isbn.rb +12 -0
- data/app/models/sfx_db/issn.rb +12 -0
- data/app/models/sfx_db/object.rb +35 -0
- data/app/models/sfx_db/object_portfolio.rb +6 -0
- data/app/models/sfx_db/publisher.rb +10 -0
- data/app/models/sfx_db/sfx_db_base.rb +54 -0
- data/app/models/sfx_db/target.rb +9 -0
- data/app/models/sfx_db/target_service.rb +10 -0
- data/app/models/sfx_db/title.rb +10 -0
- data/app/models/sfx_db.rb +10 -0
- data/app/models/sfx_url.rb +35 -0
- data/app/views/emailer/citation.text.erb +28 -0
- data/app/views/emailer/short_citation.text.erb +8 -0
- data/app/views/export_email/_email.html.erb +25 -0
- data/app/views/export_email/_send_email.html.erb +3 -0
- data/app/views/export_email/_send_txt.html.erb +3 -0
- data/app/views/export_email/_txt.html.erb +62 -0
- data/app/views/export_email/email.html.erb +3 -0
- data/app/views/export_email/send_email.html.erb +1 -0
- data/app/views/export_email/send_txt.html.erb +1 -0
- data/app/views/export_email/txt.html.erb +3 -0
- data/app/views/js_helper/loader.erb.js +13 -0
- data/app/views/layouts/umlaut.html.erb +52 -0
- data/app/views/open_search/index.html.erb +9 -0
- data/app/views/resolve/_api_in_progress.xml.erb +21 -0
- data/app/views/resolve/_background_progress.html.erb +51 -0
- data/app/views/resolve/_background_updater.html.erb +38 -0
- data/app/views/resolve/_citation.html.erb +87 -0
- data/app/views/resolve/_coins.html.erb +1 -0
- data/app/views/resolve/_compact_citation.html.erb +33 -0
- data/app/views/resolve/_cover_image.html.erb +35 -0
- data/app/views/resolve/_fulltext.html.erb +55 -0
- data/app/views/resolve/_help.html.erb +17 -0
- data/app/views/resolve/_holding.html.erb +91 -0
- data/app/views/resolve/_related_items.html.erb +35 -0
- data/app/views/resolve/_search_inside.html.erb +62 -0
- data/app/views/resolve/_section_display.html.erb +49 -0
- data/app/views/resolve/_service_errors.html.erb +29 -0
- data/app/views/resolve/_standard_response_item.html.erb +89 -0
- data/app/views/resolve/api.xml.builder +72 -0
- data/app/views/resolve/background_status.html.erb +26 -0
- data/app/views/resolve/index.html.erb +73 -0
- data/app/views/resolve/partial_html_sections.xml.erb +30 -0
- data/app/views/search/_a_to_z.html.erb +6 -0
- data/app/views/search/_citation.html.erb +94 -0
- data/app/views/search/_pager.html.erb +60 -0
- data/app/views/search/books.html.erb +103 -0
- data/app/views/search/journal_search.html.erb +90 -0
- data/app/views/search/journals.html.erb +167 -0
- data/app/views/search/opensearch_description.rxml +10 -0
- data/app/views/testing/index.html.erb +1 -0
- data/app/views/umlaut/README +5 -0
- data/app/views/umlaut/error.html.erb +45 -0
- data/db/migrate/01_umlaut_init.rb +113 -0
- data/db/orig_fixed_data/service_type_values.yml +120 -0
- data/db/seeds.rb +7 -0
- data/lib/CronTab.rb +192 -0
- data/lib/aws_product_sign.rb +146 -0
- data/lib/exlibris/aleph/patron.rb +64 -0
- data/lib/exlibris/aleph/record.rb +54 -0
- data/lib/exlibris/aleph/rest_api.rb +29 -0
- data/lib/exlibris/primo/holding.rb +192 -0
- data/lib/exlibris/primo/rsrc.rb +17 -0
- data/lib/exlibris/primo/searcher.rb +276 -0
- data/lib/exlibris/primo/source/aleph.rb +46 -0
- data/lib/exlibris/primo/source/distribution/nyu_aleph.rb +323 -0
- data/lib/exlibris/primo/toc.rb +17 -0
- data/lib/exlibris/primo_ws.rb +140 -0
- data/lib/generators/templates/umlaut_services.yml +237 -0
- data/lib/generators/umlaut/asset_hooks_generator.rb +44 -0
- data/lib/generators/umlaut/install_generator.rb +110 -0
- data/lib/hip3/bib.rb +291 -0
- data/lib/hip3/bib_searcher.rb +302 -0
- data/lib/hip3/custom_field_lookup.rb +44 -0
- data/lib/hip3/holding.rb +50 -0
- data/lib/hip3/item.rb +65 -0
- data/lib/hip3/receipt.rb +7 -0
- data/lib/hip3/serial_copy.rb +82 -0
- data/lib/holding.rb +32 -0
- data/lib/marc_helper.rb +254 -0
- data/lib/metadata_helper.rb +312 -0
- data/lib/opensearch_feed.rb +398 -0
- data/lib/opensearch_query.rb +98 -0
- data/lib/referent_filter.rb +16 -0
- data/lib/referent_filters/dissertation_catch.rb +45 -0
- data/lib/section_renderer.rb +503 -0
- data/lib/service.rb +336 -0
- data/lib/service_adaptors/ajax_export.rb +37 -0
- data/lib/service_adaptors/amazon.rb +412 -0
- data/lib/service_adaptors/blacklight.rb +327 -0
- data/lib/service_adaptors/book_finder.rb +40 -0
- data/lib/service_adaptors/bx.rb +51 -0
- data/lib/service_adaptors/cover_thing.rb +73 -0
- data/lib/service_adaptors/elsevier_cover.rb +57 -0
- data/lib/service_adaptors/email_export.rb +10 -0
- data/lib/service_adaptors/ezproxy.rb +171 -0
- data/lib/service_adaptors/google_book_search.rb +442 -0
- data/lib/service_adaptors/gpo.rb +124 -0
- data/lib/service_adaptors/hathi_trust.rb +308 -0
- data/lib/service_adaptors/hip3_service.rb +150 -0
- data/lib/service_adaptors/hip_holding_search.rb +237 -0
- data/lib/service_adaptors/internet_archive.rb +488 -0
- data/lib/service_adaptors/isbn_db.rb +86 -0
- data/lib/service_adaptors/isi.rb +258 -0
- data/lib/service_adaptors/jcr.rb +146 -0
- data/lib/service_adaptors/opac.rb +351 -0
- data/lib/service_adaptors/open_library.rb +316 -0
- data/lib/service_adaptors/open_library_cover.rb +73 -0
- data/lib/service_adaptors/primo_service.rb +392 -0
- data/lib/service_adaptors/primo_source.rb +78 -0
- data/lib/service_adaptors/pubmed.rb +133 -0
- data/lib/service_adaptors/request_to_fixture.rb +68 -0
- data/lib/service_adaptors/scopus.rb +295 -0
- data/lib/service_adaptors/sfx-new.rb +557 -0
- data/lib/service_adaptors/sfx.rb +566 -0
- data/lib/service_adaptors/sfx_backchannel_record.rb +69 -0
- data/lib/service_adaptors/txt_holding_export.rb +32 -0
- data/lib/service_adaptors/ulrichs_cover.rb +57 -0
- data/lib/service_adaptors/ulrichs_link.rb +47 -0
- data/lib/service_adaptors/worldcat.rb +116 -0
- data/lib/service_adaptors/worldcat_identities.rb +591 -0
- data/lib/tasks/umlaut.rake +134 -0
- data/lib/umlaut/default_configuration.rb +5 -0
- data/lib/umlaut/routes.rb +136 -0
- data/lib/umlaut/version.rb +3 -0
- data/lib/umlaut.rb +37 -0
- data/lib/umlaut_configurable.rb +343 -0
- data/lib/umlaut_http.rb +100 -0
- data/lib/xml_schema_helper.rb +109 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/controllers/umlaut_controller.rb +112 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config/application.rb +45 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database-jhu.yml +44 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +34 -0
- data/test/dummy/config/environments/production.rb +60 -0
- data/test/dummy/config/environments/test.rb +39 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +10 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +61 -0
- data/test/dummy/config/umlaut_services.yml +237 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/migrate/20111228211210_umlaut_init.rb +113 -0
- data/test/dummy/db/schema.rb +124 -0
- data/test/dummy/log/development.log +12981 -0
- data/test/dummy/log/production.log +0 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +26 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/dummy/tmp/cache/assets/C5F/340/sprockets%2F99692920160b7a279b86a80415b79db7 +0 -0
- data/test/dummy/tmp/cache/assets/C70/4D0/sprockets%2F034ad2036e623081bd352800786dfe80 +0 -0
- data/test/dummy/tmp/cache/assets/C73/920/sprockets%2Fd371318f22900492fd180f17c5e2a504 +9268 -0
- data/test/dummy/tmp/cache/assets/C80/980/sprockets%2Fc94807409c1523d43e18d25f35d93c41 +0 -0
- data/test/dummy/tmp/cache/assets/C8F/780/sprockets%2Fe47e28558116fb5f8038754e60d1961d +11769 -0
- data/test/dummy/tmp/cache/assets/CAA/EB0/sprockets%2F1d179210e8b76f1ea63c802688a015e4 +9271 -0
- data/test/dummy/tmp/cache/assets/CBB/9C0/sprockets%2F706f28923fb754cad04b9107c89986a1 +0 -0
- data/test/dummy/tmp/cache/assets/CBF/B60/sprockets%2F08ca89671549936265dcb673bf02e36f +0 -0
- data/test/dummy/tmp/cache/assets/CC9/9F0/sprockets%2F306166316e2cafd13c15e62b51a2339d +0 -0
- data/test/dummy/tmp/cache/assets/CF6/F20/sprockets%2F5b2ffa1103079dfd555197838f87a99f +0 -0
- data/test/dummy/tmp/cache/assets/CF7/2B0/sprockets%2F25a7c73655bd3598173b39d9f98bcd46 +862 -0
- data/test/dummy/tmp/cache/assets/CFE/080/sprockets%2F37fe9f4255baddbd549a659914929398 +0 -0
- data/test/dummy/tmp/cache/assets/D22/060/sprockets%2F9aec77b768e91a802d284271c58e2f7e +21357 -0
- data/test/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
- data/test/dummy/tmp/cache/assets/D33/6D0/sprockets%2F500129c57f1146e556ec3aacd6cd38c1 +0 -0
- data/test/dummy/tmp/cache/assets/D33/FD0/sprockets%2F2ba0b4e6334a77b923e5f770381bb2bf +0 -0
- data/test/dummy/tmp/cache/assets/D42/C20/sprockets%2Fbcf14e437b1582bf93b77670acf8e090 +21353 -0
- data/test/dummy/tmp/cache/assets/D50/A30/sprockets%2F7d8b294ac433db5d056538f8cf7c66b9 +0 -0
- data/test/dummy/tmp/cache/assets/D54/ED0/sprockets%2F71c9fa01091d432b131da3bb73faf3d4 +872 -0
- data/test/dummy/tmp/cache/assets/D65/590/sprockets%2Fc1bb92fc3406a126b7dd302edc96d629 +0 -0
- data/test/dummy/tmp/cache/assets/D71/6B0/sprockets%2Fde558b71b494cf09b1bf055c8dff0353 +0 -0
- data/test/dummy/tmp/cache/assets/D72/610/sprockets%2Fa8c708eeb30ef93de34d755d4f45d023 +859 -0
- data/test/dummy/tmp/cache/assets/D76/AD0/sprockets%2Fe2158cde93188cf5ab6457bc6d6602ec +0 -0
- data/test/dummy/tmp/cache/assets/D7A/E40/sprockets%2F9622ffcc499a57627cd1bb18fe31b8e4 +11772 -0
- data/test/dummy/tmp/cache/assets/D84/210/sprockets%2Fabd0103ccec2b428ac62c94e4c40b384 +0 -0
- data/test/dummy/tmp/cache/assets/D9B/770/sprockets%2F8aacf02eb7dbb0949704b28f27b87e0b +0 -0
- data/test/dummy/tmp/cache/assets/DA6/A80/sprockets%2F92e26d8e58d5bcc8b8f6c25d1b05b9c1 +0 -0
- data/test/dummy/tmp/cache/assets/DE8/790/sprockets%2Fd1333bde2b9aafcc712d11dd09ab35d8 +0 -0
- data/test/dummy/tmp/cache/assets/DF7/F30/sprockets%2F7bc16c4109b17fabe29f8ddbbf732d1c +374 -0
- data/test/dummy/tmp/cache/assets/E03/570/sprockets%2F493bdc0ac14cd4f57fdfe4253f992bde +0 -0
- data/test/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/test/dummy/tmp/cache/assets/E0B/4B0/sprockets%2F7988df51a61c81ce6ede4a2d4c8cce4f +377 -0
- data/test/dummy/tmp/cache/assets/E5F/960/sprockets%2Fdc007b6cad5c7ef08e33ec28cfff0ef6 +0 -0
- data/test/fixtures/dispatched_services.yml +5 -0
- data/test/fixtures/permalinks.yml +5 -0
- data/test/fixtures/referent_values.yml +1734 -0
- data/test/fixtures/referents.yml +156 -0
- data/test/fixtures/requests.yml +284 -0
- data/test/fixtures/service_responses.yml +5 -0
- data/test/fixtures/sfx_urls.yml +4 -0
- data/test/performance/browsing_test.rb +9 -0
- data/test/test_helper.rb +10 -0
- data/test/umlaut_test.rb +7 -0
- data/test/unit/aleph_patron_test.rb +39 -0
- data/test/unit/aleph_record_benchmarks.rb +28 -0
- data/test/unit/aleph_record_test.rb +30 -0
- data/test/unit/aws_product_sign_test.rb +93 -0
- data/test/unit/collection_test.rb +76 -0
- data/test/unit/google_book_search_test.rb +101 -0
- data/test/unit/primo_searcher_test.rb +403 -0
- data/test/unit/primo_service_test.rb +939 -0
- data/test/unit/primo_ws_test.rb +131 -0
- data/test/unit/service_response_test.rb +9 -0
- data/test/unit/service_test.rb +33 -0
- metadata +580 -0
@@ -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
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# Link to BookFinder.com to compare online new and used prices for a book.
|
2
|
+
# Requires an ISBN.
|
3
|
+
# Does not a pre-check, just generates the link blind, but I think almost any
|
4
|
+
# ISBN will get results on BookFinder.
|
5
|
+
class BookFinder < Service
|
6
|
+
require 'isbn'
|
7
|
+
|
8
|
+
def initialize(config)
|
9
|
+
@display_text = "Compare online prices"
|
10
|
+
@display_name = "BookFinder.com"
|
11
|
+
# %s is where the ISBN goes
|
12
|
+
@url_template = 'http://www.bookfinder.com/search/?isbn=%s&st=xl&ac=qr'
|
13
|
+
|
14
|
+
super(config)
|
15
|
+
end
|
16
|
+
|
17
|
+
def service_types_generated
|
18
|
+
return [ServiceTypeValue['highlighted_link']]
|
19
|
+
end
|
20
|
+
|
21
|
+
def handle(umlaut_request)
|
22
|
+
isbn = umlaut_request.referent.isbn
|
23
|
+
|
24
|
+
# Unless we have a valid isbn, give up
|
25
|
+
return request.dispatched(self, true) unless isbn && ISBN.valid?(isbn)
|
26
|
+
|
27
|
+
# Okay, make a link
|
28
|
+
url = @url_template.sub('%s', isbn)
|
29
|
+
|
30
|
+
umlaut_request.add_service_response(
|
31
|
+
:service=>self,
|
32
|
+
:url=> url,
|
33
|
+
:display_text=> @display_text,
|
34
|
+
:service_type_value => :highlighted_link
|
35
|
+
)
|
36
|
+
|
37
|
+
return request.dispatched(self, true)
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Uses bX recommender service to generate links to "similar" articles.
|
2
|
+
#
|
3
|
+
# REQUIREMENTS: You must be an bX customer. Required field "token" is specific to your institution.
|
4
|
+
# Optional fields:
|
5
|
+
# max_records - max number of records returned; default 10
|
6
|
+
# threshold - minimum score a recommendation must have in order to be included in the results; scores range from 0 to 100; default 50
|
7
|
+
# openurl_base - base for other link resolver; default "/resolve"
|
8
|
+
# source - values "local"|"global"; default "global"
|
9
|
+
class Bx < Service
|
10
|
+
require 'open-uri'
|
11
|
+
require 'nokogiri'
|
12
|
+
|
13
|
+
required_config_params :token
|
14
|
+
|
15
|
+
def initialize(config)
|
16
|
+
@display_name = "Bx"
|
17
|
+
@base_url = "http://recommender.service.exlibrisgroup.com/service/recommender/openurl"
|
18
|
+
@max_records = "5"
|
19
|
+
@threshold = "50"
|
20
|
+
@source = "global"
|
21
|
+
@openurl_base = "/resolve"
|
22
|
+
super(config)
|
23
|
+
end
|
24
|
+
|
25
|
+
def service_types_generated
|
26
|
+
return [ServiceTypeValue[:similar]]
|
27
|
+
end
|
28
|
+
|
29
|
+
def handle(request)
|
30
|
+
format = "rss"
|
31
|
+
bx_url = "#{@base_url}?res_dat=format%3D#{format}%26source%3D#{@source}%26token%3D#{@token}%26maxRecords%3D#{@max_records}%26threshold%3D#{@threshold}%26baseUrl%3D#{@openurl_base}&#{request.to_context_object.kev}"
|
32
|
+
response = open(bx_url)
|
33
|
+
Rails.logger.debug("bX URL #{bx_url.inspect}")
|
34
|
+
response_xml_str = response.read
|
35
|
+
Rails.logger.debug("bX Response #{response_xml_str.inspect}")
|
36
|
+
response_xml = Nokogiri::XML(response_xml_str)
|
37
|
+
response_xml.search("//item") do |item|
|
38
|
+
title = item.at("title").inner_text
|
39
|
+
author = item.at("author").inner_text
|
40
|
+
display_text = (author.nil?)? "#{title}" : "#{author}; #{title}"
|
41
|
+
url = item.at("link").inner_text
|
42
|
+
request.add_service_response(
|
43
|
+
:service=>self,
|
44
|
+
:display_text => display_text,
|
45
|
+
:url => url,
|
46
|
+
:service_type_value => :similar)
|
47
|
+
end
|
48
|
+
return request.dispatched(self, true)
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# Find book covers from LibraryThing's CoverThing service.
|
2
|
+
# Only fetches "medium" size image. Fetches only by ISBN.
|
3
|
+
|
4
|
+
class CoverThing < Service
|
5
|
+
require 'net/http'
|
6
|
+
include MetadataHelper
|
7
|
+
required_config_params :developer_key
|
8
|
+
|
9
|
+
def service_types_generated
|
10
|
+
return [ ServiceTypeValue[:cover_image] ]
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(config)
|
14
|
+
@display_name = "LibraryThing"
|
15
|
+
# http://covers.librarything.com/devkey/KEY/medium/isbn/0545010225
|
16
|
+
@base_url = 'http://covers.librarything.com/devkey/';
|
17
|
+
@lt404url = 'http://www.librarything.com/coverthing404.php'
|
18
|
+
|
19
|
+
@credits = {
|
20
|
+
"LibraryThing" => "http://www.librarything.com/"
|
21
|
+
}
|
22
|
+
|
23
|
+
super(config)
|
24
|
+
end
|
25
|
+
|
26
|
+
def handle(request)
|
27
|
+
image_url = image_url(request.referent)
|
28
|
+
return request.dispatched(self, true) unless image_url
|
29
|
+
|
30
|
+
uri = URI.parse(image_url)
|
31
|
+
response = nil
|
32
|
+
# All we need is a HEAD request to check content-length.
|
33
|
+
Net::HTTP.start(uri.host, uri.port) {|http|
|
34
|
+
response = http.head(uri.path)
|
35
|
+
}
|
36
|
+
|
37
|
+
# Only way to know if we got an image or a transparent placeholder
|
38
|
+
# is to check the content-length. Currently the transparent placeholder
|
39
|
+
# is 43 bytes. -- not true anymore, now we can check for a redirect,
|
40
|
+
# I guess.
|
41
|
+
|
42
|
+
# Not sure why response is ever nil, but sometimes it is, let's log
|
43
|
+
# some info.
|
44
|
+
if ( response.kind_of?(Net::HTTPRedirection) && response["location"] == @lt404url)
|
45
|
+
# no cover found.
|
46
|
+
return request.dispatched(self, true)
|
47
|
+
elsif ( response.nil? || response.content_length.nil? )
|
48
|
+
Rails.logger.debug("CoverThing: Null response for #{uri}, status #{response.class}")
|
49
|
+
end
|
50
|
+
unless (response.nil? || response.content_length.nil? || response.content_length < 50)
|
51
|
+
request.add_service_response(
|
52
|
+
:service=>self,
|
53
|
+
:display_text => 'Cover Image',
|
54
|
+
:key=> 'medium',
|
55
|
+
:url => image_url,
|
56
|
+
:size => 'medium',
|
57
|
+
:service_type_value => :cover_image
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
return request.dispatched(self, true)
|
62
|
+
end
|
63
|
+
|
64
|
+
def image_url(referent)
|
65
|
+
isbn = get_identifier(:urn, "isbn", referent)
|
66
|
+
isbn.gsub!(/[^\d]/, '') if isbn # just numeric isbn
|
67
|
+
return nil if isbn.blank? # need an isbn to make the request
|
68
|
+
|
69
|
+
return @base_url + @developer_key + '/medium/isbn/' + isbn
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
end
|