umlaut 3.0.0alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (293) hide show
  1. data/LICENSE +7 -0
  2. data/README.md +49 -0
  3. data/Rakefile +37 -0
  4. data/app/assets/images/error.gif +0 -0
  5. data/app/assets/images/export_bg_bot.gif +0 -0
  6. data/app/assets/images/export_bg_mid.gif +0 -0
  7. data/app/assets/images/export_bg_top.gif +0 -0
  8. data/app/assets/images/famfamfam/book_open.png +0 -0
  9. data/app/assets/images/famfamfam/cross.png +0 -0
  10. data/app/assets/images/famfamfam/page_sound.gif +0 -0
  11. data/app/assets/images/famfamfam/page_text.gif +0 -0
  12. data/app/assets/images/famfamfam/page_up.gif +0 -0
  13. data/app/assets/images/famfamfam/page_white.png +0 -0
  14. data/app/assets/images/famfamfam/readme.html +1495 -0
  15. data/app/assets/images/famfamfam/tiny_cross.png +0 -0
  16. data/app/assets/images/frame_remove.gif +0 -0
  17. data/app/assets/images/ico_go.gif +0 -0
  18. data/app/assets/images/jhu_findit.gif +0 -0
  19. data/app/assets/images/list_closed.png +0 -0
  20. data/app/assets/images/list_open.png +0 -0
  21. data/app/assets/images/more_info.gif +0 -0
  22. data/app/assets/images/rails.png +0 -0
  23. data/app/assets/images/request.gif +0 -0
  24. data/app/assets/images/spinner.gif +0 -0
  25. data/app/assets/javascripts/umlaut/ajax_windows.js +35 -0
  26. data/app/assets/javascripts/umlaut/ensure_window_size.js.erb +34 -0
  27. data/app/assets/javascripts/umlaut/expand_contract_toggle.js +25 -0
  28. data/app/assets/javascripts/umlaut/search_autocomplete.js +46 -0
  29. data/app/assets/javascripts/umlaut/simple_visible_toggle.js +8 -0
  30. data/app/assets/javascripts/umlaut/update_html.js +152 -0
  31. data/app/assets/javascripts/umlaut.js +17 -0
  32. data/app/assets/stylesheets/umlaut.css +857 -0
  33. data/app/controllers/application_controller.rb +14 -0
  34. data/app/controllers/export_email_controller.rb +123 -0
  35. data/app/controllers/js_helper_controller.rb +10 -0
  36. data/app/controllers/link_router_controller.rb +87 -0
  37. data/app/controllers/open_search_controller.rb +9 -0
  38. data/app/controllers/resolve_controller.rb +288 -0
  39. data/app/controllers/resource_controller.rb +83 -0
  40. data/app/controllers/search_controller.rb +328 -0
  41. data/app/controllers/search_methods/sfx3.rb +148 -0
  42. data/app/controllers/search_methods/sfx4.rb +257 -0
  43. data/app/controllers/search_methods/sfx_api.rb +47 -0
  44. data/app/controllers/store_controller.rb +64 -0
  45. data/app/controllers/umlaut/controller_behavior.rb +20 -0
  46. data/app/controllers/umlaut/controller_logic.rb +96 -0
  47. data/app/controllers/umlaut/error_handling.rb +48 -0
  48. data/app/controllers/umlaut_controller.rb +112 -0
  49. data/app/helpers/application_helper.rb +4 -0
  50. data/app/helpers/emailer_helper.rb +43 -0
  51. data/app/helpers/export_email_helper.rb +34 -0
  52. data/app/helpers/open_search_helper.rb +7 -0
  53. data/app/helpers/resolve_helper.rb +225 -0
  54. data/app/helpers/search_helper.rb +50 -0
  55. data/app/helpers/umlaut/footer_helper.rb +64 -0
  56. data/app/helpers/umlaut/helper.rb +62 -0
  57. data/app/helpers/umlaut/html_head_helper.rb +37 -0
  58. data/app/helpers/umlaut/url_generation.rb +77 -0
  59. data/app/mailers/emailer.rb +48 -0
  60. data/app/models/clickthrough.rb +2 -0
  61. data/app/models/collection.rb +259 -0
  62. data/app/models/crossref_lookup.rb +2 -0
  63. data/app/models/dispatched_service.rb +58 -0
  64. data/app/models/permalink.rb +29 -0
  65. data/app/models/referent.rb +473 -0
  66. data/app/models/referent_value.rb +14 -0
  67. data/app/models/request.rb +449 -0
  68. data/app/models/service_response.rb +179 -0
  69. data/app/models/service_store.rb +59 -0
  70. data/app/models/service_type_value.rb +58 -0
  71. data/app/models/service_wave.rb +150 -0
  72. data/app/models/sfx_db/az_additional_title.rb +11 -0
  73. data/app/models/sfx_db/az_letter_group.rb +11 -0
  74. data/app/models/sfx_db/az_title.rb +38 -0
  75. data/app/models/sfx_db/az_title_v2.rb +34 -0
  76. data/app/models/sfx_db/isbn.rb +12 -0
  77. data/app/models/sfx_db/issn.rb +12 -0
  78. data/app/models/sfx_db/object.rb +35 -0
  79. data/app/models/sfx_db/object_portfolio.rb +6 -0
  80. data/app/models/sfx_db/publisher.rb +10 -0
  81. data/app/models/sfx_db/sfx_db_base.rb +54 -0
  82. data/app/models/sfx_db/target.rb +9 -0
  83. data/app/models/sfx_db/target_service.rb +10 -0
  84. data/app/models/sfx_db/title.rb +10 -0
  85. data/app/models/sfx_db.rb +10 -0
  86. data/app/models/sfx_url.rb +35 -0
  87. data/app/views/emailer/citation.text.erb +28 -0
  88. data/app/views/emailer/short_citation.text.erb +8 -0
  89. data/app/views/export_email/_email.html.erb +25 -0
  90. data/app/views/export_email/_send_email.html.erb +3 -0
  91. data/app/views/export_email/_send_txt.html.erb +3 -0
  92. data/app/views/export_email/_txt.html.erb +62 -0
  93. data/app/views/export_email/email.html.erb +3 -0
  94. data/app/views/export_email/send_email.html.erb +1 -0
  95. data/app/views/export_email/send_txt.html.erb +1 -0
  96. data/app/views/export_email/txt.html.erb +3 -0
  97. data/app/views/js_helper/loader.erb.js +13 -0
  98. data/app/views/layouts/umlaut.html.erb +52 -0
  99. data/app/views/open_search/index.html.erb +9 -0
  100. data/app/views/resolve/_api_in_progress.xml.erb +21 -0
  101. data/app/views/resolve/_background_progress.html.erb +51 -0
  102. data/app/views/resolve/_background_updater.html.erb +38 -0
  103. data/app/views/resolve/_citation.html.erb +87 -0
  104. data/app/views/resolve/_coins.html.erb +1 -0
  105. data/app/views/resolve/_compact_citation.html.erb +33 -0
  106. data/app/views/resolve/_cover_image.html.erb +35 -0
  107. data/app/views/resolve/_fulltext.html.erb +55 -0
  108. data/app/views/resolve/_help.html.erb +17 -0
  109. data/app/views/resolve/_holding.html.erb +91 -0
  110. data/app/views/resolve/_related_items.html.erb +35 -0
  111. data/app/views/resolve/_search_inside.html.erb +62 -0
  112. data/app/views/resolve/_section_display.html.erb +49 -0
  113. data/app/views/resolve/_service_errors.html.erb +29 -0
  114. data/app/views/resolve/_standard_response_item.html.erb +89 -0
  115. data/app/views/resolve/api.xml.builder +72 -0
  116. data/app/views/resolve/background_status.html.erb +26 -0
  117. data/app/views/resolve/index.html.erb +73 -0
  118. data/app/views/resolve/partial_html_sections.xml.erb +30 -0
  119. data/app/views/search/_a_to_z.html.erb +6 -0
  120. data/app/views/search/_citation.html.erb +94 -0
  121. data/app/views/search/_pager.html.erb +60 -0
  122. data/app/views/search/books.html.erb +103 -0
  123. data/app/views/search/journal_search.html.erb +90 -0
  124. data/app/views/search/journals.html.erb +167 -0
  125. data/app/views/search/opensearch_description.rxml +10 -0
  126. data/app/views/testing/index.html.erb +1 -0
  127. data/app/views/umlaut/README +5 -0
  128. data/app/views/umlaut/error.html.erb +45 -0
  129. data/db/migrate/01_umlaut_init.rb +113 -0
  130. data/db/orig_fixed_data/service_type_values.yml +120 -0
  131. data/db/seeds.rb +7 -0
  132. data/lib/CronTab.rb +192 -0
  133. data/lib/aws_product_sign.rb +146 -0
  134. data/lib/exlibris/aleph/patron.rb +64 -0
  135. data/lib/exlibris/aleph/record.rb +54 -0
  136. data/lib/exlibris/aleph/rest_api.rb +29 -0
  137. data/lib/exlibris/primo/holding.rb +192 -0
  138. data/lib/exlibris/primo/rsrc.rb +17 -0
  139. data/lib/exlibris/primo/searcher.rb +276 -0
  140. data/lib/exlibris/primo/source/aleph.rb +46 -0
  141. data/lib/exlibris/primo/source/distribution/nyu_aleph.rb +323 -0
  142. data/lib/exlibris/primo/toc.rb +17 -0
  143. data/lib/exlibris/primo_ws.rb +140 -0
  144. data/lib/generators/templates/umlaut_services.yml +237 -0
  145. data/lib/generators/umlaut/asset_hooks_generator.rb +44 -0
  146. data/lib/generators/umlaut/install_generator.rb +110 -0
  147. data/lib/hip3/bib.rb +291 -0
  148. data/lib/hip3/bib_searcher.rb +302 -0
  149. data/lib/hip3/custom_field_lookup.rb +44 -0
  150. data/lib/hip3/holding.rb +50 -0
  151. data/lib/hip3/item.rb +65 -0
  152. data/lib/hip3/receipt.rb +7 -0
  153. data/lib/hip3/serial_copy.rb +82 -0
  154. data/lib/holding.rb +32 -0
  155. data/lib/marc_helper.rb +254 -0
  156. data/lib/metadata_helper.rb +312 -0
  157. data/lib/opensearch_feed.rb +398 -0
  158. data/lib/opensearch_query.rb +98 -0
  159. data/lib/referent_filter.rb +16 -0
  160. data/lib/referent_filters/dissertation_catch.rb +45 -0
  161. data/lib/section_renderer.rb +503 -0
  162. data/lib/service.rb +336 -0
  163. data/lib/service_adaptors/ajax_export.rb +37 -0
  164. data/lib/service_adaptors/amazon.rb +412 -0
  165. data/lib/service_adaptors/blacklight.rb +327 -0
  166. data/lib/service_adaptors/book_finder.rb +40 -0
  167. data/lib/service_adaptors/bx.rb +51 -0
  168. data/lib/service_adaptors/cover_thing.rb +73 -0
  169. data/lib/service_adaptors/elsevier_cover.rb +57 -0
  170. data/lib/service_adaptors/email_export.rb +10 -0
  171. data/lib/service_adaptors/ezproxy.rb +171 -0
  172. data/lib/service_adaptors/google_book_search.rb +442 -0
  173. data/lib/service_adaptors/gpo.rb +124 -0
  174. data/lib/service_adaptors/hathi_trust.rb +308 -0
  175. data/lib/service_adaptors/hip3_service.rb +150 -0
  176. data/lib/service_adaptors/hip_holding_search.rb +237 -0
  177. data/lib/service_adaptors/internet_archive.rb +488 -0
  178. data/lib/service_adaptors/isbn_db.rb +86 -0
  179. data/lib/service_adaptors/isi.rb +258 -0
  180. data/lib/service_adaptors/jcr.rb +146 -0
  181. data/lib/service_adaptors/opac.rb +351 -0
  182. data/lib/service_adaptors/open_library.rb +316 -0
  183. data/lib/service_adaptors/open_library_cover.rb +73 -0
  184. data/lib/service_adaptors/primo_service.rb +392 -0
  185. data/lib/service_adaptors/primo_source.rb +78 -0
  186. data/lib/service_adaptors/pubmed.rb +133 -0
  187. data/lib/service_adaptors/request_to_fixture.rb +68 -0
  188. data/lib/service_adaptors/scopus.rb +295 -0
  189. data/lib/service_adaptors/sfx-new.rb +557 -0
  190. data/lib/service_adaptors/sfx.rb +566 -0
  191. data/lib/service_adaptors/sfx_backchannel_record.rb +69 -0
  192. data/lib/service_adaptors/txt_holding_export.rb +32 -0
  193. data/lib/service_adaptors/ulrichs_cover.rb +57 -0
  194. data/lib/service_adaptors/ulrichs_link.rb +47 -0
  195. data/lib/service_adaptors/worldcat.rb +116 -0
  196. data/lib/service_adaptors/worldcat_identities.rb +591 -0
  197. data/lib/tasks/umlaut.rake +134 -0
  198. data/lib/umlaut/default_configuration.rb +5 -0
  199. data/lib/umlaut/routes.rb +136 -0
  200. data/lib/umlaut/version.rb +3 -0
  201. data/lib/umlaut.rb +37 -0
  202. data/lib/umlaut_configurable.rb +343 -0
  203. data/lib/umlaut_http.rb +100 -0
  204. data/lib/xml_schema_helper.rb +109 -0
  205. data/test/dummy/Rakefile +7 -0
  206. data/test/dummy/app/assets/javascripts/application.js +13 -0
  207. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  208. data/test/dummy/app/controllers/application_controller.rb +3 -0
  209. data/test/dummy/app/controllers/umlaut_controller.rb +112 -0
  210. data/test/dummy/app/helpers/application_helper.rb +2 -0
  211. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  212. data/test/dummy/config/application.rb +45 -0
  213. data/test/dummy/config/boot.rb +10 -0
  214. data/test/dummy/config/database-jhu.yml +44 -0
  215. data/test/dummy/config/database.yml +25 -0
  216. data/test/dummy/config/environment.rb +5 -0
  217. data/test/dummy/config/environments/development.rb +34 -0
  218. data/test/dummy/config/environments/production.rb +60 -0
  219. data/test/dummy/config/environments/test.rb +39 -0
  220. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  221. data/test/dummy/config/initializers/inflections.rb +10 -0
  222. data/test/dummy/config/initializers/mime_types.rb +5 -0
  223. data/test/dummy/config/initializers/secret_token.rb +7 -0
  224. data/test/dummy/config/initializers/session_store.rb +8 -0
  225. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  226. data/test/dummy/config/locales/en.yml +5 -0
  227. data/test/dummy/config/routes.rb +61 -0
  228. data/test/dummy/config/umlaut_services.yml +237 -0
  229. data/test/dummy/config.ru +4 -0
  230. data/test/dummy/db/migrate/20111228211210_umlaut_init.rb +113 -0
  231. data/test/dummy/db/schema.rb +124 -0
  232. data/test/dummy/log/development.log +12981 -0
  233. data/test/dummy/log/production.log +0 -0
  234. data/test/dummy/public/404.html +26 -0
  235. data/test/dummy/public/422.html +26 -0
  236. data/test/dummy/public/500.html +26 -0
  237. data/test/dummy/public/favicon.ico +0 -0
  238. data/test/dummy/script/rails +6 -0
  239. data/test/dummy/tmp/cache/assets/C5F/340/sprockets%2F99692920160b7a279b86a80415b79db7 +0 -0
  240. data/test/dummy/tmp/cache/assets/C70/4D0/sprockets%2F034ad2036e623081bd352800786dfe80 +0 -0
  241. data/test/dummy/tmp/cache/assets/C73/920/sprockets%2Fd371318f22900492fd180f17c5e2a504 +9268 -0
  242. data/test/dummy/tmp/cache/assets/C80/980/sprockets%2Fc94807409c1523d43e18d25f35d93c41 +0 -0
  243. data/test/dummy/tmp/cache/assets/C8F/780/sprockets%2Fe47e28558116fb5f8038754e60d1961d +11769 -0
  244. data/test/dummy/tmp/cache/assets/CAA/EB0/sprockets%2F1d179210e8b76f1ea63c802688a015e4 +9271 -0
  245. data/test/dummy/tmp/cache/assets/CBB/9C0/sprockets%2F706f28923fb754cad04b9107c89986a1 +0 -0
  246. data/test/dummy/tmp/cache/assets/CBF/B60/sprockets%2F08ca89671549936265dcb673bf02e36f +0 -0
  247. data/test/dummy/tmp/cache/assets/CC9/9F0/sprockets%2F306166316e2cafd13c15e62b51a2339d +0 -0
  248. data/test/dummy/tmp/cache/assets/CF6/F20/sprockets%2F5b2ffa1103079dfd555197838f87a99f +0 -0
  249. data/test/dummy/tmp/cache/assets/CF7/2B0/sprockets%2F25a7c73655bd3598173b39d9f98bcd46 +862 -0
  250. data/test/dummy/tmp/cache/assets/CFE/080/sprockets%2F37fe9f4255baddbd549a659914929398 +0 -0
  251. data/test/dummy/tmp/cache/assets/D22/060/sprockets%2F9aec77b768e91a802d284271c58e2f7e +21357 -0
  252. data/test/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
  253. data/test/dummy/tmp/cache/assets/D33/6D0/sprockets%2F500129c57f1146e556ec3aacd6cd38c1 +0 -0
  254. data/test/dummy/tmp/cache/assets/D33/FD0/sprockets%2F2ba0b4e6334a77b923e5f770381bb2bf +0 -0
  255. data/test/dummy/tmp/cache/assets/D42/C20/sprockets%2Fbcf14e437b1582bf93b77670acf8e090 +21353 -0
  256. data/test/dummy/tmp/cache/assets/D50/A30/sprockets%2F7d8b294ac433db5d056538f8cf7c66b9 +0 -0
  257. data/test/dummy/tmp/cache/assets/D54/ED0/sprockets%2F71c9fa01091d432b131da3bb73faf3d4 +872 -0
  258. data/test/dummy/tmp/cache/assets/D65/590/sprockets%2Fc1bb92fc3406a126b7dd302edc96d629 +0 -0
  259. data/test/dummy/tmp/cache/assets/D71/6B0/sprockets%2Fde558b71b494cf09b1bf055c8dff0353 +0 -0
  260. data/test/dummy/tmp/cache/assets/D72/610/sprockets%2Fa8c708eeb30ef93de34d755d4f45d023 +859 -0
  261. data/test/dummy/tmp/cache/assets/D76/AD0/sprockets%2Fe2158cde93188cf5ab6457bc6d6602ec +0 -0
  262. data/test/dummy/tmp/cache/assets/D7A/E40/sprockets%2F9622ffcc499a57627cd1bb18fe31b8e4 +11772 -0
  263. data/test/dummy/tmp/cache/assets/D84/210/sprockets%2Fabd0103ccec2b428ac62c94e4c40b384 +0 -0
  264. data/test/dummy/tmp/cache/assets/D9B/770/sprockets%2F8aacf02eb7dbb0949704b28f27b87e0b +0 -0
  265. data/test/dummy/tmp/cache/assets/DA6/A80/sprockets%2F92e26d8e58d5bcc8b8f6c25d1b05b9c1 +0 -0
  266. data/test/dummy/tmp/cache/assets/DE8/790/sprockets%2Fd1333bde2b9aafcc712d11dd09ab35d8 +0 -0
  267. data/test/dummy/tmp/cache/assets/DF7/F30/sprockets%2F7bc16c4109b17fabe29f8ddbbf732d1c +374 -0
  268. data/test/dummy/tmp/cache/assets/E03/570/sprockets%2F493bdc0ac14cd4f57fdfe4253f992bde +0 -0
  269. data/test/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
  270. data/test/dummy/tmp/cache/assets/E0B/4B0/sprockets%2F7988df51a61c81ce6ede4a2d4c8cce4f +377 -0
  271. data/test/dummy/tmp/cache/assets/E5F/960/sprockets%2Fdc007b6cad5c7ef08e33ec28cfff0ef6 +0 -0
  272. data/test/fixtures/dispatched_services.yml +5 -0
  273. data/test/fixtures/permalinks.yml +5 -0
  274. data/test/fixtures/referent_values.yml +1734 -0
  275. data/test/fixtures/referents.yml +156 -0
  276. data/test/fixtures/requests.yml +284 -0
  277. data/test/fixtures/service_responses.yml +5 -0
  278. data/test/fixtures/sfx_urls.yml +4 -0
  279. data/test/performance/browsing_test.rb +9 -0
  280. data/test/test_helper.rb +10 -0
  281. data/test/umlaut_test.rb +7 -0
  282. data/test/unit/aleph_patron_test.rb +39 -0
  283. data/test/unit/aleph_record_benchmarks.rb +28 -0
  284. data/test/unit/aleph_record_test.rb +30 -0
  285. data/test/unit/aws_product_sign_test.rb +93 -0
  286. data/test/unit/collection_test.rb +76 -0
  287. data/test/unit/google_book_search_test.rb +101 -0
  288. data/test/unit/primo_searcher_test.rb +403 -0
  289. data/test/unit/primo_service_test.rb +939 -0
  290. data/test/unit/primo_ws_test.rb +131 -0
  291. data/test/unit/service_response_test.rb +9 -0
  292. data/test/unit/service_test.rb +33 -0
  293. metadata +580 -0
@@ -0,0 +1,124 @@
1
+ # Still in progress. Uses illegal info:sudoc and info:gpo to get a
2
+ # a sudoc or a GPO Item Number for a given referent, and finds online
3
+ # availability, and/or links to GPO lookup for local depository with the
4
+ # item.
5
+ class Gpo < Service
6
+ include MetadataHelper
7
+ require 'nokogiri'
8
+ require 'open-uri'
9
+
10
+
11
+ def initialize(config)
12
+ @display_name = "U.S. Government Printing Office"
13
+ @gpo_item_find = true
14
+ @sudoc_url_lookup = true
15
+ super(config)
16
+ end
17
+
18
+ def service_types_generated
19
+ a = []
20
+ a.push(ServiceTypeValue["highlighted_link"]) if @gpo_item_find
21
+ a.push(ServiceTypeValue["fulltext"]) if @sudoc_url_lookup
22
+ return a
23
+ end
24
+
25
+ def handle(request)
26
+
27
+ if ( @gpo_item_find )
28
+ items = analyze_gpo_items( get_gpo_item_nums(request.referent) )
29
+
30
+ items.each do |item, formats|
31
+ # Generate URL to GPO Item Number lookup to finding
32
+ # it in a repository near you.
33
+
34
+ request.add_service_response(:service => self,
35
+ :display_text => "Find in a Federal Depository Library",
36
+ :url => gpo_item_lookup_url(item),
37
+ :notes => "In " + formats.join(" or "),
38
+ :service_type_value => "highlighted_link"
39
+ )
40
+ end
41
+ end
42
+ sudoc = get_sudoc(request.referent)
43
+
44
+ if ( sudoc && @sudoc_url_lookup )
45
+ add_links_from_sudoc(request, sudoc)
46
+ end
47
+
48
+
49
+ request.dispatched(self, true)
50
+
51
+ end
52
+
53
+ # Takes an array of string of GPO Items with formats in parens, groups
54
+ # them by individual Item Number, identified by formats.
55
+ def analyze_gpo_items(items)
56
+ item_hash = {}
57
+
58
+ items.each do |i|
59
+
60
+ bare_item = i
61
+ format_str = 'paper'
62
+
63
+ # seperate the format marker from the base item number, if present.
64
+ # if it's not present, means paper.
65
+ if ( i =~ /^(.*)\(([^\)]+)\)\s*$/ )
66
+ bare_item = $1.strip
67
+ format_str = $2.strip
68
+ format_str = "microform" if format_str == "MF"
69
+ end
70
+
71
+ item_hash[bare_item] ||= []
72
+
73
+ item_hash[bare_item].push( format_str )
74
+ end
75
+
76
+ return item_hash
77
+ end
78
+
79
+ def gpo_item_lookup_url(item)
80
+ return "http://catalog.gpo.gov/fdlpdir/locate.jsp?ItemNumber=" + CGI.escape(item)
81
+ end
82
+
83
+ def add_links_from_sudoc(request, sudoc)
84
+ # Screen scrape the GPO catalog.
85
+
86
+ response = open( gpo_sudoc_find_url(sudoc) ).read
87
+
88
+ response_dom = Nokogiri::HTML(response)
89
+
90
+ # Find each tr with class tr1, holding a td => The sixth td in there =>
91
+ # one or more 'a' tags in there. These are links to fulltext.
92
+ links = response_dom.search('//tr[@class = "tr1"][td]/td[7]/a')
93
+
94
+ urls_seen = []
95
+
96
+ links.each do |link|
97
+ # The href is an internally pointing ILS link. But the text inside
98
+ # the a is what we want, it's actually a URL, fortunately. .
99
+
100
+ url = link.inner_text
101
+ unless urls_seen.include?(url)
102
+
103
+ notes = nil
104
+ if (links.length > 1)
105
+ notes = "via " + URI.parse(url).host
106
+ end
107
+
108
+ request.add_service_response(:service => self,
109
+ :display_text => @display_name,
110
+ :url => url,
111
+ :notes => notes,
112
+ :service_type_value => "fulltext"
113
+ )
114
+ urls_seen.push( url )
115
+ end
116
+ end
117
+
118
+ end
119
+
120
+ def gpo_sudoc_find_url(sudoc)
121
+ return "http://catalog.gpo.gov/F/?func=find-a&find_code=GVD&request=#{CGI.escape('"'+sudoc+'"')}&local_base=GPO01PUB"
122
+ end
123
+
124
+ end
@@ -0,0 +1,308 @@
1
+ require 'open-uri'
2
+ require 'multi_json'
3
+ require 'cgi'
4
+
5
+ # Service that searches HathiTrust from the University of Michigan
6
+ #
7
+ # Supports full text links, and search inside.
8
+ #
9
+ # We link to HathiTrust using a direct babel.hathitrust.org URL instead
10
+ # of the handle.net redirection, for two reasonsL
11
+ # 1) Can't use the handle.net redirection for the "direct link to search
12
+ # results for user-entered query" feature.
13
+ # 2) Some may want to force a Shibboleth login on HT links. Can't do that
14
+ # with the handle.net redirection either. If you do want to do that,
15
+ # possibly in concert with an EZProxy mediated WAYFless login,
16
+ # set direct_link_base in your services.yml to:
17
+ # "https://babel.hathitrust.org/shcgi/"
18
+ #
19
+ # Many (but not all) HT books will also be in Google Books (and vice versa)
20
+ # However, HT was more generous in deciding what books are public domain than GBS.
21
+ # Therefore the main expected use case is to use with Google Books, with
22
+ # HT being a lower priority, using preempted_by config.
23
+ #
24
+ # Some may prefer HT search inside interface to Google, so search inside
25
+ # is not suppressed with presence of google. You can turn off HT
26
+ # search inside entirely if you like.
27
+ #
28
+ # For HT records representing one volume of several, a :excerpts type
29
+ # response will be added if full text is avail for some. Or a :highlighted_link
30
+ # if only search inside is available for some.
31
+ # Or set config show_multi_volume=false to prevent this and ignore partial
32
+ # volumes.
33
+ #
34
+ # Two possibilities are available for sdr rights "full" or "searchonly".
35
+ # The third possibility is that sdr will be null.
36
+ #
37
+ # An ISBN with search-only: 0195101464
38
+ class HathiTrust < Service
39
+ include MetadataHelper
40
+
41
+ attr_reader :url, :display_name, :note
42
+
43
+ def service_types_generated
44
+ types = [ ServiceTypeValue[:fulltext] ]
45
+ types.concat([ServiceTypeValue[:excerpts], ServiceTypeValue[:highlighted_link]]) if @show_multi_volume
46
+ types << ServiceTypeValue[:search_inside] if @show_search_inside
47
+ return types
48
+ end
49
+
50
+ def initialize(config)
51
+ @api_url = 'http://catalog.hathitrust.org/api/volumes'
52
+ # Set to 'https://babel.hathitrust.org/shcgi/' to force
53
+ # Shibboleth login, possibly in concert with EZProxy providing
54
+ # WAYFLess login.
55
+ @direct_link_base = 'http://babel.hathitrust.org/cgi/'
56
+ @display_name = 'HathiTrust'
57
+ @num_full_views = 1 # max num full view links to include
58
+ @note = '' #'Fulltext books from the University of Michigan'
59
+ @show_search_inside = true
60
+ @show_multi_volume = true
61
+
62
+ @credits = {
63
+ "HathiTrust" => "http://www.hathitrust.org"
64
+ }
65
+
66
+ super(config)
67
+ end
68
+
69
+ def handle(request)
70
+ params = get_parameters(request.referent)
71
+ return request.dispatched(self, true) if params.blank?
72
+
73
+ ht_json = do_query(params)
74
+ return request.dispatched(self, true) if ht_json.nil?
75
+
76
+ #extract the "items" list from the first result group from
77
+ #response.
78
+ first_group = ht_json.values.first
79
+ items = first_group["items"]
80
+
81
+
82
+
83
+ # Only add fulltext if we're not skipping due to GBS
84
+ if ( preempted_by(request, "fulltext"))
85
+ Rails.logger.debug("#{self.class}: Skipping due to pre-emption")
86
+ else
87
+ full_views_shown = create_fulltext_service_response(request, items)
88
+ end
89
+
90
+ if @show_multi_volume
91
+ #possibly partial volumes
92
+ create_partial_volume_responses(request, ht_json)
93
+ end
94
+
95
+
96
+
97
+ create_search_inside(request, items)
98
+
99
+ return request.dispatched(self, true)
100
+ end
101
+
102
+ # just a wrapper around get_bibkey_parameters
103
+ def get_parameters(rft)
104
+ # API supports oclcnum, isbn, or lccn, and can provide more than one of each.
105
+ get_bibkey_parameters(rft) do |isbn, lccn, oclcnum|
106
+ keys = Array.new
107
+
108
+ keys << "oclc:" + CGI.escape(oclcnum) unless oclcnum.blank?
109
+ keys << "lccn:" + CGI.escape(lccn) unless lccn.blank?
110
+ # Only include ISBN if we have it and we do NOT have oclc or lccn,
111
+ # Bill Dueber's advice for best matching. HT api will only match
112
+ # if ALL the id's we supply match.
113
+ keys << "isbn:" + CGI.escape(isbn) unless (isbn.blank? || keys.length > 0)
114
+
115
+ if keys.length > 0
116
+ return keys.join(";")
117
+ else
118
+ return nil
119
+ end
120
+ end
121
+ end
122
+
123
+ # method that takes a referent and a block for parameter creation
124
+ # The block receives isbn, lccn, oclcnum and is responsible for formatting
125
+ # the parameters for the particular service
126
+ # FIXME consider moving this into metadata_helper
127
+ def get_bibkey_parameters(rft)
128
+ isbn = get_identifier(:urn, "isbn", rft)
129
+ oclcnum = get_identifier(:info, "oclcnum", rft)
130
+ lccn = get_lccn(rft)
131
+
132
+ yield(isbn, lccn, oclcnum)
133
+ end
134
+
135
+ # conducts query and parses the JSON
136
+ def do_query(params)
137
+ link = @api_url + "/brief/json/" + params
138
+ return MultiJson.decode( open(link).read )
139
+ end
140
+
141
+
142
+ def create_fulltext_service_response(request, items)
143
+ return nil if items.empty?
144
+
145
+ display_name = @display_name
146
+ count = 0
147
+
148
+ items.each do |item|
149
+ next if is_serial_part?(item)
150
+
151
+
152
+ next unless full_view?(item)
153
+
154
+ request.add_service_response(
155
+ :service=>self,
156
+ :display_text=>display_name,
157
+ :url=> direct_url_to(item),
158
+ :notes=> note_for(item),
159
+ :service_type_value => :fulltext
160
+ )
161
+ count += 1
162
+ break if count == @num_full_views
163
+ end
164
+ return count
165
+ end
166
+
167
+
168
+ # If HT has partial serial volumes, include a link to that.
169
+ # Need to pass in complete HT json response
170
+ def create_partial_volume_responses(request, ht_json)
171
+ items = ht_json.values.first["items"]
172
+ full_ids = items.collect do |i|
173
+ i["fromRecord"] if (is_serial_part?(i) && full_view?(i))
174
+ end.compact.uniq
175
+
176
+ full_ids.each do |recordId|
177
+ record = ht_json.values.first["records"][recordId]
178
+ next unless record && record["recordURL"]
179
+
180
+ request.add_service_response(
181
+ :service=>self,
182
+ :display_text=>@display_name,
183
+ :url=> record["recordURL"],
184
+ :notes => excerpt_note_for(record),
185
+ :service_type_value => :excerpts
186
+ )
187
+ end
188
+
189
+ if full_ids.empty?
190
+ search_ids = items.collect do |i|
191
+ i["fromRecord"] if (is_serial_part?(i) )
192
+ end.compact.uniq
193
+
194
+ search_ids.each do |recordId|
195
+ record = ht_json.values.first["records"][recordId]
196
+ next unless record && record["recordURL"]
197
+
198
+ request.add_service_response(
199
+ :service=>self,
200
+ :display_text=>"Search inside some volumes",
201
+ :url=> record["recordURL"],
202
+ :service_type_value => :highlighted_link
203
+ )
204
+
205
+ end
206
+
207
+ end
208
+
209
+
210
+ end
211
+
212
+ def create_search_inside(request, items)
213
+ return if items.empty?
214
+
215
+ # Can only include search from the first one
216
+ # There's search inside for _any_ HT item. We think.
217
+ item = items.first
218
+
219
+ # if this is a serial, we don't want to search inside just part of it, forget it
220
+ return if is_serial_part?(item)
221
+
222
+ direct_url = search_url_to(item)
223
+ return unless direct_url
224
+
225
+ request.add_service_response(
226
+ :service => self,
227
+ :display_text=>@display_name,
228
+ :url=> direct_url,
229
+ :service_type_value => :search_inside
230
+ )
231
+ end
232
+
233
+ def direct_url_to(item_json)
234
+ if @direct_link_base
235
+ # we're constructing our own link because we need our EZProxy
236
+ # to recognize it for WAYFLess login, which it won't if we use
237
+ # the handle.net url, sorry.
238
+ # We also need direct link for direct link to search results.
239
+ @direct_link_base + "pt?id=" + CGI.escape(item_json['htid'])
240
+ else
241
+ item['itemURL']
242
+ end
243
+ end
244
+
245
+ def note_for(item)
246
+ if item['orig']
247
+ "Digitized from #{item['orig']}"
248
+ else
249
+ nil
250
+ end
251
+ end
252
+
253
+ def excerpt_note_for(record)
254
+ return nil unless record["titles"].kind_of?(Array)
255
+ "Some volumes of: #{record["titles"].first}"
256
+ end
257
+
258
+ def is_serial_part?(item)
259
+ # if it's got enumCron, then it's just part of a serial,
260
+ # we don't want to say the serial title as a whole has full text
261
+ # or can be searched, skip it.
262
+ return item['enumcron']
263
+ end
264
+
265
+ def full_view?(item)
266
+ item["usRightsString"] == "Full view"
267
+ end
268
+
269
+ def search_url_to(item_json)
270
+ if @direct_link_base
271
+ @direct_link_base + "ptsearch?id=" + CGI.escape(item_json['htid'])
272
+ else
273
+ return nil
274
+ end
275
+ end
276
+
277
+
278
+
279
+
280
+ # Handle search_inside
281
+ def response_url(service_response, submitted_params)
282
+ if ( ! (service_response.service_type_value.name == "search_inside" ))
283
+ return super(service_response, submitted_params)
284
+ else
285
+ base = service_response[:url]
286
+ query = CGI.escape(submitted_params["query"] || "")
287
+ url = base + "&q1=#{query}"
288
+
289
+ return url
290
+ end
291
+ end
292
+
293
+ # sample OCLCnums with appropriate results showing that we can pick up other
294
+ # resources by using this service
295
+ # 02029914 MBooks: full, GBS: info with search inside
296
+ # 01635828 MBooks: full, GBS: snippet
297
+ # 55517975 MBooks: search, GBS: limited preview
298
+ # 02299399 MBooks: full, GBS: snippet
299
+ # 16857172 MBooks: full, GBS: info
300
+
301
+ # Example of a serial with some full text volumes:
302
+ # JAMA, lccn:07037314
303
+ #
304
+ # Example of a multi-volume with search-only, split accross
305
+ # two HT records.
306
+ # Handbook of biochemistry and molecular biology lccn: 75029514
307
+
308
+ end
@@ -0,0 +1,150 @@
1
+ # Needs to be called Hip3Service to avoid conflicting with Hip3 module
2
+ # Params include:
3
+ # map_856_to_service : Umlaut ServiceTypeValue to map 856 links to. Defaults
4
+ # to fulltext_title_level
5
+
6
+
7
+ class Hip3Service < Service
8
+ required_config_params :base_path, :display_name
9
+ attr_reader :base_path
10
+
11
+ include MetadataHelper
12
+ include MarcHelper
13
+
14
+ def initialize(config)
15
+ # defaults
16
+ @map_856_to_service = 'fulltext'
17
+ # If you are sending an OpenURL from a library service, you may
18
+ # have the HIP bibnum, and include it in the OpenURL as, eg.
19
+ # rft_id=http://catalog.library.jhu.edu/bib/343434 (except URL-encoded)
20
+ # Then you'd set rft_id_bibnum_prefix to http://catalog.library.jhu.edu/bib/
21
+ @rft_id_bibnum_prefix = nil
22
+ @profile = "general"
23
+ super(config)
24
+
25
+ # Trim question-mark from base_url, if given
26
+ @base_path.chop! if (@base_path.rindex('?') == @base_path.length)
27
+ end
28
+
29
+ # Standard method, used by background service updater. See Service docs.
30
+ def service_types_generated
31
+ # We generate full text and holdings types, right now.
32
+ types = [ ServiceTypeValue[:fulltext], ServiceTypeValue[:holding], ServiceTypeValue[:table_of_contents] ]
33
+
34
+ return types
35
+ end
36
+
37
+
38
+ def handle(request)
39
+ bib_searcher = Hip3::BibSearcher.new(@base_path)
40
+
41
+ bib_searcher.issn = request.referent.issn
42
+ bib_searcher.isbn = request.referent.isbn
43
+ bib_searcher.sudoc = get_sudoc(request.referent)
44
+
45
+ results = bib_searcher.search
46
+
47
+ add_856_links(request, results.collect {|b| b.marc_xml})
48
+ add_copies(request, results)
49
+
50
+ return request.dispatched(self, true)
51
+
52
+ end
53
+
54
+ # Takes an array of Hip3::Bib objects believed to be exact matches
55
+ # for the citation querried, and adds response objects for them
56
+ # Returns a hash of arrays of ServiceResponses added.
57
+ def add_copies(request, bib_array, options = {})
58
+ #debugger
59
+
60
+ # default
61
+ options[:match_reliability] ||= ServiceResponse::MatchExact
62
+
63
+ responses_added = Hash.new
64
+
65
+
66
+
67
+
68
+ #Okay, we actually want to make each _copy_ into a service response.
69
+ #A bib may have multiple copies. We are merging bibs, and just worrying
70
+ #about the aggregated list of copies.
71
+ holdings = bib_array.collect { |bib| bib.holdings }.flatten
72
+ bib_array.each do |bib|
73
+
74
+
75
+ bib.holdings.each do |holding|
76
+
77
+
78
+ next if holding.dummy?
79
+
80
+
81
+
82
+ service_data = {}
83
+ service_data[:url] = holding.bib.http_url
84
+ service_data[:source_name] = holding.collection_str unless holding.collection_str.nil?
85
+ service_data[:call_number] = holding.call_no
86
+ service_data[:status] = holding.status_str
87
+ service_data[:location] = holding.location_str
88
+ service_data[:collection_str] = holding.collection_str
89
+ service_data[:copy_str] = holding.copy_str
90
+ service_data[:coverage_str] = holding.coverage_str
91
+ service_data[:coverage_str_array] = holding.coverage_str_to_a
92
+ service_data[:notes] = holding.notes
93
+ # If it's not a serial copy, we can add a direct request url.
94
+ unless ( holding.kind_of?(Hip3::SerialCopy) )
95
+ service_data[:request_url] = self.base_path + "?profile=#{@profile}&menu=request&aspect=none&bibkey=#{holding.bib.bibNum}&itemkey=#{holding.id}"
96
+ end
97
+
98
+ # Need to say it's not an exact match neccesarily?
99
+
100
+ unless ( options[:match_reliability] == ServiceResponse::MatchExact )
101
+ service_data[:match_reliability] = options[:match_reliability]
102
+
103
+ service_data[:edition_str] = edition_statement(bib.marc_xml)
104
+ end
105
+
106
+ display_text = ""
107
+ #display_text << (holding.location_str + ' ')unless holding.location_str.nil?
108
+ display_text << (holding.copy_str + ' ') unless holding.copy_str.nil?
109
+
110
+ # coverage strings, may be multiple
111
+ holding.coverage_str_to_a.each {|s| display_text << (s + ' ')}
112
+
113
+ display_text << holding.notes unless holding.notes.nil?
114
+ service_data[:display_text] = display_text
115
+
116
+ response = request.add_service_response(
117
+ service_data.merge(
118
+ :service=>self,
119
+ :service_type_value => 'holding' )
120
+ )
121
+
122
+ responses_added['holding'] ||= Array.new
123
+ responses_added['holding'].push( response )
124
+
125
+ end
126
+ end
127
+
128
+ return responses_added
129
+ end
130
+
131
+ def url_service_type( field )
132
+ return service_type_for_856(field, :default_service_type => @map_856_to_service)
133
+ end
134
+
135
+ def get_bibnum(rft)
136
+ return nil unless @rft_id_bibnum_prefix
137
+
138
+ identifier = rft.identifiers.find do |id|
139
+ id[0, @rft_id_bibnum_prefix.length] == @rft_id_bibnum_prefix
140
+ end
141
+
142
+ if ( identifier )
143
+ return identifier[@rft_id_bibnum_prefix.length, identifier.length]
144
+ else
145
+ return nil
146
+ end
147
+
148
+ end
149
+
150
+ end