berkeley_library-av-core 0.4.0
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 +7 -0
- data/.dockerignore +391 -0
- data/.github/workflows/build.yml +30 -0
- data/.gitignore +388 -0
- data/.idea/av_core.iml +146 -0
- data/.idea/codeStyles/Project.xml +12 -0
- data/.idea/codeStyles/codeStyleConfig.xml +5 -0
- data/.idea/go.imports.xml +6 -0
- data/.idea/inspectionProfiles/Project_Default.xml +37 -0
- data/.idea/misc.xml +6 -0
- data/.idea/modules.xml +8 -0
- data/.idea/vcs.xml +6 -0
- data/.rubocop.yml +241 -0
- data/.ruby-version +1 -0
- data/.simplecov +8 -0
- data/CHANGES.md +38 -0
- data/Gemfile +3 -0
- data/Jenkinsfile +16 -0
- data/LICENSE.md +21 -0
- data/README.md +20 -0
- data/Rakefile +20 -0
- data/av_core.gemspec +49 -0
- data/lib/berkeley_library/av/config.rb +238 -0
- data/lib/berkeley_library/av/constants.rb +30 -0
- data/lib/berkeley_library/av/core/module_info.rb +18 -0
- data/lib/berkeley_library/av/core.rb +7 -0
- data/lib/berkeley_library/av/marc/util.rb +114 -0
- data/lib/berkeley_library/av/marc.rb +52 -0
- data/lib/berkeley_library/av/metadata/README.md +5 -0
- data/lib/berkeley_library/av/metadata/field.rb +110 -0
- data/lib/berkeley_library/av/metadata/fields.rb +130 -0
- data/lib/berkeley_library/av/metadata/link.rb +28 -0
- data/lib/berkeley_library/av/metadata/readers/alma.rb +54 -0
- data/lib/berkeley_library/av/metadata/readers/base.rb +53 -0
- data/lib/berkeley_library/av/metadata/readers/tind.rb +52 -0
- data/lib/berkeley_library/av/metadata/readers.rb +2 -0
- data/lib/berkeley_library/av/metadata/source.rb +93 -0
- data/lib/berkeley_library/av/metadata/tind_html_metadata_da.json +2076 -0
- data/lib/berkeley_library/av/metadata/value.rb +121 -0
- data/lib/berkeley_library/av/metadata.rb +103 -0
- data/lib/berkeley_library/av/record.rb +86 -0
- data/lib/berkeley_library/av/record_id.rb +121 -0
- data/lib/berkeley_library/av/record_not_found.rb +7 -0
- data/lib/berkeley_library/av/restrictions.rb +36 -0
- data/lib/berkeley_library/av/track.rb +132 -0
- data/lib/berkeley_library/av/types/duration.rb +67 -0
- data/lib/berkeley_library/av/types/file_type.rb +84 -0
- data/lib/berkeley_library/av/util.rb +65 -0
- data/rakelib/bundle.rake +8 -0
- data/rakelib/coverage.rake +11 -0
- data/rakelib/gem.rake +54 -0
- data/rakelib/rubocop.rake +18 -0
- data/rakelib/spec.rake +12 -0
- data/spec/.rubocop.yml +116 -0
- data/spec/data/10.23.19.JessieLaCavalier.02.mrc +3 -0
- data/spec/data/alma/991005939359706532-sru.xml +123 -0
- data/spec/data/alma/991034756419706532-sru.xml +162 -0
- data/spec/data/alma/991047179369706532-sru.xml +210 -0
- data/spec/data/alma/991054360089706532-sru.xml +186 -0
- data/spec/data/alma/b11082434-sru.xml +165 -0
- data/spec/data/alma/b18538031-sru.xml +123 -0
- data/spec/data/alma/b20786580-sru.xml +123 -0
- data/spec/data/alma/b22139647-sru.xml +171 -0
- data/spec/data/alma/b22139658-sru.xml +282 -0
- data/spec/data/alma/b23161018-sru.xml +182 -0
- data/spec/data/alma/b23305522-sru.xml +144 -0
- data/spec/data/alma/b24071548-sru.xml +136 -0
- data/spec/data/alma/b24659129-sru.xml +210 -0
- data/spec/data/alma/b25207857-sru.xml +217 -0
- data/spec/data/alma/b25716973-sru.xml +186 -0
- data/spec/data/alma/b25742488-sru.xml +246 -0
- data/spec/data/record-(cityarts)00002.xml +78 -0
- data/spec/data/record-(cityarts)00773.xml +94 -0
- data/spec/data/record-(clir)00020.xml +153 -0
- data/spec/data/record-(miscmat)00615.xml +45 -0
- data/spec/data/record-(pacradio)00107.xml +85 -0
- data/spec/data/record-(pacradio)01469.xml +82 -0
- data/spec/data/record-empty-result.xml +4 -0
- data/spec/data/record-multiple-998s-disordered.xml +178 -0
- data/spec/data/record-multiple-998s.xml +178 -0
- data/spec/data/record-physcolloquia-bk00169017b.xml +78 -0
- data/spec/data/record-ragged-998-subfields.xml +122 -0
- data/spec/data/record-ragged-998s-multiple-fields.xml +160 -0
- data/spec/data/record-redirect-to-login.html +288 -0
- data/spec/data/record_id/bibs_with_check_digits.txt +151 -0
- data/spec/data/search-1993.xml +158 -0
- data/spec/data/search-b23305516.xml +81 -0
- data/spec/lib/berkeley_library/av/av_spec.rb +12 -0
- data/spec/lib/berkeley_library/av/config_spec.rb +250 -0
- data/spec/lib/berkeley_library/av/marc/util_spec.rb +150 -0
- data/spec/lib/berkeley_library/av/marc_spec.rb +62 -0
- data/spec/lib/berkeley_library/av/metadata/field_spec.rb +81 -0
- data/spec/lib/berkeley_library/av/metadata/fields_spec.rb +180 -0
- data/spec/lib/berkeley_library/av/metadata/metadata_spec.rb +274 -0
- data/spec/lib/berkeley_library/av/metadata/source_spec.rb +261 -0
- data/spec/lib/berkeley_library/av/metadata/value_spec.rb +29 -0
- data/spec/lib/berkeley_library/av/record_id_spec.rb +72 -0
- data/spec/lib/berkeley_library/av/record_spec.rb +284 -0
- data/spec/lib/berkeley_library/av/track_spec.rb +335 -0
- data/spec/lib/berkeley_library/av/types/duration_spec.rb +91 -0
- data/spec/lib/berkeley_library/av/types/file_type_spec.rb +98 -0
- data/spec/lib/berkeley_library/av/util_spec.rb +30 -0
- data/spec/spec_helper.rb +63 -0
- metadata +499 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module BerkeleyLibrary
|
|
2
|
+
module AV
|
|
3
|
+
module Core
|
|
4
|
+
class ModuleInfo
|
|
5
|
+
NAME = 'berkeley_library-av-core'.freeze
|
|
6
|
+
AUTHOR = 'David Moles'.freeze
|
|
7
|
+
AUTHOR_EMAIL = 'dmoles@berkeley.edu'.freeze
|
|
8
|
+
SUMMARY = 'UC Berkeley Library audio/video core code'.freeze
|
|
9
|
+
DESCRIPTION = 'Gem for UC Berkeley Library shared audio/video code'.freeze
|
|
10
|
+
LICENSE = 'MIT'.freeze
|
|
11
|
+
VERSION = '0.4.0'.freeze
|
|
12
|
+
HOMEPAGE = 'https://git.lib.berkeley.edu/lap/av_core'.freeze
|
|
13
|
+
|
|
14
|
+
private_class_method :new
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
require 'berkeley_library/av/config'
|
|
2
|
+
require 'berkeley_library/av/marc'
|
|
3
|
+
require 'berkeley_library/av/metadata'
|
|
4
|
+
require 'berkeley_library/av/record'
|
|
5
|
+
require 'berkeley_library/av/record_id'
|
|
6
|
+
require 'berkeley_library/av/track'
|
|
7
|
+
require 'berkeley_library/av/util'
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
require 'marc'
|
|
2
|
+
require 'berkeley_library/av/util'
|
|
3
|
+
|
|
4
|
+
module BerkeleyLibrary
|
|
5
|
+
module AV
|
|
6
|
+
module Marc
|
|
7
|
+
module Util
|
|
8
|
+
include AV::Util
|
|
9
|
+
|
|
10
|
+
# Extracts the subfield values from the specifed MARC data field and
|
|
11
|
+
# returns the groups of related subfield values in order as they
|
|
12
|
+
# appear. For instance, for the following data field:
|
|
13
|
+
#
|
|
14
|
+
# ```
|
|
15
|
+
# 505 00$tQuatrain II$g(16:35) --$tWater ways$g(1:57) --$tWaves$g(10:49).
|
|
16
|
+
# ```
|
|
17
|
+
#
|
|
18
|
+
# this method would return:
|
|
19
|
+
#
|
|
20
|
+
# ```
|
|
21
|
+
# [
|
|
22
|
+
# {t: 'Quatrain II', g: '(16:35)'},
|
|
23
|
+
# {t: 'Water ways', g: '(01:57)'},
|
|
24
|
+
# {t: 'Waves', g: '(10:49)'}
|
|
25
|
+
# ]
|
|
26
|
+
# ```
|
|
27
|
+
#
|
|
28
|
+
# If an order is provided, each group will be reordered according to
|
|
29
|
+
# that order. E.g., given the order `[:g, :t]`, the above would be
|
|
30
|
+
# returned instead as:
|
|
31
|
+
#
|
|
32
|
+
# ```
|
|
33
|
+
# [
|
|
34
|
+
# {g: '(16:35)', t: 'Quatrain II'},
|
|
35
|
+
# {g: '(01:57)', t: 'Water ways'},
|
|
36
|
+
# {g: '(10:49)', t: 'Waves'},
|
|
37
|
+
# ]
|
|
38
|
+
# ```
|
|
39
|
+
#
|
|
40
|
+
# @param data_field [MARC::DataField] the data field
|
|
41
|
+
# @param order [Array<Symbol>, nil] the order of subfield codes
|
|
42
|
+
# @return [Array<Hash<Symbol, String>>] the grouped values
|
|
43
|
+
def group_subfield_values(data_field, order: nil)
|
|
44
|
+
# TODO: do we still need this?
|
|
45
|
+
grouped_subfields = group_subfields(data_field.subfields, order:)
|
|
46
|
+
grouped_subfields.each_with_object([]) do |subfield_group, value_groups|
|
|
47
|
+
value_group = subfield_group.transform_values { |sf| tidy_value(sf.value) }
|
|
48
|
+
value_groups << value_group
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Extracts the subfieldsfrom the specifed MARC data field and
|
|
53
|
+
# returns the groups of related subfieldsin order as they
|
|
54
|
+
# appear. For instance, for the following data field:
|
|
55
|
+
#
|
|
56
|
+
# ```
|
|
57
|
+
# 505 00$tQuatrain II$g(16:35) --$tWater ways$g(1:57) --$tWaves$g(10:49).
|
|
58
|
+
# ```
|
|
59
|
+
#
|
|
60
|
+
# this method would return:
|
|
61
|
+
#
|
|
62
|
+
# ```
|
|
63
|
+
# [
|
|
64
|
+
# {t: #<Subfield @code='t', @value='Quatrain II'>, g: #<Subfield @code='g', @value='(16:35)'>},
|
|
65
|
+
# {t: #<Subfield @code='t', @value='Water ways'>, g: #<Subfield @code='g', @value='(01:57)'>},
|
|
66
|
+
# {t: #<Subfield @code='t', @value='Waves'>, g: #<Subfield @code='g', @value='(10:49)'>}
|
|
67
|
+
# ]
|
|
68
|
+
# ```
|
|
69
|
+
#
|
|
70
|
+
# If an order is provided, each group will be reordered according to
|
|
71
|
+
# that order. E.g., given the order `[:g, :t]`, the above would be
|
|
72
|
+
# returned instead as:
|
|
73
|
+
#
|
|
74
|
+
# ```
|
|
75
|
+
# [
|
|
76
|
+
# {g: #<Subfield @code='g', @value='(16:35)'>, t: #<Subfield @code='t', @value='Quatrain II'>},
|
|
77
|
+
# {g: #<Subfield @code='g', @value='(01:57)'>, t: #<Subfield @code='t', @value='Water ways'>},
|
|
78
|
+
# {g: #<Subfield @code='g', @value='(10:49)'>, t: #<Subfield @code='t', @value='Waves'>},
|
|
79
|
+
# ]
|
|
80
|
+
# ```
|
|
81
|
+
#
|
|
82
|
+
# @param data_field [MARC::DataField] the data field
|
|
83
|
+
# @param order [Array<Symbol>, nil] the order of subfield codes
|
|
84
|
+
# @return [Array<Hash<Symbol, String>>] the grouped values
|
|
85
|
+
def group_subfields(subfields, order: nil)
|
|
86
|
+
by_code = subfields_by_code(subfields)
|
|
87
|
+
order = by_code.keys if order.nil? || order.empty?
|
|
88
|
+
group_by_code(by_code, order)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def subfields_by_code(subfields)
|
|
94
|
+
subfields.each_with_object({}) do |sf, h|
|
|
95
|
+
(h[sf.code.to_sym] ||= []) << sf
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def group_by_code(subfields_by_code, order)
|
|
100
|
+
order.each_with_object([]) do |code, groups|
|
|
101
|
+
csym = code.to_sym
|
|
102
|
+
next unless (subfields = subfields_by_code[csym])
|
|
103
|
+
|
|
104
|
+
subfields.each_with_index { |v, i| (groups[i] ||= {})[csym] = v }
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
class << self
|
|
109
|
+
include AV::Marc::Util
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require 'marc'
|
|
2
|
+
|
|
3
|
+
module BerkeleyLibrary
|
|
4
|
+
module AV
|
|
5
|
+
module Marc
|
|
6
|
+
class << self
|
|
7
|
+
# Parses MARCXML.
|
|
8
|
+
#
|
|
9
|
+
# @param xml [String] the XML to parse
|
|
10
|
+
# @return [MARC::Record, nil] the MARC record from the specified XML
|
|
11
|
+
def from_xml(xml)
|
|
12
|
+
# noinspection RubyYardReturnMatch,RubyMismatchedReturnType
|
|
13
|
+
all_from_xml(xml).first
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Parses MARCXML.
|
|
17
|
+
#
|
|
18
|
+
# @param xml [String] the XML to parse
|
|
19
|
+
# @return [MARC::XMLReader] the MARC records
|
|
20
|
+
def all_from_xml(xml)
|
|
21
|
+
input = StringIO.new(xml.scrub)
|
|
22
|
+
MARC::XMLReader.new(input)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Returns a reader for the specified MARC file.
|
|
26
|
+
#
|
|
27
|
+
# @param marc_path [String] the path to a MARC file in XML or binary format
|
|
28
|
+
# @param external_encoding [String] the encoding, for binary files
|
|
29
|
+
# @return [MARC::XMLReader] if the file path ends in `.xml`
|
|
30
|
+
# @return [MARC::Reader] if the file path ends in `.mrc`
|
|
31
|
+
def reader_for(marc_path, external_encoding: 'MARC-8')
|
|
32
|
+
downcased_path = marc_path.downcase
|
|
33
|
+
return MARC::XMLReader.new(marc_path) if downcased_path.end_with?('.xml')
|
|
34
|
+
return MARC::Reader.new(marc_path, external_encoding:) if downcased_path.end_with?('.mrc')
|
|
35
|
+
|
|
36
|
+
raise ArgumentError, "Unable to determine reader needed for MARC file #{marc_path.inspect}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns a MARC record read from the specified file (or the first record
|
|
40
|
+
# if the file contains multiple records).
|
|
41
|
+
#
|
|
42
|
+
# @param marc_path [String] the path to a MARC file in XML or binary format
|
|
43
|
+
# @param external_encoding [String] the encoding, for binary files
|
|
44
|
+
# @return [MARC::Record] the MARC record
|
|
45
|
+
def read(marc_path, external_encoding: 'MARC-8')
|
|
46
|
+
# noinspection RubyYardReturnMatch
|
|
47
|
+
reader_for(marc_path, external_encoding:).first
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# `tind_html_metadata_da.json`
|
|
2
|
+
|
|
3
|
+
This file contains a snapshot of JSON data extracted from the HTML source of the
|
|
4
|
+
[TIND Record Page Configuration Interface](https://digicoll.lib.berkeley.edu/displays/?ln=en§ion=tind_html_metadata_da),
|
|
5
|
+
defining the labels, visibility, and order of the MARC fields on the TIND record display page.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
require 'berkeley_library/av/constants'
|
|
2
|
+
require 'berkeley_library/av/marc/util'
|
|
3
|
+
require 'berkeley_library/av/metadata/value'
|
|
4
|
+
require 'marc/spec'
|
|
5
|
+
|
|
6
|
+
module BerkeleyLibrary
|
|
7
|
+
module AV
|
|
8
|
+
class Metadata
|
|
9
|
+
class Field
|
|
10
|
+
include AV::Marc::Util
|
|
11
|
+
include Comparable
|
|
12
|
+
|
|
13
|
+
# ------------------------------------------------------------
|
|
14
|
+
# Constants
|
|
15
|
+
|
|
16
|
+
SPEC_TAG_RE = /^([0-9a-z.]{3})/
|
|
17
|
+
|
|
18
|
+
# ------------------------------------------------------------
|
|
19
|
+
# Accessors
|
|
20
|
+
|
|
21
|
+
attr_reader :order, :label, :tag, :spec, :query, :subfields_separator, :subfield_order
|
|
22
|
+
|
|
23
|
+
# ------------------------------------------------------------
|
|
24
|
+
# Initializer
|
|
25
|
+
|
|
26
|
+
# rubocop:disable Metrics/ParameterLists
|
|
27
|
+
def initialize(order:, label:, spec:, tag: nil, subfields_separator: ' ', subfield_order: [])
|
|
28
|
+
@order = order
|
|
29
|
+
@label = label
|
|
30
|
+
@spec = spec
|
|
31
|
+
@query = MARC::Spec.parse_query(spec)
|
|
32
|
+
@tag = tag || query.tag_str
|
|
33
|
+
@subfields_separator = subfields_separator
|
|
34
|
+
@subfield_order = subfield_order
|
|
35
|
+
end
|
|
36
|
+
# rubocop:enable Metrics/ParameterLists
|
|
37
|
+
|
|
38
|
+
# ------------------------------------------------------------
|
|
39
|
+
# Public methods
|
|
40
|
+
|
|
41
|
+
def value_from(marc_record)
|
|
42
|
+
results = MARC::Spec.execute_query(query, marc_record)
|
|
43
|
+
subfield_groups = subfield_groups_from_result(results)
|
|
44
|
+
Value.value_for(self, subfield_groups)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def same_metadata?(other)
|
|
48
|
+
raise ArgumentError, "Not a #{class_name(self)}: #{other}" unless other.is_a?(Field)
|
|
49
|
+
|
|
50
|
+
%i[tag query subfields_separator subfield_order].all? do |attr|
|
|
51
|
+
(other.respond_to?(attr) && send(attr) == other.send(attr))
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# ------------------------------
|
|
56
|
+
# Object
|
|
57
|
+
|
|
58
|
+
def to_s
|
|
59
|
+
"#{order}. #{query} #{label.inspect}".tap do |str|
|
|
60
|
+
str << " #{subfields_separator.inspect}" unless subfields_separator == ' '
|
|
61
|
+
str << " $#{subfield_order.join('$')}" unless subfield_order.empty?
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def inspect
|
|
66
|
+
"#<#{class_name(self)} #{self}>"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def hash
|
|
70
|
+
sort_attrs.map { |a| send(a) }.hash
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# ------------------------------
|
|
74
|
+
# Comparable
|
|
75
|
+
|
|
76
|
+
def <=>(other)
|
|
77
|
+
compare_by_attributes(self, other, *sort_attrs)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# ------------------------------------------------------------
|
|
81
|
+
# Private
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def sort_attrs
|
|
86
|
+
%i[order tag query subfields_separator label subfield_order]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def subfield_groups_from_result(marc_result)
|
|
90
|
+
if marc_result.is_a?(Array)
|
|
91
|
+
return [] if marc_result.empty?
|
|
92
|
+
return marc_result.map { |r| subfield_groups_from_result(r) }.flatten unless marc_result[0].is_a?(MARC::Subfield)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
subfields = subfields_from_result(marc_result)
|
|
96
|
+
group_subfields(subfields, order: subfield_order)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def subfields_from_result(marc_result)
|
|
100
|
+
return marc_result if marc_result.is_a?(Array) && marc_result[0].is_a?(MARC::Subfield)
|
|
101
|
+
return [marc_result] if marc_result.is_a?(MARC::Subfield)
|
|
102
|
+
return [] unless marc_result.respond_to?(:subfields)
|
|
103
|
+
|
|
104
|
+
marc_result.subfields
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
require 'berkeley_library/av/metadata/field'
|
|
2
|
+
|
|
3
|
+
module BerkeleyLibrary
|
|
4
|
+
module AV
|
|
5
|
+
class Metadata
|
|
6
|
+
module Fields
|
|
7
|
+
include AV::Constants
|
|
8
|
+
|
|
9
|
+
TITLE = Field.new(order: 1, label: 'Title', spec: "#{TAG_TITLE_FIELD}$a")
|
|
10
|
+
|
|
11
|
+
DESCRIPTION = Field.new(order: 2, label: 'Description', spec: '520$a')
|
|
12
|
+
CREATOR_PERSONAL = Field.new(order: 2, label: 'Creator', spec: '700')
|
|
13
|
+
CREATOR_CORPORATE = Field.new(order: 2, label: 'Creator', spec: '710')
|
|
14
|
+
TRACKS = Field.new(order: 99, label: 'Tracks', spec: TAG_TRACK_FIELD, subfield_order: %w[g t a])
|
|
15
|
+
CATALOG_LINK = Field.new(order: 999, label: 'Linked Resources', spec: "#{TAG_LINK_FIELD}{^1=\\4}{^2=\\1}")
|
|
16
|
+
|
|
17
|
+
STANDARD_FIELDS = [
|
|
18
|
+
TITLE,
|
|
19
|
+
DESCRIPTION,
|
|
20
|
+
CREATOR_PERSONAL,
|
|
21
|
+
CREATOR_CORPORATE,
|
|
22
|
+
TRACKS,
|
|
23
|
+
CATALOG_LINK
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
26
|
+
TIND_CONFIG = File.join(__dir__, 'tind_html_metadata_da.json')
|
|
27
|
+
|
|
28
|
+
REQ_ATTRS = %w[visible params labels order].freeze
|
|
29
|
+
TAG_ATTRS = %w[tag fields tag_1 tag_2].freeze
|
|
30
|
+
TAG_RE = /(?<tag>[0-9]{3})(?<ind1>[a-z0-9_%])(?<ind2>[a-z0-9_%])(?<subfield>[a-z0-9])?/
|
|
31
|
+
|
|
32
|
+
class << self
|
|
33
|
+
|
|
34
|
+
def default_fields
|
|
35
|
+
@default_fields ||= begin
|
|
36
|
+
default_fields = STANDARD_FIELDS + from_tind_config(TIND_CONFIG)
|
|
37
|
+
unique_by_metadata(default_fields)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def from_tind_config(json_config)
|
|
42
|
+
json_config_hash = ensure_hash(json_config)
|
|
43
|
+
json_config_hash['config'].filter_map { |jf| to_field(jf) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def default_values_from(marc_record)
|
|
47
|
+
default_fields.each_with_object({}) do |f, vv|
|
|
48
|
+
field_value = f.value_from(marc_record)
|
|
49
|
+
vv[f] = field_value if field_value
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
# @return [Hash] the config hash
|
|
56
|
+
def ensure_hash(json_config)
|
|
57
|
+
json_config = File.read(json_config) if File.file?(json_config)
|
|
58
|
+
json_config.is_a?(Hash) ? json_config : JSON.parse(json_config)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# rubocop:disable Metrics/MethodLength
|
|
62
|
+
def to_field(json_field)
|
|
63
|
+
return unless can_display?(json_field)
|
|
64
|
+
|
|
65
|
+
params = json_field['params']
|
|
66
|
+
tag, marc_spec = tag_and_spec_from(params)
|
|
67
|
+
return unless tag
|
|
68
|
+
|
|
69
|
+
Field.new(
|
|
70
|
+
order: json_field['order'].to_i,
|
|
71
|
+
label: json_field['labels']['en'],
|
|
72
|
+
tag:,
|
|
73
|
+
spec: marc_spec,
|
|
74
|
+
subfields_separator: (params['subfields_separator'] || ' '),
|
|
75
|
+
subfield_order: params['subfield_order'].to_s.split(',')
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
# rubocop:enable Metrics/MethodLength
|
|
79
|
+
|
|
80
|
+
def unique_by_metadata(fields)
|
|
81
|
+
fields.sort.each_with_object([]) do |f, uniques|
|
|
82
|
+
already_present = uniques.any? { |u| u.same_metadata?(f) }
|
|
83
|
+
uniques << f unless already_present
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def can_display?(json_field)
|
|
88
|
+
REQ_ATTRS.all? { |f| json_field[f] } &&
|
|
89
|
+
json_field['labels'].key?('en') &&
|
|
90
|
+
json_field['machine_name'] != 'local_245_880_linking' # extra title
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def tag_and_spec_from(params)
|
|
94
|
+
tag_and_spec = TAG_ATTRS.lazy.filter_map { |attr| parse_tag_and_spec(params[attr]) }.first
|
|
95
|
+
return tag_and_spec if tag_and_spec
|
|
96
|
+
|
|
97
|
+
return unless (input_tag = params['input_tag'])
|
|
98
|
+
|
|
99
|
+
input_subfield = params['input_subfield']
|
|
100
|
+
parse_tag_and_spec("#{input_tag}#{input_subfield}")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def parse_tag_and_spec(val)
|
|
104
|
+
return unless (md = TAG_RE.match(val))
|
|
105
|
+
|
|
106
|
+
tag = md[:tag]
|
|
107
|
+
[tag, to_marc_spec(tag, md[:ind1], md[:ind2], md[:subfield])]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def to_marc_spec(tag, ind1_val, ind2_val, subfield)
|
|
111
|
+
[
|
|
112
|
+
tag,
|
|
113
|
+
to_subfield_spec(subfield),
|
|
114
|
+
to_ind_spec(1, ind1_val),
|
|
115
|
+
to_ind_spec(2, ind2_val)
|
|
116
|
+
].join
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def to_subfield_spec(sf)
|
|
120
|
+
"$#{sf}" if sf
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def to_ind_spec(i, ind_val)
|
|
124
|
+
"{^#{i}=\\#{ind_val}}" unless [nil, '', '%', '_'].include?(ind_val)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module BerkeleyLibrary
|
|
2
|
+
module AV
|
|
3
|
+
class Metadata
|
|
4
|
+
class Link
|
|
5
|
+
include Comparable
|
|
6
|
+
|
|
7
|
+
attr_reader :body, :url
|
|
8
|
+
|
|
9
|
+
def initialize(body:, url:)
|
|
10
|
+
@body = body
|
|
11
|
+
@url = url
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_s
|
|
15
|
+
"[#{body}](#{url})"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def <=>(other)
|
|
19
|
+
return unless other
|
|
20
|
+
return 0 if equal?(other)
|
|
21
|
+
return unless other.is_a?(Link)
|
|
22
|
+
|
|
23
|
+
to_s <=> other.to_s
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require 'berkeley_library/util/uris'
|
|
2
|
+
|
|
3
|
+
require 'berkeley_library/av/config'
|
|
4
|
+
require 'berkeley_library/av/record_id'
|
|
5
|
+
require 'berkeley_library/av/metadata/readers/base'
|
|
6
|
+
|
|
7
|
+
module BerkeleyLibrary
|
|
8
|
+
module AV
|
|
9
|
+
class Metadata
|
|
10
|
+
module Readers
|
|
11
|
+
module Alma
|
|
12
|
+
include Base
|
|
13
|
+
|
|
14
|
+
def marc_uri_for(record_id)
|
|
15
|
+
query_string = URI.encode_www_form(
|
|
16
|
+
'version' => '1.2',
|
|
17
|
+
'operation' => 'searchRetrieve',
|
|
18
|
+
'query' => sru_query_value_for(record_id)
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
URIs.append(AV::Config.alma_sru_base_uri, '?', query_string)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
protected
|
|
25
|
+
|
|
26
|
+
def _display_uri_for(record_id)
|
|
27
|
+
URIs.append(AV::Config.alma_permalink_base_uri, "alma#{record_id}")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def sru_query_value_for(record_id)
|
|
33
|
+
id_type = AV::RecordId::Type.for_id(record_id)
|
|
34
|
+
return "alma.mms_id=#{record_id}" if id_type == AV::RecordId::Type::ALMA
|
|
35
|
+
return millennium_query_value(record_id) if id_type == AV::RecordId::Type::MILLENNIUM
|
|
36
|
+
|
|
37
|
+
raise ArgumentError, "Invalid record type: #{id_type}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def millennium_query_value(bib_number)
|
|
41
|
+
other_system_number = other_system_number_for_bib(bib_number)
|
|
42
|
+
"alma.other_system_number=#{other_system_number}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def other_system_number_for_bib(bib_number)
|
|
46
|
+
bib_number = RecordId.ensure_check_digit(bib_number)
|
|
47
|
+
"UCB-#{bib_number}-#{AV::Config.alma_institution_code.downcase}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require 'berkeley_library/av/util'
|
|
2
|
+
require 'berkeley_library/av/record_not_found'
|
|
3
|
+
|
|
4
|
+
module BerkeleyLibrary
|
|
5
|
+
module AV
|
|
6
|
+
class Metadata
|
|
7
|
+
module Readers
|
|
8
|
+
module Base
|
|
9
|
+
include AV::Util
|
|
10
|
+
|
|
11
|
+
# @return [MARC::Record] the MARC record
|
|
12
|
+
def record_for(record_id)
|
|
13
|
+
first_record_for(record_id)
|
|
14
|
+
rescue AV::RecordNotFound
|
|
15
|
+
raise
|
|
16
|
+
rescue StandardError => e
|
|
17
|
+
raise not_found(record_id, e.message)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
# @return [MARC::Record] the MARC record
|
|
23
|
+
def first_record_for(record_id)
|
|
24
|
+
marc_uri = marc_uri_for(record_id)
|
|
25
|
+
record_from(marc_uri, record_id)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def record_from(marc_uri, record_id)
|
|
29
|
+
xml = do_get(marc_uri)
|
|
30
|
+
AV::Marc.from_xml(xml).tap do |record|
|
|
31
|
+
raise not_found(record_id, "GET #{marc_uri} returned: #{xml}", marc_uri:) unless record
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def not_found(record_id, details, marc_uri: nil)
|
|
36
|
+
msg = "Can't find #{name} record for ID #{record_id}: #{details}."
|
|
37
|
+
|
|
38
|
+
marc_uri_msg = marc_uri_message(record_id, marc_uri)
|
|
39
|
+
msg = [msg, marc_uri_msg].join(' ') if marc_uri_msg
|
|
40
|
+
|
|
41
|
+
AV::RecordNotFound.new(msg)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def marc_uri_message(record_id, marc_uri)
|
|
45
|
+
" MARC URI: #{marc_uri | marc_uri_for(record_id)}"
|
|
46
|
+
rescue StandardError
|
|
47
|
+
# nil
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require 'berkeley_library/util/uris'
|
|
2
|
+
|
|
3
|
+
require 'berkeley_library/av/config'
|
|
4
|
+
require 'berkeley_library/av/record_id'
|
|
5
|
+
require 'berkeley_library/av/metadata/readers/base'
|
|
6
|
+
|
|
7
|
+
module BerkeleyLibrary
|
|
8
|
+
module AV
|
|
9
|
+
class Metadata
|
|
10
|
+
module Readers
|
|
11
|
+
module TIND
|
|
12
|
+
include Base
|
|
13
|
+
|
|
14
|
+
TIND_ID_FIELD = '035__a'.freeze
|
|
15
|
+
|
|
16
|
+
ID_FIELDS = {
|
|
17
|
+
AV::RecordId::Type::MILLENNIUM => '901__m'.freeze,
|
|
18
|
+
AV::RecordId::Type::OCLC => '901__o'.freeze
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
def marc_uri_for(record_id)
|
|
22
|
+
id_field = id_field_for(record_id)
|
|
23
|
+
query_string = URI.encode_www_form(
|
|
24
|
+
'p' => "#{id_field}:\"#{record_id}\"",
|
|
25
|
+
'of' => 'xm'
|
|
26
|
+
)
|
|
27
|
+
URIs.append(base_uri, 'search', '?', query_string)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
protected
|
|
31
|
+
|
|
32
|
+
def _display_uri_for(record_id)
|
|
33
|
+
URIs.append(base_uri, 'record', record_id)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def base_uri
|
|
39
|
+
AV::Config.tind_base_uri
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def id_field_for(record_id)
|
|
43
|
+
id_type = AV::RecordId::Type.for_id(record_id)
|
|
44
|
+
raise ArgumentError, "Can't look up Alma record #{record_id} in TIND" if id_type == AV::RecordId::Type::ALMA
|
|
45
|
+
|
|
46
|
+
ID_FIELDS[id_type] || TIND_ID_FIELD
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|