cocina-models 0.93.1 → 0.94.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/.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
|