berkeley_library-alma 0.0.5 → 0.0.7.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.
- checksums.yaml +4 -4
- data/.idea/alma.iml +12 -13
- data/.idea/inspectionProfiles/Project_Default.xml +2 -0
- data/CHANGES.md +16 -0
- data/berkeley_library-alma.gemspec +2 -2
- data/lib/berkeley_library/alma/config.rb +6 -4
- data/lib/berkeley_library/alma/module_info.rb +1 -1
- data/lib/berkeley_library/alma/record_id.rb +4 -8
- data/lib/berkeley_library/alma/sru/sru.rb +108 -0
- data/lib/berkeley_library/alma/sru/xml_reader.rb +148 -0
- data/lib/berkeley_library/alma/sru.rb +1 -79
- data/spec/data/availability-sru-page-1.xml +522 -0
- data/spec/data/availability-sru-page-2.xml +266 -0
- data/spec/lib/berkeley_library/alma/sru/sru_spec.rb +98 -0
- data/spec/lib/berkeley_library/alma/sru/xml_reader_spec.rb +75 -0
- data/spec/spec_helper.rb +5 -5
- metadata +22 -8
- data/spec/lib/berkeley_library/alma/sru_spec.rb +0 -29
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 75af9a35d41937d5f783bb9ef9bca06e2896692b17289ed2d226213268525b8d
|
|
4
|
+
data.tar.gz: a19bf19c4d06f4453f1a1deab67967f9a8109e558e3f93a3222232bcf131dd8e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 15a42332adcabc623f442b8dbe9139fc81eecf4a724dae5b7c6f59e9f3dea6611de8901105eea70391f7921440020e5042db71c569b65fb4fb95b09c5106a1f1
|
|
7
|
+
data.tar.gz: 60fa24417122d3847c89013c0d0b7d20cee77953d87170b515b1b25f6635a279166acf7f8e76b1f6e1f322f7a7c7c351de51f5d6f92fec8c5834a116710ada49
|
data/.idea/alma.iml
CHANGED
|
@@ -12,15 +12,15 @@
|
|
|
12
12
|
</content>
|
|
13
13
|
<orderEntry type="jdk" jdkName="RVM: ruby-2.7.5" jdkType="RUBY_SDK" />
|
|
14
14
|
<orderEntry type="sourceFolder" forTests="false" />
|
|
15
|
-
<orderEntry type="library" scope="PROVIDED" name="actionpack (
|
|
16
|
-
<orderEntry type="library" scope="PROVIDED" name="actionview (
|
|
17
|
-
<orderEntry type="library" scope="PROVIDED" name="activesupport (
|
|
15
|
+
<orderEntry type="library" scope="PROVIDED" name="actionpack (v7.0.2.4, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
16
|
+
<orderEntry type="library" scope="PROVIDED" name="actionview (v7.0.2.4, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
17
|
+
<orderEntry type="library" scope="PROVIDED" name="activesupport (v7.0.2.4, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
18
18
|
<orderEntry type="library" scope="PROVIDED" name="addressable (v2.8.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
19
19
|
<orderEntry type="library" scope="PROVIDED" name="amazing_print (v1.4.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
20
20
|
<orderEntry type="library" scope="PROVIDED" name="ast (v2.4.2, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
21
|
-
<orderEntry type="library" scope="PROVIDED" name="berkeley_library-logging (v0.2.
|
|
21
|
+
<orderEntry type="library" scope="PROVIDED" name="berkeley_library-logging (v0.2.6, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
22
22
|
<orderEntry type="library" scope="PROVIDED" name="berkeley_library-marc (v0.3.1, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
23
|
-
<orderEntry type="library" scope="PROVIDED" name="berkeley_library-util (v0.1.
|
|
23
|
+
<orderEntry type="library" scope="PROVIDED" name="berkeley_library-util (v0.1.2, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
24
24
|
<orderEntry type="library" scope="PROVIDED" name="builder (v3.2.4, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
25
25
|
<orderEntry type="library" scope="PROVIDED" name="bundle-audit (v0.1.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
26
26
|
<orderEntry type="library" scope="PROVIDED" name="bundler (v2.1.4, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
<orderEntry type="library" scope="PROVIDED" name="ci_reporter (v2.0.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
29
29
|
<orderEntry type="library" scope="PROVIDED" name="ci_reporter_rspec (v1.0.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
30
30
|
<orderEntry type="library" scope="PROVIDED" name="colorize (v0.8.1, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
31
|
-
<orderEntry type="library" scope="PROVIDED" name="concurrent-ruby (v1.1.
|
|
31
|
+
<orderEntry type="library" scope="PROVIDED" name="concurrent-ruby (v1.1.10, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
32
32
|
<orderEntry type="library" scope="PROVIDED" name="crack (v0.4.5, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
33
33
|
<orderEntry type="library" scope="PROVIDED" name="crass (v1.0.6, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
34
34
|
<orderEntry type="library" scope="PROVIDED" name="diff-lcs (v1.5.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
@@ -39,16 +39,15 @@
|
|
|
39
39
|
<orderEntry type="library" scope="PROVIDED" name="http-accept (v1.7.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
40
40
|
<orderEntry type="library" scope="PROVIDED" name="http-cookie (v1.0.4, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
41
41
|
<orderEntry type="library" scope="PROVIDED" name="i18n (v1.10.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
42
|
-
<orderEntry type="library" scope="PROVIDED" name="lograge (v0.
|
|
43
|
-
<orderEntry type="library" scope="PROVIDED" name="loofah (v2.
|
|
42
|
+
<orderEntry type="library" scope="PROVIDED" name="lograge (v0.12.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
43
|
+
<orderEntry type="library" scope="PROVIDED" name="loofah (v2.17.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
44
44
|
<orderEntry type="library" scope="PROVIDED" name="marc (v1.1.1, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
45
45
|
<orderEntry type="library" scope="PROVIDED" name="method_source (v1.0.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
46
46
|
<orderEntry type="library" scope="PROVIDED" name="mime-types (v3.4.1, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
47
47
|
<orderEntry type="library" scope="PROVIDED" name="mime-types-data (v3.2022.0105, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
48
|
-
<orderEntry type="library" scope="PROVIDED" name="mini_portile2 (v2.7.1, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
49
48
|
<orderEntry type="library" scope="PROVIDED" name="minitest (v5.15.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
50
49
|
<orderEntry type="library" scope="PROVIDED" name="netrc (v0.11.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
51
|
-
<orderEntry type="library" scope="PROVIDED" name="nokogiri (v1.13.
|
|
50
|
+
<orderEntry type="library" scope="PROVIDED" name="nokogiri (v1.13.5, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
52
51
|
<orderEntry type="library" scope="PROVIDED" name="oj (v3.13.11, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
53
52
|
<orderEntry type="library" scope="PROVIDED" name="ougai (v1.9.1, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
54
53
|
<orderEntry type="library" scope="PROVIDED" name="parallel (v1.21.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
@@ -60,7 +59,7 @@
|
|
|
60
59
|
<orderEntry type="library" scope="PROVIDED" name="rack-test (v1.1.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
61
60
|
<orderEntry type="library" scope="PROVIDED" name="rails-dom-testing (v2.0.3, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
62
61
|
<orderEntry type="library" scope="PROVIDED" name="rails-html-sanitizer (v1.4.2, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
63
|
-
<orderEntry type="library" scope="PROVIDED" name="railties (
|
|
62
|
+
<orderEntry type="library" scope="PROVIDED" name="railties (v7.0.2.4, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
64
63
|
<orderEntry type="library" scope="PROVIDED" name="rainbow (v3.1.1, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
65
64
|
<orderEntry type="library" scope="PROVIDED" name="rake (v13.0.6, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
66
65
|
<orderEntry type="library" scope="PROVIDED" name="regexp_parser (v2.2.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
@@ -87,7 +86,7 @@
|
|
|
87
86
|
<orderEntry type="library" scope="PROVIDED" name="typesafe_enum (v0.3.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
88
87
|
<orderEntry type="library" scope="PROVIDED" name="tzinfo (v2.0.4, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
89
88
|
<orderEntry type="library" scope="PROVIDED" name="unf (v0.1.4, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
90
|
-
<orderEntry type="library" scope="PROVIDED" name="unf_ext (v0.0.8, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
89
|
+
<orderEntry type="library" scope="PROVIDED" name="unf_ext (v0.0.8.1, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
91
90
|
<orderEntry type="library" scope="PROVIDED" name="unicode-display_width (v2.1.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
92
91
|
<orderEntry type="library" scope="PROVIDED" name="webmock (v3.14.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
93
92
|
<orderEntry type="library" scope="PROVIDED" name="webrick (v1.7.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
@@ -109,7 +108,7 @@
|
|
|
109
108
|
</RakeTaskImpl>
|
|
110
109
|
<RakeTaskImpl description="Run all specs in spec directory, with coverage" fullCommand="coverage" id="coverage" />
|
|
111
110
|
<RakeTaskImpl description="Run tests, check test coverage, check code style, check for vulnerabilities, build gem" fullCommand="default" id="default" />
|
|
112
|
-
<RakeTaskImpl description="Build berkeley_library-alma.gemspec as berkeley_library-alma-0.0.
|
|
111
|
+
<RakeTaskImpl description="Build berkeley_library-alma.gemspec as berkeley_library-alma-0.0.5.gem" fullCommand="gem" id="gem" />
|
|
113
112
|
<RakeTaskImpl description="Run RuboCop with auto-correct, and output results to console" fullCommand="ra" id="ra" />
|
|
114
113
|
<RakeTaskImpl description="Run rubocop with HTML output" fullCommand="rubocop" id="rubocop" />
|
|
115
114
|
<RakeTaskImpl id="rubocop">
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
<inspection_tool class="GoUnusedGlobalVariable" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
8
8
|
<inspection_tool class="GoUnusedVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
|
9
9
|
<inspection_tool class="GrazieInspection" enabled="false" level="TYPO" enabled_by_default="false" />
|
|
10
|
+
<inspection_tool class="HttpUrlsUsage" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
|
10
11
|
<inspection_tool class="LanguageDetectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
11
12
|
<inspection_tool class="Rubocop" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
12
13
|
<inspection_tool class="RubyCaseWithoutElseBlockInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
@@ -15,6 +16,7 @@
|
|
|
15
16
|
</inspection_tool>
|
|
16
17
|
<inspection_tool class="RubyNilAnalysis" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
|
17
18
|
<inspection_tool class="RubyStringKeysInHashInspection" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
|
19
|
+
<inspection_tool class="RubyTooManyInstanceVariablesInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
|
18
20
|
<inspection_tool class="RubyUnnecessaryReturnStatement" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
|
19
21
|
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
|
20
22
|
<option name="processCode" value="true" />
|
data/CHANGES.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
# 0.0.7.1 (24 May 2022)
|
|
2
|
+
|
|
3
|
+
- Set minimum Nokogiri version to address
|
|
4
|
+
[CVE-2022-29181](https://nvd.nist.gov/vuln/detail/CVE-2022-29181)
|
|
5
|
+
|
|
6
|
+
# 0.0.7 (6 May 2022)
|
|
7
|
+
|
|
8
|
+
- `SRU#get_marc_records` now accepts a `max_records` parameter corresponding to the SRU
|
|
9
|
+
`maximumRecords` query parameter for page size.
|
|
10
|
+
- `BerkeleyLibrary::Alma::Config.default!` now returns the `Config` singleton rather than
|
|
11
|
+
an arbitrary config value.
|
|
12
|
+
|
|
13
|
+
# 0.0.6 (5 May 2022)
|
|
14
|
+
|
|
15
|
+
- `SRU#get_marc_records` now supports paginated results.
|
|
16
|
+
|
|
1
17
|
# 0.0.5 (4 May 2022)
|
|
2
18
|
|
|
3
19
|
- extracts `SRU` module for performing SRU queries.
|
|
@@ -24,8 +24,8 @@ Gem::Specification.new do |spec|
|
|
|
24
24
|
|
|
25
25
|
spec.add_dependency 'berkeley_library-logging', '~> 0.2'
|
|
26
26
|
spec.add_dependency 'berkeley_library-marc', '~> 0.3.1'
|
|
27
|
-
spec.add_dependency 'berkeley_library-util', '~> 0.1', '>= 0.1.
|
|
28
|
-
spec.add_dependency 'nokogiri', '~> 1.
|
|
27
|
+
spec.add_dependency 'berkeley_library-util', '~> 0.1', '>= 0.1.2'
|
|
28
|
+
spec.add_dependency 'nokogiri', '~> 1.13', '>= 1.13.6'
|
|
29
29
|
|
|
30
30
|
spec.add_development_dependency 'bundle-audit', '~> 0.1'
|
|
31
31
|
spec.add_development_dependency 'ci_reporter_rspec', '~> 1.0'
|
|
@@ -118,10 +118,12 @@ module BerkeleyLibrary
|
|
|
118
118
|
|
|
119
119
|
def default!
|
|
120
120
|
BerkeleyLibrary::Alma.configure do
|
|
121
|
-
config.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
config.tap do |c|
|
|
122
|
+
c.alma_sru_host = ENV.fetch('LIT_ALMA_SRU_HOST', 'berkeley.alma.exlibrisgroup.com')
|
|
123
|
+
c.alma_institution_code = ENV.fetch('LIT_ALMA_INSTITUTION_CODE', '01UCS_BER')
|
|
124
|
+
c.alma_primo_host = ENV.fetch('LIT_ALMA_PRIMO_HOST', 'search.library.berkeley.edu')
|
|
125
|
+
c.alma_permalink_key = ENV.fetch('LIT_ALMA_PERMALINK_KEY', 'iqob43')
|
|
126
|
+
end
|
|
125
127
|
end
|
|
126
128
|
end
|
|
127
129
|
|
|
@@ -7,7 +7,7 @@ module BerkeleyLibrary
|
|
|
7
7
|
SUMMARY = 'Alma/Primo utilities for the UC Berkeley Library'.freeze
|
|
8
8
|
DESCRIPTION = 'A gem providing Alma/Primo-related utility code for the UC Berkeley Library'.freeze
|
|
9
9
|
LICENSE = 'MIT'.freeze
|
|
10
|
-
VERSION = '0.0.
|
|
10
|
+
VERSION = '0.0.7.1'.freeze
|
|
11
11
|
HOMEPAGE = 'https://github.com/BerkeleyLibrary/alma'.freeze
|
|
12
12
|
end
|
|
13
13
|
end
|
|
@@ -48,7 +48,7 @@ module BerkeleyLibrary
|
|
|
48
48
|
#
|
|
49
49
|
# @return [URI] the MARC URI
|
|
50
50
|
def marc_uri
|
|
51
|
-
SRU.sru_query_uri(sru_query_value)
|
|
51
|
+
SRU.sru_query_uri(sru_query_value, max_records: 1)
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
# Makes an SRU query for this record and returns a MARC record, or nil if the
|
|
@@ -60,12 +60,8 @@ module BerkeleyLibrary
|
|
|
60
60
|
# @return [MARC::Record, nil] the MARC record
|
|
61
61
|
# rubocop:disable Naming/AccessorMethodName
|
|
62
62
|
def get_marc_record
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return nil
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
logger.warn("GET #{marc_uri} did not return a MARC record") unless (marc_record = marc_reader.first)
|
|
63
|
+
records = SRU.marc_records_for(sru_query_value, max_records: 1)
|
|
64
|
+
logger.warn("GET #{marc_uri} did not return a MARC record") unless (marc_record = records.first)
|
|
69
65
|
marc_record
|
|
70
66
|
end
|
|
71
67
|
# rubocop:enable Naming/AccessorMethodName
|
|
@@ -76,7 +72,7 @@ module BerkeleyLibrary
|
|
|
76
72
|
# @return [String, nil] the SRU query response body, or nil in the event of an error.
|
|
77
73
|
# rubocop:disable Naming/AccessorMethodName
|
|
78
74
|
def get_marc_xml
|
|
79
|
-
SRU.make_sru_query(sru_query_value)
|
|
75
|
+
SRU.make_sru_query(sru_query_value, max_records: 1)
|
|
80
76
|
end
|
|
81
77
|
# rubocop:enable Naming/AccessorMethodName
|
|
82
78
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
require 'berkeley_library/util/uris'
|
|
3
|
+
require 'berkeley_library/alma/config'
|
|
4
|
+
require 'berkeley_library/alma/constants'
|
|
5
|
+
require 'berkeley_library/alma/record_id'
|
|
6
|
+
require 'berkeley_library/alma/sru/xml_reader'
|
|
7
|
+
|
|
8
|
+
module BerkeleyLibrary
|
|
9
|
+
module Alma
|
|
10
|
+
module SRU
|
|
11
|
+
include BerkeleyLibrary::Logging
|
|
12
|
+
include Constants
|
|
13
|
+
|
|
14
|
+
DEFAULT_MAX_RECORDS = 10
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
include SRU
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Given a list of record IDs, returns the MARC records for each ID (if found).
|
|
21
|
+
# Note that the order of the records is not guaranteed.
|
|
22
|
+
#
|
|
23
|
+
# @param record_ids [Array<String, RecordId>] the record IDs to look up
|
|
24
|
+
# @param freeze [Boolean] whether to freeze the records
|
|
25
|
+
# @param max_records the number of records per SRU page.
|
|
26
|
+
# @return [Enumerator::Lazy<MARC::Record>] the records
|
|
27
|
+
def get_marc_records(*record_ids, max_records: DEFAULT_MAX_RECORDS, freeze: false)
|
|
28
|
+
# noinspection RubyMismatchedReturnType
|
|
29
|
+
parsed_ids = record_ids.filter_map { |id| RecordId.parse(id) }
|
|
30
|
+
raise ArgumentError, "Argument #{record_ids.inspect} contain no valid record IDs" if parsed_ids.empty?
|
|
31
|
+
|
|
32
|
+
sru_query_value = parsed_ids.map(&:sru_query_value).join(' or ')
|
|
33
|
+
SRU.marc_records_for(sru_query_value, max_records: max_records, freeze: freeze)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Returns a URI for retrieving records for the specified query
|
|
37
|
+
# via SRU. Requires {Config#alma_sru_base_uri} to be set.
|
|
38
|
+
#
|
|
39
|
+
# @return [URI] the MARC URI
|
|
40
|
+
# @param max_records the number of records per SRU page
|
|
41
|
+
def sru_query_uri(sru_query_value, max_records: DEFAULT_MAX_RECORDS)
|
|
42
|
+
query_string = URI.encode_www_form(
|
|
43
|
+
'version' => '1.2',
|
|
44
|
+
'operation' => 'searchRetrieve',
|
|
45
|
+
'query' => sru_query_value
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
Util::URIs.append(Config.alma_sru_base_uri, '?', query_string, '&', "maximumRecords=#{max_records}")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Makes an SRU query for the specified query value and returns the query response
|
|
52
|
+
# as a string.
|
|
53
|
+
#
|
|
54
|
+
# @param query_value [String] the value of the SRU query parameter
|
|
55
|
+
# @param max_records the number of records per SRU page
|
|
56
|
+
# @return [String, nil] the SRU query response body, or nil in the event of an error.
|
|
57
|
+
def make_sru_query(query_value, max_records: DEFAULT_MAX_RECORDS)
|
|
58
|
+
uri = sru_query_uri(query_value, max_records: max_records)
|
|
59
|
+
do_get(uri)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Makes an SRU query for the specified query value and returns the query response
|
|
63
|
+
# as MARC records.
|
|
64
|
+
#
|
|
65
|
+
# @param query_value [String] the value of the SRU query parameter
|
|
66
|
+
# @param freeze [Boolean] whether to freeze the records
|
|
67
|
+
# @param max_records the number of records per SRU page
|
|
68
|
+
# @return [Enumerator::Lazy<MARC::Record>] the records
|
|
69
|
+
def marc_records_for(query_value, max_records: DEFAULT_MAX_RECORDS, freeze: false)
|
|
70
|
+
Enumerator.new do |y|
|
|
71
|
+
uri = sru_query_uri(query_value, max_records: max_records)
|
|
72
|
+
perform_query(uri, freeze: freeze) { |rec| y << rec }
|
|
73
|
+
end.lazy
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def perform_query(query_uri, start_record: nil, freeze: false, &block)
|
|
79
|
+
full_query_uri = full_query_uri_for(query_uri, start_record)
|
|
80
|
+
next_start_record = perform_single_query(full_query_uri, freeze, &block)
|
|
81
|
+
return unless next_start_record
|
|
82
|
+
|
|
83
|
+
perform_query(query_uri, start_record: next_start_record, freeze: freeze, &block)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def full_query_uri_for(query_uri, start_record)
|
|
87
|
+
return query_uri unless start_record
|
|
88
|
+
|
|
89
|
+
BerkeleyLibrary::Util::URIs.append(query_uri, "&startRecord=#{start_record}")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def perform_single_query(query_uri, freeze, &block)
|
|
93
|
+
return unless (xml = do_get(query_uri))
|
|
94
|
+
|
|
95
|
+
xml_reader = XMLReader.read(xml, freeze: freeze)
|
|
96
|
+
xml_reader.each(&block)
|
|
97
|
+
xml_reader.next_record_position
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def do_get(uri)
|
|
101
|
+
Util::URIs.get(uri, headers: { user_agent: DEFAULT_USER_AGENT })
|
|
102
|
+
rescue RestClient::Exception => e
|
|
103
|
+
logger.warn("GET #{uri} failed", e)
|
|
104
|
+
nil
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
require 'nokogiri'
|
|
2
|
+
require 'marc/xml_parsers'
|
|
3
|
+
require 'berkeley_library/util/files'
|
|
4
|
+
|
|
5
|
+
module BerkeleyLibrary
|
|
6
|
+
module Alma
|
|
7
|
+
module SRU
|
|
8
|
+
# A customized XML reader for reading MARC records from SRU search results.
|
|
9
|
+
class XMLReader
|
|
10
|
+
include Enumerable
|
|
11
|
+
include ::MARC::NokogiriReader
|
|
12
|
+
include BerkeleyLibrary::Util::Files
|
|
13
|
+
|
|
14
|
+
# ############################################################
|
|
15
|
+
# Constants
|
|
16
|
+
|
|
17
|
+
NS_SRW = 'http://www.loc.gov/zing/srw/'.freeze
|
|
18
|
+
NS_MARC = 'http://www.loc.gov/MARC21/slim'.freeze
|
|
19
|
+
|
|
20
|
+
# ############################################################
|
|
21
|
+
# Attributes
|
|
22
|
+
|
|
23
|
+
# @return [Integer, nil] the record identifier of the most recently parsed record, if any
|
|
24
|
+
attr_reader :last_record_id
|
|
25
|
+
|
|
26
|
+
# @return [Integer, nil] the record position of the most recently parsed record, if any
|
|
27
|
+
attr_reader :last_record_position
|
|
28
|
+
|
|
29
|
+
# @return [Integer, nil] the next record position, if present
|
|
30
|
+
attr_reader :next_record_position
|
|
31
|
+
|
|
32
|
+
# Returns the total number of records, based on the `<numberOfRecords/>` tag
|
|
33
|
+
# returned in the SRU response.
|
|
34
|
+
#
|
|
35
|
+
# Note that the total is not guaranteed to be present, and if present,
|
|
36
|
+
# may not be present unless at least some records have been parsed.
|
|
37
|
+
#
|
|
38
|
+
# @return [Integer, nil] the total number of records, or `nil` if the total has not been read yet
|
|
39
|
+
def num_records
|
|
40
|
+
@num_records&.to_i
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Returns the number of records yielded.
|
|
44
|
+
#
|
|
45
|
+
# @return [Integer] the number of records yielded.
|
|
46
|
+
def records_yielded
|
|
47
|
+
@records_yielded ||= 0
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# ############################################################
|
|
51
|
+
# Initializer
|
|
52
|
+
|
|
53
|
+
def initialize(source, freeze: false)
|
|
54
|
+
@handle = ensure_io(source)
|
|
55
|
+
@freeze = freeze
|
|
56
|
+
init
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class << self
|
|
60
|
+
# Reads MARC records from an XML datasource given either as an XML string, a file path,
|
|
61
|
+
# or as an IO object.
|
|
62
|
+
#
|
|
63
|
+
# @param source [String, Pathname, IO] an XML string, the path to a file, or an IO to read from directly
|
|
64
|
+
# @param freeze [Boolean] whether to freeze each record after reading
|
|
65
|
+
def read(source, freeze: false)
|
|
66
|
+
new(source, freeze: freeze)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# ############################################################
|
|
71
|
+
# MARC::GenericPullParser overrides
|
|
72
|
+
|
|
73
|
+
def yield_record
|
|
74
|
+
@record[:record].tap do |record|
|
|
75
|
+
record.freeze if @freeze
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
super
|
|
79
|
+
ensure
|
|
80
|
+
increment_records_yielded!
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# ############################################################
|
|
84
|
+
# Nokogiri::XML::SAX::Document overrides
|
|
85
|
+
|
|
86
|
+
# @see Nokogiri::XML::Sax::Document#start_element_namespace
|
|
87
|
+
# rubocop:disable Metrics/ParameterLists
|
|
88
|
+
def start_element_namespace(name, attrs = [], prefix = nil, uri = nil, ns = [])
|
|
89
|
+
super
|
|
90
|
+
|
|
91
|
+
@current_element_ns = uri
|
|
92
|
+
@current_element_name = name
|
|
93
|
+
end
|
|
94
|
+
# rubocop:enable Metrics/ParameterLists
|
|
95
|
+
|
|
96
|
+
# @see Nokogiri::XML::Sax::Document#end_element_namespace
|
|
97
|
+
def end_element_namespace(name, prefix = nil, uri = nil)
|
|
98
|
+
# Delay yielding record till we reach the end of the outer SRU <record/>
|
|
99
|
+
# element (not the inner MARC <record/> element), so we can record the
|
|
100
|
+
# values of <recordIdentifier> and <recordPosition/>
|
|
101
|
+
if name.downcase == 'record'
|
|
102
|
+
yield_record if uri == NS_SRW
|
|
103
|
+
elsif uri == NS_MARC
|
|
104
|
+
super
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
@current_element_name = nil
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# @see Nokogiri::XML::Sax::Document#characters
|
|
111
|
+
# rubocop:disable Metrics/MethodLength
|
|
112
|
+
def characters(string)
|
|
113
|
+
return super unless NS_SRW == @current_element_ns
|
|
114
|
+
return unless (name = @current_element_name)
|
|
115
|
+
|
|
116
|
+
case name
|
|
117
|
+
when 'numberOfRecords'
|
|
118
|
+
@num_records = string
|
|
119
|
+
when 'recordIdentifier'
|
|
120
|
+
@last_record_id = string
|
|
121
|
+
when 'recordPosition'
|
|
122
|
+
@last_record_position = string.to_i
|
|
123
|
+
when 'nextRecordPosition'
|
|
124
|
+
@next_record_position = string.to_i
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
# rubocop:enable Metrics/MethodLength
|
|
128
|
+
|
|
129
|
+
# ############################################################
|
|
130
|
+
# Private
|
|
131
|
+
|
|
132
|
+
private
|
|
133
|
+
|
|
134
|
+
def ensure_io(file)
|
|
135
|
+
return file if reader_like?(file)
|
|
136
|
+
return File.new(file) if file_exists?(file)
|
|
137
|
+
return StringIO.new(file) if file =~ /^\s*</x
|
|
138
|
+
|
|
139
|
+
raise ArgumentError, "Don't know how to read XML from #{file.inspect}: not an IO, file path, or XML text"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def increment_records_yielded!
|
|
143
|
+
@records_yielded = records_yielded + 1
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -1,79 +1 @@
|
|
|
1
|
-
|
|
2
|
-
require 'berkeley_library/alma/config'
|
|
3
|
-
require 'berkeley_library/alma/constants'
|
|
4
|
-
require 'berkeley_library/alma/record_id'
|
|
5
|
-
require 'berkeley_library/util/uris'
|
|
6
|
-
|
|
7
|
-
module BerkeleyLibrary
|
|
8
|
-
module Alma
|
|
9
|
-
module SRU
|
|
10
|
-
include BerkeleyLibrary::Logging
|
|
11
|
-
include Constants
|
|
12
|
-
|
|
13
|
-
class << self
|
|
14
|
-
include SRU
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
# Given a list of record IDs, returns the MARC records for each ID (if found).
|
|
18
|
-
#
|
|
19
|
-
# @param record_ids [Array<String, RecordId>] the record IDs to look up
|
|
20
|
-
# @return [MARC::XMLReader, nil] a reader for the MARC records, or nil if
|
|
21
|
-
# the records could not be read
|
|
22
|
-
def get_marc_records(*record_ids)
|
|
23
|
-
# noinspection RubyMismatchedReturnType
|
|
24
|
-
parsed_ids = record_ids.filter_map { |id| RecordId.parse(id) }
|
|
25
|
-
raise ArgumentError, "Argument #{record_ids.inspect} contain no valid record IDs" if parsed_ids.empty?
|
|
26
|
-
|
|
27
|
-
sru_query_value = parsed_ids.map(&:sru_query_value).join(' or ')
|
|
28
|
-
SRU.marc_records_for(sru_query_value)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Returns a URI for retrieving records for the specified query
|
|
32
|
-
# via SRU. Requires {Config#alma_sru_base_uri} to be set.
|
|
33
|
-
#
|
|
34
|
-
# @return [URI] the MARC URI
|
|
35
|
-
def sru_query_uri(sru_query_value)
|
|
36
|
-
query_string = URI.encode_www_form(
|
|
37
|
-
'version' => '1.2',
|
|
38
|
-
'operation' => 'searchRetrieve',
|
|
39
|
-
'query' => sru_query_value
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
Util::URIs.append(Config.alma_sru_base_uri, '?', query_string)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# Makes an SRU query for the specified query value and returns the query response
|
|
46
|
-
# as a string.
|
|
47
|
-
#
|
|
48
|
-
# @param query_value [String] the value of the SRU query parameter
|
|
49
|
-
# @return [String, nil] the SRU query response body, or nil in the event of an error.
|
|
50
|
-
def make_sru_query(query_value)
|
|
51
|
-
uri = sru_query_uri(query_value)
|
|
52
|
-
do_get(uri)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# Makes an SRU query for the specified query value and returns the query response
|
|
56
|
-
# as MARC records.
|
|
57
|
-
#
|
|
58
|
-
# @param query_value [String] the value of the SRU query parameter
|
|
59
|
-
# @return [MARC::XMLReader, nil] a reader for the MARC records, or nil if
|
|
60
|
-
# the records could not be read
|
|
61
|
-
def marc_records_for(query_value)
|
|
62
|
-
return unless (xml = make_sru_query(query_value))
|
|
63
|
-
|
|
64
|
-
input = StringIO.new(xml.scrub)
|
|
65
|
-
MARC::XMLReader.new(input, parser: 'nokogiri')
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
private
|
|
69
|
-
|
|
70
|
-
def do_get(uri)
|
|
71
|
-
# TODO: can we get the XML as an IO rather than as a string?
|
|
72
|
-
Util::URIs.get(uri, headers: { user_agent: DEFAULT_USER_AGENT })
|
|
73
|
-
rescue RestClient::Exception => e
|
|
74
|
-
logger.warn("GET #{uri} failed", e)
|
|
75
|
-
nil
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
end
|
|
1
|
+
Dir.glob(File.expand_path('sru/*.rb', __dir__)).sort.each(&method(:require))
|