median 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +118 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +5 -0
- data/Rakefile +38 -0
- data/lib/median.rb +33 -0
- data/lib/median/aleph.rb +18 -0
- data/lib/median/aleph/authentication.rb +64 -0
- data/lib/median/aleph/cash.rb +35 -0
- data/lib/median/aleph/hold.rb +86 -0
- data/lib/median/aleph/item.rb +34 -0
- data/lib/median/aleph/loan.rb +59 -0
- data/lib/median/aleph/patron.rb +110 -0
- data/lib/median/aleph/search.rb +11 -0
- data/lib/median/aleph/utils.rb +82 -0
- data/lib/median/primo.rb +9 -0
- data/lib/median/primo/facet.rb +27 -0
- data/lib/median/primo/record.rb +115 -0
- data/lib/median/primo/search.rb +32 -0
- data/lib/median/primo/search_request.rb +218 -0
- data/lib/median/primo/search_result.rb +64 -0
- data/lib/median/version.rb +3 -0
- data/lib/median/xml_support.rb +91 -0
- data/lib/tasks/median_tasks.rake +4 -0
- data/median.gemspec +24 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/mailers/.gitkeep +0 -0
- data/test/dummy/app/models/.gitkeep +0 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +56 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +67 -0
- data/test/dummy/config/environments/test.rb +37 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +58 -0
- data/test/dummy/lib/assets/.gitkeep +0 -0
- data/test/dummy/log/.gitkeep +0 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/median_test.rb +7 -0
- data/test/test_helper.rb +15 -0
- 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
|
data/lib/median/primo.rb
ADDED
@@ -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
|