cocina-models 0.93.1 → 0.94.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +0 -1
- data/.rubocop.yml +36 -1
- data/Gemfile.lock +15 -14
- data/README.md +1 -1
- data/description_types.yml +14 -0
- data/docs/description_types.md +11 -0
- data/lib/cocina/models/builders/title_builder.rb +142 -87
- data/lib/cocina/models/validators/validator.rb +4 -6
- data/lib/cocina/models/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 38c6748d5c7659224f4139fc5d96b6b58732183bf133bb8ea32657a81fadc9ff
|
4
|
+
data.tar.gz: 29615bf905de450af973bd8a7ee00d0cd45810a8d2bd2b55166b2e686e0e048f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d6192a5bc54d0133483649d0bf421a4ce32f85b328d9cda131a6ce8aff7df180b045de1b58d5c78836a7ec53e326b04a141e3a616046febfc31650ec8ef4e80
|
7
|
+
data.tar.gz: 93069e72c38d812ddd493c0de9abcb80a0733fad353a36721821250cbf8c36482b1711748760a8f9a1cbc8fa4915654cf4236496f4a99eaa4ab8e687fee74cbe
|
data/.rspec
CHANGED
data/.rubocop.yml
CHANGED
@@ -458,4 +458,39 @@ Style/YAMLFileRead: # new in 1.53
|
|
458
458
|
RSpec/ReceiveMessages: # new in 2.23
|
459
459
|
Enabled: true
|
460
460
|
RSpec/Rails/NegationBeValid: # new in 2.23
|
461
|
-
Enabled: true
|
461
|
+
Enabled: true
|
462
|
+
|
463
|
+
Lint/ItWithoutArgumentsInBlock: # new in 1.59
|
464
|
+
Enabled: true
|
465
|
+
Lint/LiteralAssignmentInCondition: # new in 1.58
|
466
|
+
Enabled: true
|
467
|
+
Style/SingleLineDoEndBlock: # new in 1.57
|
468
|
+
Enabled: true
|
469
|
+
Style/SuperWithArgsParentheses: # new in 1.58
|
470
|
+
Enabled: true
|
471
|
+
Capybara/ClickLinkOrButtonStyle: # new in 2.19
|
472
|
+
Enabled: true
|
473
|
+
Capybara/RedundantWithinFind: # new in 2.20
|
474
|
+
Enabled: true
|
475
|
+
Capybara/RSpec/HaveSelector: # new in 2.19
|
476
|
+
Enabled: true
|
477
|
+
Capybara/RSpec/PredicateMatcher: # new in 2.19
|
478
|
+
Enabled: true
|
479
|
+
FactoryBot/ExcessiveCreateList: # new in 2.25
|
480
|
+
Enabled: true
|
481
|
+
FactoryBot/IdSequence: # new in 2.24
|
482
|
+
Enabled: true
|
483
|
+
RSpec/EmptyMetadata: # new in 2.24
|
484
|
+
Enabled: true
|
485
|
+
RSpec/Eq: # new in 2.24
|
486
|
+
Enabled: true
|
487
|
+
RSpec/MetadataStyle: # new in 2.24
|
488
|
+
Enabled: true
|
489
|
+
RSpec/RedundantPredicateMatcher: # new in 2.26
|
490
|
+
Enabled: true
|
491
|
+
RSpec/RemoveConst: # new in 2.26
|
492
|
+
Enabled: true
|
493
|
+
RSpec/SpecFilePathFormat: # new in 2.24
|
494
|
+
Enabled: true
|
495
|
+
RSpec/SpecFilePathSuffix: # new in 2.24
|
496
|
+
Enabled: true
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
cocina-models (0.
|
4
|
+
cocina-models (0.94.1)
|
5
5
|
activesupport
|
6
6
|
deprecation
|
7
7
|
dry-struct (~> 1.0)
|
@@ -21,7 +21,7 @@ PATH
|
|
21
21
|
GEM
|
22
22
|
remote: https://rubygems.org/
|
23
23
|
specs:
|
24
|
-
activesupport (7.1.
|
24
|
+
activesupport (7.1.3)
|
25
25
|
base64
|
26
26
|
bigdecimal
|
27
27
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
@@ -34,14 +34,14 @@ GEM
|
|
34
34
|
ast (2.4.2)
|
35
35
|
attr_extras (7.1.0)
|
36
36
|
base64 (0.2.0)
|
37
|
-
bigdecimal (3.1.
|
37
|
+
bigdecimal (3.1.6)
|
38
38
|
byebug (11.1.3)
|
39
39
|
committee (5.0.0)
|
40
40
|
json_schema (~> 0.14, >= 0.14.3)
|
41
41
|
openapi_parser (~> 1.0)
|
42
42
|
rack (>= 1.5)
|
43
43
|
commonmarker (0.23.10)
|
44
|
-
concurrent-ruby (1.2.
|
44
|
+
concurrent-ruby (1.2.3)
|
45
45
|
connection_pool (2.4.1)
|
46
46
|
deprecation (1.1.0)
|
47
47
|
activesupport
|
@@ -62,7 +62,8 @@ GEM
|
|
62
62
|
dry-types (>= 1.7, < 2)
|
63
63
|
ice_nine (~> 0.11)
|
64
64
|
zeitwerk (~> 2.6)
|
65
|
-
dry-types (1.7.
|
65
|
+
dry-types (1.7.2)
|
66
|
+
bigdecimal (~> 3.0)
|
66
67
|
concurrent-ruby (~> 1.0)
|
67
68
|
dry-core (~> 1.0)
|
68
69
|
dry-inflector (~> 1.0)
|
@@ -81,7 +82,7 @@ GEM
|
|
81
82
|
multi_json
|
82
83
|
language_server-protocol (3.17.0.3)
|
83
84
|
mini_portile2 (2.8.5)
|
84
|
-
minitest (5.
|
85
|
+
minitest (5.21.2)
|
85
86
|
multi_json (1.15.0)
|
86
87
|
mutex_m (0.2.0)
|
87
88
|
nokogiri (1.16.0)
|
@@ -92,7 +93,7 @@ GEM
|
|
92
93
|
openapi_parser (1.0.0)
|
93
94
|
optimist (3.1.0)
|
94
95
|
parallel (1.24.0)
|
95
|
-
parser (3.
|
96
|
+
parser (3.3.0.5)
|
96
97
|
ast (~> 2.4.1)
|
97
98
|
racc
|
98
99
|
patience_diff (1.2.0)
|
@@ -101,7 +102,7 @@ GEM
|
|
101
102
|
rack (3.0.8)
|
102
103
|
rainbow (3.1.1)
|
103
104
|
rake (13.1.0)
|
104
|
-
regexp_parser (2.
|
105
|
+
regexp_parser (2.9.0)
|
105
106
|
rexml (3.2.6)
|
106
107
|
rspec (3.12.0)
|
107
108
|
rspec-core (~> 3.12.0)
|
@@ -120,11 +121,11 @@ GEM
|
|
120
121
|
rspec-core (>= 2, < 4, != 2.12.0)
|
121
122
|
rss (0.3.0)
|
122
123
|
rexml
|
123
|
-
rubocop (1.
|
124
|
+
rubocop (1.60.1)
|
124
125
|
json (~> 2.3)
|
125
126
|
language_server-protocol (>= 3.17.0)
|
126
127
|
parallel (~> 1.10)
|
127
|
-
parser (>= 3.
|
128
|
+
parser (>= 3.3.0.2)
|
128
129
|
rainbow (>= 2.2.2, < 4.0)
|
129
130
|
regexp_parser (>= 1.8, < 3.0)
|
130
131
|
rexml (>= 3.2.5, < 4.0)
|
@@ -133,13 +134,13 @@ GEM
|
|
133
134
|
unicode-display_width (>= 2.4.0, < 3.0)
|
134
135
|
rubocop-ast (1.30.0)
|
135
136
|
parser (>= 3.2.1.0)
|
136
|
-
rubocop-capybara (2.
|
137
|
+
rubocop-capybara (2.20.0)
|
138
|
+
rubocop (~> 1.41)
|
139
|
+
rubocop-factory_bot (2.25.1)
|
137
140
|
rubocop (~> 1.41)
|
138
|
-
rubocop-factory_bot (2.24.0)
|
139
|
-
rubocop (~> 1.33)
|
140
141
|
rubocop-rake (0.6.0)
|
141
142
|
rubocop (~> 1.0)
|
142
|
-
rubocop-rspec (2.
|
143
|
+
rubocop-rspec (2.26.1)
|
143
144
|
rubocop (~> 1.40)
|
144
145
|
rubocop-capybara (~> 2.17)
|
145
146
|
rubocop-factory_bot (~> 2.22)
|
data/README.md
CHANGED
@@ -143,7 +143,7 @@ which pushes the gem to rubygems.org.
|
|
143
143
|
|
144
144
|
### Step 2: Update client gems coupled to the models
|
145
145
|
|
146
|
-
Release new versions of [sdr-client](https://github.com/sul-dlss/sdr-client)
|
146
|
+
Release new versions of [sdr-client](https://github.com/sul-dlss/sdr-client), [dor-services-client](https://github.com/sul-dlss/dor-services-client/), and [dor_indexing](https://github.com/sul-dlss/dor_indexing/) pinned to use the new cocina-models version because applications such as [Argo](https://github.com/sul-dlss/argo) depend on both of these gems using the same models.
|
147
147
|
|
148
148
|
### Step 3: Update services directly coupled to the models
|
149
149
|
|
data/description_types.yml
CHANGED
@@ -356,6 +356,7 @@ identifier:
|
|
356
356
|
- value: DOI
|
357
357
|
code: doi
|
358
358
|
- value: druid
|
359
|
+
- value: EUR-OP
|
359
360
|
- value: FOLIO
|
360
361
|
description: FOLIO HRID for the source record of the metadata.
|
361
362
|
- value: GTIN-14 ID
|
@@ -391,6 +392,8 @@ identifier:
|
|
391
392
|
- value: PMCID
|
392
393
|
- value: PMID
|
393
394
|
- value: record id
|
395
|
+
- value: ROR
|
396
|
+
code: ror
|
394
397
|
- value: Senate Number
|
395
398
|
- value: Series
|
396
399
|
- value: SICI
|
@@ -424,6 +427,8 @@ note:
|
|
424
427
|
- value: access note
|
425
428
|
status: deprecated
|
426
429
|
use: access
|
430
|
+
- value: access restriction
|
431
|
+
description: Information about restrictions on who may or how to access a resource.
|
427
432
|
- value: acquisition
|
428
433
|
description: The transfer of a resource to a repository.
|
429
434
|
- value: action
|
@@ -432,6 +437,9 @@ note:
|
|
432
437
|
- value: additions
|
433
438
|
description: Resources added after initial acquisition.
|
434
439
|
- value: admin
|
440
|
+
status: deprecated
|
441
|
+
use: administrative
|
442
|
+
- value: administrative
|
435
443
|
description: Administrative or internal use.
|
436
444
|
- value: affiliation
|
437
445
|
description: Institution with which a person or other entity is associated.
|
@@ -542,6 +550,9 @@ note:
|
|
542
550
|
- value: reproduction
|
543
551
|
- value: research
|
544
552
|
- value: restriction
|
553
|
+
- value: restriction on access
|
554
|
+
status: deprecated
|
555
|
+
use: access restriction
|
545
556
|
- value: rubric
|
546
557
|
- value: scope and content
|
547
558
|
- value: secfol
|
@@ -561,6 +572,7 @@ note:
|
|
561
572
|
- value: table of contents
|
562
573
|
- value: target audience
|
563
574
|
- value: technical note
|
575
|
+
description: Technological requirements for accessing or using the resource.
|
564
576
|
- value: thesis
|
565
577
|
- value: transcript
|
566
578
|
- value: translation
|
@@ -712,6 +724,8 @@ subject.structuredValue:
|
|
712
724
|
description: An individual identity.
|
713
725
|
- value: place
|
714
726
|
description: A geographic location associated with the content of a resource.
|
727
|
+
- value: point coordinates
|
728
|
+
description: The latitude and longitude of a place associated with the content of a resource.
|
715
729
|
- value: region
|
716
730
|
description: An area that incorporates more than one first-order jurisdiction.
|
717
731
|
- value: south
|
data/docs/description_types.md
CHANGED
@@ -363,6 +363,7 @@ _Path: identifier.type_
|
|
363
363
|
* document number
|
364
364
|
* DOI
|
365
365
|
* druid
|
366
|
+
* EUR-OP
|
366
367
|
* FOLIO
|
367
368
|
* FOLIO HRID for the source record of the metadata.
|
368
369
|
* GTIN-14 ID
|
@@ -385,6 +386,7 @@ _Path: identifier.type_
|
|
385
386
|
* PMCID
|
386
387
|
* PMID
|
387
388
|
* record id
|
389
|
+
* ROR
|
388
390
|
* Senate Number
|
389
391
|
* Series
|
390
392
|
* SICI
|
@@ -409,6 +411,8 @@ _Path: note.type_
|
|
409
411
|
* Information about gaining access to a resource.
|
410
412
|
* access note
|
411
413
|
* Deprecated. Preferred usage: access
|
414
|
+
* access restriction
|
415
|
+
* Information about restrictions on who may or how to access a resource.
|
412
416
|
* acquisition
|
413
417
|
* The transfer of a resource to a repository.
|
414
418
|
* action
|
@@ -417,6 +421,8 @@ _Path: note.type_
|
|
417
421
|
* additions
|
418
422
|
* Resources added after initial acquisition.
|
419
423
|
* admin
|
424
|
+
* Deprecated. Preferred usage: administrative
|
425
|
+
* administrative
|
420
426
|
* Administrative or internal use.
|
421
427
|
* affiliation
|
422
428
|
* Institution with which a person or other entity is associated.
|
@@ -522,6 +528,8 @@ _Path: note.type_
|
|
522
528
|
* reproduction
|
523
529
|
* research
|
524
530
|
* restriction
|
531
|
+
* restriction on access
|
532
|
+
* Deprecated. Preferred usage: access restriction
|
525
533
|
* rubric
|
526
534
|
* scope and content
|
527
535
|
* secfol
|
@@ -539,6 +547,7 @@ _Path: note.type_
|
|
539
547
|
* table of contents
|
540
548
|
* target audience
|
541
549
|
* technical note
|
550
|
+
* Technological requirements for accessing or using the resource.
|
542
551
|
* thesis
|
543
552
|
* transcript
|
544
553
|
* translation
|
@@ -695,6 +704,8 @@ _Path: subject.structuredValue.type_
|
|
695
704
|
* An individual identity.
|
696
705
|
* place
|
697
706
|
* A geographic location associated with the content of a resource.
|
707
|
+
* point coordinates
|
708
|
+
* The latitude and longitude of a place associated with the content of a resource.
|
698
709
|
* region
|
699
710
|
* An area that incorporates more than one first-order jurisdiction.
|
700
711
|
* south
|
@@ -12,7 +12,8 @@ module Cocina
|
|
12
12
|
# @param [Symbol] strategy ":first" is the strategy for selection when primary or display
|
13
13
|
# title are missing
|
14
14
|
# @param [Boolean] add_punctuation determines if the title should be formmated with punctuation
|
15
|
-
# @return [String] the title value for Solr
|
15
|
+
# @return [String, Array] the title value for Solr - for :first strategy, a string; for :all strategy, an array
|
16
|
+
# (e.g. title displayed in blacklight search results vs boosting values for search result rankings)
|
16
17
|
def self.build(titles, strategy: :first, add_punctuation: true)
|
17
18
|
if titles.respond_to?(:description)
|
18
19
|
Deprecation.warn(self,
|
@@ -27,7 +28,7 @@ module Cocina
|
|
27
28
|
# we can boost matches on it in search results (boost matching this string higher than matching full title string)
|
28
29
|
# e.g. "The Hobbit" (main_title) vs "The Hobbit, or, There and Back Again (full_title)
|
29
30
|
# @param [[Array<Cocina::Models::Title,Cocina::Models::DescriptiveValue>] titles the titles to consider
|
30
|
-
# @return [String] the main title value for Solr
|
31
|
+
# @return [Array<String>] the main title value(s) for Solr - array due to possible parallelValue
|
31
32
|
def self.main_title(titles)
|
32
33
|
new(strategy: :first, add_punctuation: false).main_title(titles)
|
33
34
|
end
|
@@ -35,9 +36,9 @@ module Cocina
|
|
35
36
|
# the "full title" is the title WITH subtitle, part name, etc. We want to able able to index it separately so
|
36
37
|
# we can boost matches on it in search results (boost matching this string higher than other titles present)
|
37
38
|
# @param [[Array<Cocina::Models::Title,Cocina::Models::DescriptiveValue>] titles the titles to consider
|
38
|
-
# @return [String] the title value for Solr
|
39
|
+
# @return [Array<String>] the full title value(s) for Solr - array due to possible parallelValue
|
39
40
|
def self.full_title(titles)
|
40
|
-
new(strategy: :first, add_punctuation: false).build(titles)
|
41
|
+
[new(strategy: :first, add_punctuation: false, only_one_parallel_value: false).build(titles)].flatten.compact
|
41
42
|
end
|
42
43
|
|
43
44
|
# "additional titles" are all title data except for full_title. We want to able able to index it separately so
|
@@ -45,16 +46,28 @@ module Cocina
|
|
45
46
|
# @param [[Array<Cocina::Models::Title,Cocina::Models::DescriptiveValue>] titles the titles to consider
|
46
47
|
# @return [Array<String>] the values for Solr
|
47
48
|
def self.additional_titles(titles)
|
48
|
-
new(strategy: :all, add_punctuation: false).build(titles) -
|
49
|
+
[new(strategy: :all, add_punctuation: false).build(titles)].flatten - full_title(titles)
|
49
50
|
end
|
50
51
|
|
51
|
-
|
52
|
+
# @param strategy [Symbol] ":first" selects a single title value based on precedence of
|
53
|
+
# primary, untyped, first occurrence. ":all" returns an array containing all the values.
|
54
|
+
# @param add_punctuation [boolean] whether the title should be formmated with punctuation (think of a structured
|
55
|
+
# value coming from a MARC record, which is designed for catalog cards.)
|
56
|
+
# @param only_one_parallel_value [boolean] when true, choose one of the parallel values according to precedence
|
57
|
+
# of primary, untyped, first occurrence. When false, return an array containing all the parallel values.
|
58
|
+
# Why? Think of e.g. title displayed in blacklight search results vs boosting values for ranking of search
|
59
|
+
# results
|
60
|
+
def initialize(strategy:, add_punctuation:, only_one_parallel_value: true)
|
52
61
|
@strategy = strategy
|
53
62
|
@add_punctuation = add_punctuation
|
63
|
+
@only_one_parallel_value = only_one_parallel_value
|
54
64
|
end
|
55
65
|
|
56
66
|
# @param [[Array<Cocina::Models::Title>] cocina_titles the titles to consider
|
57
|
-
# @return [String] the title value for Solr
|
67
|
+
# @return [String, Array] the title value for Solr - for :first strategy, a string; for :all strategy, an array
|
68
|
+
# (e.g. title displayed in blacklight search results vs boosting values for search result rankings)
|
69
|
+
#
|
70
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
58
71
|
def build(cocina_titles)
|
59
72
|
cocina_title = primary_title(cocina_titles) || untyped_title(cocina_titles)
|
60
73
|
cocina_title = other_title(cocina_titles) if cocina_title.blank?
|
@@ -62,15 +75,23 @@ module Cocina
|
|
62
75
|
if strategy == :first
|
63
76
|
extract_title(cocina_title)
|
64
77
|
else
|
65
|
-
cocina_titles.map { |ctitle| extract_title(ctitle) }.flatten
|
78
|
+
result = cocina_titles.map { |ctitle| extract_title(ctitle) }.flatten
|
79
|
+
if only_one_parallel_value? && result.length == 1
|
80
|
+
result.first
|
81
|
+
else
|
82
|
+
result
|
83
|
+
end
|
66
84
|
end
|
67
85
|
end
|
86
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
68
87
|
|
88
|
+
# this is the single "short title" - the title without subtitle, part name, etc.
|
89
|
+
# this may be useful for boosting and exact matching for search results
|
90
|
+
# @return [Array<String>] the main title value(s) for Solr - can be array due to parallel titles
|
69
91
|
def main_title(titles)
|
70
92
|
cocina_title = primary_title(titles) || untyped_title(titles)
|
71
93
|
cocina_title = other_title(titles) if cocina_title.blank?
|
72
94
|
|
73
|
-
cocina_title = cocina_title.first if cocina_title.is_a?(Array)
|
74
95
|
extract_main_title(cocina_title)
|
75
96
|
end
|
76
97
|
|
@@ -79,40 +100,71 @@ module Cocina
|
|
79
100
|
attr_reader :strategy
|
80
101
|
|
81
102
|
def extract_title(cocina_title)
|
103
|
+
title_values = if cocina_title.value
|
104
|
+
cocina_title.value
|
105
|
+
elsif cocina_title.structuredValue.present?
|
106
|
+
rebuild_structured_value(cocina_title)
|
107
|
+
elsif cocina_title.parallelValue.present?
|
108
|
+
extract_title_parallel_values(cocina_title)
|
109
|
+
end
|
110
|
+
result = [title_values].flatten.compact.map { |val| remove_trailing_punctuation(val.strip) }
|
111
|
+
result.length == 1 ? result.first : result
|
112
|
+
end
|
113
|
+
|
114
|
+
# stategy :first says to return a single value (default: true)
|
115
|
+
# only_one_parallel_value? says to return a single value, even if that value is a parallelValue (default: false)
|
116
|
+
#
|
117
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
118
|
+
def extract_title_parallel_values(cocina_title)
|
119
|
+
primary = cocina_title.parallelValue.find { |pvalue| pvalue.status == 'primary' }
|
120
|
+
if primary && only_one_parallel_value? && strategy == :first
|
121
|
+
# we have a primary title and we know we want a single value
|
122
|
+
extract_title(primary)
|
123
|
+
elsif only_one_parallel_value? && strategy == :first
|
124
|
+
# no primary value; algorithm says prefer an untyped value over a typed value for single value
|
125
|
+
untyped = cocina_title.parallelValue.find { |pvalue| pvalue.type.blank? }
|
126
|
+
extract_title(untyped || cocina_title.parallelValue.first)
|
127
|
+
else
|
128
|
+
cocina_title.parallelValue.map { |pvalue| extract_title(pvalue) }
|
129
|
+
end
|
130
|
+
end
|
131
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
132
|
+
|
133
|
+
# @return [Array<String>] the main title value(s) for Solr - can be array due to parallel titles
|
134
|
+
def extract_main_title(cocina_title) # rubocop:disable Metrics/PerceivedComplexity
|
82
135
|
result = if cocina_title.value
|
83
|
-
cocina_title.value
|
136
|
+
cocina_title.value # covers both title and main title types
|
84
137
|
elsif cocina_title.structuredValue.present?
|
85
|
-
|
138
|
+
main_title_from_structured_values(cocina_title)
|
86
139
|
elsif cocina_title.parallelValue.present?
|
87
|
-
|
140
|
+
primary = cocina_title.parallelValue.find { |pvalue| pvalue.status == 'primary' }
|
141
|
+
if primary
|
142
|
+
extract_main_title(primary)
|
143
|
+
else
|
144
|
+
cocina_title.parallelValue.map { |pvalue| extract_main_title(pvalue) }
|
145
|
+
end
|
88
146
|
end
|
89
|
-
|
90
|
-
end
|
147
|
+
return [] if result.blank?
|
91
148
|
|
92
|
-
|
93
|
-
if cocina_title.value
|
94
|
-
cocina_title.value # covers both title and main title types
|
95
|
-
elsif cocina_title.structuredValue.present?
|
96
|
-
main_title_from_structured_values(cocina_title)
|
97
|
-
elsif cocina_title.parallelValue.present?
|
98
|
-
main_title(cocina_title.parallelValue)
|
99
|
-
end
|
149
|
+
[result].flatten.compact.map { |val| remove_trailing_punctuation(val) }
|
100
150
|
end
|
101
151
|
|
102
152
|
def add_punctuation?
|
103
153
|
@add_punctuation
|
104
154
|
end
|
105
155
|
|
156
|
+
def only_one_parallel_value?
|
157
|
+
@only_one_parallel_value
|
158
|
+
end
|
159
|
+
|
106
160
|
# @return [Cocina::Models::Title, nil] title that has status=primary
|
107
|
-
def primary_title(
|
108
|
-
primary_title =
|
109
|
-
title.status == 'primary'
|
110
|
-
end
|
161
|
+
def primary_title(cocina_titles)
|
162
|
+
primary_title = cocina_titles.find { |title| title.status == 'primary' }
|
111
163
|
return primary_title if primary_title.present?
|
112
164
|
|
113
165
|
# NOTE: structuredValues would only have status primary assigned as a sibling, not as an attribute
|
114
166
|
|
115
|
-
|
167
|
+
cocina_titles.find do |title|
|
116
168
|
title.parallelValue&.find do |parallel_title|
|
117
169
|
parallel_title.status == 'primary'
|
118
170
|
end
|
@@ -149,56 +201,63 @@ module Cocina
|
|
149
201
|
# @return [String] the title value from combining the pieces of the structured_values by type and order
|
150
202
|
# with desired punctuation per specs
|
151
203
|
#
|
204
|
+
# for punctuaion funky town, thank MARC and catalog cards
|
205
|
+
#
|
206
|
+
# rubocop:disable Metrics/AbcSize
|
152
207
|
# rubocop:disable Metrics/CyclomaticComplexity
|
153
208
|
# rubocop:disable Metrics/MethodLength
|
154
209
|
# rubocop:disable Metrics/PerceivedComplexity
|
155
|
-
def
|
156
|
-
|
157
|
-
main_title = ''
|
158
|
-
subtitle = ''
|
159
|
-
non_sort_value = ''
|
210
|
+
def rebuild_structured_value(cocina_title)
|
211
|
+
result = ''
|
160
212
|
part_name_number = ''
|
161
|
-
|
162
|
-
# There can be a structuredValue inside a structuredValue
|
213
|
+
cocina_title.structuredValue.each do |structured_value| # rubocop:disable Metrics/BlockLength
|
214
|
+
# There can be a structuredValue inside a structuredValue, for example,
|
163
215
|
# a uniform title where both the name and the title have internal StructuredValue
|
164
|
-
return
|
216
|
+
return rebuild_structured_value(structured_value) if structured_value.structuredValue.present?
|
165
217
|
|
166
218
|
value = structured_value.value&.strip
|
167
219
|
next unless value
|
168
220
|
|
169
|
-
# additional types
|
221
|
+
# additional types ignored here, e.g. name, uniform ...
|
170
222
|
case structured_value.type&.downcase
|
171
223
|
when 'nonsorting characters'
|
172
|
-
|
224
|
+
padding = non_sorting_padding(cocina_title, value)
|
225
|
+
result = add_non_sorting_value(result, value, padding)
|
173
226
|
when 'part name', 'part number'
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
[subtitle, value].join(' ')
|
182
|
-
else
|
183
|
-
value
|
184
|
-
end
|
185
|
-
elsif subtitle.present?
|
186
|
-
# subtitle is preceded by space colon space, unless it is at the beginning of the title string
|
187
|
-
"#{subtitle.sub(/[. :]+$/, '')} : #{value.sub(/^:/, '').strip}"
|
227
|
+
if part_name_number.blank?
|
228
|
+
part_name_number = part_name_number(cocina_title.structuredValue)
|
229
|
+
result = if !add_punctuation?
|
230
|
+
[result, part_name_number].join(' ')
|
231
|
+
elsif result.present?
|
232
|
+
# part name/number is preceded by period space, unless it is at the beginning of the title string
|
233
|
+
"#{result.sub(/[ .,]*$/, '')}. #{part_name_number}. "
|
188
234
|
else
|
189
|
-
|
235
|
+
"#{part_name_number}. "
|
190
236
|
end
|
237
|
+
end
|
238
|
+
when 'main title', 'title'
|
239
|
+
# nonsorting characters ending with hyphen, apostrophe or space should be slammed against the main title,
|
240
|
+
# even if we are not adding punctuation
|
241
|
+
result = if add_punctuation? || result.ends_with?(' ') || result.ends_with?('-') || result.ends_with?('\'')
|
242
|
+
[result, value].join
|
243
|
+
else
|
244
|
+
[remove_trailing_punctuation(result), remove_trailing_punctuation(value)].select(&:presence).join(' ')
|
245
|
+
end
|
246
|
+
when 'subtitle'
|
247
|
+
result = if !add_punctuation?
|
248
|
+
[result, value].select(&:presence).join(' ')
|
249
|
+
elsif result.present?
|
250
|
+
# subtitle is preceded by space colon space, unless it is at the beginning of the title string
|
251
|
+
"#{result.sub(/[. :]+$/, '')} : #{value.sub(/^:/, '').strip}"
|
252
|
+
else
|
253
|
+
result = value.sub(/^:/, '').strip
|
254
|
+
end
|
191
255
|
end
|
192
256
|
end
|
193
257
|
|
194
|
-
|
195
|
-
if add_punctuation?
|
196
|
-
combine_with_punctuation(non_sort_value: non_sort_value, main_title: main_title, subtitle: subtitle,
|
197
|
-
part_name_number: part_name_number)
|
198
|
-
else
|
199
|
-
["#{non_sort_value}#{main_title}", subtitle, part_name_number].select(&:presence).join(' ')
|
200
|
-
end
|
258
|
+
result
|
201
259
|
end
|
260
|
+
# rubocop:enable Metrics/AbcSize
|
202
261
|
# rubocop:enable Metrics/CyclomaticComplexity
|
203
262
|
# rubocop:enable Metrics/MethodLength
|
204
263
|
# rubocop:enable Metrics/PerceivedComplexity
|
@@ -206,11 +265,14 @@ module Cocina
|
|
206
265
|
# main_title is title.structuredValue.value with type 'main title' (or just title.value)
|
207
266
|
# @param [Cocina::Models::Title] title with structured values
|
208
267
|
# @return [String] the main title value
|
209
|
-
|
268
|
+
#
|
269
|
+
# rubocop:disable Metrics/MethodLength
|
270
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
271
|
+
def main_title_from_structured_values(cocina_title)
|
210
272
|
result = ''
|
211
273
|
# combine pieces of the cocina structuredValue into a single title
|
212
274
|
cocina_title.structuredValue.each do |structured_value|
|
213
|
-
# There can be a structuredValue inside a structuredValue
|
275
|
+
# There can be a structuredValue inside a structuredValue, for example,
|
214
276
|
# a uniform title where both the name and the title have internal StructuredValue
|
215
277
|
return main_title_from_structured_values(structured_value) if structured_value.structuredValue.present?
|
216
278
|
|
@@ -219,43 +281,36 @@ module Cocina
|
|
219
281
|
|
220
282
|
case structured_value.type&.downcase
|
221
283
|
when 'nonsorting characters'
|
222
|
-
|
223
|
-
result =
|
224
|
-
when 'main title'
|
225
|
-
result =
|
226
|
-
|
227
|
-
|
284
|
+
padding = non_sorting_padding(cocina_title, value)
|
285
|
+
result = add_non_sorting_value(result, value, padding)
|
286
|
+
when 'main title', 'title'
|
287
|
+
result = if ['\'', '-'].include?(result.last)
|
288
|
+
[result, value].join
|
289
|
+
else
|
290
|
+
[remove_trailing_punctuation(result).strip, remove_trailing_punctuation(value).strip].select(&:presence).join(' ')
|
291
|
+
end
|
228
292
|
end
|
229
293
|
end
|
230
|
-
result
|
231
|
-
end
|
232
294
|
|
233
|
-
# Thank MARC and catalog cards for this mess. We need to add punctuation.
|
234
|
-
# rubocop:disable Metrics/MethodLength
|
235
|
-
def combine_with_punctuation(non_sort_value:, main_title:, subtitle:, part_name_number:)
|
236
|
-
result = "#{non_sort_value}#{main_title}"
|
237
|
-
if subtitle.present?
|
238
|
-
result = if result.present?
|
239
|
-
"#{result.sub(/[. :]+$/, '')} : #{subtitle.sub(/^:/, '').strip}"
|
240
|
-
else
|
241
|
-
result = subtitle
|
242
|
-
end
|
243
|
-
end
|
244
|
-
if part_name_number.present?
|
245
|
-
result = if result.present?
|
246
|
-
"#{result.sub(/[ .,]*$/, '')}. #{part_name_number}."
|
247
|
-
else
|
248
|
-
"#{part_name_number}."
|
249
|
-
end
|
250
|
-
end
|
251
295
|
result
|
252
296
|
end
|
253
297
|
# rubocop:enable Metrics/MethodLength
|
298
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
254
299
|
|
300
|
+
# Thank MARC and catalog cards for this mess.
|
255
301
|
def remove_trailing_punctuation(title)
|
256
302
|
title.sub(%r{[ .,;:/\\]+$}, '')
|
257
303
|
end
|
258
304
|
|
305
|
+
def add_non_sorting_value(title_so_far, non_sorting_value, padding)
|
306
|
+
non_sort_value = "#{non_sorting_value}#{padding}"
|
307
|
+
if title_so_far.present?
|
308
|
+
[title_so_far.strip, padding, non_sort_value].join
|
309
|
+
else
|
310
|
+
non_sort_value
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
259
314
|
def non_sorting_padding(title, non_sorting_value)
|
260
315
|
non_sort_note = title.note&.find { |note| note.type&.downcase == 'nonsorting character count' }
|
261
316
|
if non_sort_note
|
@@ -11,9 +11,7 @@ module Cocina
|
|
11
11
|
PurlValidator,
|
12
12
|
CatalogLinksValidator,
|
13
13
|
AssociatedNameValidator,
|
14
|
-
|
15
|
-
# See also spec/cocina/models/validatable_spec.rb:59
|
16
|
-
# DescriptionTypesValidator,
|
14
|
+
DescriptionTypesValidator,
|
17
15
|
DescriptionValuesValidator,
|
18
16
|
DateTimeValidator,
|
19
17
|
LanguageTagValidator
|
@@ -27,12 +25,12 @@ module Cocina
|
|
27
25
|
VALIDATORS.each { |validator| validator.validate(clazz, attributes_hash) }
|
28
26
|
end
|
29
27
|
|
30
|
-
def self.deep_transform_values(object,
|
28
|
+
def self.deep_transform_values(object, ...)
|
31
29
|
case object
|
32
30
|
when Hash
|
33
|
-
object.transform_values { |value| deep_transform_values(value,
|
31
|
+
object.transform_values { |value| deep_transform_values(value, ...) }
|
34
32
|
when Array
|
35
|
-
object.map { |e| deep_transform_values(e,
|
33
|
+
object.map { |e| deep_transform_values(e, ...) }
|
36
34
|
else
|
37
35
|
yield(object)
|
38
36
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cocina-models
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.94.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Coyne
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-01-
|
11
|
+
date: 2024-01-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|