cocina-models 0.74.1 → 0.77.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +40 -11
  3. data/.rubocop_todo.yml +71 -2
  4. data/README.md +19 -3
  5. data/cocina-models.gemspec +2 -0
  6. data/description_types.yml +168 -39
  7. data/docs/description_types.md +471 -216
  8. data/lib/cocina/generator/generator.rb +7 -15
  9. data/lib/cocina/generator/schema.rb +1 -3
  10. data/lib/cocina/generator/schema_base.rb +0 -8
  11. data/lib/cocina/generator/schema_ref.rb +1 -1
  12. data/lib/cocina/generator/schema_value.rb +14 -4
  13. data/lib/cocina/models/access.rb +4 -4
  14. data/lib/cocina/models/admin_policy.rb +1 -1
  15. data/lib/cocina/models/admin_policy_access_template.rb +7 -7
  16. data/lib/cocina/models/admin_policy_administrative.rb +1 -1
  17. data/lib/cocina/models/admin_policy_with_metadata.rb +3 -3
  18. data/lib/cocina/models/builders/dro_rights_description_builder.rb +69 -0
  19. data/lib/cocina/models/builders/name_title_group_builder.rb +130 -0
  20. data/lib/cocina/models/builders/rights_description_builder.rb +83 -0
  21. data/lib/cocina/models/builders/title_builder.rb +211 -0
  22. data/lib/cocina/models/citation_only_access.rb +2 -2
  23. data/lib/cocina/models/collection_access.rb +4 -4
  24. data/lib/cocina/models/collection_identification.rb +1 -1
  25. data/lib/cocina/models/collection_with_metadata.rb +2 -2
  26. data/lib/cocina/models/contributor.rb +4 -4
  27. data/lib/cocina/models/controlled_digital_lending_access.rb +2 -2
  28. data/lib/cocina/models/dark_access.rb +4 -4
  29. data/lib/cocina/models/description.rb +3 -3
  30. data/lib/cocina/models/descriptive_basic_value.rb +13 -13
  31. data/lib/cocina/models/descriptive_parallel_contributor.rb +5 -5
  32. data/lib/cocina/models/descriptive_parallel_event.rb +3 -3
  33. data/lib/cocina/models/descriptive_value.rb +13 -13
  34. data/lib/cocina/models/descriptive_value_language.rb +6 -6
  35. data/lib/cocina/models/dro.rb +1 -1
  36. data/lib/cocina/models/dro_access.rb +8 -8
  37. data/lib/cocina/models/dro_with_metadata.rb +3 -3
  38. data/lib/cocina/models/embargo.rb +5 -5
  39. data/lib/cocina/models/event.rb +3 -3
  40. data/lib/cocina/models/file.rb +4 -4
  41. data/lib/cocina/models/file_access.rb +4 -4
  42. data/lib/cocina/models/identification.rb +2 -2
  43. data/lib/cocina/models/language.rb +12 -12
  44. data/lib/cocina/models/location_based_access.rb +1 -1
  45. data/lib/cocina/models/location_based_download_access.rb +1 -1
  46. data/lib/cocina/models/mapping/error_notifier.rb +36 -0
  47. data/lib/cocina/models/mapping/from_mods/access.rb +177 -0
  48. data/lib/cocina/models/mapping/from_mods/admin_metadata.rb +217 -0
  49. data/lib/cocina/models/mapping/from_mods/alt_rep_group.rb +26 -0
  50. data/lib/cocina/models/mapping/from_mods/authority.rb +51 -0
  51. data/lib/cocina/models/mapping/from_mods/contributor.rb +161 -0
  52. data/lib/cocina/models/mapping/from_mods/description.rb +98 -0
  53. data/lib/cocina/models/mapping/from_mods/description_builder.rb +61 -0
  54. data/lib/cocina/models/mapping/from_mods/event.rb +543 -0
  55. data/lib/cocina/models/mapping/from_mods/form.rb +381 -0
  56. data/lib/cocina/models/mapping/from_mods/geographic.rb +219 -0
  57. data/lib/cocina/models/mapping/from_mods/hydrus_default_title_builder.rb +28 -0
  58. data/lib/cocina/models/mapping/from_mods/identifier.rb +51 -0
  59. data/lib/cocina/models/mapping/from_mods/identifier_builder.rb +71 -0
  60. data/lib/cocina/models/mapping/from_mods/identifier_type.rb +292 -0
  61. data/lib/cocina/models/mapping/from_mods/language.rb +36 -0
  62. data/lib/cocina/models/mapping/from_mods/language_script.rb +30 -0
  63. data/lib/cocina/models/mapping/from_mods/language_term.rb +106 -0
  64. data/lib/cocina/models/mapping/from_mods/name_builder.rb +307 -0
  65. data/lib/cocina/models/mapping/from_mods/note.rb +162 -0
  66. data/lib/cocina/models/mapping/from_mods/part_builder.rb +147 -0
  67. data/lib/cocina/models/mapping/from_mods/primary.rb +27 -0
  68. data/lib/cocina/models/mapping/from_mods/purl.rb +53 -0
  69. data/lib/cocina/models/mapping/from_mods/related_resource.rb +105 -0
  70. data/lib/cocina/models/mapping/from_mods/subject.rb +413 -0
  71. data/lib/cocina/models/mapping/from_mods/subject_authority_codes.rb +794 -0
  72. data/lib/cocina/models/mapping/from_mods/title.rb +160 -0
  73. data/lib/cocina/models/mapping/from_mods/title_builder.rb +106 -0
  74. data/lib/cocina/models/mapping/from_mods/title_builder_strategy.rb +19 -0
  75. data/lib/cocina/models/mapping/from_mods/value_uri.rb +25 -0
  76. data/lib/cocina/models/mapping/normalizers/base.rb +16 -0
  77. data/lib/cocina/models/mapping/normalizers/mods/geo_extension_normalizer.rb +69 -0
  78. data/lib/cocina/models/mapping/normalizers/mods/name_normalizer.rb +191 -0
  79. data/lib/cocina/models/mapping/normalizers/mods/origin_info_normalizer.rb +157 -0
  80. data/lib/cocina/models/mapping/normalizers/mods/subject_normalizer.rb +296 -0
  81. data/lib/cocina/models/mapping/normalizers/mods/title_normalizer.rb +91 -0
  82. data/lib/cocina/models/mapping/normalizers/mods_normalizer.rb +409 -0
  83. data/lib/cocina/models/mapping/purl.rb +27 -0
  84. data/lib/cocina/models/mapping/to_mods/access.rb +155 -0
  85. data/lib/cocina/models/mapping/to_mods/admin_metadata.rb +129 -0
  86. data/lib/cocina/models/mapping/to_mods/contributor.rb +49 -0
  87. data/lib/cocina/models/mapping/to_mods/description.rb +63 -0
  88. data/lib/cocina/models/mapping/to_mods/event.rb +200 -0
  89. data/lib/cocina/models/mapping/to_mods/form.rb +292 -0
  90. data/lib/cocina/models/mapping/to_mods/geographic.rb +151 -0
  91. data/lib/cocina/models/mapping/to_mods/id_generator.rb +25 -0
  92. data/lib/cocina/models/mapping/to_mods/identifier.rb +57 -0
  93. data/lib/cocina/models/mapping/to_mods/language.rb +82 -0
  94. data/lib/cocina/models/mapping/to_mods/mods_writer.rb +38 -0
  95. data/lib/cocina/models/mapping/to_mods/name_title_group.rb +29 -0
  96. data/lib/cocina/models/mapping/to_mods/name_writer.rb +228 -0
  97. data/lib/cocina/models/mapping/to_mods/note.rb +105 -0
  98. data/lib/cocina/models/mapping/to_mods/part_writer.rb +115 -0
  99. data/lib/cocina/models/mapping/to_mods/related_resource.rb +108 -0
  100. data/lib/cocina/models/mapping/to_mods/role_writer.rb +50 -0
  101. data/lib/cocina/models/mapping/to_mods/subject.rb +486 -0
  102. data/lib/cocina/models/mapping/to_mods/title.rb +260 -0
  103. data/lib/cocina/models/object_metadata.rb +2 -2
  104. data/lib/cocina/models/presentation.rb +2 -2
  105. data/lib/cocina/models/related_resource.rb +9 -9
  106. data/lib/cocina/models/release_tag.rb +4 -4
  107. data/lib/cocina/models/request_admin_policy.rb +1 -1
  108. data/lib/cocina/models/request_administrative.rb +1 -1
  109. data/lib/cocina/models/request_collection.rb +2 -2
  110. data/lib/cocina/models/request_description.rb +3 -3
  111. data/lib/cocina/models/request_dro.rb +4 -4
  112. data/lib/cocina/models/request_file.rb +5 -5
  113. data/lib/cocina/models/request_identification.rb +1 -1
  114. data/lib/cocina/models/sequence.rb +1 -1
  115. data/lib/cocina/models/source.rb +4 -4
  116. data/lib/cocina/models/standard.rb +5 -5
  117. data/lib/cocina/models/stanford_access.rb +2 -2
  118. data/lib/cocina/models/title.rb +13 -13
  119. data/lib/cocina/models/validators/associated_name_validator.rb +77 -0
  120. data/lib/cocina/models/validators/dark_validator.rb +4 -2
  121. data/lib/cocina/models/validators/open_api_validator.rb +0 -4
  122. data/lib/cocina/models/validators/validator.rb +1 -0
  123. data/lib/cocina/models/version.rb +1 -1
  124. data/lib/cocina/models/world_access.rb +2 -2
  125. data/lib/cocina/models.rb +4 -0
  126. data/lib/cocina/rspec/factories.rb +205 -0
  127. data/lib/cocina/rspec.rb +2 -0
  128. data/openapi.yml +4 -4
  129. metadata +97 -24
  130. data/docs/_config.yml +0 -1
  131. data/docs/maps/Agent.json +0 -18
  132. data/docs/maps/Collection.json +0 -240
  133. data/docs/maps/DRO.json +0 -316
  134. data/docs/maps/Description.json +0 -17
  135. data/docs/maps/File.json +0 -196
  136. data/docs/maps/Fileset.json +0 -143
  137. data/docs/maps/README.md +0 -7
  138. data/docs/maps/ReleaseTag.json +0 -39
  139. data/docs/maps/Sequence.json +0 -46
  140. data/docs/maps/Title.json +0 -18
  141. data/docs/sampleETD/foxml-export.xml +0 -935
  142. data/docs/sampleETD/foxml.xml +0 -3475
  143. data/docs/sampleETD/xn109qc9773_bibframe.ttl +0 -95
  144. data/docs/sampleETD/xn109qc9773_taco.json +0 -158
  145. data/lib/cocina/models/dro_rights_description_builder.rb +0 -67
  146. data/lib/cocina/models/rights_description_builder.rb +0 -81
  147. data/lib/cocina/models/title_builder.rb +0 -208
@@ -54,29 +54,22 @@ module Cocina
54
54
 
55
55
  <<~MARKDOWN
56
56
  #{'#' * (field.count('.') + 1)} #{header_markdown}
57
- _Path: #{field}_
57
+ _Path: #{field}.type_
58
58
  #{types_markdown}
59
59
  MARKDOWN
60
- end.join("\n")
60
+ end.join
61
61
 
62
62
  remove_file 'docs/description_types.md'
63
- create_file 'docs/description_types.md', h1_markdown + markdown
63
+ create_file 'docs/description_types.md', markdown
64
64
  end
65
65
 
66
66
  private
67
67
 
68
- def h1_markdown
69
- <<~MARKDOWN
70
- # Description types
71
-
72
- MARKDOWN
73
- end
74
-
75
68
  def field_markdown_from(field)
76
69
  header = field.split('.')
77
70
  .grep_v(/groupedValue|structuredValue/)
78
71
  .join(' ')
79
- .capitalize
72
+ .upcase_first
80
73
 
81
74
  header_suffix = if field.ends_with?('structuredValue')
82
75
  'part types for structured value'
@@ -91,7 +84,9 @@ module Cocina
91
84
  def types_markdown_from(types)
92
85
  types.map do |type|
93
86
  " * #{type['value']}".tap do |type_value|
94
- type_value << ": #{type['description']}" if type['description']
87
+ type_value << "\n * #{type['description']}" if type['description']
88
+ type_value << "\n * Deprecated." if type['status'] == 'deprecated'
89
+ type_value << " Preferred usage: #{type['use']}" if type['use']
95
90
  end
96
91
  end.join("\n")
97
92
  end
@@ -122,10 +117,7 @@ module Cocina
122
117
 
123
118
  NO_CLEAN = [
124
119
  'checkable.rb',
125
- 'dro_rights_description_builder.rb',
126
120
  'license.rb',
127
- 'rights_description_builder.rb',
128
- 'title_builder.rb',
129
121
  'validatable.rb',
130
122
  'version.rb',
131
123
  'vocabulary.rb'
@@ -101,9 +101,7 @@ module Cocina
101
101
  return [] if doc.one_of.nil?
102
102
 
103
103
  # All properties must be objects.
104
- unless doc.one_of.all? { |schema| schema.type == 'object' }
105
- raise 'All properties for oneOf must be objects'
106
- end
104
+ raise 'All properties for oneOf must be objects' unless doc.one_of.all? { |schema| schema.type == 'object' }
107
105
 
108
106
  doc.one_of.flat_map do |one_of_doc|
109
107
  one_of_doc.properties.map do |key, properties_doc|
@@ -23,14 +23,6 @@ module Cocina
23
23
  key || schema_doc.name
24
24
  end
25
25
 
26
- # Allows non-required values to not be provided. This allows smaller
27
- # requests as not every field needs to be present.
28
- def omittable
29
- return '' if required && !relaxed
30
-
31
- '.meta(omittable: true)'
32
- end
33
-
34
26
  # Allows nillable values to be set to nil. This is useful when doing
35
27
  # an update and you want to clear out a value.
36
28
  def optional
@@ -8,7 +8,7 @@ module Cocina
8
8
  if required && !relaxed
9
9
  "attribute(:#{name.camelize(:lower)}, #{schema_doc.name}.default { #{schema_doc.name}.new })"
10
10
  else
11
- "attribute :#{name.camelize(:lower)}, #{schema_doc.name}.optional.meta(omittable: true)"
11
+ "attribute? :#{name.camelize(:lower)}, #{schema_doc.name}.optional"
12
12
  end
13
13
  end
14
14
  end
@@ -4,15 +4,25 @@ module Cocina
4
4
  module Generator
5
5
  # Class for generating from an openapi value
6
6
  class SchemaValue < SchemaBase
7
- # rubocop:disable Layout/LineLength
8
7
  def generate
9
8
  # optional has to come before default or the default value that gets set will be nil.
10
- "#{description}#{example}#{relaxed_comment}attribute :#{name.camelize(:lower)}, Types::#{dry_datatype(schema_doc)}#{optional}#{default}#{enum}#{omittable}"
9
+ if required && !relaxed
10
+ "#{preamble}attribute :#{name.camelize(:lower)}, #{type}"
11
+ else
12
+ "#{preamble}attribute? :#{name.camelize(:lower)}, #{type}"
13
+ end
11
14
  end
12
- # rubocop:enable Layout/LineLength
13
15
 
14
16
  private
15
17
 
18
+ def type
19
+ "Types::#{dry_datatype(schema_doc)}#{optional}#{default}#{enum}"
20
+ end
21
+
22
+ def preamble
23
+ "#{description}#{example}#{relaxed_comment}"
24
+ end
25
+
16
26
  def enum
17
27
  return '' if !schema_doc.enum || relaxed
18
28
 
@@ -32,7 +42,7 @@ module Cocina
32
42
  # If type is boolean and default is false, erroneously getting a nil.
33
43
  # Assuming that if required, then default is false.
34
44
  default = schema_doc.default
35
- default = false if default.nil? && schema_doc.type == 'boolean' && required
45
+ default = false if default.nil? && schema_doc.type == 'boolean'
36
46
 
37
47
  return '' if default.nil?
38
48
 
@@ -5,15 +5,15 @@ module Cocina
5
5
  class Access < Struct
6
6
  # Access level.
7
7
  # Validation of this property is relaxed. See the openapi for full validation.
8
- attribute :view, Types::Strict::String.optional.default('dark').meta(omittable: true)
8
+ attribute? :view, Types::Strict::String.optional.default('dark')
9
9
  # Download access level.
10
10
  # Validation of this property is relaxed. See the openapi for full validation.
11
- attribute :download, Types::Strict::String.optional.default('none').meta(omittable: true)
11
+ attribute? :download, Types::Strict::String.optional.default('none')
12
12
  # Not used for this access type, must be null.
13
13
  # Validation of this property is relaxed. See the openapi for full validation.
14
- attribute :location, Types::Strict::String.optional.meta(omittable: true)
14
+ attribute? :location, Types::Strict::String.optional
15
15
  # Validation of this property is relaxed. See the openapi for full validation.
16
- attribute :controlledDigitalLending, Types::Strict::Bool.optional.meta(omittable: true)
16
+ attribute? :controlledDigitalLending, Types::Strict::Bool.optional.default(false)
17
17
  end
18
18
  end
19
19
  end
@@ -18,7 +18,7 @@ module Cocina
18
18
  attribute :label, Types::Strict::String
19
19
  attribute :version, Types::Strict::Integer
20
20
  attribute(:administrative, AdminPolicyAdministrative.default { AdminPolicyAdministrative.new })
21
- attribute :description, Description.optional.meta(omittable: true)
21
+ attribute? :description, Description.optional
22
22
  end
23
23
  end
24
24
  end
@@ -3,23 +3,23 @@
3
3
  module Cocina
4
4
  module Models
5
5
  class AdminPolicyAccessTemplate < Struct
6
- attribute :view, Types::Strict::String.enum('world', 'stanford', 'location-based', 'citation-only', 'dark').meta(omittable: true)
6
+ attribute? :view, Types::Strict::String.enum('world', 'stanford', 'location-based', 'citation-only', 'dark')
7
7
  # Available for controlled digital lending.
8
- attribute :controlledDigitalLending, Types::Strict::Bool.meta(omittable: true)
8
+ attribute? :controlledDigitalLending, Types::Strict::Bool.default(false)
9
9
  # The human readable copyright statement that applies
10
10
  # example: Copyright World Trade Organization
11
- attribute :copyright, Types::Strict::String.optional.meta(omittable: true)
11
+ attribute? :copyright, Types::Strict::String.optional
12
12
  # Download access level. This is used in the transition from Fedora as a way to set a default download level at registration that is copied down to all the files.
13
13
 
14
- attribute :download, Types::Strict::String.enum('world', 'stanford', 'location-based', 'none').meta(omittable: true)
14
+ attribute? :download, Types::Strict::String.enum('world', 'stanford', 'location-based', 'none')
15
15
  # If access or download is "location-based", this indicates which location should have access. This is used in the transition from Fedora as a way to set a default location at registration that is copied down to all the files.
16
16
 
17
- attribute :location, Types::Strict::String.optional.enum('spec', 'music', 'ars', 'art', 'hoover', 'm&m').meta(omittable: true)
17
+ attribute? :location, Types::Strict::String.optional.enum('spec', 'music', 'ars', 'art', 'hoover', 'm&m')
18
18
  # The human readable use and reproduction statement that applies
19
19
  # example: Property rights reside with the repository. Literary rights reside with the creators of the documents or their heirs. To obtain permission to publish or reproduce, please contact the Public Services Librarian of the Dept. of Special Collections (http://library.stanford.edu/spc).
20
- attribute :useAndReproductionStatement, Types::Strict::String.optional.meta(omittable: true)
20
+ attribute? :useAndReproductionStatement, Types::Strict::String.optional
21
21
  # The license governing reuse of the Collection. Should be an IRI for known licenses (i.e. CC, RightsStatement.org URI, etc.).
22
- attribute :license, Types::Strict::String.optional.meta(omittable: true)
22
+ attribute? :license, Types::Strict::String.optional
23
23
  end
24
24
  end
25
25
  end
@@ -7,7 +7,7 @@ module Cocina
7
7
  attribute :registrationWorkflow, Types::Strict::Array.of(Types::Strict::String).default([].freeze)
8
8
  # An additional workflow to start for objects managed by this admin policy once the end-accession workflow step is complete
9
9
  # example: wasCrawlPreassemblyWF
10
- attribute :disseminationWorkflow, Types::Strict::String.meta(omittable: true)
10
+ attribute? :disseminationWorkflow, Types::Strict::String
11
11
  attribute :collectionsForRegistration, Types::Strict::Array.of(Types::Strict::String).default([].freeze)
12
12
  # example: druid:bc123df4567
13
13
  attribute :hasAdminPolicy, Types::Strict::String
@@ -18,11 +18,11 @@ module Cocina
18
18
  attribute :label, Types::Strict::String
19
19
  attribute :version, Types::Strict::Integer
20
20
  attribute(:administrative, AdminPolicyAdministrative.default { AdminPolicyAdministrative.new })
21
- attribute :description, Description.optional.meta(omittable: true)
21
+ attribute? :description, Description.optional
22
22
  # When the object was created.
23
- attribute :created, Types::Params::DateTime.meta(omittable: true)
23
+ attribute? :created, Types::Params::DateTime
24
24
  # When the object was modified.
25
- attribute :modified, Types::Params::DateTime.meta(omittable: true)
25
+ attribute? :modified, Types::Params::DateTime
26
26
  # Key for optimistic locking. The contents of the key is not specified.
27
27
  attribute :lock, Types::Strict::String
28
28
  end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ module Models
5
+ module Builders
6
+ # Rights description builder for items
7
+ class DroRightsDescriptionBuilder < RightsDescriptionBuilder
8
+ # @param [Cocina::Models::DRO] cocina_item
9
+
10
+ # This overrides the superclass
11
+ # @return [Cocina::Models::DROAccess]
12
+ def object_access
13
+ @object_access ||= cocina.access
14
+ end
15
+
16
+ private
17
+
18
+ def object_level_access
19
+ super + access_level_from_files.uniq.map { |str| "#{str} (file)" }
20
+ end
21
+
22
+ def access_level_from_files
23
+ # dark access doesn't permit any file access
24
+ return [] if object_access.view == 'dark'
25
+
26
+ file_access_nodes.reject { |fa| same_as_object_access?(fa) }.flat_map do |fa|
27
+ file_access_from_file(fa)
28
+ end
29
+ end
30
+
31
+ # rubocop:disable Metrics/MethodLength
32
+ def file_access_from_file(file_access)
33
+ basic_access = if file_access[:view] == 'location-based'
34
+ "location: #{file_access[:location]}"
35
+ else
36
+ file_access[:view]
37
+ end
38
+
39
+ return [basic_access] if file_access[:view] == file_access[:download]
40
+
41
+ basic_access += ' (no-download)' if file_access[:view] != 'dark'
42
+
43
+ case file_access[:download]
44
+ when 'stanford'
45
+ [basic_access, 'stanford']
46
+ when 'location-based'
47
+ # Here we're using location to mean download location.
48
+ [basic_access, "location: #{file_access[:location]}"]
49
+ else
50
+ [basic_access]
51
+ end
52
+ end
53
+ # rubocop:enable Metrics/MethodLength
54
+
55
+ def same_as_object_access?(file_access)
56
+ (file_access[:view] == object_access.view && file_access[:download] == object_access.download) ||
57
+ (object_access.view == 'citation-only' && file_access[:view] == 'dark')
58
+ end
59
+
60
+ def file_access_nodes
61
+ Array(cocina.structural.contains)
62
+ .flat_map { |fs| Array(fs.structural.contains) }
63
+ .map { |file| file.access.to_h }
64
+ .uniq
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ module Models
5
+ module Builders
6
+ # Helpers for MODS nameTitleGroups.
7
+ # MODS titles need to know if they match a contributor and thus need a nameTitleGroup
8
+ # MODS contributors need to know if they match a title and thus need a nameTitleGroup
9
+ # If there is a match, the nameTitleGroup number has to be consistent for the matching title(s)
10
+ # and the contributor(s)
11
+ class NameTitleGroupBuilder
12
+ # When to assign nameTitleGroup to MODS from cocina:
13
+ # for cocina title of type "uniform",
14
+ # look for cocina title properties :value or :structuredValue (recurse down through :parallelValue
15
+ # as needed), and look for associated :note with :type of "associated name" at the level of the
16
+ # non-empty title [value|structuredValue]
17
+ # The note of type "associated name" will have [value|structuredValue] which will match
18
+ # [value|structuredValue] for a contributor (possibly after recursing through :parallelValue).
19
+ # Thus, a title [value|structuredValue] and a contributor [value|structuredValue] are associated in
20
+ # cocina.
21
+ #
22
+ # If those criteria not met in Cocina, do not assign nameTitleGroup in MODS
23
+ #
24
+ # @params [Cocina::Models::Title] title
25
+ # @return [Hash<Hash, Hash>] key: hash of value or structuredValue property for title
26
+ # value: hash of value or structuredValue property for contributor
27
+ # e.g. {{:value=>"Portrait of the artist as a young man"}=>{:value=>"James Joyce"}}
28
+ # e.g. {{:value=>"Portrait of the artist as a young man"}=>{:structuredValue=>
29
+ # [{:value=>"Joyce, James", :type=>"name"},{:value=>"1882-1941", :type=>"life dates"}]}}
30
+ # e.g. {{:structuredValue=>[{:value=>"Demanding Food", :type=>"main"},
31
+ # {:value=>"A Cat's Life", :type=>"subtitle"}]}=>{:value=>"James Joyce"}}
32
+ # this complexity is needed for multilingual titles mapping to multilingual names.
33
+ def self.build_title_values_to_contributor_name_values(title)
34
+ result = {}
35
+ return result if title.blank?
36
+
37
+ # pair title value with contributor name value
38
+ title_value_note_slices(title).each do |value_note_slice|
39
+ title_val_slice = slice_of_value_or_structured_value(value_note_slice)
40
+ next if title_val_slice.blank?
41
+
42
+ associated_name_note = value_note_slice[:note]&.detect { |note| note[:type] == 'associated name' }
43
+ next if associated_name_note.blank?
44
+
45
+ # relevant note will be Array of either
46
+ # {
47
+ # value: 'string contributor name',
48
+ # type: 'associated name'
49
+ # }
50
+ # OR
51
+ # {
52
+ # structuredValue: [ structuredValue contributor name ],
53
+ # type: 'associated name'
54
+ # }
55
+ # and we want the hash without the :type attribute
56
+ result[title_val_slice] = slice_of_value_or_structured_value(associated_name_note)
57
+ end
58
+ result
59
+ end
60
+
61
+ def self.contributor_for_contributor_name_value_slice(contributor_name_value_slice:, contributors:)
62
+ Array(contributors).find do |contributor|
63
+ contrib_name_value_slices = contributor_name_value_slices(contributor)
64
+ contrib_name_value_slices.include?(contributor_name_value_slice)
65
+ end
66
+ end
67
+
68
+ # @params [Cocina::Models::Contributor] contributor
69
+ # @return [Hash] where we are only interested in
70
+ # hashes containing (either :value or :structureValue)
71
+ def self.contributor_name_value_slices(contributor)
72
+ return if contributor&.name.blank?
73
+
74
+ slices = []
75
+ Array(contributor.name).each do |contrib_name|
76
+ slices << value_slices(contrib_name)
77
+ end
78
+ slices.flatten
79
+ end
80
+
81
+ # @params [Cocina::Models::DescriptiveValue] desc_value
82
+ # @return [Array<Cocina::Models::DescriptiveValue>] where we are only interested in
83
+ # hashes containing (either :value or :structuredValue)
84
+ def self.value_slices(desc_value)
85
+ slices = []
86
+ desc_value_slice = desc_value.to_h.slice(:value, :structuredValue, :parallelValue)
87
+ if desc_value_slice[:value].present? || desc_value_slice[:structuredValue].present?
88
+ slices << desc_value_slice.select { |_k, value| value.present? }
89
+ elsif desc_value_slice[:parallelValue].present?
90
+ desc_value_slice[:parallelValue].each { |parallel_val| slices << value_slices(parallel_val) }
91
+ end
92
+ # ignoring groupedValue
93
+ slices.flatten
94
+ end
95
+ # private_class_method :value_slices
96
+
97
+ # for a given Hash (from a Cocina DescriptiveValue or Title or Name or ...)
98
+ # result will be either
99
+ # { value: 'string value' }
100
+ # OR
101
+ # { structuredValue: [ some structuredValue ] }
102
+ def self.slice_of_value_or_structured_value(hash)
103
+ if hash[:value].present?
104
+ hash.slice(:value).select { |_k, value| value.present? }
105
+ elsif hash[:structuredValue].present?
106
+ hash.slice(:structuredValue).select { |_k, value| value.present? }
107
+ end
108
+ end
109
+
110
+ # reduce parallelValues down to value or structuredValue for these slices
111
+ # @params [Cocina::Models::Title] title
112
+ # @return [Array<Cocina::Models::DescriptiveValue>] where we are only interested in
113
+ # hashes containing (either :value or :structureValue) and :note if present
114
+ def self.title_value_note_slices(title)
115
+ slices = []
116
+ title_slice = title.to_h.slice(:value, :structuredValue, :parallelValue, :note)
117
+ if title_slice[:value].present? || title_slice[:structuredValue].present?
118
+ slices << title_slice.select { |_k, value| value.present? }
119
+ elsif title_slice[:parallelValue].present?
120
+ title_slice[:parallelValue].each do |parallel_val|
121
+ slices << title_value_note_slices(parallel_val)
122
+ end
123
+ end
124
+ # ignoring groupedValue
125
+ slices.flatten
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ module Models
5
+ module Builders
6
+ # RightsDescriptionBuilder
7
+ class RightsDescriptionBuilder
8
+ # @param [Cocina::Models::AdminPolicy, Cocina::Models::DRO] cocina_object
9
+ def self.build(cocina_object)
10
+ new(cocina_object).build
11
+ end
12
+
13
+ def initialize(cocina_object)
14
+ @cocina = cocina_object
15
+ end
16
+
17
+ # This is set up to work for APOs, but this method is to be overridden on sub classes
18
+ # @return [Cocina::Models::AdminPolicyDefaultAccess]
19
+ def object_access
20
+ @object_access ||= cocina.administrative.accessTemplate
21
+ end
22
+
23
+ def build
24
+ return 'controlled digital lending' if object_access.controlledDigitalLending
25
+
26
+ return ['dark'] if object_access.view == 'dark'
27
+
28
+ object_level_access
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :cocina
34
+
35
+ # rubocop:disable Metrics/MethodLength
36
+ def object_level_access
37
+ case object_access.view
38
+ when 'citation-only'
39
+ ['citation']
40
+ when 'world'
41
+ world_object_access
42
+ when 'location-based'
43
+ case object_access.download
44
+ when 'none'
45
+ ["location: #{object_access.location} (no-download)"]
46
+ else
47
+ ["location: #{object_access.location}"]
48
+ end
49
+ when 'stanford'
50
+ stanford_object_access
51
+ end
52
+ end
53
+ # rubocop:enable Metrics/MethodLength
54
+
55
+ def stanford_object_access
56
+ case object_access.download
57
+ when 'none'
58
+ ['stanford (no-download)']
59
+ when 'location-based'
60
+ # this is an odd case we might want to move away from. See https://github.com/sul-dlss/cocina-models/issues/258
61
+ ['stanford (no-download)', "location: #{object_access.location}"]
62
+ else
63
+ ['stanford']
64
+ end
65
+ end
66
+
67
+ def world_object_access
68
+ case object_access.download
69
+ when 'stanford'
70
+ ['stanford', 'world (no-download)']
71
+ when 'none'
72
+ ['world (no-download)']
73
+ when 'world'
74
+ ['world']
75
+ when 'location-based'
76
+ # this is an odd case we might want to move away from. See https://github.com/sul-dlss/cocina-models/issues/258
77
+ ['world (no-download)', "location: #{object_access.location}"]
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end