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.
- checksums.yaml +4 -4
- data/README.md +5 -11
- data/lib/cocina_display/cocina_record.rb +20 -78
- data/lib/cocina_display/concerns/contributors.rb +94 -0
- data/lib/cocina_display/concerns/events.rb +137 -0
- data/lib/cocina_display/concerns/identifiers.rb +60 -0
- data/lib/cocina_display/concerns/titles.rb +64 -0
- data/lib/cocina_display/contributor.rb +179 -0
- data/lib/cocina_display/dates/date.rb +688 -0
- data/lib/cocina_display/dates/date_range.rb +122 -0
- data/lib/cocina_display/imprint.rb +123 -0
- data/lib/cocina_display/marc_country_codes.rb +394 -0
- data/lib/cocina_display/title_builder.rb +27 -7
- data/lib/cocina_display/utils.rb +33 -0
- data/lib/cocina_display/version.rb +1 -1
- metadata +26 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0724f307088c9cdd9348af463e3c659811eef65d0f1f04e094b4c664a36cfed1
|
4
|
+
data.tar.gz: 4b49472d07a085455ca72204e6ebde3cfc27c634f8ba9cd5b2c2a47d62d19aad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: edc1e8977792d21fd6fef292f486c11eab4011dc63c854af1217aced6930ea55538e158abc261491c08b1f8492c374c52102b44931849b2350e1dacf54310d41
|
7
|
+
data.tar.gz: 664691764d4a4ff7da024af64ce9dd2d0841d8c014ee9439c3ebebe4b3a4c2e585e7f608f2ff943a04586dfaad8fc4ec7cfa714fade929ca9b0278ec740bff8b
|
data/README.md
CHANGED
@@ -28,25 +28,19 @@ To start, you need some Cocina in JSON form.
|
|
28
28
|
|
29
29
|
You can download some directly from PURL by visiting an object's PURL URL and appending `.json` to the end, like `https://purl.stanford.edu/bb112zx3193.json`. Some examples are available in the `spec/fixtures` directory.
|
30
30
|
|
31
|
-
|
31
|
+
There is also a helper method to fetch the Cocina JSON for a given DRUID and immediately parse it into a `CocinaRecord` object:
|
32
32
|
|
33
33
|
```ruby
|
34
|
-
|
35
|
-
|
34
|
+
> record = CocinaDisplay::CocinaRecord.fetch('bb112zx3193')
|
35
|
+
=> #<CocinaDisplay::CocinaRecord:0x00007f8c8c0b5c80
|
36
36
|
```
|
37
37
|
|
38
38
|
### Working with objects
|
39
39
|
|
40
|
-
|
40
|
+
The `CocinaRecord` class provides some methods to access common fields, as well as an underlying hash representation parsed from the JSON.
|
41
41
|
|
42
42
|
```ruby
|
43
|
-
>
|
44
|
-
=> true
|
45
|
-
> record = CocinaDisplay::CocinaRecord.new(cocina_json)
|
46
|
-
=>
|
47
|
-
#<CocinaDisplay::CocinaRecord:0x000000012d11b600
|
48
|
-
...
|
49
|
-
> record.title
|
43
|
+
> record.main_title
|
50
44
|
=> "Bugatti Type 51A. Road & Track Salon January 1957"
|
51
45
|
> record.content_type
|
52
46
|
=> "image"
|
@@ -2,16 +2,34 @@
|
|
2
2
|
|
3
3
|
require "janeway"
|
4
4
|
require "json"
|
5
|
-
require "
|
5
|
+
require "net/http"
|
6
6
|
require "active_support"
|
7
7
|
require "active_support/core_ext/object/blank"
|
8
8
|
require "active_support/core_ext/hash/conversions"
|
9
9
|
|
10
|
-
require_relative "
|
10
|
+
require_relative "concerns/events"
|
11
|
+
require_relative "concerns/contributors"
|
12
|
+
require_relative "concerns/identifiers"
|
13
|
+
require_relative "concerns/titles"
|
11
14
|
|
12
15
|
module CocinaDisplay
|
13
16
|
# Public Cocina metadata for an SDR object, as fetched from PURL.
|
14
17
|
class CocinaRecord
|
18
|
+
include CocinaDisplay::Concerns::Events
|
19
|
+
include CocinaDisplay::Concerns::Contributors
|
20
|
+
include CocinaDisplay::Concerns::Identifiers
|
21
|
+
include CocinaDisplay::Concerns::Titles
|
22
|
+
|
23
|
+
# Fetch a public Cocina document from PURL and create a CocinaRecord.
|
24
|
+
# @note This is intended to be used in development or testing only.
|
25
|
+
# @param druid [String] The bare DRUID of the object to fetch.
|
26
|
+
# @return [CocinaDisplay::CocinaRecord]
|
27
|
+
# :nocov:
|
28
|
+
def self.fetch(druid)
|
29
|
+
new(Net::HTTP.get(URI("https://purl.stanford.edu/#{druid}.json")))
|
30
|
+
end
|
31
|
+
# :nocov:
|
32
|
+
|
15
33
|
# The parsed Cocina document.
|
16
34
|
# @return [Hash]
|
17
35
|
attr_reader :cocina_doc
|
@@ -32,60 +50,6 @@ module CocinaDisplay
|
|
32
50
|
Janeway.enum_for(path_expression, cocina_doc)
|
33
51
|
end
|
34
52
|
|
35
|
-
# The DRUID for the object, with the +druid:+ prefix.
|
36
|
-
# @return [String]
|
37
|
-
# @example
|
38
|
-
# record.druid #=> "druid:bb099mt5053"
|
39
|
-
def druid
|
40
|
-
cocina_doc["externalIdentifier"]
|
41
|
-
end
|
42
|
-
|
43
|
-
# The DRUID for the object, without the +druid:+ prefix.
|
44
|
-
# @return [String]
|
45
|
-
# @example
|
46
|
-
# record.bare_druid #=> "bb099mt5053"
|
47
|
-
def bare_druid
|
48
|
-
druid.delete_prefix("druid:")
|
49
|
-
end
|
50
|
-
|
51
|
-
# The DOI for the object, if there is one – just the identifier part.
|
52
|
-
# @return [String, nil]
|
53
|
-
# @example
|
54
|
-
# record.doi #=> "10.25740/ppax-bf07"
|
55
|
-
def doi
|
56
|
-
doi_id = path("$.identification.doi").first ||
|
57
|
-
path("$.description.identifier[?match(@.type, 'doi|DOI')].value").first ||
|
58
|
-
path("$.description.identifier[?search(@.uri, 'doi.org')].uri").first
|
59
|
-
|
60
|
-
URI(doi_id).path.delete_prefix("/") if doi_id.present?
|
61
|
-
end
|
62
|
-
|
63
|
-
# The DOI as a URL, if there is one. Any valid DOI should resolve via doi.org.
|
64
|
-
# @return [String, nil]
|
65
|
-
# @example
|
66
|
-
# record.doi_url #=> "https://doi.org/10.25740/ppax-bf07"
|
67
|
-
def doi_url
|
68
|
-
URI.join("https://doi.org", doi).to_s if doi.present?
|
69
|
-
end
|
70
|
-
|
71
|
-
# The HRID of the item in FOLIO, if defined.
|
72
|
-
# @note This doesn't imply the object is available in Searchworks at this ID.
|
73
|
-
# @return [String, nil]
|
74
|
-
# @example
|
75
|
-
# record.folio_hrid #=> "a12845814"
|
76
|
-
def folio_hrid
|
77
|
-
path("$.identification.catalogLinks[?(@.catalog == 'folio')].catalogRecordId").first
|
78
|
-
end
|
79
|
-
|
80
|
-
# The FOLIO HRID if defined, otherwise the bare DRUID.
|
81
|
-
# @note This doesn't imply the object is available in Searchworks at this ID.
|
82
|
-
# @see folio_hrid
|
83
|
-
# @see bare_druid
|
84
|
-
# @return [String]
|
85
|
-
def searchworks_id
|
86
|
-
folio_hrid || bare_druid
|
87
|
-
end
|
88
|
-
|
89
53
|
# Timestamp when the Cocina was created.
|
90
54
|
# @note This is for the metadata itself, not the object.
|
91
55
|
# @return [Time]
|
@@ -115,28 +79,6 @@ module CocinaDisplay
|
|
115
79
|
content_type == "collection"
|
116
80
|
end
|
117
81
|
|
118
|
-
# The main title for the object.
|
119
|
-
# @note If you need more formatting control, consider using {CocinaDisplay::TitleBuilder} directly.
|
120
|
-
# @return [String]
|
121
|
-
# @example
|
122
|
-
# record.title #=> "Bugatti Type 51A. Road & Track Salon January 1957"
|
123
|
-
def title
|
124
|
-
CocinaDisplay::TitleBuilder.build(
|
125
|
-
cocina_doc.dig("description", "title"),
|
126
|
-
catalog_links: cocina_doc.dig("identification", "catalogLinks")
|
127
|
-
)
|
128
|
-
end
|
129
|
-
|
130
|
-
# Alternative or translated titles for the object. Does not include the main title.
|
131
|
-
# @return [Array<String>]
|
132
|
-
# @example
|
133
|
-
# record.additional_titles #=> ["Alternate title 1", "Alternate title 2"]
|
134
|
-
def additional_titles
|
135
|
-
CocinaDisplay::TitleBuilder.additional_titles(
|
136
|
-
cocina_doc.dig("description", "title")
|
137
|
-
)
|
138
|
-
end
|
139
|
-
|
140
82
|
# Traverse nested FileSets and return an enumerator over their files.
|
141
83
|
# Each file is a +Hash+.
|
142
84
|
# @return [Enumerator] Enumerator over file hashes
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require_relative "../contributor"
|
2
|
+
|
3
|
+
module CocinaDisplay
|
4
|
+
module Concerns
|
5
|
+
# Methods for finding and formatting names for contributors
|
6
|
+
module Contributors
|
7
|
+
# The main author's name, formatted for display.
|
8
|
+
# @param with_date [Boolean] Include life dates, if present
|
9
|
+
# @return [String]
|
10
|
+
# @return [nil] if no main author is found
|
11
|
+
# @example
|
12
|
+
# record.main_author #=> "Smith, John"
|
13
|
+
# @example with date
|
14
|
+
# record.main_author(with_date: true) #=> "Smith, John, 1970-2020"
|
15
|
+
def main_author(with_date: false)
|
16
|
+
main_author_contributor&.display_name(with_date: with_date)
|
17
|
+
end
|
18
|
+
|
19
|
+
# All author names except the main one, formatted for display.
|
20
|
+
# @param with_date [Boolean] Include life dates, if present
|
21
|
+
# @return [Array<String>]
|
22
|
+
def additional_authors(with_date: false)
|
23
|
+
additional_author_contributors.map { |c| c.display_name(with_date: with_date) }
|
24
|
+
end
|
25
|
+
|
26
|
+
# All names of authors who are people, formatted for display.
|
27
|
+
# @param with_date [Boolean] Include life dates, if present
|
28
|
+
# @return [Array<String>]
|
29
|
+
def person_authors(with_date: false)
|
30
|
+
authors.filter(&:person?).map { |c| c.display_name(with_date: with_date) }
|
31
|
+
end
|
32
|
+
|
33
|
+
# All names of non-person authors, formatted for display.
|
34
|
+
# This includes organizations, conferences, families, etc.
|
35
|
+
# @return [Array<String>]
|
36
|
+
# @see https://github.com/sul-dlss/cocina-models/blob/main/docs/description_types.md#contributor-types
|
37
|
+
def impersonal_authors
|
38
|
+
authors.reject(&:person?).map(&:display_name)
|
39
|
+
end
|
40
|
+
|
41
|
+
# All names of authors that are organizations, formatted for display.
|
42
|
+
# @return [Array<String>]
|
43
|
+
def organization_authors
|
44
|
+
authors.filter(&:organization?).map(&:display_name)
|
45
|
+
end
|
46
|
+
|
47
|
+
# All names of authors that are conferences, formatted for display.
|
48
|
+
# @return [Array<String>]
|
49
|
+
def conference_authors
|
50
|
+
authors.filter(&:conference?).map(&:display_name)
|
51
|
+
end
|
52
|
+
|
53
|
+
# A string value for sorting by author that sorts missing values last.
|
54
|
+
# Ignores punctuation and leading/trailing spaces.
|
55
|
+
# @return [String]
|
56
|
+
def sort_author
|
57
|
+
(main_author_contributor&.display_name || "\u{10FFFF}").gsub(/[[:punct:]]*/, "").strip
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# All contributors for the object, including authors, editors, etc.
|
63
|
+
# @return [Array<Contributor>]
|
64
|
+
def contributors
|
65
|
+
@contributors ||= path("$.description.contributor[*]").map { |c| Contributor.new(c) }
|
66
|
+
end
|
67
|
+
|
68
|
+
# All contributors with a "creator" or "author" role.
|
69
|
+
# @return [Array<Contributor>]
|
70
|
+
# @see Contributor#author?
|
71
|
+
def authors
|
72
|
+
contributors.filter(&:author?)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Contributor object representing the primary author.
|
76
|
+
# Selected according to the following rules:
|
77
|
+
# 1. If there is a primary author or creator, use that.
|
78
|
+
# 2. If there are no primary authors or creators, use the first one.
|
79
|
+
# 3. If there are none at all, use the first contributor without any role.
|
80
|
+
# @return [Contributor]
|
81
|
+
# @return [nil] if no suitable contributor is found
|
82
|
+
def main_author_contributor
|
83
|
+
authors.find(&:primary?).presence || authors.first || contributors.find { |c| !c.role? }.presence
|
84
|
+
end
|
85
|
+
|
86
|
+
# All author/creator contributors except the main one.
|
87
|
+
# @return [Array<Contributor>]
|
88
|
+
def additional_author_contributors
|
89
|
+
return [] if authors.empty? || authors.one? || !authors.include?(main_author_contributor)
|
90
|
+
authors - [main_author_contributor]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require_relative "../dates/date"
|
2
|
+
require_relative "../dates/date_range"
|
3
|
+
require_relative "../imprint"
|
4
|
+
|
5
|
+
module CocinaDisplay
|
6
|
+
module Concerns
|
7
|
+
module Events
|
8
|
+
# The earliest preferred publication date as a Date object.
|
9
|
+
# If the date was a range or interval, uses the start (or end if no start).
|
10
|
+
# Considers publication, creation, and capture dates in that order.
|
11
|
+
# Prefers dates marked as primary and those with a declared encoding.
|
12
|
+
# @param ignore_qualified [Boolean] Reject qualified dates (e.g. approximate)
|
13
|
+
# @return [Date, nil]
|
14
|
+
# @see https://github.com/inukshuk/edtf-ruby
|
15
|
+
def pub_date_edtf(ignore_qualified: false)
|
16
|
+
date = pub_date(ignore_qualified: ignore_qualified)
|
17
|
+
return unless date
|
18
|
+
|
19
|
+
if date.is_a? CocinaDisplay::Dates::DateRange
|
20
|
+
date = date.start || date.stop
|
21
|
+
end
|
22
|
+
|
23
|
+
edtf_date = date.date
|
24
|
+
return unless edtf_date
|
25
|
+
|
26
|
+
if edtf_date.is_a? EDTF::Interval
|
27
|
+
edtf_date.from
|
28
|
+
else
|
29
|
+
edtf_date
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# The earliest preferred publication year as an integer.
|
34
|
+
# If the date was a range or interval, uses the start (or end if no start).
|
35
|
+
# Considers publication, creation, and capture dates in that order.
|
36
|
+
# Prefers dates marked as primary and those with a declared encoding.
|
37
|
+
# @param ignore_qualified [Boolean] Reject qualified dates (e.g. approximate)
|
38
|
+
# @return [Integer, nil]
|
39
|
+
# @note 6 BCE will return -5; 4 CE will return 4.
|
40
|
+
def pub_year_int(ignore_qualified: false)
|
41
|
+
pub_date_edtf(ignore_qualified: ignore_qualified)&.year
|
42
|
+
end
|
43
|
+
|
44
|
+
# String for displaying the earliest preferred publication year or range.
|
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 [String, nil]
|
49
|
+
# @example Year range
|
50
|
+
# CocinaRecord.fetch('bb099mt5053').pub_year_display_str #=> "1932 - 2012"
|
51
|
+
def pub_year_display_str(ignore_qualified: false)
|
52
|
+
date = pub_date(ignore_qualified: ignore_qualified)
|
53
|
+
return unless date
|
54
|
+
|
55
|
+
date.decoded_value(allowed_precisions: [:year, :decade, :century])
|
56
|
+
end
|
57
|
+
|
58
|
+
# String for displaying the imprint statement(s).
|
59
|
+
# @return [String, nil]
|
60
|
+
# @see CocinaDisplay::Imprint#display_str
|
61
|
+
# @example
|
62
|
+
# CocinaRecord.fetch('bt553vr2845').imprint_display_str #=> "New York : Meridian Book, 1993, c1967"
|
63
|
+
def imprint_display_str
|
64
|
+
imprints.map(&:display_str).compact_blank.join("; ")
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
# Event dates as an array of CocinaDisplay::Dates::Date objects.
|
70
|
+
# If type is provided, keep dates with a matching event type OR date type.
|
71
|
+
# @param type [Symbol, nil] Filter by event type (e.g. :publication).
|
72
|
+
# @return [Array<CocinaDisplay::Dates::Date>] The list of event dates
|
73
|
+
def event_dates(type: nil)
|
74
|
+
filter_expr = type.present? ? "?match(@.type, \"#{type}\")" : "*"
|
75
|
+
|
76
|
+
Enumerator::Chain.new(
|
77
|
+
path("$.description.event[*].date[#{filter_expr}]"),
|
78
|
+
path("$.description.event[#{filter_expr}].date[*]")
|
79
|
+
).uniq.map do |date|
|
80
|
+
CocinaDisplay::Dates::Date.from_cocina(date)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Array of CocinaDisplay::Imprint objects for all relevant Cocina events.
|
85
|
+
# Considers publication, creation, capture, and copyright events.
|
86
|
+
# Considers event types as well as date types if the event is untyped.
|
87
|
+
# Prefers events where the date was not encoded, if any.
|
88
|
+
# @return [Array<CocinaDisplay::Imprint>] The list of Imprint objects
|
89
|
+
def imprints
|
90
|
+
filter_expr = "\"(publication|creation|capture|copyright)\""
|
91
|
+
|
92
|
+
imprints = Enumerator::Chain.new(
|
93
|
+
path("$.description.event[?match(@.type, #{filter_expr})]"),
|
94
|
+
path("$.description.event[?@.date[?match(@.type, #{filter_expr})]]")
|
95
|
+
).uniq.map do |event|
|
96
|
+
CocinaDisplay::Imprint.new(event)
|
97
|
+
end
|
98
|
+
|
99
|
+
imprints.reject(&:date_encoding?).presence || imprints
|
100
|
+
end
|
101
|
+
|
102
|
+
# The earliest preferred publication date as a CocinaDisplay::Dates::Date object.
|
103
|
+
# Considers publication, creation, and capture dates in that order.
|
104
|
+
# Prefers dates marked as primary and those with a declared encoding.
|
105
|
+
# @param ignore_qualified [Boolean] Reject qualified dates (e.g. approximate)
|
106
|
+
# @return [CocinaDisplay::Dates::Date] The earliest preferred date
|
107
|
+
# @return [nil] if no dates are left after filtering
|
108
|
+
def pub_date(ignore_qualified: false)
|
109
|
+
[:publication, :creation, :capture].map do |type|
|
110
|
+
earliest_preferred_date(event_dates(type: type), ignore_qualified: ignore_qualified)
|
111
|
+
end.compact.first
|
112
|
+
end
|
113
|
+
|
114
|
+
# Choose the earliest, best date from a provided list of event dates.
|
115
|
+
# Rules to consider:
|
116
|
+
# 1. Reject any dates that were not parsed.
|
117
|
+
# 2. If `ignore_qualified` is true, reject any qualified dates.
|
118
|
+
# 3. If there are any primary dates, prefer those dates.
|
119
|
+
# 4. If there are any encoded dates, prefer those dates.
|
120
|
+
# 5. From whatever is left, choose the earliest date.
|
121
|
+
# @param dates [Array<CocinaDisplay::Dates::Date>] The list of dates
|
122
|
+
# @param ignore_qualified [Boolean] Reject qualified dates (e.g. approximate)
|
123
|
+
# @return [CocinaDisplay::Dates::Date] The earliest preferred date
|
124
|
+
# @return [nil] if no dates are left after filtering
|
125
|
+
def earliest_preferred_date(dates, ignore_qualified: false)
|
126
|
+
return nil if dates.empty?
|
127
|
+
|
128
|
+
dates.filter!(&:parsed_date?)
|
129
|
+
dates.reject!(&:approximate?) if ignore_qualified
|
130
|
+
dates = dates.filter(&:primary?).presence || dates
|
131
|
+
dates = dates.filter(&:encoding?).presence || dates
|
132
|
+
|
133
|
+
dates.min
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module CocinaDisplay
|
2
|
+
module Concerns
|
3
|
+
# Methods for extracting and formatting identifiers from Cocina records.
|
4
|
+
module Identifiers
|
5
|
+
# The DRUID for the object, with the +druid:+ prefix.
|
6
|
+
# @return [String]
|
7
|
+
# @example
|
8
|
+
# record.druid #=> "druid:bb099mt5053"
|
9
|
+
def druid
|
10
|
+
cocina_doc["externalIdentifier"]
|
11
|
+
end
|
12
|
+
|
13
|
+
# The DRUID for the object, without the +druid:+ prefix.
|
14
|
+
# @return [String]
|
15
|
+
# @example
|
16
|
+
# record.bare_druid #=> "bb099mt5053"
|
17
|
+
def bare_druid
|
18
|
+
druid.delete_prefix("druid:")
|
19
|
+
end
|
20
|
+
|
21
|
+
# The DOI for the object, if there is one – just the identifier part.
|
22
|
+
# @return [String, nil]
|
23
|
+
# @example
|
24
|
+
# record.doi #=> "10.25740/ppax-bf07"
|
25
|
+
def doi
|
26
|
+
doi_id = path("$.identification.doi").first ||
|
27
|
+
path("$.description.identifier[?match(@.type, 'doi|DOI')].value").first ||
|
28
|
+
path("$.description.identifier[?search(@.uri, 'doi.org')].uri").first
|
29
|
+
|
30
|
+
URI(doi_id).path.delete_prefix("/") if doi_id.present?
|
31
|
+
end
|
32
|
+
|
33
|
+
# The DOI as a URL, if there is one. Any valid DOI should resolve via doi.org.
|
34
|
+
# @return [String, nil]
|
35
|
+
# @example
|
36
|
+
# record.doi_url #=> "https://doi.org/10.25740/ppax-bf07"
|
37
|
+
def doi_url
|
38
|
+
URI.join("https://doi.org", doi).to_s if doi.present?
|
39
|
+
end
|
40
|
+
|
41
|
+
# The HRID of the item in FOLIO, if defined.
|
42
|
+
# @note This doesn't imply the object is available in Searchworks at this ID.
|
43
|
+
# @return [String, nil]
|
44
|
+
# @example
|
45
|
+
# record.folio_hrid #=> "a12845814"
|
46
|
+
def folio_hrid
|
47
|
+
path("$.identification.catalogLinks[?(@.catalog == 'folio')].catalogRecordId").first
|
48
|
+
end
|
49
|
+
|
50
|
+
# The FOLIO HRID if defined, otherwise the bare DRUID.
|
51
|
+
# @note This doesn't imply the object is available in Searchworks at this ID.
|
52
|
+
# @see folio_hrid
|
53
|
+
# @see bare_druid
|
54
|
+
# @return [String]
|
55
|
+
def searchworks_id
|
56
|
+
folio_hrid || bare_druid
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require_relative "../title_builder"
|
2
|
+
|
3
|
+
module CocinaDisplay
|
4
|
+
module Concerns
|
5
|
+
# Methods for finding and formatting titles.
|
6
|
+
module Titles
|
7
|
+
# The main title for the object, without subtitle, part name, etc.
|
8
|
+
# If there are multiple titles, uses the first.
|
9
|
+
# @see CocinaDisplay::TitleBuilder#main_title
|
10
|
+
# @note This corresponds to the "short title" in MODS XML, or MARC 245$a only.
|
11
|
+
# @return [String]
|
12
|
+
def main_title
|
13
|
+
CocinaDisplay::TitleBuilder.main_title(cocina_titles).first
|
14
|
+
end
|
15
|
+
|
16
|
+
# The full title for the object, including subtitle, part name, etc.
|
17
|
+
# If there are multiple titles, uses the first.
|
18
|
+
# @see CocinaDisplay::TitleBuilder#full_title
|
19
|
+
# @note This corresponds to the entire MARC 245 field.
|
20
|
+
# @return [String]
|
21
|
+
def full_title
|
22
|
+
CocinaDisplay::TitleBuilder.full_title(cocina_titles, catalog_links: catalog_links).first
|
23
|
+
end
|
24
|
+
|
25
|
+
# The full title, joined together with additional punctuation.
|
26
|
+
# If there are multiple titles, uses the first.
|
27
|
+
# @see CocinaDisplay::TitleBuilder#build
|
28
|
+
# @return [String]
|
29
|
+
def display_title
|
30
|
+
CocinaDisplay::TitleBuilder.build(cocina_titles, catalog_links: catalog_links)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Any additional titles for the object excluding the main title.
|
34
|
+
# @return [Array<String>]
|
35
|
+
# @see CocinaDisplay::TitleBuilder#additional_titles
|
36
|
+
def additional_titles
|
37
|
+
CocinaDisplay::TitleBuilder.additional_titles(cocina_titles)
|
38
|
+
end
|
39
|
+
|
40
|
+
# A string value for sorting by title that sorts missing values last.
|
41
|
+
# Ignores punctuation, leading/trailing spaces, and non-sorting characters.
|
42
|
+
# @see CocinaDisplay::TitleBuilder#sort_title
|
43
|
+
# @return [String]
|
44
|
+
def sort_title
|
45
|
+
CocinaDisplay::TitleBuilder.sort_title(cocina_titles, catalog_links: catalog_links).first || "\u{10FFFF}"
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# The titles from the Cocina document, as an array of hashes.
|
51
|
+
# @return [Array<Hash>]
|
52
|
+
def cocina_titles
|
53
|
+
@cocina_titles ||= Array(cocina_doc.dig("description", "title"))
|
54
|
+
end
|
55
|
+
|
56
|
+
# The catalog links from the Cocina document, as an array of hashes.
|
57
|
+
# These link to FOLIO and can include part labels used to construct titles.
|
58
|
+
# @return [Array<Hash>]
|
59
|
+
def catalog_links
|
60
|
+
@catalog_links ||= Array(cocina_doc.dig("identification", "catalogLinks"))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|