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
data/lib/hip3/receipt.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
module Hip3
|
2
|
+
# Keeps a reference to it's bib, if it needs to load it's data,
|
3
|
+
# it asks bib to load all
|
4
|
+
# data, and the bib loads it at once for all copies, in one fetch.
|
5
|
+
class SerialCopy < Holding
|
6
|
+
@@Field_labels = {:location => 'Location', :collection => 'Collection', :call_no => 'Call No.', :copy_str => 'Copy No.', :status => 'Status', :notes => 'Notes'}
|
7
|
+
attr_accessor :items # array of items
|
8
|
+
attr_accessor :items_loaded
|
9
|
+
attr_accessor :runs # array of run types/statements
|
10
|
+
|
11
|
+
def initialize(argBibObj, serialXmlElement=nil)
|
12
|
+
self.bib = argBibObj
|
13
|
+
self.items_loaded = false
|
14
|
+
if ( serialXmlElement )
|
15
|
+
loadFromSerialElement( serialXmlElement )
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def items
|
20
|
+
bib.load_items_from_store if ! items_loaded?
|
21
|
+
|
22
|
+
return @items || []
|
23
|
+
end
|
24
|
+
|
25
|
+
def items_loaded?
|
26
|
+
return (items_loaded == true)
|
27
|
+
end
|
28
|
+
|
29
|
+
def loadFromSerialElement( serialElement )
|
30
|
+
self.location_str = serialElement.at('/location').inner_text
|
31
|
+
self.id = serialElement.at('/copykey').inner_text
|
32
|
+
|
33
|
+
# Okay, this part is potentially fragile, we have to pull out based on
|
34
|
+
# order in the XML, not sure if that can change. Sorry, that's HIP for you.
|
35
|
+
copyElements = serialElement.search('/copy/cell/data/text').collect {|e| e.inner_text}
|
36
|
+
# Fix this to use field lookup
|
37
|
+
self.location_str = bib.copy_field_lookup.text_value_for(copyElements, @@Field_labels[:location])
|
38
|
+
self.collection_str = bib.copy_field_lookup.text_value_for(copyElements, @@Field_labels[:collection])
|
39
|
+
self.call_no = bib.copy_field_lookup.text_value_for(copyElements, @@Field_labels[:call_no])
|
40
|
+
self.copy_str = bib.copy_field_lookup.text_value_for(copyElements, @@Field_labels[:copy_str])
|
41
|
+
self.status_str = bib.copy_field_lookup.text_value_for(copyElements, @@Field_labels[:status])
|
42
|
+
self.notes = bib.copy_field_lookup.text_value_for(copyElements, @@Field_labels[:notes])
|
43
|
+
|
44
|
+
|
45
|
+
#Okay, got to get the 'runs' for summary holdings info.
|
46
|
+
self.runs ||= []
|
47
|
+
serialElement.search('/runlist/run').each do |run|
|
48
|
+
label = run.at('/runlabel').inner_text
|
49
|
+
run.search('/data/rundata').each do |rundata|
|
50
|
+
run = {:label => label, :statement => textValue(rundata.at('/text'))}
|
51
|
+
run[:note] = textValue(rundata.at('/note'))
|
52
|
+
|
53
|
+
self.runs.push( run )
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Not too useful, use coverage_str_to_a instead usually
|
59
|
+
def coverage_str
|
60
|
+
return runs.to_s
|
61
|
+
end
|
62
|
+
|
63
|
+
# Over-riding
|
64
|
+
def coverage_str_to_a
|
65
|
+
runs.collect do |r|
|
66
|
+
s = ''
|
67
|
+
(s << r[:label] << ": ") if (! r[:label].blank?) && r[:label] != "Main run"
|
68
|
+
s << r[:statement]
|
69
|
+
s << '-- ' << r[:note] if r[:note]
|
70
|
+
s
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def register_item(item)
|
75
|
+
items ||= []
|
76
|
+
|
77
|
+
unless items.include?(item)
|
78
|
+
items.push(item)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/holding.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
class Holding
|
2
|
+
attr_accessor :locations, :identifier
|
3
|
+
def initialize
|
4
|
+
@locations = []
|
5
|
+
end
|
6
|
+
def find_location(location)
|
7
|
+
@locations.each do | loc |
|
8
|
+
return loc if loc.name == location
|
9
|
+
end
|
10
|
+
return nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def find_item_by_attribute(key, value)
|
14
|
+
@locations.each do | loc |
|
15
|
+
loc.items.each do | item |
|
16
|
+
return if item.instance_variable_get('@'+key) == value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
return nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class HoldingLocation
|
24
|
+
attr_accessor :name, :code, :items
|
25
|
+
def initialize
|
26
|
+
@items = []
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class HoldingItem
|
31
|
+
attr_accessor :identifier, :status_code, :status_date, :status, :call_number, :enumeration, :chron, :year
|
32
|
+
end
|
data/lib/marc_helper.rb
ADDED
@@ -0,0 +1,254 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module MarcHelper
|
4
|
+
|
5
|
+
# Takes an array of ruby MARC objects, adds ServiceResponses
|
6
|
+
# for the 856 links contained.
|
7
|
+
# Returns a hash of arrays of ServiceResponse objects added, keyed
|
8
|
+
# by service type value string.
|
9
|
+
def add_856_links(request, marc_records, options = {})
|
10
|
+
options[:default_service_type] ||= "fulltext"
|
11
|
+
options[:match_reliability] ||= ServiceResponse::MatchExact
|
12
|
+
|
13
|
+
responses_added = Hash.new
|
14
|
+
|
15
|
+
# Keep track of urls to avoid putting the exact same url in twice
|
16
|
+
urls_seen = Array.new
|
17
|
+
|
18
|
+
marc_records.each do |marc_xml|
|
19
|
+
|
20
|
+
marc_xml.find_all {|f| '856' === f.tag}.each do |field|
|
21
|
+
# Might have more than one $u, in which case we want to
|
22
|
+
# possibly add each of them. Might have 0 $u in which case
|
23
|
+
# we skip.
|
24
|
+
field.subfields.find_all {|sf| sf.code == 'u'}.each do |sf|
|
25
|
+
url = sf.value
|
26
|
+
|
27
|
+
# Already got it from another catalog record?
|
28
|
+
next if urls_seen.include?(url)
|
29
|
+
|
30
|
+
# Trying to avoid duplicates with SFX/link resolver.
|
31
|
+
next if should_skip_856_link?(request, marc_xml, url)
|
32
|
+
|
33
|
+
urls_seen.push(url)
|
34
|
+
|
35
|
+
|
36
|
+
display_name = nil
|
37
|
+
if field['y']
|
38
|
+
display_name = field['y']
|
39
|
+
else
|
40
|
+
# okay let's try taking just the domain from the url
|
41
|
+
begin
|
42
|
+
u_obj = URI::parse( url )
|
43
|
+
display_name = u_obj.host
|
44
|
+
rescue Exception
|
45
|
+
end
|
46
|
+
# Okay, can't parse out a domain, whole url then.
|
47
|
+
display_name = url if display_name.nil?
|
48
|
+
end
|
49
|
+
# But if we've got a $3, the closest MARC comes to a field
|
50
|
+
# that explains what this actually IS, use that too please.
|
51
|
+
display_name = field['3'] + ' from ' + display_name if field['3']
|
52
|
+
|
53
|
+
# Build the response.
|
54
|
+
|
55
|
+
response_params = {:service=>self, :display_text=>display_name, :url=>url}
|
56
|
+
# get all those $z subfields and put em in notes.
|
57
|
+
response_params[:url] = url
|
58
|
+
|
59
|
+
# subfield 3 is being used for OCA records loaded in our catalog.
|
60
|
+
response_params[:notes] =
|
61
|
+
field.subfields.collect {|f| f.value if (f.code == 'z') }.compact.join('; ')
|
62
|
+
|
63
|
+
is_journal = (marc_xml.leader[7,1] == 's')
|
64
|
+
unless ( field['3'] || ! is_journal ) # subfield 3 is in fact some kind of coverage note, usually
|
65
|
+
response_params[:notes] += "; " unless response_params[:notes].blank?
|
66
|
+
response_params[:notes] += "Dates of coverage unknown."
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
unless ( options[:match_reliability] == ServiceResponse::MatchExact )
|
71
|
+
response_params[:match_reliability] = options[:match_reliability]
|
72
|
+
|
73
|
+
response_params[:edition_str] = edition_statement(marc_xml)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Figure out the right service type value for this, fulltext, ToC,
|
77
|
+
# whatever.
|
78
|
+
response_params[:service_type_value] = service_type_for_856( field, options )
|
79
|
+
|
80
|
+
# fulltext urls from MARC are always marked as specially stupid.
|
81
|
+
response_params[:coverage_checked] = false
|
82
|
+
response_params[:can_link_to_article] = false
|
83
|
+
|
84
|
+
# Some debugging info, add the 001 bibID if we have one.
|
85
|
+
|
86
|
+
response_params[:debug_info] = "BibID: #{marc_xml['001'].value}" if marc_xml['001']
|
87
|
+
|
88
|
+
|
89
|
+
# Add the response
|
90
|
+
response = request.add_service_response(response_params)
|
91
|
+
|
92
|
+
responses_added[response_params[:service_type_value]] ||= Array.new
|
93
|
+
responses_added[response_params[:service_type_value]].push(response)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
return responses_added
|
98
|
+
end
|
99
|
+
|
100
|
+
# Used by #add_856_links. Complicated logic to try and avoid
|
101
|
+
# presenting a URL from the catalog that duplicates what SFX does,
|
102
|
+
# but present a URL from the catalog when it's really needed.
|
103
|
+
#
|
104
|
+
# One reason not to include Catalog links for an article-level
|
105
|
+
# citation, even if SFX provided no targets, is maybe SFX
|
106
|
+
# provided no targets because SFX _knew_ that the _particular date_
|
107
|
+
# requested is not available. The catalog doesn't know that, but
|
108
|
+
# we don't want to show a link from the catalog that SFX really
|
109
|
+
# already knew wasn't going to be available.
|
110
|
+
#
|
111
|
+
# So:
|
112
|
+
#
|
113
|
+
# If this is a journal, skip the URL if it matches in our
|
114
|
+
# SFXUrl finder, because that means we think it's an SFX controlled
|
115
|
+
# URL. But if it's not a journal, use it anyway, because it's probably
|
116
|
+
# an e-book that is not in SFX, even if it's from a vendor who is in
|
117
|
+
# SFX. We use MARC leader byte 7 to tell if it's a journal. Confusing enough?
|
118
|
+
# Not yet! Even if it is a journal, if this isn't an article-level
|
119
|
+
# cite and there are no other full text already provided, we
|
120
|
+
# still include.
|
121
|
+
def should_skip_856_link?(request, marc_record, url)
|
122
|
+
is_journal = (marc_record.leader[7,1] == 's')
|
123
|
+
|
124
|
+
return ( is_journal &&
|
125
|
+
SfxUrl.sfx_controls_url?(url) &&
|
126
|
+
!( request.title_level_citation? &&
|
127
|
+
request.get_service_type("fulltext").length == 0
|
128
|
+
)
|
129
|
+
)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Take a ruby Marc Field object representing an 856 field,
|
133
|
+
# decide what umlaut service type value to map it to. Fulltext, ToC, etc.
|
134
|
+
# This is neccesarily a heuristic guess, Marc doesn't have enough granularity
|
135
|
+
# to really let us know for sure.
|
136
|
+
def service_type_for_856(field, options)
|
137
|
+
options[:default_service_type] ||= "fulltext_title_level"
|
138
|
+
|
139
|
+
# LC records here at hopkins have "Table of contents only" in the 856$3
|
140
|
+
# Think that's a convention from LC?
|
141
|
+
if (field['3'] && field['3'].downcase =~ /table of contents( only)?/)
|
142
|
+
return "table_of_contents"
|
143
|
+
elsif (field['3'] && field['3'].downcase =~ /description/)
|
144
|
+
# If it contains the word 'description', it's probably an abstract.
|
145
|
+
# That's the best we can do, sadly.
|
146
|
+
return "abstract"
|
147
|
+
elsif (field['3'] && field['3'].downcase == 'sample text')
|
148
|
+
# LC records often include these links.
|
149
|
+
return "excerpts"
|
150
|
+
elsif ( field['u'] =~ /www\.loc\.gov/ )
|
151
|
+
# Any other loc.gov link, we know it's not full text, don't put
|
152
|
+
# it in full text field, put it as "see also".
|
153
|
+
return "highlighted_link"
|
154
|
+
else
|
155
|
+
return options[:default_service_type]
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# A MARC record has two dates in it, date1 and date2. Exactly
|
160
|
+
# what they represent is something of an esoteric mystery.
|
161
|
+
# But this will return them both, in an array.
|
162
|
+
def get_years(marc)
|
163
|
+
array = []
|
164
|
+
|
165
|
+
# no marc 008? Weird, but okay.
|
166
|
+
return array unless marc['008']
|
167
|
+
|
168
|
+
date1 = marc['008'].value[7,4]
|
169
|
+
date1.strip! if date1
|
170
|
+
array.push(date1) unless date1.blank?
|
171
|
+
|
172
|
+
date2 = marc['008'].value[11,4]
|
173
|
+
date2.strip! if date2
|
174
|
+
array.push(date2) unless date2.blank?
|
175
|
+
|
176
|
+
return array
|
177
|
+
end
|
178
|
+
|
179
|
+
# Take the title out of a marc record
|
180
|
+
def get_title(marc)
|
181
|
+
marc['245'].find_all {|sf| sf.code == "a" || sf.code == "b" || sf.code == "k"}.collect {|sf| sf.text}.join(" ").sub(/\s*[;:\/.,]\s*$/)
|
182
|
+
end
|
183
|
+
|
184
|
+
|
185
|
+
# From a marc record, get a string useful to display for identifying
|
186
|
+
# which edition/version of a work this represents.
|
187
|
+
def edition_statement(marc, options = {})
|
188
|
+
options[:include_repro_info] ||= true
|
189
|
+
options[:exclude_533_fields] = ['7','f','b', 'e']
|
190
|
+
|
191
|
+
parts = Array.new
|
192
|
+
|
193
|
+
return "" unless marc
|
194
|
+
|
195
|
+
#245$h GMD
|
196
|
+
unless ( marc['245'].blank? || marc['245']['h'].blank? )
|
197
|
+
parts.push('(' + marc['245']['h'].gsub(/[^\w\s]/, '').strip.titlecase + ')')
|
198
|
+
end
|
199
|
+
|
200
|
+
#250
|
201
|
+
if ( marc['250'])
|
202
|
+
parts.push( marc['250']['a'] ) unless marc['250']['a'].blank?
|
203
|
+
parts.push( marc['250']['b'] ) unless marc['250']['b'].blank?
|
204
|
+
end
|
205
|
+
|
206
|
+
# 260
|
207
|
+
if ( marc['260'])
|
208
|
+
if (marc['260']['b'] =~ /s\.n\./)
|
209
|
+
parts.push(marc['260']['a']) unless marc['260']['a'].blank?
|
210
|
+
else
|
211
|
+
parts.push(marc['260']['b']) unless marc['260']['b'].blank?
|
212
|
+
end
|
213
|
+
parts.push( marc['260']['c'] ) unless marc['260']['c'].blank?
|
214
|
+
end
|
215
|
+
|
216
|
+
# 533
|
217
|
+
if options[:include_repro_info] && marc['533']
|
218
|
+
marc['533'].subfields.each do |s|
|
219
|
+
if ( s.code == 'a' )
|
220
|
+
parts.push('<em>' + s.value.gsub(/[^\w\s]/, '') + '</em>:' )
|
221
|
+
elsif (! options[:exclude_533_fields].include?( s.code ))
|
222
|
+
parts.push(s.value)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
return nil if parts.length == 0
|
228
|
+
|
229
|
+
return parts.join(' ')
|
230
|
+
end
|
231
|
+
|
232
|
+
# AACR2 "General Material Designation" . While these are (I think?)
|
233
|
+
# controlled, it's actually really hard to find the list. Maybe they're
|
234
|
+
# only semi-controlled.
|
235
|
+
# ONE list can be found here: http://www.oclc.org/bibformats/en/onlinecataloging/default.shtm#BCGFECEG
|
236
|
+
def gmd_values
|
237
|
+
# 'computer file' is an old one that may still be found in data.
|
238
|
+
return ['activity card',
|
239
|
+
'art original','art reproduction','braille','chart','diorama','electronic resource','computer file', 'filmstrip','flash card','game','globe','kit','manuscript','map','microform','microscope slides','model','motion picture','music','picture','realia','slide','sound recording','technical drawing','text','toy','transparency','videorecording']
|
240
|
+
end
|
241
|
+
|
242
|
+
# removes something that looks like an AACR2 GMD in square brackets from
|
243
|
+
# the string. Pretty kludgey.
|
244
|
+
def strip_gmd(arg_string, options = {})
|
245
|
+
options[:replacement] ||= ':'
|
246
|
+
|
247
|
+
gmd_values.each do |gmd_val|
|
248
|
+
arg_string = arg_string.sub(/\[#{gmd_val}( \((tactile|braile|large print)\))?\]/, options[:replacement])
|
249
|
+
end
|
250
|
+
return arg_string
|
251
|
+
end
|
252
|
+
|
253
|
+
|
254
|
+
end
|
@@ -0,0 +1,312 @@
|
|
1
|
+
# Helper class to get keyword searchable terms from OpenURL author and title
|
2
|
+
#
|
3
|
+
# OpenURLs have some commonly agreed upon metadata elements. This module is
|
4
|
+
# meant to help simplify things by sorting through the metadata and extracting
|
5
|
+
# what we need in a simpler interface. These values are specifically constructed
|
6
|
+
# from the citation to work well as keyword searches in other services.
|
7
|
+
#
|
8
|
+
# Also includes some helpful methods for getting identifiers out in a convenient to work with way, regardless of non-standard ways they may have been stored.
|
9
|
+
|
10
|
+
module MetadataHelper
|
11
|
+
include MarcHelper # for strip gmd functionality
|
12
|
+
|
13
|
+
# DEPRECATED, not flexible enough, you really need to custom fit
|
14
|
+
# for your given target.
|
15
|
+
# method that accepts a referent to return hash of common metadata elements
|
16
|
+
# choosing the available element for the format and the best available for
|
17
|
+
# searching. Wrapper around the other methods.
|
18
|
+
def get_search_terms(rft)
|
19
|
+
title = get_search_title(rft)
|
20
|
+
creator = get_search_creator(rft)
|
21
|
+
|
22
|
+
# returns a hash of values so that more keys can be added
|
23
|
+
# and not break services that use this module
|
24
|
+
return {:title => title, :creator => creator}
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
# A utility method to 'normalize' a title, for use when trying to match a
|
29
|
+
# title from one place with records in another database.
|
30
|
+
# Does lowercasing and removing puncutation, but also stripping out
|
31
|
+
# a bunch of other things that may result
|
32
|
+
# in false negatives. Exactly how you want to do for best results depends
|
33
|
+
# on the particular data you are working with, you need to experiment to see.
|
34
|
+
# MANY options are offered, although defaults are somewhat sensible.
|
35
|
+
# Much of this stuff especially takes account of titles that may have
|
36
|
+
# been generated from mark.
|
37
|
+
# Will never return the emtpy string, will sometimes return nil.
|
38
|
+
def normalize_title(arg_title, options = {})
|
39
|
+
# default options
|
40
|
+
options[:rstrip_parens] ||= true
|
41
|
+
options[:remove_all_parens] ||= true
|
42
|
+
options[:strip_gmd] ||= true
|
43
|
+
options[:subtitle_on_semicolon] ||=false
|
44
|
+
options[:remove_subtitle] ||= false
|
45
|
+
options[:normalize_ampersand] ||= true
|
46
|
+
options[:remove_punctuation] ||= true
|
47
|
+
# Even if you're removing other punctuation, keep the apostrophes?
|
48
|
+
options[:keep_apostrophes] ||=false
|
49
|
+
|
50
|
+
return nil if arg_title.nil?
|
51
|
+
title = arg_title.clone
|
52
|
+
|
53
|
+
return nil if title.blank?
|
54
|
+
|
55
|
+
# Sometimes titles given in the OpenURL have some additional stuff
|
56
|
+
# in parens at the end, that messes up the search and isn't really
|
57
|
+
# part of the title. Eliminate!
|
58
|
+
title.gsub!(/\([^)]*\)\s*$/, '') if options[:rstrip_parens]
|
59
|
+
# Or, not even just at the end, but anywhere!
|
60
|
+
title.gsub!(/\([^)]*\)/, '') if options[:remove_all_parens]
|
61
|
+
|
62
|
+
# Remove things in brackets, part of an AACR2 GMD that's made it in.
|
63
|
+
# replace with ':' so we can keep track of the fact that everything
|
64
|
+
# that came afterwards was a sub-title like thing.
|
65
|
+
title = strip_gmd(title) if options[:strip_gmd]
|
66
|
+
|
67
|
+
# There seems to be some catoging/metadata disagreement about when to
|
68
|
+
# use ';' for a subtitle instead of ':'. Normalize to ':'.
|
69
|
+
title.sub!(/[\;]/, ':') if options[:subtitle_on_semicolon]
|
70
|
+
|
71
|
+
title.sub!(/\:(.*)$/, '') if options[:remove_subtitle]
|
72
|
+
|
73
|
+
# Change ampersands to 'and' for consistency, we see it both ways.
|
74
|
+
title.gsub!(/\&/, ' and ') if options[:normalize_ampersand]
|
75
|
+
|
76
|
+
# remove non-alphanumeric, excluding apostrophe
|
77
|
+
title.gsub!(/[^\w\s\']/, ' ') if options[:remove_punctuation]
|
78
|
+
|
79
|
+
# apostrophe not to space, just eat it.
|
80
|
+
title.gsub!(/[\']/, '') if options[:remove_punctuation] && ! options[:keep_apostrophes]
|
81
|
+
|
82
|
+
# compress whitespace
|
83
|
+
title.strip!
|
84
|
+
title.gsub!(/\s+/, ' ')
|
85
|
+
|
86
|
+
title.downcase!
|
87
|
+
|
88
|
+
title = nil if title.blank?
|
89
|
+
|
90
|
+
return title
|
91
|
+
end
|
92
|
+
|
93
|
+
# pick title out of OpenURL referent from best element available,
|
94
|
+
# no normalization.
|
95
|
+
def raw_search_title(rft)
|
96
|
+
# Just make one call to create metadata hash
|
97
|
+
metadata = rft.metadata
|
98
|
+
title = nil
|
99
|
+
if rft.format == 'journal' && metadata['atitle']
|
100
|
+
title = metadata['atitle']
|
101
|
+
elsif rft.format == 'book'
|
102
|
+
title = metadata['btitle'] unless metadata['btitle'].blank?
|
103
|
+
title = metadata['title'] if title.blank?
|
104
|
+
|
105
|
+
# Well, if we don't know the format and we do have a title use that.
|
106
|
+
# This might happen if we only have an ISBN to start and then enhance.
|
107
|
+
# So should services like Amazon also enhance with a format, should
|
108
|
+
# we simplify this method to not worry about format so much, or do we
|
109
|
+
# keep this as is?
|
110
|
+
elsif metadata['btitle']
|
111
|
+
title = metadata['btitle']
|
112
|
+
elsif metadata['title']
|
113
|
+
title = metadata['title']
|
114
|
+
elsif metadata['jtitle']
|
115
|
+
title = metadata['jtitle']
|
116
|
+
end
|
117
|
+
return title
|
118
|
+
end
|
119
|
+
|
120
|
+
# chooses the best available title for the format, normalizes
|
121
|
+
def get_search_title(rft, options = {})
|
122
|
+
#defaults
|
123
|
+
options = {:remove_all_parens => true,
|
124
|
+
:subtitle_on_semicolon => true,
|
125
|
+
:remove_subtitle => true,
|
126
|
+
:remove_punctuation => true}.merge(options)
|
127
|
+
|
128
|
+
title = raw_search_title(rft)
|
129
|
+
|
130
|
+
return normalize_title(title, options)
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
# chooses the best available creator for the format
|
135
|
+
def get_search_creator(rft)
|
136
|
+
# Just make one call to create metadata hash
|
137
|
+
metadata = rft.metadata
|
138
|
+
# Identify dc.creator query. Prefer aulast alone if available.
|
139
|
+
creator = nil
|
140
|
+
|
141
|
+
creator = metadata['aulast'] unless metadata['aulast'].blank?
|
142
|
+
creator = metadata['au'] if creator.blank?
|
143
|
+
# FIXME if capital letters are next to each other should we insert a space?
|
144
|
+
# Should we assume capitals next to each other are initials?
|
145
|
+
# Maybe only if we use au?
|
146
|
+
# Logic like this makes refactoring to use Referent.to_citation less useful.
|
147
|
+
|
148
|
+
# FIXME strip out commas from creator if we use au?
|
149
|
+
|
150
|
+
return nil if creator.blank?
|
151
|
+
|
152
|
+
return creator
|
153
|
+
end
|
154
|
+
|
155
|
+
def get_top_level_creator(rft)
|
156
|
+
# If it's a non-journal thing, add the author if we have an aulast (preferred) or au.
|
157
|
+
# But wait--if it's a book _part_, don't include the author name, since
|
158
|
+
# it _might_ just be the author of the part, not of the book.
|
159
|
+
unless (rft.format == "journal" ||
|
160
|
+
( rft.format == "book" && ! rft.metadata['atitle'].blank?))
|
161
|
+
return get_search_creator(rft)
|
162
|
+
end
|
163
|
+
return nil
|
164
|
+
end
|
165
|
+
|
166
|
+
# oclcnum, lccn, and isbn are both _supposed_ to be stored as identifiers
|
167
|
+
# with an info: uri. info:oclcnum/#, info:lccn/#. But SFX sometimes stores
|
168
|
+
# them in the referent metadata instead: rft.lccn, rft.oclcnum. .
|
169
|
+
#
|
170
|
+
# On the other hand, isbn and issn can legitimately be included in referent
|
171
|
+
# metadata or as a urn.
|
172
|
+
#
|
173
|
+
# This method will find you an identifier accross multiple places.
|
174
|
+
#
|
175
|
+
# type: :urn or :info
|
176
|
+
# subscheme: "lccn", "oclcnum", "isbn", "issn", or anything else that could be found in either a urn an info uri or a referent metadata.
|
177
|
+
# referent: an umlaut Referent object
|
178
|
+
#
|
179
|
+
# returns nil if no identifier found, otherwise the bare identifier (not formatted into a urn/uri right now. Option should be maybe be added?)
|
180
|
+
def get_identifier(type, sub_scheme, referent, options = {} )
|
181
|
+
options[:multiple] ||= false
|
182
|
+
|
183
|
+
raise Exception.new("type must be :urn or :info") unless type == :urn or type == :info
|
184
|
+
|
185
|
+
prefix = case type
|
186
|
+
when :info then "info:#{sub_scheme}/"
|
187
|
+
when :urn then "urn:#{sub_scheme}:"
|
188
|
+
end
|
189
|
+
|
190
|
+
bare_identifier = nil
|
191
|
+
identifiers = referent.identifiers.collect {|id| $1 if id =~ /^#{prefix}(.*)/}.compact
|
192
|
+
|
193
|
+
if ( identifiers.blank? && ['lccn', 'oclcnum', 'isbn', 'issn', 'doi', 'pmid'].include?(sub_scheme) )
|
194
|
+
# try the referent metadata
|
195
|
+
from_rft = referent.metadata[sub_scheme]
|
196
|
+
identifiers = [from_rft] unless from_rft.blank?
|
197
|
+
end
|
198
|
+
|
199
|
+
if ( options[:multiple])
|
200
|
+
return identifiers
|
201
|
+
elsif ( identifiers[0].blank? )
|
202
|
+
return nil
|
203
|
+
else
|
204
|
+
return identifiers[0]
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
# finds and normalizes an LCCN. If multiple LCCNs are in the record,
|
210
|
+
# returns the first one.
|
211
|
+
def get_lccn(rft)
|
212
|
+
lccn = get_identifier(:info, "lccn", rft)
|
213
|
+
|
214
|
+
lccn = normalize_lccn(lccn)
|
215
|
+
|
216
|
+
return lccn
|
217
|
+
end
|
218
|
+
|
219
|
+
# Gets an ISSN, makes sure it's a valid ISSN or else returns nil.
|
220
|
+
# So will return a valid ISSN (NOT empty string) or nil.
|
221
|
+
def get_issn(rft)
|
222
|
+
issn = rft.metadata['issn']
|
223
|
+
issn = nil unless issn =~ /\d{4}(-)?\d{3}(\d|X)/
|
224
|
+
return issn
|
225
|
+
end
|
226
|
+
|
227
|
+
# Some normalization. See:
|
228
|
+
# http://info-uri.info/registry/OAIHandler?verb=GetRecord&metadataPrefix=reg&identifier=info:lccn/
|
229
|
+
# doesn't validate right now, only normalizes.
|
230
|
+
# tbd, raise exception if invalid string.
|
231
|
+
def normalize_lccn(lccn)
|
232
|
+
if ( lccn )
|
233
|
+
# remove whitespace
|
234
|
+
lccn = lccn.gsub(/\s/, '')
|
235
|
+
# remove any forward slashes and anything after them
|
236
|
+
lccn = lccn.sub(/\/.*$/, '')
|
237
|
+
# pad anything after a hyphen before removing hyphen, if neccesary
|
238
|
+
lccn = lccn.sub(/-(.*)/) do |match_str|
|
239
|
+
if $1.length < 6
|
240
|
+
("0" * (6 - $1.length)) + $1
|
241
|
+
else
|
242
|
+
$1
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
return lccn
|
247
|
+
end
|
248
|
+
|
249
|
+
# Gets isbn, also removes any weird stuff on the end sometimes
|
250
|
+
# included as 'isbn', but not part of the isbn. Like (paperback)
|
251
|
+
# and such.
|
252
|
+
def get_isbn(rft)
|
253
|
+
isbn = get_identifier(:urn, "isbn", rft)
|
254
|
+
isbn = isbn.gsub(/[^\dX-]/, '') if isbn
|
255
|
+
return nil if isbn.blank?
|
256
|
+
return isbn
|
257
|
+
end
|
258
|
+
|
259
|
+
def get_oclcnum(rft)
|
260
|
+
return get_identifier(:info, "oclcnum", rft)
|
261
|
+
end
|
262
|
+
|
263
|
+
def get_doi(rft)
|
264
|
+
return get_identifier(:info, "doi", rft)
|
265
|
+
end
|
266
|
+
|
267
|
+
def get_pmid(rft)
|
268
|
+
return get_identifier(:info, "pmid", rft)
|
269
|
+
end
|
270
|
+
|
271
|
+
# Returns an array, possibly empty.
|
272
|
+
def get_gpo_item_nums(rft)
|
273
|
+
# In a technically illegal but used by OCLC info:gpo uri
|
274
|
+
ids = get_identifier(:info, "gpo", rft, :multiple => true)
|
275
|
+
# Remove the uri part.
|
276
|
+
return ids.collect {|id| id.sub(/^info:gpo\//, '') }
|
277
|
+
end
|
278
|
+
|
279
|
+
def get_sudoc(rft)
|
280
|
+
# Don't forget to unescape the sudoc that was escaped to maek it a uri!
|
281
|
+
|
282
|
+
# Option 1: In a technically illegal but oh well info:sudoc uri
|
283
|
+
|
284
|
+
sudoc = get_identifier(:info, "sudoc", rft)
|
285
|
+
sudoc = CGI.unescape(sudoc) if sudoc
|
286
|
+
|
287
|
+
# Option 2: rsinger's purl for sudoc. http://dilettantes.code4lib.org/2009/03/a-uri-scheme-for-sudocs/
|
288
|
+
unless sudoc
|
289
|
+
sudoc = rft.identifiers.collect {|id| $1 if id =~ /^http:\/\/purl.org\/NET\/sudoc\/(.*)$/}.compact.slice(0)
|
290
|
+
sudoc = CGI.unescape(sudoc) if sudoc
|
291
|
+
end
|
292
|
+
|
293
|
+
return sudoc
|
294
|
+
end
|
295
|
+
|
296
|
+
def get_year(rft)
|
297
|
+
# Some link generators use an illegal 'year' parameter
|
298
|
+
if (date = (rft['date'] || rft['year']))
|
299
|
+
return date[0,4]
|
300
|
+
end
|
301
|
+
return nil
|
302
|
+
end
|
303
|
+
|
304
|
+
# Look at weird bad OpenURLs, use heuristics to see if the 'title' probably
|
305
|
+
# represents a journal rather than a book.
|
306
|
+
def title_is_serial?(rft)
|
307
|
+
(rft.format != "book" &&
|
308
|
+
( ! rft.metadata['jtitle'].blank?) &&
|
309
|
+
rft.metadata['btitle'].blank?)
|
310
|
+
end
|
311
|
+
|
312
|
+
end
|