exlibris-primo 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +3 -0
  3. data/Rakefile +38 -0
  4. data/lib/exlibris-primo.rb +11 -0
  5. data/lib/exlibris/primo/holding.rb +185 -0
  6. data/lib/exlibris/primo/related_link.rb +19 -0
  7. data/lib/exlibris/primo/rsrc.rb +19 -0
  8. data/lib/exlibris/primo/searcher.rb +292 -0
  9. data/lib/exlibris/primo/source/aleph.rb +50 -0
  10. data/lib/exlibris/primo/toc.rb +19 -0
  11. data/lib/exlibris/primo/version.rb +5 -0
  12. data/lib/exlibris/primo/web_service.rb +145 -0
  13. data/lib/tasks/exlibris-primo_tasks.rake +4 -0
  14. data/test/dummy/README.rdoc +261 -0
  15. data/test/dummy/Rakefile +7 -0
  16. data/test/dummy/app/assets/javascripts/application.js +15 -0
  17. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  18. data/test/dummy/app/controllers/application_controller.rb +3 -0
  19. data/test/dummy/app/helpers/application_helper.rb +2 -0
  20. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  21. data/test/dummy/config.ru +4 -0
  22. data/test/dummy/config/application.rb +56 -0
  23. data/test/dummy/config/boot.rb +10 -0
  24. data/test/dummy/config/database.yml +25 -0
  25. data/test/dummy/config/environment.rb +5 -0
  26. data/test/dummy/config/environments/development.rb +37 -0
  27. data/test/dummy/config/environments/production.rb +67 -0
  28. data/test/dummy/config/environments/test.rb +37 -0
  29. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  30. data/test/dummy/config/initializers/inflections.rb +15 -0
  31. data/test/dummy/config/initializers/mime_types.rb +5 -0
  32. data/test/dummy/config/initializers/secret_token.rb +7 -0
  33. data/test/dummy/config/initializers/session_store.rb +8 -0
  34. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  35. data/test/dummy/config/locales/en.yml +5 -0
  36. data/test/dummy/config/routes.rb +58 -0
  37. data/test/dummy/db/test.sqlite3 +0 -0
  38. data/test/dummy/log/test.log +410 -0
  39. data/test/dummy/public/404.html +26 -0
  40. data/test/dummy/public/422.html +26 -0
  41. data/test/dummy/public/500.html +25 -0
  42. data/test/dummy/public/favicon.ico +0 -0
  43. data/test/dummy/script/rails +6 -0
  44. data/test/exlibris-primo_test.rb +7 -0
  45. data/test/test_helper.rb +10 -0
  46. data/test/unit/searcher_benchmarks.rb +82 -0
  47. data/test/unit/searcher_test.rb +383 -0
  48. data/test/unit/web_service_benchmarks.rb +60 -0
  49. data/test/unit/web_service_test.rb +124 -0
  50. metadata +174 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ = Exlibris::Primo
2
+
3
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'Exlibris::Primo'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+
25
+
26
+ Bundler::GemHelper.install_tasks
27
+
28
+ require 'rake/testtask'
29
+
30
+ Rake::TestTask.new(:test) do |t|
31
+ t.libs << 'lib'
32
+ t.libs << 'test'
33
+ t.pattern = 'test/**/*_test.rb'
34
+ t.verbose = false
35
+ end
36
+
37
+
38
+ task :default => :test
@@ -0,0 +1,11 @@
1
+ PATH = File.dirname(__FILE__) + "/exlibris/primo/"
2
+ [
3
+ 'web_service',
4
+ 'holding',
5
+ 'related_link',
6
+ 'rsrc',
7
+ 'toc',
8
+ 'searcher'
9
+ ].each do |library|
10
+ require PATH + library
11
+ end
@@ -0,0 +1,185 @@
1
+ # == Overview
2
+ # Exlibris::Primo::Holding represents a Primo holding.
3
+ # This class should be extended to create Primo source objects for
4
+ # expanding holdings information, linking to Primo sources, and storing
5
+ # additional metadata based on those sources.
6
+ #
7
+ # == Tips on Extending
8
+ # When extending the class, a few basics guidelines should be observed.
9
+ # 1. A Exlibris::Primo::Holding is initialized from random Hash of parameters.
10
+ # Instance variables are created from these parameters for use in the class.
11
+ #
12
+ # 2. A Exlibris::Primo::Holding can be initialized from an input
13
+ # Exlibris::Primo::Holding by specifying the reserved
14
+ # parameter name :holding, i.e. :holding => input_holding.
15
+ # If the input holding has instance variables that are also specified in
16
+ # the random Hash, the value in the Hash takes precedence.
17
+ #
18
+ # 3. The following methods are available for overriding:
19
+ # expand - expand holdings information based on data source. default: [self]
20
+ # dedup? - does this data source contain duplicate holdings that need to be deduped? default: false
21
+ #
22
+ # 4. The following instance variables will be saved in the view_data and will be available
23
+ # to a local holding partial:
24
+ # @record_id, @source_id, @original_source_id, @source_record_id,
25
+ # @availlibrary, @institution_code, @institution, @library_code, @library,
26
+ # @status_code, @status, @id_one, @id_two, @origin, @display_type, @coverage, @notes,
27
+ # @url, @request_url, @source_data
28
+ #
29
+ # 5. Additional source data should be saved in the @source_data instance variable.
30
+ # @source_data is a hash that can contain any number of string elements,
31
+ # perfect for storing local source information.
32
+ #
33
+ # == Examples
34
+ # Example of Primo source implementations are:
35
+ # * Exlibris::Primo::Source::Aleph
36
+ module Exlibris
37
+ module Primo
38
+ class Holding
39
+ @base_attributes = [ :record_id, :source_id, :original_source_id, :source_record_id,
40
+ :availlibrary, :institution_code, :institution, :library_code, :library,
41
+ :status_code, :status, :id_one, :id_two, :origin, :display_type, :coverage, :notes,
42
+ :url, :request_url, :source_data ]
43
+ # Make sure attribute you're aliasing in in base_attributes
44
+ @attribute_aliases = { :collection => :id_one, :call_number => :id_two }
45
+ @required_parameters = [ :base_url, :record_id, :source_id,
46
+ :original_source_id, :source_record_id, :availlibrary,
47
+ :institution_code, :library_code, :id_one, :id_two, :status_code ]
48
+ @parameter_default_values = { :vid => "DEFAULT", :config => {},
49
+ :max_holdings => 10, :coverage => [], :source_data => {} }
50
+ @decode_variables = {
51
+ :institution => {},
52
+ :library => { :address => "libraries" },
53
+ :status => { :address => "statuses" }
54
+ }
55
+ class << self; attr_reader :base_attributes, :attribute_aliases, :required_parameters, :parameter_default_values, :decode_variables end
56
+
57
+ def initialize(parameters={})
58
+ # Set attr_readers
59
+ base_attributes = (self.class.base_attributes.nil?) ?
60
+ Exlibris::Primo::Holding.base_attributes : self.class.base_attributes
61
+ base_attributes.each { |attribute|
62
+ self.class.send(:attr_reader, attribute)
63
+ }
64
+ # Defensive copy the holding parameter.
65
+ holding = parameters[:holding].clone unless parameters[:holding].nil?
66
+ raise "Initialization error in #{self.class}. Unexpected holding parameter: #{holding.class}." unless holding.kind_of? Holding or holding.nil?
67
+ # Copy the defensive copy of holding to self.
68
+ holding.instance_variables.each { |name|
69
+ instance_variable_set((name).to_sym, holding.instance_variable_get(name))
70
+ } if holding.kind_of? Holding
71
+ # Add required instance variables, raising an exception if they're missing
72
+ # Params passed in overwrite instance variables copied from the holding
73
+ required_parameters = (self.class.required_parameters.nil?) ?
74
+ Exlibris::Primo::Holding.required_parameters : self.class.required_parameters
75
+ required_parameters.each do |param|
76
+ instance_variable_set(
77
+ "@#{param}".to_sym,
78
+ parameters.delete(param) {
79
+ instance_variable_get("@#{param}") if instance_variable_defined?("@#{param}") }
80
+ )
81
+ raise_required_parameter_error param unless instance_variable_defined?("@#{param}")
82
+ end
83
+ # Set additional instance variables from passed parameters
84
+ # Params passed in overwrite instance variables copied from the holding
85
+ parameters.each { |param, value|
86
+ instance_variable_set("@#{param}".to_sym, value)
87
+ }
88
+ # If appropriate, add defaults to non-required elements
89
+ parameter_default_values = (self.class.parameter_default_values.nil?) ?
90
+ Exlibris::Primo::Holding.parameter_default_values : self.class.parameter_default_values
91
+ parameter_default_values.each { |param, default|
92
+ instance_variable_set("@#{param}".to_sym, default) unless instance_variable_defined?("@#{param}")
93
+ }
94
+ # Set decoded fields
95
+ decode_variables = (self.class.decode_variables.nil?) ?
96
+ Exlibris::Primo::Holding.decode_variables : self.class.decode_variables
97
+ decode_variables.each { |var, decode_params|
98
+ decode var, decode_params, true
99
+ }
100
+ # Deep link URL to record
101
+ @url = primo_url if @url.nil?
102
+ # Set source parameters
103
+ @source_config = @config["sources"][source_id] unless @config["sources"].nil?
104
+ @source_class = @source_config["class_name"] unless @source_config.nil?
105
+ @source_url = @source_config["base_url"] unless @source_config.nil?
106
+ @source_type = @source_config["type"] unless @source_config.nil?
107
+ @source_data = {
108
+ :source_class => @source_class,
109
+ :source_url => @source_url,
110
+ :source_type => @source_type
111
+ }
112
+ # Set aliases for convenience
113
+ attribute_aliases = (self.class.attribute_aliases.nil?) ?
114
+ Exlibris::Primo::Holding.attribute_aliases : self.class.attribute_aliases
115
+ attribute_aliases.each { |alias_name, method_name|
116
+ begin
117
+ self.class.send(:alias_method, alias_name.to_sym, method_name.to_sym)
118
+ rescue NameError => ne
119
+ raise NameError, "Error in #{self}. Make sure method, #{method_name}, is defined. You may need to add it to #{self} @base_attributes.\nRoot exception: #{ne.message}"
120
+ end
121
+ }
122
+ end
123
+
124
+ # Returns an array of self.
125
+ # Should be overridden by source subclasses to map multiple holdings
126
+ # to one availlibrary.
127
+ def expand
128
+ return [self]
129
+ end
130
+
131
+ # Determine if we're de-duplicating.
132
+ # Should be overridden by source subclasses if appropriate.
133
+ def dedup?
134
+ return false
135
+ end
136
+
137
+ # Return this holding as a new holdings subclass instance based on source
138
+ def to_source
139
+ return self if @source_class.nil?
140
+ begin
141
+ # Get source class in Primo::Source module
142
+ return Exlibris::Primo::Source.const_get(@source_class).new(:holding => self)
143
+ rescue Exception => e
144
+ Rails.logger.error("#{e.message}")
145
+ Rails.logger.error("Class #{@source_class} can't be found in Exlibris::Primo::Source.
146
+ Please check primo.yml to ensure the class_name is defined correctly.
147
+ Not converting to source.")
148
+ return self
149
+ end
150
+ end
151
+
152
+ def [](key)
153
+ raise "Error in #{self.class}. #{key} doesn't exist or is restricted." unless self.class.base_attributes.include?(key)
154
+ method(key).call
155
+ end
156
+
157
+ protected
158
+ def decode(var, decode_params={}, refresh=false)
159
+ return instance_variable_get("@#{var}") unless (not instance_variable_defined?("@#{var}")) or refresh
160
+ code_sym = (decode_params[:code].nil?) ? "#{var}_code".to_sym : decode_params[:code]
161
+ code = instance_variable_get("@#{code_sym}")
162
+ config_sym = (decode_params[:config].nil?) ? :config : decode_params[:config]
163
+ config = instance_variable_get("@#{config_sym}")
164
+ address = (decode_params[:address].nil?) ? "#{var}s" : decode_params[:address]
165
+ instance_variable_set("@#{var}",
166
+ (config[address].nil? or config[address][code].nil?) ?
167
+ code : config[address][code]) unless code.nil?
168
+ end
169
+
170
+ # Returns Primo deep link URL to record
171
+ def primo_url
172
+ "#{@base_url}/primo_library/libweb/action/dlDisplay.do?docId=#{@record_id}&institution=#{@institution_code}&vid=#{@vid}"
173
+ end
174
+
175
+ private
176
+ # def self.add_attr_reader(reader)
177
+ # attr_reader reader.to_sym
178
+ # end
179
+ #
180
+ def raise_required_parameter_error(parameter)
181
+ raise "Initialization error in #{self.class}. Missing required parameter: #{parameter}."
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,19 @@
1
+ module Exlibris
2
+ module Primo
3
+ # Class for handling Primo related links from links/addlink
4
+ class RelatedLink
5
+ @base_attributes = [ :record_id, :addlink, :url, :display, :notes ]
6
+ class << self; attr_reader :base_attributes end
7
+ def initialize(options={})
8
+ base_attributes = (self.class.base_attributes.nil?) ?
9
+ Exlibris::Primo::RelatedLink.base_attributes : self.class.base_attributes
10
+ base_attributes.each { |attribute|
11
+ self.class.send(:attr_reader, attribute)
12
+ }
13
+ options.each { |option, value|
14
+ self.instance_variable_set(('@'+option.to_s).to_sym, value)
15
+ }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module Exlibris
2
+ module Primo
3
+ # Class for handling Primo Rsrcs from links/linktorsrc
4
+ class Rsrc
5
+ @base_attributes = [ :record_id, :linktorsrc, :v, :url, :display, :institution_code, :origin, :notes ]
6
+ class << self; attr_reader :base_attributes end
7
+ def initialize(options={})
8
+ base_attributes = (self.class.base_attributes.nil?) ?
9
+ Exlibris::Primo::Rsrc.base_attributes : self.class.base_attributes
10
+ base_attributes.each { |attribute|
11
+ self.class.send(:attr_reader, attribute)
12
+ }
13
+ options.each { |option, value|
14
+ self.instance_variable_set(('@'+option.to_s).to_sym, value)
15
+ }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,292 @@
1
+ # == Overview
2
+ # Searcher searches Primo for records.
3
+ # Searcher must have sufficient metadata to make
4
+ # the request. Sufficient means either:
5
+ # * We have a Primo doc id
6
+ # * We have either an isbn OR an issn
7
+ # * We have a title AND an author AND a genre
8
+ # If none of these criteria are met, Searcher.search
9
+ # will raise a RuntimeException.
10
+
11
+ module Exlibris
12
+ module Primo
13
+ class Searcher
14
+ #@required_setup = [ :base_url ]
15
+ #@setup_default_values = { :vid => "DEFAULT", :config => {} }
16
+
17
+ attr_reader :response, :count
18
+ attr_reader :cover_image, :titles, :author
19
+ attr_reader :holdings, :rsrcs, :tocs, :related_links
20
+ PNX_NS = {'pnx' => 'http://www.exlibrisgroup.com/xsd/primo/primo_nm_bib'}
21
+ SEARCH_NS = {'search' => 'http://www.exlibrisgroup.com/xsd/jaguar/search'}
22
+
23
+ # Instantiates the object and performs the search for based on the input search criteria.
24
+ # setup parameter requires { :base_url => http://primo.server.institution.edu }
25
+ # Other optional parameters are :vid => "view_id", :config => { Hash of primo config settings}
26
+ # search_params are a sufficient combination of
27
+ # { :primo_id => "primo_1", :isbn => "ISBN", :issn => "ISSN",
28
+ # :title => "=Title", :author => "Author", :genre => "Genre" }
29
+ def initialize(setup, search_params)
30
+ @holdings = []
31
+ @rsrcs = []
32
+ @tocs = []
33
+ @related_links = []
34
+ @holding_attributes = Exlibris::Primo::Holding.base_attributes
35
+ @base_url = setup[:base_url]
36
+ raise_required_setup_parameter_error :base_url if @base_url.nil?
37
+ @institution = setup[:institution]
38
+ raise_required_setup_parameter_error :institution if @institution.nil?
39
+ @vid = setup.fetch(:vid, "DEFAULT")
40
+ raise_required_setup_parameter_error :vid if @vid.nil?
41
+ @config = setup.fetch(:config, {})
42
+ raise_required_setup_parameter_error :config if @config.nil?
43
+ search_params.each { |param, value| self.instance_variable_set("@#{param}".to_sym, value) }
44
+ # Perform the search
45
+ search
46
+ end
47
+
48
+ private
49
+ def self.add_attr_reader(reader)
50
+ attr_reader reader.to_sym
51
+ end
52
+
53
+ # Execute search based on instance vars
54
+ # Process Holdings based on display/availlibrary
55
+ # Process URLs based on links/linktorsrc
56
+ # Process TOCs based on links/linktotoc
57
+ def search
58
+ Rails.logger.warn("Insufficient search terms for #{self.class}. "+
59
+ "Please refer to #{self.class}'s documentation to determine how to structure "+
60
+ "a sufficient query.") and return if insufficient_query?
61
+ # Call Primo Web Services
62
+ unless @primo_id.nil? or @primo_id.empty?
63
+ get_record = Exlibris::Primo::WebService::GetRecord.new(@primo_id, @base_url, {:institution => @institution})
64
+ @response = get_record.response
65
+ process_record and process_search_results #since this is a search in addition to being a record call
66
+ else
67
+ brief_search = Exlibris::Primo::WebService::SearchBrief.new(search_params, @base_url, {:institution => @institution})
68
+ @response = brief_search.response
69
+ process_search_results
70
+ end
71
+ end
72
+
73
+ # Determine whether we have sufficient search criteria to search
74
+ # Sufficient means either:
75
+ # * We have a Primo doc id
76
+ # * We have either an isbn OR an issn
77
+ # * We have a title AND an author AND a genre
78
+ def insufficient_query?
79
+ return false unless (@primo_id.nil? or @primo_id.empty?)
80
+ return false unless (@issn.nil? or @issn.empty?) and (@isbn.nil? or @isbn.empty?)
81
+ return false unless (@title.nil? or @title.empty?) or (@author.nil? or @author.empty?) or (@genre.nil? or @genre.empty?)
82
+ return true
83
+ end
84
+
85
+ # Search params are determined by input to Exlibris::PrimoWS::SearchBrief
86
+ def search_params
87
+ search_params = {}
88
+ unless (@issn.nil? or @issn.empty?) and (@isbn.nil? or @isbn.empty?)
89
+ search_params[:isbn] = @isbn unless @isbn.nil?
90
+ search_params[:issn] = @issn if search_params.empty?
91
+ else
92
+ search_params[:title] = @title unless @title.nil?
93
+ search_params[:author] = @author unless @title.nil? or @author.nil?
94
+ search_params[:genre] = @genre unless @title.nil? or @author.nil? or @genre.nil?
95
+ end
96
+ return search_params
97
+ end
98
+
99
+ # Process a single record
100
+ def process_record
101
+ @count = response.at("//search:DOCSET", SEARCH_NS)["TOTALHITS"] unless response.nil? or @count
102
+ response.at("//pnx:addata", PNX_NS).children.each do |addata_child|
103
+ name = addata_child.name and value = addata_child.inner_text if addata_child.elem?
104
+ next if value.nil?
105
+ self.class.add_attr_reader name.to_sym unless name.nil?
106
+ instance_variable_set("@#{name}".to_sym, "#{value}") unless name.nil?
107
+ end
108
+ @cover_image = response.at("//pnx:addata/pnx:lad02", PNX_NS).inner_text unless response.at("//pnx:addata/pnx:lad02", PNX_NS).nil?
109
+ @titles = []
110
+ response.search("//pnx:display/pnx:title", PNX_NS).each do |title|
111
+ @titles.push(title.inner_text)
112
+ end
113
+ @authors = []
114
+ response.search("//pnx:display/pnx:creator", PNX_NS).each do |creator|
115
+ @authors.push(creator.inner_text)
116
+ end
117
+ end
118
+
119
+ # Process search results
120
+ # Process Holdings based on display/availlibrary
121
+ # Process URLs based on links/linktorsrc
122
+ # Process TOCs based on links/linktotoc
123
+ def process_search_results
124
+ @count = response.at("//search:DOCSET", SEARCH_NS)["TOTALHITS"] unless response.nil? or @count
125
+ # Loop through records to set metadata for holdings, urls and tocs
126
+ response.search("//pnx:record", PNX_NS).each do |record|
127
+ # Default genre to article if necessary
128
+ record_genre = (record.xpath("pnx:addata/pnx:genre", PNX_NS).nil?) ? "article" : record.xpath("pnx:addata/pnx:genre", PNX_NS).inner_text
129
+ # Don't process if passed in genre doesn't match the record genre unless the discrepancy is only b/w journals and articles
130
+ # If we're working off id numbers, we should be good to proceed
131
+ next unless @primo_id or @isbn or @issn or
132
+ @genre == record_genre or (@genre == "journal" and record_genre == "article")
133
+ # Just take the first element for record level elements
134
+ # (should only be one, except sourceid which will be handled later)
135
+ record_id = record.xpath("pnx:control/pnx:recordid", PNX_NS).inner_text
136
+ display_type = record.xpath("pnx:display/pnx:type", PNX_NS).inner_text
137
+ original_source_id = record.xpath("pnx:control/pnx:originalsourceid", PNX_NS).inner_text unless record.xpath("pnx:control/pnx:originalsourceid", PNX_NS).nil?
138
+ original_source_ids = process_control_hash(record, "pnx:control/pnx:originalsourceid", PNX_NS)
139
+ source_id = record.xpath("pnx:control/pnx:sourceid", PNX_NS).inner_text
140
+ source_ids = process_control_hash(record, "pnx:control/pnx:sourceid", PNX_NS)
141
+ source_record_id = record.xpath("pnx:control/pnx:sourcerecordid", PNX_NS).inner_text
142
+ # Process holdings
143
+ source_record_ids = process_control_hash(record, "pnx:control/pnx:sourcerecordid", PNX_NS)
144
+ record.xpath("pnx:display/pnx:availlibrary", PNX_NS).each do |availlibrary|
145
+ availlibrary, institution_code, library_code, id_one, id_two, status_code, origin = process_availlibrary availlibrary
146
+ holding_original_source_id = (origin.nil?) ? original_source_ids[record_id] : original_source_ids[origin] unless original_source_ids.empty?
147
+ holding_original_source_id = original_source_id if holding_original_source_id.nil?
148
+ holding_source_id = (origin.nil?) ? source_ids[record_id] : source_ids[origin] unless source_ids.empty?
149
+ holding_source_id = source_id if holding_source_id.nil?
150
+ holding_source_record_id = (origin.nil?) ? source_record_ids[record_id] : source_record_ids[origin] unless source_record_ids.empty?
151
+ holding_source_record_id = source_record_id if holding_source_record_id.nil?
152
+ holding_parameters = {
153
+ :base_url => @base_url, :vid => @vid, :config => @config,
154
+ :record_id => record_id, :original_source_id => holding_original_source_id,
155
+ :source_id => holding_source_id, :source_record_id => holding_source_record_id,
156
+ :origin => origin, :availlibrary => availlibrary, :institution_code => institution_code,
157
+ :library_code => library_code, :id_one => id_one, :id_two => id_two,
158
+ :status_code => status_code, :origin => origin, :display_type => display_type, :notes => ""# ,
159
+ # :match_reliability =>
160
+ # (record.xpath("pnx:display/pnx:title", PNX_NS) and record.xpath("pnx:display/pnx:creator", PNX_NS)) ?
161
+ # (reliable_match?(:title => record.xpath("pnx:display/pnx:title", PNX_NS).inner_text, :author => record.xpath("pnx:display/pnx:creator", PNX_NS).inner_text)) ?
162
+ # ServiceResponse::MatchExact : ServiceResponse::MatchUnsure : ServiceResponse::MatchExact
163
+ }
164
+ holding = Exlibris::Primo::Holding.new(holding_parameters)
165
+ @holdings.push(holding) unless holding.nil?
166
+ end
167
+ # Process urls
168
+ record.xpath("pnx:links/pnx:linktorsrc", PNX_NS).each do |linktorsrc|
169
+ linktorsrc, v, url, display, institution_code, origin = process_linktorsrc linktorsrc
170
+ rsrc = Exlibris::Primo::Rsrc.new({
171
+ :record_id => record_id, :linktorsrc => linktorsrc,
172
+ :v => v, :url => url, :display => display,
173
+ :institution_code => institution_code, :origin => origin,
174
+ :notes => ""
175
+ }) unless linktorsrc.nil?
176
+ @rsrcs.push(rsrc) unless (rsrc.nil? or rsrc.url.nil?)
177
+ end
178
+ # Process tocs
179
+ record.xpath("pnx:links/pnx:linktotoc", PNX_NS).each do |linktotoc|
180
+ linktotoc, url, display = process_linktotoc linktotoc
181
+ toc = Exlibris::Primo::Toc.new({
182
+ :record_id => record_id, :linktotoc => linktotoc,
183
+ :url => url, :display => display,
184
+ :notes => ""
185
+ }) unless linktotoc.nil?
186
+ @tocs.push(toc) unless (toc.nil? or toc.url.nil?)
187
+ end
188
+ # Process addlinks
189
+ record.xpath("pnx:links/pnx:addlink", PNX_NS).each do |addlink|
190
+ addlink, url, display = process_addlink addlink
191
+ related_link = Exlibris::Primo::RelatedLink.new({
192
+ :record_id => record_id, :addlink => addlink,
193
+ :url => url, :display => display,
194
+ :notes => ""
195
+ }) unless addlink.nil?
196
+ @related_links.push(related_link) unless (related_link.nil? or related_link.url.nil?)
197
+ end
198
+ end
199
+ end
200
+
201
+ def process_control_hash(record, xpath, ns)
202
+ h = {}
203
+ record.xpath(xpath, ns).each do |e|
204
+ str = e.inner_text unless e.nil?
205
+ a = str.split(/\$(?=\$)/) unless str.nil?
206
+ v = nil
207
+ o = nil
208
+ a.each do |s|
209
+ v = s.sub!(/^\$V/, "") unless s.match(/^\$V/).nil?
210
+ o = s.sub!(/^\$O/, "") unless s.match(/^\$O/).nil?
211
+ end
212
+ h[o] = v unless (o.nil? or v.nil?)
213
+ end
214
+ return h
215
+ end
216
+
217
+ # Determine how sure we are that this is a match.
218
+ # Dynamically compares record metadata to input values
219
+ # based on the values passed in.
220
+ # Minimum requirement is to check title.
221
+ def reliable_match?(record_metadata)
222
+ return true unless (@primo_id.nil? or @primo_id.empty?)
223
+ return true unless (@issn.nil? or @issn.empty?) and (@isbn.nil? or @isbn.empty?)
224
+ return false if (record_metadata.nil? or record_metadata.empty? or record_metadata[:title].nil? or record_metadata[:title].empty?)
225
+ # Titles must be equal
226
+ return false unless record_metadata[:title].downcase.eql?(@title.downcase)
227
+ # Compare record metadata with metadata that was passed in.
228
+ # Only check if the record metadata value contains the input value since we can't be too strict.
229
+ record_metadata.each { |type, value| return false if value.downcase.match("#{self.method(type).call}".downcase).nil?}
230
+ return true
231
+ end
232
+
233
+ def process_availlibrary(input)
234
+ availlibrary, institution_code, library_code, id_one, id_two, status_code, origin =
235
+ nil, nil, nil, nil, nil, nil, nil
236
+ return institution_code, library_code, id_one, id_two, status_code, origin if input.nil? or input.inner_text.nil?
237
+ availlibrary = input.inner_text
238
+ availlibrary.split(/\$(?=\$)/).each do |s|
239
+ institution_code = s.sub!(/^\$I/, "") unless s.match(/^\$I/).nil?
240
+ library_code = s.sub!(/^\$L/, "") unless s.match(/^\$L/).nil?
241
+ id_one = s.sub!(/^\$1/, "") unless s.match(/^\$1/).nil?
242
+ id_two = s.sub!(/^\$2/, "") unless s.match(/^\$2/).nil?
243
+ # Always display "Check Availability" if this is from Primo.
244
+ #@status_code = s.sub!(/^\$S/, "") unless s.match(/^\$S/).nil?
245
+ status_code = "check_holdings"
246
+ origin = s.sub!(/^\$O/, "") unless s.match(/^\$O/).nil?
247
+ end
248
+ return availlibrary, institution_code, library_code, id_one, id_two, status_code, origin
249
+ end
250
+
251
+ def process_linktorsrc(input)
252
+ linktorsrc, v, url, display, institution_code, origin = nil, nil, nil, nil, nil, nil
253
+ return linktorsrc, v, url, display, institution_code, origin if input.nil? or input.inner_text.nil?
254
+ linktorsrc = input.inner_text
255
+ linktorsrc.split(/\$(?=\$)/).each do |s|
256
+ v = s.sub!(/^\$V/, "") unless s.match(/^\$V/).nil?
257
+ url = s.sub!(/^\$U/, "") unless s.match(/^\$U/).nil?
258
+ display = s.sub!(/^\$D/, "") unless s.match(/^\$D/).nil?
259
+ institution_code = s.sub!(/^\$I/, "") unless s.match(/^\$I/).nil?
260
+ origin = s.sub!(/^\$O/, "") unless s.match(/^\$O/).nil?
261
+ end
262
+ return linktorsrc, v, url, display, institution_code, origin
263
+ end
264
+
265
+ def process_linktotoc(input)
266
+ linktotoc, url, display, = nil, nil, nil
267
+ return linktotoc, url, display if input.nil? or input.inner_text.nil?
268
+ linktotoc = input.inner_text
269
+ linktotoc.split(/\$(?=\$)/).each do |s|
270
+ url = s.sub!(/^\$U/, "") unless s.match(/^\$U/).nil?
271
+ display = s.sub!(/^\$D/, "") unless s.match(/^\$D/).nil?
272
+ end
273
+ return linktotoc, url, display
274
+ end
275
+
276
+ def process_addlink(input)
277
+ addlink, url, display, = nil, nil, nil
278
+ return addlink, url, display if input.nil? or input.inner_text.nil?
279
+ addlink = input.inner_text
280
+ addlink.split(/\$(?=\$)/).each do |s|
281
+ url = s.sub!(/^\$U/, "") unless s.match(/^\$U/).nil?
282
+ display = s.sub!(/^\$D/, "") unless s.match(/^\$D/).nil?
283
+ end
284
+ return addlink, url, display
285
+ end
286
+
287
+ def raise_required_setup_parameter_error(parameter)
288
+ raise "Initialization error in #{self.class}. Missing required setup parameter: #{parameter}."
289
+ end
290
+ end
291
+ end
292
+ end