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,29 @@
1
+ module Exlibris::Aleph
2
+ require 'httparty'
3
+ class RestAPI
4
+ include HTTParty
5
+ format :xml
6
+ def initialize(uri)
7
+ @uri = uri
8
+ raise "Initialization error in #{self.class}. Missing URI." if @uri.nil?
9
+ end
10
+ def error
11
+ return nil if reply_code == "0000"
12
+ return "#{reply_text}"
13
+ end
14
+ def reply_code
15
+ return "No response." if @response.nil?
16
+ return (not @response.first.last.kind_of?(Hash) or @response.first.last["reply_code"].nil?) ? "Unexpected response hash." : @response.first.last["reply_code"] if @response.instance_of?(Hash)
17
+ response_match = @response.match(/\<reply-code\>(.+)\<\/reply-code\>/) if @response.instance_of?(String)
18
+ return (response_match.nil?) ? "Unexpected response string." : response_match[1] if @response.instance_of?(String)
19
+ return "Unexpected response type."
20
+ end
21
+ def reply_text
22
+ return "No response." if @response.nil?
23
+ return (not @response.first.last.kind_of?(Hash) or @response.first.last["reply_text"].nil?) ? "Unexpected response hash." : @response.first.last["reply_text"] if @response.instance_of?(Hash)
24
+ response_match = @response.match(/\<reply-text\>(.+)\<\/reply-text\>/) if @response.instance_of?(String)
25
+ return (response_match.nil?) ? "Unexpected response string." : response_match[1] if @response.instance_of?(String)
26
+ return "Unexpected response type."
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,192 @@
1
+ module Exlibris::Primo
2
+ # == Overview
3
+ # Exlibris::Primo::Holding represents a Primo holding.
4
+ # This class should be extended to create Primo source objects for
5
+ # expanding holdings information, linking to Primo sources, and storing
6
+ # additional metadata based on those sources.
7
+ #
8
+ # == Tips on Extending
9
+ # When extending the class, a few basics guidelines should be observed.
10
+ # 1. A Exlibris::Primo::Holding is initialized from random Hash of parameters.
11
+ # Instance variables are created from these parameters for use in the class.
12
+ #
13
+ # 2. A Exlibris::Primo::Holding can be initialized from an input
14
+ # Exlibris::Primo::Holding by specifying the reserved
15
+ # parameter name :holding, i.e. :holding => input_holding.
16
+ # If the input holding has instance variables that are also specified in
17
+ # the random Hash, the value in the Hash takes precedence.
18
+ #
19
+ # 3. The following methods are available for overriding:
20
+ # expand - expand holdings information based on data source. default: [self]
21
+ # dedup? - does this data source contain duplicate holdings that need to be deduped? default: false
22
+ #
23
+ # 4. The following instance variables will be saved in the view_data and will be available
24
+ # to a local holding partial:
25
+ # @record_id, @source_id, @original_source_id, @source_record_id,
26
+ # @availlibrary, @institution_code, @institution, @library_code, @library,
27
+ # @status_code, @status, @id_one, @id_two, @origin, @display_type, @coverage, @notes,
28
+ # @url, @request_url, @match_reliability, @request_link_supports_ajax_call, @source_data
29
+ #
30
+ # 5. Additional source data should be saved in the @source_data instance variable.
31
+ # @source_data is a hash that can contain any number of string elements,
32
+ # perfect for storing local source information.
33
+ # @source_data will get saved in the view_data and will be available to a
34
+ # local holding partial.
35
+ #
36
+ # == Examples
37
+ # Example of Primo source implementations are:
38
+ # * Exlibris::Primo::Source::Aleph
39
+ # * Exlibris::Primo::Source::Local::NYUAleph
40
+ class Holding
41
+ @base_attributes = [ :record_id, :source_id, :original_source_id, :source_record_id,
42
+ :availlibrary, :institution_code, :institution, :library_code, :library,
43
+ :status_code, :status, :id_one, :id_two, :origin, :display_type, :coverage, :notes,
44
+ :url, :request_url, :match_reliability, :request_link_supports_ajax_call, :source_data ]
45
+ # Make sure attribute you're aliasing in in base_attributes
46
+ @attribute_aliases = { :collection => :id_one, :call_number => :id_two }
47
+ @required_parameters = [ :base_url, :record_id, :source_id,
48
+ :original_source_id, :source_record_id, :availlibrary,
49
+ :institution_code, :library_code, :id_one, :id_two, :status_code ]
50
+ @parameter_default_values = { :vid => "DEFAULT", :config => {},
51
+ :max_holdings => 10, :request_link_supports_ajax_call => false,
52
+ :coverage => [], :source_data => {} }
53
+ @decode_variables = {
54
+ :institution => {},
55
+ :library => { :address => "libraries" },
56
+ :status => { :address => "statuses" }
57
+ }
58
+ class << self; attr_reader :base_attributes, :attribute_aliases, :required_parameters, :parameter_default_values, :decode_variables end
59
+
60
+ def initialize(parameters={})
61
+ # Set attr_readers
62
+ base_attributes = (self.class.base_attributes.nil?) ?
63
+ Exlibris::Primo::Holding.base_attributes : self.class.base_attributes
64
+ base_attributes.each { |attribute|
65
+ self.class.send(:attr_reader, attribute)
66
+ }
67
+ # Defensive copy the holding parameter.
68
+ holding = parameters[:holding].clone unless parameters[:holding].nil?
69
+ raise "Initialization error in #{self.class}. Unexpected holding parameter: #{holding.class}." unless holding.kind_of? Holding or holding.nil?
70
+ # Copy the defensive copy of holding to self.
71
+ holding.instance_variables.each { |name|
72
+ instance_variable_set((name).to_sym, holding.instance_variable_get(name))
73
+ } if holding.kind_of? Holding
74
+ # Add required instance variables, raising an exception if they're missing
75
+ # Params passed in overwrite instance variables copied from the holding
76
+ required_parameters = (self.class.required_parameters.nil?) ?
77
+ Exlibris::Primo::Holding.required_parameters : self.class.required_parameters
78
+ required_parameters.each do |param|
79
+ instance_variable_set(
80
+ "@#{param}".to_sym,
81
+ parameters.delete(param) {
82
+ instance_variable_get("@#{param}") if instance_variable_defined?("@#{param}") }
83
+ )
84
+ raise_required_parameter_error param unless instance_variable_defined?("@#{param}")
85
+ end
86
+ # Set additional instance variables from passed parameters
87
+ # Params passed in overwrite instance variables copied from the holding
88
+ parameters.each { |param, value|
89
+ instance_variable_set("@#{param}".to_sym, value)
90
+ }
91
+ # If appropriate, add defaults to non-required elements
92
+ parameter_default_values = (self.class.parameter_default_values.nil?) ?
93
+ Exlibris::Primo::Holding.parameter_default_values : self.class.parameter_default_values
94
+ parameter_default_values.each { |param, default|
95
+ instance_variable_set("@#{param}".to_sym, default) unless instance_variable_defined?("@#{param}")
96
+ }
97
+ # Set decoded fields
98
+ decode_variables = (self.class.decode_variables.nil?) ?
99
+ Exlibris::Primo::Holding.decode_variables : self.class.decode_variables
100
+ decode_variables.each { |var, decode_params|
101
+ decode var, decode_params, true
102
+ }
103
+ # Deep link URL to record
104
+ @url = primo_url if @url.nil?
105
+ # Set source parameters
106
+ @source_config = @config["sources"][source_id] unless @config["sources"].nil?
107
+ @source_class = @source_config["class_name"] unless @source_config.nil?
108
+ @source_url = @source_config["base_url"] unless @source_config.nil?
109
+ @source_type = @source_config["type"] unless @source_config.nil?
110
+ @source_data = {
111
+ :source_class => @source_class,
112
+ :source_url => @source_url,
113
+ :source_type => @source_type
114
+ }
115
+ # Set aliases for convenience
116
+ attribute_aliases = (self.class.attribute_aliases.nil?) ?
117
+ Exlibris::Primo::Holding.attribute_aliases : self.class.attribute_aliases
118
+ attribute_aliases.each { |alias_name, method_name|
119
+ begin
120
+ self.class.send(:alias_method, alias_name.to_sym, method_name.to_sym)
121
+ rescue NameError => ne
122
+ raise NameError, "Error in #{self}. Make sure method, #{method_name}, is defined. You may need to add it to #{self} @base_attributes.\nRoot exception: #{ne.message}"
123
+ end
124
+ }
125
+ end
126
+
127
+ # Returns an array of self.
128
+ # Should be overridden by source subclasses to map multiple holdings
129
+ # to one availlibrary.
130
+ def expand
131
+ return [self]
132
+ end
133
+
134
+ # Determine if we're de-duplicating.
135
+ # Should be overridden by source subclasses if appropriate.
136
+ def dedup?
137
+ return false
138
+ end
139
+
140
+ # Return this holding as a new holdings subclass instance based on source
141
+ def to_source
142
+ return self if @source_class.nil?
143
+ begin
144
+ # Check source class in source module, if not found, see if there is a local class
145
+ return (Exlibris::Primo::Source.const_defined?(@source_class)) ?
146
+ Exlibris::Primo::Source.const_get(@source_class).new(:holding => self) :
147
+ Exlibris::Primo::Source::Local.const_get(@source_class).new(:holding => self)
148
+ rescue Exception => e
149
+ # !!!!!!!!!!REMOVE NEXT LINE (raise e) WHEN GOING TO PRODUCTION!!!!!!!!!
150
+ raise e
151
+ Rails.logger.error("#{e.message}")
152
+ Rails.logger.error("Class #{@source_class} can't be found in either
153
+ Exlibris::Primo::Source or Exlibris::Primo::Source::Local.
154
+ Please check primo.yml to ensure the class_name is defined correctly.
155
+ Not converting to source.")
156
+ return self
157
+ end
158
+ end
159
+
160
+ def [](key)
161
+ raise "Error in #{self.class}. #{key} doesn't exist or is restricted." unless self.class.base_attributes.include?(key)
162
+ method(key).call
163
+ end
164
+
165
+ protected
166
+ def decode(var, decode_params={}, refresh=false)
167
+ return instance_variable_get("@#{var}") unless (not instance_variable_defined?("@#{var}")) or refresh
168
+ code_sym = (decode_params[:code].nil?) ? "#{var}_code".to_sym : decode_params[:code]
169
+ code = instance_variable_get("@#{code_sym}")
170
+ config_sym = (decode_params[:config].nil?) ? :config : decode_params[:config]
171
+ config = instance_variable_get("@#{config_sym}")
172
+ address = (decode_params[:address].nil?) ? "#{var}s" : decode_params[:address]
173
+ instance_variable_set("@#{var}",
174
+ (config[address].nil? or config[address][code].nil?) ?
175
+ code : config[address][code]) unless code.nil?
176
+ end
177
+
178
+ # Returns Primo deep link URL to record
179
+ def primo_url
180
+ "#{@base_url}/primo_library/libweb/action/dlDisplay.do?docId=#{@record_id}&institution=#{@institution_code}&vid=#{@vid}"
181
+ end
182
+
183
+ private
184
+ # def self.add_attr_reader(reader)
185
+ # attr_reader reader.to_sym
186
+ # end
187
+ #
188
+ def raise_required_parameter_error(parameter)
189
+ raise "Initialization error in #{self.class}. Missing required parameter: #{parameter}."
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,17 @@
1
+ module Exlibris::Primo
2
+ # Class for handling Primo Rsrcs from links/linktorsrc
3
+ class Rsrc
4
+ @base_attributes = [ :record_id, :linktorsrc, :v, :url, :display, :institution_code, :origin, :notes ]
5
+ class << self; attr_reader :base_attributes end
6
+ def initialize(options={})
7
+ base_attributes = (self.class.base_attributes.nil?) ?
8
+ Exlibris::Primo::Rsrc.base_attributes : self.class.base_attributes
9
+ base_attributes.each { |attribute|
10
+ self.class.send(:attr_reader, attribute)
11
+ }
12
+ options.each { |option, value|
13
+ self.instance_variable_set(('@'+option.to_s).to_sym, value)
14
+ }
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,276 @@
1
+ # == Overview
2
+ # Searcher searches Primo for records.
3
+ # Searcher must have sufficient metadata to make
4
+ # the request. Sufficient means either:
5
+ # * We have a Primo doc id
6
+ # * We have either an isbn OR an issn
7
+ # * We have a title AND an author AND a genre
8
+ # If none of these criteria are met, Searcher.search
9
+ # will raise a RuntimeException.
10
+ require "iconv"
11
+
12
+ module Exlibris::Primo
13
+ class Searcher
14
+ #@required_setup = [ :base_url ]
15
+ #@setup_default_values = { :vid => "DEFAULT", :config => {} }
16
+
17
+ attr_reader :response, :count
18
+ attr_reader :cover_image, :titles, :author
19
+ attr_reader :holdings, :rsrcs, :tocs
20
+
21
+ # Instantiates the object and performs the search for based on the input search criteria.
22
+ # setup parameter requires { :base_url => http://primo.server.institution.edu }
23
+ # Other optional parameters are :vid => "view_id", :config => { Hash of primo config settings}
24
+ # search_params are a sufficient combination of
25
+ # { :primo_id => "primo_1", :isbn => "ISBN", :issn => "ISSN",
26
+ # :title => "=Title", :author => "Author", :genre => "Genre" }
27
+ def initialize(setup, search_params)
28
+ @holdings = []
29
+ @rsrcs = []
30
+ @tocs = []
31
+ @holding_attributes = Exlibris::Primo::Holding.base_attributes
32
+ @base_url = setup[:base_url]
33
+ raise_required_setup_parameter_error :base_url if @base_url.nil?
34
+ @institution = setup[:institution]
35
+ raise_required_setup_parameter_error :institution if @institution.nil?
36
+ @vid = setup.fetch(:vid, "DEFAULT")
37
+ raise_required_setup_parameter_error :vid if @vid.nil?
38
+ @config = setup.fetch(:config, {})
39
+ raise_required_setup_parameter_error :config if @config.nil?
40
+ search_params.each { |param, value| self.instance_variable_set("@#{param}".to_sym, value) }
41
+ # Perform the search
42
+ search
43
+ end
44
+
45
+ private
46
+ def self.add_attr_reader(reader)
47
+ attr_reader reader.to_sym
48
+ end
49
+
50
+ # Execute search based on instance vars
51
+ # Process Holdings based on display/availlibrary
52
+ # Process URLs based on links/linktorsrc
53
+ # Process TOCs based on links/linktotoc
54
+ def search
55
+ Rails.logger.warn("Insufficient search terms for #{self.class}. "+
56
+ "Please refer to #{self.class}'s documentation to determine how to structure "+
57
+ "a sufficient query.") and return if insufficient_query?
58
+ # Call Primo Web Services
59
+ unless @primo_id.nil? or @primo_id.empty?
60
+ get_record = Exlibris::PrimoWS::GetRecord.new(@primo_id, @base_url, {:institution => @institution})
61
+ @response = get_record.response
62
+ process_record and process_search_results #since this is a search in addition to being a record call
63
+ else
64
+ brief_search = Exlibris::PrimoWS::SearchBrief.new(search_params, @base_url, {:institution => @institution})
65
+ @response = brief_search.response
66
+ process_search_results
67
+ end
68
+ end
69
+
70
+ # Determine whether we have sufficient search criteria to search
71
+ # Sufficient means either:
72
+ # * We have a Primo doc id
73
+ # * We have either an isbn OR an issn
74
+ # * We have a title AND an author AND a genre
75
+ def insufficient_query?
76
+ return false unless (@primo_id.nil? or @primo_id.empty?)
77
+ return false unless (@issn.nil? or @issn.empty?) and (@isbn.nil? or @isbn.empty?)
78
+ return false unless (@title.nil? or @title.empty?) or (@author.nil? or @author.empty?) or (@genre.nil? or @genre.empty?)
79
+ return true
80
+ end
81
+
82
+ # Search params are determined by input to Exlibris::PrimoWS::SearchBrief
83
+ def search_params
84
+ search_params = {}
85
+ unless (@issn.nil? or @issn.empty?) and (@isbn.nil? or @isbn.empty?)
86
+ search_params[:isbn] = @isbn unless @isbn.nil?
87
+ search_params[:issn] = @issn if search_params.empty?
88
+ else
89
+ search_params[:title] = @title unless @title.nil?
90
+ search_params[:author] = @author unless @title.nil? or @author.nil?
91
+ search_params[:genre] = @genre unless @title.nil? or @author.nil? or @genre.nil?
92
+ end
93
+ return search_params
94
+ end
95
+
96
+ # Process a single record
97
+ def process_record
98
+ @count = response.at("//DOCSET")["TOTALHITS"] unless response.nil? or @count
99
+ response.at("//addata").each_child do |addata_child|
100
+ name = addata_child.pathname and value = addata_child.inner_text.chars.to_s if addata_child.elem?
101
+ next if value.nil?
102
+ self.class.add_attr_reader name.to_sym unless name.nil?
103
+ # instance_variable_set("@#{name}".to_sym, "#{convert_diacritics(value)}") unless name.nil?
104
+ instance_variable_set("@#{name}".to_sym, "#{value}") unless name.nil?
105
+ end
106
+ @cover_image = response.at("//addata/lad02").inner_text unless response.at("//addata/lad02").nil?
107
+ @titles = []
108
+ response.search("//display/title") do |title|
109
+ @titles.push(title.inner_text.chars.to_s)
110
+ end
111
+ @authors = []
112
+ response.search("//display/creator") do |e|
113
+ @authors.push(e.inner_text.chars.to_s)
114
+ end
115
+ end
116
+
117
+ # Process search results
118
+ # Process Holdings based on display/availlibrary
119
+ # Process URLs based on links/linktorsrc
120
+ # Process TOCs based on links/linktotoc
121
+ def process_search_results
122
+ @count = (response.at("//DOCSET").nil?) ?
123
+ (response.at("//sear:DOCSET")["TOTALHITS"].nil?) ? 0 :
124
+ response.at("//sear:DOCSET")["TOTALHITS"] :
125
+ response.at("//DOCSET")["TOTALHITS"] unless response.nil? or @count
126
+ # Loop through records to set metadata for holdings, urls and tocs
127
+ response.search("//record") do |record|
128
+ # Default genre to article if necessary
129
+ record_genre = (record.at("addata/genre").nil?) ? "article" : record.at("addata/genre").inner_text
130
+ # Don't process if passed in genre doesn't match the record genre unless the discrepancy is only b/w journals and articles
131
+ # If we're working off id numbers, we should be good to proceed
132
+ next unless @primo_id or @isbn or @issn or
133
+ @genre == record_genre or (@genre == "journal" and record_genre == "article")
134
+ # Just take the first element for record level elements
135
+ # (should only be one, except sourceid which will be handled later)
136
+ record_id = record.at("control/recordid").inner_text
137
+ display_type = record.at("display/type").inner_text
138
+ original_source_id = record.at("control/originalsourceid").inner_text unless record.at("control/originalsourceid").nil?
139
+ original_source_ids = process_control_hash(record, "control/originalsourceid")
140
+ source_id = record.at("control/sourceid").inner_text
141
+ source_ids = process_control_hash(record, "control/sourceid")
142
+ source_record_id = record.at("control/sourcerecordid").inner_text
143
+ # Process holdings
144
+ source_record_ids = process_control_hash(record, "control/sourcerecordid")
145
+ record.search("display/availlibrary") do |availlibrary|
146
+ availlibrary, institution_code, library_code, id_one, id_two, status_code, origin = process_availlibrary availlibrary
147
+ holding_original_source_id = (origin.nil?) ? original_source_ids[record_id] : original_source_ids[origin] unless original_source_ids.empty?
148
+ holding_original_source_id = original_source_id if holding_original_source_id.nil?
149
+ holding_source_id = (origin.nil?) ? source_ids[record_id] : source_ids[origin] unless source_ids.empty?
150
+ holding_source_id = source_id if holding_source_id.nil?
151
+ holding_source_record_id = (origin.nil?) ? source_record_ids[record_id] : source_record_ids[origin] unless source_record_ids.empty?
152
+ holding_source_record_id = source_record_id if holding_source_record_id.nil?
153
+ holding_parameters = {
154
+ :base_url => @base_url, :vid => @vid, :config => @config,
155
+ :record_id => record_id, :original_source_id => holding_original_source_id,
156
+ :source_id => holding_source_id, :source_record_id => holding_source_record_id,
157
+ :origin => origin, :availlibrary => availlibrary, :institution_code => institution_code,
158
+ :library_code => library_code, :id_one => id_one, :id_two => id_two,
159
+ :status_code => status_code, :origin => origin, :display_type => display_type, :notes => "",
160
+ :match_reliability =>
161
+ (reliable_match?(:title => record.at("display/title").inner_text, :author => record.at("display/creator").inner_text)) ?
162
+ ServiceResponse::MatchExact : ServiceResponse::MatchUnsure
163
+ }
164
+ holding = Exlibris::Primo::Holding.new(holding_parameters)
165
+ @holdings.push(holding) unless holding.nil?
166
+ end
167
+ # Process urls
168
+ record.search("links/linktorsrc") do |linktorsrc|
169
+ linktorsrc, v, url, display, institution_code, origin = process_linktorsrc linktorsrc
170
+ rsrc = Exlibris::Primo::Rsrc.new({
171
+ :record_id => record_id, :linktorsrc => linktorsrc,
172
+ :v => v, :url => url, :display => display,
173
+ :institution_code => institution_code, :origin => origin,
174
+ :notes => ""
175
+ }) unless linktorsrc.nil?
176
+ @rsrcs.push(rsrc) unless (rsrc.nil? or rsrc.url.nil?)
177
+ end
178
+ # Process tocs
179
+ record.search("links/linktotoc") do |linktotoc|
180
+ linktotoc, url, display = process_linktotoc linktotoc
181
+ toc = Exlibris::Primo::Toc.new({
182
+ :record_id => record_id, :linktotoc => linktotoc,
183
+ :url => url, :display => display,
184
+ :notes => ""
185
+ }) unless linktotoc.nil?
186
+ @tocs.push(toc) unless (toc.nil? or toc.url.nil?)
187
+ end
188
+ end
189
+ end
190
+
191
+ def process_control_hash(record, xpath)
192
+ h = {}
193
+ record.search(xpath) do |e|
194
+ str = e.inner_text unless e.nil?
195
+ a = str.split(/\$(?=\$)/) unless str.nil?
196
+ v = nil
197
+ o = nil
198
+ a.each do |s|
199
+ v = s.sub!(/^\$V/, "") unless s.match(/^\$V/).nil?
200
+ o = s.sub!(/^\$O/, "") unless s.match(/^\$O/).nil?
201
+ end
202
+ h[o] = v unless (o.nil? or v.nil?)
203
+ end
204
+ return h
205
+ end
206
+
207
+ # Determine how sure we are that this is a match.
208
+ # Dynamically compares record metadata to input values
209
+ # based on the values passed in.
210
+ # Minimum requirement is to check title.
211
+ def reliable_match?(record_metadata)
212
+ return true unless (@primo_id.nil? or @primo_id.empty?)
213
+ return true unless (@issn.nil? or @issn.empty?) and (@isbn.nil? or @isbn.empty?)
214
+ return false if (record_metadata.nil? or record_metadata.empty? or record_metadata[:title].nil? or record_metadata[:title].empty?)
215
+ # Titles must be equal
216
+ return false unless record_metadata[:title].downcase.eql?(@title.downcase)
217
+ # Compare record metadata with metadata that was passed in.
218
+ # Only check if the record metadata value contains the input value since we can't be too strict.
219
+ record_metadata.each { |type, value| return false if value.downcase.match("#{self.method(type).call}".downcase).nil?}
220
+ return true
221
+ end
222
+
223
+ def process_availlibrary(input)
224
+ availlibrary, institution_code, library_code, id_one, id_two, status_code, origin =
225
+ nil, nil, nil, nil, nil, nil, nil
226
+ return institution_code, library_code, id_one, id_two, status_code, origin if input.nil? or input.inner_text.nil?
227
+ availlibrary = input.inner_text
228
+ availlibrary.split(/\$(?=\$)/).each do |s|
229
+ institution_code = s.sub!(/^\$I/, "") unless s.match(/^\$I/).nil?
230
+ library_code = s.sub!(/^\$L/, "") unless s.match(/^\$L/).nil?
231
+ id_one = s.sub!(/^\$1/, "") unless s.match(/^\$1/).nil?
232
+ id_two = s.sub!(/^\$2/, "") unless s.match(/^\$2/).nil?
233
+ # Always display "Check Availability" if this is from Primo.
234
+ #@status_code = s.sub!(/^\$S/, "") unless s.match(/^\$S/).nil?
235
+ status_code = "check_holdings"
236
+ origin = s.sub!(/^\$O/, "") unless s.match(/^\$O/).nil?
237
+ end
238
+ return availlibrary, institution_code, library_code, id_one, id_two, status_code, origin
239
+ end
240
+
241
+ def process_linktorsrc(input)
242
+ linktorsrc, v, url, display, institution_code, origin = nil, nil, nil, nil, nil, nil
243
+ return linktorsrc, v, url, display, institution_code, origin if input.nil? or input.inner_text.nil?
244
+ linktorsrc = input.inner_text
245
+ linktorsrc.split(/\$(?=\$)/).each do |s|
246
+ v = s.sub!(/^\$V/, "") unless s.match(/^\$V/).nil?
247
+ url = s.sub!(/^\$U/, "") unless s.match(/^\$U/).nil?
248
+ display = s.sub!(/^\$D/, "") unless s.match(/^\$D/).nil?
249
+ institution_code = s.sub!(/^\$I/, "") unless s.match(/^\$I/).nil?
250
+ origin = s.sub!(/^\$O/, "") unless s.match(/^\$O/).nil?
251
+ end
252
+ return linktorsrc, v, url, display, institution_code, origin
253
+ end
254
+
255
+ def process_linktotoc(input)
256
+ linktotoc, url, display, = nil, nil, nil
257
+ return linktotoc, url, display if input.nil? or input.inner_text.nil?
258
+ linktotoc = input.inner_text
259
+ linktotoc.split(/\$(?=\$)/).each do |s|
260
+ url = s.sub!(/^\$U/, "") unless s.match(/^\$U/).nil?
261
+ display = s.sub!(/^\$D/, "") unless s.match(/^\$D/).nil?
262
+ end
263
+ return linktotoc, url, display
264
+ end
265
+
266
+ def raise_required_setup_parameter_error(parameter)
267
+ raise "Initialization error in #{self.class}. Missing required setup parameter: #{parameter}."
268
+ end
269
+
270
+ # def convert_diacritics(string)
271
+ # converter = Iconv.new('UTF-8', 'UTF-8')
272
+ # # Convert value to UTF-8
273
+ # return converter.iconv(string) unless string.nil?
274
+ # end
275
+ end
276
+ end