cocina-models 0.74.0 → 0.76.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +40 -11
- data/.rubocop_todo.yml +71 -2
- data/README.md +19 -3
- data/cocina-models.gemspec +2 -0
- data/description_types.yml +167 -39
- data/docs/description_types.md +470 -216
- data/lib/cocina/generator/generator.rb +7 -15
- data/lib/cocina/generator/schema.rb +1 -3
- data/lib/cocina/generator/schema_base.rb +0 -8
- data/lib/cocina/generator/schema_ref.rb +1 -1
- data/lib/cocina/generator/schema_value.rb +14 -4
- data/lib/cocina/models/access.rb +4 -4
- data/lib/cocina/models/admin_policy.rb +1 -1
- data/lib/cocina/models/admin_policy_access_template.rb +7 -7
- data/lib/cocina/models/admin_policy_administrative.rb +1 -1
- data/lib/cocina/models/admin_policy_with_metadata.rb +3 -3
- data/lib/cocina/models/builders/dro_rights_description_builder.rb +69 -0
- data/lib/cocina/models/builders/name_title_group_builder.rb +130 -0
- data/lib/cocina/models/builders/rights_description_builder.rb +83 -0
- data/lib/cocina/models/builders/title_builder.rb +211 -0
- data/lib/cocina/models/citation_only_access.rb +2 -2
- data/lib/cocina/models/collection_access.rb +4 -4
- data/lib/cocina/models/collection_identification.rb +1 -1
- data/lib/cocina/models/collection_with_metadata.rb +2 -2
- data/lib/cocina/models/contributor.rb +4 -4
- data/lib/cocina/models/controlled_digital_lending_access.rb +2 -2
- data/lib/cocina/models/dark_access.rb +4 -4
- data/lib/cocina/models/description.rb +3 -3
- data/lib/cocina/models/descriptive_basic_value.rb +13 -13
- data/lib/cocina/models/descriptive_parallel_contributor.rb +5 -5
- data/lib/cocina/models/descriptive_parallel_event.rb +3 -3
- data/lib/cocina/models/descriptive_value.rb +13 -13
- data/lib/cocina/models/descriptive_value_language.rb +6 -6
- data/lib/cocina/models/dro.rb +1 -1
- data/lib/cocina/models/dro_access.rb +8 -8
- data/lib/cocina/models/dro_with_metadata.rb +3 -3
- data/lib/cocina/models/embargo.rb +5 -5
- data/lib/cocina/models/event.rb +3 -3
- data/lib/cocina/models/file.rb +4 -4
- data/lib/cocina/models/file_access.rb +4 -4
- data/lib/cocina/models/identification.rb +2 -2
- data/lib/cocina/models/language.rb +12 -12
- data/lib/cocina/models/location_based_access.rb +1 -1
- data/lib/cocina/models/location_based_download_access.rb +1 -1
- data/lib/cocina/models/mapping/error_notifier.rb +36 -0
- data/lib/cocina/models/mapping/from_mods/access.rb +177 -0
- data/lib/cocina/models/mapping/from_mods/admin_metadata.rb +217 -0
- data/lib/cocina/models/mapping/from_mods/alt_rep_group.rb +26 -0
- data/lib/cocina/models/mapping/from_mods/authority.rb +51 -0
- data/lib/cocina/models/mapping/from_mods/contributor.rb +161 -0
- data/lib/cocina/models/mapping/from_mods/description.rb +99 -0
- data/lib/cocina/models/mapping/from_mods/description_builder.rb +61 -0
- data/lib/cocina/models/mapping/from_mods/event.rb +543 -0
- data/lib/cocina/models/mapping/from_mods/form.rb +381 -0
- data/lib/cocina/models/mapping/from_mods/geographic.rb +219 -0
- data/lib/cocina/models/mapping/from_mods/hydrus_default_title_builder.rb +28 -0
- data/lib/cocina/models/mapping/from_mods/identifier.rb +51 -0
- data/lib/cocina/models/mapping/from_mods/identifier_builder.rb +71 -0
- data/lib/cocina/models/mapping/from_mods/identifier_type.rb +292 -0
- data/lib/cocina/models/mapping/from_mods/language.rb +36 -0
- data/lib/cocina/models/mapping/from_mods/language_script.rb +30 -0
- data/lib/cocina/models/mapping/from_mods/language_term.rb +106 -0
- data/lib/cocina/models/mapping/from_mods/name_builder.rb +307 -0
- data/lib/cocina/models/mapping/from_mods/note.rb +162 -0
- data/lib/cocina/models/mapping/from_mods/part_builder.rb +147 -0
- data/lib/cocina/models/mapping/from_mods/primary.rb +27 -0
- data/lib/cocina/models/mapping/from_mods/purl.rb +53 -0
- data/lib/cocina/models/mapping/from_mods/related_resource.rb +105 -0
- data/lib/cocina/models/mapping/from_mods/subject.rb +413 -0
- data/lib/cocina/models/mapping/from_mods/subject_authority_codes.rb +794 -0
- data/lib/cocina/models/mapping/from_mods/title.rb +160 -0
- data/lib/cocina/models/mapping/from_mods/title_builder.rb +106 -0
- data/lib/cocina/models/mapping/from_mods/title_builder_strategy.rb +19 -0
- data/lib/cocina/models/mapping/from_mods/value_uri.rb +25 -0
- data/lib/cocina/models/mapping/normalizers/base.rb +16 -0
- data/lib/cocina/models/mapping/normalizers/mods/geo_extension_normalizer.rb +69 -0
- data/lib/cocina/models/mapping/normalizers/mods/name_normalizer.rb +191 -0
- data/lib/cocina/models/mapping/normalizers/mods/origin_info_normalizer.rb +157 -0
- data/lib/cocina/models/mapping/normalizers/mods/subject_normalizer.rb +296 -0
- data/lib/cocina/models/mapping/normalizers/mods/title_normalizer.rb +91 -0
- data/lib/cocina/models/mapping/normalizers/mods_normalizer.rb +409 -0
- data/lib/cocina/models/mapping/purl.rb +28 -0
- data/lib/cocina/models/mapping/to_mods/access.rb +155 -0
- data/lib/cocina/models/mapping/to_mods/admin_metadata.rb +129 -0
- data/lib/cocina/models/mapping/to_mods/contributor.rb +49 -0
- data/lib/cocina/models/mapping/to_mods/description.rb +63 -0
- data/lib/cocina/models/mapping/to_mods/event.rb +200 -0
- data/lib/cocina/models/mapping/to_mods/form.rb +292 -0
- data/lib/cocina/models/mapping/to_mods/geographic.rb +151 -0
- data/lib/cocina/models/mapping/to_mods/id_generator.rb +25 -0
- data/lib/cocina/models/mapping/to_mods/identifier.rb +57 -0
- data/lib/cocina/models/mapping/to_mods/language.rb +82 -0
- data/lib/cocina/models/mapping/to_mods/mods_writer.rb +38 -0
- data/lib/cocina/models/mapping/to_mods/name_title_group.rb +29 -0
- data/lib/cocina/models/mapping/to_mods/name_writer.rb +228 -0
- data/lib/cocina/models/mapping/to_mods/note.rb +105 -0
- data/lib/cocina/models/mapping/to_mods/part_writer.rb +115 -0
- data/lib/cocina/models/mapping/to_mods/related_resource.rb +108 -0
- data/lib/cocina/models/mapping/to_mods/role_writer.rb +50 -0
- data/lib/cocina/models/mapping/to_mods/subject.rb +486 -0
- data/lib/cocina/models/mapping/to_mods/title.rb +260 -0
- data/lib/cocina/models/object_metadata.rb +2 -2
- data/lib/cocina/models/presentation.rb +2 -2
- data/lib/cocina/models/related_resource.rb +9 -9
- data/lib/cocina/models/release_tag.rb +4 -4
- data/lib/cocina/models/request_admin_policy.rb +1 -1
- data/lib/cocina/models/request_administrative.rb +1 -1
- data/lib/cocina/models/request_collection.rb +2 -2
- data/lib/cocina/models/request_description.rb +3 -3
- data/lib/cocina/models/request_dro.rb +4 -4
- data/lib/cocina/models/request_file.rb +5 -5
- data/lib/cocina/models/request_identification.rb +1 -1
- data/lib/cocina/models/sequence.rb +1 -1
- data/lib/cocina/models/source.rb +4 -4
- data/lib/cocina/models/standard.rb +5 -5
- data/lib/cocina/models/stanford_access.rb +2 -2
- data/lib/cocina/models/title.rb +13 -13
- data/lib/cocina/models/validators/associated_name_validator.rb +77 -0
- data/lib/cocina/models/validators/dark_validator.rb +4 -2
- data/lib/cocina/models/validators/open_api_validator.rb +0 -4
- data/lib/cocina/models/validators/validator.rb +1 -0
- data/lib/cocina/models/version.rb +1 -1
- data/lib/cocina/models/world_access.rb +2 -2
- data/lib/cocina/models.rb +4 -0
- data/lib/cocina/rspec/factories.rb +157 -0
- data/lib/cocina/rspec.rb +2 -0
- data/openapi.yml +4 -4
- metadata +93 -6
- data/docs/_config.yml +0 -1
- data/lib/cocina/models/dro_rights_description_builder.rb +0 -67
- data/lib/cocina/models/rights_description_builder.rb +0 -81
- data/lib/cocina/models/title_builder.rb +0 -208
@@ -5,15 +5,15 @@ module Cocina
|
|
5
5
|
class FileAccess < 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')
|
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')
|
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
|
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.
|
16
|
+
attribute? :controlledDigitalLending, Types::Strict::Bool.optional.default(false)
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
@@ -4,11 +4,11 @@ module Cocina
|
|
4
4
|
module Models
|
5
5
|
class Identification < Struct
|
6
6
|
# A barcode
|
7
|
-
attribute :barcode, Types::Nominal::Any
|
7
|
+
attribute? :barcode, Types::Nominal::Any
|
8
8
|
attribute :catalogLinks, Types::Strict::Array.of(CatalogLink).default([].freeze)
|
9
9
|
# Digital Object Identifier (https://www.doi.org)
|
10
10
|
# example: 10.25740/bc123df4567
|
11
|
-
attribute :doi, Types::Strict::String
|
11
|
+
attribute? :doi, Types::Strict::String
|
12
12
|
# Unique identifier in some other system. This is because a large proportion of what is deposited in SDR, historically and currently, are representations of objects that are also represented in other systems. For example, digitized paper and A/V collections have physical manifestations, and those physical objects are managed in systems that have their own identifiers. Similarly, books have barcodes, archival materials have collection numbers and physical locations, etc. The sourceId allows determining if an item has been deposited before and where to look for the original item if you're looking at its SDR representation. The format is: "namespace:identifier"
|
13
13
|
|
14
14
|
# example: sul:PC0170_s3_Fiesta_Bowl_2012-01-02_210609_2026
|
@@ -5,28 +5,28 @@ module Cocina
|
|
5
5
|
class Language < Struct
|
6
6
|
attribute :appliesTo, Types::Strict::Array.of(DescriptiveBasicValue).default([].freeze)
|
7
7
|
# Code value of the descriptive element.
|
8
|
-
attribute :code, Types::Strict::String
|
8
|
+
attribute? :code, Types::Strict::String
|
9
9
|
# The preferred display label to use for the descriptive element in access systems.
|
10
|
-
attribute :displayLabel, Types::Strict::String
|
11
|
-
attribute :encoding, Standard.optional
|
10
|
+
attribute? :displayLabel, Types::Strict::String
|
11
|
+
attribute? :encoding, Standard.optional
|
12
12
|
attribute :groupedValue, Types::Strict::Array.of(DescriptiveValue).default([].freeze)
|
13
13
|
attribute :note, Types::Strict::Array.of(DescriptiveValue).default([].freeze)
|
14
14
|
attribute :parallelValue, Types::Strict::Array.of(DescriptiveValue).default([].freeze)
|
15
15
|
# present for mapping to additional schemas in the future and for consistency but not otherwise used
|
16
|
-
attribute :qualifier, Types::Strict::String
|
17
|
-
attribute :script, DescriptiveValue.optional
|
18
|
-
attribute :source, Source.optional
|
16
|
+
attribute? :qualifier, Types::Strict::String
|
17
|
+
attribute? :script, DescriptiveValue.optional
|
18
|
+
attribute? :source, Source.optional
|
19
19
|
# Status of the language relative to other parallel language elements (e.g. the primary language)
|
20
|
-
attribute :status, Types::Strict::String.enum('primary')
|
21
|
-
attribute :standard, Standard.optional
|
20
|
+
attribute? :status, Types::Strict::String.enum('primary')
|
21
|
+
attribute? :standard, Standard.optional
|
22
22
|
attribute :structuredValue, Types::Strict::Array.of(DescriptiveValue).default([].freeze)
|
23
23
|
# URI value of the descriptive element.
|
24
|
-
attribute :uri, Types::Strict::String
|
24
|
+
attribute? :uri, Types::Strict::String
|
25
25
|
# Value of the descriptive element.
|
26
|
-
attribute :value, Types::Strict::String
|
26
|
+
attribute? :value, Types::Strict::String
|
27
27
|
# URL or other pointer to the location of the language information.
|
28
|
-
attribute :valueAt, Types::Strict::String
|
29
|
-
attribute :valueLanguage, DescriptiveValueLanguage.optional
|
28
|
+
attribute? :valueAt, Types::Strict::String
|
29
|
+
attribute? :valueLanguage, DescriptiveValueLanguage.optional
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -9,7 +9,7 @@ module Cocina
|
|
9
9
|
attribute :download, Types::Strict::String.enum('location-based', 'none')
|
10
10
|
# If access or download is "location-based", which location should have access.
|
11
11
|
attribute :location, Types::Strict::String.enum('spec', 'music', 'ars', 'art', 'hoover', 'm&m')
|
12
|
-
attribute :controlledDigitalLending, Types::Strict::Bool.
|
12
|
+
attribute? :controlledDigitalLending, Types::Strict::Bool.default(false).enum(false)
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -9,7 +9,7 @@ module Cocina
|
|
9
9
|
attribute :download, Types::Strict::String.enum('location-based')
|
10
10
|
# Which location should have download access.
|
11
11
|
attribute :location, Types::Strict::String.enum('spec', 'music', 'ars', 'art', 'hoover', 'm&m')
|
12
|
-
attribute :controlledDigitalLending, Types::Strict::Bool.
|
12
|
+
attribute? :controlledDigitalLending, Types::Strict::Bool.default(false).enum(false)
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocina
|
4
|
+
module Models
|
5
|
+
module Mapping
|
6
|
+
# Notifies when a data error is encountered. Notification is performed with Honeybadger.
|
7
|
+
class ErrorNotifier
|
8
|
+
# In addition, it distinguishes between warnings and errors, even if both are notified the same.
|
9
|
+
# The determination of warn / error is currently made by the metadata team.
|
10
|
+
|
11
|
+
# @param [String] druid
|
12
|
+
def initialize(druid:)
|
13
|
+
@druid = druid
|
14
|
+
end
|
15
|
+
|
16
|
+
# Notify for a non-critical data error.
|
17
|
+
# @param [String] message
|
18
|
+
# @param [Hash<String, String>] context to add to warning context
|
19
|
+
def warn(message, _context = {})
|
20
|
+
Kernel.warn "[WARN] #{message}"
|
21
|
+
end
|
22
|
+
|
23
|
+
# Notify for a critical data error.
|
24
|
+
# @param [String] message
|
25
|
+
# @param [Hash<String, String>] context to add to error context
|
26
|
+
def error(message, _context = {})
|
27
|
+
Kernel.warn "[ERROR] #{message}"
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :druid
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocina
|
4
|
+
module Models
|
5
|
+
module Mapping
|
6
|
+
module FromMods
|
7
|
+
# Maps access conditions
|
8
|
+
class Access # rubocop:disable Metrics/ClassLength
|
9
|
+
ACCESS_CONDITION_TYPES = {
|
10
|
+
'restriction on access' => 'access restriction',
|
11
|
+
'restrictionOnAccess' => 'access restriction',
|
12
|
+
'restrictionsOnAccess' => 'access restriction',
|
13
|
+
'useAndReproduction' => 'use and reproduction'
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
# @param [Nokogiri::XML::Element] resource_element mods or relatedItem element
|
17
|
+
# @param [Cocina::Models::Mapping::FromMods::DescriptionBuilder] description_builder
|
18
|
+
# @param [String] purl
|
19
|
+
# @return [Hash] a hash that can be mapped to a cocina model
|
20
|
+
def self.build(resource_element:, description_builder:, purl: nil)
|
21
|
+
new(resource_element: resource_element, description_builder: description_builder, purl: purl).build
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(resource_element:, description_builder:, purl:)
|
25
|
+
@resource_element = resource_element
|
26
|
+
@notifier = description_builder.notifier
|
27
|
+
@purl = purl
|
28
|
+
end
|
29
|
+
|
30
|
+
def build
|
31
|
+
{}.tap do |access|
|
32
|
+
physical_locations = physical_location + shelf_location + xlink_location
|
33
|
+
access[:physicalLocation] = physical_locations.presence
|
34
|
+
access[:digitalLocation] = digital_location.presence
|
35
|
+
access[:accessContact] = access_contact.presence
|
36
|
+
access[:url] = url.presence
|
37
|
+
access[:note] = (note + purl_note).presence
|
38
|
+
end.compact.presence
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
attr_reader :resource_element, :notifier, :add_sdr, :purl
|
44
|
+
|
45
|
+
# Hydrus is known to create location nodes with no children.
|
46
|
+
def location_nodes
|
47
|
+
resource_element.xpath('mods:location[*]', mods: Description::DESC_METADATA_NS)
|
48
|
+
end
|
49
|
+
|
50
|
+
def physical_location
|
51
|
+
descriptive_value_for(resource_element.xpath(
|
52
|
+
"mods:location/mods:physicalLocation[not(@type='repository')][not(@type='discovery')]", mods: Description::DESC_METADATA_NS
|
53
|
+
))
|
54
|
+
end
|
55
|
+
|
56
|
+
def digital_location
|
57
|
+
descriptive_value_for(resource_element.xpath(
|
58
|
+
"mods:location/mods:physicalLocation[(@type='discovery')]", mods: Description::DESC_METADATA_NS
|
59
|
+
))
|
60
|
+
end
|
61
|
+
|
62
|
+
def xlink_location
|
63
|
+
resource_element.xpath('mods:location/mods:physicalLocation[@xlink:href]', mods: Description::DESC_METADATA_NS,
|
64
|
+
xlink: Description::XLINK_NS).map do |node|
|
65
|
+
{
|
66
|
+
valueAt: node['xlink:href']
|
67
|
+
}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def access_contact
|
72
|
+
descriptive_value_for(resource_element.xpath("mods:location/mods:physicalLocation[@type='repository']",
|
73
|
+
mods: Description::DESC_METADATA_NS)) +
|
74
|
+
descriptive_value_for(resource_element.xpath("mods:note[@type='contact']", mods: Description::DESC_METADATA_NS),
|
75
|
+
type: 'email')
|
76
|
+
end
|
77
|
+
|
78
|
+
def shelf_location
|
79
|
+
resource_element.xpath('mods:location/mods:shelfLocator',
|
80
|
+
mods: Description::DESC_METADATA_NS).filter_map do |shelf_locator_elem|
|
81
|
+
next if shelf_locator_elem.content.blank?
|
82
|
+
|
83
|
+
{
|
84
|
+
value: shelf_locator_elem.content,
|
85
|
+
type: 'shelf locator'
|
86
|
+
}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def url
|
91
|
+
url_nodes.filter_map do |url_node|
|
92
|
+
{
|
93
|
+
value: url_node.text.presence,
|
94
|
+
displayLabel: url_node[:displayLabel]
|
95
|
+
}.tap do |attrs|
|
96
|
+
attrs[:status] = 'primary' if url_node == primary_url_node
|
97
|
+
attrs[:note] = [{ value: url_node[:note] }] if url_node[:note]
|
98
|
+
end.compact.presence
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def primary_url_node
|
103
|
+
all_primary_purl_nodes.first || all_primary_url_nodes.first || this_purl_node || all_purl_nodes.first
|
104
|
+
end
|
105
|
+
|
106
|
+
def this_purl_node
|
107
|
+
purl ? all_purl_nodes.find { |purl_node| purl_node.content == purl } : nil
|
108
|
+
end
|
109
|
+
|
110
|
+
def all_primary_url_nodes
|
111
|
+
@all_primary_url_nodes ||= all_url_nodes.select { |url_node| url_node[:usage] == 'primary display' }
|
112
|
+
end
|
113
|
+
|
114
|
+
def all_primary_purl_nodes
|
115
|
+
@all_primary_purl_nodes ||= all_purl_nodes.select { |purl_node| purl_node[:usage] == 'primary display' }
|
116
|
+
end
|
117
|
+
|
118
|
+
def all_purl_nodes
|
119
|
+
@all_purl_nodes ||= all_url_nodes.select { |url_node| Cocina::Models::Mapping::Purl.purl?(url_node.text) }
|
120
|
+
end
|
121
|
+
|
122
|
+
def all_url_nodes
|
123
|
+
@all_url_nodes ||= resource_element.xpath('mods:location/mods:url', mods: Description::DESC_METADATA_NS)
|
124
|
+
end
|
125
|
+
|
126
|
+
def primary_purl_node
|
127
|
+
@primary_purl_node ||= Purl.primary_purl_node(resource_element, purl)
|
128
|
+
end
|
129
|
+
|
130
|
+
def url_nodes
|
131
|
+
@url_nodes ||= all_url_nodes.reject { |url_node| Cocina::Models::Mapping::Purl.purl?(url_node.text) }
|
132
|
+
end
|
133
|
+
|
134
|
+
def purl_note
|
135
|
+
return [] unless primary_purl_node
|
136
|
+
|
137
|
+
Purl.purl_note(primary_purl_node)
|
138
|
+
end
|
139
|
+
|
140
|
+
def note
|
141
|
+
resource_element.xpath('mods:accessCondition', mods: Description::DESC_METADATA_NS).map do |access_elem|
|
142
|
+
{
|
143
|
+
value: access_elem.text.presence,
|
144
|
+
type: ACCESS_CONDITION_TYPES.fetch(access_elem['type'], access_elem['type']),
|
145
|
+
displayLabel: access_elem['displayLabel'],
|
146
|
+
valueAt: access_elem['xlink:href']
|
147
|
+
}.compact
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def descriptive_value_for(nodes, type: nil)
|
152
|
+
nodes.filter_map do |node|
|
153
|
+
next nil if node.text.blank?
|
154
|
+
|
155
|
+
{}.tap do |attrs|
|
156
|
+
if %w[marcorg oclcorg].include?(node[:authority])
|
157
|
+
attrs[:code] = node.text
|
158
|
+
else
|
159
|
+
attrs[:value] = node.text
|
160
|
+
end
|
161
|
+
attrs[:uri] = ValueURI.sniff(node[:valueURI], notifier)
|
162
|
+
source = {
|
163
|
+
code: Authority.normalize_code(node[:authority], notifier),
|
164
|
+
uri: Authority.normalize_uri(node[:authorityURI])
|
165
|
+
}.compact
|
166
|
+
attrs[:source] = source unless source.empty?
|
167
|
+
attrs[:type] = type || node[:type]
|
168
|
+
attrs[:displayLabel] = node[:displayLabel]
|
169
|
+
attrs[:valueLanguage] = LanguageScript.build(node: node)
|
170
|
+
end.compact
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocina
|
4
|
+
module Models
|
5
|
+
module Mapping
|
6
|
+
module FromMods
|
7
|
+
# Maps MODS recordInfo to cocina
|
8
|
+
class AdminMetadata # rubocop:disable Metrics/ClassLength
|
9
|
+
# @param [Nokogiri::XML::Element] resource_element mods or relatedItem element
|
10
|
+
# @param [Cocina::Models::Mapping::FromMods::DescriptionBuilder] description_builder
|
11
|
+
# @param [String] purl
|
12
|
+
# @return [Hash] a hash that can be mapped to a cocina model
|
13
|
+
def self.build(resource_element:, description_builder:, purl: nil)
|
14
|
+
new(resource_element: resource_element, description_builder: description_builder).build
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(resource_element:, description_builder:)
|
18
|
+
@resource_element = resource_element
|
19
|
+
@description_builder = description_builder
|
20
|
+
@notifier = description_builder.notifier
|
21
|
+
end
|
22
|
+
|
23
|
+
def build
|
24
|
+
return nil if record_info.nil?
|
25
|
+
|
26
|
+
{}.tap do |admin_metadata|
|
27
|
+
admin_metadata[:language] = build_language
|
28
|
+
admin_metadata[:contributor] = build_contributor
|
29
|
+
admin_metadata[:metadataStandard] = build_standard
|
30
|
+
admin_metadata[:note] = build_note
|
31
|
+
admin_metadata[:identifier] = build_identifier
|
32
|
+
admin_metadata[:event] = build_events
|
33
|
+
end.compact
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :resource_element, :notifier, :description_builder
|
39
|
+
|
40
|
+
def build_events
|
41
|
+
events = []
|
42
|
+
events << build_event_for(creation_event, 'creation') if creation_event
|
43
|
+
modification_events.each do |event|
|
44
|
+
events << build_event_for(event, 'modification')
|
45
|
+
end
|
46
|
+
return nil if events.empty?
|
47
|
+
|
48
|
+
events
|
49
|
+
end
|
50
|
+
|
51
|
+
def build_event_for(node, type)
|
52
|
+
event_code = node['encoding']
|
53
|
+
encoding = { code: event_code } if event_code
|
54
|
+
{
|
55
|
+
type: type,
|
56
|
+
date: [
|
57
|
+
{
|
58
|
+
value: node.text,
|
59
|
+
encoding: encoding
|
60
|
+
}.compact
|
61
|
+
]
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def build_identifier
|
66
|
+
identifiers = record_identifiers.map do |identifier|
|
67
|
+
IdentifierBuilder.build_from_record_identifier(identifier_element: identifier)
|
68
|
+
end
|
69
|
+
|
70
|
+
return nil if identifiers.empty?
|
71
|
+
|
72
|
+
identifiers
|
73
|
+
end
|
74
|
+
|
75
|
+
def build_note
|
76
|
+
notes = []
|
77
|
+
record_origins.each do |record_origin|
|
78
|
+
notes << {
|
79
|
+
type: 'record origin',
|
80
|
+
value: record_origin.text
|
81
|
+
}
|
82
|
+
end
|
83
|
+
record_info_notes.each do |info_note|
|
84
|
+
notes << if info_note['xlink:href']
|
85
|
+
{ valueAt: info_note['xlink:href'] }
|
86
|
+
else
|
87
|
+
{
|
88
|
+
type: 'record information',
|
89
|
+
value: info_note.text
|
90
|
+
}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
notes.presence
|
94
|
+
end
|
95
|
+
|
96
|
+
def build_standard
|
97
|
+
return unless description_standards
|
98
|
+
|
99
|
+
description_standards.map do |description_standard|
|
100
|
+
source = {
|
101
|
+
uri: Authority.normalize_uri(description_standard['authorityURI']),
|
102
|
+
code: description_standard['authority']
|
103
|
+
}.compact
|
104
|
+
{
|
105
|
+
uri: ValueURI.sniff(description_standard['valueURI'], notifier),
|
106
|
+
source: source.presence
|
107
|
+
}.tap do |attrs|
|
108
|
+
if description_standard.text.present?
|
109
|
+
if description_standard.text == description_standard.text.downcase
|
110
|
+
attrs[:code] = description_standard.text
|
111
|
+
else
|
112
|
+
attrs[:value] = description_standard.text
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end.compact
|
116
|
+
end.presence
|
117
|
+
end
|
118
|
+
|
119
|
+
def build_contributor
|
120
|
+
record_content_sources.map do |record_content_source|
|
121
|
+
if record_content_source['authority'] == 'marcorg'
|
122
|
+
build_contributor_code(record_content_source)
|
123
|
+
else
|
124
|
+
build_contributor_value(record_content_source)
|
125
|
+
end
|
126
|
+
end.presence
|
127
|
+
end
|
128
|
+
|
129
|
+
def build_contributor_value(record_content_source)
|
130
|
+
{
|
131
|
+
name: [
|
132
|
+
{
|
133
|
+
value: record_content_source.text,
|
134
|
+
uri: ValueURI.sniff(record_content_source['valueURI'], notifier),
|
135
|
+
source: source_for(record_content_source)
|
136
|
+
|
137
|
+
}.compact
|
138
|
+
]
|
139
|
+
}
|
140
|
+
end
|
141
|
+
|
142
|
+
def build_contributor_code(record_content_source)
|
143
|
+
{
|
144
|
+
name: [
|
145
|
+
{
|
146
|
+
code: record_content_source.text,
|
147
|
+
uri: ValueURI.sniff(record_content_source['valueURI'], notifier),
|
148
|
+
source: source_for(record_content_source)
|
149
|
+
}.compact
|
150
|
+
],
|
151
|
+
type: 'organization',
|
152
|
+
role: [
|
153
|
+
{
|
154
|
+
value: 'original cataloging agency'
|
155
|
+
}
|
156
|
+
]
|
157
|
+
}
|
158
|
+
end
|
159
|
+
|
160
|
+
def source_for(record_content_source)
|
161
|
+
{
|
162
|
+
code: record_content_source['authority'],
|
163
|
+
uri: record_content_source['authorityURI']
|
164
|
+
}.compact.presence
|
165
|
+
end
|
166
|
+
|
167
|
+
def build_language
|
168
|
+
return if language_of_cataloging.empty?
|
169
|
+
|
170
|
+
language_of_cataloging.map do |lang_node|
|
171
|
+
Cocina::Models::Mapping::FromMods::LanguageTerm.build(
|
172
|
+
language_element: lang_node,
|
173
|
+
notifier: notifier
|
174
|
+
)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def record_info
|
179
|
+
@record_info ||= resource_element.xpath('mods:recordInfo[1]', mods: Description::DESC_METADATA_NS).first
|
180
|
+
end
|
181
|
+
|
182
|
+
def language_of_cataloging
|
183
|
+
@language_of_cataloging ||= record_info.xpath('mods:languageOfCataloging', mods: Description::DESC_METADATA_NS)
|
184
|
+
end
|
185
|
+
|
186
|
+
def record_content_sources
|
187
|
+
@record_content_sources ||= record_info.xpath('mods:recordContentSource', mods: Description::DESC_METADATA_NS)
|
188
|
+
end
|
189
|
+
|
190
|
+
def description_standards
|
191
|
+
@description_standards ||= record_info.xpath('mods:descriptionStandard', mods: Description::DESC_METADATA_NS)
|
192
|
+
end
|
193
|
+
|
194
|
+
def record_origins
|
195
|
+
@record_origins ||= record_info.xpath('mods:recordOrigin', mods: Description::DESC_METADATA_NS)
|
196
|
+
end
|
197
|
+
|
198
|
+
def record_info_notes
|
199
|
+
@record_info_notes ||= record_info.xpath('mods:recordInfoNote', mods: Description::DESC_METADATA_NS)
|
200
|
+
end
|
201
|
+
|
202
|
+
def creation_event
|
203
|
+
@creation_event ||= record_info.xpath('mods:recordCreationDate', mods: Description::DESC_METADATA_NS).first
|
204
|
+
end
|
205
|
+
|
206
|
+
def modification_events
|
207
|
+
@modification_events ||= record_info.xpath('mods:recordChangeDate', mods: Description::DESC_METADATA_NS)
|
208
|
+
end
|
209
|
+
|
210
|
+
def record_identifiers
|
211
|
+
@record_identifiers ||= record_info.xpath('mods:recordIdentifier', mods: Description::DESC_METADATA_NS)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocina
|
4
|
+
module Models
|
5
|
+
module Mapping
|
6
|
+
module FromMods
|
7
|
+
# Helper class: splits nodes by altRepGroup ids
|
8
|
+
class AltRepGroup
|
9
|
+
# @param [Array<Nokogiri::XML::Element>] nodes to split
|
10
|
+
# @return [Array<Array<Nokogiri::XML::Element>>, Array<Nokogiri::XML::Element>] nodes grouped by altRepGroup, other nodes
|
11
|
+
def self.split(nodes:)
|
12
|
+
all_nodes_with_altrepgroup = nodes.reject { |node| node[:altRepGroup].blank? }
|
13
|
+
grouped_altrepgroup_nodes = all_nodes_with_altrepgroup
|
14
|
+
.group_by { |node| node[:altRepGroup] }
|
15
|
+
.values
|
16
|
+
.reject { |group_nodes| group_nodes.size == 1 }
|
17
|
+
|
18
|
+
other_nodes = nodes.reject { |node| grouped_altrepgroup_nodes.flatten.include?(node) }
|
19
|
+
|
20
|
+
[grouped_altrepgroup_nodes, other_nodes]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocina
|
4
|
+
module Models
|
5
|
+
module Mapping
|
6
|
+
module FromMods
|
7
|
+
# Helper class: normalizes Authorities
|
8
|
+
class Authority
|
9
|
+
NORMALIZE_AUTHORITY_URIS = [
|
10
|
+
'http://id.loc.gov/authorities/names',
|
11
|
+
'http://id.loc.gov/authorities/subjects',
|
12
|
+
'http://id.loc.gov/vocabulary/relators',
|
13
|
+
'http://id.loc.gov/vocabulary/countries',
|
14
|
+
'http://id.loc.gov/authorities/genreForms',
|
15
|
+
'http://id.loc.gov/vocabulary/descriptionConventions'
|
16
|
+
].freeze
|
17
|
+
|
18
|
+
def self.normalize_uri(uri)
|
19
|
+
return "#{uri}/" if NORMALIZE_AUTHORITY_URIS.include?(uri)
|
20
|
+
|
21
|
+
uri.presence
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.normalize_code(code, notifier)
|
25
|
+
if code == 'lcnaf'
|
26
|
+
notifier.warn('lcnaf authority code')
|
27
|
+
return 'naf'
|
28
|
+
end
|
29
|
+
|
30
|
+
if code == 'tgm'
|
31
|
+
notifier.warn('tgm authority code (should be lctgm)')
|
32
|
+
return 'lctgm'
|
33
|
+
end
|
34
|
+
|
35
|
+
if code == '#N/A'
|
36
|
+
notifier.warn('"#N/A" authority code')
|
37
|
+
return nil
|
38
|
+
end
|
39
|
+
|
40
|
+
if code == 'marcountry'
|
41
|
+
notifier.warn('marcountry authority code (should be marccountry)')
|
42
|
+
return 'marccountry'
|
43
|
+
end
|
44
|
+
|
45
|
+
code.presence
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|