median 0.0.1

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 (61) hide show
  1. data/.gitignore +7 -0
  2. data/Gemfile +19 -0
  3. data/Gemfile.lock +118 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.rdoc +5 -0
  6. data/Rakefile +38 -0
  7. data/lib/median.rb +33 -0
  8. data/lib/median/aleph.rb +18 -0
  9. data/lib/median/aleph/authentication.rb +64 -0
  10. data/lib/median/aleph/cash.rb +35 -0
  11. data/lib/median/aleph/hold.rb +86 -0
  12. data/lib/median/aleph/item.rb +34 -0
  13. data/lib/median/aleph/loan.rb +59 -0
  14. data/lib/median/aleph/patron.rb +110 -0
  15. data/lib/median/aleph/search.rb +11 -0
  16. data/lib/median/aleph/utils.rb +82 -0
  17. data/lib/median/primo.rb +9 -0
  18. data/lib/median/primo/facet.rb +27 -0
  19. data/lib/median/primo/record.rb +115 -0
  20. data/lib/median/primo/search.rb +32 -0
  21. data/lib/median/primo/search_request.rb +218 -0
  22. data/lib/median/primo/search_result.rb +64 -0
  23. data/lib/median/version.rb +3 -0
  24. data/lib/median/xml_support.rb +91 -0
  25. data/lib/tasks/median_tasks.rake +4 -0
  26. data/median.gemspec +24 -0
  27. data/test/dummy/README.rdoc +261 -0
  28. data/test/dummy/Rakefile +7 -0
  29. data/test/dummy/app/assets/javascripts/application.js +15 -0
  30. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  31. data/test/dummy/app/controllers/application_controller.rb +3 -0
  32. data/test/dummy/app/helpers/application_helper.rb +2 -0
  33. data/test/dummy/app/mailers/.gitkeep +0 -0
  34. data/test/dummy/app/models/.gitkeep +0 -0
  35. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  36. data/test/dummy/config.ru +4 -0
  37. data/test/dummy/config/application.rb +56 -0
  38. data/test/dummy/config/boot.rb +10 -0
  39. data/test/dummy/config/database.yml +25 -0
  40. data/test/dummy/config/environment.rb +5 -0
  41. data/test/dummy/config/environments/development.rb +37 -0
  42. data/test/dummy/config/environments/production.rb +67 -0
  43. data/test/dummy/config/environments/test.rb +37 -0
  44. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  45. data/test/dummy/config/initializers/inflections.rb +15 -0
  46. data/test/dummy/config/initializers/mime_types.rb +5 -0
  47. data/test/dummy/config/initializers/secret_token.rb +7 -0
  48. data/test/dummy/config/initializers/session_store.rb +8 -0
  49. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  50. data/test/dummy/config/locales/en.yml +5 -0
  51. data/test/dummy/config/routes.rb +58 -0
  52. data/test/dummy/lib/assets/.gitkeep +0 -0
  53. data/test/dummy/log/.gitkeep +0 -0
  54. data/test/dummy/public/404.html +26 -0
  55. data/test/dummy/public/422.html +26 -0
  56. data/test/dummy/public/500.html +25 -0
  57. data/test/dummy/public/favicon.ico +0 -0
  58. data/test/dummy/script/rails +6 -0
  59. data/test/median_test.rb +7 -0
  60. data/test/test_helper.rb +15 -0
  61. metadata +192 -0
@@ -0,0 +1,34 @@
1
+ class Median::Aleph::Item
2
+ include Median::XmlSupport
3
+
4
+ field :sub_library_code, :xpath => './z30-sub-library-code'
5
+ field :sub_library_string, :xpath => './z30/z30-sub-library'
6
+ field :collection, :xpath => './z30/z30-collection'
7
+ field :signature, :xpath => './z30/z30-call-no'
8
+ field :barcode, :xpath => './z30/z30-barcode'
9
+ field :status_code, :xpath => './z30-item-status-code'
10
+ field :status_string, :xpath => './z30/z30-item-status'
11
+ field :material, :xpath => './z30/z30-material'
12
+ field :loan_date, :xpath => './status', :process => :format_date
13
+ field :no_of_holds, :xpath => './queue', :process => :get_no_of_holds
14
+
15
+ private
16
+
17
+ def format_date(value)
18
+ begin
19
+ Date.strptime(value, '%d/%m/%y')
20
+ rescue
21
+ nil
22
+ end
23
+ end
24
+
25
+ def get_no_of_holds(value)
26
+ begin
27
+ matches = value.match(/([0-9]+).+([0-9]+)/)
28
+ matches[1].to_i
29
+ rescue
30
+ 0
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,59 @@
1
+ module Median::Aleph
2
+ module Loan
3
+
4
+ def get_loans(patron_id, options = {})
5
+ options = options.reverse_merge(history: false)
6
+ loans = []
7
+
8
+ # load loans
9
+ url = "#{Median.config.aleph_rest_service_base_url}/patron/#{patron_id}/circulationActions/loans"
10
+ xml = options[:history] ?
11
+ get_url(url, view: 'brief', type: 'history', no_loans: 250) :
12
+ get_url(url, view: 'brief')
13
+
14
+ # parse result
15
+ xml.xpath('//loan').each do |loan_xml|
16
+ loan = OpenStruct.new
17
+ loan.id = loan_xml.attribute('href').try(:content).try(:split, '/').try(:last)
18
+ loan.can_renew = (loan_xml.attribute('renew').try(:content).try(:upcase) == 'Y') ? true : false
19
+ loan.author = loan_xml.at_xpath('z13-author').try(:content)
20
+ loan.title = loan_xml.at_xpath('z13-title').try(:content)
21
+ loan.year = loan_xml.at_xpath('z13-year').try(:content)
22
+ return_date = loan_xml.at_xpath('z36h-returned-date').try(:content)
23
+ due_date = loan_xml.at_xpath('z36-due-date').try(:content)
24
+ loan.returned = return_date.present? ? Date.strptime(return_date, '%Y%m%d') : nil
25
+ loan.due = due_date.present? ? Date.strptime(due_date, '%Y%m%d') : nil
26
+ loans << loan
27
+ end
28
+
29
+ # sort by due date ascending
30
+ loans.sort{|x,y| x.due <=> y.due }
31
+
32
+ # return
33
+ loans
34
+ end
35
+
36
+ def renew_loan(patron_id, loan_id)
37
+ xml = post_url("#{Median.config.aleph_rest_service_base_url}/patron/#{patron_id}/circulationActions/loans/#{loan_id}")
38
+
39
+ result_code = xml.at_xpath('//reply-code').try(:content).to_i
40
+ if result_code == 0 # no error
41
+ return true
42
+ end
43
+
44
+ return false
45
+ end
46
+
47
+ def renew_loans(patron_id)
48
+ xml = post_url("#{Median.config.aleph_rest_service_base_url}/patron/#{patron_id}/circulationActions/loans")
49
+
50
+ result_code = xml.at_xpath('//reply-code').try(:content).to_i
51
+ if result_code == 0 # no error
52
+ return true
53
+ end
54
+
55
+ return false
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,110 @@
1
+ module Median::Aleph
2
+ module Patron
3
+
4
+ def change_patron_email(patron_id, email)
5
+ data01 = change_patron_email!(patron_id, email, "01")
6
+ data02 = change_patron_email!(patron_id, email, "02")
7
+
8
+ if bor_update_successfull?(data02)
9
+ return true
10
+ else
11
+ return false
12
+ end
13
+ end
14
+
15
+
16
+ def change_patron_password(patron_id, current_password, new_password)
17
+ # first (re)authenticate
18
+ if AlephApi.authenticate(patron_id, current_password)
19
+ # after successfull authentication, write the new password
20
+ data = post_url(Median.config.aleph_x_service_base_url,
21
+ op: 'update_bor',
22
+ update_flag: 'Y',
23
+ library: Median.config.aleph_user_library,
24
+ xml_full_req: <<-XML
25
+ <?xml version="1.0"?>
26
+ <p-file-20>
27
+ <patron-record>
28
+ <z303>
29
+ <match-id-type>01</match-id-type>
30
+ <match-id>#{patron_id}</match-id>
31
+ <record-action>X</record-action>
32
+ </z303>
33
+ <z308>
34
+ <record-action>U</record-action>
35
+ <z308-key-type>01</z308-key-type>
36
+ <z308-key-data>#{patron_id}</z308-key-data>
37
+ <z308-verification>#{new_password}</z308-verification>
38
+ </z308>
39
+ </patron-record>
40
+ </p-file-20>
41
+ XML
42
+ )
43
+
44
+ if bor_update_successfull?(data)
45
+ return true
46
+ else
47
+ return false
48
+ end
49
+ else
50
+ return false
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def change_patron_email!(patron_id, email, type)
57
+ data = post_url(Median.config.aleph_x_service_base_url,
58
+ op: 'update_bor',
59
+ update_flag: 'Y',
60
+ library: Median.config.aleph_user_library,
61
+ xml_full_req: <<-XML
62
+ <?xml version="1.0"?>
63
+ <p-file-20>
64
+ <patron-record>
65
+ <z303>
66
+ <match-id-type>01</match-id-type>
67
+ <match-id>#{patron_id}</match-id>
68
+ <record-action>X</record-action>
69
+ </z303>
70
+ <z304>
71
+ <record-action>U</record-action>
72
+ <z304-id>#{patron_id}</z304-id>
73
+ <z304-address-type>#{type}</z304-address-type>
74
+ <z304-sequence>01</z304-sequence>
75
+ <z304-email-address>#{email}</z304-email-address>
76
+ </z304>
77
+ </patron-record>
78
+ </p-file-20>
79
+ XML
80
+ )
81
+
82
+ return data
83
+ end
84
+
85
+ # Checks if the call was successfull. We face a problem here. X-Service Api
86
+ # was created by morons, so we need a dirty hack to distinguish between
87
+ # a successfull result and an error, because the message is always given
88
+ # inside an error-tag. E.g.
89
+ #
90
+ # An error:
91
+ # <update-bor>
92
+ # <patron-id>PB29096</patron-id>
93
+ # <error>Cannot update / delete z304. No match found for ID: PB29096.</error>
94
+ # ...
95
+ # </update-bor>
96
+ #
97
+ # A success:
98
+ # <update-bor>
99
+ # <patron-id>PA06003114</patron-id>
100
+ # <error>Succeeded to REWRITE table z308. cur-id PA06003114.</error>
101
+ # ...
102
+ # </update-bor>
103
+ #
104
+ def bor_update_successfull?(data)
105
+ return (data.at_xpath('/update-bor/patron-id') and
106
+ data.at_xpath('/update-bor/error').try(:content).try(:match, /Succ/i))
107
+ end
108
+
109
+ end
110
+ end
@@ -0,0 +1,11 @@
1
+ module Median::Aleph
2
+ module Search
3
+
4
+ def find_items(record_id, patron_id = nil)
5
+ url = "#{Median.config.aleph_rest_service_base_url}/record/#{record_id}/items"
6
+ xml = get_url(url, view: 'full')
7
+ xml.xpath('//items/item').collect { |xml| Item.new(xml) }
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,82 @@
1
+ module Median::Aleph
2
+ module Utils
3
+
4
+ def get_url(url, data = {})
5
+ begin
6
+ uri = get_uri(url)
7
+ uri.query = URI.encode_www_form(data)
8
+ http = get_http_connection(uri)
9
+
10
+ request = Net::HTTP::Get.new(uri.request_uri)
11
+ request.set_form_data(data)
12
+
13
+ response = http.request(request)
14
+ Nokogiri::XML(response.body)
15
+ rescue Exception => e
16
+ raise Median::ConnectionError.new(e)
17
+ end
18
+ end
19
+
20
+ def post_url(url, data = {})
21
+ begin
22
+ uri = get_uri(url)
23
+ http = get_http_connection(uri)
24
+
25
+ request = Net::HTTP::Post.new(uri.request_uri)
26
+ request.set_form_data(data)
27
+
28
+ response = http.request(request)
29
+ Nokogiri::XML(response.body)
30
+ rescue Exception => e
31
+ raise Median::ConnectionError.new(e)
32
+ end
33
+ end
34
+
35
+ def delete_url(url, data = {})
36
+ begin
37
+ uri = get_uri(url)
38
+ uri.query = URI.encode_www_form(data)
39
+ http = get_http_connection(uri)
40
+
41
+ request = Net::HTTP::Delete.new(uri.request_uri)
42
+ request.set_form_data(data)
43
+
44
+ response = http.request(request)
45
+ Nokogiri::XML(response.body)
46
+ rescue Exception => e
47
+ raise Median::ConnectionError.new(e)
48
+ end
49
+ end
50
+
51
+ def put_url(url, data = {})
52
+ begin
53
+ uri = get_uri(url)
54
+ uri.query = URI.encode_www_form(data)
55
+ http = get_http_connection(uri)
56
+
57
+ request = Net::HTTP::Put.new(uri.request_uri)
58
+ request.set_form_data(data)
59
+
60
+ response = http.request(request)
61
+ Nokogiri::XML(response.body)
62
+ rescue Exception => e
63
+ raise Median::ConnectionError.new(e)
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def get_uri(url, params = {})
70
+ url = "#{url}?#{params.collect{"#{k.to_s}=#{v.to_s}"}.join('&')}" if params
71
+ URI.parse(URI.escape(url))
72
+ end
73
+
74
+ def get_http_connection(uri)
75
+ http = Net::HTTP.new(uri.host, uri.port)
76
+ http.use_ssl = (uri.scheme == 'https')
77
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
78
+ return http
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,9 @@
1
+ require 'median/primo/search'
2
+ require 'median/primo/search_request'
3
+ require 'median/primo/search_result'
4
+ require 'median/primo/record'
5
+ require 'median/primo/facet'
6
+
7
+ module Median::Primo
8
+ extend Search
9
+ end
@@ -0,0 +1,27 @@
1
+ class Median::Primo::Facet
2
+ include Median::XmlSupport
3
+
4
+ field :name, :xpath => '.', :attribute => 'NAME'
5
+
6
+ def initialize(xml)
7
+ super(xml)
8
+ @values = xml.xpath(".//FACET_VALUES").collect{ |xml| FacetValue.new(xml) }
9
+
10
+ if name == 'creationdate'
11
+ @values.sort!{ |x, y| y.key <=> x.key }
12
+ else
13
+ @values.sort!{ |x, y| y.count <=> x.count }
14
+ end
15
+ end
16
+
17
+ attr_reader :values
18
+
19
+ private
20
+
21
+ class FacetValue
22
+ include Median::XmlSupport
23
+
24
+ field :key, :xpath => '.', :attribute => 'KEY'
25
+ field :count, :xpath => '.', :attribute => 'VALUE', :process => proc{ |v| v.to_i }
26
+ end
27
+ end
@@ -0,0 +1,115 @@
1
+ class Median::Primo::Record
2
+ include Median::XmlSupport
3
+
4
+ field :rank, xpath: '.', :attribute => 'RANK', process: proc{|v| v.to_f}
5
+ field :id, xpath: './/record/search/recordid'
6
+ field :ils_id, xpath: './/record/control/ilsapiid'
7
+ field :source_ids, xpath: './/record/control/sourceid', multiple: true
8
+ field :type, xpath: './/record/display/type'
9
+ field :title, xpath: './/record/display/title'
10
+ field :authors, xpath: './/record/display/creator', multiple: true, autosplit: ';'
11
+ field :contributors, xpath: './/record/display/contributor', multiple: true, autosplit: ';'
12
+ field :publisher, xpath: './/record/display/publisher'
13
+ field :ispartof, xpath: './/record/display/ispartof'
14
+ field :creationdate, xpath: './/record/display/creationdate'
15
+ field :edition, xpath: './/record/display/edition'
16
+ field :versions, xpath: './/record/display/version', process: proc{|v| v.to_i}, default: 1
17
+ field :subjects, xpath: './/record/search/subject', multiple: true
18
+ field :item_format, xpath: './/record/display/format'
19
+ field :languages, xpath: './/record/display/language', multiple: true, autosplit: ';'
20
+ field :identifier, xpath: './/record/display/identifier'
21
+ field :relations, xpath: './/record/display/relation', multiple: true
22
+ field :notations, xpath: './/record/search/lsr15', multiple: true, autosplit: ';'
23
+ field :superorder, xpath: './/record/search/lsr01'
24
+ field :suborder, xpath: './/record/search/lsr02'
25
+ field :frbrgroupid, xpath: './/record/facets/frbrgroupid'
26
+ field :description, xpath: './/record/display/description'
27
+ field :delivery_category, xpath: './/record/delivery/delcategory', process: proc{|v| v.downcase}
28
+ field :fulltext_available, xpath: './/record/delivery/fulltext', process: proc{|v| v.downcase != 'no_fulltext'}, default: true
29
+
30
+ attr_reader :links
31
+
32
+ def initialize(xml)
33
+ super(xml)
34
+ @links = Links.new(xml)
35
+ end
36
+
37
+ def dedub?
38
+ self.id.present? ? self.id.downcase.start_with?('dedupmrg') : false
39
+ end
40
+
41
+ def local_resource?
42
+ !online_resource?
43
+ end
44
+
45
+ def online_resource?
46
+ online_resource_categories = ['online_resource', 'online resource', 'remote search resource', 'sfx resource']
47
+ online_resource_categories.include?(self.delivery_category)
48
+ end
49
+
50
+ def versions?
51
+ versions > 1
52
+ end
53
+
54
+ def series?
55
+ type.try(:downcase) == 'serie' or delivery_category == 'structural metadata'
56
+ end
57
+
58
+ private
59
+
60
+ class Links
61
+ include Median::XmlSupport
62
+
63
+ field :openurl, xpath: './LINKS/openurl', process: :force_sfx_menu_language
64
+ field :backlink, xpath: './LINKS/backlink'
65
+ field :thumbnail, xpath: './LINKS/thumbnail'
66
+ field :resource_links, xpath: './LINKS/linktorsrc', multiple: true
67
+
68
+ def filtered_resource_links
69
+ filter_ezb_links(self.resource_links)
70
+ end
71
+
72
+ def default_resource_link
73
+ filtered_resource_links.first
74
+ end
75
+
76
+ def resource_link?
77
+ self.resource_links.present?
78
+ end
79
+
80
+
81
+ private
82
+
83
+ def filter_ezb_links(links)
84
+ ezb_link = get_ezb_link(links)
85
+ ezb_link.present? ? [ezb_link] : links
86
+ end
87
+
88
+ def get_ezb_link(links)
89
+ links.each do |link|
90
+ return link if link.include? 'uni-regensburg.de/ezeit'
91
+ end
92
+ nil
93
+ end
94
+
95
+ def force_sfx_menu_language(openurl)
96
+ openurl.gsub(/req\.language=.+&/, '') # remove the language setting to force thedefault
97
+ end
98
+ end
99
+
100
+ #
101
+ # GetIt links are broken in the SOAP API. The links always point to the first result.
102
+ #
103
+ #class GetItLinks < Array
104
+ # def initialize(xml)
105
+ # super()
106
+ # nodes = xml.xpath('./GETIT')
107
+ # nodes.each do |node|
108
+ # #puts node.inspect
109
+ # #self << {getit1: node.attribute('GetIt1').try(:value), getit2: node.attribute('GetIt2').try(:value)}
110
+ # #puts self
111
+ # end
112
+ # end
113
+ #end
114
+
115
+ end