cocina_display 1.7.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/config/searchworks_languages.yml +71 -36
- data/lib/cocina_display/concerns/contributors.rb +12 -4
- data/lib/cocina_display/concerns/events.rb +1 -2
- data/lib/cocina_display/concerns/forms.rb +4 -5
- data/lib/cocina_display/concerns/geospatial.rb +2 -1
- data/lib/cocina_display/concerns/identifiers.rb +12 -5
- data/lib/cocina_display/concerns/notes.rb +19 -0
- data/lib/cocina_display/concerns/structural.rb +65 -7
- data/lib/cocina_display/dates/date.rb +26 -27
- data/lib/cocina_display/dates/date_range.rb +32 -10
- data/lib/cocina_display/events/imprint.rb +2 -2
- data/lib/cocina_display/forms/resource_type.rb +35 -2
- data/lib/cocina_display/geospatial.rb +8 -4
- data/lib/cocina_display/json_backed_record.rb +4 -1
- data/lib/cocina_display/structural/file.rb +134 -0
- data/lib/cocina_display/structural/file_set.rb +57 -0
- data/lib/cocina_display/title.rb +1 -1
- data/lib/cocina_display/utils.rb +2 -2
- data/lib/cocina_display/version.rb +1 -1
- data/lib/cocina_display.rb +1 -2
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9b7ec735c0ad257e8313d365e8900790b9acc115f98607a61a3b4eb293a2cb9a
|
|
4
|
+
data.tar.gz: a9761a59329eb8095c055063ef63b5b9e9c99d1d754cc8af3b20d95f59ec1fa7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1e522347186bddba536dd3139b6ce540a06724879d300caa296f96dbd29c866bc087756e3b95beb4b8b20541141b41ae60b1b892dcf49b30ffe8f26e3de67102
|
|
7
|
+
data.tar.gz: e87f404d0eaa5aaf3e7fbac2ff2effdfef1a644fa7ae02b4f29f9207804249eabcd25f9c992892a839a634750817d76c78e9e8f388a79a3e855f8cb5b782e23d
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Map of language codes to language names used in Searchworks, ported from stanford-mods gem.
|
|
2
2
|
# See: https://github.com/solrmarc/stanford-solr-marc/blob/master/stanford-sw/translation_maps/language_map.properties
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
#???: null
|
|
5
|
+
aar: Afar
|
|
5
6
|
abk: Abkhaz
|
|
6
7
|
ace: Achinese
|
|
7
8
|
ach: Acoli
|
|
@@ -11,7 +12,8 @@ afa: Afroasiatic (Other)
|
|
|
11
12
|
afh: Afrihili (Artificial language)
|
|
12
13
|
afr: Afrikaans
|
|
13
14
|
ain: Ainu
|
|
14
|
-
ajm
|
|
15
|
+
# ajm is deprecated
|
|
16
|
+
ajm: Aljamía
|
|
15
17
|
aka: Akan
|
|
16
18
|
akk: Akkadian
|
|
17
19
|
alb: Albanian
|
|
@@ -24,12 +26,13 @@ anp: Angika
|
|
|
24
26
|
apa: Apache languages
|
|
25
27
|
ara: Arabic
|
|
26
28
|
arc: Aramaic
|
|
27
|
-
arg: Aragonese
|
|
29
|
+
arg: Aragonese
|
|
28
30
|
arm: Armenian
|
|
29
31
|
arn: Mapuche
|
|
30
32
|
arp: Arapaho
|
|
31
33
|
art: Artificial (Other)
|
|
32
34
|
arw: Arawak
|
|
35
|
+
# ase from iso639-3
|
|
33
36
|
ase: American Sign Language
|
|
34
37
|
asm: Assamese
|
|
35
38
|
ast: Bable
|
|
@@ -40,7 +43,7 @@ ave: Avestan
|
|
|
40
43
|
awa: Awadhi
|
|
41
44
|
aym: Aymara
|
|
42
45
|
aze: Azerbaijani
|
|
43
|
-
bad: Banda
|
|
46
|
+
bad: Banda languages
|
|
44
47
|
bai: Bamileke languages
|
|
45
48
|
bak: Bashkir
|
|
46
49
|
bal: Baluchi
|
|
@@ -55,7 +58,7 @@ bem: Bemba
|
|
|
55
58
|
ben: Bengali
|
|
56
59
|
ber: Berber (Other)
|
|
57
60
|
bho: Bhojpuri
|
|
58
|
-
bih: Bihari
|
|
61
|
+
bih: Bihari (Other)
|
|
59
62
|
bik: Bikol
|
|
60
63
|
bin: Edo
|
|
61
64
|
bis: Bislama
|
|
@@ -72,6 +75,7 @@ bur: Burmese
|
|
|
72
75
|
byn: Bilin
|
|
73
76
|
cad: Caddo
|
|
74
77
|
cai: Central American Indian (Other)
|
|
78
|
+
# cam is deprecated
|
|
75
79
|
cam: Khmer
|
|
76
80
|
car: Carib
|
|
77
81
|
cat: Catalan
|
|
@@ -83,7 +87,7 @@ chb: Chibcha
|
|
|
83
87
|
che: Chechen
|
|
84
88
|
chg: Chagatai
|
|
85
89
|
chi: Chinese
|
|
86
|
-
chk:
|
|
90
|
+
chk: Chuukese
|
|
87
91
|
chm: Mari
|
|
88
92
|
chn: Chinook jargon
|
|
89
93
|
cho: Choctaw
|
|
@@ -93,6 +97,7 @@ chu: Church Slavic
|
|
|
93
97
|
chv: Chuvash
|
|
94
98
|
chy: Cheyenne
|
|
95
99
|
cmc: Chamic languages
|
|
100
|
+
cnr: Montenegrin
|
|
96
101
|
cop: Coptic
|
|
97
102
|
cor: Cornish
|
|
98
103
|
cos: Corsican
|
|
@@ -110,8 +115,8 @@ dan: Danish
|
|
|
110
115
|
dar: Dargwa
|
|
111
116
|
day: Dayak
|
|
112
117
|
del: Delaware
|
|
113
|
-
den:
|
|
114
|
-
dgr:
|
|
118
|
+
den: Slavey
|
|
119
|
+
dgr: Tlicho
|
|
115
120
|
din: Dinka
|
|
116
121
|
div: Divehi
|
|
117
122
|
doi: Dogri
|
|
@@ -124,20 +129,25 @@ dyu: Dyula
|
|
|
124
129
|
dzo: Dzongkha
|
|
125
130
|
efi: Efik
|
|
126
131
|
egy: Egyptian
|
|
132
|
+
# per RFC 5646 and ISO 15924
|
|
127
133
|
egy-Egyd: Egyptian, Demotic
|
|
128
134
|
eka: Ekajuk
|
|
129
135
|
elx: Elamite
|
|
130
136
|
eng: English
|
|
131
137
|
enm: English, Middle (1100-1500)
|
|
132
138
|
epo: Esperanto
|
|
139
|
+
# esk is deprecated
|
|
133
140
|
esk: Eskimo languages
|
|
141
|
+
# esp is deprecated
|
|
134
142
|
esp: Esperanto
|
|
135
143
|
est: Estonian
|
|
144
|
+
# eth is deprecated
|
|
136
145
|
eth: Ethiopic
|
|
137
146
|
ewe: Ewe
|
|
138
147
|
ewo: Ewondo
|
|
139
148
|
fan: Fang
|
|
140
149
|
fao: Faroese
|
|
150
|
+
# far is deprecated
|
|
141
151
|
far: Faroese
|
|
142
152
|
fat: Fanti
|
|
143
153
|
fij: Fijian
|
|
@@ -146,17 +156,21 @@ fin: Finnish
|
|
|
146
156
|
fiu: Finno-Ugrian (Other)
|
|
147
157
|
fon: Fon
|
|
148
158
|
fre: French
|
|
159
|
+
# fri is deprecated
|
|
149
160
|
fri: Frisian
|
|
150
|
-
frm: French, Middle (ca.
|
|
151
|
-
fro: French, Old (ca. 842-
|
|
161
|
+
frm: French, Middle (ca. 1300-1600)
|
|
162
|
+
fro: French, Old (ca. 842-1300)
|
|
152
163
|
frr: North Frisian
|
|
153
164
|
frs: East Frisian
|
|
154
165
|
fry: Frisian
|
|
155
166
|
ful: Fula
|
|
156
167
|
fur: Friulian
|
|
157
|
-
gaa:
|
|
168
|
+
gaa: Gã
|
|
169
|
+
# gae is deprecated
|
|
158
170
|
gae: Scottish Gaelic
|
|
171
|
+
# gag is deprecated
|
|
159
172
|
gag: Galician
|
|
173
|
+
# gal is deprecated
|
|
160
174
|
gal: Oromo
|
|
161
175
|
gay: Gayo
|
|
162
176
|
gba: Gbaya
|
|
@@ -176,12 +190,13 @@ gor: Gorontalo
|
|
|
176
190
|
got: Gothic
|
|
177
191
|
grb: Grebo
|
|
178
192
|
grc: Greek, Ancient (to 1453)
|
|
179
|
-
gre: Greek, Modern (1453-
|
|
193
|
+
gre: Greek, Modern (1453-)
|
|
180
194
|
grn: Guarani
|
|
181
195
|
gsw: Swiss German
|
|
196
|
+
# gua is deprecated
|
|
182
197
|
gua: Guarani
|
|
183
198
|
guj: Gujarati
|
|
184
|
-
gwi: Gwich'in
|
|
199
|
+
gwi: Gwich'in
|
|
185
200
|
hai: Haida
|
|
186
201
|
hat: Haitian French Creole
|
|
187
202
|
hau: Hausa
|
|
@@ -189,7 +204,7 @@ haw: Hawaiian
|
|
|
189
204
|
heb: Hebrew
|
|
190
205
|
her: Herero
|
|
191
206
|
hil: Hiligaynon
|
|
192
|
-
him:
|
|
207
|
+
him: Western Pahari languages
|
|
193
208
|
hin: Hindi
|
|
194
209
|
hit: Hittite
|
|
195
210
|
hmn: Hmong
|
|
@@ -212,9 +227,11 @@ inc: Indic (Other)
|
|
|
212
227
|
ind: Indonesian
|
|
213
228
|
ine: Indo-European (Other)
|
|
214
229
|
inh: Ingush
|
|
230
|
+
# int is deprecated
|
|
215
231
|
int: Interlingua (International Auxiliary Language Association)
|
|
216
232
|
ipk: Inupiaq
|
|
217
233
|
ira: Iranian (Other)
|
|
234
|
+
# iri is deprecated
|
|
218
235
|
iri: Irish
|
|
219
236
|
iro: Iroquoian (Other)
|
|
220
237
|
ita: Italian
|
|
@@ -226,10 +243,10 @@ jrb: Judeo-Arabic
|
|
|
226
243
|
kaa: Kara-Kalpak
|
|
227
244
|
kab: Kabyle
|
|
228
245
|
kac: Kachin
|
|
229
|
-
kal:
|
|
246
|
+
kal: Kalâtdlisut
|
|
230
247
|
kam: Kamba
|
|
231
248
|
kan: Kannada
|
|
232
|
-
kar: Karen
|
|
249
|
+
kar: Karen languages
|
|
233
250
|
kas: Kashmiri
|
|
234
251
|
kau: Kanuri
|
|
235
252
|
kaw: Kawi
|
|
@@ -247,22 +264,25 @@ kok: Konkani
|
|
|
247
264
|
kom: Komi
|
|
248
265
|
kon: Kongo
|
|
249
266
|
kor: Korean
|
|
250
|
-
kos:
|
|
267
|
+
kos: Kosraean
|
|
251
268
|
kpe: Kpelle
|
|
252
269
|
krc: Karachay-Balkar
|
|
253
270
|
krl: Karelian
|
|
254
|
-
kro: Kru
|
|
271
|
+
kro: Kru (Other)
|
|
255
272
|
kru: Kurukh
|
|
256
273
|
kua: Kuanyama
|
|
257
274
|
kum: Kumyk
|
|
258
275
|
kur: Kurdish
|
|
276
|
+
# kus is deprecated
|
|
259
277
|
kus: Kusaie
|
|
260
|
-
kut:
|
|
278
|
+
kut: Kootenai
|
|
261
279
|
lad: Ladino
|
|
262
|
-
lah:
|
|
263
|
-
lam: Lamba
|
|
264
|
-
lan
|
|
280
|
+
lah: Lahndā
|
|
281
|
+
lam: Lamba (Zambia and Congo)
|
|
282
|
+
# lan is deprecated
|
|
283
|
+
lan: Occitan (post 1500)
|
|
265
284
|
lao: Lao
|
|
285
|
+
# lap is deprecated
|
|
266
286
|
lap: Sami
|
|
267
287
|
lat: Latin
|
|
268
288
|
lav: Latvian
|
|
@@ -272,11 +292,11 @@ lin: Lingala
|
|
|
272
292
|
lit: Lithuanian
|
|
273
293
|
lol: Mongo-Nkundu
|
|
274
294
|
loz: Lozi
|
|
275
|
-
ltz:
|
|
295
|
+
ltz: Luxembourgish
|
|
276
296
|
lua: Luba-Lulua
|
|
277
297
|
lub: Luba-Katanga
|
|
278
298
|
lug: Ganda
|
|
279
|
-
lui:
|
|
299
|
+
lui: Luiseño
|
|
280
300
|
lun: Lunda
|
|
281
301
|
luo: Luo (Kenya and Tanzania)
|
|
282
302
|
lus: Lushai
|
|
@@ -291,7 +311,8 @@ man: Mandingo
|
|
|
291
311
|
mao: Maori
|
|
292
312
|
map: Austronesian (Other)
|
|
293
313
|
mar: Marathi
|
|
294
|
-
mas:
|
|
314
|
+
mas: Maasai
|
|
315
|
+
# max is deprecated
|
|
295
316
|
max: Manx
|
|
296
317
|
may: Malay
|
|
297
318
|
mdf: Moksha
|
|
@@ -302,6 +323,7 @@ mic: Micmac
|
|
|
302
323
|
min: Minangkabau
|
|
303
324
|
# mis: Miscellaneous languages
|
|
304
325
|
mkh: Mon-Khmer (Other)
|
|
326
|
+
# mla is deprecated
|
|
305
327
|
mla: Malagasy
|
|
306
328
|
mlg: Malagasy
|
|
307
329
|
mlt: Maltese
|
|
@@ -309,9 +331,10 @@ mnc: Manchu
|
|
|
309
331
|
mni: Manipuri
|
|
310
332
|
mno: Manobo languages
|
|
311
333
|
moh: Mohawk
|
|
334
|
+
# mol is deprecated
|
|
312
335
|
mol: Moldavian
|
|
313
336
|
mon: Mongolian
|
|
314
|
-
mos:
|
|
337
|
+
mos: Mooré
|
|
315
338
|
# mul: Multiple languages
|
|
316
339
|
mun: Munda (Other)
|
|
317
340
|
mus: Creek
|
|
@@ -334,7 +357,7 @@ nia: Nias
|
|
|
334
357
|
nic: Niger-Kordofanian (Other)
|
|
335
358
|
niu: Niuean
|
|
336
359
|
nno: Norwegian (Nynorsk)
|
|
337
|
-
nob: Norwegian (
|
|
360
|
+
nob: Norwegian (Bokmål)
|
|
338
361
|
nog: Nogai
|
|
339
362
|
non: Old Norse
|
|
340
363
|
nor: Norwegian
|
|
@@ -368,10 +391,10 @@ phi: Philippine (Other)
|
|
|
368
391
|
phn: Phoenician
|
|
369
392
|
pli: Pali
|
|
370
393
|
pol: Polish
|
|
371
|
-
pon:
|
|
394
|
+
pon: Pohnpeian
|
|
372
395
|
por: Portuguese
|
|
373
396
|
pra: Prakrit languages
|
|
374
|
-
pro:
|
|
397
|
+
pro: Provençal (to 1500)
|
|
375
398
|
pus: Pushto
|
|
376
399
|
que: Quechua
|
|
377
400
|
raj: Rajasthani
|
|
@@ -391,18 +414,22 @@ sai: South American Indian (Other)
|
|
|
391
414
|
sal: Salishan languages
|
|
392
415
|
sam: Samaritan Aramaic
|
|
393
416
|
san: Sanskrit
|
|
417
|
+
# sao is deprecated
|
|
394
418
|
sao: Samoan
|
|
395
419
|
sas: Sasak
|
|
396
420
|
sat: Santali
|
|
421
|
+
# scc is deprecated
|
|
397
422
|
scc: Serbian
|
|
398
423
|
scn: Sicilian Italian
|
|
399
424
|
sco: Scots
|
|
425
|
+
# scr is deprecated
|
|
400
426
|
scr: Croatian
|
|
401
427
|
sel: Selkup
|
|
402
428
|
sem: Semitic (Other)
|
|
403
429
|
sga: Irish, Old (to 1100)
|
|
404
430
|
sgn: Sign languages
|
|
405
431
|
shn: Shan
|
|
432
|
+
# sho is deprecated
|
|
406
433
|
sho: Shona
|
|
407
434
|
sid: Sidamo
|
|
408
435
|
sin: Sinhalese
|
|
@@ -420,6 +447,7 @@ smo: Samoan
|
|
|
420
447
|
sms: Skolt Sami
|
|
421
448
|
sna: Shona
|
|
422
449
|
snd: Sindhi
|
|
450
|
+
# snh is deprecated
|
|
423
451
|
snh: Sinhalese
|
|
424
452
|
snk: Soninke
|
|
425
453
|
sog: Sogdian
|
|
@@ -432,6 +460,7 @@ srn: Sranan
|
|
|
432
460
|
srp: Serbian
|
|
433
461
|
srr: Serer
|
|
434
462
|
ssa: Nilo-Saharan (Other)
|
|
463
|
+
# sso is deprecated
|
|
435
464
|
sso: Sotho
|
|
436
465
|
ssw: Swazi
|
|
437
466
|
suk: Sukuma
|
|
@@ -440,14 +469,18 @@ sus: Susu
|
|
|
440
469
|
sux: Sumerian
|
|
441
470
|
swa: Swahili
|
|
442
471
|
swe: Swedish
|
|
472
|
+
# swz is deprecated
|
|
443
473
|
swz: Swazi
|
|
444
474
|
syc: Syriac
|
|
445
475
|
syr: Syriac, Modern
|
|
476
|
+
# tag is deprecated
|
|
446
477
|
tag: Tagalog
|
|
447
478
|
tah: Tahitian
|
|
448
479
|
tai: Tai (Other)
|
|
480
|
+
# taj is deprecated
|
|
449
481
|
taj: Tajik
|
|
450
482
|
tam: Tamil
|
|
483
|
+
# tar is deprecated
|
|
451
484
|
tar: Tatar
|
|
452
485
|
tat: Tatar
|
|
453
486
|
tel: Telugu
|
|
@@ -458,7 +491,7 @@ tgk: Tajik
|
|
|
458
491
|
tgl: Tagalog
|
|
459
492
|
tha: Thai
|
|
460
493
|
tib: Tibetan
|
|
461
|
-
tig:
|
|
494
|
+
tig: Tigré
|
|
462
495
|
tir: Tigrinya
|
|
463
496
|
tiv: Tiv
|
|
464
497
|
tkl: Tokelauan
|
|
@@ -468,10 +501,12 @@ tmh: Tamashek
|
|
|
468
501
|
tog: Tonga (Nyasa)
|
|
469
502
|
ton: Tongan
|
|
470
503
|
tpi: Tok Pisin
|
|
504
|
+
# tru is deprecated
|
|
471
505
|
tru: Truk
|
|
472
506
|
tsi: Tsimshian
|
|
473
507
|
tsn: Tswana
|
|
474
508
|
tso: Tsonga
|
|
509
|
+
# tsw is deprecated
|
|
475
510
|
tsw: Tswana
|
|
476
511
|
tuk: Turkmen
|
|
477
512
|
tum: Tumbuka
|
|
@@ -492,17 +527,17 @@ uzb: Uzbek
|
|
|
492
527
|
vai: Vai
|
|
493
528
|
ven: Venda
|
|
494
529
|
vie: Vietnamese
|
|
495
|
-
vol:
|
|
530
|
+
vol: Volapük
|
|
496
531
|
vot: Votic
|
|
497
532
|
wak: Wakashan languages
|
|
498
|
-
wal:
|
|
533
|
+
wal: Wolayta
|
|
499
534
|
war: Waray
|
|
500
|
-
was:
|
|
535
|
+
was: Washoe
|
|
501
536
|
wel: Welsh
|
|
502
|
-
wen: Sorbian
|
|
537
|
+
wen: Sorbian (Other)
|
|
503
538
|
wln: Walloon
|
|
504
539
|
wol: Wolof
|
|
505
|
-
xal:
|
|
540
|
+
xal: Oirat
|
|
506
541
|
xho: Xhosa
|
|
507
542
|
yao: Yao (Africa)
|
|
508
543
|
yap: Yapese
|
|
@@ -513,7 +548,7 @@ zap: Zapotec
|
|
|
513
548
|
zbl: Blissymbolics
|
|
514
549
|
zen: Zenaga
|
|
515
550
|
zha: Zhuang
|
|
516
|
-
znd: Zande
|
|
551
|
+
znd: Zande languages
|
|
517
552
|
zul: Zulu
|
|
518
553
|
zun: Zuni
|
|
519
554
|
# zxx: null
|
|
@@ -108,11 +108,13 @@ module CocinaDisplay
|
|
|
108
108
|
end
|
|
109
109
|
|
|
110
110
|
# All contributors for the object, including authors, editors, etc.
|
|
111
|
-
#
|
|
111
|
+
# Checks both description.contributor and description.event.contributor.
|
|
112
112
|
# @return [Array<Contributor>]
|
|
113
113
|
def contributors
|
|
114
|
-
@contributors ||=
|
|
115
|
-
.
|
|
114
|
+
@contributors ||= Enumerator::Chain.new(
|
|
115
|
+
path("$.description.contributor.*"),
|
|
116
|
+
path("$.description.event.*.contributor.*")
|
|
117
|
+
).map { |c| CocinaDisplay::Contributors::Contributor.new(c) }
|
|
116
118
|
end
|
|
117
119
|
|
|
118
120
|
# All contributors with an "author" role.
|
|
@@ -145,7 +147,13 @@ module CocinaDisplay
|
|
|
145
147
|
# @return [Array<Contributor>]
|
|
146
148
|
def additional_contributors
|
|
147
149
|
return [] if contributors.empty? || contributors.one?
|
|
148
|
-
contributors - [main_contributor]
|
|
150
|
+
contributors.reject { |c| imprint_contributors.include?(c) } - [main_contributor]
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# The contributors associated with imprint events (usually publishers).
|
|
154
|
+
# @return [Array<Contributor>]
|
|
155
|
+
def imprint_contributors
|
|
156
|
+
imprint_events.flat_map(&:contributors).uniq
|
|
149
157
|
end
|
|
150
158
|
end
|
|
151
159
|
end
|
|
@@ -38,11 +38,10 @@ module CocinaDisplay
|
|
|
38
38
|
# @param ignore_qualified [Boolean] Reject qualified dates (e.g. approximate)
|
|
39
39
|
# @return [Array<Integer>, nil]
|
|
40
40
|
# @note 6 BCE will appear as -5; 4 CE will appear as 4.
|
|
41
|
-
def
|
|
41
|
+
def pub_year_ints(ignore_qualified: false)
|
|
42
42
|
date = pub_date(ignore_qualified: ignore_qualified)
|
|
43
43
|
return unless date
|
|
44
44
|
|
|
45
|
-
date = date.as_interval if date.is_a? CocinaDisplay::Dates::DateRange
|
|
46
45
|
date.to_a.map(&:year).compact.uniq.sort
|
|
47
46
|
end
|
|
48
47
|
|
|
@@ -199,15 +199,13 @@ module CocinaDisplay
|
|
|
199
199
|
when "manuscript", "mixed material"
|
|
200
200
|
values << "Archive/Manuscript"
|
|
201
201
|
when "moving image"
|
|
202
|
-
values << "Video"
|
|
202
|
+
values << "Video/Film"
|
|
203
203
|
when "notated music"
|
|
204
204
|
values << "Music score"
|
|
205
205
|
when "software, multimedia"
|
|
206
206
|
# Prevent GIS datasets from being labeled as "Software"
|
|
207
207
|
values << "Software/Multimedia" unless cartographic? || dataset?
|
|
208
|
-
when "sound recording-musical"
|
|
209
|
-
values << "Music recording"
|
|
210
|
-
when "sound recording-nonmusical", "sound recording"
|
|
208
|
+
when "sound recording-musical", "sound recording-nonmusical", "sound recording"
|
|
211
209
|
values << "Sound recording"
|
|
212
210
|
when "still image"
|
|
213
211
|
values << "Image"
|
|
@@ -216,7 +214,8 @@ module CocinaDisplay
|
|
|
216
214
|
# 2 records currently (2025) in Searchworks do this, but it is real.
|
|
217
215
|
if periodical? || archived_website?
|
|
218
216
|
values << "Journal/Periodical" if periodical?
|
|
219
|
-
values << "
|
|
217
|
+
values << "Website" if archived_website?
|
|
218
|
+
values << "Website|Archived website" if archived_website?
|
|
220
219
|
else
|
|
221
220
|
values << "Book"
|
|
222
221
|
end
|
|
@@ -13,9 +13,10 @@ module CocinaDisplay
|
|
|
13
13
|
|
|
14
14
|
# All valid coordinate data formatted for indexing into a Solr RPT field.
|
|
15
15
|
# @note This type of field accommodates both points and bounding boxes.
|
|
16
|
+
# @note In WKT, points have longitude first, unlike {coordinates_as_point}.
|
|
16
17
|
# @see https://solr.apache.org/guide/solr/latest/query-guide/spatial-search.html#rpt
|
|
17
18
|
# @return [Array<String>]
|
|
18
|
-
# @example ["POINT(
|
|
19
|
+
# @example ["POINT(-118.2437 34.0522)", "POLYGON((-118.2437 34.0522, -118.2437 34.1996, -117.9522 34.1996, -117.9522 34.0522, -118.2437 34.0522))"]
|
|
19
20
|
def coordinates_as_wkt
|
|
20
21
|
coordinate_objects.map(&:as_wkt).uniq
|
|
21
22
|
end
|
|
@@ -8,8 +8,7 @@ module CocinaDisplay
|
|
|
8
8
|
# @example
|
|
9
9
|
# record.druid #=> "druid:bb099mt5053"
|
|
10
10
|
def druid
|
|
11
|
-
cocina_doc["externalIdentifier"] ||
|
|
12
|
-
cocina_doc.dig("description", "purl")&.split("/")&.last
|
|
11
|
+
cocina_doc["externalIdentifier"] || purl_url&.split("/")&.last
|
|
13
12
|
end
|
|
14
13
|
|
|
15
14
|
# The DRUID for the object, without the +druid:+ prefix.
|
|
@@ -64,10 +63,12 @@ module CocinaDisplay
|
|
|
64
63
|
folio_hrid || bare_druid
|
|
65
64
|
end
|
|
66
65
|
|
|
67
|
-
#
|
|
66
|
+
# All identifier objects, optionally filtered by type.
|
|
67
|
+
# @param type [String, nil] The type of identifier to filter by (e.g. "DOI").
|
|
68
|
+
# @note Type matching is case insensitive.
|
|
68
69
|
# @return [Array<Identifier>]
|
|
69
|
-
def identifiers
|
|
70
|
-
|
|
70
|
+
def identifiers(type: nil)
|
|
71
|
+
type.present? ? all_identifiers.filter { |id| id.type&.casecmp?(type) } : all_identifiers
|
|
71
72
|
end
|
|
72
73
|
|
|
73
74
|
# Labelled display data for identifiers.
|
|
@@ -78,6 +79,12 @@ module CocinaDisplay
|
|
|
78
79
|
|
|
79
80
|
private
|
|
80
81
|
|
|
82
|
+
# All identifier objects extracted from the Cocina metadata.
|
|
83
|
+
# @return [Array<Identifier>]
|
|
84
|
+
def all_identifiers
|
|
85
|
+
@identifiers ||= path("$.description.identifier[*]").map { |id| Identifier.new(id) } + Array(doi_from_identification)
|
|
86
|
+
end
|
|
87
|
+
|
|
81
88
|
# Synthetic Identifier object for a DOI in the identification block.
|
|
82
89
|
# @return [Array<Identifier>]
|
|
83
90
|
def doi_from_identification
|
|
@@ -8,6 +8,25 @@ module CocinaDisplay
|
|
|
8
8
|
@notes ||= path("$.description.note.*").map { |note| CocinaDisplay::Note.new(note) }
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
+
# Text of all abstract notes.
|
|
12
|
+
# @return [Array<String>]
|
|
13
|
+
def abstracts
|
|
14
|
+
notes.select(&:abstract?).map(&:to_s).compact_blank
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Text of all table of contents notes.
|
|
18
|
+
# @return [Array<String>]
|
|
19
|
+
def tables_of_contents
|
|
20
|
+
notes.select(&:table_of_contents?).map(&:to_s).compact_blank
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Preferred citation for the object.
|
|
24
|
+
# If there are multiple notes with this type, uses the first.
|
|
25
|
+
# @return [String, nil]
|
|
26
|
+
def preferred_citation
|
|
27
|
+
notes.find(&:preferred_citation?)&.to_s
|
|
28
|
+
end
|
|
29
|
+
|
|
11
30
|
# Abstract metadata for display.
|
|
12
31
|
# @return [Array<CocinaDisplay::DisplayData>]
|
|
13
32
|
def abstract_display_data
|
|
@@ -4,23 +4,44 @@ module CocinaDisplay
|
|
|
4
4
|
module Concerns
|
|
5
5
|
# Methods for inspecting structural metadata (e.g. file hierarchy)
|
|
6
6
|
module Structural
|
|
7
|
+
# Structured data for all file sets in the object.
|
|
8
|
+
# Each fileset contains one or more files.
|
|
9
|
+
# @return [Array<CocinaDisplay::Structural::FileSet>]
|
|
10
|
+
# @example
|
|
11
|
+
# record.filesets.each do |fileset|
|
|
12
|
+
# puts fileset.type #=> "image"
|
|
13
|
+
# puts fileset.label #=> "High Resolution Images"
|
|
14
|
+
# end
|
|
15
|
+
def filesets
|
|
16
|
+
@filesets ||= path("$.structural.contains.*").map do |fileset|
|
|
17
|
+
CocinaDisplay::Structural::FileSet.new(fileset, druid: bare_druid, base_url: stacks_base_url)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
7
21
|
# Structured data for all individual files in the object.
|
|
8
22
|
# Traverses nested FileSet structure to return a flattened array.
|
|
9
|
-
# @return [Array<
|
|
23
|
+
# @return [Array<CocinaDisplay::Structural::File>]
|
|
10
24
|
# @example
|
|
11
25
|
# record.files.each do |file|
|
|
12
|
-
# puts file
|
|
13
|
-
# puts file
|
|
26
|
+
# puts file.filename #=> "image1.jpg"
|
|
27
|
+
# puts file.size #=> 123456
|
|
14
28
|
# end
|
|
15
29
|
def files
|
|
16
|
-
|
|
30
|
+
filesets.flat_map(&:files)
|
|
17
31
|
end
|
|
18
32
|
|
|
19
33
|
# All unique MIME types of files in this object.
|
|
20
34
|
# @return [Array<String>]
|
|
21
35
|
# @example ["image/jpeg", "application/pdf"]
|
|
22
36
|
def file_mime_types
|
|
23
|
-
files.
|
|
37
|
+
files.map(&:mime_type).compact.uniq
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# All unique types of filesets in this object.
|
|
41
|
+
# @return [Array<String>]
|
|
42
|
+
# @example ["image", "document"]
|
|
43
|
+
def fileset_types
|
|
44
|
+
filesets.map(&:type).compact.uniq
|
|
24
45
|
end
|
|
25
46
|
|
|
26
47
|
# Human-readable string representation of {total_file_size_int}.
|
|
@@ -34,7 +55,25 @@ module CocinaDisplay
|
|
|
34
55
|
# @return [Integer]
|
|
35
56
|
# @example 2621440
|
|
36
57
|
def total_file_size_int
|
|
37
|
-
files.
|
|
58
|
+
files.map(&:size).compact.sum
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# URL to a thumbnail image for this object, if any.
|
|
62
|
+
# @note Uses the IIIF image server to generate an image of the given size.
|
|
63
|
+
# @param region [String] Desired region of the image (e.g., "full", "square", "x,y,w,h", "pct:x,y,w,h").
|
|
64
|
+
# @param width [String] Desired width of the image in pixels (use "!" prefix to preserve aspect ratio).
|
|
65
|
+
# @param height [String] Desired height of the image in pixels.
|
|
66
|
+
# @return [String, nil]
|
|
67
|
+
# @example "https://stacks.stanford.edu/image/iiif/ts786ny5936%2FPC0170_s1_E_0204.jp2/full/!400,400/0/default.jpg"
|
|
68
|
+
def thumbnail_url(region: "full", width: "!400", height: "400")
|
|
69
|
+
thumbnail_file&.iiif_url(region: region, width: width, height: height)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# True if the object has a usable thumbnail file.
|
|
73
|
+
# @note Does not attempt to crawl virtual object members for thumbnails.
|
|
74
|
+
# @return [Boolean]
|
|
75
|
+
def thumbnail?
|
|
76
|
+
thumbnail_file.present?
|
|
38
77
|
end
|
|
39
78
|
|
|
40
79
|
# DRUIDs of collections this object is a member of.
|
|
@@ -44,17 +83,36 @@ module CocinaDisplay
|
|
|
44
83
|
path("$.structural.isMemberOf.*").map { |druid| druid.delete_prefix("druid:") }
|
|
45
84
|
end
|
|
46
85
|
|
|
86
|
+
# Whether this object is a virtual object.
|
|
87
|
+
# @return [Boolean]
|
|
47
88
|
def virtual_object?
|
|
48
|
-
return false if
|
|
89
|
+
return false if filesets.any?
|
|
49
90
|
|
|
50
91
|
path("$.structural.hasMemberOrders.*.members.*").any?
|
|
51
92
|
end
|
|
52
93
|
|
|
94
|
+
# DRUIDs of members of this virtual object.
|
|
95
|
+
# @return [Array<String>]
|
|
96
|
+
# @example ["ts786ny5936", "tp006ms8736", "tj297ys4758"]
|
|
53
97
|
def virtual_object_members
|
|
54
98
|
return [] unless virtual_object?
|
|
55
99
|
|
|
56
100
|
path("$.structural.hasMemberOrders.*.members.*").map { |druid| druid.delete_prefix("druid:") }
|
|
57
101
|
end
|
|
102
|
+
|
|
103
|
+
# DRUIDs of virtual objects this object is a part of.
|
|
104
|
+
# @return [Array<String>]
|
|
105
|
+
# @example "hj097bm8879"
|
|
106
|
+
def virtual_object_parents
|
|
107
|
+
related_resources.filter { |res| res.type == "part of" }.map(&:druid).compact_blank
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# The thumbnail file for this object, if any.
|
|
111
|
+
# Prefers files marked as thumbnails; falls back to any JP2 image.
|
|
112
|
+
# @return [CocinaDisplay::Structural::File, nil]
|
|
113
|
+
def thumbnail_file
|
|
114
|
+
files.find(&:thumbnail?) || files.find(&:jp2_image?)
|
|
115
|
+
end
|
|
58
116
|
end
|
|
59
117
|
end
|
|
60
118
|
end
|
|
@@ -84,7 +84,7 @@ module CocinaDisplay
|
|
|
84
84
|
return
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
-
sanitized = value.gsub(
|
|
87
|
+
sanitized = value.gsub(/^\[+/, "").gsub(/[.\]]+$/, "")
|
|
88
88
|
sanitized = value.rjust(4, "0") if /^\d{3}$/.match?(value)
|
|
89
89
|
|
|
90
90
|
sanitized
|
|
@@ -291,25 +291,17 @@ module CocinaDisplay
|
|
|
291
291
|
return value.sub(/(\d{2})(\d{2})-(\d{2})/, '\1\2-\1\3')
|
|
292
292
|
end
|
|
293
293
|
|
|
294
|
-
value.gsub(/(
|
|
294
|
+
value.gsub(/(?<!\d)(\d{1,3})([xu-]{1,3})/i) { "#{Regexp.last_match(1)}#{"0" * Regexp.last_match(2).length}" }.scan(/[\d-]/).join
|
|
295
295
|
end
|
|
296
296
|
|
|
297
297
|
# Decoded version of the date with "BCE" or "CE". Strips leading zeroes.
|
|
298
298
|
# @param allowed_precisions [Array<Symbol>] List of allowed precisions for the output.
|
|
299
299
|
# Defaults to [:day, :month, :year, :decade, :century, :unknown].
|
|
300
|
-
# @param ignore_unparseable [Boolean] Return nil instead of the original value if it couldn't be parsed
|
|
301
|
-
# @param display_original_value [Boolean] Return the original value if it was not encoded
|
|
302
300
|
# @return [String]
|
|
303
|
-
def decoded_value(allowed_precisions: [:day, :month, :year, :decade, :century, :unknown]
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
if display_original_value
|
|
308
|
-
unless encoding?
|
|
309
|
-
return value.strip unless value =~ /^-?\d+$/ || value =~ /^[\dXxu?-]{4}$/
|
|
310
|
-
end
|
|
301
|
+
def decoded_value(allowed_precisions: [:day, :month, :year, :decade, :century, :unknown])
|
|
302
|
+
if !parsed_date? || (!encoding? && value !~ /^-?\d+$/ && value !~ /^[\dXxu?-]{4}$/)
|
|
303
|
+
return value.strip
|
|
311
304
|
end
|
|
312
|
-
|
|
313
305
|
if date.is_a?(EDTF::Interval)
|
|
314
306
|
range = [
|
|
315
307
|
Date.format_date(date.min, date.min.precision, allowed_precisions),
|
|
@@ -342,18 +334,25 @@ module CocinaDisplay
|
|
|
342
334
|
format(qualified_format, decoded_value)
|
|
343
335
|
end
|
|
344
336
|
|
|
345
|
-
# Range between earliest possible date and latest possible date.
|
|
346
|
-
# @note
|
|
347
|
-
# @
|
|
337
|
+
# Range of {Date}s between earliest possible date and latest possible date.
|
|
338
|
+
# @note Output has day precision, using the first day/month if unspecified.
|
|
339
|
+
# @note If the range is open-ended, uses today's date as the end date.
|
|
340
|
+
# @note {EDTF::Set}s can be disjoint ranges, but this method will return the full span, unlike {#to_a}.
|
|
341
|
+
# @return [Range<Date>, nil]
|
|
348
342
|
def as_range
|
|
349
|
-
return unless earliest_date
|
|
343
|
+
return unless earliest_date || latest_date
|
|
350
344
|
|
|
351
|
-
earliest_date
|
|
345
|
+
start = earliest_date || latest_date
|
|
346
|
+
stop = latest_date || ::Date.today
|
|
347
|
+
|
|
348
|
+
start..stop
|
|
352
349
|
end
|
|
353
350
|
|
|
354
|
-
# Array of all
|
|
355
|
-
# @note
|
|
356
|
-
# @
|
|
351
|
+
# Array of all individual {Date}s that are described by the data.
|
|
352
|
+
# @note Output dates will have the same precision as the input date (e.g. year vs day).
|
|
353
|
+
# @note If the range is open-ended, uses today's date as the end date.
|
|
354
|
+
# @note {EDTF::Set}s can be disjoint ranges; unlike {#as_range} this method will respect any gaps.
|
|
355
|
+
# @return [Array<Date>]
|
|
357
356
|
def to_a
|
|
358
357
|
case date
|
|
359
358
|
when EDTF::Set
|
|
@@ -363,8 +362,6 @@ module CocinaDisplay
|
|
|
363
362
|
end
|
|
364
363
|
end
|
|
365
364
|
|
|
366
|
-
private
|
|
367
|
-
|
|
368
365
|
class << self
|
|
369
366
|
# Returns the date in the format specified by the precision.
|
|
370
367
|
# Supports e.g. retrieving year precision when the actual date is more precise.
|
|
@@ -444,6 +441,8 @@ module CocinaDisplay
|
|
|
444
441
|
end
|
|
445
442
|
end
|
|
446
443
|
|
|
444
|
+
private
|
|
445
|
+
|
|
447
446
|
# Expand placeholders like "19XX" into an object representing the full range.
|
|
448
447
|
# @note This is different from dates with an explicit start/end in the Cocina.
|
|
449
448
|
# @see CocinaDisplay::Dates::DateRange
|
|
@@ -523,13 +522,11 @@ module CocinaDisplay
|
|
|
523
522
|
# MARC date parser; similar to EDTF but with some MARC-specific encodings.
|
|
524
523
|
class MarcFormat < Date
|
|
525
524
|
def self.normalize_to_edtf(value)
|
|
526
|
-
return nil if value == "9999" || value == "
|
|
525
|
+
return nil if value == "9999" || value == "||||"
|
|
527
526
|
|
|
528
527
|
super
|
|
529
528
|
end
|
|
530
529
|
|
|
531
|
-
private
|
|
532
|
-
|
|
533
530
|
def earliest_date
|
|
534
531
|
if value == "1uuu"
|
|
535
532
|
::Date.parse("1000-01-01")
|
|
@@ -545,6 +542,8 @@ module CocinaDisplay
|
|
|
545
542
|
super
|
|
546
543
|
end
|
|
547
544
|
end
|
|
545
|
+
|
|
546
|
+
private
|
|
548
547
|
end
|
|
549
548
|
|
|
550
549
|
# Base class for date formats that match using a regex.
|
|
@@ -600,7 +599,7 @@ module CocinaDisplay
|
|
|
600
599
|
|
|
601
600
|
# Extractor for dates encoded as Roman numerals.
|
|
602
601
|
class RomanNumeralYearFormat < ExtractorDateFormat
|
|
603
|
-
REGEX = /(?<![A-Za-z
|
|
602
|
+
REGEX = /(?<![A-Za-z.])(?<year>[MCDLXVI.]+)(?![A-Za-z])/
|
|
604
603
|
|
|
605
604
|
def self.normalize_to_edtf(text)
|
|
606
605
|
matches = text.match(REGEX)
|
|
@@ -14,8 +14,8 @@ module CocinaDisplay
|
|
|
14
14
|
# Create the individual dates; if no encoding/type declared give them
|
|
15
15
|
# top-level encoding/type
|
|
16
16
|
dates = cocina["structuredValue"].map do |sv|
|
|
17
|
+
sv["encoding"] ||= cocina["encoding"]
|
|
17
18
|
date = Date.from_cocina(sv)
|
|
18
|
-
date.encoding ||= cocina.dig("encoding", "code")
|
|
19
19
|
date.type ||= cocina["type"]
|
|
20
20
|
date
|
|
21
21
|
end
|
|
@@ -44,7 +44,7 @@ module CocinaDisplay
|
|
|
44
44
|
# @see CocinaDisplay::Date#value
|
|
45
45
|
# @return [Array<String>]
|
|
46
46
|
def value
|
|
47
|
-
[start&.value, stop&.value]
|
|
47
|
+
[start&.value, stop&.value].compact
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
# Key used to sort this date range. Respects BCE/CE ordering and precision.
|
|
@@ -52,7 +52,7 @@ module CocinaDisplay
|
|
|
52
52
|
# @see CocinaDisplay::Date#sort_key
|
|
53
53
|
# @return [String]
|
|
54
54
|
def sort_key
|
|
55
|
-
[start&.sort_key, stop&.sort_key].join(" - ")
|
|
55
|
+
[start&.sort_key, stop&.sort_key].compact.join(" - ")
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
# Base values of start/end as single string. Used for comparison/deduping.
|
|
@@ -116,7 +116,7 @@ module CocinaDisplay
|
|
|
116
116
|
[
|
|
117
117
|
start&.decoded_value(**kwargs),
|
|
118
118
|
stop&.decoded_value(**kwargs)
|
|
119
|
-
].uniq.join(" - ")
|
|
119
|
+
].uniq.join(" - ").strip
|
|
120
120
|
end
|
|
121
121
|
|
|
122
122
|
# Decoded range with "BCE" or "CE" and qualifier markers applied.
|
|
@@ -137,12 +137,34 @@ module CocinaDisplay
|
|
|
137
137
|
end
|
|
138
138
|
end
|
|
139
139
|
|
|
140
|
-
#
|
|
141
|
-
# @return [
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
140
|
+
# Earliest possible date encoded in data, respecting unspecified/imprecise info.
|
|
141
|
+
# @return [Date]
|
|
142
|
+
# @return [nil] if no start or stop date is parsable
|
|
143
|
+
def earliest_date
|
|
144
|
+
start&.earliest_date || stop&.earliest_date
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Latest possible date encoded in data, respecting unspecified/imprecise info.
|
|
148
|
+
# @note If the range is open-ended, uses today's date as the end date.
|
|
149
|
+
# @return [Date]
|
|
150
|
+
# @return [nil] if open-ended range or stop is not parsable
|
|
151
|
+
def latest_date
|
|
152
|
+
stop&.latest_date || ::Date.today
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Array of all individual {Date}s that are described by the data.
|
|
156
|
+
# @note Output dates will have the same precision as the input date (e.g. year vs day).
|
|
157
|
+
# @note {EDTF::Set}s can be disjoint ranges; unlike {#as_range} this method will respect any gaps.
|
|
158
|
+
# @note If the range is open-ended, uses today's date as the end date.
|
|
159
|
+
# @return [Array<Date>]
|
|
160
|
+
def to_a
|
|
161
|
+
start_dates = start&.to_a || []
|
|
162
|
+
stop_dates = stop&.to_a || []
|
|
163
|
+
|
|
164
|
+
return [] if start_dates.empty? && stop_dates.empty?
|
|
165
|
+
return as_range.to_a if start_dates.one? && stop_dates.one? || stop_dates.empty?
|
|
166
|
+
|
|
167
|
+
[start_dates, stop_dates].flatten.sort.uniq
|
|
146
168
|
end
|
|
147
169
|
end
|
|
148
170
|
end
|
|
@@ -23,7 +23,7 @@ module CocinaDisplay
|
|
|
23
23
|
# The date portion of the imprint statement, comprising all unique dates.
|
|
24
24
|
# @return [String]
|
|
25
25
|
def date_str
|
|
26
|
-
Utils.compact_and_join(unique_dates_for_display.map(&:qualified_value))
|
|
26
|
+
Utils.compact_and_join(unique_dates_for_display.map(&:qualified_value), delimiter: "; ")
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
# The editions portion of the imprint statement, combining all edition notes.
|
|
@@ -71,7 +71,7 @@ module CocinaDisplay
|
|
|
71
71
|
# Remove any ranges that duplicate part of an unencoded non-range date
|
|
72
72
|
ranges, singles = deduped_dates.partition { |date| date.is_a?(CocinaDisplay::Dates::DateRange) }
|
|
73
73
|
unencoded_singles_dates = singles.reject(&:encoding?).flat_map(&:to_a)
|
|
74
|
-
ranges.reject! { |
|
|
74
|
+
ranges.reject! { |date_range| unencoded_singles_dates.any? { |date| date_range.as_range.include?(date) } }
|
|
75
75
|
|
|
76
76
|
(singles + ranges).sort
|
|
77
77
|
end
|
|
@@ -2,10 +2,19 @@ module CocinaDisplay
|
|
|
2
2
|
module Forms
|
|
3
3
|
# A Resource Type form associated with part or all of a Cocina object.
|
|
4
4
|
class ResourceType < Form
|
|
5
|
-
# Resource types are lowercased for display.
|
|
5
|
+
# Resource types are lowercased for display, except self-deposit types.
|
|
6
6
|
# @return [String]
|
|
7
7
|
def to_s
|
|
8
|
-
|
|
8
|
+
stanford_self_deposit? ? flat_value : flat_value.downcase
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# For self-deposit resource types, the flat value comprises primary and any subtypes.
|
|
12
|
+
# @return [String]
|
|
13
|
+
def flat_value
|
|
14
|
+
return super unless stanford_self_deposit?
|
|
15
|
+
return primary_type unless subtypes.any?
|
|
16
|
+
|
|
17
|
+
"#{primary_type} (#{subtypes.join(", ")})"
|
|
9
18
|
end
|
|
10
19
|
|
|
11
20
|
# Is this a Stanford self-deposit resource type?
|
|
@@ -33,6 +42,30 @@ module CocinaDisplay
|
|
|
33
42
|
def type_label
|
|
34
43
|
(I18n.t("cocina_display.field_label.form.genre") if stanford_self_deposit?) || super
|
|
35
44
|
end
|
|
45
|
+
|
|
46
|
+
# The primary type, if this is a structured self-deposit resource type.
|
|
47
|
+
# @return [String, nil]
|
|
48
|
+
def primary_type
|
|
49
|
+
type_components["type"].first
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# The subtypes, if this is a structured self-deposit resource type.
|
|
53
|
+
# @return [Array<String>]
|
|
54
|
+
def subtypes
|
|
55
|
+
type_components["subtype"] || []
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# A hash containing the destructured resource type and subtypes, if any.
|
|
59
|
+
# @return [Hash<String, Array<String>>]
|
|
60
|
+
# @see https://github.com/sul-dlss/cocina-models/blob/main/docs/description_types.md#form-part-types-for-structured-value
|
|
61
|
+
# @note Only used by self-deposit resource types.
|
|
62
|
+
def type_components
|
|
63
|
+
Utils.flatten_nested_values(cocina).each_with_object({}) do |node, hash|
|
|
64
|
+
type = node["type"]
|
|
65
|
+
hash[type] ||= []
|
|
66
|
+
hash[type] << node["value"]
|
|
67
|
+
end.compact_blank
|
|
68
|
+
end
|
|
36
69
|
end
|
|
37
70
|
end
|
|
38
71
|
end
|
|
@@ -38,7 +38,7 @@ module CocinaDisplay
|
|
|
38
38
|
# @return [Coordinates, nil]
|
|
39
39
|
def parse(value)
|
|
40
40
|
# Remove all whitespace for easier matching/parsing
|
|
41
|
-
match_str = value.gsub(
|
|
41
|
+
match_str = value.gsub(/\s+/, "")
|
|
42
42
|
|
|
43
43
|
# Try each parser in order until one matches; bail out if none do
|
|
44
44
|
parser_class = [
|
|
@@ -117,10 +117,10 @@ module CocinaDisplay
|
|
|
117
117
|
# Format using the Well-Known Text (WKT) representation.
|
|
118
118
|
# @note Limits decimals to 6 places.
|
|
119
119
|
# @see https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry
|
|
120
|
-
# @example "POINT(
|
|
120
|
+
# @example "POINT(-118.2437 34.0522)"
|
|
121
121
|
# @return [String]
|
|
122
122
|
def as_wkt
|
|
123
|
-
"POINT(%.6f %.6f)" % [point.
|
|
123
|
+
"POINT(%.6f %.6f)" % [point.lng, point.lat]
|
|
124
124
|
end
|
|
125
125
|
|
|
126
126
|
# Format using the CQL ENVELOPE representation.
|
|
@@ -310,12 +310,14 @@ module CocinaDisplay
|
|
|
310
310
|
# Parse for decimal degree points, like "41.891797, 12.486419".
|
|
311
311
|
class DecimalPointParser < PointParser
|
|
312
312
|
include DecimalParser
|
|
313
|
-
|
|
313
|
+
|
|
314
|
+
PATTERN = /(?<lat>[0-9.EW+-]+),(?<lng>[0-9.NS+-]+)/
|
|
314
315
|
end
|
|
315
316
|
|
|
316
317
|
# Parser for DMS-format points, like "N34°03′08″ W118°14′37″".
|
|
317
318
|
class DMSPointParser < PointParser
|
|
318
319
|
include DMSParser
|
|
320
|
+
|
|
319
321
|
PATTERN = /(?<lat>[^EW]+)(?<lng>[^NS]+)/
|
|
320
322
|
end
|
|
321
323
|
|
|
@@ -324,6 +326,7 @@ module CocinaDisplay
|
|
|
324
326
|
# @see https://www.oclc.org/bibformats/en/2xx/255.html#subfieldc
|
|
325
327
|
class DMSBoundingBoxParser < BoundingBoxParser
|
|
326
328
|
include DMSParser
|
|
329
|
+
|
|
327
330
|
PATTERN = /(?<min_lng>.+?)-+(?<max_lng>.+)\/(?<max_lat>.+?)-+(?<min_lat>.+)/
|
|
328
331
|
end
|
|
329
332
|
|
|
@@ -331,6 +334,7 @@ module CocinaDisplay
|
|
|
331
334
|
# @example W 126.04--W 052.03/N 050.37--N 006.8
|
|
332
335
|
class DecimalBoundingBoxParser < BoundingBoxParser
|
|
333
336
|
include DecimalParser
|
|
337
|
+
|
|
334
338
|
PATTERN = /(?<min_lng>[0-9.EW]+?)-+(?<max_lng>[0-9.EW]+)\/(?<max_lat>[0-9.NS]+?)-+(?<min_lat>[0-9.NS]+)/
|
|
335
339
|
end
|
|
336
340
|
|
|
@@ -8,7 +8,10 @@ module CocinaDisplay
|
|
|
8
8
|
attr_reader :cocina_doc
|
|
9
9
|
|
|
10
10
|
# Initialize a record with a Cocina document hash.
|
|
11
|
-
# @param cocina_doc [Hash]
|
|
11
|
+
# @param cocina_doc [Hash<String, Object>] The Cocina document hash
|
|
12
|
+
# @example Initialize a record with a Cocina document
|
|
13
|
+
# cocina = Cocina.find(id)
|
|
14
|
+
# record = CocinaDisplay::CocinaDisplay.new(cocina.as_json)
|
|
12
15
|
def initialize(cocina_doc)
|
|
13
16
|
@cocina_doc = cocina_doc
|
|
14
17
|
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
module CocinaDisplay
|
|
2
|
+
module Structural
|
|
3
|
+
# Represents a single file in a Cocina object.
|
|
4
|
+
class File
|
|
5
|
+
# Underlying hash parsed from Cocina JSON.
|
|
6
|
+
attr_reader :cocina
|
|
7
|
+
|
|
8
|
+
# URL to Stacks environment that will serve this file.
|
|
9
|
+
attr_reader :base_url
|
|
10
|
+
|
|
11
|
+
# Initialize the File with Cocina file data.
|
|
12
|
+
# @param cocina [Hash] Cocina structured data for a single file
|
|
13
|
+
# @param druid [String, nil] DRUID of the object this file belongs to
|
|
14
|
+
# @note Staging objects can't infer their DRUID and need it passed in explicitly.
|
|
15
|
+
def initialize(cocina, base_url: "https://stacks.stanford.edu", druid: nil)
|
|
16
|
+
@cocina = cocina
|
|
17
|
+
@base_url = base_url
|
|
18
|
+
@druid = druid
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# The name of the file on disk, including file extension.
|
|
22
|
+
# @return [String, nil]
|
|
23
|
+
# @example "bc798xr9549_30C_Kalsang_Yulgial_thumb.jp2"
|
|
24
|
+
def filename
|
|
25
|
+
cocina["filename"]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# The MIME type of the file.
|
|
29
|
+
# @return [String, nil]
|
|
30
|
+
# @example "image/jp2"
|
|
31
|
+
def mime_type
|
|
32
|
+
cocina["hasMimeType"]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# The relation of the file to the object.
|
|
36
|
+
# @return [String, nil]
|
|
37
|
+
# @example "thumbnail"
|
|
38
|
+
def use
|
|
39
|
+
cocina["use"]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# The size in bytes of the file.
|
|
43
|
+
# @return [Integer, nil]
|
|
44
|
+
# @example 204800
|
|
45
|
+
def size
|
|
46
|
+
cocina["size"]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# True if this file was marked as a thumbnail and has nonzero dimensions.
|
|
50
|
+
# @return [Boolean]
|
|
51
|
+
def thumbnail?
|
|
52
|
+
use == "thumbnail" && nonzero_dimensions?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# True if this file is a JP2 image and has nonzero dimensions.
|
|
56
|
+
# @return [Boolean]
|
|
57
|
+
def jp2_image?
|
|
58
|
+
mime_type == "image/jp2" && nonzero_dimensions?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# True if file is an image with nonzero height and width.
|
|
62
|
+
# @return [Boolean]
|
|
63
|
+
def nonzero_dimensions?
|
|
64
|
+
height&.positive? && width&.positive?
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# The height of the image in pixels, if applicable.
|
|
68
|
+
# @return [Integer, nil]
|
|
69
|
+
def height
|
|
70
|
+
cocina.dig("presentation", "height").to_i
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# The width of the image in pixels, if applicable.
|
|
74
|
+
# @return [Integer, nil]
|
|
75
|
+
def width
|
|
76
|
+
cocina.dig("presentation", "width").to_i
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Generate a IIIF image URL for this file.
|
|
80
|
+
# @param region [String] Desired region of the image (e.g., "full", "square", "x,y,w,h", "pct:x,y,w,h").
|
|
81
|
+
# @param width [String] Desired width of the image in pixels (use "!" prefix to preserve aspect ratio).
|
|
82
|
+
# @param height [String] Desired height of the image in pixels.
|
|
83
|
+
# @return [String, nil]
|
|
84
|
+
# @example "https://stacks.stanford.edu/image/iiif/ts786ny5936%2FPC0170_s1_E_0204.jp2/full/!400,400/0/default.jpg"
|
|
85
|
+
def iiif_url(region: "full", width: "!400", height: "400")
|
|
86
|
+
return unless iiif_id.present?
|
|
87
|
+
|
|
88
|
+
"#{base_url}/image/iiif/#{iiif_id}/#{region}/#{width},#{height}/0/default.jpg"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# For images served over IIIF, we use the encoded file ID minus the extension.
|
|
92
|
+
# @return [String, nil]
|
|
93
|
+
# @example "ts786ny5936%2FPC0170_s1_E_0204"
|
|
94
|
+
def iiif_id
|
|
95
|
+
ERB::Util.url_encode(file_id.delete_suffix(".jp2")) if file_id.present? && jp2_image?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Generate a download URL for this file from stacks.
|
|
99
|
+
# @return [String, nil]
|
|
100
|
+
def download_url
|
|
101
|
+
return unless file_id.present?
|
|
102
|
+
|
|
103
|
+
"#{base_url}/file/druid:#{file_id}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
# External identifier for the file, minus the URL prefix.
|
|
109
|
+
# @return [String, nil]
|
|
110
|
+
# @note Staging and production formats differ.
|
|
111
|
+
# @example production
|
|
112
|
+
# "fn851zf9475-fn851zf9475_1/fn851zf9475_00_0001.jp2"
|
|
113
|
+
# @example staging
|
|
114
|
+
# "ddbd323d-0dd9-4f14-ba72-336c2bccfb29"
|
|
115
|
+
def external_id
|
|
116
|
+
cocina["externalIdentifier"]&.delete_prefix("https://cocina.sul.stanford.edu/file/")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# The DRUID of the object this file belongs to.
|
|
120
|
+
# @note Staging objects can't infer this from the externalIdentifier.
|
|
121
|
+
# @return [String, nil]
|
|
122
|
+
def druid
|
|
123
|
+
@druid || external_id.split("-").first if external_id.present?
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Combination of the DRUID and filename to uniquely identify the file.
|
|
127
|
+
# @return [String, nil]
|
|
128
|
+
# @example "ts786ny5936/PC0170_s1_E_0204.jp2"
|
|
129
|
+
def file_id
|
|
130
|
+
"#{druid}/#{filename}" if druid.present? && filename.present?
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module CocinaDisplay
|
|
2
|
+
module Structural
|
|
3
|
+
# Represents a set of files in a Cocina object.
|
|
4
|
+
class FileSet
|
|
5
|
+
# Underlying hash parsed from Cocina JSON.
|
|
6
|
+
attr_reader :cocina
|
|
7
|
+
|
|
8
|
+
# URL to Stacks environment that will serve this fileset.
|
|
9
|
+
attr_reader :base_url
|
|
10
|
+
|
|
11
|
+
# Initialize the FileSet with Cocina structural data.
|
|
12
|
+
# @param cocina [Hash] Cocina structured data for a single FileSet
|
|
13
|
+
# @param base_url [String] URL to Stacks environment that will serve this fileset
|
|
14
|
+
# @param druid [String, nil] DRUID of the object this fileset belongs to
|
|
15
|
+
def initialize(cocina, base_url: "https://stacks.stanford.edu", druid: nil)
|
|
16
|
+
@cocina = cocina
|
|
17
|
+
@base_url = base_url
|
|
18
|
+
@druid = druid
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# The declared type of the FileSet, like "image" or "document".
|
|
22
|
+
# @note This can differ from the contained file types.
|
|
23
|
+
# @return [String, nil]
|
|
24
|
+
def type
|
|
25
|
+
cocina["type"]&.delete_prefix("https://cocina.sul.stanford.edu/models/resources/")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# All files contained in this FileSet.
|
|
29
|
+
# @return [Array<CocinaDisplay::Structural::File>]
|
|
30
|
+
def files
|
|
31
|
+
@files ||= Array(cocina.dig("structural", "contains")).map do |file|
|
|
32
|
+
CocinaDisplay::Structural::File.new(file, base_url: base_url, druid: druid)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
# DRUID of the object this fileset belongs to.
|
|
39
|
+
# @note Inferred from the start of the externalIdentifier.
|
|
40
|
+
# @return [String, nil]
|
|
41
|
+
def druid
|
|
42
|
+
@druid || external_id[/^[a-z]{2}\d{3}[a-z]{2}\d{4}/] if external_id.present?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# External identifier for the fileset, minus the URL prefix.
|
|
46
|
+
# @return [String, nil]
|
|
47
|
+
# @note Staging and production formats differ.
|
|
48
|
+
# @example production
|
|
49
|
+
# "bk264hq9320-bk264hq9320_3"
|
|
50
|
+
# @example staging
|
|
51
|
+
# "bh114dk3076_4"
|
|
52
|
+
def external_id
|
|
53
|
+
cocina["externalIdentifier"]&.delete_prefix("https://cocina.sul.stanford.edu/fileSet/")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/cocina_display/title.rb
CHANGED
|
@@ -109,7 +109,7 @@ module CocinaDisplay
|
|
|
109
109
|
# Generate the display title by stripping trailing punctuation from the full title.
|
|
110
110
|
# @return [String, nil]
|
|
111
111
|
def display_title_str
|
|
112
|
-
full_title_str&.sub(/[
|
|
112
|
+
full_title_str&.sub(/[.,;:\/\\]+\z/, "")
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
# The main title and subtitle, joined together with a colon.
|
data/lib/cocina_display/utils.rb
CHANGED
|
@@ -13,12 +13,12 @@ module CocinaDisplay
|
|
|
13
13
|
return compacted_values.first if compacted_values.one?
|
|
14
14
|
|
|
15
15
|
compacted_values.reduce(+"") do |result, value|
|
|
16
|
-
result << if value.end_with?(delimiter.strip)
|
|
16
|
+
result << if value.end_with?(delimiter.strip) || value.start_with?(delimiter.strip)
|
|
17
17
|
value + " "
|
|
18
18
|
else
|
|
19
19
|
value + delimiter
|
|
20
20
|
end
|
|
21
|
-
end.delete_suffix(delimiter)
|
|
21
|
+
end.delete_prefix(delimiter).delete_suffix(delimiter).strip
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
# Recursively flatten structured, and grouped values in Cocina metadata.
|
data/lib/cocina_display.rb
CHANGED
|
@@ -6,8 +6,7 @@ require "janeway"
|
|
|
6
6
|
require "json"
|
|
7
7
|
require "net/http"
|
|
8
8
|
require "active_support"
|
|
9
|
-
require "active_support/core_ext
|
|
10
|
-
require "active_support/core_ext/hash/conversions"
|
|
9
|
+
require "active_support/core_ext"
|
|
11
10
|
require "geo/coord"
|
|
12
11
|
require "edtf"
|
|
13
12
|
require "i18n"
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cocina_display
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.8.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nick Budak
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 2026-02-13 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: janeway-jsonpath
|
|
@@ -268,6 +268,8 @@ files:
|
|
|
268
268
|
- lib/cocina_display/license.rb
|
|
269
269
|
- lib/cocina_display/note.rb
|
|
270
270
|
- lib/cocina_display/related_resource.rb
|
|
271
|
+
- lib/cocina_display/structural/file.rb
|
|
272
|
+
- lib/cocina_display/structural/file_set.rb
|
|
271
273
|
- lib/cocina_display/subjects/subject.rb
|
|
272
274
|
- lib/cocina_display/subjects/subject_value.rb
|
|
273
275
|
- lib/cocina_display/title.rb
|
|
@@ -296,7 +298,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
296
298
|
- !ruby/object:Gem::Version
|
|
297
299
|
version: '0'
|
|
298
300
|
requirements: []
|
|
299
|
-
rubygems_version:
|
|
301
|
+
rubygems_version: 3.6.2
|
|
300
302
|
specification_version: 4
|
|
301
303
|
summary: Helpers for rendering Cocina metadata
|
|
302
304
|
test_files: []
|