median 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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