cocina-models 0.93.0 → 0.93.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +12 -12
- data/README.md +2 -0
- data/lib/cocina/models/builders/title_builder.rb +132 -42
- data/lib/cocina/models/mapping/from_mods/contributor.rb +2 -2
- data/lib/cocina/models/mapping/from_mods/title_builder.rb +1 -1
- data/lib/cocina/models/mapping/normalizers/mods/name_normalizer.rb +1 -1
- data/lib/cocina/models/version.rb +1 -1
- data/lib/cocina/rspec/factories.rb +1 -1
- data/openapi.yml +1 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8cd049152e51891b2ab7fc1bd63fff3f95f8914f9939122ffb6990667f17ad98
|
4
|
+
data.tar.gz: 59645a00ccb0d8b21ae4830563f037dba0655a85d4f1b268ba21da66e6406d43
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: df8635b1b7318cd957f2071aac2e46cce144119d5c497d166d2e0152db0269fdccd611ea72872e80217a9e1e07ed354393add1e440cc580f86e8493953684767
|
7
|
+
data.tar.gz: f12b79669c54e657aa0956412227a18f7445ba4940d4a6a5d27f9d6d9a79fe72aa408df096114e636ff8bd99d76bdfeb99f017efef989f12a27ef891bb4a9033
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
cocina-models (0.93.
|
4
|
+
cocina-models (0.93.1)
|
5
5
|
activesupport
|
6
6
|
deprecation
|
7
7
|
dry-struct (~> 1.0)
|
@@ -21,7 +21,7 @@ PATH
|
|
21
21
|
GEM
|
22
22
|
remote: https://rubygems.org/
|
23
23
|
specs:
|
24
|
-
activesupport (7.1.
|
24
|
+
activesupport (7.1.2)
|
25
25
|
base64
|
26
26
|
bigdecimal
|
27
27
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
@@ -33,8 +33,8 @@ GEM
|
|
33
33
|
tzinfo (~> 2.0)
|
34
34
|
ast (2.4.2)
|
35
35
|
attr_extras (7.1.0)
|
36
|
-
base64 (0.
|
37
|
-
bigdecimal (3.1.
|
36
|
+
base64 (0.2.0)
|
37
|
+
bigdecimal (3.1.5)
|
38
38
|
byebug (11.1.3)
|
39
39
|
committee (5.0.0)
|
40
40
|
json_schema (~> 0.14, >= 0.14.3)
|
@@ -47,7 +47,7 @@ GEM
|
|
47
47
|
activesupport
|
48
48
|
diff-lcs (1.5.0)
|
49
49
|
docile (1.4.0)
|
50
|
-
drb (2.
|
50
|
+
drb (2.2.0)
|
51
51
|
ruby2_keywords
|
52
52
|
dry-core (1.0.1)
|
53
53
|
concurrent-ruby (~> 1.0)
|
@@ -75,7 +75,7 @@ GEM
|
|
75
75
|
i18n (1.14.1)
|
76
76
|
concurrent-ruby (~> 1.0)
|
77
77
|
ice_nine (0.11.2)
|
78
|
-
json (2.
|
78
|
+
json (2.7.1)
|
79
79
|
json_schema (0.21.0)
|
80
80
|
jsonpath (1.1.5)
|
81
81
|
multi_json
|
@@ -83,15 +83,15 @@ GEM
|
|
83
83
|
mini_portile2 (2.8.5)
|
84
84
|
minitest (5.20.0)
|
85
85
|
multi_json (1.15.0)
|
86
|
-
mutex_m (0.
|
87
|
-
nokogiri (1.
|
86
|
+
mutex_m (0.2.0)
|
87
|
+
nokogiri (1.16.0)
|
88
88
|
mini_portile2 (~> 2.8.2)
|
89
89
|
racc (~> 1.4)
|
90
90
|
openapi3_parser (0.9.2)
|
91
91
|
commonmarker (~> 0.17)
|
92
92
|
openapi_parser (1.0.0)
|
93
93
|
optimist (3.1.0)
|
94
|
-
parallel (1.
|
94
|
+
parallel (1.24.0)
|
95
95
|
parser (3.2.2.4)
|
96
96
|
ast (~> 2.4.1)
|
97
97
|
racc
|
@@ -101,7 +101,7 @@ GEM
|
|
101
101
|
rack (3.0.8)
|
102
102
|
rainbow (3.1.1)
|
103
103
|
rake (13.1.0)
|
104
|
-
regexp_parser (2.8.
|
104
|
+
regexp_parser (2.8.3)
|
105
105
|
rexml (3.2.6)
|
106
106
|
rspec (3.12.0)
|
107
107
|
rspec-core (~> 3.12.0)
|
@@ -120,7 +120,7 @@ GEM
|
|
120
120
|
rspec-core (>= 2, < 4, != 2.12.0)
|
121
121
|
rss (0.3.0)
|
122
122
|
rexml
|
123
|
-
rubocop (1.
|
123
|
+
rubocop (1.59.0)
|
124
124
|
json (~> 2.3)
|
125
125
|
language_server-protocol (>= 3.17.0)
|
126
126
|
parallel (~> 1.10)
|
@@ -128,7 +128,7 @@ GEM
|
|
128
128
|
rainbow (>= 2.2.2, < 4.0)
|
129
129
|
regexp_parser (>= 1.8, < 3.0)
|
130
130
|
rexml (>= 3.2.5, < 4.0)
|
131
|
-
rubocop-ast (>= 1.
|
131
|
+
rubocop-ast (>= 1.30.0, < 2.0)
|
132
132
|
ruby-progressbar (~> 1.7)
|
133
133
|
unicode-display_width (>= 2.4.0, < 3.0)
|
134
134
|
rubocop-ast (1.30.0)
|
data/README.md
CHANGED
@@ -12,6 +12,8 @@ The data model is expressed in an OpenAPI specification that lives in this codeb
|
|
12
12
|
|
13
13
|
Note that the data model encodes properties as camelCase, which the team believes to be consistent with other HTTP APIs and the original design of the Cocina data model. While using camelCase in Ruby code may look and feel wrong, we did explore automagic conversion between camelCase in the model and snake_case in the Ruby context. We ultimately concluded that we have enough representations of the data model in enough codebases to reasonably worry about data inconsistency problems, none of which we need in our work on SDR.
|
14
14
|
|
15
|
+
For more about the model for description see https://consul.stanford.edu/display/DIGMETADATA/Digital+Object+Metadata+Documentation#DigitalObjectMetadataDocumentation-Cocinamodel
|
16
|
+
|
15
17
|
## Configuration
|
16
18
|
|
17
19
|
Set the PURL url base:
|
@@ -6,8 +6,7 @@ module Cocina
|
|
6
6
|
module Models
|
7
7
|
module Builders
|
8
8
|
# TitleBuilder selects the prefered title from the cocina object for solr indexing
|
9
|
-
# rubocop:disable Metrics/ClassLength
|
10
|
-
class TitleBuilder
|
9
|
+
class TitleBuilder # rubocop:disable Metrics/ClassLength
|
11
10
|
extend Deprecation
|
12
11
|
# @param [[Array<Cocina::Models::Title,Cocina::Models::DescriptiveValue>] titles the titles to consider
|
13
12
|
# @param [Symbol] strategy ":first" is the strategy for selection when primary or display
|
@@ -24,24 +23,57 @@ module Cocina
|
|
24
23
|
new(strategy: strategy, add_punctuation: add_punctuation).build(titles)
|
25
24
|
end
|
26
25
|
|
26
|
+
# the "main title" is the title withOUT subtitle, part name, etc. We want to index it separately so
|
27
|
+
# we can boost matches on it in search results (boost matching this string higher than matching full title string)
|
28
|
+
# e.g. "The Hobbit" (main_title) vs "The Hobbit, or, There and Back Again (full_title)
|
29
|
+
# @param [[Array<Cocina::Models::Title,Cocina::Models::DescriptiveValue>] titles the titles to consider
|
30
|
+
# @return [String] the main title value for Solr
|
31
|
+
def self.main_title(titles)
|
32
|
+
new(strategy: :first, add_punctuation: false).main_title(titles)
|
33
|
+
end
|
34
|
+
|
35
|
+
# the "full title" is the title WITH subtitle, part name, etc. We want to able able to index it separately so
|
36
|
+
# we can boost matches on it in search results (boost matching this string higher than other titles present)
|
37
|
+
# @param [[Array<Cocina::Models::Title,Cocina::Models::DescriptiveValue>] titles the titles to consider
|
38
|
+
# @return [String] the title value for Solr
|
39
|
+
def self.full_title(titles)
|
40
|
+
new(strategy: :first, add_punctuation: false).build(titles)
|
41
|
+
end
|
42
|
+
|
43
|
+
# "additional titles" are all title data except for full_title. We want to able able to index it separately so
|
44
|
+
# we can boost matches on it in search results (boost matching these strings lower than other titles present)
|
45
|
+
# @param [[Array<Cocina::Models::Title,Cocina::Models::DescriptiveValue>] titles the titles to consider
|
46
|
+
# @return [Array<String>] the values for Solr
|
47
|
+
def self.additional_titles(titles)
|
48
|
+
new(strategy: :all, add_punctuation: false).build(titles) - [full_title(titles)]
|
49
|
+
end
|
50
|
+
|
27
51
|
def initialize(strategy:, add_punctuation:)
|
28
52
|
@strategy = strategy
|
29
53
|
@add_punctuation = add_punctuation
|
30
54
|
end
|
31
55
|
|
32
|
-
# @param [[Array<Cocina::Models::Title>]
|
56
|
+
# @param [[Array<Cocina::Models::Title>] cocina_titles the titles to consider
|
33
57
|
# @return [String] the title value for Solr
|
34
|
-
def build(
|
35
|
-
cocina_title = primary_title(
|
36
|
-
cocina_title = other_title(
|
58
|
+
def build(cocina_titles)
|
59
|
+
cocina_title = primary_title(cocina_titles) || untyped_title(cocina_titles)
|
60
|
+
cocina_title = other_title(cocina_titles) if cocina_title.blank?
|
37
61
|
|
38
62
|
if strategy == :first
|
39
63
|
extract_title(cocina_title)
|
40
64
|
else
|
41
|
-
|
65
|
+
cocina_titles.map { |ctitle| extract_title(ctitle) }.flatten
|
42
66
|
end
|
43
67
|
end
|
44
68
|
|
69
|
+
def main_title(titles)
|
70
|
+
cocina_title = primary_title(titles) || untyped_title(titles)
|
71
|
+
cocina_title = other_title(titles) if cocina_title.blank?
|
72
|
+
|
73
|
+
cocina_title = cocina_title.first if cocina_title.is_a?(Array)
|
74
|
+
extract_main_title(cocina_title)
|
75
|
+
end
|
76
|
+
|
45
77
|
private
|
46
78
|
|
47
79
|
attr_reader :strategy
|
@@ -57,6 +89,16 @@ module Cocina
|
|
57
89
|
remove_trailing_punctuation(result.strip) if result.present?
|
58
90
|
end
|
59
91
|
|
92
|
+
def extract_main_title(cocina_title)
|
93
|
+
if cocina_title.value
|
94
|
+
cocina_title.value # covers both title and main title types
|
95
|
+
elsif cocina_title.structuredValue.present?
|
96
|
+
main_title_from_structured_values(cocina_title)
|
97
|
+
elsif cocina_title.parallelValue.present?
|
98
|
+
main_title(cocina_title.parallelValue)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
60
102
|
def add_punctuation?
|
61
103
|
@add_punctuation
|
62
104
|
end
|
@@ -93,7 +135,8 @@ module Cocina
|
|
93
135
|
end
|
94
136
|
end
|
95
137
|
|
96
|
-
# This
|
138
|
+
# This is called when there is no primary title and no untyped title
|
139
|
+
# @return [Cocina::Models::Title, Array<Cocina::Models::Title>] first title or all titles
|
97
140
|
def other_title(titles)
|
98
141
|
if strategy == :first
|
99
142
|
titles.first
|
@@ -102,17 +145,19 @@ module Cocina
|
|
102
145
|
end
|
103
146
|
end
|
104
147
|
|
105
|
-
# rubocop:disable Metrics/BlockLength
|
106
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
107
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
108
|
-
# rubocop:disable Metrics/MethodLength
|
109
148
|
# @param [Cocina::Models::Title] title with structured values
|
110
149
|
# @return [String] the title value from combining the pieces of the structured_values by type and order
|
111
150
|
# with desired punctuation per specs
|
151
|
+
#
|
152
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
153
|
+
# rubocop:disable Metrics/MethodLength
|
154
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
112
155
|
def title_from_structured_values(title)
|
113
|
-
|
156
|
+
# parse out the parts
|
157
|
+
main_title = ''
|
158
|
+
subtitle = ''
|
159
|
+
non_sort_value = ''
|
114
160
|
part_name_number = ''
|
115
|
-
# combine pieces of the cocina structuredValue into a single title
|
116
161
|
title.structuredValue.each do |structured_value|
|
117
162
|
# There can be a structuredValue inside a structuredValue. For example,
|
118
163
|
# a uniform title where both the name and the title have internal StructuredValue
|
@@ -125,42 +170,88 @@ module Cocina
|
|
125
170
|
case structured_value.type&.downcase
|
126
171
|
when 'nonsorting characters'
|
127
172
|
non_sort_value = "#{value}#{non_sorting_padding(title, value)}"
|
128
|
-
structured_title = if structured_title.present?
|
129
|
-
"#{structured_title}#{non_sort_value}"
|
130
|
-
else
|
131
|
-
non_sort_value
|
132
|
-
end
|
133
173
|
when 'part name', 'part number'
|
134
|
-
if part_name_number.blank?
|
135
|
-
part_name_number = part_name_number(title.structuredValue)
|
136
|
-
structured_title = if !add_punctuation?
|
137
|
-
[structured_title, part_name_number].join(' ')
|
138
|
-
elsif structured_title.present?
|
139
|
-
"#{structured_title.sub(/[ .,]*$/, '')}. #{part_name_number}. "
|
140
|
-
else
|
141
|
-
"#{part_name_number}. "
|
142
|
-
end
|
143
|
-
end
|
174
|
+
part_name_number = part_name_number(title.structuredValue) if part_name_number.blank?
|
144
175
|
when 'main title', 'title'
|
145
|
-
|
176
|
+
main_title = value
|
146
177
|
when 'subtitle'
|
147
|
-
#
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
178
|
+
# combine multiple subtitles into a single string
|
179
|
+
subtitle = if !add_punctuation?
|
180
|
+
if subtitle.present?
|
181
|
+
[subtitle, value].join(' ')
|
182
|
+
else
|
183
|
+
value
|
184
|
+
end
|
185
|
+
elsif subtitle.present?
|
186
|
+
# subtitle is preceded by space colon space, unless it is at the beginning of the title string
|
187
|
+
"#{subtitle.sub(/[. :]+$/, '')} : #{value.sub(/^:/, '').strip}"
|
188
|
+
else
|
189
|
+
value.sub(/^:/, '').strip
|
190
|
+
end
|
155
191
|
end
|
156
192
|
end
|
157
|
-
|
193
|
+
|
194
|
+
# combine the parts into a single title string
|
195
|
+
if add_punctuation?
|
196
|
+
combine_with_punctuation(non_sort_value: non_sort_value, main_title: main_title, subtitle: subtitle,
|
197
|
+
part_name_number: part_name_number)
|
198
|
+
else
|
199
|
+
["#{non_sort_value}#{main_title}", subtitle, part_name_number].select(&:presence).join(' ')
|
200
|
+
end
|
158
201
|
end
|
159
|
-
# rubocop:enable Metrics/MethodLength
|
160
|
-
# rubocop:enable Metrics/BlockLength
|
161
202
|
# rubocop:enable Metrics/CyclomaticComplexity
|
203
|
+
# rubocop:enable Metrics/MethodLength
|
162
204
|
# rubocop:enable Metrics/PerceivedComplexity
|
163
205
|
|
206
|
+
# main_title is title.structuredValue.value with type 'main title' (or just title.value)
|
207
|
+
# @param [Cocina::Models::Title] title with structured values
|
208
|
+
# @return [String] the main title value
|
209
|
+
def main_title_from_structured_values(cocina_title) # rubocop:disable Metrics/MethodLength
|
210
|
+
result = ''
|
211
|
+
# combine pieces of the cocina structuredValue into a single title
|
212
|
+
cocina_title.structuredValue.each do |structured_value|
|
213
|
+
# There can be a structuredValue inside a structuredValue. For example,
|
214
|
+
# a uniform title where both the name and the title have internal StructuredValue
|
215
|
+
return main_title_from_structured_values(structured_value) if structured_value.structuredValue.present?
|
216
|
+
|
217
|
+
value = structured_value.value&.strip
|
218
|
+
next unless value
|
219
|
+
|
220
|
+
case structured_value.type&.downcase
|
221
|
+
when 'nonsorting characters'
|
222
|
+
non_sort_value = "#{value}#{non_sorting_padding(cocina_title, value)}"
|
223
|
+
result = "#{non_sort_value}#{result}" # non-sorting characters are at the beginning of the title
|
224
|
+
when 'main title'
|
225
|
+
result = "#{result}#{value}"
|
226
|
+
when 'title'
|
227
|
+
result = value
|
228
|
+
end
|
229
|
+
end
|
230
|
+
result
|
231
|
+
end
|
232
|
+
|
233
|
+
# Thank MARC and catalog cards for this mess. We need to add punctuation.
|
234
|
+
# rubocop:disable Metrics/MethodLength
|
235
|
+
def combine_with_punctuation(non_sort_value:, main_title:, subtitle:, part_name_number:)
|
236
|
+
result = "#{non_sort_value}#{main_title}"
|
237
|
+
if subtitle.present?
|
238
|
+
result = if result.present?
|
239
|
+
"#{result.sub(/[. :]+$/, '')} : #{subtitle.sub(/^:/, '').strip}"
|
240
|
+
else
|
241
|
+
result = subtitle
|
242
|
+
end
|
243
|
+
end
|
244
|
+
if part_name_number.present?
|
245
|
+
result = if result.present?
|
246
|
+
"#{result.sub(/[ .,]*$/, '')}. #{part_name_number}."
|
247
|
+
else
|
248
|
+
"#{part_name_number}."
|
249
|
+
end
|
250
|
+
end
|
251
|
+
result
|
252
|
+
end
|
253
|
+
# rubocop:enable Metrics/MethodLength
|
254
|
+
|
164
255
|
def remove_trailing_punctuation(title)
|
165
256
|
title.sub(%r{[ .,;:/\\]+$}, '')
|
166
257
|
end
|
@@ -205,7 +296,6 @@ module Cocina
|
|
205
296
|
end
|
206
297
|
end
|
207
298
|
end
|
208
|
-
# rubocop:enable Metrics/ClassLength
|
209
299
|
end
|
210
300
|
end
|
211
301
|
end
|
@@ -37,7 +37,7 @@ module Cocina
|
|
37
37
|
def build
|
38
38
|
grouped_altrepgroup_name_nodes, other_name_nodes = AltRepGroup.split(nodes: deduped_name_nodes)
|
39
39
|
check_altrepgroup_type_inconsistency(grouped_altrepgroup_name_nodes)
|
40
|
-
contributors = grouped_altrepgroup_name_nodes.map { |name_nodes| build_name_nodes(name_nodes) } +
|
40
|
+
contributors = grouped_altrepgroup_name_nodes.map { |name_nodes| build_name_nodes(name_nodes) } +
|
41
41
|
other_name_nodes.map { |name_node| build_name_nodes([name_node]) }
|
42
42
|
contrib_level_type_and_status(contributors)
|
43
43
|
adjust_primary(contributors.compact).presence
|
@@ -110,7 +110,7 @@ module Cocina
|
|
110
110
|
end
|
111
111
|
end
|
112
112
|
|
113
|
-
result.
|
113
|
+
result.each_value { |role_nodes| role_nodes.uniq! { |role_node| name_node_comparitor(role_node) } }
|
114
114
|
end
|
115
115
|
|
116
116
|
def check_altrepgroup_type_inconsistency(grouped_altrepgroup_name_nodes)
|
@@ -95,7 +95,7 @@ module Cocina
|
|
95
95
|
node.text.size + add
|
96
96
|
end
|
97
97
|
[{
|
98
|
-
value: count.to_s,
|
98
|
+
value: count.to_s, # cast to String until cocina-models 0.40.0 is used. See https://github.com/sul-dlss/cocina-models/pull/146
|
99
99
|
type: 'nonsorting character count'
|
100
100
|
}]
|
101
101
|
end
|
@@ -174,7 +174,7 @@ module Cocina
|
|
174
174
|
[role_node]
|
175
175
|
end
|
176
176
|
end
|
177
|
-
result.
|
177
|
+
result.each_value { |role_nodes| role_nodes.uniq! { |role_node| name_node_comparitor(role_node) } }
|
178
178
|
end
|
179
179
|
|
180
180
|
def normalize_type
|
@@ -38,7 +38,7 @@ module Cocina
|
|
38
38
|
|
39
39
|
build_type = type.to_s.delete_suffix(WITH_METADATA_SUFFIX)
|
40
40
|
|
41
|
-
fixture = public_send("build_#{build_type}".to_sym, attributes)
|
41
|
+
fixture = public_send("build_#{build_type}".to_sym, attributes) # rubocop:disable Lint/SymbolConversion
|
42
42
|
return fixture unless type.end_with?(WITH_METADATA_SUFFIX)
|
43
43
|
|
44
44
|
Cocina::Models.with_metadata(fixture, 'abc123')
|
data/openapi.yml
CHANGED
@@ -330,6 +330,7 @@ components:
|
|
330
330
|
pattern: '^[0-9]+-[0-9]+$'
|
331
331
|
example: '6772719-1001'
|
332
332
|
CitationOnlyAccess:
|
333
|
+
description: A type of access for an object wherein users can see the metadata and a list of files, but the files will not have view or download access
|
333
334
|
type: object
|
334
335
|
properties:
|
335
336
|
view:
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cocina-models
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.93.
|
4
|
+
version: 0.93.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Coyne
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -562,7 +562,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
562
562
|
- !ruby/object:Gem::Version
|
563
563
|
version: '0'
|
564
564
|
requirements: []
|
565
|
-
rubygems_version: 3.4.
|
565
|
+
rubygems_version: 3.4.13
|
566
566
|
signing_key:
|
567
567
|
specification_version: 4
|
568
568
|
summary: Data models for the SDR
|