berkeley_library-alma 0.0.3 → 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 +22 -21
- data/.idea/inspectionProfiles/Project_Default.xml +2 -0
- data/CHANGES.md +16 -0
- data/README.md +43 -5
- data/berkeley_library-alma.gemspec +1 -1
- data/bin/alma-mms-lookup +1 -3
- data/lib/berkeley_library/alma/barcode.rb +33 -0
- data/lib/berkeley_library/alma/module_info.rb +1 -1
- data/lib/berkeley_library/alma/record_id.rb +8 -26
- 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 -0
- data/spec/data/C084093187-sru.xml +299 -0
- data/spec/data/availability-sru-page-1.xml +522 -0
- data/spec/data/availability-sru-page-2.xml +266 -0
- data/spec/data/availability-sru.xml +109 -0
- data/spec/data/{b11082434-sru.xml → b110824349-sru.xml} +0 -0
- data/spec/lib/berkeley_library/alma/barcode_spec.rb +67 -0
- data/spec/lib/berkeley_library/alma/bib_number_spec.rb +2 -1
- data/spec/lib/berkeley_library/alma/mms_id_spec.rb +3 -3
- data/spec/lib/berkeley_library/alma/record_id_spec.rb +20 -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 +6 -15
- metadata +24 -6
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,23 +12,23 @@
|
|
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
|
-
<orderEntry type="library" scope="PROVIDED" name="bundler (v2.
|
26
|
+
<orderEntry type="library" scope="PROVIDED" name="bundler (v2.1.4, RVM: ruby-2.7.5) [gem]" level="application" />
|
27
27
|
<orderEntry type="library" scope="PROVIDED" name="bundler-audit (v0.9.0.1, RVM: ruby-2.7.5) [gem]" level="application" />
|
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" />
|
@@ -38,16 +38,17 @@
|
|
38
38
|
<orderEntry type="library" scope="PROVIDED" name="hashdiff (v1.0.1, RVM: ruby-2.7.5) [gem]" level="application" />
|
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
|
-
<orderEntry type="library" scope="PROVIDED" name="i18n (v1.
|
42
|
-
<orderEntry type="library" scope="PROVIDED" name="lograge (v0.
|
43
|
-
<orderEntry type="library" scope="PROVIDED" name="loofah (v2.
|
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.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.8.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
48
49
|
<orderEntry type="library" scope="PROVIDED" name="minitest (v5.15.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
49
50
|
<orderEntry type="library" scope="PROVIDED" name="netrc (v0.11.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
50
|
-
<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" />
|
51
52
|
<orderEntry type="library" scope="PROVIDED" name="oj (v3.13.11, RVM: ruby-2.7.5) [gem]" level="application" />
|
52
53
|
<orderEntry type="library" scope="PROVIDED" name="ougai (v1.9.1, RVM: ruby-2.7.5) [gem]" level="application" />
|
53
54
|
<orderEntry type="library" scope="PROVIDED" name="parallel (v1.21.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
@@ -59,23 +60,23 @@
|
|
59
60
|
<orderEntry type="library" scope="PROVIDED" name="rack-test (v1.1.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
60
61
|
<orderEntry type="library" scope="PROVIDED" name="rails-dom-testing (v2.0.3, RVM: ruby-2.7.5) [gem]" level="application" />
|
61
62
|
<orderEntry type="library" scope="PROVIDED" name="rails-html-sanitizer (v1.4.2, RVM: ruby-2.7.5) [gem]" level="application" />
|
62
|
-
<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" />
|
63
64
|
<orderEntry type="library" scope="PROVIDED" name="rainbow (v3.1.1, RVM: ruby-2.7.5) [gem]" level="application" />
|
64
65
|
<orderEntry type="library" scope="PROVIDED" name="rake (v13.0.6, RVM: ruby-2.7.5) [gem]" level="application" />
|
65
66
|
<orderEntry type="library" scope="PROVIDED" name="regexp_parser (v2.2.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
66
67
|
<orderEntry type="library" scope="PROVIDED" name="request_store (v1.5.1, RVM: ruby-2.7.5) [gem]" level="application" />
|
67
68
|
<orderEntry type="library" scope="PROVIDED" name="rest-client (v2.1.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
68
69
|
<orderEntry type="library" scope="PROVIDED" name="rexml (v3.2.5, RVM: ruby-2.7.5) [gem]" level="application" />
|
69
|
-
<orderEntry type="library" scope="PROVIDED" name="rspec (v3.
|
70
|
-
<orderEntry type="library" scope="PROVIDED" name="rspec-core (v3.
|
71
|
-
<orderEntry type="library" scope="PROVIDED" name="rspec-expectations (v3.
|
72
|
-
<orderEntry type="library" scope="PROVIDED" name="rspec-mocks (v3.
|
73
|
-
<orderEntry type="library" scope="PROVIDED" name="rspec-support (v3.
|
70
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec (v3.11.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
71
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec-core (v3.11.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
72
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec-expectations (v3.11.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
73
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec-mocks (v3.11.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
74
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec-support (v3.11.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
74
75
|
<orderEntry type="library" scope="PROVIDED" name="rubocop (v1.11.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
75
76
|
<orderEntry type="library" scope="PROVIDED" name="rubocop-ast (v1.15.1, RVM: ruby-2.7.5) [gem]" level="application" />
|
76
77
|
<orderEntry type="library" scope="PROVIDED" name="rubocop-rake (v0.6.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
77
78
|
<orderEntry type="library" scope="PROVIDED" name="rubocop-rspec (v2.4.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
78
|
-
<orderEntry type="library" scope="PROVIDED" name="ruby-marc-spec (v0.1.
|
79
|
+
<orderEntry type="library" scope="PROVIDED" name="ruby-marc-spec (v0.1.3, RVM: ruby-2.7.5) [gem]" level="application" />
|
79
80
|
<orderEntry type="library" scope="PROVIDED" name="ruby-progressbar (v1.11.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
80
81
|
<orderEntry type="library" scope="PROVIDED" name="scrub_rb (v1.0.1, RVM: ruby-2.7.5) [gem]" level="application" />
|
81
82
|
<orderEntry type="library" scope="PROVIDED" name="simplecov (v0.21.2, RVM: ruby-2.7.5) [gem]" level="application" />
|
@@ -86,12 +87,12 @@
|
|
86
87
|
<orderEntry type="library" scope="PROVIDED" name="typesafe_enum (v0.3.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
87
88
|
<orderEntry type="library" scope="PROVIDED" name="tzinfo (v2.0.4, RVM: ruby-2.7.5) [gem]" level="application" />
|
88
89
|
<orderEntry type="library" scope="PROVIDED" name="unf (v0.1.4, RVM: ruby-2.7.5) [gem]" level="application" />
|
89
|
-
<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" />
|
90
91
|
<orderEntry type="library" scope="PROVIDED" name="unicode-display_width (v2.1.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
91
92
|
<orderEntry type="library" scope="PROVIDED" name="webmock (v3.14.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
92
93
|
<orderEntry type="library" scope="PROVIDED" name="webrick (v1.7.0, RVM: ruby-2.7.5) [gem]" level="application" />
|
93
94
|
<orderEntry type="library" scope="PROVIDED" name="yard (v0.9.27, RVM: ruby-2.7.5) [gem]" level="application" />
|
94
|
-
<orderEntry type="library" scope="PROVIDED" name="zeitwerk (v2.5.
|
95
|
+
<orderEntry type="library" scope="PROVIDED" name="zeitwerk (v2.5.4, RVM: ruby-2.7.5) [gem]" level="application" />
|
95
96
|
</component>
|
96
97
|
<component name="RModuleSettingsStorage">
|
97
98
|
<LOAD_PATH number="2" string0="$MODULE_DIR$/lib" string1="$MODULE_DIR$/spec" />
|
@@ -108,7 +109,7 @@
|
|
108
109
|
</RakeTaskImpl>
|
109
110
|
<RakeTaskImpl description="Run all specs in spec directory, with coverage" fullCommand="coverage" id="coverage" />
|
110
111
|
<RakeTaskImpl description="Run tests, check test coverage, check code style, check for vulnerabilities, build gem" fullCommand="default" id="default" />
|
111
|
-
<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" />
|
112
113
|
<RakeTaskImpl description="Run RuboCop with auto-correct, and output results to console" fullCommand="ra" id="ra" />
|
113
114
|
<RakeTaskImpl description="Run rubocop with HTML output" fullCommand="rubocop" id="rubocop" />
|
114
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
@@ -1,3 +1,19 @@
|
|
1
|
+
# 0.0.6 (5 May 2022)
|
2
|
+
|
3
|
+
- `SRU#get_marc_records` now supports paginated results.
|
4
|
+
|
5
|
+
# 0.0.5 (4 May 2022)
|
6
|
+
|
7
|
+
- extracts `SRU` module for performing SRU queries.
|
8
|
+
- MARCXML parsing now uses `MARC::NokogiriReader` rather than the `REXML` default.
|
9
|
+
|
10
|
+
# 0.0.4 (15 February 2022)
|
11
|
+
|
12
|
+
- adds `BarCode` subclass of `RecordId`, to look up bibliographic records by
|
13
|
+
item barcode. Note that since barcodes have no consistent format, `BarCode`
|
14
|
+
objects need to be constructed deliberately with `BarCode#new`, not with
|
15
|
+
`RecordId#parse`.
|
16
|
+
|
1
17
|
# 0.0.3 (3 February 2022)
|
2
18
|
|
3
19
|
- use `local_field_996` instead of `other_system_number` when making SRU queries
|
data/README.md
CHANGED
@@ -41,10 +41,34 @@ options to either:
|
|
41
41
|
|
42
42
|
## Retrieving Alma records
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
[SRU](
|
47
|
-
|
44
|
+
### Via `SRU`
|
45
|
+
|
46
|
+
The [`SRU`](lib/berkeley_library/alma/sru.rb) module encapsulates Alma
|
47
|
+
[SRU](https://developers.exlibrisgroup.com/alma/integrations/sru/) queries.
|
48
|
+
|
49
|
+
Retrieving MARC records:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
reader = BerkeleyLibrary::Alma::SRU.get_marc_records('991005668209706532', '991005668359706532')
|
53
|
+
# => #<MARC::XMLReader:0x0000000135b940e8 @freeze=false, @handle=#<StringIO:0x0000000135b94160>...
|
54
|
+
```
|
55
|
+
|
56
|
+
Making arbitrary SRU queries:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
BerkeleyLibrary::Alma::SRU.make_sru_query('alma.other_system_number=UCB-b123230470-01ucs_ber')
|
60
|
+
# => "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><searchRetrieveResponse xmlns=...
|
61
|
+
```
|
62
|
+
|
63
|
+
### Via `RecordId`
|
64
|
+
|
65
|
+
In addition, individual records can be retrieved from an instance of the
|
66
|
+
[`RecordId`](lib/berkeley_library/alma/record_id.rb) class, which encapsulates
|
67
|
+
an Alma MMS ID, Millennium bib number, or item barcode.
|
68
|
+
|
69
|
+
#### Initializing `RecordID` objects
|
70
|
+
|
71
|
+
Alma MMS ID:
|
48
72
|
|
49
73
|
```ruby
|
50
74
|
mms_id_str = '991054360089706532'
|
@@ -52,7 +76,7 @@ record_id = BerkeleyLibrary::Alma::RecordId.parse(mms_id_str)
|
|
52
76
|
# => #<BerkeleyLibrary::Alma::MMSID:0x0000000138949830 @institution="6532", @mms_id="991054360089706532", @type_prefix="99", @unique_part="105436008970">
|
53
77
|
```
|
54
78
|
|
55
|
-
|
79
|
+
Millennium bib number:
|
56
80
|
|
57
81
|
```ruby
|
58
82
|
bib_number_str = 'b11082434'
|
@@ -60,6 +84,20 @@ record_id = BerkeleyLibrary::Alma::RecordId.parse(bib_number_str)
|
|
60
84
|
# => #<BerkeleyLibrary::Alma::BibNumber:0x0000000118815038 @check_str="9", @digit_str="11082434">
|
61
85
|
```
|
62
86
|
|
87
|
+
Item barcode:
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
barcode_str = 'C084093187'
|
91
|
+
barcode = BerkeleyLibrary::Alma::BarCode.new(barcode_str)
|
92
|
+
# => #<BerkeleyLibrary::Alma::BarCode:0x000000013fac4c08 @barcode="C084093187">
|
93
|
+
```
|
94
|
+
|
95
|
+
⚠️ Note that because of the free-form nature of barcodes, they cannot be auto-detected,
|
96
|
+
and hence are **not** supported by the `RecordId#parse` method; they must be instantiated
|
97
|
+
directly.
|
98
|
+
|
99
|
+
#### Using `RecordId` objects to make SRU queries
|
100
|
+
|
63
101
|
Given a `RecordId` object, you can get the SRU query URI for the corresponding MARC record:
|
64
102
|
|
65
103
|
```ruby
|
@@ -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'
|
data/bin/alma-mms-lookup
CHANGED
@@ -25,9 +25,7 @@ require 'berkeley_library/alma'
|
|
25
25
|
# Configuration
|
26
26
|
|
27
27
|
# Configure Alma URLs etc.
|
28
|
-
BerkeleyLibrary::Alma.
|
29
|
-
Config.default!
|
30
|
-
end
|
28
|
+
BerkeleyLibrary::Alma::Config.default!
|
31
29
|
|
32
30
|
# Set log level
|
33
31
|
BerkeleyLibrary::Logging.logger.level = Logger::Severity::WARN
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'berkeley_library/alma/record_id'
|
2
|
+
|
3
|
+
module BerkeleyLibrary
|
4
|
+
module Alma
|
5
|
+
# {RecordId} subclass representing an item barcode.
|
6
|
+
class BarCode
|
7
|
+
include RecordId
|
8
|
+
|
9
|
+
attr_reader :barcode
|
10
|
+
|
11
|
+
# Initialize a barcode. Since we purchase barcodes of varied formats and accept vendor
|
12
|
+
# barcodes as well we are only validating whether it's a string or not.
|
13
|
+
def initialize(barcode)
|
14
|
+
string?(barcode)
|
15
|
+
@barcode = barcode
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns the SRU query value for this Barcode.
|
19
|
+
#
|
20
|
+
# @return [String] the Barcode query value
|
21
|
+
def sru_query_value
|
22
|
+
"alma.barcode=#{@barcode}"
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def string?(barcode)
|
28
|
+
raise ArgumentError, "Barcode must be a string: #{barcode.inspect}" unless barcode.is_a?(String)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -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
|
@@ -2,6 +2,7 @@ require 'berkeley_library/logging'
|
|
2
2
|
require 'berkeley_library/marc'
|
3
3
|
require 'berkeley_library/util/uris'
|
4
4
|
require 'berkeley_library/alma/constants'
|
5
|
+
require 'berkeley_library/alma/sru'
|
5
6
|
|
6
7
|
module BerkeleyLibrary
|
7
8
|
module Alma
|
@@ -22,6 +23,9 @@ module BerkeleyLibrary
|
|
22
23
|
# also accepts a {RecordId} and simply returns it, so it can be used in
|
23
24
|
# situations where it may not be clear whether the ID has already been parsed.
|
24
25
|
#
|
26
|
+
# **Note:** Use the {BarCode} class for barcodes, which don't have a consistent
|
27
|
+
# format and hence can't be auto-detected.
|
28
|
+
#
|
25
29
|
# @param id [String, RecordId] the ID to parse
|
26
30
|
# @return [RecordId, nil] an {MMSID} or {BibNumber}, depending on the type of ID,
|
27
31
|
# or `nil` if the specified `id` is neither an MMS ID nor a bib number
|
@@ -44,13 +48,7 @@ module BerkeleyLibrary
|
|
44
48
|
#
|
45
49
|
# @return [URI] the MARC URI
|
46
50
|
def marc_uri
|
47
|
-
|
48
|
-
'version' => '1.2',
|
49
|
-
'operation' => 'searchRetrieve',
|
50
|
-
'query' => sru_query_value
|
51
|
-
)
|
52
|
-
|
53
|
-
URIs.append(Config.alma_sru_base_uri, '?', query_string)
|
51
|
+
SRU.sru_query_uri(sru_query_value)
|
54
52
|
end
|
55
53
|
|
56
54
|
# Makes an SRU query for this record and returns a MARC record, or nil if the
|
@@ -62,8 +60,8 @@ module BerkeleyLibrary
|
|
62
60
|
# @return [MARC::Record, nil] the MARC record
|
63
61
|
# rubocop:disable Naming/AccessorMethodName
|
64
62
|
def get_marc_record
|
65
|
-
|
66
|
-
logger.warn("GET #{marc_uri} did not return a MARC record") unless (marc_record =
|
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)
|
67
65
|
marc_record
|
68
66
|
end
|
69
67
|
# rubocop:enable Naming/AccessorMethodName
|
@@ -74,10 +72,7 @@ module BerkeleyLibrary
|
|
74
72
|
# @return [String, nil] the SRU query response body, or nil in the event of an error.
|
75
73
|
# rubocop:disable Naming/AccessorMethodName
|
76
74
|
def get_marc_xml
|
77
|
-
|
78
|
-
rescue RestClient::Exception => e
|
79
|
-
logger.warn("GET #{marc_uri} failed", e)
|
80
|
-
nil
|
75
|
+
SRU.make_sru_query(sru_query_value)
|
81
76
|
end
|
82
77
|
# rubocop:enable Naming/AccessorMethodName
|
83
78
|
|
@@ -95,19 +90,6 @@ module BerkeleyLibrary
|
|
95
90
|
|
96
91
|
to_s <=> other.to_s
|
97
92
|
end
|
98
|
-
|
99
|
-
# ------------------------------------------------------------
|
100
|
-
# Private methods
|
101
|
-
|
102
|
-
private
|
103
|
-
|
104
|
-
def parse_marc_xml(xml)
|
105
|
-
return unless xml
|
106
|
-
|
107
|
-
input = StringIO.new(xml.scrub)
|
108
|
-
reader = MARC::XMLReader.new(input)
|
109
|
-
reader.first
|
110
|
-
end
|
111
93
|
end
|
112
94
|
end
|
113
95
|
end
|
@@ -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
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir.glob(File.expand_path('sru/*.rb', __dir__)).sort.each(&method(:require))
|