cocina_display 0.2.0 → 0.4.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.
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support"
4
+ require "active_support/core_ext/object/blank"
5
+ require "active_support/core_ext/array/conversions"
6
+
7
+ require_relative "utils"
8
+
9
+ module CocinaDisplay
10
+ # A contributor to a work, such as an author or publisher.
11
+ class Contributor
12
+ attr_reader :cocina
13
+
14
+ # Initialize a Contributor object with Cocina structured data.
15
+ # @param cocina [Hash] The Cocina structured data for the contributor.
16
+ def initialize(cocina)
17
+ @cocina = cocina
18
+ end
19
+
20
+ # String representation of the contributor, including name and role.
21
+ # Used for debugging and logging.
22
+ # @return [String]
23
+ def to_s
24
+ Utils.compact_and_join([display_name, display_role], delimiter: ": ")
25
+ end
26
+
27
+ # Is this contributor a human?
28
+ # @return [Boolean]
29
+ def person?
30
+ cocina["type"] == "person"
31
+ end
32
+
33
+ # Is this contributor an organization?
34
+ # @return [Boolean]
35
+ def organization?
36
+ cocina["type"] == "organization"
37
+ end
38
+
39
+ # Is this contributor a conference?
40
+ # @return [Boolean]
41
+ def conference?
42
+ cocina["type"] == "conference"
43
+ end
44
+
45
+ # Is this contributor marked as primary?
46
+ # @return [Boolean]
47
+ def primary?
48
+ cocina["status"] == "primary"
49
+ end
50
+
51
+ # Does this contributor have a role that indicates they are an author?
52
+ # @return [Boolean]
53
+ def author?
54
+ roles.any? { |role| role["value"] =~ /(author|creator)/i }
55
+ end
56
+
57
+ # Does this contributor have any roles defined?
58
+ # @return [Boolean]
59
+ def role?
60
+ roles.any?
61
+ end
62
+
63
+ # The display name for the contributor as a string.
64
+ # Uses the first name if multiple names are present.
65
+ # @param with_date [Boolean] Include life dates, if present
66
+ # @return [String]
67
+ def display_name(with_date: false)
68
+ names.map { |name| name.display_str(with_date: with_date) }.first
69
+ end
70
+
71
+ # A string representation of the contributor's roles, formatted for display.
72
+ # If there are multiple roles, they are joined with commas.
73
+ # @return [String]
74
+ def display_role
75
+ roles.map { |role| role["value"] }.to_sentence
76
+ end
77
+
78
+ private
79
+
80
+ # All names in the Cocina as Name objects.
81
+ # @return [Array<Name>]
82
+ def names
83
+ @names ||= Array(cocina["name"]).map { |name| Name.new(name) }
84
+ end
85
+
86
+ # All roles in the Cocina structured data.
87
+ # @return [Array<Hash>]
88
+ def roles
89
+ Array(cocina["role"])
90
+ end
91
+
92
+ # A name associated with a contributor.
93
+ class Name
94
+ attr_reader :cocina
95
+
96
+ # Initialize a Name object with Cocina structured data.
97
+ # @param cocina [Hash] The Cocina structured data for the name.
98
+ def initialize(cocina)
99
+ @cocina = cocina
100
+ end
101
+
102
+ # The display string for the name, optionally including life dates.
103
+ # @param with_date [Boolean] Include life dates, if present
104
+ # @return [String]
105
+ # @example no dates
106
+ # name.display_name # => "King, Martin Luther, Jr."
107
+ # @example with dates
108
+ # name.display_name(with_date: true) # => "King, Martin Luther, Jr., 1929-1968"
109
+ def display_str(with_date: false)
110
+ if dates_str.present? && with_date
111
+ Utils.compact_and_join([full_name_str, dates_str], delimiter: ", ")
112
+ else
113
+ full_name_str
114
+ end
115
+ end
116
+
117
+ private
118
+
119
+ # The full name as a string.
120
+ # If any names were marked as "display", prefer those.
121
+ # Otherwise, combine all name components.
122
+ # @return [String]
123
+ def full_name_str
124
+ display_name_str.presence || Utils.compact_and_join(name_components, delimiter: ", ")
125
+ end
126
+
127
+ # Flattened form of any names explicitly marked as "display name".
128
+ # @return [String]
129
+ def display_name_str
130
+ Utils.compact_and_join(Array(name_values["display"]), delimiter: ", ")
131
+ end
132
+
133
+ # List of all name components.
134
+ # If any of forename, surname, or term of address are present, those are used.
135
+ # Otherwise, fall back to any names explicitly marked as "name" or untyped.
136
+ # @return [Array<String>]
137
+ def name_components
138
+ [surname_str, forename_ordinal_str, terms_of_address_str].compact_blank.presence || Array(name_values["name"])
139
+ end
140
+
141
+ # Flatten all forenames and ordinals into a single string.
142
+ # @return [String]
143
+ def forename_ordinal_str
144
+ Utils.compact_and_join(Array(name_values["forename"]) + Array(name_values["ordinal"]), delimiter: " ")
145
+ end
146
+
147
+ # Flatten all terms of address into a single string.
148
+ # @return [String]
149
+ def terms_of_address_str
150
+ Utils.compact_and_join(Array(name_values["term of address"]), delimiter: ", ")
151
+ end
152
+
153
+ # Flatten all surnames into a single string.
154
+ # @return [String]
155
+ def surname_str
156
+ Utils.compact_and_join(Array(name_values["surname"]), delimiter: " ")
157
+ end
158
+
159
+ # Flatten all life and activity dates into a single string.
160
+ # @return [String]
161
+ def dates_str
162
+ Utils.compact_and_join(Array(name_values["life dates"]) + Array(name_values["activity dates"]), delimiter: ", ")
163
+ end
164
+
165
+ # A hash mapping destructured name types to their values.
166
+ # Name values with no type are grouped under "name".
167
+ # @return [Hash<String, Array<String>>]
168
+ # @see https://github.com/sul-dlss/cocina-models/blob/main/docs/description_types.md#contributor-name-part-types-for-structured-value
169
+ # @note Currently we do nothing with "alternative", "inverted full name", "pseudonym", and "transliteration" types.
170
+ def name_values
171
+ Utils.flatten_structured_values(cocina).each_with_object({}) do |node, hash|
172
+ type = node["type"] || "name"
173
+ hash[type] ||= []
174
+ hash[type] << node["value"]
175
+ end.compact_blank
176
+ end
177
+ end
178
+ end
179
+ end