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.
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