cocina-models 0.101.0 → 0.103.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2198f8b258a6cae8fe0a3569eb5e36a400964d9ea0d05c6ec7bb19a761071e42
4
- data.tar.gz: 31b0657ba52c6f143b7cbceadb585f8710ff8f8d24410101a2daa22a7ad5ce56
3
+ metadata.gz: 25d2a47837021c38ac5a6f722e53e17fc78c9f07b09efa10d5d2df18a6aa714f
4
+ data.tar.gz: f52008be424354db1c55160c79ae4511c368b3bcd501a61d86d3ff23f572e57a
5
5
  SHA512:
6
- metadata.gz: 532777c074a4edcd3116bc6df43bf189b554e8b10bd63531940e9bf2019262989ae925a02582b2a51dac96dcb5f275edd686728d11f28ec33a2e8d95350d095c
7
- data.tar.gz: aa06744ae066e38d1f4c1cc8094c0bd39ef789008971ff51c0d0eb0315333cf65117a1f539b241fd91c82313d3299b4a73bd286f1d89f9f0b388b98f31aaa829
6
+ metadata.gz: bc39f2cd59a93a142503c575469ca20362a17c8a308169140f7f851dcc1099e2b758a8cef019ab52338d8f9ad25a2408c5a5a42d1c024655abc240e15a85f32b
7
+ data.tar.gz: fc2eb7c64bc4055a957c4ffc6de5a9d3106154eb12171fd214127bf6c8047cbc1e02e3a1ed3b7d33580052b148389174a96e5d7400b60c285dd8c1964c08e909
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cocina-models (0.101.0)
4
+ cocina-models (0.103.0)
5
5
  activesupport
6
6
  deprecation
7
7
  dry-struct (~> 1.0)
@@ -43,7 +43,7 @@ GEM
43
43
  openapi_parser (~> 1.0)
44
44
  rack (>= 1.5)
45
45
  concurrent-ruby (1.3.5)
46
- connection_pool (2.5.0)
46
+ connection_pool (2.5.2)
47
47
  deprecation (1.1.0)
48
48
  activesupport
49
49
  diff-lcs (1.6.1)
@@ -78,7 +78,7 @@ GEM
78
78
  i18n (1.14.7)
79
79
  concurrent-ruby (~> 1.0)
80
80
  ice_nine (0.11.2)
81
- json (2.10.2)
81
+ json (2.11.3)
82
82
  json_schema (0.21.0)
83
83
  jsonpath (1.1.5)
84
84
  multi_json
@@ -88,12 +88,12 @@ GEM
88
88
  mini_portile2 (2.8.8)
89
89
  minitest (5.25.5)
90
90
  multi_json (1.15.0)
91
- nokogiri (1.18.7)
91
+ nokogiri (1.18.8)
92
92
  mini_portile2 (~> 2.8.2)
93
93
  racc (~> 1.4)
94
94
  openapi_parser (1.0.0)
95
95
  optimist (3.2.1)
96
- parallel (1.26.3)
96
+ parallel (1.27.0)
97
97
  parser (3.3.8.0)
98
98
  ast (~> 2.4.1)
99
99
  racc
@@ -120,7 +120,7 @@ GEM
120
120
  rspec-support (3.13.2)
121
121
  rspec_junit_formatter (0.6.0)
122
122
  rspec-core (>= 2, < 4, != 2.12.0)
123
- rubocop (1.75.2)
123
+ rubocop (1.75.3)
124
124
  json (~> 2.3)
125
125
  language_server-protocol (~> 3.17.0.2)
126
126
  lint_roller (~> 1.1.0)
@@ -137,7 +137,7 @@ GEM
137
137
  rubocop-rake (0.7.1)
138
138
  lint_roller (~> 1.1)
139
139
  rubocop (>= 1.72.1)
140
- rubocop-rspec (3.5.0)
140
+ rubocop-rspec (3.6.0)
141
141
  lint_roller (~> 1.1)
142
142
  rubocop (~> 1.72, >= 1.72.1)
143
143
  ruby-progressbar (1.13.0)
@@ -8,26 +8,28 @@ module Cocina
8
8
  # TitleBuilder selects the prefered title from the cocina object for solr indexing
9
9
  class TitleBuilder # rubocop:disable Metrics/ClassLength
10
10
  extend Deprecation
11
- # @param [[Array<Cocina::Models::Title,Cocina::Models::DescriptiveValue>] titles the titles to consider
11
+ # @param [Array<Cocina::Models::Title,Cocina::Models::DescriptiveValue>] titles the titles to consider
12
+ # @param [Array<Cocina::Models::FolioCatalogLink>] catalog_links the folio catalog links to check for digital serials part labels
12
13
  # @param [Symbol] strategy ":first" is the strategy for selection when primary or display
13
14
  # title are missing
14
15
  # @param [Boolean] add_punctuation determines if the title should be formmated with punctuation
15
16
  # @return [String, Array] the title value for Solr - for :first strategy, a string; for :all strategy, an array
16
17
  # (e.g. title displayed in blacklight search results vs boosting values for search result rankings)
17
- def self.build(titles, strategy: :first, add_punctuation: true)
18
+ def self.build(titles, catalog_links: [], strategy: :first, add_punctuation: true)
19
+ part_label = catalog_links.find { |link| link.catalog == 'folio' }&.partLabel
18
20
  if titles.respond_to?(:description)
19
21
  Deprecation.warn(self,
20
22
  "Calling TitleBuilder.build with a #{titles.class} is deprecated. " \
21
23
  'It must be called with an array of titles')
22
24
  titles = titles.description.title
23
25
  end
24
- new(strategy: strategy, add_punctuation: add_punctuation).build(titles)
26
+ new(strategy: strategy, add_punctuation: add_punctuation, part_label: part_label).build(titles)
25
27
  end
26
28
 
27
29
  # the "main title" is the title withOUT subtitle, part name, etc. We want to index it separately so
28
30
  # we can boost matches on it in search results (boost matching this string higher than matching full title string)
29
31
  # e.g. "The Hobbit" (main_title) vs "The Hobbit, or, There and Back Again (full_title)
30
- # @param [[Array<Cocina::Models::Title,Cocina::Models::DescriptiveValue>] titles the titles to consider
32
+ # @param [Array<Cocina::Models::Title,Cocina::Models::DescriptiveValue>] titles the titles to consider
31
33
  # @return [Array<String>] the main title value(s) for Solr - array due to possible parallelValue
32
34
  def self.main_title(titles)
33
35
  new(strategy: :first, add_punctuation: false).main_title(titles)
@@ -35,15 +37,17 @@ module Cocina
35
37
 
36
38
  # the "full title" is the title WITH subtitle, part name, etc. We want to able able to index it separately so
37
39
  # we can boost matches on it in search results (boost matching this string higher than other titles present)
38
- # @param [[Array<Cocina::Models::Title,Cocina::Models::DescriptiveValue>] titles the titles to consider
40
+ # @param [Array<Cocina::Models::Title,Cocina::Models::DescriptiveValue>] titles the titles to consider
41
+ # @param [Array<Cocina::Models::FolioCatalogLink>] catalog_links the folio catalog links to check for digital serials part labels
39
42
  # @return [Array<String>] the full title value(s) for Solr - array due to possible parallelValue
40
- def self.full_title(titles)
41
- [new(strategy: :first, add_punctuation: false, only_one_parallel_value: false).build(titles)].flatten.compact
43
+ def self.full_title(titles, catalog_links: [])
44
+ part_label = catalog_links.find { |link| link.catalog == 'folio' }&.partLabel
45
+ [new(strategy: :first, add_punctuation: false, only_one_parallel_value: false, part_label: part_label).build(titles)].flatten.compact
42
46
  end
43
47
 
44
48
  # "additional titles" are all title data except for full_title. We want to able able to index it separately so
45
49
  # we can boost matches on it in search results (boost matching these strings lower than other titles present)
46
- # @param [[Array<Cocina::Models::Title,Cocina::Models::DescriptiveValue>] titles the titles to consider
50
+ # @param [Array<Cocina::Models::Title,Cocina::Models::DescriptiveValue>] titles the titles to consider
47
51
  # @return [Array<String>] the values for Solr
48
52
  def self.additional_titles(titles)
49
53
  [new(strategy: :all, add_punctuation: false).build(titles)].flatten - full_title(titles)
@@ -57,13 +61,15 @@ module Cocina
57
61
  # of primary, untyped, first occurrence. When false, return an array containing all the parallel values.
58
62
  # Why? Think of e.g. title displayed in blacklight search results vs boosting values for ranking of search
59
63
  # results
60
- def initialize(strategy:, add_punctuation:, only_one_parallel_value: true)
64
+ # @param part_label [String] the partLabel to add for digital serials display
65
+ def initialize(strategy:, add_punctuation:, only_one_parallel_value: true, part_label: nil)
61
66
  @strategy = strategy
62
67
  @add_punctuation = add_punctuation
63
68
  @only_one_parallel_value = only_one_parallel_value
69
+ @part_label = part_label
64
70
  end
65
71
 
66
- # @param [[Array<Cocina::Models::Title>] cocina_titles the titles to consider
72
+ # @param [Array<Cocina::Models::Title>] cocina_titles the titles to consider
67
73
  # @return [String, Array] the title value for Solr - for :first strategy, a string; for :all strategy, an array
68
74
  # (e.g. title displayed in blacklight search results vs boosting values for search result rankings)
69
75
  #
@@ -71,9 +77,10 @@ module Cocina
71
77
  def build(cocina_titles)
72
78
  cocina_title = primary_title(cocina_titles) || untyped_title(cocina_titles)
73
79
  cocina_title = other_title(cocina_titles) if cocina_title.blank?
74
-
75
80
  if strategy == :first
76
- extract_title(cocina_title)
81
+ result = extract_title(cocina_title)
82
+ result = add_part_label(result) if part_label.present?
83
+ result
77
84
  else
78
85
  result = cocina_titles.map { |ctitle| extract_title(ctitle) }.flatten
79
86
  if only_one_parallel_value? && result.length == 1
@@ -97,7 +104,13 @@ module Cocina
97
104
 
98
105
  private
99
106
 
100
- attr_reader :strategy
107
+ attr_reader :strategy, :part_label
108
+
109
+ def add_part_label(title)
110
+ # when a digital serial
111
+ title = title.sub(/[ .,]*$/, '').to_s
112
+ add_punctuation? ? "#{title}, #{part_label}" : "#{title} #{part_label}"
113
+ end
101
114
 
102
115
  def extract_title(cocina_title)
103
116
  title_values = if cocina_title.value
@@ -234,6 +247,8 @@ module Cocina
234
247
  padding = non_sorting_padding(cocina_title, value)
235
248
  result = add_non_sorting_value(result, value, padding)
236
249
  when 'part name', 'part number'
250
+ # even if there is a partLabel, use any existing structuredValue
251
+ # part name/number that remains for non-digital serials purposes
237
252
  if part_name_number.blank?
238
253
  part_name_number = part_name_number(cocina_title.structuredValue)
239
254
  result = if !add_punctuation?
@@ -11,30 +11,29 @@ module Cocina
11
11
  TAG_NAME = Cocina::Models::Mapping::FromMods::Title::TYPES.invert.merge('activity dates' => 'date').freeze
12
12
  NAME_TYPES = ['name', 'forename', 'surname', 'life dates', 'term of address'].freeze
13
13
 
14
+ def self.write(...)
15
+ new(...).write
16
+ end
17
+
14
18
  # @params [Nokogiri::XML::Builder] xml
15
19
  # @params [Array<Cocina::Models::Title>] titles
20
+ # @params [IdGenerator] id_generator
16
21
  # @params [Array<Cocina::Models::Contributor>] contributors
17
22
  # @params [Hash] additional_attrs for title
18
- # @params [IdGenerator] id_generator
19
- def self.write(xml:, titles:, id_generator:, contributors: [], additional_attrs: {})
20
- new(xml: xml, titles: titles, contributors: contributors, additional_attrs: additional_attrs,
21
- id_generator: id_generator).write
22
- end
23
-
24
- def initialize(xml:, titles:, additional_attrs:, contributors: [], id_generator: {})
23
+ # @params [Array<Cocina::Models::CatalogLink>] catalog_links a list of catalog
24
+ # links which may contain part label and sort key information
25
+ def initialize(xml:, titles:, id_generator:, contributors: [], additional_attrs: {}, catalog_links: []) # rubocop:disable Metrics/ParameterLists
25
26
  @xml = xml
26
27
  @titles = titles
28
+ @id_generator = id_generator
27
29
  @contributors = contributors
28
- @name_title_vals_index = {}
29
30
  @additional_attrs = additional_attrs
30
- @id_generator = id_generator
31
+ @catalog_links = catalog_links
32
+ @name_title_vals_index = {}
31
33
  end
32
34
 
33
- # rubocop:disable Metrics/AbcSize
34
- # rubocop:disable Metrics/BlockLength
35
- # rubocop:disable Metrics/CyclomaticComplexity
36
- def write
37
- titles.each do |title|
35
+ def write # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
36
+ titles.each do |title| # rubocop:disable Metrics/BlockLength
38
37
  name_title_vals_index = name_title_vals_index_for(title)
39
38
 
40
39
  if title.valueAt
@@ -78,13 +77,10 @@ module Cocina
78
77
  end
79
78
  end
80
79
  end
81
- # rubocop:enable Metrics/AbcSize
82
- # rubocop:enable Metrics/BlockLength
83
- # rubocop:enable Metrics/CyclomaticComplexity
84
80
 
85
81
  private
86
82
 
87
- attr_reader :xml, :titles, :contributors, :name_title_vals_index, :id_generator, :additional_attrs
83
+ attr_reader :xml, :titles, :contributors, :name_title_vals_index, :id_generator, :additional_attrs, :catalog_links
88
84
 
89
85
  def write_xlink(title:)
90
86
  attrs = { 'xlink:href' => title.valueAt }
@@ -173,9 +169,21 @@ module Cocina
173
169
  title_info_attrs = title_info_attrs_for(title).merge(title_info_attrs)
174
170
  xml.titleInfo(with_uri_info(title, title_info_attrs)) do
175
171
  title_parts = flatten_structured_value(title)
176
- title_parts_without_names = title_parts_without_names(title_parts)
172
+ write_title_parts!(title_parts: title_parts_without_names(title_parts), title: title)
173
+ end
174
+ end
177
175
 
178
- title_parts_without_names.each do |title_part|
176
+ def write_title_parts!(title_parts:, title:)
177
+ # If title parts contain the main title and a part label has been
178
+ # recorded in a catalog link, short-circuit the usual title part
179
+ # parsing and pull the part label from the catalog link instead of
180
+ # from descriptive metadata
181
+ if (main_title = title_parts.find { |part| part.type == 'main title' }&.value) &&
182
+ (part_label = catalog_links.find { |link| link.catalog == 'folio' && link.partLabel.present? }&.partLabel)
183
+ xml.title(main_title)
184
+ xml.partNumber(part_label)
185
+ else
186
+ title_parts.each do |title_part|
179
187
  title_type = tag_name_for(title_part)
180
188
  title_value = title_value_for(title_part, title_type, title)
181
189
  xml.public_send(title_type, title_value) if title_part.note.blank?
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
- VERSION = '0.101.0'
5
+ VERSION = '0.103.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cocina-models
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.101.0
4
+ version: 0.103.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Coyne
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-04-15 00:00:00.000000000 Z
10
+ date: 2025-04-29 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activesupport