cocina_display 1.1.3 → 1.2.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 +4 -4
- data/.rspec +0 -1
- data/.standard.yml +1 -1
- data/README.md +21 -2
- data/config/i18n-tasks.yml +0 -0
- data/config/licenses.yml +59 -0
- data/config/locales/en.yml +109 -0
- data/lib/cocina_display/cocina_record.rb +27 -63
- data/lib/cocina_display/concerns/accesses.rb +78 -0
- data/lib/cocina_display/concerns/contributors.rb +32 -11
- data/lib/cocina_display/concerns/events.rb +19 -6
- data/lib/cocina_display/concerns/forms.rb +98 -11
- data/lib/cocina_display/concerns/geospatial.rb +9 -5
- data/lib/cocina_display/concerns/identifiers.rb +15 -4
- data/lib/cocina_display/concerns/languages.rb +6 -2
- data/lib/cocina_display/concerns/notes.rb +36 -0
- data/lib/cocina_display/concerns/related_resources.rb +20 -0
- data/lib/cocina_display/concerns/subjects.rb +25 -8
- data/lib/cocina_display/concerns/titles.rb +67 -25
- data/lib/cocina_display/concerns/{access.rb → url_helpers.rb} +3 -3
- data/lib/cocina_display/concerns.rb +6 -0
- data/lib/cocina_display/contributors/contributor.rb +47 -26
- data/lib/cocina_display/contributors/name.rb +18 -14
- data/lib/cocina_display/contributors/role.rb +20 -13
- data/lib/cocina_display/dates/date.rb +55 -14
- data/lib/cocina_display/dates/date_range.rb +0 -2
- data/lib/cocina_display/description/access.rb +41 -0
- data/lib/cocina_display/description/access_contact.rb +11 -0
- data/lib/cocina_display/description/url.rb +17 -0
- data/lib/cocina_display/display_data.rb +104 -0
- data/lib/cocina_display/events/event.rb +8 -4
- data/lib/cocina_display/events/imprint.rb +0 -10
- data/lib/cocina_display/events/location.rb +0 -2
- data/lib/cocina_display/events/note.rb +33 -0
- data/lib/cocina_display/forms/form.rb +71 -0
- data/lib/cocina_display/forms/genre.rb +12 -0
- data/lib/cocina_display/forms/resource_type.rb +38 -0
- data/lib/cocina_display/geospatial.rb +1 -1
- data/lib/cocina_display/identifier.rb +101 -0
- data/lib/cocina_display/json_backed_record.rb +27 -0
- data/lib/cocina_display/language.rb +9 -11
- data/lib/cocina_display/license.rb +32 -0
- data/lib/cocina_display/note.rb +103 -0
- data/lib/cocina_display/related_resource.rb +74 -0
- data/lib/cocina_display/subjects/subject.rb +32 -9
- data/lib/cocina_display/subjects/subject_value.rb +34 -16
- data/lib/cocina_display/title.rb +194 -0
- data/lib/cocina_display/utils.rb +4 -4
- data/lib/cocina_display/version.rb +1 -1
- data/lib/cocina_display.rb +30 -2
- metadata +45 -11
- data/lib/cocina_display/title_builder.rb +0 -397
- /data/lib/cocina_display/vocabularies/{marc_country_codes.rb → marc_country.rb} +0 -0
- /data/lib/cocina_display/vocabularies/{marc_relator_codes.rb → marc_relator.rb} +0 -0
@@ -0,0 +1,194 @@
|
|
1
|
+
module CocinaDisplay
|
2
|
+
# A group of related {TitleValue}s associated with an item.
|
3
|
+
class Title
|
4
|
+
# The underlying Cocina hash.
|
5
|
+
attr_reader :cocina
|
6
|
+
|
7
|
+
# Type of the title, e.g. "uniform", "alternative", etc.
|
8
|
+
# @see https://github.com/sul-dlss/cocina-models/blob/main/docs/description_types.md#title-types
|
9
|
+
# @return [String, nil]
|
10
|
+
attr_accessor :type
|
11
|
+
|
12
|
+
# Status of the title, e.g. "primary".
|
13
|
+
# @return [String, nil]
|
14
|
+
attr_accessor :status
|
15
|
+
|
16
|
+
# Create a new Title object.
|
17
|
+
# @param cocina [Hash]
|
18
|
+
# @param part_label [String, nil] part label for digital serials
|
19
|
+
# @param part_numbers [Array<String>] part numbers for related resources
|
20
|
+
def initialize(cocina, part_label: nil, part_numbers: nil)
|
21
|
+
@cocina = cocina
|
22
|
+
@part_label = part_label
|
23
|
+
@part_numbers = part_numbers
|
24
|
+
@type = cocina["type"].presence
|
25
|
+
@status = cocina["status"].presence
|
26
|
+
end
|
27
|
+
|
28
|
+
# Label used when displaying the title.
|
29
|
+
# @return [String]
|
30
|
+
def label
|
31
|
+
cocina["displayLabel"].presence || type_label
|
32
|
+
end
|
33
|
+
|
34
|
+
# Does this title have a type?
|
35
|
+
# @return [Boolean]
|
36
|
+
def type?
|
37
|
+
type.present?
|
38
|
+
end
|
39
|
+
|
40
|
+
# Is this marked as a primary title?
|
41
|
+
# @return [Boolean]
|
42
|
+
def primary?
|
43
|
+
status == "primary"
|
44
|
+
end
|
45
|
+
|
46
|
+
# The string representation of the title, for display.
|
47
|
+
# @see #display_title
|
48
|
+
# @return [String, nil]
|
49
|
+
def to_s
|
50
|
+
display_title
|
51
|
+
end
|
52
|
+
|
53
|
+
# The short form of the title, without subtitle, part name, etc.
|
54
|
+
# @note This corresponds to the "short title" in MODS XML, or MARC 245$a only.
|
55
|
+
# @return [String, nil]
|
56
|
+
# @example "M. de Courville"
|
57
|
+
def short_title
|
58
|
+
short_title_str.presence || cocina["value"]
|
59
|
+
end
|
60
|
+
|
61
|
+
# The long form of the title, including subtitle, part name, etc.
|
62
|
+
# @note This corresponds to the entire MARC 245 field.
|
63
|
+
# @return [String, nil]
|
64
|
+
# @example "M. de Courville [estampe]"
|
65
|
+
def full_title
|
66
|
+
full_title_str.presence || cocina["value"]
|
67
|
+
end
|
68
|
+
|
69
|
+
# The long form of the title, with added punctuation between parts if not present.
|
70
|
+
# @note This corresponds to the entire MARC 245 field.
|
71
|
+
# @return [String, nil]
|
72
|
+
# @example "M. de Courville : [estampe]"
|
73
|
+
def display_title
|
74
|
+
display_title_str.presence || cocina["value"]
|
75
|
+
end
|
76
|
+
|
77
|
+
# A string value for sorting by title.
|
78
|
+
# Ignores punctuation, leading/trailing spaces, and non-sorting characters.
|
79
|
+
# If no title is present, returns a high Unicode value so it sorts last.
|
80
|
+
# @return [String]
|
81
|
+
def sort_title
|
82
|
+
return "\u{10FFFF}" unless full_title
|
83
|
+
|
84
|
+
full_title[nonsorting_char_count..]
|
85
|
+
.unicode_normalize(:nfd) # Prevent accents being stripped
|
86
|
+
.gsub(/[[:punct:]]*/, "")
|
87
|
+
.gsub(/\W{2,}/, " ") # Collapse whitespace after removing punctuation
|
88
|
+
.strip
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
# Generate the short title by joining main title and nonsorting characters with spaces.
|
94
|
+
# @return [String, nil]
|
95
|
+
def short_title_str
|
96
|
+
Utils.compact_and_join([nonsorting_chars_str, main_title_str])
|
97
|
+
end
|
98
|
+
|
99
|
+
# Generate the full title by joining all title components with spaces.
|
100
|
+
# @return [String, nil]
|
101
|
+
def full_title_str
|
102
|
+
Utils.compact_and_join([nonsorting_chars_str, main_title_str, subtitle_str, parts_str])
|
103
|
+
end
|
104
|
+
|
105
|
+
# Generate the display title by joining all components with punctuation:
|
106
|
+
# - Join main title and subtitle with " : "
|
107
|
+
# - Join part name/number/label with ", "
|
108
|
+
# - Join part string with preceding title with ". "
|
109
|
+
# - Prepend nonsorting characters with specified padding
|
110
|
+
# - Prepend associated names with ". "
|
111
|
+
# @return [String, nil]
|
112
|
+
def display_title_str
|
113
|
+
title_str = Utils.compact_and_join([main_title_str, subtitle_str], delimiter: " : ")
|
114
|
+
title_str = Utils.compact_and_join([title_str, parts_str(delimiter: ", ")], delimiter: ". ")
|
115
|
+
title_str = Utils.compact_and_join([nonsorting_chars_str, title_str]) if nonsorting_chars_str.present?
|
116
|
+
title_str = Utils.compact_and_join([names_str, title_str], delimiter: ". ") if names_str.present?
|
117
|
+
title_str.presence
|
118
|
+
end
|
119
|
+
|
120
|
+
# All nonsorting characters joined together with padding applied.
|
121
|
+
# @return [String, nil]
|
122
|
+
def nonsorting_chars_str
|
123
|
+
Utils.compact_and_join(Array(title_components["nonsorting characters"])).ljust(nonsorting_char_count, " ")
|
124
|
+
end
|
125
|
+
|
126
|
+
# The main title component(s), joined together.
|
127
|
+
# @return [String, nil]
|
128
|
+
def main_title_str
|
129
|
+
Utils.compact_and_join(Array(title_components["main title"]))
|
130
|
+
end
|
131
|
+
|
132
|
+
# The subtitle components, joined together.
|
133
|
+
# @return [String, nil]
|
134
|
+
def subtitle_str
|
135
|
+
Utils.compact_and_join(Array(title_components["subtitle"]))
|
136
|
+
end
|
137
|
+
|
138
|
+
# The part name, number, and label components, joined together.
|
139
|
+
# Default delimiter is a space, but can be overridden.
|
140
|
+
# @return [String, nil]
|
141
|
+
def parts_str(delimiter: " ")
|
142
|
+
Utils.compact_and_join(
|
143
|
+
Array(title_components["part number"] || @part_numbers) +
|
144
|
+
Array(title_components["part name"]) +
|
145
|
+
[@part_label],
|
146
|
+
delimiter: delimiter
|
147
|
+
)
|
148
|
+
end
|
149
|
+
|
150
|
+
# The associated names, joined together with periods.
|
151
|
+
# @note Only present for uniform titles.
|
152
|
+
# @return [String, nil]
|
153
|
+
def names_str
|
154
|
+
Utils.compact_and_join(names, delimiter: ". ")
|
155
|
+
end
|
156
|
+
|
157
|
+
# Destructured title components, organized by type.
|
158
|
+
# Unstructured titles and components with no type are grouped under "main title".
|
159
|
+
# @return [Hash<String, Array<String>>]
|
160
|
+
# @see https://github.com/sul-dlss/cocina-models/blob/main/docs/description_types.md#title-part-types-for-structured-value
|
161
|
+
def title_components
|
162
|
+
Utils.flatten_nested_values(cocina).each_with_object({}) do |node, hash|
|
163
|
+
type = case node["type"]
|
164
|
+
when "uniform", "alternative", "abbreviated", "translated", "transliterated", "parallel", "supplied", nil
|
165
|
+
"main title"
|
166
|
+
else
|
167
|
+
node["type"]
|
168
|
+
end
|
169
|
+
hash[type] ||= []
|
170
|
+
hash[type] << node["value"]
|
171
|
+
end.compact_blank
|
172
|
+
end
|
173
|
+
|
174
|
+
# Uniform titles can have associated person names.
|
175
|
+
# @return [String, nil]
|
176
|
+
def names
|
177
|
+
Janeway.enum_for("$.note[?(@.type=='associated name')]", cocina).map do |name|
|
178
|
+
Contributors::Name.new(name).to_s(with_date: true)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Number of nonsorting characters to ignore at the start of the title.
|
183
|
+
# @return [Integer, nil]
|
184
|
+
def nonsorting_char_count
|
185
|
+
Janeway.enum_for("$.note[?(@.type=='nonsorting character count')].value", cocina).first&.to_i || 0
|
186
|
+
end
|
187
|
+
|
188
|
+
# Type-specific label for the title, falling back to a generic "Title".
|
189
|
+
# @return [String]
|
190
|
+
def type_label
|
191
|
+
I18n.t(type&.parameterize&.underscore, scope: "cocina_display.field_label.title", default: :title)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
data/lib/cocina_display/utils.rb
CHANGED
@@ -21,7 +21,7 @@ module CocinaDisplay
|
|
21
21
|
end.delete_suffix(delimiter)
|
22
22
|
end
|
23
23
|
|
24
|
-
# Recursively flatten structured,
|
24
|
+
# Recursively flatten structured, and grouped values in Cocina metadata.
|
25
25
|
# Returns a list of hashes representing the "leaf" nodes with +value+ key.
|
26
26
|
# @return [Array<Hash>] List of node hashes with "value" present
|
27
27
|
# @param cocina [Hash] The Cocina structured data to flatten
|
@@ -35,8 +35,8 @@ module CocinaDisplay
|
|
35
35
|
# cocina = { "structuredValue" => [{"value" => "foo"}, {"value" => "bar"}] }
|
36
36
|
# Utils.flatten_nested_values(cocina)
|
37
37
|
# #=> [{"value" => "foo"}, {"value" => "bar"}]
|
38
|
-
# @example
|
39
|
-
# cocina = { "
|
38
|
+
# @example nested structured and simple values
|
39
|
+
# cocina = { "structuredValue" => [{"value" => "foo" }, { "structuredValue" => [{"value" => "bar"}, {"value" => "baz"}] }] }
|
40
40
|
# Utils.flatten_nested_values(cocina)
|
41
41
|
# #=> [{"value" => "foo"}, {"value" => "foo"}, {"value" => "baz"}]
|
42
42
|
def self.flatten_nested_values(cocina, output = [], atomic_types: [])
|
@@ -44,7 +44,7 @@ module CocinaDisplay
|
|
44
44
|
return [cocina] if atomic_types.include?(cocina["type"])
|
45
45
|
return cocina.flat_map { |node| flatten_nested_values(node, output, atomic_types: atomic_types) } if cocina.is_a?(Array)
|
46
46
|
|
47
|
-
nested_values = Array(cocina["structuredValue"]) + Array(cocina["
|
47
|
+
nested_values = Array(cocina["structuredValue"]) + Array(cocina["groupedValue"])
|
48
48
|
return output unless nested_values.any?
|
49
49
|
|
50
50
|
nested_values.flat_map { |node| flatten_nested_values(node, output, atomic_types: atomic_types) }
|
data/lib/cocina_display.rb
CHANGED
@@ -1,4 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "cocina_display/version"
|
4
|
-
|
3
|
+
# require_relative "cocina_display/version"
|
4
|
+
|
5
|
+
require "janeway"
|
6
|
+
require "json"
|
7
|
+
require "net/http"
|
8
|
+
require "active_support"
|
9
|
+
require "active_support/core_ext/object/blank"
|
10
|
+
require "active_support/core_ext/hash/conversions"
|
11
|
+
require "geo/coord"
|
12
|
+
require "edtf"
|
13
|
+
require "i18n"
|
14
|
+
require "i18n/backend/fallbacks"
|
15
|
+
I18n::Backend::Simple.include I18n::Backend::Fallbacks
|
16
|
+
I18n.load_path += Dir["#{File.expand_path("..", __dir__)}/config/locales/*.yml"]
|
17
|
+
I18n.backend.load_translations
|
18
|
+
|
19
|
+
require "zeitwerk"
|
20
|
+
loader = Zeitwerk::Loader.new
|
21
|
+
loader.tag = File.basename(__FILE__, ".rb")
|
22
|
+
loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
|
23
|
+
loader.inflector.inflect("searchworks_languages" => "SEARCHWORKS_LANGUAGES",
|
24
|
+
"marc_relator" => "MARC_RELATOR",
|
25
|
+
"marc_country" => "MARC_COUNTRY")
|
26
|
+
loader.push_dir(File.dirname(__FILE__))
|
27
|
+
loader.setup
|
28
|
+
|
29
|
+
module CocinaDisplay
|
30
|
+
# set to an object with a #notify method. This is called if an error is encountered.
|
31
|
+
mattr_accessor :notifier
|
32
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cocina_display
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Budak
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-09-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: janeway-jsonpath
|
@@ -53,19 +53,19 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '3.2'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: i18n
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '0'
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: geo_coord
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0.2'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: zeitwerk
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.7'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.7'
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
name: rake
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -210,35 +224,55 @@ files:
|
|
210
224
|
- LICENSE
|
211
225
|
- README.md
|
212
226
|
- Rakefile
|
227
|
+
- config/i18n-tasks.yml
|
228
|
+
- config/licenses.yml
|
229
|
+
- config/locales/en.yml
|
213
230
|
- lib/cocina_display.rb
|
214
231
|
- lib/cocina_display/cocina_record.rb
|
215
|
-
- lib/cocina_display/concerns
|
232
|
+
- lib/cocina_display/concerns.rb
|
233
|
+
- lib/cocina_display/concerns/accesses.rb
|
216
234
|
- lib/cocina_display/concerns/contributors.rb
|
217
235
|
- lib/cocina_display/concerns/events.rb
|
218
236
|
- lib/cocina_display/concerns/forms.rb
|
219
237
|
- lib/cocina_display/concerns/geospatial.rb
|
220
238
|
- lib/cocina_display/concerns/identifiers.rb
|
221
239
|
- lib/cocina_display/concerns/languages.rb
|
240
|
+
- lib/cocina_display/concerns/notes.rb
|
241
|
+
- lib/cocina_display/concerns/related_resources.rb
|
222
242
|
- lib/cocina_display/concerns/structural.rb
|
223
243
|
- lib/cocina_display/concerns/subjects.rb
|
224
244
|
- lib/cocina_display/concerns/titles.rb
|
245
|
+
- lib/cocina_display/concerns/url_helpers.rb
|
225
246
|
- lib/cocina_display/contributors/contributor.rb
|
226
247
|
- lib/cocina_display/contributors/name.rb
|
227
248
|
- lib/cocina_display/contributors/role.rb
|
228
249
|
- lib/cocina_display/dates/date.rb
|
229
250
|
- lib/cocina_display/dates/date_range.rb
|
251
|
+
- lib/cocina_display/description/access.rb
|
252
|
+
- lib/cocina_display/description/access_contact.rb
|
253
|
+
- lib/cocina_display/description/url.rb
|
254
|
+
- lib/cocina_display/display_data.rb
|
230
255
|
- lib/cocina_display/events/event.rb
|
231
256
|
- lib/cocina_display/events/imprint.rb
|
232
257
|
- lib/cocina_display/events/location.rb
|
258
|
+
- lib/cocina_display/events/note.rb
|
259
|
+
- lib/cocina_display/forms/form.rb
|
260
|
+
- lib/cocina_display/forms/genre.rb
|
261
|
+
- lib/cocina_display/forms/resource_type.rb
|
233
262
|
- lib/cocina_display/geospatial.rb
|
263
|
+
- lib/cocina_display/identifier.rb
|
264
|
+
- lib/cocina_display/json_backed_record.rb
|
234
265
|
- lib/cocina_display/language.rb
|
266
|
+
- lib/cocina_display/license.rb
|
267
|
+
- lib/cocina_display/note.rb
|
268
|
+
- lib/cocina_display/related_resource.rb
|
235
269
|
- lib/cocina_display/subjects/subject.rb
|
236
270
|
- lib/cocina_display/subjects/subject_value.rb
|
237
|
-
- lib/cocina_display/
|
271
|
+
- lib/cocina_display/title.rb
|
238
272
|
- lib/cocina_display/utils.rb
|
239
273
|
- lib/cocina_display/version.rb
|
240
|
-
- lib/cocina_display/vocabularies/
|
241
|
-
- lib/cocina_display/vocabularies/
|
274
|
+
- lib/cocina_display/vocabularies/marc_country.rb
|
275
|
+
- lib/cocina_display/vocabularies/marc_relator.rb
|
242
276
|
- lib/cocina_display/vocabularies/searchworks_languages.rb
|
243
277
|
- script/deep_compact.rb
|
244
278
|
- script/find_records.rb
|