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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2310483e28449dd22935fae9e5e19097c69d133cf0e393f45a5a2d9f6b0d2182
4
- data.tar.gz: e63dd927ad075874c6f8b1b5ba5344be97d6bb659ebde892f7fd05220f874c7e
3
+ metadata.gz: f3f339ed91ca38de77fe91e83e50ba0dbdae117c810b831ed7597181b949dab7
4
+ data.tar.gz: 10e954dc692155b1c2ea3e219840431b4bae188b09399060973bc873b5dc9d63
5
5
  SHA512:
6
- metadata.gz: c9e72b785cdef2be3ff32d5b2ae06863a0385d9c48f70c888c9296bf8ecee6b1762cd155b8eb1ca2cbed2ada8ec6867ee7b7675de37be24c3ca823a3fbc6924b
7
- data.tar.gz: 0bfde5b69633b904e244be9728d225672a80392702878972a981c2c857201cf1a3940256b4c015bc6a97bd04abb0b2241bf2e8c3284ddd29d35565aec09249fc
6
+ metadata.gz: 04f6b7138b9079fb12f9455e5363fce77743f19e0ae30aa01a2d13a567182d184475304316bfa81d720970cbab77ac7d8854f29e62bbcf15df43cf95593898eb
7
+ data.tar.gz: 791231009d1e945f959bae16095d022306052bf0a8a02fe76f3d722343e79434469fd1bf5e32309a638afda378a8070e7f8dd259adce155106c307808a1602c4
@@ -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 statement
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(with_date: with_date)
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 names of contributors with that role.
73
- # @param with_date [Boolean] Include life dates, if present
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(with_date: false)
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 (excluding publisher).
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.except("publisher").map do |role, contributors|
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 range
54
- # CocinaRecord.fetch('bb099mt5053').pub_year_str #=> "1932 - 2012"
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
- imprints = events.filter do |event|
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 notes associated with events.
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 event_date_display_data
135
- CocinaDisplay::DisplayData.from_objects(event_dates)
163
+ def event_display_data
164
+ CocinaDisplay::DisplayData.from_objects(events)
136
165
  end
137
166
 
138
- # DisplayData for publisher and publication place.
167
+ # DisplayData for issuance, copyright, and other notes associated with events.
139
168
  # @return [Array<CocinaDisplay::DisplayData>]
140
- def publication_display_data
141
- CocinaDisplay::DisplayData.from_strings(publication_places, label: "Place") +
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
- qualified_value
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 %e, %Y")
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 as an array.
43
+ # The joined values of the start and stop dates.
44
44
  # @see CocinaDisplay::Date#value
45
- # @return [Array<String>]
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
- "#{start&.qualified_value} - #{stop&.qualified_value}"
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
- [type, *date_types].compact.include?(match_type)
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
- # The value of the note.
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"].presence || cocina["value"].presence
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
- URI(value).path.delete_prefix("/") if value
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 + contributor_display_data + general_note_display_data + preferred_citation_display_data + access_display_data + identifier_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
@@ -103,7 +103,7 @@ module CocinaDisplay
103
103
 
104
104
  # @return [String] The formatted date/time string for display
105
105
  def to_s
106
- date.qualified_value
106
+ date.to_s
107
107
  end
108
108
  end
109
109
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  # :nodoc:
4
4
  module CocinaDisplay
5
- VERSION = "1.12.2" # :nodoc:
5
+ VERSION = "2.0.0" # :nodoc:
6
6
  end
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: 1.12.2
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-06 00:00:00.000000000 Z
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