cocina_display 1.12.2 → 2.0.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/config/locales/en.yml +3 -1
- data/lib/cocina_display/concerns/contributors.rb +8 -8
- data/lib/cocina_display/concerns/events.rb +51 -23
- data/lib/cocina_display/dates/date.rb +11 -5
- data/lib/cocina_display/dates/date_range.rb +5 -5
- data/lib/cocina_display/events/event.rb +121 -1
- data/lib/cocina_display/events/note.rb +13 -1
- data/lib/cocina_display/identifier.rb +13 -2
- data/lib/cocina_display/related_resource.rb +7 -3
- data/lib/cocina_display/subjects/subject_value.rb +1 -1
- data/lib/cocina_display/version.rb +1 -1
- metadata +2 -3
- data/lib/cocina_display/events/imprint.rb +0 -90
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f3f339ed91ca38de77fe91e83e50ba0dbdae117c810b831ed7597181b949dab7
|
|
4
|
+
data.tar.gz: 10e954dc692155b1c2ea3e219840431b4bae188b09399060973bc873b5dc9d63
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 04f6b7138b9079fb12f9455e5363fce77743f19e0ae30aa01a2d13a567182d184475304316bfa81d720970cbab77ac7d8854f29e62bbcf15df43cf95593898eb
|
|
7
|
+
data.tar.gz: 791231009d1e945f959bae16095d022306052bf0a8a02fe76f3d722343e79434469fd1bf5e32309a638afda378a8070e7f8dd259adce155106c307808a1602c4
|
data/config/locales/en.yml
CHANGED
|
@@ -16,6 +16,8 @@ en:
|
|
|
16
16
|
default: Imprint note
|
|
17
17
|
edition: Edition
|
|
18
18
|
issuance: Issuance
|
|
19
|
+
frequency: Frequency
|
|
20
|
+
copyright_statement: Copyright statement
|
|
19
21
|
form:
|
|
20
22
|
digital_origin: Digital origin
|
|
21
23
|
extent: Extent
|
|
@@ -66,7 +68,7 @@ en:
|
|
|
66
68
|
point_coordinates: Map data
|
|
67
69
|
subject: Subject
|
|
68
70
|
topic: Subject
|
|
69
|
-
use_and_reproduction: Use and reproduction
|
|
71
|
+
use_and_reproduction: Use and reproduction
|
|
70
72
|
related_resource:
|
|
71
73
|
described_by: Described by
|
|
72
74
|
describes: Describes
|
|
@@ -61,18 +61,19 @@ module CocinaDisplay
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
# A hash mapping role names to the names of contributors with that role.
|
|
64
|
+
# Contributors with no role are grouped under a nil key.
|
|
64
65
|
# @param with_date [Boolean] Include life dates, if present
|
|
65
|
-
# @return [Hash<String, Array<String>>]
|
|
66
|
+
# @return [Hash<[String,NilClass], Array<String>>]
|
|
66
67
|
def contributor_names_by_role(with_date: false)
|
|
67
|
-
contributors_by_role
|
|
68
|
+
contributors_by_role
|
|
68
69
|
.transform_values { |contributor_list| contributor_list.flat_map { |contributor| contributor.display_names(with_date: with_date) }.compact_blank }
|
|
69
70
|
.compact_blank
|
|
70
71
|
end
|
|
71
72
|
|
|
72
|
-
# A hash mapping role names to the
|
|
73
|
-
#
|
|
73
|
+
# A hash mapping role names to the Contributor objects with that role.
|
|
74
|
+
# Contributors with no role are grouped under a nil key.
|
|
74
75
|
# @return [Hash<[String,NilClass], Array<Contributor>>]
|
|
75
|
-
def contributors_by_role
|
|
76
|
+
def contributors_by_role
|
|
76
77
|
@contributors_by_role ||= contributors.each_with_object({}) do |contributor, hash|
|
|
77
78
|
if contributor.roles.empty?
|
|
78
79
|
hash[nil] ||= []
|
|
@@ -86,12 +87,11 @@ module CocinaDisplay
|
|
|
86
87
|
end
|
|
87
88
|
end
|
|
88
89
|
|
|
89
|
-
# DisplayData for Contributors, one per role
|
|
90
|
+
# DisplayData for Contributors, one per role.
|
|
90
91
|
# Contributors with no role are grouped under a default heading.
|
|
91
|
-
# @note For displaying publisher information, use {publication_display_data}.
|
|
92
92
|
# @return [Array<DisplayData>]
|
|
93
93
|
def contributor_display_data
|
|
94
|
-
contributors_by_role.
|
|
94
|
+
contributors_by_role.map do |role, contributors|
|
|
95
95
|
label = I18n.t(role, scope: "cocina_display.contributor.role",
|
|
96
96
|
default: role&.capitalize || I18n.t("default", scope: "cocina_display.contributor.role"))
|
|
97
97
|
DisplayData.new(label: label, objects: contributors)
|
|
@@ -50,15 +50,56 @@ module CocinaDisplay
|
|
|
50
50
|
# Prefers dates marked as primary and those with a declared encoding.
|
|
51
51
|
# @param ignore_qualified [Boolean] Reject qualified dates (e.g. approximate)
|
|
52
52
|
# @return [String, nil]
|
|
53
|
-
# @example Year
|
|
54
|
-
#
|
|
53
|
+
# @example Year with month and day, 2024-08-21 (vc109xd3118)
|
|
54
|
+
# "2024"
|
|
55
|
+
# @example Approximate year range, [ca. 1932 - 2012] (bb099mt5053)
|
|
56
|
+
# "1932 - 2012"
|
|
57
|
+
# @example BCE year range, [ca. 3500 BCE] - 3101 BCE (yv690gn5376)
|
|
58
|
+
# "3500 BCE - 3101 BCE"
|
|
59
|
+
# @example Unencoded string, 'about 933'
|
|
60
|
+
# "933 CE"
|
|
61
|
+
# @example Not parsable, 'invalid-date'
|
|
62
|
+
# nil
|
|
55
63
|
def pub_year_str(ignore_qualified: false)
|
|
56
64
|
date = pub_date(ignore_qualified: ignore_qualified)
|
|
57
|
-
return unless date
|
|
65
|
+
return unless date&.parsed_date?
|
|
58
66
|
|
|
59
67
|
date.decoded_value(allowed_precisions: [:year, :decade, :century])
|
|
60
68
|
end
|
|
61
69
|
|
|
70
|
+
# String for sorting lexicographically by publication date.
|
|
71
|
+
# Considers publication, creation, and capture dates in that order.
|
|
72
|
+
# Prefers dates marked as primary and those with a declared encoding.
|
|
73
|
+
# @note BCE dates have special handling; see Date#sort_key for details.
|
|
74
|
+
# @param ignore_qualified [Boolean] Reject qualified dates (e.g. approximate)
|
|
75
|
+
# @return [String, nil]
|
|
76
|
+
# @example Year with month and day, 2024-08-21 (vc109xd3118)
|
|
77
|
+
# "20240821"
|
|
78
|
+
# @example Approximate year range, [ca. 1932 - 2012] (bb099mt5053)
|
|
79
|
+
# "1932000020120000"
|
|
80
|
+
# @example BCE year range, [ca. 3500 BCE] - 3101 BCE (yv690gn5376)
|
|
81
|
+
# "-564990000-568980000"
|
|
82
|
+
def pub_date_sort_str(ignore_qualified: false)
|
|
83
|
+
pub_date(ignore_qualified: ignore_qualified)&.sort_key
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# String for displaying the publication date.
|
|
87
|
+
# Considers publication, creation, and capture dates in that order.
|
|
88
|
+
# Prefers dates marked as primary and those with a declared encoding.
|
|
89
|
+
# If not encoded, returns the original string value from the Cocina.
|
|
90
|
+
# @return [String, nil]
|
|
91
|
+
# @example w3cdtf encoded year with month and day (vc109xd3118)
|
|
92
|
+
# "August 21, 2024"
|
|
93
|
+
# @example w3cdtf encoded approximate year range (bb099mt5053)
|
|
94
|
+
# "[ca. 1932 - 2012]"
|
|
95
|
+
# @example Unencoded string, 'about 933'
|
|
96
|
+
# "about 933"
|
|
97
|
+
# @example Not parsable, 'invalid-date'
|
|
98
|
+
# "invalid-date"
|
|
99
|
+
def pub_date_str
|
|
100
|
+
pub_date&.to_s
|
|
101
|
+
end
|
|
102
|
+
|
|
62
103
|
# String for displaying the imprint statement(s).
|
|
63
104
|
# @return [String, nil]
|
|
64
105
|
# @see CocinaDisplay::Imprint#to_s
|
|
@@ -108,13 +149,7 @@ module CocinaDisplay
|
|
|
108
149
|
# Prefers events where the date was not encoded, if any.
|
|
109
150
|
# @return [Array<CocinaDisplay::Imprint>] The list of Imprint objects
|
|
110
151
|
def imprint_events
|
|
111
|
-
|
|
112
|
-
event.has_any_type?("publication", "creation", "capture", "copyright")
|
|
113
|
-
end.map do |event|
|
|
114
|
-
CocinaDisplay::Events::Imprint.new(event.cocina)
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
imprints.reject(&:date_encoding?).presence || imprints
|
|
152
|
+
events.filter(&:imprint?)
|
|
118
153
|
end
|
|
119
154
|
|
|
120
155
|
# All dates associated with the object via an event.
|
|
@@ -123,23 +158,16 @@ module CocinaDisplay
|
|
|
123
158
|
@event_dates ||= events.flat_map(&:dates)
|
|
124
159
|
end
|
|
125
160
|
|
|
126
|
-
# DisplayData for all
|
|
127
|
-
# @return [Array<CocinaDisplay::DisplayData>]
|
|
128
|
-
def event_note_display_data
|
|
129
|
-
CocinaDisplay::DisplayData.from_objects(events.flat_map(&:notes))
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
# DisplayData for all dates associated with events.
|
|
161
|
+
# DisplayData for all events associated with the object.
|
|
133
162
|
# @return [Array<CocinaDisplay::DisplayData>]
|
|
134
|
-
def
|
|
135
|
-
CocinaDisplay::DisplayData.from_objects(
|
|
163
|
+
def event_display_data
|
|
164
|
+
CocinaDisplay::DisplayData.from_objects(events)
|
|
136
165
|
end
|
|
137
166
|
|
|
138
|
-
# DisplayData for
|
|
167
|
+
# DisplayData for issuance, copyright, and other notes associated with events.
|
|
139
168
|
# @return [Array<CocinaDisplay::DisplayData>]
|
|
140
|
-
def
|
|
141
|
-
CocinaDisplay::DisplayData.
|
|
142
|
-
CocinaDisplay::DisplayData.from_strings(publisher_names, label: "Publisher")
|
|
169
|
+
def event_note_display_data
|
|
170
|
+
CocinaDisplay::DisplayData.from_objects(events.flat_map(&:notes))
|
|
143
171
|
end
|
|
144
172
|
|
|
145
173
|
# The earliest preferred publication date as a CocinaDisplay::Dates::Date object.
|
|
@@ -9,6 +9,8 @@ module CocinaDisplay
|
|
|
9
9
|
module Dates
|
|
10
10
|
# A date to be converted to a Date object.
|
|
11
11
|
class Date
|
|
12
|
+
include Comparable
|
|
13
|
+
|
|
12
14
|
# List of values that we shouldn't even attempt to parse.
|
|
13
15
|
UNPARSABLE_VALUES = ["0000-00-00", "9999", "uuuu", "[uuuu]"].freeze
|
|
14
16
|
|
|
@@ -110,6 +112,8 @@ module CocinaDisplay
|
|
|
110
112
|
end
|
|
111
113
|
|
|
112
114
|
# Compare this date to another {Date} or {DateRange} using its {sort_key}.
|
|
115
|
+
# @note Also supports `date1.between?(date2, date3)` via {Comparable}.
|
|
116
|
+
# @return [Integer, nil]
|
|
113
117
|
def <=>(other)
|
|
114
118
|
sort_key <=> other.sort_key if other.is_a?(Date) || other.is_a?(DateRange)
|
|
115
119
|
end
|
|
@@ -121,9 +125,14 @@ module CocinaDisplay
|
|
|
121
125
|
end
|
|
122
126
|
|
|
123
127
|
# The string representation of the date for display.
|
|
128
|
+
# Uses the raw value if the date was not encoded or couldn't be parsed.
|
|
124
129
|
# @return [String]
|
|
125
130
|
def to_s
|
|
126
|
-
|
|
131
|
+
if !parsed_date? || (!encoding? && value !~ /^-?\d+$/ && value !~ /^[\dXxu?-]{4}$/)
|
|
132
|
+
value.strip
|
|
133
|
+
else
|
|
134
|
+
qualified_value
|
|
135
|
+
end
|
|
127
136
|
end
|
|
128
137
|
|
|
129
138
|
# Label used to group the date for display.
|
|
@@ -300,9 +309,6 @@ module CocinaDisplay
|
|
|
300
309
|
# Defaults to [:day, :month, :year, :decade, :century, :unknown].
|
|
301
310
|
# @return [String]
|
|
302
311
|
def decoded_value(allowed_precisions: [:day, :month, :year, :decade, :century, :unknown])
|
|
303
|
-
if !parsed_date? || (!encoding? && value !~ /^-?\d+$/ && value !~ /^[\dXxu?-]{4}$/)
|
|
304
|
-
return value.strip
|
|
305
|
-
end
|
|
306
312
|
if date.is_a?(EDTF::Interval)
|
|
307
313
|
range = [
|
|
308
314
|
Date.format_date(date.min, date.min.precision, allowed_precisions),
|
|
@@ -377,7 +383,7 @@ module CocinaDisplay
|
|
|
377
383
|
when :unknown
|
|
378
384
|
"Unknown"
|
|
379
385
|
when :day
|
|
380
|
-
date.strftime("%B
|
|
386
|
+
date.strftime("%B %-d, %Y")
|
|
381
387
|
when :month
|
|
382
388
|
date.strftime("%B %Y")
|
|
383
389
|
when :year
|
|
@@ -40,11 +40,11 @@ module CocinaDisplay
|
|
|
40
40
|
@type = cocina["type"]
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
# The values of the start and stop dates
|
|
43
|
+
# The joined values of the start and stop dates.
|
|
44
44
|
# @see CocinaDisplay::Date#value
|
|
45
|
-
# @return [
|
|
45
|
+
# @return [String]
|
|
46
46
|
def value
|
|
47
|
-
[start&.value, stop&.value].compact
|
|
47
|
+
[start&.value, stop&.value].compact.uniq.join(" - ").strip
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
# Key used to sort this date range. Respects BCE/CE ordering and precision.
|
|
@@ -52,7 +52,7 @@ module CocinaDisplay
|
|
|
52
52
|
# @see CocinaDisplay::Date#sort_key
|
|
53
53
|
# @return [String]
|
|
54
54
|
def sort_key
|
|
55
|
-
[start&.sort_key, stop&.sort_key].compact.join
|
|
55
|
+
[start&.sort_key, stop&.sort_key].compact.join
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
# Base values of start/end as single string. Used for comparison/deduping.
|
|
@@ -133,7 +133,7 @@ module CocinaDisplay
|
|
|
133
133
|
"[#{decoded_value}]"
|
|
134
134
|
end
|
|
135
135
|
else
|
|
136
|
-
|
|
136
|
+
[start&.qualified_value, stop&.qualified_value].uniq.join(" - ").strip
|
|
137
137
|
end
|
|
138
138
|
end
|
|
139
139
|
|
|
@@ -2,6 +2,8 @@ module CocinaDisplay
|
|
|
2
2
|
module Events
|
|
3
3
|
# An event associated with an object, like publication.
|
|
4
4
|
class Event
|
|
5
|
+
include Comparable
|
|
6
|
+
|
|
5
7
|
attr_reader :cocina
|
|
6
8
|
|
|
7
9
|
# Initialize the event with Cocina event data.
|
|
@@ -10,6 +12,26 @@ module CocinaDisplay
|
|
|
10
12
|
@cocina = cocina
|
|
11
13
|
end
|
|
12
14
|
|
|
15
|
+
# Compare this {Event} to another {Event} using their {Date}s.
|
|
16
|
+
# @note Also supports `event1.between?(event2, event3)` via {Comparable}.
|
|
17
|
+
# @return [Integer, nil]
|
|
18
|
+
def <=>(other)
|
|
19
|
+
[unique_dates_for_display] <=> [other.unique_dates_for_display] if other.is_a?(Event)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# The display label for the event.
|
|
23
|
+
# Uses "Imprint" if the event is likely to represent an imprint statement.
|
|
24
|
+
# If the event consists solely of a date, uses the date's label.
|
|
25
|
+
# Capitalizes the event's type, or its first date's type if untyped.
|
|
26
|
+
# @return [String]
|
|
27
|
+
def label
|
|
28
|
+
return cocina["displayLabel"] if cocina["displayLabel"].present?
|
|
29
|
+
return "Imprint" if imprint?
|
|
30
|
+
return dates.map(&:label).first if date_only?
|
|
31
|
+
|
|
32
|
+
type&.capitalize || date_types.first&.capitalize || "Event"
|
|
33
|
+
end
|
|
34
|
+
|
|
13
35
|
# The declared type of the event, like "publication" or "creation".
|
|
14
36
|
# @see https://github.com/sul-dlss/cocina-models/blob/main/docs/description_types.md#event-types
|
|
15
37
|
# @note This can differ from the contained date types.
|
|
@@ -30,7 +52,7 @@ module CocinaDisplay
|
|
|
30
52
|
# @param match_type [String] The type to check against
|
|
31
53
|
# @return [Boolean]
|
|
32
54
|
def has_type?(match_type)
|
|
33
|
-
|
|
55
|
+
types.include?(match_type)
|
|
34
56
|
end
|
|
35
57
|
|
|
36
58
|
# True if the event or its dates have any of the provided types.
|
|
@@ -54,6 +76,13 @@ module CocinaDisplay
|
|
|
54
76
|
end
|
|
55
77
|
end
|
|
56
78
|
|
|
79
|
+
# True if this event is likely to represent an imprint.
|
|
80
|
+
# @note Unencoded dates or no dates often indicate an imprint statement.
|
|
81
|
+
# @return [Boolean]
|
|
82
|
+
def imprint?
|
|
83
|
+
(has_type?("publication") || types.empty?) && (dates.none?(&:encoding?) || dates.none?)
|
|
84
|
+
end
|
|
85
|
+
|
|
57
86
|
# All contributors associated with this event.
|
|
58
87
|
# @return [Array<CocinaDisplay::Contributor>]
|
|
59
88
|
def contributors
|
|
@@ -77,6 +106,97 @@ module CocinaDisplay
|
|
|
77
106
|
CocinaDisplay::Events::Note.new(note)
|
|
78
107
|
end
|
|
79
108
|
end
|
|
109
|
+
|
|
110
|
+
# String representation of the event using edition, dates, locations, and contributors.
|
|
111
|
+
# Format is inspired by typical imprint statements for books.
|
|
112
|
+
# @return [String]
|
|
113
|
+
# @example "2nd ed. - New York : John Doe, 1999"
|
|
114
|
+
def to_s
|
|
115
|
+
place_contrib = Utils.compact_and_join([place_str, contributor_str], delimiter: " : ")
|
|
116
|
+
note_place_contrib = Utils.compact_and_join([edition_note_str, place_contrib], delimiter: " - ")
|
|
117
|
+
Utils.compact_and_join([note_place_contrib, date_str, copyright_note_str], delimiter: ", ")
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Filter dates for uniqueness using base value according to predefined rules.
|
|
121
|
+
# 1. For a group of dates with the same base value, choose a single one
|
|
122
|
+
# 2. Prefer unencoded dates over encoded ones when choosing a single date
|
|
123
|
+
# 3. Remove date ranges that duplicate any unencoded non-range dates
|
|
124
|
+
# @return [Array<CocinaDisplay::Dates::Date>]
|
|
125
|
+
# @see CocinaDisplay::Dates::Date#base_value
|
|
126
|
+
# @see https://consul.stanford.edu/display/chimera/MODS+display+rules#MODSdisplayrules-3b.%3CoriginInfo%3E
|
|
127
|
+
def unique_dates_for_display
|
|
128
|
+
# Choose a single date for each group with the same base value
|
|
129
|
+
deduped_dates = dates.group_by(&:base_value).map do |base_value, group|
|
|
130
|
+
if (unencoded = group.reject(&:encoding?)).any?
|
|
131
|
+
unencoded.first
|
|
132
|
+
else
|
|
133
|
+
group.first
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Remove any ranges that duplicate part of an unencoded non-range date
|
|
138
|
+
ranges, singles = deduped_dates.partition { |date| date.is_a?(CocinaDisplay::Dates::DateRange) }
|
|
139
|
+
unencoded_singles_dates = singles.reject(&:encoding?).flat_map(&:to_a)
|
|
140
|
+
ranges.reject! { |date_range| unencoded_singles_dates.any? { |date| date_range.as_range.include?(date) } }
|
|
141
|
+
|
|
142
|
+
(singles + ranges).sort
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Filter locations to display according to predefined rules.
|
|
146
|
+
# 1. Prefer unencoded locations (plain value) over encoded ones
|
|
147
|
+
# 2. If no unencoded locations but there are MARC country codes, decode them
|
|
148
|
+
# 3. Keep only unique locations after decoding
|
|
149
|
+
def locations_for_display
|
|
150
|
+
unencoded_locs, encoded_locs = locations.partition { |loc| loc.unencoded_value? }
|
|
151
|
+
locs_for_display = unencoded_locs.presence || encoded_locs
|
|
152
|
+
locs_for_display.map(&:to_s).compact_blank.uniq
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Union of event's type and its date types.
|
|
156
|
+
# Used for imprint detection and display decisions.
|
|
157
|
+
# @return [Array<String>]
|
|
158
|
+
def types
|
|
159
|
+
[type, *date_types].compact
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
private
|
|
163
|
+
|
|
164
|
+
# Does this event include no rendered information other than its date?
|
|
165
|
+
# @note If true, the label will be "[type] date" instead of just "[type]".
|
|
166
|
+
# @return [Boolean]
|
|
167
|
+
def date_only?
|
|
168
|
+
to_s == date_str
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# The date portion of the imprint statement, comprising all unique dates.
|
|
172
|
+
# @return [String]
|
|
173
|
+
def date_str
|
|
174
|
+
Utils.compact_and_join(unique_dates_for_display.map(&:to_s), delimiter: "; ")
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Edition notes associated with the event as a single string.
|
|
178
|
+
# @return [String]
|
|
179
|
+
def edition_note_str
|
|
180
|
+
Utils.compact_and_join(notes.filter { |note| note.type == "edition" }.map(&:to_s), delimiter: ", ")
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Copyright notes associated with the event as a single string.
|
|
184
|
+
# @return [String]
|
|
185
|
+
def copyright_note_str
|
|
186
|
+
Utils.compact_and_join(notes.filter { |note| note.type == "copyright statement" }.map(&:to_s), delimiter: ", ")
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# All contributors associated with the event as a single string.
|
|
190
|
+
# @return [String]
|
|
191
|
+
def contributor_str
|
|
192
|
+
Utils.compact_and_join(contributors.map(&:display_name), delimiter: " : ")
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# The place of publication, combining all location values.
|
|
196
|
+
# @return [String]
|
|
197
|
+
def place_str
|
|
198
|
+
Utils.compact_and_join(locations_for_display, delimiter: " : ")
|
|
199
|
+
end
|
|
80
200
|
end
|
|
81
201
|
end
|
|
82
202
|
end
|
|
@@ -10,9 +10,21 @@ module CocinaDisplay
|
|
|
10
10
|
@cocina = cocina
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
#
|
|
13
|
+
# Contents of the note.
|
|
14
|
+
# @note Issuance and frequency notes are lowercased for consistency.
|
|
14
15
|
# @return [String, nil]
|
|
15
16
|
def to_s
|
|
17
|
+
case type
|
|
18
|
+
when "issuance", "frequency"
|
|
19
|
+
value&.downcase
|
|
20
|
+
else
|
|
21
|
+
value
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# The contents of the note.
|
|
26
|
+
# @return [String, nil]
|
|
27
|
+
def value
|
|
16
28
|
cocina["value"].presence
|
|
17
29
|
end
|
|
18
30
|
|
|
@@ -33,7 +33,7 @@ module CocinaDisplay
|
|
|
33
33
|
# Prefers the URI representation where present.
|
|
34
34
|
# @return [String, nil]
|
|
35
35
|
def value
|
|
36
|
-
cocina["uri"]
|
|
36
|
+
cocina["uri"] || cocina["value"]
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
# The "identifying" part of the identifier.
|
|
@@ -42,7 +42,18 @@ module CocinaDisplay
|
|
|
42
42
|
# 10.1234/doi
|
|
43
43
|
# @return [String, nil]
|
|
44
44
|
def identifier
|
|
45
|
-
|
|
45
|
+
# the uri property is a valid uri, but the value isn't necessarily a valid uri
|
|
46
|
+
uri = if cocina["uri"]
|
|
47
|
+
URI(cocina["uri"])
|
|
48
|
+
elsif cocina["value"]
|
|
49
|
+
begin
|
|
50
|
+
URI(cocina["value"])
|
|
51
|
+
rescue URI::InvalidURIError
|
|
52
|
+
nil
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
uri&.path&.delete_prefix("/")
|
|
46
57
|
end
|
|
47
58
|
|
|
48
59
|
# The identifier as a URI, if available.
|
|
@@ -55,11 +55,15 @@ module CocinaDisplay
|
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
# Nested display data for the related resource.
|
|
58
|
-
# Combines titles, contributors, notes, and access information.
|
|
59
|
-
# @note Used for extended display of citations, e.g. on hp566jq8781.
|
|
60
58
|
# @return [Array<DisplayData>]
|
|
61
59
|
def display_data
|
|
62
|
-
title_display_data +
|
|
60
|
+
title_display_data +
|
|
61
|
+
contributor_display_data +
|
|
62
|
+
event_display_data +
|
|
63
|
+
general_note_display_data +
|
|
64
|
+
preferred_citation_display_data +
|
|
65
|
+
access_display_data +
|
|
66
|
+
identifier_display_data
|
|
63
67
|
end
|
|
64
68
|
|
|
65
69
|
private
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cocina_display
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nick Budak
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-03-
|
|
10
|
+
date: 2026-03-25 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: janeway-jsonpath
|
|
@@ -269,7 +269,6 @@ files:
|
|
|
269
269
|
- lib/cocina_display/description/url.rb
|
|
270
270
|
- lib/cocina_display/display_data.rb
|
|
271
271
|
- lib/cocina_display/events/event.rb
|
|
272
|
-
- lib/cocina_display/events/imprint.rb
|
|
273
272
|
- lib/cocina_display/events/location.rb
|
|
274
273
|
- lib/cocina_display/events/note.rb
|
|
275
274
|
- lib/cocina_display/forms/form.rb
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module CocinaDisplay
|
|
4
|
-
module Events
|
|
5
|
-
# Wrapper for Cocina events used to generate an imprint statement for display.
|
|
6
|
-
class Imprint < Event
|
|
7
|
-
# The entire imprint statement formatted as a string for display.
|
|
8
|
-
# @return [String]
|
|
9
|
-
def to_s
|
|
10
|
-
place_pub = Utils.compact_and_join([place_str, publisher_str], delimiter: " : ")
|
|
11
|
-
edition_place_pub = Utils.compact_and_join([edition_str, place_pub], delimiter: " - ")
|
|
12
|
-
Utils.compact_and_join([edition_place_pub, date_str], delimiter: ", ")
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
# Were any of the dates encoded?
|
|
16
|
-
# Used to detect which event(s) most likely represent the actual imprint(s).
|
|
17
|
-
def date_encoding?
|
|
18
|
-
dates.any?(&:encoding?)
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
private
|
|
22
|
-
|
|
23
|
-
# The date portion of the imprint statement, comprising all unique dates.
|
|
24
|
-
# @return [String]
|
|
25
|
-
def date_str
|
|
26
|
-
Utils.compact_and_join(unique_dates_for_display.map(&:qualified_value), delimiter: "; ")
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# The editions portion of the imprint statement, combining all edition notes.
|
|
30
|
-
# @return [String]
|
|
31
|
-
def edition_str
|
|
32
|
-
Utils.compact_and_join(Janeway.enum_for("$.note[?@.type == 'edition'].value", cocina))
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# The place of publication, combining all location values.
|
|
36
|
-
# @return [String]
|
|
37
|
-
def place_str
|
|
38
|
-
Utils.compact_and_join(locations_for_display, delimiter: " : ")
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# The publisher information, combining all name values for publishers.
|
|
42
|
-
# @return [String]
|
|
43
|
-
def publisher_str
|
|
44
|
-
Utils.compact_and_join(publishers.map(&:display_name), delimiter: " : ")
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# All publishers associated with this imprint.
|
|
48
|
-
# @return [Array<CocinaDisplay::Contributor>]
|
|
49
|
-
# @see CocinaDisplay::Contributor#publisher?
|
|
50
|
-
def publishers
|
|
51
|
-
contributors.filter(&:publisher?)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# Filter dates for uniqueness using base value according to predefined rules.
|
|
55
|
-
# 1. For a group of dates with the same base value, choose a single one
|
|
56
|
-
# 2. Prefer unencoded dates over encoded ones when choosing a single date
|
|
57
|
-
# 3. Remove date ranges that duplicate any unencoded non-range dates
|
|
58
|
-
# @return [Array<CocinaDisplay::Dates::Date>]
|
|
59
|
-
# @see CocinaDisplay::Dates::Date#base_value
|
|
60
|
-
# @see https://consul.stanford.edu/display/chimera/MODS+display+rules#MODSdisplayrules-3b.%3CoriginInfo%3E
|
|
61
|
-
def unique_dates_for_display
|
|
62
|
-
# Choose a single date for each group with the same base value
|
|
63
|
-
deduped_dates = dates.group_by(&:base_value).map do |base_value, group|
|
|
64
|
-
if (unencoded = group.reject(&:encoding?)).any?
|
|
65
|
-
unencoded.first
|
|
66
|
-
else
|
|
67
|
-
group.first
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# Remove any ranges that duplicate part of an unencoded non-range date
|
|
72
|
-
ranges, singles = deduped_dates.partition { |date| date.is_a?(CocinaDisplay::Dates::DateRange) }
|
|
73
|
-
unencoded_singles_dates = singles.reject(&:encoding?).flat_map(&:to_a)
|
|
74
|
-
ranges.reject! { |date_range| unencoded_singles_dates.any? { |date| date_range.as_range.include?(date) } }
|
|
75
|
-
|
|
76
|
-
(singles + ranges).sort
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Filter locations to display according to predefined rules.
|
|
80
|
-
# 1. Prefer unencoded locations (plain value) over encoded ones
|
|
81
|
-
# 2. If no unencoded locations but there are MARC country codes, decode them
|
|
82
|
-
# 3. Keep only unique locations after decoding
|
|
83
|
-
def locations_for_display
|
|
84
|
-
unencoded_locs, encoded_locs = locations.partition { |loc| loc.unencoded_value? }
|
|
85
|
-
locs_for_display = unencoded_locs.presence || encoded_locs
|
|
86
|
-
locs_for_display.map(&:to_s).compact_blank.uniq
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|