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,69 @@
1
+ # This is a link_out_filter service, it msut be set up in your services.yml
2
+ # with task: link_out_filter
3
+ #
4
+ # no parameters.
5
+ #
6
+ ## Send an sfx 'pass through' URL _in the background_. Send the user to the
7
+ # target resource directly, but also send a back-channel hit to SFX
8
+ # so SFX will record it for statistical purposes. The reason we
9
+ # do it like this is sometimes the 'pass through' url doesn't work! Better
10
+ # to just lose the statistic than to mess up the user.
11
+ #
12
+ # We also do it all in a background thread, to avoid slowing down the user
13
+ # with this step if SFX is being slow.
14
+ require 'uri'
15
+ require 'net/http'
16
+
17
+ class SfxBackchannelRecord < Service
18
+
19
+ def initialize(config)
20
+ @display_name = "SFX Statistics"
21
+ super(config)
22
+ @timeout ||= 5
23
+ end
24
+
25
+ # This is meant to be called as task:link_out_filter, it doesn't have an
26
+ # implementation for handle, it implements link_out_filter() instead.
27
+ def handle(request)
28
+ raise "Not implemented."
29
+ end
30
+
31
+ # Hook method called by Umlaut.
32
+ # We always return nil because we aren't interested in modifying the url,
33
+ # just using the callback to record the click with SFX.
34
+ def link_out_filter(orig_url, service_response, other_args = {})
35
+ # Only work on responses that came from SFX.
36
+ unless (service_response.service.class.to_s == "Sfx")
37
+ return nil
38
+ end
39
+ make_backchannel_request( service_response )
40
+
41
+
42
+ return nil
43
+ end
44
+
45
+ # Does everything in a background thread to avoid slowing down the user.
46
+ def make_backchannel_request(service_response)
47
+
48
+ Thread.new do
49
+ begin
50
+ direct_sfx_url = Sfx.pass_through_url(service_response.data_values)
51
+ # now we call that url through a back channel just to record it
52
+ # with SFX.
53
+
54
+ parsed_uri = URI.parse(direct_sfx_url )
55
+ sfx_response = Net::HTTP.get_response( parsed_uri )
56
+
57
+ #raise if not 200 OK response
58
+ unless ( sfx_response.kind_of?(Net::HTTPSuccess) ||
59
+ sfx_response.kind_of?(Net::HTTPRedirection) )
60
+ # raise
61
+ sfx_response.value
62
+ end
63
+ rescue Exception => e
64
+ Rails.logger.error("Could not record sfx backchannel click for service_response id #{service_response.id} ; sfx backchannel url attempted: #{direct_sfx_url} ; problem: #{e}")
65
+ Rails.logger.error( e.backtrace.join("\n"))
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,32 @@
1
+ # Since this relies on finding holdings that exist, you need to run it in
2
+ # a service wave AFTER anything that might generate holdings.
3
+ class TxtHoldingExport < AjaxExport
4
+
5
+ def initialize(config)
6
+ @display_text = "Send to phone"
7
+ @form_controller = "export_email"
8
+ @form_action = "txt"
9
+ # providers is a hash of:
10
+ # user-presentable-string => hostname for email to txt service.
11
+ @providers = {
12
+ "Cingular/AT&T" => "cingularme.com",
13
+ "Nextel" => "messaging.nextel.com",
14
+ "Sprint" => "messaging.sprintpcs.com",
15
+ "T-Mobile"=> "tmomail.net",
16
+ "Verizon"=> "vtext.com",
17
+ "Virgin"=> "vmobl.com"
18
+ }
19
+ super(config)
20
+ end
21
+
22
+ def handle(request)
23
+
24
+ holdings = request.get_service_type('holding', { :refresh => true })
25
+ unless holdings.nil? or holdings.empty?
26
+ super(request)
27
+ else
28
+ return request.dispatched(self, true)
29
+ end
30
+ end
31
+
32
+ end
@@ -0,0 +1,57 @@
1
+ # Ulrich's provides journal cover images that are _publically_ available, at
2
+ # predictable urls based on issn. Not sure if this is intentional. But as
3
+ # an ulrich's subscriber, I feel fine using it. If my insitution was not
4
+ # an ulrich's subscriber, I probably wouldn't use this service.
5
+ class UlrichsCover < Service
6
+ require 'open-uri'
7
+
8
+ def service_types_generated
9
+ return [ServiceTypeValue[:cover_image]]
10
+ end
11
+
12
+ def initialize(config)
13
+ @base_url = "http://images.serialssolutions.com/ulrichs/"
14
+
15
+ super(config)
16
+ end
17
+
18
+ def handle(request)
19
+ issn = request.referent.issn
20
+
21
+ # We need an ISSN
22
+ return request.dispatched(self, true) unless issn
23
+
24
+ # No hyphens please
25
+ issn = issn.gsub(/[^0-9X]/, '')
26
+
27
+ check_url = @base_url + issn + '.gif'
28
+
29
+ # does it exist?
30
+ if ( url_resolves(check_url) )
31
+ request.add_service_response(:service => self,
32
+ :service_type_value => :cover_image ,
33
+ :url => check_url,
34
+ :size => "medium" )
35
+ end
36
+
37
+ return request.dispatched(self, true)
38
+ end
39
+
40
+ def url_resolves(url)
41
+ uri_obj = URI.parse(url)
42
+ response = Net::HTTP.start(uri_obj.host, uri_obj.port) {|http|
43
+ http.head(uri_obj.request_uri)
44
+ }
45
+ if (response.kind_of?( Net::HTTPSuccess ))
46
+ return true
47
+ elsif ( response.kind_of?(Net::HTTPNotFound))
48
+ return false
49
+ else
50
+ # unexpected condition, raise
51
+ response.value
52
+ end
53
+
54
+
55
+ end
56
+
57
+ end
@@ -0,0 +1,47 @@
1
+ # A very simple service that simply generates a "highlighted_link" to
2
+ # the Ulrich's periodical directory, if an ISSN is present.
3
+ # Does not actually look up first to make sure there are results, it's
4
+ # a blind link.
5
+ # config params:
6
+ # link_name: Name to put on the link. Defaults to "Periodical Information from Ulrich's Directory".
7
+ class UlrichsLink < Service
8
+
9
+ def initialize(config)
10
+ # Original one, which just apes the UlrichsWeb html interface, and gives
11
+ # you a search results screen even with only one hit.
12
+ #@base_url = "http://www.ulrichsweb.com/ulrichsweb/Search/doAdvancedSearch.asp?QuickCriteria=ISSN&Action=Search&collection=SERIAL&QueryMode=Simple&ResultTemplate=quickSearchResults.hts&SortOrder=Asc&SortField=f_display_title&ScoreThreshold=0&ResultCount=25&SrchFrm=Home&setting_saving=on&QuickCriteriaText="
13
+ super(config)
14
+ # better one, which Yvette at Ulrich's showed me for SFX, which seems to work better.
15
+ @vendor ||= "Umlaut"
16
+ @base_url ||= "https://ulrichsweb.serialssolutions.com/api/openurl?issn="
17
+ # Old one
18
+ #@base_url ||= "http://www.ulrichsweb.com/ulrichsweb/Search/call_fullCitation.asp?/vendor_redirect.asp?oVendor=#{@vendor}&oIssn="
19
+ @link_name = "Periodical information"
20
+ end
21
+
22
+ def service_types_generated
23
+ return [ServiceTypeValue[:highlighted_link]]
24
+ end
25
+
26
+ def handle(request)
27
+ unless (request.referent.issn.blank?)
28
+ display_text = @link_name
29
+
30
+ url = url_for_issn( request.referent.issn )
31
+
32
+ request.add_service_response(
33
+ :service=>self,
34
+ :url=>url,
35
+ :display_text=>display_text,
36
+ :service_type_value => :highlighted_link)
37
+ end
38
+
39
+ return request.dispatched(self, true)
40
+ end
41
+
42
+ def url_for_issn(issn)
43
+ # with or without hyphen should work fine.
44
+ return @base_url + issn
45
+ end
46
+
47
+ end
@@ -0,0 +1,116 @@
1
+ # Link to worldcat.org that relies on screen scraping to see if it's gotten
2
+ # a hit.
3
+ #
4
+ # Warning, worldcat can be awfully slow to respond.
5
+ # optional search_zip_code param.
6
+ # Optional base_url param, but I don't know why you'd want to change it.
7
+ # display_text
8
+ require 'uri'
9
+ require 'net/http'
10
+ class Worldcat < Service
11
+ include MetadataHelper
12
+ include UmlautHttp
13
+
14
+ def initialize(config)
15
+ # defaults
16
+ @suppress_precheck = false # it seems unneccesary to pre-check worldcat, it's mostly ALWAYS a positive hit. And pre-checking against worldcat is running into Worldcat's rate limiting defenses. If neccesary, you can turn this off. Really, we should be using the Worldcat API anyway.
17
+ @base_url = 'http://www.worldcat.org/'
18
+ @display_text = 'Find in other libraries'
19
+ @display_name = 'OCLC WorldCat.org'
20
+
21
+ @credits = {
22
+ "OCLC WorldCat.org" => "http://www.worldcat.org/"
23
+ }
24
+
25
+ super(config)
26
+ end
27
+
28
+ def service_types_generated
29
+ return [ServiceTypeValue['highlighted_link']]
30
+ end
31
+
32
+ def handle(request)
33
+ isbn = get_identifier(:urn, "isbn", request.referent)
34
+ issn = get_identifier(:urn, "issn", request.referent)
35
+ oclcnum = get_identifier(:info, "oclcnum", request.referent)
36
+
37
+
38
+ isxn_key = nil
39
+ isxn_value = nil
40
+ if (! oclcnum.blank?)
41
+ isxn_key = 'oclc'
42
+ isxn_value = oclcnum
43
+ elsif (! issn.blank?)
44
+ isxn_key = 'issn'
45
+ #isxn_value = ref_metadata['issn'] + '+dt:ser'
46
+ isxn_value = issn
47
+ elsif (! isbn.blank?)
48
+ isxn_key = 'isbn'
49
+ isxn_value = isbn
50
+ else
51
+ # We have no useful identifiers
52
+ return request.dispatched(self, true)
53
+ end
54
+
55
+ # Do some cleanup of the value. Sometimes spaces or other
56
+ # weird chars get in there, why not strip out everything that
57
+ # isn't a number or X?
58
+ isxn_value = isxn_value.gsub( /[^\dX]/, '')
59
+ # and URL escape just to be safe, although really shouldn't be neccesary
60
+ isxn_value = URI.escape( isxn_value )
61
+
62
+ # We do a pre-emptive lookup to worldcat to try and see if worldcat
63
+ # has a hit or not, before adding the link.
64
+ isxn_key = URI.escape( isxn_key )
65
+ uri_str = @base_url+isxn_key+'/'+isxn_value
66
+ uri_str += "&loc=#{URI.escape(@search_zip_code.to_s)}" if @search_zip_code
67
+
68
+
69
+ begin
70
+ worldcat_uri = URI.parse(uri_str)
71
+ rescue Exception => e
72
+ Rails.logger.error("Bad worldcat uri string constructed?")
73
+ Rails.logger.error(e)
74
+ return request.dispatched(self, DispatchedService::FailedFatal)
75
+ end
76
+
77
+ unless ( @suppress_precheck )
78
+
79
+ http = Net::HTTP.new worldcat_uri.host
80
+ http.open_timeout = 7
81
+ http.read_timeout = 7
82
+
83
+
84
+ begin
85
+ # Fake being a proxy to send info on actual end-user client to worldcat,
86
+ # to lessen chance of worldcat traffic limiters.
87
+ headers = proxy_like_headers( request, worldcat_uri.host )
88
+ wc_response = http.get(worldcat_uri.path, headers)
89
+ rescue Timeout::Error => exception
90
+ return request.dispatched(self, DispatchedService::FailedTemporary, exception)
91
+ end
92
+
93
+ # Bad response code?
94
+ unless wc_response.code == "200"
95
+ # Could be temporary, could be fatal. Let's say temporary.
96
+ return request.dispatched(self, DispatchedService::FailedTemporary, Exception.new("oclc returned error http status code: #{wc_response.code}"))
97
+ end
98
+
99
+ # Sadly, worldcat returns a 200 even if there are no matches.
100
+ # We need to screen-scrape to discover if there are matches.
101
+ if (wc_response.body =~ /The page you tried was not found\./)
102
+ # Not found in worldcat, we won't add a link.
103
+ return request.dispatched(self, true)
104
+ end
105
+ end
106
+
107
+ request.add_service_response(
108
+ :service=>self,
109
+ :url=>worldcat_uri.to_s,
110
+ :display_text=>@display_text,
111
+ :service_type_value => :highlighted_link
112
+ )
113
+
114
+ return request.dispatched(self, true)
115
+ end
116
+ end
@@ -0,0 +1,591 @@
1
+ # Service that uses available metadata to try to find an exact match to a
2
+ # WorldCat Identity.
3
+ #
4
+ # Requires sufficient author information and/or an oclcnumber to have enough
5
+ # info to try and find a match. Best to run AFTER services that may enhance
6
+ # metadata with this info (such as Amazon).
7
+ #
8
+ # See: http://outgoing.typepad.com/outgoing/2008/06/linking-to-worl.html
9
+ #
10
+ # Creates a highlighted_link
11
+ # Even though the WorldCat Identities API is built on top of SRU we use
12
+ # open-uri to simply fetch the "html" page (which is really XML with a stylesheet)
13
+ # and look at the XML directly.
14
+
15
+ #. SRU was too slow and was timing out the background service.
16
+ # (because SRU parses the response with REXML? we don't want to have anything
17
+ # to do with REXML! Even still retrieving the large XML file and traversing
18
+ # it with Hpricot (what we used to use) was still rather slow. The suggestion is to enable few
19
+ # note_types and then constrain the number shown. The defaults are hopefully
20
+ # sane in this regard. note_types can be set to false to turn them off.
21
+ #
22
+ # Also can create an optional link to Wikipedia.
23
+ #
24
+ # There's probably a lot more we could pull out of these identities pages if
25
+ # we wanted to. If more of these are used they might warrant their own
26
+ # service type and part of the page for better layout.
27
+
28
+ class WorldcatIdentities < Service
29
+ require 'open-uri' # SRU is too slow even though we use an SRU-like link
30
+ require 'nokogiri'
31
+ include MetadataHelper
32
+
33
+ attr_reader :url, :note_types, :display_name, :wikipedia_link, :openurl_base,
34
+ :require_identifier,
35
+ # below starts the note_types which can be restrained
36
+ :num_of_roles, :num_of_subject_headings, :num_of_works, :num_of_genres
37
+
38
+ def service_types_generated
39
+ return [ ServiceTypeValue[:highlighted_link] ]
40
+ end
41
+
42
+ def initialize(config)
43
+ @url = 'http://worldcat.org/identities/search/'
44
+ @note_types = ["combined_counts"]
45
+ @display_name = "WorldCat Identities"
46
+ @require_identifier = false
47
+ # any plural note_types can be restrained
48
+ @num_of_roles = 5
49
+ @num_of_works = 1
50
+ @num_of_genres = 5
51
+ @wikipedia_link = true
52
+ @openurl_widely_held = true
53
+ @worldcat_widely_held = false
54
+ @openurl_base = '/resolve'
55
+
56
+ @credits = {
57
+ "WorldCat Identities" => "http://www.worldcat.org/identities/"
58
+ }
59
+
60
+ super(config)
61
+ end
62
+
63
+ def handle(request)
64
+ index, query = define_query(request.referent)
65
+
66
+ unless query.blank?
67
+ do_query(request, index, query)
68
+ end
69
+ return request.dispatched(self, true)
70
+ end
71
+
72
+ def define_query(rft)
73
+ oclcnum = get_identifier(:info, "oclcnum", rft)
74
+ metadata = rft.metadata
75
+
76
+ # Do we have enough info to do a query with sufficient precision?
77
+ # We are choosing better recall in exchange for lower precision.
78
+ # We'll search with oclcnum if we have it, but not require it, we'll search
79
+ # fuzzily on various parts of the name if neccesary.
80
+ if ( oclcnum.blank? && ( metadata['aulast'].blank? || metadata['aufirst'].blank? ) && metadata['au'].blank? && metadata['aucorp'].blank? ) or (oclcnum.blank? && @require_identifier)
81
+ Rails.logger.debug("Worldcat Identities Service Adaptor: Skipped: Insufficient metadata for lookup")
82
+ return nil
83
+ end
84
+
85
+
86
+ # instead of searching across all indexes we target the one we want
87
+ name_operator = "%3D"
88
+ if ((! metadata['aulast'].blank?) && oclcnum)
89
+ # Just last name is enough, we have an oclcnum.
90
+ index = 'PersonalIdentities'
91
+ name_part = 'FamilyName'
92
+ name = clean_name(metadata['aulast'])
93
+ elsif (! metadata['au'].blank? )
94
+ # Next choice, undivided author string
95
+ index = "PersonalIdentities"
96
+ name_part = 'Name'
97
+ name = clean_name(metadata['au'])
98
+ name_operator = "all"
99
+ elsif (not metadata['aulast'].blank? and not metadata['aufirst'].blank?)
100
+ # combine them.
101
+ index = "PersonalIdentities"
102
+ name_part = 'Name'
103
+ name = clean_name(metadata['aufirst'] + ' ' + metadata['aulast'])
104
+ name_operator = "all"
105
+ elsif metadata['aucorp']
106
+ # corp name
107
+ index = 'CorporateIdentities'
108
+ name_part = 'Name'
109
+ name = clean_name(metadata['aucorp'])
110
+ else
111
+ # oclcnum but no author information at all! Might still work...
112
+ index = "Identities"
113
+ end
114
+
115
+ query_conditions = []
116
+ query_conditions << "local.#{name_part}+#{name_operator}+%22#{name}%22" if name
117
+ query_conditions << "local.OCLCNumber+%3D+%22#{CGI.escape(oclcnum)}%22" unless oclcnum.blank?
118
+
119
+ query = query_conditions.join("+and+")
120
+
121
+ # Sort keys is important when we don't have an oclcnumber, and doesn't hurt
122
+ # when we do.
123
+ query += "&sortKeys=holdingscount"
124
+ return index, query
125
+ end
126
+
127
+ # We might have to remove certain characters, but for now we just CGI.escape
128
+ # it and remove any periods
129
+ def clean_name(name)
130
+ CGI.escape(name).gsub('.', '')
131
+ end
132
+
133
+ def do_query(request, index, query)
134
+ # since we're only doing exact matching with last name and OCLCnum
135
+ # we only request 1 record to hopefully speed things up.
136
+ link = @url + index + '?query=' +query + "&maximumRecords=1"
137
+
138
+ result = open(link).read
139
+ xml = Nokogiri::XML(result)
140
+
141
+ # Identities namespaces are all over the place, it's too hard
142
+ # to interrogate with namespaces, ask nokogiri to remove them all
143
+ # instead.
144
+ xml.remove_namespaces!
145
+
146
+
147
+ return nil if xml.at("numberOfRecords").inner_text == '0'
148
+ create_link(request, xml)
149
+ create_wikipedia_link(request, xml) if @wikipedia_link
150
+ create_openurl_widely_held(request, xml) if @openurl_widely_held
151
+ create_worldcat_widely_held(request, xml) if @worldcat_widely_held
152
+ end
153
+
154
+ def create_link(request, xml)
155
+ display_name = "About " + extract_display_name(xml)
156
+ extracted_notes = extract_notes(xml) if @note_types
157
+ url = extract_url(xml)
158
+ create_service_response(request, display_name, url, extracted_notes )
159
+ end
160
+
161
+ def extract_notes(xml)
162
+ note_pieces = []
163
+ # a tiny bit of metaprogramming to make it easy to add methods and config
164
+ # for note_types
165
+ @note_types.each do |nt|
166
+ method = ("extract_" + nt).to_sym
167
+ answer = self.send(method, xml)
168
+ note_pieces << answer unless answer.nil?
169
+ end
170
+ return nil if note_pieces.blank?
171
+ return note_pieces.join(' | ')
172
+ end
173
+
174
+ def extract_display_name(doc)
175
+ name = []
176
+ rawname = doc.at("nameInfo/rawName")
177
+ return nil unless rawname
178
+ rawname.children.each do |name_part|
179
+ name << name_part.inner_text
180
+ end
181
+ return nil if name.blank?
182
+ return name.join(' ')
183
+ end
184
+
185
+ def extract_subject_headings(doc)
186
+ subject_headings = []
187
+ (doc.search("biogSH")).each_with_index do |sh, i|
188
+ subject_headings << sh.inner_text
189
+ break if @num_of_subject_headings == i + 1
190
+ end
191
+ return nil if subject_headings.blank?
192
+ "subject headings: " + subject_headings.join('; ')
193
+ end
194
+
195
+ def extract_roles(doc)
196
+ codes = []
197
+ (doc.search("relators/relator")).each_with_index do |relate, i|
198
+ codes << relate.attributes['code']
199
+ break if @num_of_roles == i + 1
200
+ end
201
+ return nil if codes.blank?
202
+ roles = codes.map{|code| RELATOR_CODES[code] }
203
+ "roles: " + roles.join(', ')
204
+ end
205
+
206
+ # FIXME a lot more could be done with "by citations". identities gives summaries
207
+ # of the most popular works as well as other descriptive information like
208
+ # subject headings. This might be able to be used for enhancing metadata.
209
+ def extract_works(doc)
210
+ works = []
211
+ doc.search("by/citation/title").each_with_index do |t, i|
212
+ works << t.inner_text
213
+ break if @num_of_works == i + 1
214
+ end
215
+ return nil if works.blank?
216
+ "most widely held #{works.length == 1 ? "work" : "works"}: " + works.join("; ")
217
+ end
218
+
219
+ def extract_genres(doc)
220
+ genres = []
221
+ doc.search("genres/genre").each_with_index do |g, i|
222
+ genres << g.inner_text
223
+ break if @num_of_genres == i + 1
224
+ end
225
+ return nil if genres.blank?
226
+ "genres: " + genres.join(', ')
227
+ end
228
+
229
+ def extract_combined_counts(doc)
230
+ work_count = extract_work_count(doc)
231
+ publications_count = extract_publications_count(doc)
232
+ holdings_count = extract_holdings_count(doc)
233
+ work_count << " in " << publications_count << " with " <<
234
+ holdings_count
235
+ end
236
+
237
+ def extract_work_count(doc)
238
+ work_count = doc.at("workCount").inner_text
239
+ return insert_commas(work_count) << " works"
240
+ end
241
+
242
+ def extract_holdings_count(doc)
243
+ total_holdings = doc.at("totalHoldings").inner_text
244
+ return insert_commas(total_holdings) << " total holdings in WorldCat"
245
+ end
246
+
247
+ def extract_publications_count(doc)
248
+ return insert_commas( doc.at("recordCount").inner_text ) << " publications"
249
+ end
250
+
251
+ def extract_url(doc)
252
+ pnkey = doc.at("pnkey").inner_text
253
+ return 'http://worldcat.org/identities/' << pnkey
254
+ end
255
+
256
+ def insert_commas(n)
257
+ n.reverse.scan(/(?:\d*\.)?\d{1,3}-?/).join(',').reverse
258
+ end
259
+
260
+ def create_service_response(request, display_name, url, extracted_notes)
261
+ request.add_service_response(
262
+ :service=>self,
263
+ :url=>url,
264
+ :display_text=>display_name,
265
+ :notes => extracted_notes,
266
+ :service_type_value => :highlighted_link)
267
+ end
268
+
269
+ def create_wikipedia_link(request, xml)
270
+ name_element = xml.at("wikiLink")
271
+ return nil unless name_element
272
+ name = name_element.inner_text
273
+ # This is the base link that worldcat identities uses so we use the same
274
+ link = "http://en.wikipedia.org/wiki/Special:Search?search=" << name
275
+ request.add_service_response(
276
+ :service=>self,
277
+ :url=>link,
278
+ :display_text=> "About " + name.titlecase,
279
+ :notes => '',
280
+ :source => 'Wikipedia',
281
+ :service_type_value => :highlighted_link)
282
+ end
283
+
284
+ def create_openurl_widely_held(request, xml)
285
+ widely_held = get_widely_held_info(xml)
286
+ # try to remove circular links
287
+ return nil if circular_link?(request, widely_held)
288
+
289
+ openurl = create_openurl(request, widely_held)
290
+
291
+ request.add_service_response(
292
+ :service=>self,
293
+ :url=>openurl,
294
+ :display_text=> widely_held['title'],
295
+ :notes => "This author's most widely held work.",
296
+ :service_type_value => :highlighted_link)
297
+ end
298
+
299
+ def circular_link?(request, citation_info)
300
+ rft = request.referent
301
+ request_oclcnum = get_identifier(:info, "oclcnum", rft)
302
+ request_title = get_search_title(rft)
303
+ return true if citation_info['oclcnum'] == request_oclcnum
304
+ #further cleaning might be necessary for titles to be good matches
305
+ return true if citation_info['title'].strip == request_title.strip
306
+ end
307
+
308
+ #createsa minimal openurl to make a new request to umlaut
309
+ def create_openurl(request, wh)
310
+ metadata = request.referent.metadata
311
+
312
+ co = OpenURL::ContextObject.new
313
+ cor = co.referent
314
+ cor.set_format(wh['record_type'])
315
+ cor.add_identifier("info:oclcnum/#{wh['oclcnum']}")
316
+ cor.set_metadata('aulast', metadata['aulast'] ) if metadata['aulast']
317
+ cor.set_metadata('aufirst', metadata['aufirst']) if metadata['aufirst']
318
+ cor.set_metadata('aucorp', metadata['aucorp']) if metadata['aucorp']
319
+ cor.set_metadata('title', wh['title'])
320
+ link = @openurl_base + '?' + co.kev
321
+ return link
322
+ end
323
+
324
+ # We just link to worldcat using the oclc number provided
325
+ # FIXME this might need special partial if we incorporate a cover image
326
+ def create_worldcat_widely_held(request, xml)
327
+
328
+ # try to prevent circular links
329
+ top_holding_info = get_widely_held_info(xml)
330
+ return nil if circular_link?(request, top_holding_info)
331
+
332
+ # http://www.worldcat.org/links/
333
+ most = top_holding_info['most']
334
+ title = top_holding_info['title']
335
+ oclcnum = top_holding_info['oclcnum']
336
+
337
+ link = 'http://www.worldcat.org/oclc/' << oclcnum
338
+ cover_image_link = extract_cover_image_link(request, most)
339
+ notes = "this author's most widely held work in WorldCat"
340
+ if cover_image_link
341
+ display_text = '<img src="' << cover_image_link << '" style="width:75px;"/>'
342
+ notes = title << ' is ' << notes
343
+ else
344
+ display_text = title
345
+ end
346
+
347
+ request.add_service_response(
348
+ :service=>self,
349
+ :url=>link,
350
+ :display_text=> display_text,
351
+ :notes => notes,
352
+ :service_type_value => :highlighted_link)
353
+ end
354
+
355
+ def get_widely_held_info(xml)
356
+ h = {}
357
+ h['most'] = most = xml.at("by/citation")
358
+ h['oclcnum'] = clean_oclcnum(most.at("oclcnum").inner_text)
359
+ h['title'] = most.at("title").inner_text
360
+ h['record_type'] = most.at('recordType').inner_text
361
+ h
362
+ end
363
+
364
+ def extract_cover_image_link(request, citation)
365
+ cover = citation.at("cover")
366
+ return nil unless cover
367
+ # we try not to show a cover if we already probably have the same cover
368
+ # showing.
369
+ oclc = clean_oclcnum( cover.attributes['oclc'] )
370
+ metadata = request.referent.metadata
371
+ if metadata['oclcnum'] and metadata['oclcnum'] =~ oclc
372
+ return nil
373
+ end
374
+ cover_number = cover.inner_text
375
+ if metadata['isbn'] and metadata['isbn'] == cover_number
376
+ return nil
377
+ end
378
+
379
+ if cover.attributes["type"] == 'isbn'
380
+ link = "http://www.worldcat.org/wcpa/servlet/DCARead?standardNoType=1&standardNo="
381
+ return link << cover_number
382
+ end
383
+ return nil
384
+ end
385
+
386
+ def clean_oclcnum(num)
387
+ # got the follow from referent.rb ~152 and added ocn
388
+ if num =~ /(ocn0*|ocm0*|\(OCoLC\)|ocl70*|0+)(.*)$/
389
+ num = $2
390
+ end
391
+ return num
392
+ end
393
+
394
+ # relator codes are from http://worldcat.org/identities/relators.xml which was
395
+ # referenced from http://worldcat.org/identities/Identities.xsl
396
+ RELATOR_CODES = {
397
+ "act" => "Actor",
398
+ "adp" => "Adapter",
399
+ "aft" => "Author of afterword, colophon, etc.",
400
+ "anm" => "Animator ",
401
+ "ann" => "Annotator",
402
+ "ant" => "Bibliographic antecedent",
403
+ "app" => "Applicant",
404
+ "aqt" => "Author in quotations or text abstracts",
405
+ "arc" => "Architect",
406
+ "arr" => "Arranger",
407
+ "art" => "Artist",
408
+ "asg" => "Assignee",
409
+ "asn" => "Associated name",
410
+ "att" => "Attributed name",
411
+ "auc" => "Auctioneer",
412
+ "aud" => "Author of dialog",
413
+ "aui" => "Author of introduction",
414
+ "aus" => "Author of screenplay",
415
+ "aut" => "Author",
416
+ "bdd" => "Binding designer",
417
+ "bjd" => "Bookjacket designer",
418
+ "bkd" => "Book designer",
419
+ "bkp" => "Book producer",
420
+ "bnd" => "Binder",
421
+ "bpd" => "Bookplate designer",
422
+ "bsl" => "Bookseller",
423
+ "ccp" => "Conceptor",
424
+ "chr" => "Choreographer",
425
+ "clb" => "Collaborator",
426
+ "cli" => "Client",
427
+ "cll" => "Calligrapher",
428
+ "clt" => "Collotyper",
429
+ "cmm" => "Commentator",
430
+ "cmp" => "Composer",
431
+ "cmt" => "Compositor",
432
+ "cng" => "Cinematographer ",
433
+ "cnd" => "Conductor",
434
+ "cns" => "Censor",
435
+ "coe" => "Contestant -appellee",
436
+ "col" => "Collector",
437
+ "com" => "Compiler",
438
+ "cos" => "Contestant",
439
+ "cot" => "Contestant -appellant",
440
+ "cov" => "Cover designer",
441
+ "cpc" => "Copyright claimant",
442
+ "cpe" => "Complainant-appellee",
443
+ "cph" => "Copyright holder",
444
+ "cpl" => "Complainant",
445
+ "cpt" => "Complainant-appellant",
446
+ "cre" => "Creator",
447
+ "crp" => "Correspondent",
448
+ "crr" => "Corrector",
449
+ "csl" => "Consultant",
450
+ "csp" => "Consultant to a project",
451
+ "cst" => "Costume designer",
452
+ "ctb" => "Contributor",
453
+ "cte" => "Contestee-appellee",
454
+ "ctg" => "Cartographer",
455
+ "ctr" => "Contractor",
456
+ "cts" => "Contestee",
457
+ "ctt" => "Contestee-appellant",
458
+ "cur" => "Curator",
459
+ "cwt" => "Commentator for written text",
460
+ "dfd" => "Defendant",
461
+ "dfe" => "Defendant-appellee",
462
+ "dft" => "Defendant-appellant",
463
+ "dgg" => "Degree grantor",
464
+ "dis" => "Dissertant",
465
+ "dln" => "Delineator",
466
+ "dnc" => "Dancer",
467
+ "dnr" => "Donor",
468
+ "dpc" => "Depicted",
469
+ "dpt" => "Depositor",
470
+ "drm" => "Draftsman",
471
+ "drt" => "Director",
472
+ "dsr" => "Designer",
473
+ "dst" => "Distributor",
474
+ "dte" => "Dedicatee",
475
+ "dto" => "Dedicator",
476
+ "dub" => "Dubious author",
477
+ "edt" => "Editor",
478
+ "egr" => "Engraver",
479
+ "elt" => "Electrotyper",
480
+ "eng" => "Engineer",
481
+ "etr" => "Etcher",
482
+ "exp" => "Expert",
483
+ "fac" => "Facsimilist",
484
+ "flm" => "Film editor",
485
+ "fmo" => "Former owner",
486
+ "fpy" => "First party",
487
+ "fnd" => "Funder",
488
+ "frg" => "Forger",
489
+ "grt" => "Graphic technician",
490
+ "hnr" => "Honoree",
491
+ "hst" => "Host",
492
+ "ill" => "Illustrator",
493
+ "ilu" => "Illuminator",
494
+ "ins" => "Inscriber",
495
+ "inv" => "Inventor",
496
+ "itr" => "Instrumentalist",
497
+ "ive" => "Interviewee",
498
+ "ivr" => "Interviewer",
499
+ "lbt" => "Librettist",
500
+ "lee" => "Libelee-appellee",
501
+ "lel" => "Libelee",
502
+ "len" => "Lender",
503
+ "let" => "Libelee-appellant",
504
+ "lgd" => "Lighting designer ",
505
+ "lie" => "Libelant-appellee",
506
+ "lil" => "Libelant",
507
+ "lit" => "Libelant-appellant",
508
+ "lsa" => "Landscape architect",
509
+ "lse" => "Licensee",
510
+ "lso" => "Licensor",
511
+ "ltg" => "Lithographer",
512
+ "lyr" => "Lyricist",
513
+ "mfr" => "Manufacturer ",
514
+ "mdc" => "Metadata contact",
515
+ "mod" => "Moderator",
516
+ "mon" => "Monitor",
517
+ "mrk" => "Markup editor",
518
+ "mte" => "Metal-engraver",
519
+ "mus" => "Musician",
520
+ "nrt" => "Narrator",
521
+ "opn" => "Opponent",
522
+ "org" => "Originator",
523
+ "orm" => "Organizer of meeting",
524
+ "oth" => "Other",
525
+ "own" => "Owner",
526
+ "pat" => "Patron",
527
+ "pbd" => "Publishing director",
528
+ "pbl" => "Publisher",
529
+ "pfr" => "Proofreader",
530
+ "pht" => "Photographer",
531
+ "plt" => "Platemaker",
532
+ "pop" => "Printer of plates",
533
+ "ppm" => "Papermaker",
534
+ "ppt" => "Puppeteer ",
535
+ "prc" => "Process contact",
536
+ "prd" => "Production personnel",
537
+ "prf" => "Performer",
538
+ "prg" => "Programmer",
539
+ "prm" => "Printmaker",
540
+ "pro" => "Producer",
541
+ "prt" => "Printer",
542
+ "pta" => "Patent applicant",
543
+ "pte" => "Plaintiff -appellee",
544
+ "ptf" => "Plaintiff",
545
+ "pth" => "Patent holder",
546
+ "ptt" => "Plaintiff-appellant",
547
+ "rbr" => "Rubricator",
548
+ "rce" => "Recording engineer",
549
+ "rcp" => "Recipient",
550
+ "red" => "Redactor",
551
+ "ren" => "Renderer",
552
+ "res" => "Researcher",
553
+ "rev" => "Reviewer",
554
+ "rpt" => "Reporter",
555
+ "rpy" => "Responsible party",
556
+ "rse" => "Respondent -appellee",
557
+ "rsg" => "Restager ",
558
+ "rsp" => "Respondent",
559
+ "rst" => "Respondent-appellant",
560
+ "rth" => "Research team head",
561
+ "rtm" => "Research team member",
562
+ "sad" => "Scientific advisor",
563
+ "sce" => "Scenarist",
564
+ "scl" => "Sculptor",
565
+ "scr" => "Scribe",
566
+ "sec" => "Secretary",
567
+ "sgn" => "Signer",
568
+ "sng" => "Singer",
569
+ "spk" => "Speaker",
570
+ "spn" => "Sponsor",
571
+ "spy" => "Second party",
572
+ "srv" => "Surveyor",
573
+ "std" => "Set designer ",
574
+ "stl" => "Storyteller",
575
+ "stn" => "Standards body",
576
+ "str" => "Stereotyper",
577
+ "tch" => "Teacher ",
578
+ "ths" => "Thesis advisor",
579
+ "trc" => "Transcriber",
580
+ "trl" => "Translator",
581
+ "tyd" => "Type designer",
582
+ "tyg" => "Typographer",
583
+ "vdg" => "Videographer ",
584
+ "voc" => "Vocalist",
585
+ "wam" => "Writer of accompanying material",
586
+ "wdc" => "Woodcutter",
587
+ "wde" => "Wood -engraver",
588
+ "wit" => "Witness"
589
+ }
590
+
591
+ end