cocina_display 0.4.0 → 0.5.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/lib/cocina_display/cocina_record.rb +11 -65
- data/lib/cocina_display/concerns/access.rb +71 -0
- data/lib/cocina_display/concerns/events.rb +14 -0
- data/lib/cocina_display/concerns/identifiers.rb +11 -3
- data/lib/cocina_display/concerns/subjects.rb +58 -0
- data/lib/cocina_display/contributor.rb +12 -6
- data/lib/cocina_display/subject.rb +127 -0
- data/lib/cocina_display/utils.rb +22 -6
- data/lib/cocina_display/version.rb +1 -1
- metadata +7 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5662a1512975323d0324c31021fcef9cd4ff337ea8acfb5f6a3352d4abf0e303
|
4
|
+
data.tar.gz: fe0444d5c104e393718a996260346f98691f2e84585483bb7a2062acab347bfb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8ff64dadfe8b2492f23d38bcc320e8de4bc6100af09adfd93d25df0aeb84c91585072dbcbb6cafc297bd503d773ee7b6bf35281fdda1d573e65f94fcb866a5a0
|
7
|
+
data.tar.gz: 29c42ee65d59dbac907159435f8c9924742cc4d598e656a787dee8cbfceaab8231f2bbf8cb87d14ee180ed586fc51b4d498e8009b6fd5442442d8b0932ebd30d
|
@@ -11,6 +11,8 @@ require_relative "concerns/events"
|
|
11
11
|
require_relative "concerns/contributors"
|
12
12
|
require_relative "concerns/identifiers"
|
13
13
|
require_relative "concerns/titles"
|
14
|
+
require_relative "concerns/access"
|
15
|
+
require_relative "concerns/subjects"
|
14
16
|
|
15
17
|
module CocinaDisplay
|
16
18
|
# Public Cocina metadata for an SDR object, as fetched from PURL.
|
@@ -19,6 +21,8 @@ module CocinaDisplay
|
|
19
21
|
include CocinaDisplay::Concerns::Contributors
|
20
22
|
include CocinaDisplay::Concerns::Identifiers
|
21
23
|
include CocinaDisplay::Concerns::Titles
|
24
|
+
include CocinaDisplay::Concerns::Access
|
25
|
+
include CocinaDisplay::Concerns::Subjects
|
22
26
|
|
23
27
|
# Fetch a public Cocina document from PURL and create a CocinaRecord.
|
24
28
|
# @note This is intended to be used in development or testing only.
|
@@ -73,6 +77,13 @@ module CocinaDisplay
|
|
73
77
|
cocina_doc["type"].split("/").last
|
74
78
|
end
|
75
79
|
|
80
|
+
# Primary processing label for the object.
|
81
|
+
# @note This may or may not be the same as the title.
|
82
|
+
# @return [String, nil]
|
83
|
+
def label
|
84
|
+
cocina_doc["label"]
|
85
|
+
end
|
86
|
+
|
76
87
|
# True if the object is a collection.
|
77
88
|
# @return [Boolean]
|
78
89
|
def collection?
|
@@ -90,70 +101,5 @@ module CocinaDisplay
|
|
90
101
|
def files
|
91
102
|
path("$.structural.contains[*].structural.contains[*]")
|
92
103
|
end
|
93
|
-
|
94
|
-
# The PURL URL for this object.
|
95
|
-
# @return [String]
|
96
|
-
# @example
|
97
|
-
# record.purl_url #=> "https://purl.stanford.edu/bx658jh7339"
|
98
|
-
def purl_url
|
99
|
-
cocina_doc.dig("description", "purl") || "https://purl.stanford.edu/#{bare_druid}"
|
100
|
-
end
|
101
|
-
|
102
|
-
# The URL to the PURL environment this object is from.
|
103
|
-
# @note Objects accessed via UAT will still have a production PURL base URL.
|
104
|
-
# @return [String]
|
105
|
-
# @example
|
106
|
-
# record.purl_base_url #=> "https://purl.stanford.edu"
|
107
|
-
def purl_base_url
|
108
|
-
URI(purl_url).origin
|
109
|
-
end
|
110
|
-
|
111
|
-
# The URL to the stacks environment this object is shelved in.
|
112
|
-
# Corresponds to the PURL environment.
|
113
|
-
# @see purl_base_url
|
114
|
-
# @return [String]
|
115
|
-
# @example
|
116
|
-
# record.stacks_base_url #=> "https://stacks.stanford.edu"
|
117
|
-
def stacks_base_url
|
118
|
-
if purl_base_url == "https://sul-purl-stage.stanford.edu"
|
119
|
-
"https://sul-stacks-stage.stanford.edu"
|
120
|
-
else
|
121
|
-
"https://stacks.stanford.edu"
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
# The oEmbed URL for the object, optionally with additional parameters.
|
126
|
-
# Corresponds to the PURL environment.
|
127
|
-
# @param params [Hash] Additional parameters to include in the oEmbed URL.
|
128
|
-
# @return [String]
|
129
|
-
# @return [nil] if the object is a collection.
|
130
|
-
# @example Generate an oEmbed URL for the viewer and hide the title
|
131
|
-
# record.oembed_url(hide_title: true) #=> "https://purl.stanford.edu/bx658jh7339/embed.json?hide_title=true"
|
132
|
-
def oembed_url(params: {})
|
133
|
-
return if collection?
|
134
|
-
|
135
|
-
params[:url] ||= purl_url
|
136
|
-
"#{purl_base_url}/embed.json?#{params.to_query}"
|
137
|
-
end
|
138
|
-
|
139
|
-
# The download URL to get the entire object as a .zip file.
|
140
|
-
# Stacks generates the .zip for the object on request.
|
141
|
-
# @return [String]
|
142
|
-
# @example
|
143
|
-
# record.download_url #=> "https://stacks.stanford.edu/object/bx658jh7339"
|
144
|
-
def download_url
|
145
|
-
"#{stacks_base_url}/object/#{bare_druid}"
|
146
|
-
end
|
147
|
-
|
148
|
-
# The IIIF manifest URL for the object.
|
149
|
-
# PURL generates the IIIF manifest.
|
150
|
-
# @param version [Integer] The IIIF presentation spec version to use (3 or 2).
|
151
|
-
# @return [String]
|
152
|
-
# @example
|
153
|
-
# record.iiif_manifest_url #=> "https://purl.stanford.edu/bx658jh7339/iiif3/manifest"
|
154
|
-
def iiif_manifest_url(version: 3)
|
155
|
-
iiif_path = (version == 3) ? "iiif3" : "iiif"
|
156
|
-
"#{purl_url}/#{iiif_path}/manifest"
|
157
|
-
end
|
158
104
|
end
|
159
105
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module CocinaDisplay
|
2
|
+
module Concerns
|
3
|
+
# Methods that generate URLs to access an object.
|
4
|
+
module Access
|
5
|
+
# The PURL URL for this object.
|
6
|
+
# @return [String]
|
7
|
+
# @example
|
8
|
+
# record.purl_url #=> "https://purl.stanford.edu/bx658jh7339"
|
9
|
+
def purl_url
|
10
|
+
cocina_doc.dig("description", "purl") || "https://purl.stanford.edu/#{bare_druid}"
|
11
|
+
end
|
12
|
+
|
13
|
+
# The URL to the PURL environment this object is from.
|
14
|
+
# @note Objects accessed via UAT will still have a production PURL base URL.
|
15
|
+
# @return [String]
|
16
|
+
# @example
|
17
|
+
# record.purl_base_url #=> "https://purl.stanford.edu"
|
18
|
+
def purl_base_url
|
19
|
+
URI(purl_url).origin
|
20
|
+
end
|
21
|
+
|
22
|
+
# The URL to the stacks environment this object is shelved in.
|
23
|
+
# Corresponds to the PURL environment.
|
24
|
+
# @see purl_base_url
|
25
|
+
# @return [String]
|
26
|
+
# @example
|
27
|
+
# record.stacks_base_url #=> "https://stacks.stanford.edu"
|
28
|
+
def stacks_base_url
|
29
|
+
if purl_base_url == "https://sul-purl-stage.stanford.edu"
|
30
|
+
"https://sul-stacks-stage.stanford.edu"
|
31
|
+
else
|
32
|
+
"https://stacks.stanford.edu"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# The oEmbed URL for the object, optionally with additional parameters.
|
37
|
+
# Corresponds to the PURL environment.
|
38
|
+
# @param params [Hash] Additional parameters to include in the oEmbed URL.
|
39
|
+
# @return [String]
|
40
|
+
# @return [nil] if the object is a collection.
|
41
|
+
# @example Generate an oEmbed URL for the viewer and hide the title
|
42
|
+
# record.oembed_url(hide_title: true) #=> "https://purl.stanford.edu/bx658jh7339/embed.json?hide_title=true"
|
43
|
+
def oembed_url(params: {})
|
44
|
+
return if collection?
|
45
|
+
|
46
|
+
params[:url] ||= purl_url
|
47
|
+
"#{purl_base_url}/embed.json?#{params.to_query}"
|
48
|
+
end
|
49
|
+
|
50
|
+
# The download URL to get the entire object as a .zip file.
|
51
|
+
# Stacks generates the .zip for the object on request.
|
52
|
+
# @return [String]
|
53
|
+
# @example
|
54
|
+
# record.download_url #=> "https://stacks.stanford.edu/object/bx658jh7339"
|
55
|
+
def download_url
|
56
|
+
"#{stacks_base_url}/object/#{bare_druid}"
|
57
|
+
end
|
58
|
+
|
59
|
+
# The IIIF manifest URL for the object.
|
60
|
+
# PURL generates the IIIF manifest.
|
61
|
+
# @param version [Integer] The IIIF presentation spec version to use (3 or 2).
|
62
|
+
# @return [String]
|
63
|
+
# @example
|
64
|
+
# record.iiif_manifest_url #=> "https://purl.stanford.edu/bx658jh7339/iiif3/manifest"
|
65
|
+
def iiif_manifest_url(version: 3)
|
66
|
+
iiif_path = (version == 3) ? "iiif3" : "iiif"
|
67
|
+
"#{purl_url}/#{iiif_path}/manifest"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -41,6 +41,20 @@ module CocinaDisplay
|
|
41
41
|
pub_date_edtf(ignore_qualified: ignore_qualified)&.year
|
42
42
|
end
|
43
43
|
|
44
|
+
# The range of preferred publication years as an array of integers.
|
45
|
+
# Considers publication, creation, and capture dates in that order.
|
46
|
+
# Prefers dates marked as primary and those with a declared encoding.
|
47
|
+
# @param ignore_qualified [Boolean] Reject qualified dates (e.g. approximate)
|
48
|
+
# @return [Array<Integer>, nil]
|
49
|
+
# @note 6 BCE will appear as -5; 4 CE will appear as 4.
|
50
|
+
def pub_year_int_range(ignore_qualified: false)
|
51
|
+
date = pub_date(ignore_qualified: ignore_qualified)
|
52
|
+
return unless date
|
53
|
+
|
54
|
+
date = date.as_interval if date.is_a? CocinaDisplay::Dates::DateRange
|
55
|
+
date.to_a.map(&:year).compact.uniq.sort
|
56
|
+
end
|
57
|
+
|
44
58
|
# String for displaying the earliest preferred publication year or range.
|
45
59
|
# Considers publication, creation, and capture dates in that order.
|
46
60
|
# Prefers dates marked as primary and those with a declared encoding.
|
@@ -40,11 +40,19 @@ module CocinaDisplay
|
|
40
40
|
|
41
41
|
# The HRID of the item in FOLIO, if defined.
|
42
42
|
# @note This doesn't imply the object is available in Searchworks at this ID.
|
43
|
+
# @param [refresh] [Boolean] Filter to links with refresh set to this value.
|
43
44
|
# @return [String, nil]
|
44
|
-
# @example
|
45
|
+
# @example With a link regardless of refresh:
|
45
46
|
# record.folio_hrid #=> "a12845814"
|
46
|
-
|
47
|
-
|
47
|
+
# @example With a link that is not refreshed:
|
48
|
+
# record.folio_hrid(refresh: true) #=> nil
|
49
|
+
def folio_hrid(refresh: nil)
|
50
|
+
link = path("$.identification.catalogLinks[?(@.catalog == 'folio')]").first
|
51
|
+
hrid = link&.dig("catalogRecordId")
|
52
|
+
return if hrid.blank?
|
53
|
+
return hrid if refresh.nil?
|
54
|
+
|
55
|
+
(link["refresh"] == refresh) ? hrid : nil
|
48
56
|
end
|
49
57
|
|
50
58
|
# The FOLIO HRID if defined, otherwise the bare DRUID.
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require_relative "../subject"
|
2
|
+
|
3
|
+
module CocinaDisplay
|
4
|
+
module Concerns
|
5
|
+
# Methods for extracting and formatting subject information.
|
6
|
+
module Subjects
|
7
|
+
# All unique subjects that are topics, formatted as strings for display.
|
8
|
+
# @return [Array<String>]
|
9
|
+
def subject_topics
|
10
|
+
subjects.filter { |s| s.type == "topic" }.map(&:display_str).uniq
|
11
|
+
end
|
12
|
+
|
13
|
+
# All unique subjects that are genres, formatted as strings for display.
|
14
|
+
# @return [Array<String>]
|
15
|
+
def subject_genres
|
16
|
+
subjects.filter { |s| s.type == "genre" }.map(&:display_str).uniq
|
17
|
+
end
|
18
|
+
|
19
|
+
# All unique subjects that are titles, formatted as strings for display.
|
20
|
+
# @return [Array<String>]
|
21
|
+
def subject_titles
|
22
|
+
subjects.filter { |s| s.type == "title" }.map(&:display_str).uniq
|
23
|
+
end
|
24
|
+
|
25
|
+
# All unique subjects that are date/time info, formatted as strings for display.
|
26
|
+
# @return [Array<String>]
|
27
|
+
def subject_temporal
|
28
|
+
subjects.filter { |s| s.type == "time" }.map(&:display_str).uniq
|
29
|
+
end
|
30
|
+
|
31
|
+
# All unique subjects that are occupations, formatted as strings for display.
|
32
|
+
# @return [Array<String>]
|
33
|
+
def subject_occupations
|
34
|
+
subjects.filter { |s| s.type == "occupation" }.map(&:display_str).uniq
|
35
|
+
end
|
36
|
+
|
37
|
+
# All unique subjects that are names of entities, formatted as strings for display.
|
38
|
+
# @note Multiple types are handled: person, family, organization, conference, etc.
|
39
|
+
# @see CocinaDisplay::NameSubject
|
40
|
+
# @return [Array<String>]
|
41
|
+
def subject_names
|
42
|
+
subjects.filter { |s| s.is_a? NameSubject }.map(&:display_str).uniq
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# All subjects, accessible as Subject objects.
|
48
|
+
# Checks both description.subject and description.geographic.subject.
|
49
|
+
# @return [Array<Subject>]
|
50
|
+
def subjects
|
51
|
+
@subjects ||= Enumerator::Chain.new(
|
52
|
+
path("$.description.subject[*]"),
|
53
|
+
path("$.description.geographic[*].subject[*]")
|
54
|
+
).map { |s| Subject.from_cocina(s) }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -100,6 +100,10 @@ module CocinaDisplay
|
|
100
100
|
end
|
101
101
|
|
102
102
|
# The display string for the name, optionally including life dates.
|
103
|
+
# Uses these values in order, if present:
|
104
|
+
# 1. Unstructured value
|
105
|
+
# 2. Any structured/parallel values marked as "display"
|
106
|
+
# 3. Joined structured values, optionally with life dates
|
103
107
|
# @param with_date [Boolean] Include life dates, if present
|
104
108
|
# @return [String]
|
105
109
|
# @example no dates
|
@@ -107,7 +111,11 @@ module CocinaDisplay
|
|
107
111
|
# @example with dates
|
108
112
|
# name.display_name(with_date: true) # => "King, Martin Luther, Jr., 1929-1968"
|
109
113
|
def display_str(with_date: false)
|
110
|
-
if
|
114
|
+
if cocina["value"].present?
|
115
|
+
cocina["value"]
|
116
|
+
elsif display_name_str.present?
|
117
|
+
display_name_str
|
118
|
+
elsif dates_str.present? && with_date
|
111
119
|
Utils.compact_and_join([full_name_str, dates_str], delimiter: ", ")
|
112
120
|
else
|
113
121
|
full_name_str
|
@@ -116,12 +124,10 @@ module CocinaDisplay
|
|
116
124
|
|
117
125
|
private
|
118
126
|
|
119
|
-
# The full name as a string.
|
120
|
-
# If any names were marked as "display", prefer those.
|
121
|
-
# Otherwise, combine all name components.
|
127
|
+
# The full name as a string, combining all name components.
|
122
128
|
# @return [String]
|
123
129
|
def full_name_str
|
124
|
-
|
130
|
+
Utils.compact_and_join(name_components, delimiter: ", ")
|
125
131
|
end
|
126
132
|
|
127
133
|
# Flattened form of any names explicitly marked as "display name".
|
@@ -168,7 +174,7 @@ module CocinaDisplay
|
|
168
174
|
# @see https://github.com/sul-dlss/cocina-models/blob/main/docs/description_types.md#contributor-name-part-types-for-structured-value
|
169
175
|
# @note Currently we do nothing with "alternative", "inverted full name", "pseudonym", and "transliteration" types.
|
170
176
|
def name_values
|
171
|
-
Utils.
|
177
|
+
Utils.flatten_nested_values(cocina).each_with_object({}) do |node, hash|
|
172
178
|
type = node["type"] || "name"
|
173
179
|
hash[type] ||= []
|
174
180
|
hash[type] << node["value"]
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require_relative "utils"
|
2
|
+
require_relative "contributor"
|
3
|
+
require_relative "title_builder"
|
4
|
+
require_relative "dates/date"
|
5
|
+
|
6
|
+
module CocinaDisplay
|
7
|
+
# Base class for subjects in Cocina structured data.
|
8
|
+
class Subject
|
9
|
+
attr_reader :cocina
|
10
|
+
|
11
|
+
# Extract the type of the subject from the Cocina structured data.
|
12
|
+
# If no top-level type, uses the first structuredValue type.
|
13
|
+
# @param cocina [Hash] The Cocina structured data for the subject.
|
14
|
+
# @return [String, nil] The type of the subject, or nil if none
|
15
|
+
# @see https://github.com/sul-dlss/cocina-models/blob/main/docs/description_types.md#subject-types
|
16
|
+
def self.detect_type(cocina)
|
17
|
+
cocina["type"] || Utils.flatten_nested_values(cocina).pick("type")
|
18
|
+
end
|
19
|
+
|
20
|
+
# Choose and create the appropriate Subject subclass based on type.
|
21
|
+
# @param cocina [Hash] The Cocina structured data for the subject.
|
22
|
+
# @return [Subject]
|
23
|
+
# @see detect_type
|
24
|
+
def self.from_cocina(cocina)
|
25
|
+
case detect_type(cocina)
|
26
|
+
when "person", "family", "organization", "conference", "event", "name"
|
27
|
+
NameSubject.new(cocina)
|
28
|
+
when "title"
|
29
|
+
TitleSubject.new(cocina)
|
30
|
+
when "time"
|
31
|
+
TemporalSubject.new(cocina)
|
32
|
+
# TODO: special handling for geospatial subjects
|
33
|
+
# when "map coordinates", "bounding box coordinates", "point coordinates"
|
34
|
+
else
|
35
|
+
Subject.new(cocina)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Initialize a Subject object with Cocina structured data.
|
40
|
+
# @param cocina [Hash] The Cocina structured data for the subject.
|
41
|
+
def initialize(cocina)
|
42
|
+
@cocina = cocina
|
43
|
+
end
|
44
|
+
|
45
|
+
# The type of the subject.
|
46
|
+
# If no top-level type, uses the first structuredValue type.
|
47
|
+
# @return [String, nil] The type of the subject, or nil if none
|
48
|
+
# @see detect_type
|
49
|
+
def type
|
50
|
+
self.class.detect_type(cocina)
|
51
|
+
end
|
52
|
+
|
53
|
+
# A string representation of the subject, formatted for display.
|
54
|
+
# Concatenates any structured values with an appropriate delimiter.
|
55
|
+
# Subclasses may override this for more specific formatting.
|
56
|
+
# @return [String]
|
57
|
+
def display_str
|
58
|
+
Utils.compact_and_join(descriptive_values, delimiter: delimiter)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
# Flatten any structured values into an array of Hashes with "value" keys.
|
64
|
+
# If no structured values, will return the top-level cocina data.
|
65
|
+
# @see Utils.flatten_nested_values
|
66
|
+
# @return [Array<Hash>] An array of Hashes representing all values.
|
67
|
+
def descriptive_values
|
68
|
+
Utils.flatten_nested_values(cocina).pluck("value")
|
69
|
+
end
|
70
|
+
|
71
|
+
# Delimiter to use for joining structured subject values.
|
72
|
+
# LCSH uses a comma (the default); catalog headings use " > ".
|
73
|
+
# @return [String]
|
74
|
+
def delimiter
|
75
|
+
if cocina["displayLabel"]&.downcase == "catalog heading"
|
76
|
+
" > "
|
77
|
+
else
|
78
|
+
", "
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# A subject representing a named entity.
|
84
|
+
class NameSubject < Subject
|
85
|
+
attr_reader :name
|
86
|
+
|
87
|
+
# Initialize a NameSubject object with Cocina structured data.
|
88
|
+
# @param cocina [Hash] The Cocina structured data for the subject.
|
89
|
+
def initialize(cocina)
|
90
|
+
super
|
91
|
+
@name = Contributor::Name.new(cocina)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Use the contributor name formatting rules for display.
|
95
|
+
# @return [String] The formatted name string, including life dates
|
96
|
+
# @see CocinaDisplay::Contributor::Name#display_str
|
97
|
+
def display_str
|
98
|
+
@name.display_str(with_date: true)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# A subject representing an entity with a title.
|
103
|
+
class TitleSubject < Subject
|
104
|
+
# Construct a title string to use for display.
|
105
|
+
# @see CocinaDisplay::TitleBuilder.build
|
106
|
+
# @note Unclear how often structured title subjects occur "in the wild".
|
107
|
+
# @return [String]
|
108
|
+
def display_str
|
109
|
+
TitleBuilder.build([cocina])
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# A subject representing a date and/or time.
|
114
|
+
class TemporalSubject < Subject
|
115
|
+
attr_reader :date
|
116
|
+
|
117
|
+
def initialize(cocina)
|
118
|
+
super
|
119
|
+
@date = Dates::Date.from_cocina(cocina)
|
120
|
+
end
|
121
|
+
|
122
|
+
# @return [String] The formatted date/time string for display
|
123
|
+
def display_str
|
124
|
+
@date.qualified_value
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/cocina_display/utils.rb
CHANGED
@@ -19,15 +19,31 @@ module CocinaDisplay
|
|
19
19
|
end.delete_suffix(delimiter)
|
20
20
|
end
|
21
21
|
|
22
|
-
# Recursively flatten structured values in Cocina metadata.
|
23
|
-
# Returns a list of hashes representing the "leaf" nodes with
|
22
|
+
# Recursively flatten structured and parallel values in Cocina metadata.
|
23
|
+
# Returns a list of hashes representing the "leaf" nodes with +value+ key.
|
24
24
|
# @return [Array<Hash>] List of node hashes with "value" present
|
25
|
-
|
25
|
+
# @param cocina [Hash] The Cocina structured data to flatten
|
26
|
+
# @param output [Array] Used for recursion, should be empty on first call
|
27
|
+
# @example simple value
|
28
|
+
# cocina = { "value" => "John Doe", "type" => "name" }
|
29
|
+
# Utils.flatten_nested_values(cocina)
|
30
|
+
# #=> [{"value" => "John Doe", "type" => "name"}]
|
31
|
+
# @example structured values
|
32
|
+
# cocina = { "structuredValue" => [{"value" => "foo"}, {"value" => "bar"}] }
|
33
|
+
# Utils.flatten_nested_values(cocina)
|
34
|
+
# #=> [{"value" => "foo"}, {"value" => "bar"}]
|
35
|
+
# @example parallel structured and simple values
|
36
|
+
# cocina = { "parallelValue" => [{"value" => "foo" }, { "structuredValue" => [{"value" => "bar"}, {"value" => "baz"}] }] }
|
37
|
+
# Utils.flatten_nested_values(cocina)
|
38
|
+
# #=> [{"value" => "foo"}, {"value" => "foo"}, {"value" => "baz"}]
|
39
|
+
def self.flatten_nested_values(cocina, output = [])
|
26
40
|
return [cocina] if cocina["value"].present?
|
27
|
-
return cocina.flat_map { |node|
|
28
|
-
return output unless (structured_values = Array(cocina["structuredValue"])).present?
|
41
|
+
return cocina.flat_map { |node| flatten_nested_values(node, output) } if cocina.is_a?(Array)
|
29
42
|
|
30
|
-
|
43
|
+
nested_values = Array(cocina["structuredValue"]) + Array(cocina["parallelValue"])
|
44
|
+
return output unless nested_values.any?
|
45
|
+
|
46
|
+
nested_values.flat_map { |node| flatten_nested_values(node, output) }
|
31
47
|
end
|
32
48
|
end
|
33
49
|
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: 0.
|
4
|
+
version: 0.5.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-07-
|
11
|
+
date: 2025-07-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: janeway-jsonpath
|
@@ -28,22 +28,16 @@ dependencies:
|
|
28
28
|
name: activesupport
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '8.0'
|
34
31
|
- - ">="
|
35
32
|
- !ruby/object:Gem::Version
|
36
|
-
version:
|
33
|
+
version: '7'
|
37
34
|
type: :runtime
|
38
35
|
prerelease: false
|
39
36
|
version_requirements: !ruby/object:Gem::Requirement
|
40
37
|
requirements:
|
41
|
-
- - "~>"
|
42
|
-
- !ruby/object:Gem::Version
|
43
|
-
version: '8.0'
|
44
38
|
- - ">="
|
45
39
|
- !ruby/object:Gem::Version
|
46
|
-
version:
|
40
|
+
version: '7'
|
47
41
|
- !ruby/object:Gem::Dependency
|
48
42
|
name: edtf
|
49
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -176,15 +170,18 @@ files:
|
|
176
170
|
- Rakefile
|
177
171
|
- lib/cocina_display.rb
|
178
172
|
- lib/cocina_display/cocina_record.rb
|
173
|
+
- lib/cocina_display/concerns/access.rb
|
179
174
|
- lib/cocina_display/concerns/contributors.rb
|
180
175
|
- lib/cocina_display/concerns/events.rb
|
181
176
|
- lib/cocina_display/concerns/identifiers.rb
|
177
|
+
- lib/cocina_display/concerns/subjects.rb
|
182
178
|
- lib/cocina_display/concerns/titles.rb
|
183
179
|
- lib/cocina_display/contributor.rb
|
184
180
|
- lib/cocina_display/dates/date.rb
|
185
181
|
- lib/cocina_display/dates/date_range.rb
|
186
182
|
- lib/cocina_display/imprint.rb
|
187
183
|
- lib/cocina_display/marc_country_codes.rb
|
184
|
+
- lib/cocina_display/subject.rb
|
188
185
|
- lib/cocina_display/title_builder.rb
|
189
186
|
- lib/cocina_display/utils.rb
|
190
187
|
- lib/cocina_display/version.rb
|