berkeley_library-alma 0.0.5 → 0.0.6
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 +13 -13
- data/.idea/inspectionProfiles/Project_Default.xml +2 -0
- data/CHANGES.md +4 -0
- data/berkeley_library-alma.gemspec +1 -1
- data/lib/berkeley_library/alma/module_info.rb +1 -1
- data/lib/berkeley_library/alma/record_id.rb +2 -6
- data/lib/berkeley_library/alma/sru/sru.rb +102 -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
- metadata +14 -6
- 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: ab227f94abea9d1275bceb4ed361cf2d75021ae7d9cf49283e6139404e9e327f
|
|
4
|
+
data.tar.gz: 2370efa8b2bc2b72e6e506cf38271a74ee8a785cbebf7f42b19e49bf84b4a6da
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 438181aea5e19163ca57dd7c5c9086fd6d324ac5bb6e05fd33917cc70327ca5682e1f07a9ffc5280104207562f6fdda4443070bdc8ba3163b519ba70f96f2558
|
|
7
|
+
data.tar.gz: dc9cafbf0b36ee0ac1875d8cc89b0b4a10bdccd37456f67004e0c3abb0097fde7b8d5da31c946ab1eb4f300dd1f14ad5dcaf550ca697aef0de4f8b6d81521bdb
|
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,16 @@
|
|
|
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.
|
|
48
|
+
<orderEntry type="library" scope="PROVIDED" name="mini_portile2 (v2.8.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
49
49
|
<orderEntry type="library" scope="PROVIDED" name="minitest (v5.15.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
50
50
|
<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.
|
|
51
|
+
<orderEntry type="library" scope="PROVIDED" name="nokogiri (v1.13.5, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
52
52
|
<orderEntry type="library" scope="PROVIDED" name="oj (v3.13.11, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
53
53
|
<orderEntry type="library" scope="PROVIDED" name="ougai (v1.9.1, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
54
54
|
<orderEntry type="library" scope="PROVIDED" name="parallel (v1.21.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
<orderEntry type="library" scope="PROVIDED" name="rack-test (v1.1.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
61
61
|
<orderEntry type="library" scope="PROVIDED" name="rails-dom-testing (v2.0.3, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
62
62
|
<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 (
|
|
63
|
+
<orderEntry type="library" scope="PROVIDED" name="railties (v7.0.2.4, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
64
64
|
<orderEntry type="library" scope="PROVIDED" name="rainbow (v3.1.1, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
65
65
|
<orderEntry type="library" scope="PROVIDED" name="rake (v13.0.6, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
66
66
|
<orderEntry type="library" scope="PROVIDED" name="regexp_parser (v2.2.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
<orderEntry type="library" scope="PROVIDED" name="typesafe_enum (v0.3.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
88
88
|
<orderEntry type="library" scope="PROVIDED" name="tzinfo (v2.0.4, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
89
89
|
<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" />
|
|
90
|
+
<orderEntry type="library" scope="PROVIDED" name="unf_ext (v0.0.8.1, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
91
91
|
<orderEntry type="library" scope="PROVIDED" name="unicode-display_width (v2.1.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
92
92
|
<orderEntry type="library" scope="PROVIDED" name="webmock (v3.14.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
93
93
|
<orderEntry type="library" scope="PROVIDED" name="webrick (v1.7.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
</RakeTaskImpl>
|
|
110
110
|
<RakeTaskImpl description="Run all specs in spec directory, with coverage" fullCommand="coverage" id="coverage" />
|
|
111
111
|
<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.
|
|
112
|
+
<RakeTaskImpl description="Build berkeley_library-alma.gemspec as berkeley_library-alma-0.0.5.gem" fullCommand="gem" id="gem" />
|
|
113
113
|
<RakeTaskImpl description="Run RuboCop with auto-correct, and output results to console" fullCommand="ra" id="ra" />
|
|
114
114
|
<RakeTaskImpl description="Run rubocop with HTML output" fullCommand="rubocop" id="rubocop" />
|
|
115
115
|
<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
|
@@ -24,7 +24,7 @@ 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.
|
|
27
|
+
spec.add_dependency 'berkeley_library-util', '~> 0.1', '>= 0.1.2'
|
|
28
28
|
spec.add_dependency 'nokogiri', '~> 1.12'
|
|
29
29
|
|
|
30
30
|
spec.add_development_dependency 'bundle-audit', '~> 0.1'
|
|
@@ -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.6'.freeze
|
|
11
11
|
HOMEPAGE = 'https://github.com/BerkeleyLibrary/alma'.freeze
|
|
12
12
|
end
|
|
13
13
|
end
|
|
@@ -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)
|
|
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
|
|
@@ -0,0 +1,102 @@
|
|
|
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
|
+
class << self
|
|
15
|
+
include SRU
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Given a list of record IDs, returns the MARC records for each ID (if found).
|
|
19
|
+
# Note that the order of the records is not guaranteed.
|
|
20
|
+
#
|
|
21
|
+
# @param record_ids [Array<String, RecordId>] the record IDs to look up
|
|
22
|
+
# @param freeze [Boolean] whether to freeze the records
|
|
23
|
+
# @return [Enumerator::Lazy<MARC::Record>] the records
|
|
24
|
+
def get_marc_records(*record_ids, freeze: false)
|
|
25
|
+
# noinspection RubyMismatchedReturnType
|
|
26
|
+
parsed_ids = record_ids.filter_map { |id| RecordId.parse(id) }
|
|
27
|
+
raise ArgumentError, "Argument #{record_ids.inspect} contain no valid record IDs" if parsed_ids.empty?
|
|
28
|
+
|
|
29
|
+
sru_query_value = parsed_ids.map(&:sru_query_value).join(' or ')
|
|
30
|
+
SRU.marc_records_for(sru_query_value, freeze: freeze)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Returns a URI for retrieving records for the specified query
|
|
34
|
+
# via SRU. Requires {Config#alma_sru_base_uri} to be set.
|
|
35
|
+
#
|
|
36
|
+
# @return [URI] the MARC URI
|
|
37
|
+
def sru_query_uri(sru_query_value)
|
|
38
|
+
query_string = URI.encode_www_form(
|
|
39
|
+
'version' => '1.2',
|
|
40
|
+
'operation' => 'searchRetrieve',
|
|
41
|
+
'query' => sru_query_value
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
Util::URIs.append(Config.alma_sru_base_uri, '?', query_string)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Makes an SRU query for the specified query value and returns the query response
|
|
48
|
+
# as a string.
|
|
49
|
+
#
|
|
50
|
+
# @param query_value [String] the value of the SRU query parameter
|
|
51
|
+
# @return [String, nil] the SRU query response body, or nil in the event of an error.
|
|
52
|
+
def make_sru_query(query_value)
|
|
53
|
+
uri = sru_query_uri(query_value)
|
|
54
|
+
do_get(uri)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Makes an SRU query for the specified query value and returns the query response
|
|
58
|
+
# as MARC records.
|
|
59
|
+
#
|
|
60
|
+
# @param query_value [String] the value of the SRU query parameter
|
|
61
|
+
# @param freeze [Boolean] whether to freeze the records
|
|
62
|
+
# @return [Enumerator::Lazy<MARC::Record>] the records
|
|
63
|
+
def marc_records_for(query_value, freeze: false)
|
|
64
|
+
Enumerator.new do |y|
|
|
65
|
+
uri = sru_query_uri(query_value)
|
|
66
|
+
perform_query(uri, freeze: freeze) { |rec| y << rec }
|
|
67
|
+
end.lazy
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def perform_query(query_uri, start_record: nil, freeze: false, &block)
|
|
73
|
+
full_query_uri = full_query_uri_for(query_uri, start_record)
|
|
74
|
+
next_start_record = perform_single_query(full_query_uri, freeze, &block)
|
|
75
|
+
return unless next_start_record
|
|
76
|
+
|
|
77
|
+
perform_query(query_uri, start_record: next_start_record, freeze: freeze, &block)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def full_query_uri_for(query_uri, start_record)
|
|
81
|
+
return query_uri unless start_record
|
|
82
|
+
|
|
83
|
+
BerkeleyLibrary::Util::URIs.append(query_uri, "&startRecord=#{start_record}")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def perform_single_query(query_uri, freeze, &block)
|
|
87
|
+
return unless (xml = do_get(query_uri))
|
|
88
|
+
|
|
89
|
+
xml_reader = XMLReader.read(xml, freeze: freeze)
|
|
90
|
+
xml_reader.each(&block)
|
|
91
|
+
xml_reader.next_record_position
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def do_get(uri)
|
|
95
|
+
Util::URIs.get(uri, headers: { user_agent: DEFAULT_USER_AGENT })
|
|
96
|
+
rescue RestClient::Exception => e
|
|
97
|
+
logger.warn("GET #{uri} failed", e)
|
|
98
|
+
nil
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
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))
|