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