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,7 @@
1
+ module Hip3
2
+ class Receipt
3
+ def foo
4
+ raise "foo"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,82 @@
1
+ module Hip3
2
+ # Keeps a reference to it's bib, if it needs to load it's data,
3
+ # it asks bib to load all
4
+ # data, and the bib loads it at once for all copies, in one fetch.
5
+ class SerialCopy < Holding
6
+ @@Field_labels = {:location => 'Location', :collection => 'Collection', :call_no => 'Call No.', :copy_str => 'Copy No.', :status => 'Status', :notes => 'Notes'}
7
+ attr_accessor :items # array of items
8
+ attr_accessor :items_loaded
9
+ attr_accessor :runs # array of run types/statements
10
+
11
+ def initialize(argBibObj, serialXmlElement=nil)
12
+ self.bib = argBibObj
13
+ self.items_loaded = false
14
+ if ( serialXmlElement )
15
+ loadFromSerialElement( serialXmlElement )
16
+ end
17
+ end
18
+
19
+ def items
20
+ bib.load_items_from_store if ! items_loaded?
21
+
22
+ return @items || []
23
+ end
24
+
25
+ def items_loaded?
26
+ return (items_loaded == true)
27
+ end
28
+
29
+ def loadFromSerialElement( serialElement )
30
+ self.location_str = serialElement.at('/location').inner_text
31
+ self.id = serialElement.at('/copykey').inner_text
32
+
33
+ # Okay, this part is potentially fragile, we have to pull out based on
34
+ # order in the XML, not sure if that can change. Sorry, that's HIP for you.
35
+ copyElements = serialElement.search('/copy/cell/data/text').collect {|e| e.inner_text}
36
+ # Fix this to use field lookup
37
+ self.location_str = bib.copy_field_lookup.text_value_for(copyElements, @@Field_labels[:location])
38
+ self.collection_str = bib.copy_field_lookup.text_value_for(copyElements, @@Field_labels[:collection])
39
+ self.call_no = bib.copy_field_lookup.text_value_for(copyElements, @@Field_labels[:call_no])
40
+ self.copy_str = bib.copy_field_lookup.text_value_for(copyElements, @@Field_labels[:copy_str])
41
+ self.status_str = bib.copy_field_lookup.text_value_for(copyElements, @@Field_labels[:status])
42
+ self.notes = bib.copy_field_lookup.text_value_for(copyElements, @@Field_labels[:notes])
43
+
44
+
45
+ #Okay, got to get the 'runs' for summary holdings info.
46
+ self.runs ||= []
47
+ serialElement.search('/runlist/run').each do |run|
48
+ label = run.at('/runlabel').inner_text
49
+ run.search('/data/rundata').each do |rundata|
50
+ run = {:label => label, :statement => textValue(rundata.at('/text'))}
51
+ run[:note] = textValue(rundata.at('/note'))
52
+
53
+ self.runs.push( run )
54
+ end
55
+ end
56
+ end
57
+
58
+ # Not too useful, use coverage_str_to_a instead usually
59
+ def coverage_str
60
+ return runs.to_s
61
+ end
62
+
63
+ # Over-riding
64
+ def coverage_str_to_a
65
+ runs.collect do |r|
66
+ s = ''
67
+ (s << r[:label] << ": ") if (! r[:label].blank?) && r[:label] != "Main run"
68
+ s << r[:statement]
69
+ s << '-- ' << r[:note] if r[:note]
70
+ s
71
+ end
72
+ end
73
+
74
+ def register_item(item)
75
+ items ||= []
76
+
77
+ unless items.include?(item)
78
+ items.push(item)
79
+ end
80
+ end
81
+ end
82
+ end
data/lib/holding.rb ADDED
@@ -0,0 +1,32 @@
1
+ class Holding
2
+ attr_accessor :locations, :identifier
3
+ def initialize
4
+ @locations = []
5
+ end
6
+ def find_location(location)
7
+ @locations.each do | loc |
8
+ return loc if loc.name == location
9
+ end
10
+ return nil
11
+ end
12
+
13
+ def find_item_by_attribute(key, value)
14
+ @locations.each do | loc |
15
+ loc.items.each do | item |
16
+ return if item.instance_variable_get('@'+key) == value
17
+ end
18
+ end
19
+ return nil
20
+ end
21
+ end
22
+
23
+ class HoldingLocation
24
+ attr_accessor :name, :code, :items
25
+ def initialize
26
+ @items = []
27
+ end
28
+ end
29
+
30
+ class HoldingItem
31
+ attr_accessor :identifier, :status_code, :status_date, :status, :call_number, :enumeration, :chron, :year
32
+ end
@@ -0,0 +1,254 @@
1
+
2
+
3
+ module MarcHelper
4
+
5
+ # Takes an array of ruby MARC objects, adds ServiceResponses
6
+ # for the 856 links contained.
7
+ # Returns a hash of arrays of ServiceResponse objects added, keyed
8
+ # by service type value string.
9
+ def add_856_links(request, marc_records, options = {})
10
+ options[:default_service_type] ||= "fulltext"
11
+ options[:match_reliability] ||= ServiceResponse::MatchExact
12
+
13
+ responses_added = Hash.new
14
+
15
+ # Keep track of urls to avoid putting the exact same url in twice
16
+ urls_seen = Array.new
17
+
18
+ marc_records.each do |marc_xml|
19
+
20
+ marc_xml.find_all {|f| '856' === f.tag}.each do |field|
21
+ # Might have more than one $u, in which case we want to
22
+ # possibly add each of them. Might have 0 $u in which case
23
+ # we skip.
24
+ field.subfields.find_all {|sf| sf.code == 'u'}.each do |sf|
25
+ url = sf.value
26
+
27
+ # Already got it from another catalog record?
28
+ next if urls_seen.include?(url)
29
+
30
+ # Trying to avoid duplicates with SFX/link resolver.
31
+ next if should_skip_856_link?(request, marc_xml, url)
32
+
33
+ urls_seen.push(url)
34
+
35
+
36
+ display_name = nil
37
+ if field['y']
38
+ display_name = field['y']
39
+ else
40
+ # okay let's try taking just the domain from the url
41
+ begin
42
+ u_obj = URI::parse( url )
43
+ display_name = u_obj.host
44
+ rescue Exception
45
+ end
46
+ # Okay, can't parse out a domain, whole url then.
47
+ display_name = url if display_name.nil?
48
+ end
49
+ # But if we've got a $3, the closest MARC comes to a field
50
+ # that explains what this actually IS, use that too please.
51
+ display_name = field['3'] + ' from ' + display_name if field['3']
52
+
53
+ # Build the response.
54
+
55
+ response_params = {:service=>self, :display_text=>display_name, :url=>url}
56
+ # get all those $z subfields and put em in notes.
57
+ response_params[:url] = url
58
+
59
+ # subfield 3 is being used for OCA records loaded in our catalog.
60
+ response_params[:notes] =
61
+ field.subfields.collect {|f| f.value if (f.code == 'z') }.compact.join('; ')
62
+
63
+ is_journal = (marc_xml.leader[7,1] == 's')
64
+ unless ( field['3'] || ! is_journal ) # subfield 3 is in fact some kind of coverage note, usually
65
+ response_params[:notes] += "; " unless response_params[:notes].blank?
66
+ response_params[:notes] += "Dates of coverage unknown."
67
+ end
68
+
69
+
70
+ unless ( options[:match_reliability] == ServiceResponse::MatchExact )
71
+ response_params[:match_reliability] = options[:match_reliability]
72
+
73
+ response_params[:edition_str] = edition_statement(marc_xml)
74
+ end
75
+
76
+ # Figure out the right service type value for this, fulltext, ToC,
77
+ # whatever.
78
+ response_params[:service_type_value] = service_type_for_856( field, options )
79
+
80
+ # fulltext urls from MARC are always marked as specially stupid.
81
+ response_params[:coverage_checked] = false
82
+ response_params[:can_link_to_article] = false
83
+
84
+ # Some debugging info, add the 001 bibID if we have one.
85
+
86
+ response_params[:debug_info] = "BibID: #{marc_xml['001'].value}" if marc_xml['001']
87
+
88
+
89
+ # Add the response
90
+ response = request.add_service_response(response_params)
91
+
92
+ responses_added[response_params[:service_type_value]] ||= Array.new
93
+ responses_added[response_params[:service_type_value]].push(response)
94
+ end
95
+ end
96
+ end
97
+ return responses_added
98
+ end
99
+
100
+ # Used by #add_856_links. Complicated logic to try and avoid
101
+ # presenting a URL from the catalog that duplicates what SFX does,
102
+ # but present a URL from the catalog when it's really needed.
103
+ #
104
+ # One reason not to include Catalog links for an article-level
105
+ # citation, even if SFX provided no targets, is maybe SFX
106
+ # provided no targets because SFX _knew_ that the _particular date_
107
+ # requested is not available. The catalog doesn't know that, but
108
+ # we don't want to show a link from the catalog that SFX really
109
+ # already knew wasn't going to be available.
110
+ #
111
+ # So:
112
+ #
113
+ # If this is a journal, skip the URL if it matches in our
114
+ # SFXUrl finder, because that means we think it's an SFX controlled
115
+ # URL. But if it's not a journal, use it anyway, because it's probably
116
+ # an e-book that is not in SFX, even if it's from a vendor who is in
117
+ # SFX. We use MARC leader byte 7 to tell if it's a journal. Confusing enough?
118
+ # Not yet! Even if it is a journal, if this isn't an article-level
119
+ # cite and there are no other full text already provided, we
120
+ # still include.
121
+ def should_skip_856_link?(request, marc_record, url)
122
+ is_journal = (marc_record.leader[7,1] == 's')
123
+
124
+ return ( is_journal &&
125
+ SfxUrl.sfx_controls_url?(url) &&
126
+ !( request.title_level_citation? &&
127
+ request.get_service_type("fulltext").length == 0
128
+ )
129
+ )
130
+ end
131
+
132
+ # Take a ruby Marc Field object representing an 856 field,
133
+ # decide what umlaut service type value to map it to. Fulltext, ToC, etc.
134
+ # This is neccesarily a heuristic guess, Marc doesn't have enough granularity
135
+ # to really let us know for sure.
136
+ def service_type_for_856(field, options)
137
+ options[:default_service_type] ||= "fulltext_title_level"
138
+
139
+ # LC records here at hopkins have "Table of contents only" in the 856$3
140
+ # Think that's a convention from LC?
141
+ if (field['3'] && field['3'].downcase =~ /table of contents( only)?/)
142
+ return "table_of_contents"
143
+ elsif (field['3'] && field['3'].downcase =~ /description/)
144
+ # If it contains the word 'description', it's probably an abstract.
145
+ # That's the best we can do, sadly.
146
+ return "abstract"
147
+ elsif (field['3'] && field['3'].downcase == 'sample text')
148
+ # LC records often include these links.
149
+ return "excerpts"
150
+ elsif ( field['u'] =~ /www\.loc\.gov/ )
151
+ # Any other loc.gov link, we know it's not full text, don't put
152
+ # it in full text field, put it as "see also".
153
+ return "highlighted_link"
154
+ else
155
+ return options[:default_service_type]
156
+ end
157
+ end
158
+
159
+ # A MARC record has two dates in it, date1 and date2. Exactly
160
+ # what they represent is something of an esoteric mystery.
161
+ # But this will return them both, in an array.
162
+ def get_years(marc)
163
+ array = []
164
+
165
+ # no marc 008? Weird, but okay.
166
+ return array unless marc['008']
167
+
168
+ date1 = marc['008'].value[7,4]
169
+ date1.strip! if date1
170
+ array.push(date1) unless date1.blank?
171
+
172
+ date2 = marc['008'].value[11,4]
173
+ date2.strip! if date2
174
+ array.push(date2) unless date2.blank?
175
+
176
+ return array
177
+ end
178
+
179
+ # Take the title out of a marc record
180
+ def get_title(marc)
181
+ marc['245'].find_all {|sf| sf.code == "a" || sf.code == "b" || sf.code == "k"}.collect {|sf| sf.text}.join(" ").sub(/\s*[;:\/.,]\s*$/)
182
+ end
183
+
184
+
185
+ # From a marc record, get a string useful to display for identifying
186
+ # which edition/version of a work this represents.
187
+ def edition_statement(marc, options = {})
188
+ options[:include_repro_info] ||= true
189
+ options[:exclude_533_fields] = ['7','f','b', 'e']
190
+
191
+ parts = Array.new
192
+
193
+ return "" unless marc
194
+
195
+ #245$h GMD
196
+ unless ( marc['245'].blank? || marc['245']['h'].blank? )
197
+ parts.push('(' + marc['245']['h'].gsub(/[^\w\s]/, '').strip.titlecase + ')')
198
+ end
199
+
200
+ #250
201
+ if ( marc['250'])
202
+ parts.push( marc['250']['a'] ) unless marc['250']['a'].blank?
203
+ parts.push( marc['250']['b'] ) unless marc['250']['b'].blank?
204
+ end
205
+
206
+ # 260
207
+ if ( marc['260'])
208
+ if (marc['260']['b'] =~ /s\.n\./)
209
+ parts.push(marc['260']['a']) unless marc['260']['a'].blank?
210
+ else
211
+ parts.push(marc['260']['b']) unless marc['260']['b'].blank?
212
+ end
213
+ parts.push( marc['260']['c'] ) unless marc['260']['c'].blank?
214
+ end
215
+
216
+ # 533
217
+ if options[:include_repro_info] && marc['533']
218
+ marc['533'].subfields.each do |s|
219
+ if ( s.code == 'a' )
220
+ parts.push('<em>' + s.value.gsub(/[^\w\s]/, '') + '</em>:' )
221
+ elsif (! options[:exclude_533_fields].include?( s.code ))
222
+ parts.push(s.value)
223
+ end
224
+ end
225
+ end
226
+
227
+ return nil if parts.length == 0
228
+
229
+ return parts.join(' ')
230
+ end
231
+
232
+ # AACR2 "General Material Designation" . While these are (I think?)
233
+ # controlled, it's actually really hard to find the list. Maybe they're
234
+ # only semi-controlled.
235
+ # ONE list can be found here: http://www.oclc.org/bibformats/en/onlinecataloging/default.shtm#BCGFECEG
236
+ def gmd_values
237
+ # 'computer file' is an old one that may still be found in data.
238
+ return ['activity card',
239
+ 'art original','art reproduction','braille','chart','diorama','electronic resource','computer file', 'filmstrip','flash card','game','globe','kit','manuscript','map','microform','microscope slides','model','motion picture','music','picture','realia','slide','sound recording','technical drawing','text','toy','transparency','videorecording']
240
+ end
241
+
242
+ # removes something that looks like an AACR2 GMD in square brackets from
243
+ # the string. Pretty kludgey.
244
+ def strip_gmd(arg_string, options = {})
245
+ options[:replacement] ||= ':'
246
+
247
+ gmd_values.each do |gmd_val|
248
+ arg_string = arg_string.sub(/\[#{gmd_val}( \((tactile|braile|large print)\))?\]/, options[:replacement])
249
+ end
250
+ return arg_string
251
+ end
252
+
253
+
254
+ end
@@ -0,0 +1,312 @@
1
+ # Helper class to get keyword searchable terms from OpenURL author and title
2
+ #
3
+ # OpenURLs have some commonly agreed upon metadata elements. This module is
4
+ # meant to help simplify things by sorting through the metadata and extracting
5
+ # what we need in a simpler interface. These values are specifically constructed
6
+ # from the citation to work well as keyword searches in other services.
7
+ #
8
+ # Also includes some helpful methods for getting identifiers out in a convenient to work with way, regardless of non-standard ways they may have been stored.
9
+
10
+ module MetadataHelper
11
+ include MarcHelper # for strip gmd functionality
12
+
13
+ # DEPRECATED, not flexible enough, you really need to custom fit
14
+ # for your given target.
15
+ # method that accepts a referent to return hash of common metadata elements
16
+ # choosing the available element for the format and the best available for
17
+ # searching. Wrapper around the other methods.
18
+ def get_search_terms(rft)
19
+ title = get_search_title(rft)
20
+ creator = get_search_creator(rft)
21
+
22
+ # returns a hash of values so that more keys can be added
23
+ # and not break services that use this module
24
+ return {:title => title, :creator => creator}
25
+ end
26
+
27
+
28
+ # A utility method to 'normalize' a title, for use when trying to match a
29
+ # title from one place with records in another database.
30
+ # Does lowercasing and removing puncutation, but also stripping out
31
+ # a bunch of other things that may result
32
+ # in false negatives. Exactly how you want to do for best results depends
33
+ # on the particular data you are working with, you need to experiment to see.
34
+ # MANY options are offered, although defaults are somewhat sensible.
35
+ # Much of this stuff especially takes account of titles that may have
36
+ # been generated from mark.
37
+ # Will never return the emtpy string, will sometimes return nil.
38
+ def normalize_title(arg_title, options = {})
39
+ # default options
40
+ options[:rstrip_parens] ||= true
41
+ options[:remove_all_parens] ||= true
42
+ options[:strip_gmd] ||= true
43
+ options[:subtitle_on_semicolon] ||=false
44
+ options[:remove_subtitle] ||= false
45
+ options[:normalize_ampersand] ||= true
46
+ options[:remove_punctuation] ||= true
47
+ # Even if you're removing other punctuation, keep the apostrophes?
48
+ options[:keep_apostrophes] ||=false
49
+
50
+ return nil if arg_title.nil?
51
+ title = arg_title.clone
52
+
53
+ return nil if title.blank?
54
+
55
+ # Sometimes titles given in the OpenURL have some additional stuff
56
+ # in parens at the end, that messes up the search and isn't really
57
+ # part of the title. Eliminate!
58
+ title.gsub!(/\([^)]*\)\s*$/, '') if options[:rstrip_parens]
59
+ # Or, not even just at the end, but anywhere!
60
+ title.gsub!(/\([^)]*\)/, '') if options[:remove_all_parens]
61
+
62
+ # Remove things in brackets, part of an AACR2 GMD that's made it in.
63
+ # replace with ':' so we can keep track of the fact that everything
64
+ # that came afterwards was a sub-title like thing.
65
+ title = strip_gmd(title) if options[:strip_gmd]
66
+
67
+ # There seems to be some catoging/metadata disagreement about when to
68
+ # use ';' for a subtitle instead of ':'. Normalize to ':'.
69
+ title.sub!(/[\;]/, ':') if options[:subtitle_on_semicolon]
70
+
71
+ title.sub!(/\:(.*)$/, '') if options[:remove_subtitle]
72
+
73
+ # Change ampersands to 'and' for consistency, we see it both ways.
74
+ title.gsub!(/\&/, ' and ') if options[:normalize_ampersand]
75
+
76
+ # remove non-alphanumeric, excluding apostrophe
77
+ title.gsub!(/[^\w\s\']/, ' ') if options[:remove_punctuation]
78
+
79
+ # apostrophe not to space, just eat it.
80
+ title.gsub!(/[\']/, '') if options[:remove_punctuation] && ! options[:keep_apostrophes]
81
+
82
+ # compress whitespace
83
+ title.strip!
84
+ title.gsub!(/\s+/, ' ')
85
+
86
+ title.downcase!
87
+
88
+ title = nil if title.blank?
89
+
90
+ return title
91
+ end
92
+
93
+ # pick title out of OpenURL referent from best element available,
94
+ # no normalization.
95
+ def raw_search_title(rft)
96
+ # Just make one call to create metadata hash
97
+ metadata = rft.metadata
98
+ title = nil
99
+ if rft.format == 'journal' && metadata['atitle']
100
+ title = metadata['atitle']
101
+ elsif rft.format == 'book'
102
+ title = metadata['btitle'] unless metadata['btitle'].blank?
103
+ title = metadata['title'] if title.blank?
104
+
105
+ # Well, if we don't know the format and we do have a title use that.
106
+ # This might happen if we only have an ISBN to start and then enhance.
107
+ # So should services like Amazon also enhance with a format, should
108
+ # we simplify this method to not worry about format so much, or do we
109
+ # keep this as is?
110
+ elsif metadata['btitle']
111
+ title = metadata['btitle']
112
+ elsif metadata['title']
113
+ title = metadata['title']
114
+ elsif metadata['jtitle']
115
+ title = metadata['jtitle']
116
+ end
117
+ return title
118
+ end
119
+
120
+ # chooses the best available title for the format, normalizes
121
+ def get_search_title(rft, options = {})
122
+ #defaults
123
+ options = {:remove_all_parens => true,
124
+ :subtitle_on_semicolon => true,
125
+ :remove_subtitle => true,
126
+ :remove_punctuation => true}.merge(options)
127
+
128
+ title = raw_search_title(rft)
129
+
130
+ return normalize_title(title, options)
131
+
132
+ end
133
+
134
+ # chooses the best available creator for the format
135
+ def get_search_creator(rft)
136
+ # Just make one call to create metadata hash
137
+ metadata = rft.metadata
138
+ # Identify dc.creator query. Prefer aulast alone if available.
139
+ creator = nil
140
+
141
+ creator = metadata['aulast'] unless metadata['aulast'].blank?
142
+ creator = metadata['au'] if creator.blank?
143
+ # FIXME if capital letters are next to each other should we insert a space?
144
+ # Should we assume capitals next to each other are initials?
145
+ # Maybe only if we use au?
146
+ # Logic like this makes refactoring to use Referent.to_citation less useful.
147
+
148
+ # FIXME strip out commas from creator if we use au?
149
+
150
+ return nil if creator.blank?
151
+
152
+ return creator
153
+ end
154
+
155
+ def get_top_level_creator(rft)
156
+ # If it's a non-journal thing, add the author if we have an aulast (preferred) or au.
157
+ # But wait--if it's a book _part_, don't include the author name, since
158
+ # it _might_ just be the author of the part, not of the book.
159
+ unless (rft.format == "journal" ||
160
+ ( rft.format == "book" && ! rft.metadata['atitle'].blank?))
161
+ return get_search_creator(rft)
162
+ end
163
+ return nil
164
+ end
165
+
166
+ # oclcnum, lccn, and isbn are both _supposed_ to be stored as identifiers
167
+ # with an info: uri. info:oclcnum/#, info:lccn/#. But SFX sometimes stores
168
+ # them in the referent metadata instead: rft.lccn, rft.oclcnum. .
169
+ #
170
+ # On the other hand, isbn and issn can legitimately be included in referent
171
+ # metadata or as a urn.
172
+ #
173
+ # This method will find you an identifier accross multiple places.
174
+ #
175
+ # type: :urn or :info
176
+ # subscheme: "lccn", "oclcnum", "isbn", "issn", or anything else that could be found in either a urn an info uri or a referent metadata.
177
+ # referent: an umlaut Referent object
178
+ #
179
+ # returns nil if no identifier found, otherwise the bare identifier (not formatted into a urn/uri right now. Option should be maybe be added?)
180
+ def get_identifier(type, sub_scheme, referent, options = {} )
181
+ options[:multiple] ||= false
182
+
183
+ raise Exception.new("type must be :urn or :info") unless type == :urn or type == :info
184
+
185
+ prefix = case type
186
+ when :info then "info:#{sub_scheme}/"
187
+ when :urn then "urn:#{sub_scheme}:"
188
+ end
189
+
190
+ bare_identifier = nil
191
+ identifiers = referent.identifiers.collect {|id| $1 if id =~ /^#{prefix}(.*)/}.compact
192
+
193
+ if ( identifiers.blank? && ['lccn', 'oclcnum', 'isbn', 'issn', 'doi', 'pmid'].include?(sub_scheme) )
194
+ # try the referent metadata
195
+ from_rft = referent.metadata[sub_scheme]
196
+ identifiers = [from_rft] unless from_rft.blank?
197
+ end
198
+
199
+ if ( options[:multiple])
200
+ return identifiers
201
+ elsif ( identifiers[0].blank? )
202
+ return nil
203
+ else
204
+ return identifiers[0]
205
+ end
206
+
207
+ end
208
+
209
+ # finds and normalizes an LCCN. If multiple LCCNs are in the record,
210
+ # returns the first one.
211
+ def get_lccn(rft)
212
+ lccn = get_identifier(:info, "lccn", rft)
213
+
214
+ lccn = normalize_lccn(lccn)
215
+
216
+ return lccn
217
+ end
218
+
219
+ # Gets an ISSN, makes sure it's a valid ISSN or else returns nil.
220
+ # So will return a valid ISSN (NOT empty string) or nil.
221
+ def get_issn(rft)
222
+ issn = rft.metadata['issn']
223
+ issn = nil unless issn =~ /\d{4}(-)?\d{3}(\d|X)/
224
+ return issn
225
+ end
226
+
227
+ # Some normalization. See:
228
+ # http://info-uri.info/registry/OAIHandler?verb=GetRecord&metadataPrefix=reg&identifier=info:lccn/
229
+ # doesn't validate right now, only normalizes.
230
+ # tbd, raise exception if invalid string.
231
+ def normalize_lccn(lccn)
232
+ if ( lccn )
233
+ # remove whitespace
234
+ lccn = lccn.gsub(/\s/, '')
235
+ # remove any forward slashes and anything after them
236
+ lccn = lccn.sub(/\/.*$/, '')
237
+ # pad anything after a hyphen before removing hyphen, if neccesary
238
+ lccn = lccn.sub(/-(.*)/) do |match_str|
239
+ if $1.length < 6
240
+ ("0" * (6 - $1.length)) + $1
241
+ else
242
+ $1
243
+ end
244
+ end
245
+ end
246
+ return lccn
247
+ end
248
+
249
+ # Gets isbn, also removes any weird stuff on the end sometimes
250
+ # included as 'isbn', but not part of the isbn. Like (paperback)
251
+ # and such.
252
+ def get_isbn(rft)
253
+ isbn = get_identifier(:urn, "isbn", rft)
254
+ isbn = isbn.gsub(/[^\dX-]/, '') if isbn
255
+ return nil if isbn.blank?
256
+ return isbn
257
+ end
258
+
259
+ def get_oclcnum(rft)
260
+ return get_identifier(:info, "oclcnum", rft)
261
+ end
262
+
263
+ def get_doi(rft)
264
+ return get_identifier(:info, "doi", rft)
265
+ end
266
+
267
+ def get_pmid(rft)
268
+ return get_identifier(:info, "pmid", rft)
269
+ end
270
+
271
+ # Returns an array, possibly empty.
272
+ def get_gpo_item_nums(rft)
273
+ # In a technically illegal but used by OCLC info:gpo uri
274
+ ids = get_identifier(:info, "gpo", rft, :multiple => true)
275
+ # Remove the uri part.
276
+ return ids.collect {|id| id.sub(/^info:gpo\//, '') }
277
+ end
278
+
279
+ def get_sudoc(rft)
280
+ # Don't forget to unescape the sudoc that was escaped to maek it a uri!
281
+
282
+ # Option 1: In a technically illegal but oh well info:sudoc uri
283
+
284
+ sudoc = get_identifier(:info, "sudoc", rft)
285
+ sudoc = CGI.unescape(sudoc) if sudoc
286
+
287
+ # Option 2: rsinger's purl for sudoc. http://dilettantes.code4lib.org/2009/03/a-uri-scheme-for-sudocs/
288
+ unless sudoc
289
+ sudoc = rft.identifiers.collect {|id| $1 if id =~ /^http:\/\/purl.org\/NET\/sudoc\/(.*)$/}.compact.slice(0)
290
+ sudoc = CGI.unescape(sudoc) if sudoc
291
+ end
292
+
293
+ return sudoc
294
+ end
295
+
296
+ def get_year(rft)
297
+ # Some link generators use an illegal 'year' parameter
298
+ if (date = (rft['date'] || rft['year']))
299
+ return date[0,4]
300
+ end
301
+ return nil
302
+ end
303
+
304
+ # Look at weird bad OpenURLs, use heuristics to see if the 'title' probably
305
+ # represents a journal rather than a book.
306
+ def title_is_serial?(rft)
307
+ (rft.format != "book" &&
308
+ ( ! rft.metadata['jtitle'].blank?) &&
309
+ rft.metadata['btitle'].blank?)
310
+ end
311
+
312
+ end