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,69 @@
|
|
1
|
+
# This is a link_out_filter service, it msut be set up in your services.yml
|
2
|
+
# with task: link_out_filter
|
3
|
+
#
|
4
|
+
# no parameters.
|
5
|
+
#
|
6
|
+
## Send an sfx 'pass through' URL _in the background_. Send the user to the
|
7
|
+
# target resource directly, but also send a back-channel hit to SFX
|
8
|
+
# so SFX will record it for statistical purposes. The reason we
|
9
|
+
# do it like this is sometimes the 'pass through' url doesn't work! Better
|
10
|
+
# to just lose the statistic than to mess up the user.
|
11
|
+
#
|
12
|
+
# We also do it all in a background thread, to avoid slowing down the user
|
13
|
+
# with this step if SFX is being slow.
|
14
|
+
require 'uri'
|
15
|
+
require 'net/http'
|
16
|
+
|
17
|
+
class SfxBackchannelRecord < Service
|
18
|
+
|
19
|
+
def initialize(config)
|
20
|
+
@display_name = "SFX Statistics"
|
21
|
+
super(config)
|
22
|
+
@timeout ||= 5
|
23
|
+
end
|
24
|
+
|
25
|
+
# This is meant to be called as task:link_out_filter, it doesn't have an
|
26
|
+
# implementation for handle, it implements link_out_filter() instead.
|
27
|
+
def handle(request)
|
28
|
+
raise "Not implemented."
|
29
|
+
end
|
30
|
+
|
31
|
+
# Hook method called by Umlaut.
|
32
|
+
# We always return nil because we aren't interested in modifying the url,
|
33
|
+
# just using the callback to record the click with SFX.
|
34
|
+
def link_out_filter(orig_url, service_response, other_args = {})
|
35
|
+
# Only work on responses that came from SFX.
|
36
|
+
unless (service_response.service.class.to_s == "Sfx")
|
37
|
+
return nil
|
38
|
+
end
|
39
|
+
make_backchannel_request( service_response )
|
40
|
+
|
41
|
+
|
42
|
+
return nil
|
43
|
+
end
|
44
|
+
|
45
|
+
# Does everything in a background thread to avoid slowing down the user.
|
46
|
+
def make_backchannel_request(service_response)
|
47
|
+
|
48
|
+
Thread.new do
|
49
|
+
begin
|
50
|
+
direct_sfx_url = Sfx.pass_through_url(service_response.data_values)
|
51
|
+
# now we call that url through a back channel just to record it
|
52
|
+
# with SFX.
|
53
|
+
|
54
|
+
parsed_uri = URI.parse(direct_sfx_url )
|
55
|
+
sfx_response = Net::HTTP.get_response( parsed_uri )
|
56
|
+
|
57
|
+
#raise if not 200 OK response
|
58
|
+
unless ( sfx_response.kind_of?(Net::HTTPSuccess) ||
|
59
|
+
sfx_response.kind_of?(Net::HTTPRedirection) )
|
60
|
+
# raise
|
61
|
+
sfx_response.value
|
62
|
+
end
|
63
|
+
rescue Exception => e
|
64
|
+
Rails.logger.error("Could not record sfx backchannel click for service_response id #{service_response.id} ; sfx backchannel url attempted: #{direct_sfx_url} ; problem: #{e}")
|
65
|
+
Rails.logger.error( e.backtrace.join("\n"))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Since this relies on finding holdings that exist, you need to run it in
|
2
|
+
# a service wave AFTER anything that might generate holdings.
|
3
|
+
class TxtHoldingExport < AjaxExport
|
4
|
+
|
5
|
+
def initialize(config)
|
6
|
+
@display_text = "Send to phone"
|
7
|
+
@form_controller = "export_email"
|
8
|
+
@form_action = "txt"
|
9
|
+
# providers is a hash of:
|
10
|
+
# user-presentable-string => hostname for email to txt service.
|
11
|
+
@providers = {
|
12
|
+
"Cingular/AT&T" => "cingularme.com",
|
13
|
+
"Nextel" => "messaging.nextel.com",
|
14
|
+
"Sprint" => "messaging.sprintpcs.com",
|
15
|
+
"T-Mobile"=> "tmomail.net",
|
16
|
+
"Verizon"=> "vtext.com",
|
17
|
+
"Virgin"=> "vmobl.com"
|
18
|
+
}
|
19
|
+
super(config)
|
20
|
+
end
|
21
|
+
|
22
|
+
def handle(request)
|
23
|
+
|
24
|
+
holdings = request.get_service_type('holding', { :refresh => true })
|
25
|
+
unless holdings.nil? or holdings.empty?
|
26
|
+
super(request)
|
27
|
+
else
|
28
|
+
return request.dispatched(self, true)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# Ulrich's provides journal cover images that are _publically_ available, at
|
2
|
+
# predictable urls based on issn. Not sure if this is intentional. But as
|
3
|
+
# an ulrich's subscriber, I feel fine using it. If my insitution was not
|
4
|
+
# an ulrich's subscriber, I probably wouldn't use this service.
|
5
|
+
class UlrichsCover < Service
|
6
|
+
require 'open-uri'
|
7
|
+
|
8
|
+
def service_types_generated
|
9
|
+
return [ServiceTypeValue[:cover_image]]
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(config)
|
13
|
+
@base_url = "http://images.serialssolutions.com/ulrichs/"
|
14
|
+
|
15
|
+
super(config)
|
16
|
+
end
|
17
|
+
|
18
|
+
def handle(request)
|
19
|
+
issn = request.referent.issn
|
20
|
+
|
21
|
+
# We need an ISSN
|
22
|
+
return request.dispatched(self, true) unless issn
|
23
|
+
|
24
|
+
# No hyphens please
|
25
|
+
issn = issn.gsub(/[^0-9X]/, '')
|
26
|
+
|
27
|
+
check_url = @base_url + issn + '.gif'
|
28
|
+
|
29
|
+
# does it exist?
|
30
|
+
if ( url_resolves(check_url) )
|
31
|
+
request.add_service_response(:service => self,
|
32
|
+
:service_type_value => :cover_image ,
|
33
|
+
:url => check_url,
|
34
|
+
:size => "medium" )
|
35
|
+
end
|
36
|
+
|
37
|
+
return request.dispatched(self, true)
|
38
|
+
end
|
39
|
+
|
40
|
+
def url_resolves(url)
|
41
|
+
uri_obj = URI.parse(url)
|
42
|
+
response = Net::HTTP.start(uri_obj.host, uri_obj.port) {|http|
|
43
|
+
http.head(uri_obj.request_uri)
|
44
|
+
}
|
45
|
+
if (response.kind_of?( Net::HTTPSuccess ))
|
46
|
+
return true
|
47
|
+
elsif ( response.kind_of?(Net::HTTPNotFound))
|
48
|
+
return false
|
49
|
+
else
|
50
|
+
# unexpected condition, raise
|
51
|
+
response.value
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# A very simple service that simply generates a "highlighted_link" to
|
2
|
+
# the Ulrich's periodical directory, if an ISSN is present.
|
3
|
+
# Does not actually look up first to make sure there are results, it's
|
4
|
+
# a blind link.
|
5
|
+
# config params:
|
6
|
+
# link_name: Name to put on the link. Defaults to "Periodical Information from Ulrich's Directory".
|
7
|
+
class UlrichsLink < Service
|
8
|
+
|
9
|
+
def initialize(config)
|
10
|
+
# Original one, which just apes the UlrichsWeb html interface, and gives
|
11
|
+
# you a search results screen even with only one hit.
|
12
|
+
#@base_url = "http://www.ulrichsweb.com/ulrichsweb/Search/doAdvancedSearch.asp?QuickCriteria=ISSN&Action=Search&collection=SERIAL&QueryMode=Simple&ResultTemplate=quickSearchResults.hts&SortOrder=Asc&SortField=f_display_title&ScoreThreshold=0&ResultCount=25&SrchFrm=Home&setting_saving=on&QuickCriteriaText="
|
13
|
+
super(config)
|
14
|
+
# better one, which Yvette at Ulrich's showed me for SFX, which seems to work better.
|
15
|
+
@vendor ||= "Umlaut"
|
16
|
+
@base_url ||= "https://ulrichsweb.serialssolutions.com/api/openurl?issn="
|
17
|
+
# Old one
|
18
|
+
#@base_url ||= "http://www.ulrichsweb.com/ulrichsweb/Search/call_fullCitation.asp?/vendor_redirect.asp?oVendor=#{@vendor}&oIssn="
|
19
|
+
@link_name = "Periodical information"
|
20
|
+
end
|
21
|
+
|
22
|
+
def service_types_generated
|
23
|
+
return [ServiceTypeValue[:highlighted_link]]
|
24
|
+
end
|
25
|
+
|
26
|
+
def handle(request)
|
27
|
+
unless (request.referent.issn.blank?)
|
28
|
+
display_text = @link_name
|
29
|
+
|
30
|
+
url = url_for_issn( request.referent.issn )
|
31
|
+
|
32
|
+
request.add_service_response(
|
33
|
+
:service=>self,
|
34
|
+
:url=>url,
|
35
|
+
:display_text=>display_text,
|
36
|
+
:service_type_value => :highlighted_link)
|
37
|
+
end
|
38
|
+
|
39
|
+
return request.dispatched(self, true)
|
40
|
+
end
|
41
|
+
|
42
|
+
def url_for_issn(issn)
|
43
|
+
# with or without hyphen should work fine.
|
44
|
+
return @base_url + issn
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# Link to worldcat.org that relies on screen scraping to see if it's gotten
|
2
|
+
# a hit.
|
3
|
+
#
|
4
|
+
# Warning, worldcat can be awfully slow to respond.
|
5
|
+
# optional search_zip_code param.
|
6
|
+
# Optional base_url param, but I don't know why you'd want to change it.
|
7
|
+
# display_text
|
8
|
+
require 'uri'
|
9
|
+
require 'net/http'
|
10
|
+
class Worldcat < Service
|
11
|
+
include MetadataHelper
|
12
|
+
include UmlautHttp
|
13
|
+
|
14
|
+
def initialize(config)
|
15
|
+
# defaults
|
16
|
+
@suppress_precheck = false # it seems unneccesary to pre-check worldcat, it's mostly ALWAYS a positive hit. And pre-checking against worldcat is running into Worldcat's rate limiting defenses. If neccesary, you can turn this off. Really, we should be using the Worldcat API anyway.
|
17
|
+
@base_url = 'http://www.worldcat.org/'
|
18
|
+
@display_text = 'Find in other libraries'
|
19
|
+
@display_name = 'OCLC WorldCat.org'
|
20
|
+
|
21
|
+
@credits = {
|
22
|
+
"OCLC WorldCat.org" => "http://www.worldcat.org/"
|
23
|
+
}
|
24
|
+
|
25
|
+
super(config)
|
26
|
+
end
|
27
|
+
|
28
|
+
def service_types_generated
|
29
|
+
return [ServiceTypeValue['highlighted_link']]
|
30
|
+
end
|
31
|
+
|
32
|
+
def handle(request)
|
33
|
+
isbn = get_identifier(:urn, "isbn", request.referent)
|
34
|
+
issn = get_identifier(:urn, "issn", request.referent)
|
35
|
+
oclcnum = get_identifier(:info, "oclcnum", request.referent)
|
36
|
+
|
37
|
+
|
38
|
+
isxn_key = nil
|
39
|
+
isxn_value = nil
|
40
|
+
if (! oclcnum.blank?)
|
41
|
+
isxn_key = 'oclc'
|
42
|
+
isxn_value = oclcnum
|
43
|
+
elsif (! issn.blank?)
|
44
|
+
isxn_key = 'issn'
|
45
|
+
#isxn_value = ref_metadata['issn'] + '+dt:ser'
|
46
|
+
isxn_value = issn
|
47
|
+
elsif (! isbn.blank?)
|
48
|
+
isxn_key = 'isbn'
|
49
|
+
isxn_value = isbn
|
50
|
+
else
|
51
|
+
# We have no useful identifiers
|
52
|
+
return request.dispatched(self, true)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Do some cleanup of the value. Sometimes spaces or other
|
56
|
+
# weird chars get in there, why not strip out everything that
|
57
|
+
# isn't a number or X?
|
58
|
+
isxn_value = isxn_value.gsub( /[^\dX]/, '')
|
59
|
+
# and URL escape just to be safe, although really shouldn't be neccesary
|
60
|
+
isxn_value = URI.escape( isxn_value )
|
61
|
+
|
62
|
+
# We do a pre-emptive lookup to worldcat to try and see if worldcat
|
63
|
+
# has a hit or not, before adding the link.
|
64
|
+
isxn_key = URI.escape( isxn_key )
|
65
|
+
uri_str = @base_url+isxn_key+'/'+isxn_value
|
66
|
+
uri_str += "&loc=#{URI.escape(@search_zip_code.to_s)}" if @search_zip_code
|
67
|
+
|
68
|
+
|
69
|
+
begin
|
70
|
+
worldcat_uri = URI.parse(uri_str)
|
71
|
+
rescue Exception => e
|
72
|
+
Rails.logger.error("Bad worldcat uri string constructed?")
|
73
|
+
Rails.logger.error(e)
|
74
|
+
return request.dispatched(self, DispatchedService::FailedFatal)
|
75
|
+
end
|
76
|
+
|
77
|
+
unless ( @suppress_precheck )
|
78
|
+
|
79
|
+
http = Net::HTTP.new worldcat_uri.host
|
80
|
+
http.open_timeout = 7
|
81
|
+
http.read_timeout = 7
|
82
|
+
|
83
|
+
|
84
|
+
begin
|
85
|
+
# Fake being a proxy to send info on actual end-user client to worldcat,
|
86
|
+
# to lessen chance of worldcat traffic limiters.
|
87
|
+
headers = proxy_like_headers( request, worldcat_uri.host )
|
88
|
+
wc_response = http.get(worldcat_uri.path, headers)
|
89
|
+
rescue Timeout::Error => exception
|
90
|
+
return request.dispatched(self, DispatchedService::FailedTemporary, exception)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Bad response code?
|
94
|
+
unless wc_response.code == "200"
|
95
|
+
# Could be temporary, could be fatal. Let's say temporary.
|
96
|
+
return request.dispatched(self, DispatchedService::FailedTemporary, Exception.new("oclc returned error http status code: #{wc_response.code}"))
|
97
|
+
end
|
98
|
+
|
99
|
+
# Sadly, worldcat returns a 200 even if there are no matches.
|
100
|
+
# We need to screen-scrape to discover if there are matches.
|
101
|
+
if (wc_response.body =~ /The page you tried was not found\./)
|
102
|
+
# Not found in worldcat, we won't add a link.
|
103
|
+
return request.dispatched(self, true)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
request.add_service_response(
|
108
|
+
:service=>self,
|
109
|
+
:url=>worldcat_uri.to_s,
|
110
|
+
:display_text=>@display_text,
|
111
|
+
:service_type_value => :highlighted_link
|
112
|
+
)
|
113
|
+
|
114
|
+
return request.dispatched(self, true)
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,591 @@
|
|
1
|
+
# Service that uses available metadata to try to find an exact match to a
|
2
|
+
# WorldCat Identity.
|
3
|
+
#
|
4
|
+
# Requires sufficient author information and/or an oclcnumber to have enough
|
5
|
+
# info to try and find a match. Best to run AFTER services that may enhance
|
6
|
+
# metadata with this info (such as Amazon).
|
7
|
+
#
|
8
|
+
# See: http://outgoing.typepad.com/outgoing/2008/06/linking-to-worl.html
|
9
|
+
#
|
10
|
+
# Creates a highlighted_link
|
11
|
+
# Even though the WorldCat Identities API is built on top of SRU we use
|
12
|
+
# open-uri to simply fetch the "html" page (which is really XML with a stylesheet)
|
13
|
+
# and look at the XML directly.
|
14
|
+
|
15
|
+
#. SRU was too slow and was timing out the background service.
|
16
|
+
# (because SRU parses the response with REXML? we don't want to have anything
|
17
|
+
# to do with REXML! Even still retrieving the large XML file and traversing
|
18
|
+
# it with Hpricot (what we used to use) was still rather slow. The suggestion is to enable few
|
19
|
+
# note_types and then constrain the number shown. The defaults are hopefully
|
20
|
+
# sane in this regard. note_types can be set to false to turn them off.
|
21
|
+
#
|
22
|
+
# Also can create an optional link to Wikipedia.
|
23
|
+
#
|
24
|
+
# There's probably a lot more we could pull out of these identities pages if
|
25
|
+
# we wanted to. If more of these are used they might warrant their own
|
26
|
+
# service type and part of the page for better layout.
|
27
|
+
|
28
|
+
class WorldcatIdentities < Service
|
29
|
+
require 'open-uri' # SRU is too slow even though we use an SRU-like link
|
30
|
+
require 'nokogiri'
|
31
|
+
include MetadataHelper
|
32
|
+
|
33
|
+
attr_reader :url, :note_types, :display_name, :wikipedia_link, :openurl_base,
|
34
|
+
:require_identifier,
|
35
|
+
# below starts the note_types which can be restrained
|
36
|
+
:num_of_roles, :num_of_subject_headings, :num_of_works, :num_of_genres
|
37
|
+
|
38
|
+
def service_types_generated
|
39
|
+
return [ ServiceTypeValue[:highlighted_link] ]
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(config)
|
43
|
+
@url = 'http://worldcat.org/identities/search/'
|
44
|
+
@note_types = ["combined_counts"]
|
45
|
+
@display_name = "WorldCat Identities"
|
46
|
+
@require_identifier = false
|
47
|
+
# any plural note_types can be restrained
|
48
|
+
@num_of_roles = 5
|
49
|
+
@num_of_works = 1
|
50
|
+
@num_of_genres = 5
|
51
|
+
@wikipedia_link = true
|
52
|
+
@openurl_widely_held = true
|
53
|
+
@worldcat_widely_held = false
|
54
|
+
@openurl_base = '/resolve'
|
55
|
+
|
56
|
+
@credits = {
|
57
|
+
"WorldCat Identities" => "http://www.worldcat.org/identities/"
|
58
|
+
}
|
59
|
+
|
60
|
+
super(config)
|
61
|
+
end
|
62
|
+
|
63
|
+
def handle(request)
|
64
|
+
index, query = define_query(request.referent)
|
65
|
+
|
66
|
+
unless query.blank?
|
67
|
+
do_query(request, index, query)
|
68
|
+
end
|
69
|
+
return request.dispatched(self, true)
|
70
|
+
end
|
71
|
+
|
72
|
+
def define_query(rft)
|
73
|
+
oclcnum = get_identifier(:info, "oclcnum", rft)
|
74
|
+
metadata = rft.metadata
|
75
|
+
|
76
|
+
# Do we have enough info to do a query with sufficient precision?
|
77
|
+
# We are choosing better recall in exchange for lower precision.
|
78
|
+
# We'll search with oclcnum if we have it, but not require it, we'll search
|
79
|
+
# fuzzily on various parts of the name if neccesary.
|
80
|
+
if ( oclcnum.blank? && ( metadata['aulast'].blank? || metadata['aufirst'].blank? ) && metadata['au'].blank? && metadata['aucorp'].blank? ) or (oclcnum.blank? && @require_identifier)
|
81
|
+
Rails.logger.debug("Worldcat Identities Service Adaptor: Skipped: Insufficient metadata for lookup")
|
82
|
+
return nil
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
# instead of searching across all indexes we target the one we want
|
87
|
+
name_operator = "%3D"
|
88
|
+
if ((! metadata['aulast'].blank?) && oclcnum)
|
89
|
+
# Just last name is enough, we have an oclcnum.
|
90
|
+
index = 'PersonalIdentities'
|
91
|
+
name_part = 'FamilyName'
|
92
|
+
name = clean_name(metadata['aulast'])
|
93
|
+
elsif (! metadata['au'].blank? )
|
94
|
+
# Next choice, undivided author string
|
95
|
+
index = "PersonalIdentities"
|
96
|
+
name_part = 'Name'
|
97
|
+
name = clean_name(metadata['au'])
|
98
|
+
name_operator = "all"
|
99
|
+
elsif (not metadata['aulast'].blank? and not metadata['aufirst'].blank?)
|
100
|
+
# combine them.
|
101
|
+
index = "PersonalIdentities"
|
102
|
+
name_part = 'Name'
|
103
|
+
name = clean_name(metadata['aufirst'] + ' ' + metadata['aulast'])
|
104
|
+
name_operator = "all"
|
105
|
+
elsif metadata['aucorp']
|
106
|
+
# corp name
|
107
|
+
index = 'CorporateIdentities'
|
108
|
+
name_part = 'Name'
|
109
|
+
name = clean_name(metadata['aucorp'])
|
110
|
+
else
|
111
|
+
# oclcnum but no author information at all! Might still work...
|
112
|
+
index = "Identities"
|
113
|
+
end
|
114
|
+
|
115
|
+
query_conditions = []
|
116
|
+
query_conditions << "local.#{name_part}+#{name_operator}+%22#{name}%22" if name
|
117
|
+
query_conditions << "local.OCLCNumber+%3D+%22#{CGI.escape(oclcnum)}%22" unless oclcnum.blank?
|
118
|
+
|
119
|
+
query = query_conditions.join("+and+")
|
120
|
+
|
121
|
+
# Sort keys is important when we don't have an oclcnumber, and doesn't hurt
|
122
|
+
# when we do.
|
123
|
+
query += "&sortKeys=holdingscount"
|
124
|
+
return index, query
|
125
|
+
end
|
126
|
+
|
127
|
+
# We might have to remove certain characters, but for now we just CGI.escape
|
128
|
+
# it and remove any periods
|
129
|
+
def clean_name(name)
|
130
|
+
CGI.escape(name).gsub('.', '')
|
131
|
+
end
|
132
|
+
|
133
|
+
def do_query(request, index, query)
|
134
|
+
# since we're only doing exact matching with last name and OCLCnum
|
135
|
+
# we only request 1 record to hopefully speed things up.
|
136
|
+
link = @url + index + '?query=' +query + "&maximumRecords=1"
|
137
|
+
|
138
|
+
result = open(link).read
|
139
|
+
xml = Nokogiri::XML(result)
|
140
|
+
|
141
|
+
# Identities namespaces are all over the place, it's too hard
|
142
|
+
# to interrogate with namespaces, ask nokogiri to remove them all
|
143
|
+
# instead.
|
144
|
+
xml.remove_namespaces!
|
145
|
+
|
146
|
+
|
147
|
+
return nil if xml.at("numberOfRecords").inner_text == '0'
|
148
|
+
create_link(request, xml)
|
149
|
+
create_wikipedia_link(request, xml) if @wikipedia_link
|
150
|
+
create_openurl_widely_held(request, xml) if @openurl_widely_held
|
151
|
+
create_worldcat_widely_held(request, xml) if @worldcat_widely_held
|
152
|
+
end
|
153
|
+
|
154
|
+
def create_link(request, xml)
|
155
|
+
display_name = "About " + extract_display_name(xml)
|
156
|
+
extracted_notes = extract_notes(xml) if @note_types
|
157
|
+
url = extract_url(xml)
|
158
|
+
create_service_response(request, display_name, url, extracted_notes )
|
159
|
+
end
|
160
|
+
|
161
|
+
def extract_notes(xml)
|
162
|
+
note_pieces = []
|
163
|
+
# a tiny bit of metaprogramming to make it easy to add methods and config
|
164
|
+
# for note_types
|
165
|
+
@note_types.each do |nt|
|
166
|
+
method = ("extract_" + nt).to_sym
|
167
|
+
answer = self.send(method, xml)
|
168
|
+
note_pieces << answer unless answer.nil?
|
169
|
+
end
|
170
|
+
return nil if note_pieces.blank?
|
171
|
+
return note_pieces.join(' | ')
|
172
|
+
end
|
173
|
+
|
174
|
+
def extract_display_name(doc)
|
175
|
+
name = []
|
176
|
+
rawname = doc.at("nameInfo/rawName")
|
177
|
+
return nil unless rawname
|
178
|
+
rawname.children.each do |name_part|
|
179
|
+
name << name_part.inner_text
|
180
|
+
end
|
181
|
+
return nil if name.blank?
|
182
|
+
return name.join(' ')
|
183
|
+
end
|
184
|
+
|
185
|
+
def extract_subject_headings(doc)
|
186
|
+
subject_headings = []
|
187
|
+
(doc.search("biogSH")).each_with_index do |sh, i|
|
188
|
+
subject_headings << sh.inner_text
|
189
|
+
break if @num_of_subject_headings == i + 1
|
190
|
+
end
|
191
|
+
return nil if subject_headings.blank?
|
192
|
+
"subject headings: " + subject_headings.join('; ')
|
193
|
+
end
|
194
|
+
|
195
|
+
def extract_roles(doc)
|
196
|
+
codes = []
|
197
|
+
(doc.search("relators/relator")).each_with_index do |relate, i|
|
198
|
+
codes << relate.attributes['code']
|
199
|
+
break if @num_of_roles == i + 1
|
200
|
+
end
|
201
|
+
return nil if codes.blank?
|
202
|
+
roles = codes.map{|code| RELATOR_CODES[code] }
|
203
|
+
"roles: " + roles.join(', ')
|
204
|
+
end
|
205
|
+
|
206
|
+
# FIXME a lot more could be done with "by citations". identities gives summaries
|
207
|
+
# of the most popular works as well as other descriptive information like
|
208
|
+
# subject headings. This might be able to be used for enhancing metadata.
|
209
|
+
def extract_works(doc)
|
210
|
+
works = []
|
211
|
+
doc.search("by/citation/title").each_with_index do |t, i|
|
212
|
+
works << t.inner_text
|
213
|
+
break if @num_of_works == i + 1
|
214
|
+
end
|
215
|
+
return nil if works.blank?
|
216
|
+
"most widely held #{works.length == 1 ? "work" : "works"}: " + works.join("; ")
|
217
|
+
end
|
218
|
+
|
219
|
+
def extract_genres(doc)
|
220
|
+
genres = []
|
221
|
+
doc.search("genres/genre").each_with_index do |g, i|
|
222
|
+
genres << g.inner_text
|
223
|
+
break if @num_of_genres == i + 1
|
224
|
+
end
|
225
|
+
return nil if genres.blank?
|
226
|
+
"genres: " + genres.join(', ')
|
227
|
+
end
|
228
|
+
|
229
|
+
def extract_combined_counts(doc)
|
230
|
+
work_count = extract_work_count(doc)
|
231
|
+
publications_count = extract_publications_count(doc)
|
232
|
+
holdings_count = extract_holdings_count(doc)
|
233
|
+
work_count << " in " << publications_count << " with " <<
|
234
|
+
holdings_count
|
235
|
+
end
|
236
|
+
|
237
|
+
def extract_work_count(doc)
|
238
|
+
work_count = doc.at("workCount").inner_text
|
239
|
+
return insert_commas(work_count) << " works"
|
240
|
+
end
|
241
|
+
|
242
|
+
def extract_holdings_count(doc)
|
243
|
+
total_holdings = doc.at("totalHoldings").inner_text
|
244
|
+
return insert_commas(total_holdings) << " total holdings in WorldCat"
|
245
|
+
end
|
246
|
+
|
247
|
+
def extract_publications_count(doc)
|
248
|
+
return insert_commas( doc.at("recordCount").inner_text ) << " publications"
|
249
|
+
end
|
250
|
+
|
251
|
+
def extract_url(doc)
|
252
|
+
pnkey = doc.at("pnkey").inner_text
|
253
|
+
return 'http://worldcat.org/identities/' << pnkey
|
254
|
+
end
|
255
|
+
|
256
|
+
def insert_commas(n)
|
257
|
+
n.reverse.scan(/(?:\d*\.)?\d{1,3}-?/).join(',').reverse
|
258
|
+
end
|
259
|
+
|
260
|
+
def create_service_response(request, display_name, url, extracted_notes)
|
261
|
+
request.add_service_response(
|
262
|
+
:service=>self,
|
263
|
+
:url=>url,
|
264
|
+
:display_text=>display_name,
|
265
|
+
:notes => extracted_notes,
|
266
|
+
:service_type_value => :highlighted_link)
|
267
|
+
end
|
268
|
+
|
269
|
+
def create_wikipedia_link(request, xml)
|
270
|
+
name_element = xml.at("wikiLink")
|
271
|
+
return nil unless name_element
|
272
|
+
name = name_element.inner_text
|
273
|
+
# This is the base link that worldcat identities uses so we use the same
|
274
|
+
link = "http://en.wikipedia.org/wiki/Special:Search?search=" << name
|
275
|
+
request.add_service_response(
|
276
|
+
:service=>self,
|
277
|
+
:url=>link,
|
278
|
+
:display_text=> "About " + name.titlecase,
|
279
|
+
:notes => '',
|
280
|
+
:source => 'Wikipedia',
|
281
|
+
:service_type_value => :highlighted_link)
|
282
|
+
end
|
283
|
+
|
284
|
+
def create_openurl_widely_held(request, xml)
|
285
|
+
widely_held = get_widely_held_info(xml)
|
286
|
+
# try to remove circular links
|
287
|
+
return nil if circular_link?(request, widely_held)
|
288
|
+
|
289
|
+
openurl = create_openurl(request, widely_held)
|
290
|
+
|
291
|
+
request.add_service_response(
|
292
|
+
:service=>self,
|
293
|
+
:url=>openurl,
|
294
|
+
:display_text=> widely_held['title'],
|
295
|
+
:notes => "This author's most widely held work.",
|
296
|
+
:service_type_value => :highlighted_link)
|
297
|
+
end
|
298
|
+
|
299
|
+
def circular_link?(request, citation_info)
|
300
|
+
rft = request.referent
|
301
|
+
request_oclcnum = get_identifier(:info, "oclcnum", rft)
|
302
|
+
request_title = get_search_title(rft)
|
303
|
+
return true if citation_info['oclcnum'] == request_oclcnum
|
304
|
+
#further cleaning might be necessary for titles to be good matches
|
305
|
+
return true if citation_info['title'].strip == request_title.strip
|
306
|
+
end
|
307
|
+
|
308
|
+
#createsa minimal openurl to make a new request to umlaut
|
309
|
+
def create_openurl(request, wh)
|
310
|
+
metadata = request.referent.metadata
|
311
|
+
|
312
|
+
co = OpenURL::ContextObject.new
|
313
|
+
cor = co.referent
|
314
|
+
cor.set_format(wh['record_type'])
|
315
|
+
cor.add_identifier("info:oclcnum/#{wh['oclcnum']}")
|
316
|
+
cor.set_metadata('aulast', metadata['aulast'] ) if metadata['aulast']
|
317
|
+
cor.set_metadata('aufirst', metadata['aufirst']) if metadata['aufirst']
|
318
|
+
cor.set_metadata('aucorp', metadata['aucorp']) if metadata['aucorp']
|
319
|
+
cor.set_metadata('title', wh['title'])
|
320
|
+
link = @openurl_base + '?' + co.kev
|
321
|
+
return link
|
322
|
+
end
|
323
|
+
|
324
|
+
# We just link to worldcat using the oclc number provided
|
325
|
+
# FIXME this might need special partial if we incorporate a cover image
|
326
|
+
def create_worldcat_widely_held(request, xml)
|
327
|
+
|
328
|
+
# try to prevent circular links
|
329
|
+
top_holding_info = get_widely_held_info(xml)
|
330
|
+
return nil if circular_link?(request, top_holding_info)
|
331
|
+
|
332
|
+
# http://www.worldcat.org/links/
|
333
|
+
most = top_holding_info['most']
|
334
|
+
title = top_holding_info['title']
|
335
|
+
oclcnum = top_holding_info['oclcnum']
|
336
|
+
|
337
|
+
link = 'http://www.worldcat.org/oclc/' << oclcnum
|
338
|
+
cover_image_link = extract_cover_image_link(request, most)
|
339
|
+
notes = "this author's most widely held work in WorldCat"
|
340
|
+
if cover_image_link
|
341
|
+
display_text = '<img src="' << cover_image_link << '" style="width:75px;"/>'
|
342
|
+
notes = title << ' is ' << notes
|
343
|
+
else
|
344
|
+
display_text = title
|
345
|
+
end
|
346
|
+
|
347
|
+
request.add_service_response(
|
348
|
+
:service=>self,
|
349
|
+
:url=>link,
|
350
|
+
:display_text=> display_text,
|
351
|
+
:notes => notes,
|
352
|
+
:service_type_value => :highlighted_link)
|
353
|
+
end
|
354
|
+
|
355
|
+
def get_widely_held_info(xml)
|
356
|
+
h = {}
|
357
|
+
h['most'] = most = xml.at("by/citation")
|
358
|
+
h['oclcnum'] = clean_oclcnum(most.at("oclcnum").inner_text)
|
359
|
+
h['title'] = most.at("title").inner_text
|
360
|
+
h['record_type'] = most.at('recordType').inner_text
|
361
|
+
h
|
362
|
+
end
|
363
|
+
|
364
|
+
def extract_cover_image_link(request, citation)
|
365
|
+
cover = citation.at("cover")
|
366
|
+
return nil unless cover
|
367
|
+
# we try not to show a cover if we already probably have the same cover
|
368
|
+
# showing.
|
369
|
+
oclc = clean_oclcnum( cover.attributes['oclc'] )
|
370
|
+
metadata = request.referent.metadata
|
371
|
+
if metadata['oclcnum'] and metadata['oclcnum'] =~ oclc
|
372
|
+
return nil
|
373
|
+
end
|
374
|
+
cover_number = cover.inner_text
|
375
|
+
if metadata['isbn'] and metadata['isbn'] == cover_number
|
376
|
+
return nil
|
377
|
+
end
|
378
|
+
|
379
|
+
if cover.attributes["type"] == 'isbn'
|
380
|
+
link = "http://www.worldcat.org/wcpa/servlet/DCARead?standardNoType=1&standardNo="
|
381
|
+
return link << cover_number
|
382
|
+
end
|
383
|
+
return nil
|
384
|
+
end
|
385
|
+
|
386
|
+
def clean_oclcnum(num)
|
387
|
+
# got the follow from referent.rb ~152 and added ocn
|
388
|
+
if num =~ /(ocn0*|ocm0*|\(OCoLC\)|ocl70*|0+)(.*)$/
|
389
|
+
num = $2
|
390
|
+
end
|
391
|
+
return num
|
392
|
+
end
|
393
|
+
|
394
|
+
# relator codes are from http://worldcat.org/identities/relators.xml which was
|
395
|
+
# referenced from http://worldcat.org/identities/Identities.xsl
|
396
|
+
RELATOR_CODES = {
|
397
|
+
"act" => "Actor",
|
398
|
+
"adp" => "Adapter",
|
399
|
+
"aft" => "Author of afterword, colophon, etc.",
|
400
|
+
"anm" => "Animator ",
|
401
|
+
"ann" => "Annotator",
|
402
|
+
"ant" => "Bibliographic antecedent",
|
403
|
+
"app" => "Applicant",
|
404
|
+
"aqt" => "Author in quotations or text abstracts",
|
405
|
+
"arc" => "Architect",
|
406
|
+
"arr" => "Arranger",
|
407
|
+
"art" => "Artist",
|
408
|
+
"asg" => "Assignee",
|
409
|
+
"asn" => "Associated name",
|
410
|
+
"att" => "Attributed name",
|
411
|
+
"auc" => "Auctioneer",
|
412
|
+
"aud" => "Author of dialog",
|
413
|
+
"aui" => "Author of introduction",
|
414
|
+
"aus" => "Author of screenplay",
|
415
|
+
"aut" => "Author",
|
416
|
+
"bdd" => "Binding designer",
|
417
|
+
"bjd" => "Bookjacket designer",
|
418
|
+
"bkd" => "Book designer",
|
419
|
+
"bkp" => "Book producer",
|
420
|
+
"bnd" => "Binder",
|
421
|
+
"bpd" => "Bookplate designer",
|
422
|
+
"bsl" => "Bookseller",
|
423
|
+
"ccp" => "Conceptor",
|
424
|
+
"chr" => "Choreographer",
|
425
|
+
"clb" => "Collaborator",
|
426
|
+
"cli" => "Client",
|
427
|
+
"cll" => "Calligrapher",
|
428
|
+
"clt" => "Collotyper",
|
429
|
+
"cmm" => "Commentator",
|
430
|
+
"cmp" => "Composer",
|
431
|
+
"cmt" => "Compositor",
|
432
|
+
"cng" => "Cinematographer ",
|
433
|
+
"cnd" => "Conductor",
|
434
|
+
"cns" => "Censor",
|
435
|
+
"coe" => "Contestant -appellee",
|
436
|
+
"col" => "Collector",
|
437
|
+
"com" => "Compiler",
|
438
|
+
"cos" => "Contestant",
|
439
|
+
"cot" => "Contestant -appellant",
|
440
|
+
"cov" => "Cover designer",
|
441
|
+
"cpc" => "Copyright claimant",
|
442
|
+
"cpe" => "Complainant-appellee",
|
443
|
+
"cph" => "Copyright holder",
|
444
|
+
"cpl" => "Complainant",
|
445
|
+
"cpt" => "Complainant-appellant",
|
446
|
+
"cre" => "Creator",
|
447
|
+
"crp" => "Correspondent",
|
448
|
+
"crr" => "Corrector",
|
449
|
+
"csl" => "Consultant",
|
450
|
+
"csp" => "Consultant to a project",
|
451
|
+
"cst" => "Costume designer",
|
452
|
+
"ctb" => "Contributor",
|
453
|
+
"cte" => "Contestee-appellee",
|
454
|
+
"ctg" => "Cartographer",
|
455
|
+
"ctr" => "Contractor",
|
456
|
+
"cts" => "Contestee",
|
457
|
+
"ctt" => "Contestee-appellant",
|
458
|
+
"cur" => "Curator",
|
459
|
+
"cwt" => "Commentator for written text",
|
460
|
+
"dfd" => "Defendant",
|
461
|
+
"dfe" => "Defendant-appellee",
|
462
|
+
"dft" => "Defendant-appellant",
|
463
|
+
"dgg" => "Degree grantor",
|
464
|
+
"dis" => "Dissertant",
|
465
|
+
"dln" => "Delineator",
|
466
|
+
"dnc" => "Dancer",
|
467
|
+
"dnr" => "Donor",
|
468
|
+
"dpc" => "Depicted",
|
469
|
+
"dpt" => "Depositor",
|
470
|
+
"drm" => "Draftsman",
|
471
|
+
"drt" => "Director",
|
472
|
+
"dsr" => "Designer",
|
473
|
+
"dst" => "Distributor",
|
474
|
+
"dte" => "Dedicatee",
|
475
|
+
"dto" => "Dedicator",
|
476
|
+
"dub" => "Dubious author",
|
477
|
+
"edt" => "Editor",
|
478
|
+
"egr" => "Engraver",
|
479
|
+
"elt" => "Electrotyper",
|
480
|
+
"eng" => "Engineer",
|
481
|
+
"etr" => "Etcher",
|
482
|
+
"exp" => "Expert",
|
483
|
+
"fac" => "Facsimilist",
|
484
|
+
"flm" => "Film editor",
|
485
|
+
"fmo" => "Former owner",
|
486
|
+
"fpy" => "First party",
|
487
|
+
"fnd" => "Funder",
|
488
|
+
"frg" => "Forger",
|
489
|
+
"grt" => "Graphic technician",
|
490
|
+
"hnr" => "Honoree",
|
491
|
+
"hst" => "Host",
|
492
|
+
"ill" => "Illustrator",
|
493
|
+
"ilu" => "Illuminator",
|
494
|
+
"ins" => "Inscriber",
|
495
|
+
"inv" => "Inventor",
|
496
|
+
"itr" => "Instrumentalist",
|
497
|
+
"ive" => "Interviewee",
|
498
|
+
"ivr" => "Interviewer",
|
499
|
+
"lbt" => "Librettist",
|
500
|
+
"lee" => "Libelee-appellee",
|
501
|
+
"lel" => "Libelee",
|
502
|
+
"len" => "Lender",
|
503
|
+
"let" => "Libelee-appellant",
|
504
|
+
"lgd" => "Lighting designer ",
|
505
|
+
"lie" => "Libelant-appellee",
|
506
|
+
"lil" => "Libelant",
|
507
|
+
"lit" => "Libelant-appellant",
|
508
|
+
"lsa" => "Landscape architect",
|
509
|
+
"lse" => "Licensee",
|
510
|
+
"lso" => "Licensor",
|
511
|
+
"ltg" => "Lithographer",
|
512
|
+
"lyr" => "Lyricist",
|
513
|
+
"mfr" => "Manufacturer ",
|
514
|
+
"mdc" => "Metadata contact",
|
515
|
+
"mod" => "Moderator",
|
516
|
+
"mon" => "Monitor",
|
517
|
+
"mrk" => "Markup editor",
|
518
|
+
"mte" => "Metal-engraver",
|
519
|
+
"mus" => "Musician",
|
520
|
+
"nrt" => "Narrator",
|
521
|
+
"opn" => "Opponent",
|
522
|
+
"org" => "Originator",
|
523
|
+
"orm" => "Organizer of meeting",
|
524
|
+
"oth" => "Other",
|
525
|
+
"own" => "Owner",
|
526
|
+
"pat" => "Patron",
|
527
|
+
"pbd" => "Publishing director",
|
528
|
+
"pbl" => "Publisher",
|
529
|
+
"pfr" => "Proofreader",
|
530
|
+
"pht" => "Photographer",
|
531
|
+
"plt" => "Platemaker",
|
532
|
+
"pop" => "Printer of plates",
|
533
|
+
"ppm" => "Papermaker",
|
534
|
+
"ppt" => "Puppeteer ",
|
535
|
+
"prc" => "Process contact",
|
536
|
+
"prd" => "Production personnel",
|
537
|
+
"prf" => "Performer",
|
538
|
+
"prg" => "Programmer",
|
539
|
+
"prm" => "Printmaker",
|
540
|
+
"pro" => "Producer",
|
541
|
+
"prt" => "Printer",
|
542
|
+
"pta" => "Patent applicant",
|
543
|
+
"pte" => "Plaintiff -appellee",
|
544
|
+
"ptf" => "Plaintiff",
|
545
|
+
"pth" => "Patent holder",
|
546
|
+
"ptt" => "Plaintiff-appellant",
|
547
|
+
"rbr" => "Rubricator",
|
548
|
+
"rce" => "Recording engineer",
|
549
|
+
"rcp" => "Recipient",
|
550
|
+
"red" => "Redactor",
|
551
|
+
"ren" => "Renderer",
|
552
|
+
"res" => "Researcher",
|
553
|
+
"rev" => "Reviewer",
|
554
|
+
"rpt" => "Reporter",
|
555
|
+
"rpy" => "Responsible party",
|
556
|
+
"rse" => "Respondent -appellee",
|
557
|
+
"rsg" => "Restager ",
|
558
|
+
"rsp" => "Respondent",
|
559
|
+
"rst" => "Respondent-appellant",
|
560
|
+
"rth" => "Research team head",
|
561
|
+
"rtm" => "Research team member",
|
562
|
+
"sad" => "Scientific advisor",
|
563
|
+
"sce" => "Scenarist",
|
564
|
+
"scl" => "Sculptor",
|
565
|
+
"scr" => "Scribe",
|
566
|
+
"sec" => "Secretary",
|
567
|
+
"sgn" => "Signer",
|
568
|
+
"sng" => "Singer",
|
569
|
+
"spk" => "Speaker",
|
570
|
+
"spn" => "Sponsor",
|
571
|
+
"spy" => "Second party",
|
572
|
+
"srv" => "Surveyor",
|
573
|
+
"std" => "Set designer ",
|
574
|
+
"stl" => "Storyteller",
|
575
|
+
"stn" => "Standards body",
|
576
|
+
"str" => "Stereotyper",
|
577
|
+
"tch" => "Teacher ",
|
578
|
+
"ths" => "Thesis advisor",
|
579
|
+
"trc" => "Transcriber",
|
580
|
+
"trl" => "Translator",
|
581
|
+
"tyd" => "Type designer",
|
582
|
+
"tyg" => "Typographer",
|
583
|
+
"vdg" => "Videographer ",
|
584
|
+
"voc" => "Vocalist",
|
585
|
+
"wam" => "Writer of accompanying material",
|
586
|
+
"wdc" => "Woodcutter",
|
587
|
+
"wde" => "Wood -engraver",
|
588
|
+
"wit" => "Witness"
|
589
|
+
}
|
590
|
+
|
591
|
+
end
|