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,124 @@
|
|
1
|
+
# Still in progress. Uses illegal info:sudoc and info:gpo to get a
|
2
|
+
# a sudoc or a GPO Item Number for a given referent, and finds online
|
3
|
+
# availability, and/or links to GPO lookup for local depository with the
|
4
|
+
# item.
|
5
|
+
class Gpo < Service
|
6
|
+
include MetadataHelper
|
7
|
+
require 'nokogiri'
|
8
|
+
require 'open-uri'
|
9
|
+
|
10
|
+
|
11
|
+
def initialize(config)
|
12
|
+
@display_name = "U.S. Government Printing Office"
|
13
|
+
@gpo_item_find = true
|
14
|
+
@sudoc_url_lookup = true
|
15
|
+
super(config)
|
16
|
+
end
|
17
|
+
|
18
|
+
def service_types_generated
|
19
|
+
a = []
|
20
|
+
a.push(ServiceTypeValue["highlighted_link"]) if @gpo_item_find
|
21
|
+
a.push(ServiceTypeValue["fulltext"]) if @sudoc_url_lookup
|
22
|
+
return a
|
23
|
+
end
|
24
|
+
|
25
|
+
def handle(request)
|
26
|
+
|
27
|
+
if ( @gpo_item_find )
|
28
|
+
items = analyze_gpo_items( get_gpo_item_nums(request.referent) )
|
29
|
+
|
30
|
+
items.each do |item, formats|
|
31
|
+
# Generate URL to GPO Item Number lookup to finding
|
32
|
+
# it in a repository near you.
|
33
|
+
|
34
|
+
request.add_service_response(:service => self,
|
35
|
+
:display_text => "Find in a Federal Depository Library",
|
36
|
+
:url => gpo_item_lookup_url(item),
|
37
|
+
:notes => "In " + formats.join(" or "),
|
38
|
+
:service_type_value => "highlighted_link"
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
sudoc = get_sudoc(request.referent)
|
43
|
+
|
44
|
+
if ( sudoc && @sudoc_url_lookup )
|
45
|
+
add_links_from_sudoc(request, sudoc)
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
request.dispatched(self, true)
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
# Takes an array of string of GPO Items with formats in parens, groups
|
54
|
+
# them by individual Item Number, identified by formats.
|
55
|
+
def analyze_gpo_items(items)
|
56
|
+
item_hash = {}
|
57
|
+
|
58
|
+
items.each do |i|
|
59
|
+
|
60
|
+
bare_item = i
|
61
|
+
format_str = 'paper'
|
62
|
+
|
63
|
+
# seperate the format marker from the base item number, if present.
|
64
|
+
# if it's not present, means paper.
|
65
|
+
if ( i =~ /^(.*)\(([^\)]+)\)\s*$/ )
|
66
|
+
bare_item = $1.strip
|
67
|
+
format_str = $2.strip
|
68
|
+
format_str = "microform" if format_str == "MF"
|
69
|
+
end
|
70
|
+
|
71
|
+
item_hash[bare_item] ||= []
|
72
|
+
|
73
|
+
item_hash[bare_item].push( format_str )
|
74
|
+
end
|
75
|
+
|
76
|
+
return item_hash
|
77
|
+
end
|
78
|
+
|
79
|
+
def gpo_item_lookup_url(item)
|
80
|
+
return "http://catalog.gpo.gov/fdlpdir/locate.jsp?ItemNumber=" + CGI.escape(item)
|
81
|
+
end
|
82
|
+
|
83
|
+
def add_links_from_sudoc(request, sudoc)
|
84
|
+
# Screen scrape the GPO catalog.
|
85
|
+
|
86
|
+
response = open( gpo_sudoc_find_url(sudoc) ).read
|
87
|
+
|
88
|
+
response_dom = Nokogiri::HTML(response)
|
89
|
+
|
90
|
+
# Find each tr with class tr1, holding a td => The sixth td in there =>
|
91
|
+
# one or more 'a' tags in there. These are links to fulltext.
|
92
|
+
links = response_dom.search('//tr[@class = "tr1"][td]/td[7]/a')
|
93
|
+
|
94
|
+
urls_seen = []
|
95
|
+
|
96
|
+
links.each do |link|
|
97
|
+
# The href is an internally pointing ILS link. But the text inside
|
98
|
+
# the a is what we want, it's actually a URL, fortunately. .
|
99
|
+
|
100
|
+
url = link.inner_text
|
101
|
+
unless urls_seen.include?(url)
|
102
|
+
|
103
|
+
notes = nil
|
104
|
+
if (links.length > 1)
|
105
|
+
notes = "via " + URI.parse(url).host
|
106
|
+
end
|
107
|
+
|
108
|
+
request.add_service_response(:service => self,
|
109
|
+
:display_text => @display_name,
|
110
|
+
:url => url,
|
111
|
+
:notes => notes,
|
112
|
+
:service_type_value => "fulltext"
|
113
|
+
)
|
114
|
+
urls_seen.push( url )
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
def gpo_sudoc_find_url(sudoc)
|
121
|
+
return "http://catalog.gpo.gov/F/?func=find-a&find_code=GVD&request=#{CGI.escape('"'+sudoc+'"')}&local_base=GPO01PUB"
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
@@ -0,0 +1,308 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'multi_json'
|
3
|
+
require 'cgi'
|
4
|
+
|
5
|
+
# Service that searches HathiTrust from the University of Michigan
|
6
|
+
#
|
7
|
+
# Supports full text links, and search inside.
|
8
|
+
#
|
9
|
+
# We link to HathiTrust using a direct babel.hathitrust.org URL instead
|
10
|
+
# of the handle.net redirection, for two reasonsL
|
11
|
+
# 1) Can't use the handle.net redirection for the "direct link to search
|
12
|
+
# results for user-entered query" feature.
|
13
|
+
# 2) Some may want to force a Shibboleth login on HT links. Can't do that
|
14
|
+
# with the handle.net redirection either. If you do want to do that,
|
15
|
+
# possibly in concert with an EZProxy mediated WAYFless login,
|
16
|
+
# set direct_link_base in your services.yml to:
|
17
|
+
# "https://babel.hathitrust.org/shcgi/"
|
18
|
+
#
|
19
|
+
# Many (but not all) HT books will also be in Google Books (and vice versa)
|
20
|
+
# However, HT was more generous in deciding what books are public domain than GBS.
|
21
|
+
# Therefore the main expected use case is to use with Google Books, with
|
22
|
+
# HT being a lower priority, using preempted_by config.
|
23
|
+
#
|
24
|
+
# Some may prefer HT search inside interface to Google, so search inside
|
25
|
+
# is not suppressed with presence of google. You can turn off HT
|
26
|
+
# search inside entirely if you like.
|
27
|
+
#
|
28
|
+
# For HT records representing one volume of several, a :excerpts type
|
29
|
+
# response will be added if full text is avail for some. Or a :highlighted_link
|
30
|
+
# if only search inside is available for some.
|
31
|
+
# Or set config show_multi_volume=false to prevent this and ignore partial
|
32
|
+
# volumes.
|
33
|
+
#
|
34
|
+
# Two possibilities are available for sdr rights "full" or "searchonly".
|
35
|
+
# The third possibility is that sdr will be null.
|
36
|
+
#
|
37
|
+
# An ISBN with search-only: 0195101464
|
38
|
+
class HathiTrust < Service
|
39
|
+
include MetadataHelper
|
40
|
+
|
41
|
+
attr_reader :url, :display_name, :note
|
42
|
+
|
43
|
+
def service_types_generated
|
44
|
+
types = [ ServiceTypeValue[:fulltext] ]
|
45
|
+
types.concat([ServiceTypeValue[:excerpts], ServiceTypeValue[:highlighted_link]]) if @show_multi_volume
|
46
|
+
types << ServiceTypeValue[:search_inside] if @show_search_inside
|
47
|
+
return types
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize(config)
|
51
|
+
@api_url = 'http://catalog.hathitrust.org/api/volumes'
|
52
|
+
# Set to 'https://babel.hathitrust.org/shcgi/' to force
|
53
|
+
# Shibboleth login, possibly in concert with EZProxy providing
|
54
|
+
# WAYFLess login.
|
55
|
+
@direct_link_base = 'http://babel.hathitrust.org/cgi/'
|
56
|
+
@display_name = 'HathiTrust'
|
57
|
+
@num_full_views = 1 # max num full view links to include
|
58
|
+
@note = '' #'Fulltext books from the University of Michigan'
|
59
|
+
@show_search_inside = true
|
60
|
+
@show_multi_volume = true
|
61
|
+
|
62
|
+
@credits = {
|
63
|
+
"HathiTrust" => "http://www.hathitrust.org"
|
64
|
+
}
|
65
|
+
|
66
|
+
super(config)
|
67
|
+
end
|
68
|
+
|
69
|
+
def handle(request)
|
70
|
+
params = get_parameters(request.referent)
|
71
|
+
return request.dispatched(self, true) if params.blank?
|
72
|
+
|
73
|
+
ht_json = do_query(params)
|
74
|
+
return request.dispatched(self, true) if ht_json.nil?
|
75
|
+
|
76
|
+
#extract the "items" list from the first result group from
|
77
|
+
#response.
|
78
|
+
first_group = ht_json.values.first
|
79
|
+
items = first_group["items"]
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
# Only add fulltext if we're not skipping due to GBS
|
84
|
+
if ( preempted_by(request, "fulltext"))
|
85
|
+
Rails.logger.debug("#{self.class}: Skipping due to pre-emption")
|
86
|
+
else
|
87
|
+
full_views_shown = create_fulltext_service_response(request, items)
|
88
|
+
end
|
89
|
+
|
90
|
+
if @show_multi_volume
|
91
|
+
#possibly partial volumes
|
92
|
+
create_partial_volume_responses(request, ht_json)
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
create_search_inside(request, items)
|
98
|
+
|
99
|
+
return request.dispatched(self, true)
|
100
|
+
end
|
101
|
+
|
102
|
+
# just a wrapper around get_bibkey_parameters
|
103
|
+
def get_parameters(rft)
|
104
|
+
# API supports oclcnum, isbn, or lccn, and can provide more than one of each.
|
105
|
+
get_bibkey_parameters(rft) do |isbn, lccn, oclcnum|
|
106
|
+
keys = Array.new
|
107
|
+
|
108
|
+
keys << "oclc:" + CGI.escape(oclcnum) unless oclcnum.blank?
|
109
|
+
keys << "lccn:" + CGI.escape(lccn) unless lccn.blank?
|
110
|
+
# Only include ISBN if we have it and we do NOT have oclc or lccn,
|
111
|
+
# Bill Dueber's advice for best matching. HT api will only match
|
112
|
+
# if ALL the id's we supply match.
|
113
|
+
keys << "isbn:" + CGI.escape(isbn) unless (isbn.blank? || keys.length > 0)
|
114
|
+
|
115
|
+
if keys.length > 0
|
116
|
+
return keys.join(";")
|
117
|
+
else
|
118
|
+
return nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# method that takes a referent and a block for parameter creation
|
124
|
+
# The block receives isbn, lccn, oclcnum and is responsible for formatting
|
125
|
+
# the parameters for the particular service
|
126
|
+
# FIXME consider moving this into metadata_helper
|
127
|
+
def get_bibkey_parameters(rft)
|
128
|
+
isbn = get_identifier(:urn, "isbn", rft)
|
129
|
+
oclcnum = get_identifier(:info, "oclcnum", rft)
|
130
|
+
lccn = get_lccn(rft)
|
131
|
+
|
132
|
+
yield(isbn, lccn, oclcnum)
|
133
|
+
end
|
134
|
+
|
135
|
+
# conducts query and parses the JSON
|
136
|
+
def do_query(params)
|
137
|
+
link = @api_url + "/brief/json/" + params
|
138
|
+
return MultiJson.decode( open(link).read )
|
139
|
+
end
|
140
|
+
|
141
|
+
|
142
|
+
def create_fulltext_service_response(request, items)
|
143
|
+
return nil if items.empty?
|
144
|
+
|
145
|
+
display_name = @display_name
|
146
|
+
count = 0
|
147
|
+
|
148
|
+
items.each do |item|
|
149
|
+
next if is_serial_part?(item)
|
150
|
+
|
151
|
+
|
152
|
+
next unless full_view?(item)
|
153
|
+
|
154
|
+
request.add_service_response(
|
155
|
+
:service=>self,
|
156
|
+
:display_text=>display_name,
|
157
|
+
:url=> direct_url_to(item),
|
158
|
+
:notes=> note_for(item),
|
159
|
+
:service_type_value => :fulltext
|
160
|
+
)
|
161
|
+
count += 1
|
162
|
+
break if count == @num_full_views
|
163
|
+
end
|
164
|
+
return count
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
# If HT has partial serial volumes, include a link to that.
|
169
|
+
# Need to pass in complete HT json response
|
170
|
+
def create_partial_volume_responses(request, ht_json)
|
171
|
+
items = ht_json.values.first["items"]
|
172
|
+
full_ids = items.collect do |i|
|
173
|
+
i["fromRecord"] if (is_serial_part?(i) && full_view?(i))
|
174
|
+
end.compact.uniq
|
175
|
+
|
176
|
+
full_ids.each do |recordId|
|
177
|
+
record = ht_json.values.first["records"][recordId]
|
178
|
+
next unless record && record["recordURL"]
|
179
|
+
|
180
|
+
request.add_service_response(
|
181
|
+
:service=>self,
|
182
|
+
:display_text=>@display_name,
|
183
|
+
:url=> record["recordURL"],
|
184
|
+
:notes => excerpt_note_for(record),
|
185
|
+
:service_type_value => :excerpts
|
186
|
+
)
|
187
|
+
end
|
188
|
+
|
189
|
+
if full_ids.empty?
|
190
|
+
search_ids = items.collect do |i|
|
191
|
+
i["fromRecord"] if (is_serial_part?(i) )
|
192
|
+
end.compact.uniq
|
193
|
+
|
194
|
+
search_ids.each do |recordId|
|
195
|
+
record = ht_json.values.first["records"][recordId]
|
196
|
+
next unless record && record["recordURL"]
|
197
|
+
|
198
|
+
request.add_service_response(
|
199
|
+
:service=>self,
|
200
|
+
:display_text=>"Search inside some volumes",
|
201
|
+
:url=> record["recordURL"],
|
202
|
+
:service_type_value => :highlighted_link
|
203
|
+
)
|
204
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
end
|
211
|
+
|
212
|
+
def create_search_inside(request, items)
|
213
|
+
return if items.empty?
|
214
|
+
|
215
|
+
# Can only include search from the first one
|
216
|
+
# There's search inside for _any_ HT item. We think.
|
217
|
+
item = items.first
|
218
|
+
|
219
|
+
# if this is a serial, we don't want to search inside just part of it, forget it
|
220
|
+
return if is_serial_part?(item)
|
221
|
+
|
222
|
+
direct_url = search_url_to(item)
|
223
|
+
return unless direct_url
|
224
|
+
|
225
|
+
request.add_service_response(
|
226
|
+
:service => self,
|
227
|
+
:display_text=>@display_name,
|
228
|
+
:url=> direct_url,
|
229
|
+
:service_type_value => :search_inside
|
230
|
+
)
|
231
|
+
end
|
232
|
+
|
233
|
+
def direct_url_to(item_json)
|
234
|
+
if @direct_link_base
|
235
|
+
# we're constructing our own link because we need our EZProxy
|
236
|
+
# to recognize it for WAYFLess login, which it won't if we use
|
237
|
+
# the handle.net url, sorry.
|
238
|
+
# We also need direct link for direct link to search results.
|
239
|
+
@direct_link_base + "pt?id=" + CGI.escape(item_json['htid'])
|
240
|
+
else
|
241
|
+
item['itemURL']
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def note_for(item)
|
246
|
+
if item['orig']
|
247
|
+
"Digitized from #{item['orig']}"
|
248
|
+
else
|
249
|
+
nil
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def excerpt_note_for(record)
|
254
|
+
return nil unless record["titles"].kind_of?(Array)
|
255
|
+
"Some volumes of: #{record["titles"].first}"
|
256
|
+
end
|
257
|
+
|
258
|
+
def is_serial_part?(item)
|
259
|
+
# if it's got enumCron, then it's just part of a serial,
|
260
|
+
# we don't want to say the serial title as a whole has full text
|
261
|
+
# or can be searched, skip it.
|
262
|
+
return item['enumcron']
|
263
|
+
end
|
264
|
+
|
265
|
+
def full_view?(item)
|
266
|
+
item["usRightsString"] == "Full view"
|
267
|
+
end
|
268
|
+
|
269
|
+
def search_url_to(item_json)
|
270
|
+
if @direct_link_base
|
271
|
+
@direct_link_base + "ptsearch?id=" + CGI.escape(item_json['htid'])
|
272
|
+
else
|
273
|
+
return nil
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
|
278
|
+
|
279
|
+
|
280
|
+
# Handle search_inside
|
281
|
+
def response_url(service_response, submitted_params)
|
282
|
+
if ( ! (service_response.service_type_value.name == "search_inside" ))
|
283
|
+
return super(service_response, submitted_params)
|
284
|
+
else
|
285
|
+
base = service_response[:url]
|
286
|
+
query = CGI.escape(submitted_params["query"] || "")
|
287
|
+
url = base + "&q1=#{query}"
|
288
|
+
|
289
|
+
return url
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# sample OCLCnums with appropriate results showing that we can pick up other
|
294
|
+
# resources by using this service
|
295
|
+
# 02029914 MBooks: full, GBS: info with search inside
|
296
|
+
# 01635828 MBooks: full, GBS: snippet
|
297
|
+
# 55517975 MBooks: search, GBS: limited preview
|
298
|
+
# 02299399 MBooks: full, GBS: snippet
|
299
|
+
# 16857172 MBooks: full, GBS: info
|
300
|
+
|
301
|
+
# Example of a serial with some full text volumes:
|
302
|
+
# JAMA, lccn:07037314
|
303
|
+
#
|
304
|
+
# Example of a multi-volume with search-only, split accross
|
305
|
+
# two HT records.
|
306
|
+
# Handbook of biochemistry and molecular biology lccn: 75029514
|
307
|
+
|
308
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# Needs to be called Hip3Service to avoid conflicting with Hip3 module
|
2
|
+
# Params include:
|
3
|
+
# map_856_to_service : Umlaut ServiceTypeValue to map 856 links to. Defaults
|
4
|
+
# to fulltext_title_level
|
5
|
+
|
6
|
+
|
7
|
+
class Hip3Service < Service
|
8
|
+
required_config_params :base_path, :display_name
|
9
|
+
attr_reader :base_path
|
10
|
+
|
11
|
+
include MetadataHelper
|
12
|
+
include MarcHelper
|
13
|
+
|
14
|
+
def initialize(config)
|
15
|
+
# defaults
|
16
|
+
@map_856_to_service = 'fulltext'
|
17
|
+
# If you are sending an OpenURL from a library service, you may
|
18
|
+
# have the HIP bibnum, and include it in the OpenURL as, eg.
|
19
|
+
# rft_id=http://catalog.library.jhu.edu/bib/343434 (except URL-encoded)
|
20
|
+
# Then you'd set rft_id_bibnum_prefix to http://catalog.library.jhu.edu/bib/
|
21
|
+
@rft_id_bibnum_prefix = nil
|
22
|
+
@profile = "general"
|
23
|
+
super(config)
|
24
|
+
|
25
|
+
# Trim question-mark from base_url, if given
|
26
|
+
@base_path.chop! if (@base_path.rindex('?') == @base_path.length)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Standard method, used by background service updater. See Service docs.
|
30
|
+
def service_types_generated
|
31
|
+
# We generate full text and holdings types, right now.
|
32
|
+
types = [ ServiceTypeValue[:fulltext], ServiceTypeValue[:holding], ServiceTypeValue[:table_of_contents] ]
|
33
|
+
|
34
|
+
return types
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def handle(request)
|
39
|
+
bib_searcher = Hip3::BibSearcher.new(@base_path)
|
40
|
+
|
41
|
+
bib_searcher.issn = request.referent.issn
|
42
|
+
bib_searcher.isbn = request.referent.isbn
|
43
|
+
bib_searcher.sudoc = get_sudoc(request.referent)
|
44
|
+
|
45
|
+
results = bib_searcher.search
|
46
|
+
|
47
|
+
add_856_links(request, results.collect {|b| b.marc_xml})
|
48
|
+
add_copies(request, results)
|
49
|
+
|
50
|
+
return request.dispatched(self, true)
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
# Takes an array of Hip3::Bib objects believed to be exact matches
|
55
|
+
# for the citation querried, and adds response objects for them
|
56
|
+
# Returns a hash of arrays of ServiceResponses added.
|
57
|
+
def add_copies(request, bib_array, options = {})
|
58
|
+
#debugger
|
59
|
+
|
60
|
+
# default
|
61
|
+
options[:match_reliability] ||= ServiceResponse::MatchExact
|
62
|
+
|
63
|
+
responses_added = Hash.new
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
|
68
|
+
#Okay, we actually want to make each _copy_ into a service response.
|
69
|
+
#A bib may have multiple copies. We are merging bibs, and just worrying
|
70
|
+
#about the aggregated list of copies.
|
71
|
+
holdings = bib_array.collect { |bib| bib.holdings }.flatten
|
72
|
+
bib_array.each do |bib|
|
73
|
+
|
74
|
+
|
75
|
+
bib.holdings.each do |holding|
|
76
|
+
|
77
|
+
|
78
|
+
next if holding.dummy?
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
service_data = {}
|
83
|
+
service_data[:url] = holding.bib.http_url
|
84
|
+
service_data[:source_name] = holding.collection_str unless holding.collection_str.nil?
|
85
|
+
service_data[:call_number] = holding.call_no
|
86
|
+
service_data[:status] = holding.status_str
|
87
|
+
service_data[:location] = holding.location_str
|
88
|
+
service_data[:collection_str] = holding.collection_str
|
89
|
+
service_data[:copy_str] = holding.copy_str
|
90
|
+
service_data[:coverage_str] = holding.coverage_str
|
91
|
+
service_data[:coverage_str_array] = holding.coverage_str_to_a
|
92
|
+
service_data[:notes] = holding.notes
|
93
|
+
# If it's not a serial copy, we can add a direct request url.
|
94
|
+
unless ( holding.kind_of?(Hip3::SerialCopy) )
|
95
|
+
service_data[:request_url] = self.base_path + "?profile=#{@profile}&menu=request&aspect=none&bibkey=#{holding.bib.bibNum}&itemkey=#{holding.id}"
|
96
|
+
end
|
97
|
+
|
98
|
+
# Need to say it's not an exact match neccesarily?
|
99
|
+
|
100
|
+
unless ( options[:match_reliability] == ServiceResponse::MatchExact )
|
101
|
+
service_data[:match_reliability] = options[:match_reliability]
|
102
|
+
|
103
|
+
service_data[:edition_str] = edition_statement(bib.marc_xml)
|
104
|
+
end
|
105
|
+
|
106
|
+
display_text = ""
|
107
|
+
#display_text << (holding.location_str + ' ')unless holding.location_str.nil?
|
108
|
+
display_text << (holding.copy_str + ' ') unless holding.copy_str.nil?
|
109
|
+
|
110
|
+
# coverage strings, may be multiple
|
111
|
+
holding.coverage_str_to_a.each {|s| display_text << (s + ' ')}
|
112
|
+
|
113
|
+
display_text << holding.notes unless holding.notes.nil?
|
114
|
+
service_data[:display_text] = display_text
|
115
|
+
|
116
|
+
response = request.add_service_response(
|
117
|
+
service_data.merge(
|
118
|
+
:service=>self,
|
119
|
+
:service_type_value => 'holding' )
|
120
|
+
)
|
121
|
+
|
122
|
+
responses_added['holding'] ||= Array.new
|
123
|
+
responses_added['holding'].push( response )
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
return responses_added
|
129
|
+
end
|
130
|
+
|
131
|
+
def url_service_type( field )
|
132
|
+
return service_type_for_856(field, :default_service_type => @map_856_to_service)
|
133
|
+
end
|
134
|
+
|
135
|
+
def get_bibnum(rft)
|
136
|
+
return nil unless @rft_id_bibnum_prefix
|
137
|
+
|
138
|
+
identifier = rft.identifiers.find do |id|
|
139
|
+
id[0, @rft_id_bibnum_prefix.length] == @rft_id_bibnum_prefix
|
140
|
+
end
|
141
|
+
|
142
|
+
if ( identifier )
|
143
|
+
return identifier[@rft_id_bibnum_prefix.length, identifier.length]
|
144
|
+
else
|
145
|
+
return nil
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|