mods 2.4.0 → 3.0.0

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.
Files changed (45) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ruby.yml +24 -0
  3. data/.gitignore +1 -0
  4. data/Gemfile +0 -4
  5. data/README.md +1 -3
  6. data/lib/mods/date.rb +51 -17
  7. data/lib/mods/marc_country_codes.rb +12 -10
  8. data/lib/mods/nom_terminology.rb +110 -849
  9. data/lib/mods/reader.rb +9 -39
  10. data/lib/mods/record.rb +13 -28
  11. data/lib/mods/version.rb +1 -1
  12. data/mods.gemspec +3 -3
  13. data/spec/fixture_data/hp566jq8781.xml +334 -0
  14. data/spec/integration/parker_spec.rb +217 -0
  15. data/spec/{date_spec.rb → lib/date_spec.rb} +8 -1
  16. data/spec/lib/language_spec.rb +123 -0
  17. data/spec/lib/location_spec.rb +175 -0
  18. data/spec/lib/name_spec.rb +368 -0
  19. data/spec/lib/origin_info_spec.rb +134 -0
  20. data/spec/lib/part_spec.rb +162 -0
  21. data/spec/lib/physical_description_spec.rb +72 -0
  22. data/spec/{reader_spec.rb → lib/reader_spec.rb} +1 -41
  23. data/spec/lib/record_info_spec.rb +114 -0
  24. data/spec/lib/record_spec.rb +287 -0
  25. data/spec/lib/related_item_spec.rb +124 -0
  26. data/spec/lib/subject_spec.rb +427 -0
  27. data/spec/lib/title_spec.rb +108 -0
  28. data/spec/lib/top_level_elmnts_simple_spec.rb +169 -0
  29. data/spec/spec_helper.rb +87 -6
  30. data/spec/support/fixtures.rb +9 -0
  31. metadata +61 -43
  32. data/.coveralls.yml +0 -1
  33. data/.travis.yml +0 -6
  34. data/spec/language_spec.rb +0 -118
  35. data/spec/location_spec.rb +0 -295
  36. data/spec/name_spec.rb +0 -759
  37. data/spec/origin_info_spec.rb +0 -447
  38. data/spec/part_spec.rb +0 -471
  39. data/spec/physical_description_spec.rb +0 -144
  40. data/spec/record_info_spec.rb +0 -493
  41. data/spec/record_spec.rb +0 -356
  42. data/spec/related_item_spec.rb +0 -305
  43. data/spec/subject_spec.rb +0 -809
  44. data/spec/title_spec.rb +0 -226
  45. data/spec/top_level_elmnts_simple_spec.rb +0 -369
@@ -0,0 +1,217 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe 'Parker manuscript' do
4
+ let(:record) do
5
+ Mods::Record.new.from_file(File.expand_path('../fixture_data/hp566jq8781.xml', __dir__))
6
+ end
7
+
8
+ it 'has title data' do
9
+ expect(record.title_info).to match_array(
10
+ [
11
+ have_attributes(
12
+ authority: 'Corpus Christi College',
13
+ title: have_attributes(text: 'Cambridge, Corpus Christi College, MS 367: Miscellaneous Compilation including Old English Material and Historical and Philosophical Works')
14
+ ),
15
+ have_attributes(
16
+ authority: 'James Catalog',
17
+ type_at: 'alternative',
18
+ title: have_attributes(text: 'Chronica. Anglo-Saxon fragments, etc.')
19
+ )
20
+ ]
21
+ )
22
+
23
+ expect(record.title_info.full_title).to match_array([
24
+ "Cambridge, Corpus Christi College, MS 367: Miscellaneous Compilation including Old English Material and Historical and Philosophical Works",
25
+ "Chronica. Anglo-Saxon fragments, etc."
26
+ ])
27
+
28
+ expect(record.title_info.short_title).to eq [
29
+ "Cambridge, Corpus Christi College, MS 367: Miscellaneous Compilation including Old English Material and Historical and Philosophical Works"
30
+ ]
31
+
32
+ expect(record.title_info.sort_title).to eq [
33
+ "Cambridge, Corpus Christi College, MS 367: Miscellaneous Compilation including Old English Material and Historical and Philosophical Works"
34
+ ]
35
+
36
+ expect(record.title_info.alternative_title).to eq [
37
+ "Chronica. Anglo-Saxon fragments, etc."
38
+ ]
39
+ end
40
+
41
+
42
+ it 'has typeOfResource data' do
43
+ expect(record.typeOfResource).to match_array([
44
+ have_attributes(
45
+ manuscript: 'yes',
46
+ text: 'mixed material'
47
+ )
48
+ ])
49
+ end
50
+
51
+ it 'has originInfo data' do
52
+ expect(record.origin_info.to_a).to include(
53
+ have_attributes(
54
+ dateCreated: match_array([
55
+ have_attributes(
56
+ encoding: 'w3cdtf',
57
+ keyDate: 'yes',
58
+ point: 'start',
59
+ qualifier: 'approximate',
60
+ text: '1400'
61
+ ),
62
+ have_attributes(
63
+ text: '1499'
64
+ )
65
+ ]),
66
+ ),
67
+ have_attributes(
68
+ dateCreated: match_array([
69
+ have_attributes(
70
+ text: '1300'
71
+ ),
72
+ have_attributes(
73
+ text: '1399'
74
+ )
75
+ ])
76
+ )
77
+ )
78
+ end
79
+
80
+ it 'has abstract data' do
81
+ expect(record.abstract).to match_array([
82
+ have_attributes(
83
+ displayLabel: 'Summary',
84
+ type_at: 'summary',
85
+ text: a_string_starting_with('CCCC MS 367')
86
+ )
87
+ ])
88
+ end
89
+
90
+ it 'has table of contents data' do
91
+ expect(record.tableOfContents).to match_array([
92
+ have_attributes(
93
+ displayLabel: 'Contents',
94
+ text: a_string_starting_with('Polychronicon (epitome and continuation to 1429)')
95
+ )
96
+ ])
97
+ end
98
+
99
+ it 'has notes' do
100
+ expect(record.note).to match_array([
101
+ have_attributes(
102
+ displayLabel: 'M.R. James Date',
103
+ type_at: 'date',
104
+ text: 'xv, xi-xii, xiv, xi'
105
+ )
106
+ ])
107
+ end
108
+
109
+ it 'has languages' do
110
+ expect(record.language.length).to eq 2
111
+ expect(record.language.code_term).to match_array([
112
+ have_attributes(
113
+ authority: 'iso639-2b',
114
+ text: 'lat',
115
+ type_at: 'code'
116
+ ),
117
+ have_attributes(
118
+ text: 'ang'
119
+ ),
120
+ ])
121
+ expect(record.language.text_term).to be_empty
122
+ end
123
+
124
+ it 'has a physical description' do
125
+ expect(record.physical_description.first).to have_attributes(
126
+ note: array_including(
127
+ have_attributes(
128
+ displayLabel: 'Material',
129
+ type_at: 'material',
130
+ text: 'Paper and vellum'
131
+ ),
132
+ have_attributes(
133
+ text: 'five volumes'
134
+ ),
135
+ have_attributes(
136
+ text: '220'
137
+ )
138
+ ),
139
+ extent: match_array([
140
+ have_attributes(text: 'ff. 53 + 52')
141
+ ])
142
+ )
143
+ end
144
+
145
+ it 'has identifiers' do
146
+ expect(record.identifier.to_a).to include(
147
+ have_attributes(
148
+ text: '342',
149
+ displayLabel: 'TJames'
150
+ ),
151
+ have_attributes(
152
+ text: '19. 9',
153
+ displayLabel: 'Stanley'
154
+ )
155
+ )
156
+ end
157
+
158
+ it 'has a location' do
159
+ expect(record.location.to_a).to include(
160
+ have_attributes(
161
+ physicalLocation: have_attributes(
162
+ text: 'UK, Cambridge, Corpus Christi College, Parker Library',
163
+ type_at: ['repository']
164
+ ),
165
+ shelfLocator: have_attributes(
166
+ text: 'MS 367'
167
+ )
168
+ )
169
+ )
170
+ end
171
+
172
+ it 'has related items' do
173
+ expect(record.related_item.to_a).to include(
174
+ have_attributes(
175
+ displayLabel: 'Downloadable James Catalogue Record',
176
+ type_at: 'isReferencedBy',
177
+ titleInfo: array_including(
178
+ have_attributes(
179
+ title: have_attributes(
180
+ text: a_string_starting_with('https://stacks.stanford.edu')
181
+ )
182
+ )
183
+ )
184
+ ),
185
+ have_attributes(
186
+ type_at: 'constituent',
187
+ titleInfo: array_including([
188
+ have_attributes(
189
+ title: [
190
+ have_attributes(text: 'Ranulf Higden OSB, Polychronicon (epitome and continuation to 1429)')
191
+ ],
192
+ partNumber: [
193
+ have_attributes(text: '1r-29v')
194
+ ]
195
+ )
196
+ ]),
197
+ note: array_including(
198
+ have_attributes(text: 'Dates are marked in the margin'),
199
+ have_attributes(type_at: 'incipit', text: a_string_starting_with('(1r) Ieronimus'))
200
+ )
201
+ )
202
+ )
203
+ end
204
+
205
+ it 'has access conditions' do
206
+ expect(record.accessCondition).to match_array([
207
+ have_attributes(
208
+ type_at: 'useAndReproduction',
209
+ text: a_string_starting_with('Images courtesy')
210
+ ),
211
+ have_attributes(
212
+ type_at: 'license',
213
+ text: a_string_starting_with('CC by-nc: Attribution')
214
+ )
215
+ ])
216
+ end
217
+ end
@@ -209,6 +209,9 @@ RSpec.describe Mods::Date do
209
209
  '1900-uu-uu' => Date.parse('1900-01-01')..Date.parse('1900-12-31'),
210
210
  '1900-uu-15' => Date.parse('1900-01-15')..Date.parse('1900-12-15'),
211
211
  '1900-06-uu' => Date.parse('1900-06-01')..Date.parse('1900-06-30'),
212
+ '-250' => Date.parse('-250-01-01')..Date.parse('-250-12-31'), # EDTF requires a 4 digit year, but what can you do.
213
+ '63' => Date.parse('0063-01-01')..Date.parse('0063-12-31'),
214
+ '125' => Date.parse('125-01-01')..Date.parse('125-12-31'),
212
215
  }.each do |data, expected|
213
216
  describe "with #{data}" do
214
217
  let(:date_element) { "<dateCreated encoding=\"edtf\">#{data}</dateCreated>" }
@@ -303,7 +306,8 @@ RSpec.describe Mods::Date do
303
306
  {
304
307
  'Minguo 19 [1930]' => Date.parse('1930-01-01')..Date.parse('1930-12-31'),
305
308
  '1745 mag. 14' => Date.parse('1745-01-01')..Date.parse('1745-12-31'),
306
- '-745' => Date.parse('-745-01-01')..Date.parse('-745-12-31'),
309
+ '-745' => '', # too ambiguious to even attempt.
310
+ '-1999' => '', # too ambiguious to even attempt.
307
311
  '[1923]' => Date.parse('1923-01-01')..Date.parse('1923-12-31'),
308
312
  '1532.' => Date.parse('1532-01-01')..Date.parse('1532-12-31'),
309
313
  '[ca 1834]' => Date.parse('1834-01-01')..Date.parse('1834-12-31'),
@@ -319,8 +323,11 @@ RSpec.describe Mods::Date do
319
323
  '193-' => Date.parse('1930-01-01')..Date.parse('1939-12-31'),
320
324
  '196_' => Date.parse('1960-01-01')..Date.parse('1969-12-31'),
321
325
  '196x' => Date.parse('1960-01-01')..Date.parse('1969-12-31'),
326
+ '196u' => Date.parse('1960-01-01')..Date.parse('1969-12-31'),
327
+ '1960s' => Date.parse('1960-01-01')..Date.parse('1969-12-31'),
322
328
  '186?' => Date.parse('1860-01-01')..Date.parse('1869-12-31'),
323
329
  '1700?' => Date.parse('1700-01-01')..Date.parse('1700-12-31'),
330
+ 'early 1730s' => Date.parse('1730-01-01')..Date.parse('1739-12-31'),
324
331
  '[1670-1684]' => Date.parse('1670-01-01')..Date.parse('1684-12-31'),
325
332
  '[18]74' => Date.parse('1874-01-01')..Date.parse('1874-12-31'),
326
333
  '250 B.C.' => Date.parse('-0249-01-01')..Date.parse('-249-12-31'),
@@ -0,0 +1,123 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe 'Mods <language> Element' do
4
+ context 'with a simple record one with language term' do
5
+ subject(:record) do
6
+ mods_record("<language><languageTerm authority='iso639-2b' type='code'>fre</languageTerm></language>")
7
+ end
8
+
9
+ describe '#languages' do
10
+ it 'translates the code into text' do
11
+ expect(record.languages).to eq(['French'])
12
+ end
13
+ end
14
+
15
+ describe '#language' do
16
+ describe '#languageTerm' do
17
+ it 'has attribute accessors' do
18
+ expect(record.language.languageTerm).to have_attributes(
19
+ type_at: ['code'],
20
+ authority: ['iso639-2b'],
21
+ text: 'fre',
22
+ size: 1
23
+ )
24
+ end
25
+ end
26
+
27
+ describe '#code_term' do
28
+ it 'gets the languageTerms with type="code"' do
29
+ expect(record.language.code_term.map(&:text)).to eq ['fre']
30
+ end
31
+ end
32
+
33
+ describe '#text_term' do
34
+ it 'filters out the languageTerms with type="code"' do
35
+ expect(record.language.text_term).to be_empty
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ context 'with a record with a mix of language codes and plain-text languages' do
42
+ subject(:record) do
43
+ mods_record(<<-XML)
44
+ <language>
45
+ <languageTerm authority='iso639-2b' type='code'>spa</languageTerm>
46
+ <languageTerm authority='iso639-2b' type='text'>Spanish</languageTerm>
47
+ <languageTerm authority='iso639-2b' type='code'>dut</languageTerm>
48
+ <languageTerm authority='iso639-2b' type='text'>Dutch</languageTerm>
49
+ </language>
50
+ XML
51
+ end
52
+
53
+ describe '#languages' do
54
+ it 'translates the code into text' do
55
+ expect(record.languages).to include 'Spanish; Castilian', 'Dutch; Flemish'
56
+ end
57
+
58
+ it 'passes thru language values that are already text (not code)' do
59
+ expect(record.languages).to include 'Spanish', 'Dutch'
60
+ end
61
+ end
62
+
63
+ describe '#language' do
64
+ describe '#code_term' do
65
+ it 'gets the languageTerms with type="code"' do
66
+ expect(record.language.code_term.map(&:text)).to eq ['spa', 'dut']
67
+ end
68
+ end
69
+
70
+ describe '#text_term' do
71
+ it 'filters out the languageTerms with type="code"' do
72
+ expect(record.language.text_term.map(&:text)).to eq %w[Spanish Dutch]
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ context 'with a record with multiple language codes in one element' do
79
+ subject(:record) do
80
+ mods_record(<<-XML)
81
+ <language>
82
+ <languageTerm authority='iso639-2b' type='code'>per ara, dut</languageTerm>
83
+ </language>
84
+ XML
85
+ end
86
+
87
+ describe '#languages' do
88
+ it 'creates a separate value for each language in a comma, space, or | separated list' do
89
+ expect(record.languages).to include 'Arabic', 'Persian', 'Dutch; Flemish'
90
+ end
91
+ end
92
+ end
93
+
94
+ context 'with a record without a languageTerm child' do
95
+ subject(:record) do
96
+ mods_record(<<-XML)
97
+ <language>Greek</language>
98
+ XML
99
+ end
100
+
101
+ describe '#languages' do
102
+ it 'preserves values that are not inside <languageTerm> elements' do
103
+ expect(mods_record('<language>Greek</language>').languages).to eq(['Greek'])
104
+ end
105
+ end
106
+ end
107
+
108
+ context 'wih a record with some authority attributes' do
109
+ subject(:record) do
110
+ mods_record(<<-XML)
111
+ <language><languageTerm authorityURI='http://example.com' valueURI='http://example.com/zzz'>zzz</languageTerm></language>
112
+ XML
113
+ end
114
+
115
+ describe '#language' do
116
+ describe '#languageTerm' do
117
+ it 'recognizes other authority attributes' do
118
+ expect(record.language.languageTerm).to have_attributes(authorityURI: ['http://example.com'], valueURI: ['http://example.com/zzz'])
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,175 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe "Mods <location> Element" do
4
+ context 'with a record with a physical location' do
5
+ subject(:record) do
6
+ mods_record("<location><physicalLocation>here</physicalLocation></location>")
7
+ end
8
+
9
+ it "should have access to text value of element" do
10
+ expect(record.location.physicalLocation.text).to eq("here")
11
+ end
12
+ end
13
+
14
+ context 'with a record with a physical location that has an authority' do
15
+ subject(:record) do
16
+ mods_record("<location><physicalLocation authority='marcorg'>MnRM</physicalLocation></location>")
17
+ end
18
+
19
+ it "should have access to text value of element" do
20
+ expect(record.location.physicalLocation.map { |n| n.text }).to eq(["MnRM"])
21
+ end
22
+
23
+ it "should recognize authority attribute" do
24
+ expect(record.location.physicalLocation.authority).to eq(["marcorg"])
25
+ end
26
+ end
27
+
28
+ context 'with a record with a displayLabel' do
29
+ subject(:record) do
30
+ mods_record("<location><physicalLocation displayLabel='Correspondence'>some address</physicalLocation></location>")
31
+ end
32
+
33
+ it "should recognize displayLabel attribute" do
34
+ expect(record.location.physicalLocation.displayLabel).to eq(["Correspondence"])
35
+ end
36
+ end
37
+
38
+ context 'with a record with a shelfLocator' do
39
+ subject(:record) do
40
+ mods_record(<<-XML)
41
+ <location>
42
+ <physicalLocation>Library of Congress </physicalLocation>
43
+ <shelfLocator>DAG no. 1410</shelfLocator>
44
+ </location>
45
+ XML
46
+ end
47
+
48
+ it "has a shelfLocator child element" do
49
+ expect(record.location.shelfLocator.map { |n| n.text }).to eq(["DAG no. 1410"])
50
+ end
51
+ end
52
+
53
+ context 'with a record with a url location' do
54
+ let(:url_attribs) do
55
+ mods_record(<<-XML).location.url
56
+ <location>
57
+ <url
58
+ displayLabel='Digital collection of 46 images available online'
59
+ usage='primary display'
60
+ note='something'
61
+ dateLastAccessed='2021-12-21'>
62
+ http://searchworks.stanford.edu/?f%5Bcollection%5D%5B%5D=The+Reid+W.+Dennis+Collection+of+California+Lithographs&amp;view=gallery
63
+ </url>
64
+ </location>
65
+ XML
66
+ end
67
+
68
+ it 'has the right attributes' do
69
+ expect(url_attribs).to have_attributes(
70
+ displayLabel: ['Digital collection of 46 images available online'],
71
+ usage: ['primary display'],
72
+ note: ['something'],
73
+ dateLastAccessed: ['2021-12-21']
74
+ )
75
+ end
76
+ end
77
+
78
+ context 'with a record with multiple urls' do
79
+ let(:mult_flavor_loc_urls) do
80
+ mods_record(<<-XML).location.url
81
+ <location>
82
+ <url access='preview'>http://preview.org</url>
83
+ <url access='object in context'>http://context.org</url>
84
+ <url access='raw object'>http://object.org</url>
85
+ </location>
86
+ XML
87
+ end
88
+
89
+ describe '#url' do
90
+ it 'has access to text value of element' do
91
+ expect(mult_flavor_loc_urls.map(&:text)).to eq ["http://preview.org", "http://context.org", "http://object.org"]
92
+ end
93
+
94
+ describe '#access' do
95
+ it 'provides access to the access attributes' do
96
+ expect(mult_flavor_loc_urls.access).to eq ['preview', 'object in context', 'raw object']
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ context 'with an empty url element' do
103
+ let(:empty_url) do
104
+ mods_record(<<-XML).location.url
105
+ <location><url /></location>
106
+ XML
107
+ end
108
+
109
+ it 'is an empty string for single empty url element' do
110
+ expect(empty_url.text).to be_empty
111
+ end
112
+ end
113
+
114
+ context 'with a record with holdingSimple data' do
115
+ subject(:record) do
116
+ mods_record(<<-XML)
117
+ <location>
118
+ <physicalLocation authority='marcorg'>MnRM</physicalLocation>
119
+ <holdingSimple>
120
+ <copyInformation>
121
+ <subLocation>Patient reading room</subLocation>
122
+ <shelfLocator>QH511.A1J68</shelfLocator>
123
+ <enumerationAndChronology unitType='1'> v.1-v.8 1970-1976</enumerationAndChronology>
124
+ </copyInformation>
125
+ </holdingSimple>
126
+ </location>
127
+ XML
128
+ end
129
+
130
+ it "has aholdingSimple child element" do
131
+ expect(record.location.holdingSimple.copyInformation.first).to have_attributes(
132
+ sub_location: have_attributes(text: 'Patient reading room'),
133
+ shelf_locator: have_attributes(text: 'QH511.A1J68'),
134
+ enumeration_and_chronology: have_attributes(text: ' v.1-v.8 1970-1976', unitType: ['1'])
135
+ )
136
+ end
137
+ end
138
+
139
+
140
+ context 'with a record with holdingComplex data' do
141
+ subject(:record) do
142
+ mods_record(<<-XML)
143
+ <location>
144
+ <physicalLocation>Menlo Park Public Library</physicalLocation>
145
+ <holdingExternal>
146
+ <holding xmlns='info:ofi/fmt:xml:xsd:iso20775' xsi:schemaLocation='info:ofi/fmt:xml:xsd:iso20775 http://www.loc.gov/standards/iso20775/N130_ISOholdings_v6_1.xsd'>
147
+ <institutionIdentifier>
148
+ <value>JRF</value>
149
+ <typeOrSource>
150
+ <pointer>http://worldcat.org/registry/institutions/</pointer>
151
+ </typeOrSource>
152
+ </institutionIdentifier>
153
+ <physicalLocation>Menlo Park Public Library</physicalLocation>
154
+ <physicalAddress>
155
+ <text>Menlo Park, CA 94025 United States </text>
156
+ </physicalAddress>
157
+ <electronicAddress>
158
+ <text>http://www.worldcat.org/wcpa/oclc/15550774? page=frame&amp;url=%3D%3FUTF-8%3FB%FaHR0cDovL2NhdGFsb2cucGxzaW5mby5vcmcvc2VhcmNoL2kwMTk1MDM4NjMw%3F%3D&amp;title=Menlo+Park+Public+Library&amp;linktype=opac&amp;detail=JRF%3AMenlo+Park+Public+Library%3APublic&amp;app=wcapi&amp;id=OCL-OCLC+Staff+use</text>
159
+ </electronicAddress>
160
+ <holdingSimple>
161
+ <copiesSummary>
162
+ <copiesCount>1</copiesCount>
163
+ </copiesSummary>
164
+ </holdingSimple>
165
+ </holding>
166
+ </holdingExternal>
167
+ </location>
168
+ XML
169
+ end
170
+
171
+ it "has a holdingComplex child element" do
172
+ expect(record.location.holdingExternal.xpath('//h:holding/h:physicalLocation', h: 'info:ofi/fmt:xml:xsd:iso20775').map(&:text)).to eq ['Menlo Park Public Library']
173
+ end
174
+ end
175
+ end