cocina_display 1.1.3 → 1.2.1
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/config/marc_countries.yml +385 -0
- data/config/marc_relators.yml +310 -0
- data/config/searchworks_languages.yml +520 -0
- data/lib/cocina_display/cocina_record.rb +29 -64
- 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 +25 -5
- 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 +31 -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 +9 -3
- 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 +18 -12
- 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 +221 -0
- data/lib/cocina_display/utils.rb +4 -4
- data/lib/cocina_display/version.rb +1 -1
- data/lib/cocina_display.rb +32 -2
- metadata +46 -12
- data/lib/cocina_display/title_builder.rb +0 -397
- data/lib/cocina_display/vocabularies/marc_country_codes.rb +0 -393
- data/lib/cocina_display/vocabularies/marc_relator_codes.rb +0 -318
- data/lib/cocina_display/vocabularies/searchworks_languages.rb +0 -526
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CocinaDisplay
|
|
4
|
+
# A data structure to be rendered into HTML by the consumer.
|
|
5
|
+
class DisplayData
|
|
6
|
+
class << self
|
|
7
|
+
# Given objects that support #to_s and #label, group them into {DisplayData}.
|
|
8
|
+
# Groups by each object's +label+ and keeps unique, non-blank values.
|
|
9
|
+
# @param objects [Array<Object>]
|
|
10
|
+
# @return [Array<DisplayData>]
|
|
11
|
+
def from_objects(objects)
|
|
12
|
+
objects.group_by(&:label)
|
|
13
|
+
.map { |label, objs| new(label: label, objects: objs) }
|
|
14
|
+
.reject { |data| data.values.empty? }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Given an array of Cocina hashes, group them into {DisplayData}.
|
|
18
|
+
# Uses +label+ as the label if provided, but honors +displayLabel+ if set.
|
|
19
|
+
# Keeps the unique, non-blank values under each label.
|
|
20
|
+
# @param cocina [Array<Hash>]
|
|
21
|
+
# @param label [String]
|
|
22
|
+
# @return [Array<DisplayData>]
|
|
23
|
+
def from_cocina(cocina, label: nil)
|
|
24
|
+
from_objects(descriptive_values_from_cocina(cocina, label: label))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Create display data from string values.
|
|
28
|
+
# @param value [String] The string values to display
|
|
29
|
+
# @param label [String] The label for the display data
|
|
30
|
+
# @return [Array<DisplayData>] The display data
|
|
31
|
+
def from_strings(values, label: nil)
|
|
32
|
+
from_objects(descriptive_values_from_strings(values, label: label))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Create an array containing a descriptive object from string values.
|
|
36
|
+
# Can be used to combine a string derived value with other metadata objects.
|
|
37
|
+
# @param strings [Array<String>] The string values to display
|
|
38
|
+
# @param label [String] The label for the display data
|
|
39
|
+
# @return [Array<DescriptiveValue>] The descriptive values
|
|
40
|
+
def descriptive_values_from_strings(strings, label: nil)
|
|
41
|
+
strings.map { |string| DescriptiveValue.new(label: label, value: string) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Take one or several DisplayData and merge into a single hash.
|
|
45
|
+
# Keys are labels; values are the merged array of values for that label.
|
|
46
|
+
# @param display_data [DisplayData, Array<DisplayData>]
|
|
47
|
+
# @return [Hash{String => Array<String>}] The merged hash
|
|
48
|
+
def to_hash(display_data)
|
|
49
|
+
Array(display_data).map(&:to_h).reduce(:merge)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
# Wrap Cocina nodes into {DescriptiveValue} so they are labelled.
|
|
55
|
+
# Uses +displayLabel+ from the node if present, otherwise uses the provided label.
|
|
56
|
+
# @param cocina [Array<Hash>]
|
|
57
|
+
# @param label [String]
|
|
58
|
+
# @return [Array<DescriptiveValue>]
|
|
59
|
+
def descriptive_values_from_cocina(cocina, label: nil)
|
|
60
|
+
cocina.map { |node| DescriptiveValue.new(label: node["displayLabel"] || label, value: node["value"]) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Wrapper to make Cocina descriptive values respond to #to_s and #label.
|
|
64
|
+
# @attr [String] label
|
|
65
|
+
# @attr [String] value
|
|
66
|
+
DescriptiveValue = Data.define(:label, :value) do
|
|
67
|
+
def to_s
|
|
68
|
+
value
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Create a DisplayData object from a list of objects that share a label
|
|
74
|
+
# @param label [String]
|
|
75
|
+
# @param objects [Array<#to_s>]
|
|
76
|
+
def initialize(label:, objects:)
|
|
77
|
+
@label = label
|
|
78
|
+
@objects = objects
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
attr_reader :label, :objects
|
|
82
|
+
|
|
83
|
+
# The unique, non-blank values for display
|
|
84
|
+
# @return [Array<String>]
|
|
85
|
+
def values
|
|
86
|
+
objects.flat_map { |object| split_string_on_newlines(object.to_s) }.compact_blank.uniq
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Express the display data as a hash mapping the label to its values.
|
|
90
|
+
# @return [Hash{String => Array<String>}] The label and values
|
|
91
|
+
def to_h
|
|
92
|
+
{label => values}
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
# Split a string on newlines (including HTML-encoded newlines) and strip whitespace.
|
|
98
|
+
# @param string [String] The string to split
|
|
99
|
+
# @return [Array<String>]
|
|
100
|
+
def split_string_on_newlines(string)
|
|
101
|
+
string&.gsub(" ", "\n")&.split("\n")&.map(&:strip)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
require_relative "location"
|
|
2
|
-
require_relative "../dates/date"
|
|
3
|
-
require_relative "../contributors/contributor"
|
|
4
|
-
|
|
5
1
|
module CocinaDisplay
|
|
6
2
|
module Events
|
|
7
3
|
# An event associated with an object, like publication.
|
|
@@ -73,6 +69,14 @@ module CocinaDisplay
|
|
|
73
69
|
CocinaDisplay::Events::Location.new(location)
|
|
74
70
|
end
|
|
75
71
|
end
|
|
72
|
+
|
|
73
|
+
# All notes associated with this event.
|
|
74
|
+
# @return [Array<CocinaDisplay::Events::Note>]
|
|
75
|
+
def notes
|
|
76
|
+
@notes ||= Array(cocina["note"]).map do |note|
|
|
77
|
+
CocinaDisplay::Events::Note.new(note)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
76
80
|
end
|
|
77
81
|
end
|
|
78
82
|
end
|
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "edtf"
|
|
4
|
-
require "active_support"
|
|
5
|
-
require "active_support/core_ext/enumerable"
|
|
6
|
-
require "active_support/core_ext/object/blank"
|
|
7
|
-
|
|
8
|
-
require_relative "event"
|
|
9
|
-
require_relative "../utils"
|
|
10
|
-
require_relative "../dates/date"
|
|
11
|
-
require_relative "../dates/date_range"
|
|
12
|
-
|
|
13
3
|
module CocinaDisplay
|
|
14
4
|
module Events
|
|
15
5
|
# Wrapper for Cocina events used to generate an imprint statement for display.
|
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
require_relative "../vocabularies/marc_country_codes"
|
|
2
|
-
|
|
3
1
|
module CocinaDisplay
|
|
4
2
|
module Events
|
|
5
3
|
# A single location represented in a Cocina event, like a publication place.
|
|
6
4
|
class Location
|
|
5
|
+
MARC_COUNTRIES_FILE_PATH = CocinaDisplay.root / "config" / "marc_countries.yml"
|
|
6
|
+
|
|
7
7
|
attr_reader :cocina
|
|
8
8
|
|
|
9
|
+
# A hash mapping MARC country codes to their names.
|
|
10
|
+
# @return [Hash{String => String}]
|
|
11
|
+
def self.marc_countries
|
|
12
|
+
@marc_countries ||= YAML.safe_load_file(MARC_COUNTRIES_FILE_PATH)
|
|
13
|
+
end
|
|
14
|
+
|
|
9
15
|
# Initialize a Location object with Cocina structured data.
|
|
10
16
|
# @param cocina [Hash] The Cocina structured data for the location.
|
|
11
17
|
def initialize(cocina)
|
|
@@ -36,7 +42,7 @@ module CocinaDisplay
|
|
|
36
42
|
# Decoded country name if the location is encoded with a MARC country code.
|
|
37
43
|
# @return [String, nil]
|
|
38
44
|
def decoded_country
|
|
39
|
-
|
|
45
|
+
Location.marc_countries[code] if marc_country? && valid_country_code?
|
|
40
46
|
end
|
|
41
47
|
|
|
42
48
|
# Is this a decodable country code?
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module CocinaDisplay
|
|
2
|
+
module Events
|
|
3
|
+
# A single note represented in a Cocina event, like an issuance or edition note.
|
|
4
|
+
class Note
|
|
5
|
+
attr_reader :cocina
|
|
6
|
+
|
|
7
|
+
# Initialize a Note object with Cocina structured data.
|
|
8
|
+
# @param cocina [Hash] The Cocina structured data for the note.
|
|
9
|
+
def initialize(cocina)
|
|
10
|
+
@cocina = cocina
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# The value of the note.
|
|
14
|
+
# @return [String, nil]
|
|
15
|
+
def to_s
|
|
16
|
+
cocina["value"].presence
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# The type of the note, like "issuance" or "edition".
|
|
20
|
+
# @return [String, nil]
|
|
21
|
+
def type
|
|
22
|
+
cocina["type"].presence
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# The display label for the note.
|
|
26
|
+
# @return [String]
|
|
27
|
+
def label
|
|
28
|
+
cocina["displayLabel"].presence ||
|
|
29
|
+
I18n.t(type&.parameterize&.underscore, default: :default, scope: "cocina_display.field_label.event.note")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CocinaDisplay
|
|
4
|
+
# Classes for extracting format/genre information from a Cocina object.
|
|
5
|
+
module Forms
|
|
6
|
+
# A form associated with part or all of a Cocina object.
|
|
7
|
+
class Form
|
|
8
|
+
attr_reader :cocina
|
|
9
|
+
|
|
10
|
+
# Create a Form object from Cocina structured data.
|
|
11
|
+
# Delegates to subclasses for specific types.
|
|
12
|
+
# @param cocina [Hash]
|
|
13
|
+
# @return [Form]
|
|
14
|
+
def self.from_cocina(cocina)
|
|
15
|
+
case cocina["type"]
|
|
16
|
+
when "genre"
|
|
17
|
+
Genre.new(cocina)
|
|
18
|
+
when "resource type"
|
|
19
|
+
ResourceType.new(cocina)
|
|
20
|
+
else
|
|
21
|
+
new(cocina)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Create a Form object from Cocina structured data.
|
|
26
|
+
# @param cocina [Hash]
|
|
27
|
+
def initialize(cocina)
|
|
28
|
+
@cocina = cocina
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The value to use for display.
|
|
32
|
+
# @return [String]
|
|
33
|
+
def to_s
|
|
34
|
+
flat_value
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Single concatenated string value for the form.
|
|
38
|
+
# @return [String]
|
|
39
|
+
def flat_value
|
|
40
|
+
Utils.compact_and_join(values, delimiter: " > ")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# The raw values from the Cocina data, flattened if nested.
|
|
44
|
+
# @return [String]
|
|
45
|
+
def values
|
|
46
|
+
Utils.flatten_nested_values(cocina).pluck("value").compact_blank
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The label to use for display.
|
|
50
|
+
# Uses a displayLabel if available, otherwise looks up via type.
|
|
51
|
+
# @return [String]
|
|
52
|
+
def label
|
|
53
|
+
cocina["displayLabel"].presence || type_label
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# The type of form, such as "genre", "extent", etc.
|
|
57
|
+
# @return [String, nil]
|
|
58
|
+
def type
|
|
59
|
+
cocina["type"]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
# Type-specific label for this form value.
|
|
65
|
+
# @return [String]
|
|
66
|
+
def type_label
|
|
67
|
+
I18n.t(type&.parameterize&.underscore, default: :form, scope: "cocina_display.field_label.form")
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module CocinaDisplay
|
|
2
|
+
module Forms
|
|
3
|
+
# A Resource Type form associated with part or all of a Cocina object.
|
|
4
|
+
class ResourceType < Form
|
|
5
|
+
# Resource types are lowercased for display.
|
|
6
|
+
# @return [String]
|
|
7
|
+
def to_s
|
|
8
|
+
super&.downcase
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Is this a Stanford self-deposit resource type?
|
|
12
|
+
# @note These are handled separately when displayed.
|
|
13
|
+
# @return [Boolean]
|
|
14
|
+
def stanford_self_deposit?
|
|
15
|
+
source == "Stanford self-deposit resource types"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Is this a MODS resource type?
|
|
19
|
+
# @return [Boolean]
|
|
20
|
+
def mods?
|
|
21
|
+
source == "MODS resource types"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
# @return [String]
|
|
27
|
+
def source
|
|
28
|
+
cocina.dig("source", "value")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Stanford self-deposit resource types are labeled "Genre".
|
|
32
|
+
# @return [String]
|
|
33
|
+
def type_label
|
|
34
|
+
(I18n.t("cocina_display.field_label.form.genre") if stanford_self_deposit?) || super
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -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,11 +1,18 @@
|
|
|
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.
|
|
6
5
|
class Language
|
|
6
|
+
SEARCHWORKS_LANGUAGES_FILE_PATH = CocinaDisplay.root / "config" / "searchworks_languages.yml"
|
|
7
|
+
|
|
7
8
|
attr_reader :cocina
|
|
8
9
|
|
|
10
|
+
# A hash of language codes to language names recognized by Searchworks.
|
|
11
|
+
# @return [Hash{String => String}]
|
|
12
|
+
def self.searchworks_languages
|
|
13
|
+
@searchworks_languages ||= YAML.safe_load_file(SEARCHWORKS_LANGUAGES_FILE_PATH)
|
|
14
|
+
end
|
|
15
|
+
|
|
9
16
|
# Create a Language object from Cocina structured data.
|
|
10
17
|
# @param cocina [Hash]
|
|
11
18
|
def initialize(cocina)
|
|
@@ -27,21 +34,20 @@ module CocinaDisplay
|
|
|
27
34
|
# Decoded name of the language based on the code, if present.
|
|
28
35
|
# @return [String, nil]
|
|
29
36
|
def decoded_value
|
|
30
|
-
|
|
37
|
+
Language.searchworks_languages[code] if searchworks_language?
|
|
31
38
|
end
|
|
32
39
|
|
|
33
|
-
#
|
|
34
|
-
# @
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Vocabularies::SEARCHWORKS_LANGUAGES.value?(to_s)
|
|
40
|
+
# Display label for this field.
|
|
41
|
+
# @return [String]
|
|
42
|
+
def label
|
|
43
|
+
cocina["displayLabel"].presence || I18n.t("cocina_display.field_label.language")
|
|
38
44
|
end
|
|
39
45
|
|
|
40
|
-
# True if the language
|
|
41
|
-
# @see
|
|
46
|
+
# True if the language is recognized by Searchworks.
|
|
47
|
+
# @see CocinaDisplay::Language.searchworks_languages
|
|
42
48
|
# @return [Boolean]
|
|
43
|
-
def
|
|
44
|
-
|
|
49
|
+
def searchworks_language?
|
|
50
|
+
Language.searchworks_languages.value?(cocina["value"]) || Language.searchworks_languages.key?(code)
|
|
45
51
|
end
|
|
46
52
|
end
|
|
47
53
|
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 = CocinaDisplay.root / "config" / "licenses.yml"
|
|
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_file(LICENSE_FILE_PATH)
|
|
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
|