exlibris-primo 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 (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