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,101 @@
|
|
1
|
+
module CocinaDisplay
|
2
|
+
# An identifier for an object or a descriptive value.
|
3
|
+
class Identifier
|
4
|
+
attr_reader :cocina
|
5
|
+
|
6
|
+
# Source URI values for common identifiers
|
7
|
+
# If you have the bare ID, you can always add it to these to make a valid URL
|
8
|
+
SOURCE_URIS = {
|
9
|
+
"ORCID" => "https://orcid.org/",
|
10
|
+
"ROR" => "https://ror.org/",
|
11
|
+
"DOI" => "https://doi.org/",
|
12
|
+
"ISNI" => "https://isni.org/"
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
# Initialize an Identifier from Cocina structured data.
|
16
|
+
# @param cocina [Hash]
|
17
|
+
def initialize(cocina)
|
18
|
+
@cocina = cocina
|
19
|
+
end
|
20
|
+
|
21
|
+
# String representation of the identifier.
|
22
|
+
# Prefers the URI representation where present.
|
23
|
+
# @return [String]
|
24
|
+
def to_s
|
25
|
+
uri || value
|
26
|
+
end
|
27
|
+
|
28
|
+
def ==(other)
|
29
|
+
other.is_a?(Identifier) && other.cocina == cocina
|
30
|
+
end
|
31
|
+
|
32
|
+
# The raw value from the Cocina structured data.
|
33
|
+
# Prefers the URI representation where present.
|
34
|
+
# @return [String, nil]
|
35
|
+
def value
|
36
|
+
cocina["uri"].presence || cocina["value"].presence
|
37
|
+
end
|
38
|
+
|
39
|
+
# The "identifying" part of the identifier.
|
40
|
+
# Tries to parse from the end of the URI.
|
41
|
+
# @example DOI
|
42
|
+
# 10.1234/doi
|
43
|
+
# @return [String, nil]
|
44
|
+
def identifier
|
45
|
+
URI(value).path.delete_prefix("/") if value
|
46
|
+
end
|
47
|
+
|
48
|
+
# The identifier as a URI, if available.
|
49
|
+
# Tries to construct a URI if the parts are available to do so.
|
50
|
+
# @example DOI
|
51
|
+
# https://doi.org/10.1234/doi
|
52
|
+
# @return [String, nil]
|
53
|
+
def uri
|
54
|
+
cocina["uri"].presence || ([scheme_uri.delete_suffix("/"), identifier].join("/") if scheme_uri && identifier)
|
55
|
+
end
|
56
|
+
|
57
|
+
# The type of the identifier, e.g. "DOI".
|
58
|
+
# @return [String, nil]
|
59
|
+
def type
|
60
|
+
("DOI" if doi?) || cocina["type"].presence
|
61
|
+
end
|
62
|
+
|
63
|
+
# The declared encoding of the identifier, if any.
|
64
|
+
# @return [String, nil]
|
65
|
+
def code
|
66
|
+
cocina.dig("source", "code").presence
|
67
|
+
end
|
68
|
+
|
69
|
+
# The base URI used to resolve the identifier, if any.
|
70
|
+
# @example DOI
|
71
|
+
# https://doi.org/
|
72
|
+
# @return [String, nil]
|
73
|
+
def scheme_uri
|
74
|
+
cocina.dig("source", "uri") || SOURCE_URIS[type]
|
75
|
+
end
|
76
|
+
|
77
|
+
# Label used to render the identifier for display.
|
78
|
+
# Uses a displayLabel if available, otherwise tries to look up via type.
|
79
|
+
# Falls back to a generic label for any unknown identifier types.
|
80
|
+
# @return [String]
|
81
|
+
def label
|
82
|
+
cocina["displayLabel"].presence ||
|
83
|
+
I18n.t(label_key, default: :identifier, scope: "cocina_display.field_label.identifier")
|
84
|
+
end
|
85
|
+
|
86
|
+
# Check if the identifier is a DOI.
|
87
|
+
# There are several indicators that could suggest this.
|
88
|
+
# @return [Boolean]
|
89
|
+
def doi?
|
90
|
+
cocina["type"]&.match?(/doi/i) || code == "doi" || cocina["uri"]&.include?("://doi.org")
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
# Key used for i18n lookup of the label, based on the type.
|
96
|
+
# @return [String, nil]
|
97
|
+
def label_key
|
98
|
+
type&.parameterize&.underscore
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CocinaDisplay
|
4
|
+
class JsonBackedRecord
|
5
|
+
# The parsed Cocina document.
|
6
|
+
# @return [Hash]
|
7
|
+
attr_reader :cocina_doc
|
8
|
+
|
9
|
+
# Initialize a CocinaRecord with a Cocina document hash.
|
10
|
+
# @param cocina_doc [Hash]
|
11
|
+
def initialize(cocina_doc)
|
12
|
+
@cocina_doc = cocina_doc
|
13
|
+
end
|
14
|
+
|
15
|
+
# Evaluate a JSONPath expression against the Cocina document.
|
16
|
+
# @return [Enumerator] An enumerator that yields results matching the expression.
|
17
|
+
# @param path_expression [String] The JSONPath expression to evaluate.
|
18
|
+
# @see https://www.rubydoc.info/gems/janeway-jsonpath/0.6.0/file/README.md
|
19
|
+
# @example Name values for contributors
|
20
|
+
# record.path("$.description.contributor.*.name.*.value").search #=> ["Smith, John", "ACME Corp."]
|
21
|
+
# @example Filtering nodes using a condition
|
22
|
+
# record.path("$.description.contributor[?(@.type == 'person')].name.*.value").search #=> ["Smith, John"]
|
23
|
+
def path(path_expression)
|
24
|
+
Janeway.enum_for(path_expression, cocina_doc)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -1,5 +1,4 @@
|
|
1
|
-
|
2
|
-
require_relative "vocabularies/searchworks_languages"
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
3
|
module CocinaDisplay
|
5
4
|
# A language associated with part or all of a Cocina object.
|
@@ -27,21 +26,20 @@ module CocinaDisplay
|
|
27
26
|
# Decoded name of the language based on the code, if present.
|
28
27
|
# @return [String, nil]
|
29
28
|
def decoded_value
|
30
|
-
Vocabularies::SEARCHWORKS_LANGUAGES[code]
|
29
|
+
Vocabularies::SEARCHWORKS_LANGUAGES[code] if searchworks_language?
|
30
|
+
end
|
31
|
+
|
32
|
+
# Display label for this field.
|
33
|
+
# @return [String]
|
34
|
+
def label
|
35
|
+
cocina["displayLabel"].presence || I18n.t("cocina_display.field_label.language")
|
31
36
|
end
|
32
37
|
|
33
38
|
# True if the language is recognized by Searchworks.
|
34
39
|
# @see CocinaDisplay::Vocabularies::SEARCHWORKS_LANGUAGES
|
35
40
|
# @return [Boolean]
|
36
41
|
def searchworks_language?
|
37
|
-
Vocabularies::SEARCHWORKS_LANGUAGES.value?(
|
38
|
-
end
|
39
|
-
|
40
|
-
# True if the language has a code sourced from the ISO 639 vocabulary.
|
41
|
-
# @see https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes
|
42
|
-
# @return [Boolean]
|
43
|
-
def iso_639?
|
44
|
-
cocina.dig("source", "code")&.start_with? "iso639"
|
42
|
+
Vocabularies::SEARCHWORKS_LANGUAGES.value?(cocina["value"]) || Vocabularies::SEARCHWORKS_LANGUAGES.key?(code)
|
45
43
|
end
|
46
44
|
end
|
47
45
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CocinaDisplay
|
4
|
+
# A license associated with part or all of a Cocina object.
|
5
|
+
# This is the license entity used for translating a license URL into text
|
6
|
+
# for display.
|
7
|
+
class License
|
8
|
+
LICENSE_FILE_PATH = File.join(__dir__, "..", "..", "config", "licenses.yml").freeze
|
9
|
+
|
10
|
+
attr_reader :description, :uri
|
11
|
+
|
12
|
+
# Raised when the license provided is not valid
|
13
|
+
class LegacyLicenseError < StandardError; end
|
14
|
+
|
15
|
+
# A hash of license URLs to their description attributes
|
16
|
+
# @return [Hash{String => Hash{String => String}}]
|
17
|
+
def self.licenses
|
18
|
+
@licenses || YAML.safe_load(ERB.new(File.read(LICENSE_FILE_PATH)).result)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Initialize a License from a license URL.
|
22
|
+
# @param url [String] The license URL.
|
23
|
+
# @raise [LegacyLicenseError] if the license URL is not in the config
|
24
|
+
def initialize(url:)
|
25
|
+
raise LegacyLicenseError unless License.licenses.key?(url)
|
26
|
+
|
27
|
+
attrs = License.licenses.fetch(url)
|
28
|
+
@uri = url
|
29
|
+
@description = attrs.fetch("description")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module CocinaDisplay
|
2
|
+
# A note associated with a cocina record
|
3
|
+
class Note
|
4
|
+
ABSTRACT_TYPES = ["summary", "abstract", "scope and content"].freeze
|
5
|
+
ABSTRACT_DISPLAY_LABEL_REGEX = /Abstract|Summary|Scope and content/i
|
6
|
+
PREFERRED_CITATION_TYPES = ["preferred citation"].freeze
|
7
|
+
PREFERRED_CITATION_DISPLAY_LABEL_REGEX = /Preferred citation/i
|
8
|
+
TOC_TYPES = ["table of contents"].freeze
|
9
|
+
TOC_DISPLAY_LABEL_REGEX = /Table of contents/i
|
10
|
+
|
11
|
+
attr_reader :cocina
|
12
|
+
|
13
|
+
# Initialize a Note from Cocina structured data.
|
14
|
+
# @param cocina [Hash]
|
15
|
+
def initialize(cocina)
|
16
|
+
@cocina = cocina
|
17
|
+
end
|
18
|
+
|
19
|
+
# String representation of the note.
|
20
|
+
# @return [String, nil]
|
21
|
+
def to_s
|
22
|
+
Utils.compact_and_join(values, delimiter: " -- ").presence
|
23
|
+
end
|
24
|
+
|
25
|
+
# The raw values from the Cocina data, flattened if nested.
|
26
|
+
# @return [String]
|
27
|
+
def values
|
28
|
+
Utils.flatten_nested_values(cocina).pluck("value").compact_blank
|
29
|
+
end
|
30
|
+
|
31
|
+
# The raw values from the Cocina data as a hash with type as key.
|
32
|
+
# @return [Hash{String => String}]
|
33
|
+
def values_by_type
|
34
|
+
Utils.flatten_nested_values(cocina).each_with_object({}) do |node, hash|
|
35
|
+
type = node["type"]
|
36
|
+
hash[type] ||= []
|
37
|
+
hash[type] << node["value"]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# The type of the note, e.g. "abstract".
|
42
|
+
# @return [String, nil]
|
43
|
+
def type
|
44
|
+
cocina["type"].presence
|
45
|
+
end
|
46
|
+
|
47
|
+
# The display label set in Cocina
|
48
|
+
# @return [String, nil]
|
49
|
+
def display_label
|
50
|
+
cocina["displayLabel"].presence
|
51
|
+
end
|
52
|
+
|
53
|
+
# Label used to render the note for display.
|
54
|
+
# Uses a displayLabel if available, otherwise tries to look up via type.
|
55
|
+
# Falls back to a default label derived from the type or a generic note label if
|
56
|
+
# no type is set.
|
57
|
+
# @return [String]
|
58
|
+
def label
|
59
|
+
display_label ||
|
60
|
+
I18n.t(type&.parameterize&.underscore, default: default_label, scope: "cocina_display.field_label.note")
|
61
|
+
end
|
62
|
+
|
63
|
+
# Check if the note is an abstract
|
64
|
+
# @return [Boolean]
|
65
|
+
def abstract?
|
66
|
+
display_label&.match?(ABSTRACT_DISPLAY_LABEL_REGEX) ||
|
67
|
+
ABSTRACT_TYPES.include?(type)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Check if the note is a general note (not a table of contents, abstract, preferred citation, or part)
|
71
|
+
# @return [Boolean]
|
72
|
+
def general_note?
|
73
|
+
!table_of_contents? && !abstract? && !preferred_citation? && !part?
|
74
|
+
end
|
75
|
+
|
76
|
+
# Check if the note is a preferred citation
|
77
|
+
# @return [Boolean]
|
78
|
+
def preferred_citation?
|
79
|
+
display_label&.match?(PREFERRED_CITATION_DISPLAY_LABEL_REGEX) ||
|
80
|
+
PREFERRED_CITATION_TYPES.include?(type)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Check if the note is a table of contents
|
84
|
+
# @return [Boolean]
|
85
|
+
def table_of_contents?
|
86
|
+
display_label&.match?(TOC_DISPLAY_LABEL_REGEX) ||
|
87
|
+
TOC_TYPES.include?(type)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Check if the note is a part note
|
91
|
+
# @note These are combined with the title and not displayed separately.
|
92
|
+
# @return [Boolean]
|
93
|
+
def part?
|
94
|
+
type == "part"
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def default_label
|
100
|
+
type&.capitalize || I18n.t("cocina_display.field_label.note.note")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CocinaDisplay
|
4
|
+
# A resource related to the record. See https://github.com/sul-dlss/cocina-models/blob/main/lib/cocina/models/related_resource.rb
|
5
|
+
# @note Related resources have no structural metadata.
|
6
|
+
class RelatedResource < JsonBackedRecord
|
7
|
+
include CocinaDisplay::Concerns::Accesses
|
8
|
+
include CocinaDisplay::Concerns::Events
|
9
|
+
include CocinaDisplay::Concerns::Contributors
|
10
|
+
include CocinaDisplay::Concerns::Identifiers
|
11
|
+
include CocinaDisplay::Concerns::Notes
|
12
|
+
include CocinaDisplay::Concerns::Titles
|
13
|
+
include CocinaDisplay::Concerns::UrlHelpers
|
14
|
+
include CocinaDisplay::Concerns::Subjects
|
15
|
+
include CocinaDisplay::Concerns::Forms
|
16
|
+
include CocinaDisplay::Concerns::Languages
|
17
|
+
include CocinaDisplay::Concerns::Geospatial
|
18
|
+
|
19
|
+
# Description of the relation to the source record.
|
20
|
+
# @return [String]
|
21
|
+
# @example "is part of"
|
22
|
+
# @see https://github.com/sul-dlss/cocina-models/blob/main/docs/description_types.md#relatedresource-types
|
23
|
+
attr_reader :type
|
24
|
+
|
25
|
+
# Restructure the hash so that everything is under "description" key, since
|
26
|
+
# it's all descriptive metadata. This makes most CocinaRecord methods work.
|
27
|
+
def initialize(cocina_doc)
|
28
|
+
@type = cocina_doc["type"]
|
29
|
+
super({"description" => cocina_doc.except("type")})
|
30
|
+
end
|
31
|
+
|
32
|
+
# Label used to group the related resource for display.
|
33
|
+
# @return [String]
|
34
|
+
def label
|
35
|
+
cocina_doc.dig("description", "displayLabel").presence || type_label
|
36
|
+
end
|
37
|
+
|
38
|
+
# String representation of the related resource.
|
39
|
+
# @return [String, nil]
|
40
|
+
def to_s
|
41
|
+
main_title || url
|
42
|
+
end
|
43
|
+
|
44
|
+
# URL to the related resource for link construction.
|
45
|
+
# If there are multiple URLs, uses the first.
|
46
|
+
# @return [String, nil]
|
47
|
+
def url
|
48
|
+
urls.first&.to_s || purl_url
|
49
|
+
end
|
50
|
+
|
51
|
+
# Is this a related resource with a URL?
|
52
|
+
# @return [Boolean]
|
53
|
+
def url?
|
54
|
+
url.present?
|
55
|
+
end
|
56
|
+
|
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
|
+
# @return [Array<DisplayData>]
|
61
|
+
def display_data
|
62
|
+
title_display_data + contributor_display_data + general_note_display_data + preferred_citation_display_data + access_display_data
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# Key used for i18n lookup of the label, based on the type.
|
68
|
+
# Falls back to a generic label for any unknown types.
|
69
|
+
# @return [String]
|
70
|
+
def type_label
|
71
|
+
I18n.t(type&.parameterize&.underscore, default: :related_to, scope: "cocina_display.field_label.related_resource")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -1,6 +1,3 @@
|
|
1
|
-
require_relative "../utils"
|
2
|
-
require_relative "subject_value"
|
3
|
-
|
4
1
|
module CocinaDisplay
|
5
2
|
module Subjects
|
6
3
|
# Base class for subjects in Cocina structured data.
|
@@ -27,23 +24,49 @@ module CocinaDisplay
|
|
27
24
|
subject_values.map(&:to_s).compact_blank
|
28
25
|
end
|
29
26
|
|
30
|
-
#
|
27
|
+
# The value to use for display.
|
28
|
+
# Genre values are capitalized; other subject values are not.
|
31
29
|
# @return [String]
|
32
30
|
def to_s
|
31
|
+
(type == "genre") ? display_value&.upcase_first : display_value
|
32
|
+
end
|
33
|
+
|
34
|
+
# A string representation of the entire subject, concatenated for display.
|
35
|
+
# @return [String]
|
36
|
+
def display_value
|
33
37
|
Utils.compact_and_join(display_values, delimiter: " > ")
|
34
38
|
end
|
35
39
|
|
40
|
+
# Label used to render the subject for display.
|
41
|
+
# Uses a displayLabel if available, otherwise looks up via type.
|
42
|
+
# @return [String]
|
43
|
+
def label
|
44
|
+
cocina["displayLabel"].presence || type_label
|
45
|
+
end
|
46
|
+
|
36
47
|
# Individual values composing this subject.
|
37
48
|
# Can be multiple if the Cocina featured nested data.
|
38
|
-
#
|
49
|
+
# All SubjectValues inherit the type of their parent Subject.
|
39
50
|
# @return [Array<SubjectValue>]
|
40
51
|
def subject_values
|
41
|
-
@subject_values ||=
|
42
|
-
|
43
|
-
|
44
|
-
|
52
|
+
@subject_values ||= (Array(cocina["parallelValue"]).presence || [cocina]).flat_map do |node|
|
53
|
+
if SubjectValue.atomic_types.include?(type)
|
54
|
+
SubjectValue.from_cocina(node, type: type)
|
55
|
+
else
|
56
|
+
Utils.flatten_nested_values(node, atomic_types: SubjectValue.atomic_types).flat_map do |value|
|
57
|
+
SubjectValue.from_cocina(value, type: value["type"] || type)
|
58
|
+
end
|
59
|
+
end
|
45
60
|
end
|
46
61
|
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# Type-specific label for this subject.
|
66
|
+
# @return [String]
|
67
|
+
def type_label
|
68
|
+
I18n.t(type&.parameterize&.underscore, default: :subject, scope: "cocina_display.field_label.subject")
|
69
|
+
end
|
47
70
|
end
|
48
71
|
end
|
49
72
|
end
|
@@ -1,11 +1,3 @@
|
|
1
|
-
require "geo/coord"
|
2
|
-
|
3
|
-
require_relative "subject"
|
4
|
-
require_relative "../contributors/name"
|
5
|
-
require_relative "../title_builder"
|
6
|
-
require_relative "../dates/date"
|
7
|
-
require_relative "../geospatial"
|
8
|
-
|
9
1
|
module CocinaDisplay
|
10
2
|
module Subjects
|
11
3
|
# A descriptive value that can be part of a Subject.
|
@@ -16,11 +8,29 @@ module CocinaDisplay
|
|
16
8
|
# @see https://github.com/sul-dlss/cocina-models/blob/main/docs/description_types.md#subject-part-types-for-structured-value
|
17
9
|
attr_accessor :type
|
18
10
|
|
19
|
-
# Create
|
11
|
+
# Create SubjectValues from Cocina structured data.
|
12
|
+
# Pre-coordinated string values will be split into multiple SubjectValues.
|
20
13
|
# @param cocina [Hash] The Cocina structured data for the subject.
|
21
|
-
# @
|
22
|
-
|
23
|
-
|
14
|
+
# @param type [String, nil] The type, coming from the parent Subject.
|
15
|
+
# @return [Array<SubjectValue>]
|
16
|
+
def self.from_cocina(cocina, type:)
|
17
|
+
split_pre_coordinated_values(cocina, type: type).map do |value|
|
18
|
+
SUBJECT_VALUE_TYPES.fetch(type, SubjectValue).new(value).tap do |obj|
|
19
|
+
obj.type ||= type
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Split a pre-coordinated subject value joined with "--" into multiple values.
|
25
|
+
# Ignores the "--" string for coordinate subject types, which use it differently.
|
26
|
+
# @param cocina [Hash] The Cocina structured data for the subject.
|
27
|
+
# @return [Array<Hash>] An array of Cocina hashes, one for each split value
|
28
|
+
def self.split_pre_coordinated_values(cocina, type:)
|
29
|
+
if cocina["value"].is_a?(String) && cocina["value"].include?("--") && !type.include?("coordinates")
|
30
|
+
cocina["value"].split("--").map { |v| cocina.merge("value" => v.strip) }
|
31
|
+
else
|
32
|
+
[cocina]
|
33
|
+
end
|
24
34
|
end
|
25
35
|
|
26
36
|
# All subject value types that should not be further destructured.
|
@@ -65,12 +75,20 @@ module CocinaDisplay
|
|
65
75
|
|
66
76
|
# A subject value representing an entity with a title.
|
67
77
|
class TitleSubjectValue < SubjectValue
|
78
|
+
attr_reader :title
|
79
|
+
|
80
|
+
# Initialize a TitleSubjectValue object with Cocina structured data.
|
81
|
+
# @param cocina [Hash] The Cocina structured data for the subject.
|
82
|
+
def initialize(cocina)
|
83
|
+
super
|
84
|
+
@title = Title.new(cocina)
|
85
|
+
end
|
86
|
+
|
68
87
|
# Construct a title string to use for display.
|
69
|
-
# @see CocinaDisplay::
|
70
|
-
# @
|
71
|
-
# @return [String]
|
88
|
+
# @see CocinaDisplay::Title#to_s
|
89
|
+
# @return [String, nil]
|
72
90
|
def to_s
|
73
|
-
|
91
|
+
title.to_s
|
74
92
|
end
|
75
93
|
end
|
76
94
|
|