cocina-models 0.75.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 -12
- data/.rubocop_todo.yml +71 -2
- data/README.md +10 -3
- data/cocina-models.gemspec +2 -0
- data/description_types.yml +165 -38
- data/docs/description_types.md +469 -216
- data/lib/cocina/generator/generator.rb +7 -12
- 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/name_title_group_builder.rb +0 -4
- data/lib/cocina/models/builders/title_builder.rb +0 -2
- 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/dark_validator.rb +4 -2
- data/lib/cocina/models/validators/open_api_validator.rb +0 -4
- 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 +88 -3
- data/docs/_config.yml +0 -1
@@ -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
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocina
|
4
|
+
module Models
|
5
|
+
module Mapping
|
6
|
+
module FromMods
|
7
|
+
# Maps contributors
|
8
|
+
class Contributor # rubocop:disable Metrics/ClassLength
|
9
|
+
# key: MODS, value: cocina
|
10
|
+
ROLES = {
|
11
|
+
'personal' => 'person',
|
12
|
+
'corporate' => 'organization',
|
13
|
+
'family' => 'family',
|
14
|
+
'conference' => 'conference'
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
NAME_PART = {
|
18
|
+
'family' => 'surname',
|
19
|
+
'given' => 'forename',
|
20
|
+
'termsOfAddress' => 'term of address',
|
21
|
+
'date' => 'life dates'
|
22
|
+
}.freeze
|
23
|
+
|
24
|
+
# @param [Nokogiri::XML::Element] resource_element mods or relatedItem element
|
25
|
+
# @param [Cocina::Models::Mapping::FromMods::DescriptionBuilder] description_builder
|
26
|
+
# @param [String] purl
|
27
|
+
# @return [Hash] a hash that can be mapped to a cocina model
|
28
|
+
def self.build(resource_element:, description_builder:, purl: nil)
|
29
|
+
new(resource_element: resource_element, description_builder: description_builder).build
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(resource_element:, description_builder:)
|
33
|
+
@resource_element = resource_element
|
34
|
+
@notifier = description_builder.notifier
|
35
|
+
end
|
36
|
+
|
37
|
+
def build
|
38
|
+
grouped_altrepgroup_name_nodes, other_name_nodes = AltRepGroup.split(nodes: deduped_name_nodes)
|
39
|
+
check_altrepgroup_type_inconsistency(grouped_altrepgroup_name_nodes)
|
40
|
+
contributors = grouped_altrepgroup_name_nodes.map { |name_nodes| build_name_nodes(name_nodes) } + \
|
41
|
+
other_name_nodes.map { |name_node| build_name_nodes([name_node]) }
|
42
|
+
contrib_level_type_and_status(contributors)
|
43
|
+
adjust_primary(contributors.compact).presence
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
attr_reader :resource_element, :notifier
|
49
|
+
|
50
|
+
def deduped_name_nodes
|
51
|
+
# In addition, to plain-old dupes, need to get rid of names that are dupes where
|
52
|
+
# e.g., one has a nameTitleGroup and one does not
|
53
|
+
# Need to retain nameTitleGroups, so sorting so those first. (Array.uniq takes first.)
|
54
|
+
name_nodes = resource_element.xpath('mods:name', mods: Description::DESC_METADATA_NS)
|
55
|
+
nametitle_nodes, other_nodes = name_nodes.partition { |name_node| name_node['nameTitleGroup'] }
|
56
|
+
ordered_name_nodes = nametitle_nodes + other_nodes
|
57
|
+
uniq_name_nodes = uniq_name_nodes(ordered_name_nodes)
|
58
|
+
|
59
|
+
notifier.warn('Duplicate name entry') if name_nodes.size != uniq_name_nodes.size
|
60
|
+
|
61
|
+
include_all_uniq_roles(uniq_name_nodes)
|
62
|
+
end
|
63
|
+
|
64
|
+
# uniq retains the first value, so sort input with that in mind.
|
65
|
+
# @param [Array<Nokogiri::XML::Node>]
|
66
|
+
# @return [Array<Nokogiri::XML::Node>] (yes, Johnny, this returns array of nodes, not comparitors)
|
67
|
+
def uniq_name_nodes(name_nodes)
|
68
|
+
name_nodes.uniq do |name_node|
|
69
|
+
name_node_comparitor(name_node)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# remove usage and nameTitleGroup attributes and role nodes for uniqueness comparison
|
74
|
+
# @return [String] a string to be used by Array.uniq for .eql? comparisons
|
75
|
+
def name_node_comparitor(name_node)
|
76
|
+
dup_name_node = name_node.dup
|
77
|
+
dup_name_node.delete('usage')
|
78
|
+
dup_name_node.delete('nameTitleGroup')
|
79
|
+
dup_name_node.xpath('mods:role', mods: Description::DESC_METADATA_NS).each(&:unlink)
|
80
|
+
dup_name_node.to_s.strip.gsub(/\s+/, ' ')
|
81
|
+
end
|
82
|
+
|
83
|
+
# ensure all roles for each uniq name node are present
|
84
|
+
# @return [Array<Nokogiri::XML::Node] the uniq name nodes with all roles present
|
85
|
+
def include_all_uniq_roles(uniq_name_nodes)
|
86
|
+
names_to_roles = name_comparitor_2_role_nodes # compute this once
|
87
|
+
uniq_name_nodes.each do |uniq_name_node|
|
88
|
+
role_nodes = names_to_roles[name_node_comparitor(uniq_name_node)]
|
89
|
+
next if role_nodes.blank?
|
90
|
+
|
91
|
+
uniq_name_node.xpath('mods:role', mods: Description::DESC_METADATA_NS).each(&:unlink)
|
92
|
+
role_nodes.each { |role_node| uniq_name_node.add_child(role_node) }
|
93
|
+
end
|
94
|
+
uniq_name_nodes
|
95
|
+
end
|
96
|
+
|
97
|
+
# @return [Hash<String, Array[Nokogiri::XML::Node]] key is the string comparitor for a name node;
|
98
|
+
# value is an Array of uniq role nodes
|
99
|
+
def name_comparitor_2_role_nodes
|
100
|
+
result = {}
|
101
|
+
|
102
|
+
# we must do this outside the loop in case of duplicate name nodes
|
103
|
+
all_role_nodes = resource_element.xpath('mods:name/mods:role', mods: Description::DESC_METADATA_NS)
|
104
|
+
all_role_nodes.each do |role_node|
|
105
|
+
name_comparitor = name_node_comparitor(role_node.parent)
|
106
|
+
result[name_comparitor] = if result[name_comparitor]
|
107
|
+
result[name_comparitor] << role_node
|
108
|
+
else
|
109
|
+
[role_node]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
result.each { |_k, role_nodes| role_nodes.uniq! { |role_node| name_node_comparitor(role_node) } }
|
114
|
+
end
|
115
|
+
|
116
|
+
def check_altrepgroup_type_inconsistency(grouped_altrepgroup_name_nodes)
|
117
|
+
grouped_altrepgroup_name_nodes.each do |altrepgroup_name_nodes|
|
118
|
+
altrepgroup_name_types = altrepgroup_name_nodes.group_by { |name_node| name_node['type'] }.keys
|
119
|
+
next unless altrepgroup_name_types.size > 1
|
120
|
+
|
121
|
+
notifier.error('Multiple types for same altRepGroup', { types: altrepgroup_name_types })
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def build_name_nodes(name_nodes)
|
126
|
+
NameBuilder.build(name_elements: name_nodes, notifier: notifier).presence
|
127
|
+
end
|
128
|
+
|
129
|
+
def adjust_primary(contributors)
|
130
|
+
Primary.adjust(contributors, 'contributor', notifier)
|
131
|
+
contributors.each do |contributor|
|
132
|
+
Array(contributor[:name]).each do |name|
|
133
|
+
Primary.adjust(name[:parallelValue], 'name', notifier) if name[:parallelValue]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
contributors
|
137
|
+
end
|
138
|
+
|
139
|
+
# 'type' and 'status' are generated in name_builder on the name level object,
|
140
|
+
# but we want them at the contributor level object.
|
141
|
+
def contrib_level_type_and_status(contributors)
|
142
|
+
contributors.each do |contributor|
|
143
|
+
next if contributor.blank?
|
144
|
+
|
145
|
+
Array(contributor[:name]).each do |name|
|
146
|
+
if name[:status] == 'primary'
|
147
|
+
contributor[:status] = 'primary'
|
148
|
+
name.delete(:status)
|
149
|
+
end
|
150
|
+
if name[:type].present? && ROLES.value?(name[:type])
|
151
|
+
contributor[:type] = name[:type].presence
|
152
|
+
name.delete(:type)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocina
|
4
|
+
module Models
|
5
|
+
module Mapping
|
6
|
+
module FromMods
|
7
|
+
# Creates Cocina Description objects from MODS xml
|
8
|
+
class Description
|
9
|
+
DESC_METADATA_NS = 'http://www.loc.gov/mods/v3'
|
10
|
+
XLINK_NS = 'http://www.w3.org/1999/xlink'
|
11
|
+
|
12
|
+
# @param [Nokogiri::XML] mods
|
13
|
+
# @param [String] druid
|
14
|
+
# @oaram [String] label
|
15
|
+
# @param [TitleBuilder] title_builder - defaults to Title class
|
16
|
+
# @param [Cocina::Models::Mapping::ErrorNotifier] notifier
|
17
|
+
# @return [Hash] a hash that can be mapped to a cocina descriptive model
|
18
|
+
# @raises [Cocina::Mapper::InvalidDescMetadata] if some assumption about descMetadata is violated
|
19
|
+
def self.props(mods:, druid:, label:, title_builder: Title, notifier: nil)
|
20
|
+
new(title_builder: title_builder, mods: mods, druid: druid, label: label, notifier: notifier).props
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(title_builder:, mods:, label:, druid:, notifier:)
|
24
|
+
@title_builder = title_builder
|
25
|
+
@ng_xml = mods
|
26
|
+
@notifier = notifier || ErrorNotifier.new(druid: druid)
|
27
|
+
@druid = druid
|
28
|
+
@label = label
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Hash] a hash that can be mapped to a cocina descriptive model
|
32
|
+
# @raises [Cocina::Mapper::InvalidDescMetadata] if some assumption about descMetadata is violated
|
33
|
+
def props
|
34
|
+
return nil if ng_xml.root.nil?
|
35
|
+
|
36
|
+
check_altrepgroups
|
37
|
+
check_version
|
38
|
+
props = DescriptionBuilder.build(title_builder: title_builder,
|
39
|
+
resource_element: ng_xml.root,
|
40
|
+
notifier: notifier,
|
41
|
+
purl: druid ? Cocina::Models::Mapping::Purl.for(druid: druid) : nil)
|
42
|
+
props[:title] = [{ value: label }] unless props.key?(:title)
|
43
|
+
props
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
attr_reader :title_builder, :ng_xml, :notifier, :druid, :label
|
49
|
+
|
50
|
+
def check_altrepgroups
|
51
|
+
ng_xml.xpath('//mods:*[@altRepGroup]', mods: DESC_METADATA_NS)
|
52
|
+
.group_by { |node| node['altRepGroup'] }
|
53
|
+
.values
|
54
|
+
.select { |nodes| nodes.size > 1 }
|
55
|
+
.each do |nodes|
|
56
|
+
notifier.warn('Unpaired altRepGroup') if altrepgroup_error?(nodes)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
61
|
+
def altrepgroup_error?(nodes)
|
62
|
+
return true if nodes.map(&:name).uniq.size != 1
|
63
|
+
|
64
|
+
# For subjects, script/lang may be in child so looking in both locations.
|
65
|
+
scripts = nodes.map do |node|
|
66
|
+
node['script'].presence || node.elements.first&.attribute('script')&.presence
|
67
|
+
end.uniq
|
68
|
+
# Every node has a different script.
|
69
|
+
return false if scripts.size == nodes.size
|
70
|
+
|
71
|
+
langs = nodes.map do |node|
|
72
|
+
node['lang'].presence || node.elements.first&.attribute('lang')&.presence
|
73
|
+
end.uniq
|
74
|
+
# Every node has a different lang.
|
75
|
+
return false if langs.size == nodes.size
|
76
|
+
|
77
|
+
# No scripts or langs
|
78
|
+
return false if scripts.compact.empty? && langs.compact.empty?
|
79
|
+
|
80
|
+
# altRepGroups can have the same script, e.g. Latn for English and French
|
81
|
+
return false if scripts.size == 1
|
82
|
+
|
83
|
+
true
|
84
|
+
end
|
85
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
86
|
+
|
87
|
+
def check_version
|
88
|
+
match = /MODS version (\d\.\d)/.match(ng_xml.root.at('//mods:recordInfo/mods:recordOrigin',
|
89
|
+
mods: DESC_METADATA_NS)&.content)
|
90
|
+
|
91
|
+
return unless match
|
92
|
+
|
93
|
+
notifier.warn('MODS version mismatch') if match[1] != ng_xml.root['version']
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocina
|
4
|
+
module Models
|
5
|
+
module Mapping
|
6
|
+
module FromMods
|
7
|
+
# Creates Cocina Description objects from MODS resource element.
|
8
|
+
class DescriptionBuilder
|
9
|
+
attr_reader :notifier
|
10
|
+
|
11
|
+
BUILDERS = {
|
12
|
+
note: Note,
|
13
|
+
language: Language,
|
14
|
+
contributor: Contributor,
|
15
|
+
event: Event,
|
16
|
+
subject: Subject,
|
17
|
+
form: Form,
|
18
|
+
identifier: Identifier,
|
19
|
+
adminMetadata: AdminMetadata,
|
20
|
+
relatedResource: RelatedResource,
|
21
|
+
geographic: Geographic,
|
22
|
+
access: Access
|
23
|
+
}.freeze
|
24
|
+
|
25
|
+
# @param [Nokogiri::XML::Element] resource_element mods or relatedItem element
|
26
|
+
# @param [Cocina::Models::Mapping::ErrorNotifier] notifier
|
27
|
+
# @param [TitleBuilder] title_builder - defaults to Title class
|
28
|
+
# @param [String] purl
|
29
|
+
# @return [Hash] a hash that can be mapped to a cocina description model
|
30
|
+
def self.build(resource_element:, notifier:, title_builder: Title, purl: nil)
|
31
|
+
new(title_builder: title_builder, notifier: notifier).build(resource_element: resource_element,
|
32
|
+
purl: purl)
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(notifier:, title_builder: Title)
|
36
|
+
@title_builder = title_builder
|
37
|
+
@notifier = notifier
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Hash] a hash that can be mapped to a cocina description model
|
41
|
+
def build(resource_element:, purl: nil, require_title: true)
|
42
|
+
cocina_description = {}
|
43
|
+
title_result = @title_builder.build(resource_element: resource_element, require_title: require_title,
|
44
|
+
notifier: notifier)
|
45
|
+
cocina_description[:title] = title_result if title_result.present?
|
46
|
+
|
47
|
+
purl_value = purl || Purl.primary_purl_value(resource_element, purl)
|
48
|
+
cocina_description[:purl] = purl_value if purl_value
|
49
|
+
|
50
|
+
BUILDERS.each do |description_property, builder|
|
51
|
+
result = builder.build(resource_element: resource_element, description_builder: self,
|
52
|
+
purl: purl_value)
|
53
|
+
cocina_description.merge!(description_property => result) if result.present?
|
54
|
+
end
|
55
|
+
cocina_description
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|