neu-mods 0.5.0 → 0.7.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.
- checksums.yaml +4 -4
- data/.version +1 -1
- data/README.md +33 -6
- data/lib/neu/mods/data/iso639-2.txt +487 -0
- data/lib/neu/mods/language_codes.rb +53 -0
- data/lib/neu/mods/projection.rb +272 -54
- data/lib/neu/mods/selectors.rb +20 -4
- data/lib/neu-mods.rb +7 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: afd81033ef60fdd497880f538d3730d1518a37b362307028a165a1f93105bdd2
|
|
4
|
+
data.tar.gz: 3f6e1aa77c231d6622dceb4d4ae66d500329776659bf4feb2cb4ea8ded455932
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6b643273728275f712a266a33a64cf34bbe078fa405ca29ffd59af52f0e9ee7c9102f3867f6d4129bcbb5b050d28c9a00995190ec5568fea6761ee1af87254c4
|
|
7
|
+
data.tar.gz: b86248e24362d8e1436c47c4a0decf5254207ad587791f8fb0434ac7560e4d7a79e3e429464d89ad50dcc92d208e63ad456235a0c94a6c328b7b6efc0951d22f
|
data/.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.7.0
|
data/README.md
CHANGED
|
@@ -31,15 +31,29 @@ doc.plain_title # => "What's New - How We Respond to Disaster, Episode 1"
|
|
|
31
31
|
doc.title_parts # => { non_sort:, subtitle:, title:, part_name:, part_number: }
|
|
32
32
|
# byte-faithful -- the edit forms pre-fill from these
|
|
33
33
|
doc.abstract # => normalized, paragraph-joined String
|
|
34
|
+
doc.languages # => ["English"] a code-only <languageTerm>eng</> included
|
|
34
35
|
doc.topical_subjects # => ["Civil society", ...] (every <topic>, for the access copy)
|
|
35
36
|
doc.keywords # => [...] (only the editable attribute-free keyword subjects)
|
|
36
37
|
doc.date_created_with_precision
|
|
37
38
|
# => [DateTime, "year"|"month"|"day"] w3cdtf YYYY, YYYY-MM
|
|
38
39
|
# and YYYY-MM-DD all parse; the precision says which shape
|
|
39
40
|
# the record declared, so display cannot invent a month or
|
|
40
|
-
# a day the record never claimed
|
|
41
|
+
# a day the record never claimed. Same for
|
|
42
|
+
# date_issued_with_precision and
|
|
43
|
+
# copyright_date_with_precision.
|
|
44
|
+
doc.notes # => [{ type: "funding", value: "..." }, ...]
|
|
45
|
+
doc.related_items # => [{ type: "otherFormat", title: "..." }, ...]
|
|
46
|
+
# every relatedItem that is not a series or a host
|
|
47
|
+
doc.location # => [{ physical_location:, shelf_location:, url: }, ...]
|
|
48
|
+
doc.map_data # => [{ scale:, projection:, coordinates: }, ...]
|
|
41
49
|
doc.to_h # => full projection, keyed to Atlas's Metadata::MODS attributes
|
|
42
50
|
|
|
51
|
+
# The field registry -- the single declaration of what this gem projects.
|
|
52
|
+
# name => :one or :many. to_h is derived from it, and a consumer builds its own
|
|
53
|
+
# schema from it rather than re-listing the field set by hand. Cardinality
|
|
54
|
+
# follows what MODS marks repeatable, so a field can never silently truncate.
|
|
55
|
+
NEU::MODS::FIELDS # => { main_title: :one, names: :many, ... }
|
|
56
|
+
|
|
43
57
|
# Pure title composition (no document needed) — for callers that already hold
|
|
44
58
|
# the parts (e.g. Atlas's access-copy model) and must not re-parse XML on read.
|
|
45
59
|
NEU::MODS.compose_title(non_sort: "", title: "What's New",
|
|
@@ -99,11 +113,24 @@ intentional notes:
|
|
|
99
113
|
its quirks* (e.g. multiple `given` nameParts concatenate with no separator),
|
|
100
114
|
to preserve existing Solr/display output. Cleanups are a deliberate future
|
|
101
115
|
contract change, not a silent one.
|
|
102
|
-
- **
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
116
|
+
- **Languages are translated; roles are not.** Both read the `type="text"` term
|
|
117
|
+
first. A code-only `languageTerm` is then translated through the vendored ISO
|
|
118
|
+
639 registry (`lib/neu/mods/data/iso639-2.txt`, from the Library of Congress),
|
|
119
|
+
so `eng` projects `English`. That happens here rather than in a consumer's
|
|
120
|
+
display layer because otherwise Solr indexes `eng` while the page shows
|
|
121
|
+
`English`, and the language facet reads in codes.
|
|
122
|
+
A code-only `roleTerm` stays raw. A MARC relator is a display *label*, and the
|
|
123
|
+
label vocabulary belongs to the consumer — Cerberus's edit form and Atlas's
|
|
124
|
+
display word the same role differently. An unrecognised language code also
|
|
125
|
+
stays raw, since the record still said something.
|
|
126
|
+
- **`description` is not projected.** MODS has no element of that name, and the
|
|
127
|
+
two candidates — an `abstract` variant and `physicalDescription/note` —
|
|
128
|
+
describe different things. Projecting a guess would put wrong data in the
|
|
129
|
+
field rather than leave an empty one, so it waits on a decision.
|
|
130
|
+
- **A date range is not projected.** `dateCreated`, `dateIssued` and
|
|
131
|
+
`copyrightDate` each project one value with its precision. A record that
|
|
132
|
+
ranges a date uses `point="start"` / `point="end"`, which needs a shape of its
|
|
133
|
+
own rather than a second array.
|
|
107
134
|
|
|
108
135
|
## Source convention
|
|
109
136
|
|
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
aar||aa|Afar|afar
|
|
2
|
+
abk||ab|Abkhazian|abkhaze
|
|
3
|
+
ace|||Achinese|aceh
|
|
4
|
+
ach|||Acoli|acoli
|
|
5
|
+
ada|||Adangme|adangme
|
|
6
|
+
ady|||Adyghe; Adygei|adyghé
|
|
7
|
+
afa|||Afro-Asiatic languages|afro-asiatiques, langues
|
|
8
|
+
afh|||Afrihili|afrihili
|
|
9
|
+
afr||af|Afrikaans|afrikaans
|
|
10
|
+
ain|||Ainu|aïnou
|
|
11
|
+
aka||ak|Akan|akan
|
|
12
|
+
akk|||Akkadian|akkadien
|
|
13
|
+
alb|sqi|sq|Albanian|albanais
|
|
14
|
+
ale|||Aleut|aléoute
|
|
15
|
+
alg|||Algonquian languages|algonquines, langues
|
|
16
|
+
alt|||Southern Altai|altai du Sud
|
|
17
|
+
amh||am|Amharic|amharique
|
|
18
|
+
ang|||English, Old (ca.450-1100)|anglo-saxon (ca.450-1100)
|
|
19
|
+
anp|||Angika|angika
|
|
20
|
+
apa|||Apache languages|apaches, langues
|
|
21
|
+
ara||ar|Arabic|arabe
|
|
22
|
+
arc|||Official Aramaic (700-300 BCE); Imperial Aramaic (700-300 BCE)|araméen d'empire (700-300 BCE)
|
|
23
|
+
arg||an|Aragonese|aragonais
|
|
24
|
+
arm|hye|hy|Armenian|arménien
|
|
25
|
+
arn|||Mapudungun; Mapuche|mapudungun; mapuche; mapuce
|
|
26
|
+
arp|||Arapaho|arapaho
|
|
27
|
+
art|||Artificial languages|artificielles, langues
|
|
28
|
+
arw|||Arawak|arawak
|
|
29
|
+
asm||as|Assamese|assamais
|
|
30
|
+
ast|||Asturian; Bable; Leonese; Asturleonese|asturien; bable; léonais; asturoléonais
|
|
31
|
+
ath|||Athapascan languages|athapascanes, langues
|
|
32
|
+
aus|||Australian languages|australiennes, langues
|
|
33
|
+
ava||av|Avaric|avar
|
|
34
|
+
ave||ae|Avestan|avestique
|
|
35
|
+
awa|||Awadhi|awadhi
|
|
36
|
+
aym||ay|Aymara|aymara
|
|
37
|
+
aze||az|Azerbaijani|azéri
|
|
38
|
+
bad|||Banda languages|banda, langues
|
|
39
|
+
bai|||Bamileke languages|bamiléké, langues
|
|
40
|
+
bak||ba|Bashkir|bachkir
|
|
41
|
+
bal|||Baluchi|baloutchi
|
|
42
|
+
bam||bm|Bambara|bambara
|
|
43
|
+
ban|||Balinese|balinais
|
|
44
|
+
baq|eus|eu|Basque|basque
|
|
45
|
+
bas|||Basa|basa
|
|
46
|
+
bat|||Baltic languages|baltes, langues
|
|
47
|
+
bej|||Beja; Bedawiyet|bedja
|
|
48
|
+
bel||be|Belarusian|biélorusse
|
|
49
|
+
bem|||Bemba|bemba
|
|
50
|
+
ben||bn|Bengali|bengali
|
|
51
|
+
ber|||Berber languages|berbères, langues
|
|
52
|
+
bho|||Bhojpuri|bhojpuri
|
|
53
|
+
bih|||Bihari languages|langues biharis
|
|
54
|
+
bik|||Bikol|bikol
|
|
55
|
+
bin|||Bini; Edo|bini; edo
|
|
56
|
+
bis||bi|Bislama|bichlamar
|
|
57
|
+
bla|||Siksika|blackfoot
|
|
58
|
+
bnt|||Bantu languages|bantou, langues
|
|
59
|
+
bos||bs|Bosnian|bosniaque
|
|
60
|
+
bra|||Braj|braj
|
|
61
|
+
bre||br|Breton|breton
|
|
62
|
+
btk|||Batak languages|batak, langues
|
|
63
|
+
bua|||Buriat|bouriate
|
|
64
|
+
bug|||Buginese|bugi
|
|
65
|
+
bul||bg|Bulgarian|bulgare
|
|
66
|
+
bur|mya|my|Burmese|birman
|
|
67
|
+
byn|||Blin; Bilin|blin; bilen
|
|
68
|
+
cad|||Caddo|caddo
|
|
69
|
+
cai|||Central American Indian languages|amérindiennes de L'Amérique centrale, langues
|
|
70
|
+
car|||Galibi Carib|karib; galibi; carib
|
|
71
|
+
cat||ca|Catalan; Valencian|catalan; valencien
|
|
72
|
+
cau|||Caucasian languages|caucasiennes, langues
|
|
73
|
+
ceb|||Cebuano|cebuano
|
|
74
|
+
cel|||Celtic languages|celtiques, langues; celtes, langues
|
|
75
|
+
cha||ch|Chamorro|chamorro
|
|
76
|
+
chb|||Chibcha|chibcha
|
|
77
|
+
che||ce|Chechen|tchétchène
|
|
78
|
+
chg|||Chagatai|djaghataï
|
|
79
|
+
chi|zho|zh|Chinese|chinois
|
|
80
|
+
chk|||Chuukese|chuuk
|
|
81
|
+
chm|||Mari|mari
|
|
82
|
+
chn|||Chinook jargon|chinook, jargon
|
|
83
|
+
cho|||Choctaw|choctaw
|
|
84
|
+
chp|||Chipewyan; Dene Suline|chipewyan
|
|
85
|
+
chr|||Cherokee|cherokee
|
|
86
|
+
chu||cu|Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic|slavon d'église; vieux slave; slavon liturgique; vieux bulgare
|
|
87
|
+
chv||cv|Chuvash|tchouvache
|
|
88
|
+
chy|||Cheyenne|cheyenne
|
|
89
|
+
cmc|||Chamic languages|chames, langues
|
|
90
|
+
cnr|||Montenegrin|monténégrin
|
|
91
|
+
cop|||Coptic|copte
|
|
92
|
+
cor||kw|Cornish|cornique
|
|
93
|
+
cos||co|Corsican|corse
|
|
94
|
+
cpe|||Creoles and pidgins, English based|créoles et pidgins basés sur l'anglais
|
|
95
|
+
cpf|||Creoles and pidgins, French-based|créoles et pidgins basés sur le français
|
|
96
|
+
cpp|||Creoles and pidgins, Portuguese-based|créoles et pidgins basés sur le portugais
|
|
97
|
+
cre||cr|Cree|cree
|
|
98
|
+
crh|||Crimean Tatar; Crimean Turkish|tatar de Crimé
|
|
99
|
+
crp|||Creoles and pidgins|créoles et pidgins
|
|
100
|
+
csb|||Kashubian|kachoube
|
|
101
|
+
cus|||Cushitic languages|couchitiques, langues
|
|
102
|
+
cze|ces|cs|Czech|tchèque
|
|
103
|
+
dak|||Dakota|dakota
|
|
104
|
+
dan||da|Danish|danois
|
|
105
|
+
dar|||Dargwa|dargwa
|
|
106
|
+
day|||Land Dayak languages|dayak, langues
|
|
107
|
+
del|||Delaware|delaware
|
|
108
|
+
den|||Slave (Athapascan)|esclave (athapascan)
|
|
109
|
+
dgr|||Tlicho; Dogrib|tlicho; dogrib
|
|
110
|
+
din|||Dinka|dinka
|
|
111
|
+
div||dv|Divehi; Dhivehi; Maldivian|maldivien
|
|
112
|
+
doi|||Dogri|dogri
|
|
113
|
+
dra|||Dravidian languages|dravidiennes, langues
|
|
114
|
+
dsb|||Lower Sorbian|bas-sorabe
|
|
115
|
+
dua|||Duala|douala
|
|
116
|
+
dum|||Dutch, Middle (ca.1050-1350)|néerlandais moyen (ca. 1050-1350)
|
|
117
|
+
dut|nld|nl|Dutch; Flemish|néerlandais; flamand
|
|
118
|
+
dyu|||Dyula|dioula
|
|
119
|
+
dzo||dz|Dzongkha|dzongkha
|
|
120
|
+
efi|||Efik|efik
|
|
121
|
+
egy|||Egyptian (Ancient)|égyptien
|
|
122
|
+
eka|||Ekajuk|ekajuk
|
|
123
|
+
elx|||Elamite|élamite
|
|
124
|
+
eng||en|English|anglais
|
|
125
|
+
enm|||English, Middle (1100-1500)|anglais moyen (1100-1500)
|
|
126
|
+
epo||eo|Esperanto|espéranto
|
|
127
|
+
est||et|Estonian|estonien
|
|
128
|
+
ewe||ee|Ewe|éwé
|
|
129
|
+
ewo|||Ewondo|éwondo
|
|
130
|
+
fan|||Fang|fang
|
|
131
|
+
fao||fo|Faroese|féroïen
|
|
132
|
+
fat|||Fanti|fanti
|
|
133
|
+
fij||fj|Fijian|fidjien
|
|
134
|
+
fil|||Filipino; Pilipino|filipino; pilipino
|
|
135
|
+
fin||fi|Finnish|finnois
|
|
136
|
+
fiu|||Finno-Ugrian languages|finno-ougriennes, langues
|
|
137
|
+
fon|||Fon|fon
|
|
138
|
+
fre|fra|fr|French|français
|
|
139
|
+
frm|||French, Middle (ca.1400-1600)|français moyen (1400-1600)
|
|
140
|
+
fro|||French, Old (842-ca.1400)|français ancien (842-ca.1400)
|
|
141
|
+
frr|||Northern Frisian|frison septentrional
|
|
142
|
+
frs|||Eastern Frisian|frison oriental
|
|
143
|
+
fry||fy|Western Frisian|frison occidental
|
|
144
|
+
ful||ff|Fulah|peul
|
|
145
|
+
fur|||Friulian|frioulan
|
|
146
|
+
gaa|||Ga|ga
|
|
147
|
+
gay|||Gayo|gayo
|
|
148
|
+
gba|||Gbaya|gbaya
|
|
149
|
+
gem|||Germanic languages|germaniques, langues
|
|
150
|
+
geo|kat|ka|Georgian|géorgien
|
|
151
|
+
ger|deu|de|German|allemand
|
|
152
|
+
gez|||Geez|guèze
|
|
153
|
+
gil|||Gilbertese|kiribati
|
|
154
|
+
gla||gd|Gaelic; Scottish Gaelic|gaélique; gaélique écossais
|
|
155
|
+
gle||ga|Irish|irlandais
|
|
156
|
+
glg||gl|Galician|galicien
|
|
157
|
+
glv||gv|Manx|manx; mannois
|
|
158
|
+
gmh|||German, Middle High (ca.1050-1500)|allemand, moyen haut (ca. 1050-1500)
|
|
159
|
+
goh|||German, Old High (ca.750-1050)|allemand, vieux haut (ca. 750-1050)
|
|
160
|
+
gon|||Gondi|gond
|
|
161
|
+
gor|||Gorontalo|gorontalo
|
|
162
|
+
got|||Gothic|gothique
|
|
163
|
+
grb|||Grebo|grebo
|
|
164
|
+
grc|||Greek, Ancient (to 1453)|grec ancien (jusqu'à 1453)
|
|
165
|
+
gre|ell|el|Modern Greek (1453-)|grec moderne (1453-)
|
|
166
|
+
grn||gn|Guarani|guarani
|
|
167
|
+
gsw|||Swiss German; Alemannic; Alsatian|suisse alémanique; alémanique; alsacien
|
|
168
|
+
guj||gu|Gujarati|goudjrati
|
|
169
|
+
gwi|||Gwich'in|gwich'in
|
|
170
|
+
hai|||Haida|haida
|
|
171
|
+
hat||ht|Haitian; Haitian Creole|haïtien; créole haïtien
|
|
172
|
+
hau||ha|Hausa|haoussa
|
|
173
|
+
haw|||Hawaiian|hawaïen
|
|
174
|
+
heb||he|Hebrew|hébreu
|
|
175
|
+
her||hz|Herero|herero
|
|
176
|
+
hil|||Hiligaynon|hiligaynon
|
|
177
|
+
him|||Himachali languages; Western Pahari languages|langues himachalis; langues paharis occidentales
|
|
178
|
+
hin||hi|Hindi|hindi
|
|
179
|
+
hit|||Hittite|hittite
|
|
180
|
+
hmn|||Hmong; Mong|hmong
|
|
181
|
+
hmo||ho|Hiri Motu|hiri motu
|
|
182
|
+
hrv||hr|Croatian|croate
|
|
183
|
+
hsb|||Upper Sorbian|haut-sorabe
|
|
184
|
+
hun||hu|Hungarian|hongrois
|
|
185
|
+
hup|||Hupa|hupa
|
|
186
|
+
iba|||Iban|iban
|
|
187
|
+
ibo||ig|Igbo|igbo
|
|
188
|
+
ice|isl|is|Icelandic|islandais
|
|
189
|
+
ido||io|Ido|ido
|
|
190
|
+
iii||ii|Sichuan Yi; Nuosu|yi de Sichuan
|
|
191
|
+
ijo|||Ijo languages|ijo, langues
|
|
192
|
+
iku||iu|Inuktitut|inuktitut
|
|
193
|
+
ile||ie|Interlingue; Occidental|interlingue
|
|
194
|
+
ilo|||Iloko|ilocano
|
|
195
|
+
ina||ia|Interlingua (International Auxiliary Language Association)|interlingua (langue auxiliaire internationale)
|
|
196
|
+
inc|||Indic languages|indo-aryennes, langues
|
|
197
|
+
ind||id|Indonesian|indonésien
|
|
198
|
+
ine|||Indo-European languages|indo-européennes, langues
|
|
199
|
+
inh|||Ingush|ingouche
|
|
200
|
+
ipk||ik|Inupiaq|inupiaq
|
|
201
|
+
ira|||Iranian languages|iraniennes, langues
|
|
202
|
+
iro|||Iroquoian languages|iroquoises, langues
|
|
203
|
+
ita||it|Italian|italien
|
|
204
|
+
jav||jv|Javanese|javanais
|
|
205
|
+
jbo|||Lojban|lojban
|
|
206
|
+
jpn||ja|Japanese|japonais
|
|
207
|
+
jpr|||Judeo-Persian|judéo-persan
|
|
208
|
+
jrb|||Judeo-Arabic|judéo-arabe
|
|
209
|
+
kaa|||Kara-Kalpak|karakalpak
|
|
210
|
+
kab|||Kabyle|kabyle
|
|
211
|
+
kac|||Kachin; Jingpho|kachin; jingpho
|
|
212
|
+
kal||kl|Kalaallisut; Greenlandic|groenlandais
|
|
213
|
+
kam|||Kamba|kamba
|
|
214
|
+
kan||kn|Kannada|kannada
|
|
215
|
+
kar|||Karen languages|karen, langues
|
|
216
|
+
kas||ks|Kashmiri|kashmiri
|
|
217
|
+
kau||kr|Kanuri|kanouri
|
|
218
|
+
kaw|||Kawi|kawi
|
|
219
|
+
kaz||kk|Kazakh|kazakh
|
|
220
|
+
kbd|||Kabardian|kabardien
|
|
221
|
+
kha|||Khasi|khasi
|
|
222
|
+
khi|||Khoisan languages|khoïsan, langues
|
|
223
|
+
khm||km|Central Khmer|khmer central
|
|
224
|
+
kho|||Khotanese; Sakan|khotanais; sakan
|
|
225
|
+
kik||ki|Kikuyu; Gikuyu|kikuyu
|
|
226
|
+
kin||rw|Kinyarwanda|rwanda
|
|
227
|
+
kir||ky|Kirghiz; Kyrgyz|kirghiz
|
|
228
|
+
kmb|||Kimbundu|kimbundu
|
|
229
|
+
kok|||Konkani|konkani
|
|
230
|
+
kom||kv|Komi|kom
|
|
231
|
+
kon||kg|Kongo|kongo
|
|
232
|
+
kor||ko|Korean|coréen
|
|
233
|
+
kos|||Kosraean|kosrae
|
|
234
|
+
kpe|||Kpelle|kpellé
|
|
235
|
+
krc|||Karachay-Balkar|karatchai balkar
|
|
236
|
+
krl|||Karelian|carélien
|
|
237
|
+
kro|||Kru languages|krou, langues
|
|
238
|
+
kru|||Kurukh|kurukh
|
|
239
|
+
kua||kj|Kuanyama; Kwanyama|kuanyama; kwanyama
|
|
240
|
+
kum|||Kumyk|koumyk
|
|
241
|
+
kur||ku|Kurdish|kurde
|
|
242
|
+
kut|||Kutenai|kutenai
|
|
243
|
+
lad|||Ladino|judéo-espagnol
|
|
244
|
+
lah|||Lahnda|lahnda
|
|
245
|
+
lam|||Lamba|lamba
|
|
246
|
+
lao||lo|Lao|lao
|
|
247
|
+
lat||la|Latin|latin
|
|
248
|
+
lav||lv|Latvian|letton
|
|
249
|
+
lez|||Lezghian|lezghien
|
|
250
|
+
lim||li|Limburgan; Limburger; Limburgish|limbourgeois
|
|
251
|
+
lin||ln|Lingala|lingala
|
|
252
|
+
lit||lt|Lithuanian|lituanien
|
|
253
|
+
lol|||Mongo|mongo
|
|
254
|
+
loz|||Lozi|lozi
|
|
255
|
+
ltz||lb|Luxembourgish; Letzeburgesch|luxembourgeois
|
|
256
|
+
lua|||Luba-Lulua|luba-lulua
|
|
257
|
+
lub||lu|Luba-Katanga|luba-katanga
|
|
258
|
+
lug||lg|Ganda|ganda
|
|
259
|
+
lui|||Luiseno|luiseno
|
|
260
|
+
lun|||Lunda|lunda
|
|
261
|
+
luo|||Luo (Kenya and Tanzania)|luo (Kenya et Tanzanie)
|
|
262
|
+
lus|||Lushai|lushai
|
|
263
|
+
mac|mkd|mk|Macedonian|macédonien
|
|
264
|
+
mad|||Madurese|madourais
|
|
265
|
+
mag|||Magahi|magahi
|
|
266
|
+
mah||mh|Marshallese|marshall
|
|
267
|
+
mai|||Maithili|maithili
|
|
268
|
+
mak|||Makasar|makassar
|
|
269
|
+
mal||ml|Malayalam|malayalam
|
|
270
|
+
man|||Mandingo|mandingue
|
|
271
|
+
mao|mri|mi|Maori|maori
|
|
272
|
+
map|||Austronesian languages|austronésiennes, langues
|
|
273
|
+
mar||mr|Marathi|marathe
|
|
274
|
+
mas|||Masai|massaï
|
|
275
|
+
may|msa|ms|Malay|malais
|
|
276
|
+
mdf|||Moksha|moksa
|
|
277
|
+
mdr|||Mandar|mandar
|
|
278
|
+
men|||Mende|mendé
|
|
279
|
+
mga|||Irish, Middle (900-1200)|irlandais moyen (900-1200)
|
|
280
|
+
mic|||Mi'kmaq; Micmac|mi'kmaq; micmac
|
|
281
|
+
min|||Minangkabau|minangkabau
|
|
282
|
+
mis|||Uncoded languages|langues non codées
|
|
283
|
+
mkh|||Mon-Khmer languages|môn-khmer, langues
|
|
284
|
+
mlg||mg|Malagasy|malgache
|
|
285
|
+
mlt||mt|Maltese|maltais
|
|
286
|
+
mnc|||Manchu|mandchou
|
|
287
|
+
mni|||Manipuri|manipuri
|
|
288
|
+
mno|||Manobo languages|manobo, langues
|
|
289
|
+
moh|||Mohawk|mohawk
|
|
290
|
+
mon||mn|Mongolian|mongol
|
|
291
|
+
mos|||Mossi|moré
|
|
292
|
+
mul|||Multiple languages|multilingue
|
|
293
|
+
mun|||Munda languages|mounda, langues
|
|
294
|
+
mus|||Creek|muskogee
|
|
295
|
+
mwl|||Mirandese|mirandais
|
|
296
|
+
mwr|||Marwari|marvari
|
|
297
|
+
myn|||Mayan languages|maya, langues
|
|
298
|
+
myv|||Erzya|erza
|
|
299
|
+
nah|||Nahuatl languages|nahuatl, langues
|
|
300
|
+
nai|||North American Indian languages|nord-amérindiennes, langues
|
|
301
|
+
nap|||Neapolitan|napolitain
|
|
302
|
+
nau||na|Nauru|nauruan
|
|
303
|
+
nav||nv|Navajo; Navaho|navaho
|
|
304
|
+
nbl||nr|South Ndebele|ndébélé du Sud
|
|
305
|
+
nde||nd|North Ndebele|ndébélé du Nord
|
|
306
|
+
ndo||ng|Ndonga|ndonga
|
|
307
|
+
nds|||Low German; Low Saxon; German, Low; Saxon, Low|bas allemand; bas saxon; allemand, bas; saxon, bas
|
|
308
|
+
nep||ne|Nepali|népalais
|
|
309
|
+
new|||Nepal Bhasa; Newar; Newari|nepal bhasa; néwar; newari
|
|
310
|
+
nia|||Nias|nias
|
|
311
|
+
nic|||Niger-Kordofanian languages|nigéro-kordofaniennes, langues
|
|
312
|
+
niu|||Niuean|niué
|
|
313
|
+
nno||nn|Norwegian Nynorsk|norvégien nynorsk
|
|
314
|
+
nob||nb|Norwegian Bokmål|norvégien bokmål
|
|
315
|
+
nog|||Nogai|nogaï; nogay
|
|
316
|
+
non|||Norse, Old|norrois, vieux
|
|
317
|
+
nor||no|Norwegian|norvégien
|
|
318
|
+
nqo|||N'Ko|n'ko
|
|
319
|
+
nso|||Pedi; Sepedi; Northern Sotho|pedi; sepedi; sotho du Nord
|
|
320
|
+
nub|||Nubian languages|nubiennes, langues
|
|
321
|
+
nwc|||Classical Newari; Old Newari; Classical Nepal Bhasa|newari classique
|
|
322
|
+
nya||ny|Chichewa; Chewa; Nyanja|chichewa; chewa; nyanja
|
|
323
|
+
nym|||Nyamwezi|nyamwezi
|
|
324
|
+
nyn|||Nyankole|nyankolé
|
|
325
|
+
nyo|||Nyoro|nyoro
|
|
326
|
+
nzi|||Nzima|nzema
|
|
327
|
+
oci||oc|Occitan (post 1500)|occitan (après 1500)
|
|
328
|
+
oji||oj|Ojibwa|ojibwa
|
|
329
|
+
ori||or|Oriya|oriya
|
|
330
|
+
orm||om|Oromo|galla
|
|
331
|
+
osa|||Osage|osage
|
|
332
|
+
oss||os|Ossetian; Ossetic|ossète
|
|
333
|
+
ota|||Turkish, Ottoman (1500-1928)|turc ottoman (1500-1928)
|
|
334
|
+
oto|||Otomian languages|otomi, langues
|
|
335
|
+
paa|||Papuan languages|papoues, langues
|
|
336
|
+
pag|||Pangasinan|pangasinan
|
|
337
|
+
pal|||Pahlavi|pahlavi
|
|
338
|
+
pam|||Pampanga; Kapampangan|pampangan
|
|
339
|
+
pan||pa|Panjabi; Punjabi|pendjabi
|
|
340
|
+
pap|||Papiamento|papiamento
|
|
341
|
+
pau|||Palauan|palau
|
|
342
|
+
peo|||Persian, Old (ca.600-400 B.C.)|perse, vieux (ca. 600-400 av. J.-C.)
|
|
343
|
+
per|fas|fa|Persian|persan
|
|
344
|
+
phi|||Philippine languages|philippines, langues
|
|
345
|
+
phn|||Phoenician|phénicien
|
|
346
|
+
pli||pi|Pali|pali
|
|
347
|
+
pol||pl|Polish|polonais
|
|
348
|
+
pon|||Pohnpeian|pohnpei
|
|
349
|
+
por||pt|Portuguese|portugais
|
|
350
|
+
pra|||Prakrit languages|prâkrit, langues
|
|
351
|
+
pro|||Provençal, Old (to 1500); Occitan, Old (to 1500)|provençal ancien (jusqu'à 1500); occitan ancien (jusqu'à 1500)
|
|
352
|
+
pus||ps|Pushto; Pashto|pachto
|
|
353
|
+
qaa-qtz|||Reserved for local use|réservée à l'usage local
|
|
354
|
+
que||qu|Quechua|quechua
|
|
355
|
+
raj|||Rajasthani|rajasthani
|
|
356
|
+
rap|||Rapanui|rapanui
|
|
357
|
+
rar|||Rarotongan; Cook Islands Maori|rarotonga; maori des îles Cook
|
|
358
|
+
roa|||Romance languages|romanes, langues
|
|
359
|
+
roh||rm|Romansh|romanche
|
|
360
|
+
rom|||Romany|tsigane
|
|
361
|
+
rum|ron|ro|Romanian; Moldavian; Moldovan|roumain; moldave
|
|
362
|
+
run||rn|Rundi|rundi
|
|
363
|
+
rup|||Aromanian; Arumanian; Macedo-Romanian|aroumain; macédo-roumain
|
|
364
|
+
rus||ru|Russian|russe
|
|
365
|
+
sad|||Sandawe|sandawe
|
|
366
|
+
sag||sg|Sango|sango
|
|
367
|
+
sah|||Yakut|iakoute
|
|
368
|
+
sai|||South American Indian languages|sud-amérindiennes, langues
|
|
369
|
+
sal|||Salishan languages|salishennes, langues
|
|
370
|
+
sam|||Samaritan Aramaic|samaritain
|
|
371
|
+
san||sa|Sanskrit|sanskrit
|
|
372
|
+
sas|||Sasak|sasak
|
|
373
|
+
sat|||Santali|santal
|
|
374
|
+
scn|||Sicilian|sicilien
|
|
375
|
+
sco|||Scots|écossais
|
|
376
|
+
sel|||Selkup|selkoupe
|
|
377
|
+
sem|||Semitic languages|sémitiques, langues
|
|
378
|
+
sga|||Irish, Old (to 900)|irlandais ancien (jusqu'à 900)
|
|
379
|
+
sgn|||Sign Languages|langues des signes
|
|
380
|
+
shn|||Shan|chan
|
|
381
|
+
sid|||Sidamo|sidamo
|
|
382
|
+
sin||si|Sinhala; Sinhalese|singhalais
|
|
383
|
+
sio|||Siouan languages|sioux, langues
|
|
384
|
+
sit|||Sino-Tibetan languages|sino-tibétaines, langues
|
|
385
|
+
sla|||Slavic languages|slaves, langues
|
|
386
|
+
slo|slk|sk|Slovak|slovaque
|
|
387
|
+
slv||sl|Slovenian|slovène
|
|
388
|
+
sma|||Southern Sami|sami du Sud
|
|
389
|
+
sme||se|Northern Sami|sami du Nord
|
|
390
|
+
smi|||Sami languages|sames, langues
|
|
391
|
+
smj|||Lule Sami|sami de Lule
|
|
392
|
+
smn|||Inari Sami|sami d'Inari
|
|
393
|
+
smo||sm|Samoan|samoan
|
|
394
|
+
sms|||Skolt Sami|sami skolt
|
|
395
|
+
sna||sn|Shona|shona
|
|
396
|
+
snd||sd|Sindhi|sindhi
|
|
397
|
+
snk|||Soninke|soninké
|
|
398
|
+
sog|||Sogdian|sogdien
|
|
399
|
+
som||so|Somali|somali
|
|
400
|
+
son|||Songhai languages|songhai, langues
|
|
401
|
+
sot||st|Sotho, Southern|sotho du Sud
|
|
402
|
+
spa||es|Spanish; Castilian|espagnol; castillan
|
|
403
|
+
srd||sc|Sardinian|sarde
|
|
404
|
+
srn|||Sranan Tongo|sranan tongo
|
|
405
|
+
srp||sr|Serbian|serbe
|
|
406
|
+
srr|||Serer|sérère
|
|
407
|
+
ssa|||Nilo-Saharan languages|nilo-sahariennes, langues
|
|
408
|
+
ssw||ss|Swati|swati
|
|
409
|
+
suk|||Sukuma|sukuma
|
|
410
|
+
sun||su|Sundanese|soundanais
|
|
411
|
+
sus|||Susu|soussou
|
|
412
|
+
sux|||Sumerian|sumérien
|
|
413
|
+
swa||sw|Swahili|swahili
|
|
414
|
+
swe||sv|Swedish|suédois
|
|
415
|
+
syc|||Classical Syriac|syriaque classique
|
|
416
|
+
syr|||Syriac|syriaque
|
|
417
|
+
tah||ty|Tahitian|tahitien
|
|
418
|
+
tai|||Tai languages|tai, langues
|
|
419
|
+
tam||ta|Tamil|tamoul
|
|
420
|
+
tat||tt|Tatar|tatar
|
|
421
|
+
tel||te|Telugu|télougou
|
|
422
|
+
tem|||Timne|temne
|
|
423
|
+
ter|||Tereno|tereno
|
|
424
|
+
tet|||Tetum|tetum
|
|
425
|
+
tgk||tg|Tajik|tadjik
|
|
426
|
+
tgl||tl|Tagalog|tagalog
|
|
427
|
+
tha||th|Thai|thaï
|
|
428
|
+
tib|bod|bo|Tibetan|tibétain
|
|
429
|
+
tig|||Tigre|tigré
|
|
430
|
+
tir||ti|Tigrinya|tigrigna
|
|
431
|
+
tiv|||Tiv|tiv
|
|
432
|
+
tkl|||Tokelau|tokelau
|
|
433
|
+
tlh|||Klingon; tlhIngan-Hol|klingon
|
|
434
|
+
tli|||Tlingit|tlingit
|
|
435
|
+
tmh|||Tamashek|tamacheq
|
|
436
|
+
tog|||Tonga (Nyasa)|tonga (Nyasa)
|
|
437
|
+
ton||to|Tonga (Tonga Islands)|tongan (Îles Tonga)
|
|
438
|
+
tpi|||Tok Pisin|tok pisin
|
|
439
|
+
tsi|||Tsimshian|tsimshian
|
|
440
|
+
tsn||tn|Tswana|tswana
|
|
441
|
+
tso||ts|Tsonga|tsonga
|
|
442
|
+
tuk||tk|Turkmen|turkmène
|
|
443
|
+
tum|||Tumbuka|tumbuka
|
|
444
|
+
tup|||Tupi languages|tupi, langues
|
|
445
|
+
tur||tr|Turkish|turc
|
|
446
|
+
tut|||Altaic languages|altaïques, langues
|
|
447
|
+
tvl|||Tuvalu|tuvalu
|
|
448
|
+
twi||tw|Twi|twi
|
|
449
|
+
tyv|||Tuvinian|touva
|
|
450
|
+
udm|||Udmurt|oudmourte
|
|
451
|
+
uga|||Ugaritic|ougaritique
|
|
452
|
+
uig||ug|Uighur; Uyghur|ouïgour
|
|
453
|
+
ukr||uk|Ukrainian|ukrainien
|
|
454
|
+
umb|||Umbundu|umbundu
|
|
455
|
+
und|||Undetermined|indéterminée
|
|
456
|
+
urd||ur|Urdu|ourdou
|
|
457
|
+
uzb||uz|Uzbek|ouszbek
|
|
458
|
+
vai|||Vai|vaï
|
|
459
|
+
ven||ve|Venda|venda
|
|
460
|
+
vie||vi|Vietnamese|vietnamien
|
|
461
|
+
vol||vo|Volapük|volapük
|
|
462
|
+
vot|||Votic|vote
|
|
463
|
+
wak|||Wakashan languages|wakashanes, langues
|
|
464
|
+
wal|||Wolaitta; Wolaytta|wolaitta; wolaytta
|
|
465
|
+
war|||Waray|waray
|
|
466
|
+
was|||Washo|washo
|
|
467
|
+
wel|cym|cy|Welsh|gallois
|
|
468
|
+
wen|||Sorbian languages|sorabes, langues
|
|
469
|
+
wln||wa|Walloon|wallon
|
|
470
|
+
wol||wo|Wolof|wolof
|
|
471
|
+
xal|||Kalmyk; Oirat|kalmouk; oïrat
|
|
472
|
+
xho||xh|Xhosa|xhosa
|
|
473
|
+
yao|||Yao|yao
|
|
474
|
+
yap|||Yapese|yapois
|
|
475
|
+
yid||yi|Yiddish|yiddish
|
|
476
|
+
yor||yo|Yoruba|yoruba
|
|
477
|
+
ypk|||Yupik languages|yupik, langues
|
|
478
|
+
zap|||Zapotec|zapotèque
|
|
479
|
+
zbl|||Blissymbols; Blissymbolics; Bliss|symboles Bliss; Bliss
|
|
480
|
+
zen|||Zenaga|zenaga
|
|
481
|
+
zgh|||Standard Moroccan Tamazight|amazighe standard marocain
|
|
482
|
+
zha||za|Zhuang; Chuang|zhuang; chuang
|
|
483
|
+
znd|||Zande languages|zandé, langues
|
|
484
|
+
zul||zu|Zulu|zoulou
|
|
485
|
+
zun|||Zuni|zuni
|
|
486
|
+
zxx|||No linguistic content; Not applicable|pas de contenu linguistique; non applicable
|
|
487
|
+
zza|||Zaza; Dimili; Dimli; Kirdki; Kirmanjki; Zazaki|zaza; dimili; dimli; kirdki; kirmanjki; zazaki
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module NEU
|
|
4
|
+
module MODS
|
|
5
|
+
# ISO 639 code -> English language name, for projecting a code-only
|
|
6
|
+
# languageTerm as something a reader (and a Solr facet) can use.
|
|
7
|
+
#
|
|
8
|
+
# This is the one place a code is translated, on purpose. The alternative --
|
|
9
|
+
# mapping in each consumer's display layer -- leaves Solr indexing "eng"
|
|
10
|
+
# while the page shows "English", so the language facet reads in codes. One
|
|
11
|
+
# table, read once, keeps the projection, the display and the index agreed.
|
|
12
|
+
#
|
|
13
|
+
# Roles are deliberately NOT translated here; a MARC relator is a display
|
|
14
|
+
# label, and the label vocabulary belongs to the consumer (see README).
|
|
15
|
+
module LanguageCodes
|
|
16
|
+
# The Library of Congress ISO 639-2 registry, vendored byte-for-byte from
|
|
17
|
+
# https://www.loc.gov/standards/iso639-2/ISO-639-2_utf-8.txt so its
|
|
18
|
+
# provenance is a diff rather than a claim. Pipe-delimited:
|
|
19
|
+
# alpha3-bibliographic | alpha3-terminologic | alpha2 | English | French.
|
|
20
|
+
# It lives outside lib/*.rb because the English names carry non-ASCII and
|
|
21
|
+
# the source-purity spec holds the Ruby files to ASCII.
|
|
22
|
+
REGISTRY_PATH = File.expand_path("data/iso639-2.txt", __dir__)
|
|
23
|
+
|
|
24
|
+
# Every alpha-3 bibliographic, alpha-3 terminologic and alpha-2 code that
|
|
25
|
+
# the registry defines, each pointing at its English name. MODS records
|
|
26
|
+
# declare authority="iso639-2b" most often, but iso639-1 and the
|
|
27
|
+
# terminologic codes are equally valid, and a lookup table costs the same
|
|
28
|
+
# whether it holds one form or all three.
|
|
29
|
+
TERMS = File.readlines(REGISTRY_PATH, encoding: "bom|utf-8").each_with_object({}) do |line, terms|
|
|
30
|
+
bibliographic, terminologic, alpha2, english = line.chomp.split("|")
|
|
31
|
+
next if english.to_s.empty?
|
|
32
|
+
|
|
33
|
+
# The registry lists synonyms in preference order, so "Spanish;
|
|
34
|
+
# Castilian" becomes "Spanish". Rendering every synonym would make a
|
|
35
|
+
# facet value nobody would click.
|
|
36
|
+
name = english.split(";").first.strip
|
|
37
|
+
[bibliographic, terminologic, alpha2].each do |code|
|
|
38
|
+
terms[code] = name unless code.to_s.empty?
|
|
39
|
+
end
|
|
40
|
+
end.freeze
|
|
41
|
+
|
|
42
|
+
# The English name for a code, or the code itself when the registry does
|
|
43
|
+
# not define it. Never returns nil for a present value: an unrecognised
|
|
44
|
+
# code is still what the record says, and dropping it would lose data.
|
|
45
|
+
def self.term(code)
|
|
46
|
+
key = code.to_s.strip
|
|
47
|
+
return key if key.empty?
|
|
48
|
+
|
|
49
|
+
TERMS.fetch(key.downcase, key)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
data/lib/neu/mods/projection.rb
CHANGED
|
@@ -12,6 +12,8 @@ module NEU
|
|
|
12
12
|
# Empty-value conventions mirror Atlas: scalar fields are "" when absent
|
|
13
13
|
# (matching `.text.squish` on an empty node set), except `permanent_url` and
|
|
14
14
|
# `date_created`, which are nil when their node is absent. Arrays are [].
|
|
15
|
+
# Which fields are scalar and which are arrays is declared in FIELDS, not
|
|
16
|
+
# left to each method to decide.
|
|
15
17
|
module Projection
|
|
16
18
|
# --- Title ---------------------------------------------------------------
|
|
17
19
|
|
|
@@ -25,16 +27,19 @@ module NEU
|
|
|
25
27
|
# rewrite the curator's characters in the preservation XML on the next save.
|
|
26
28
|
# #access_title_parts is the normalised surface.
|
|
27
29
|
def title_parts
|
|
28
|
-
|
|
29
|
-
{
|
|
30
|
-
non_sort: child_text(ti, "mods:nonSort"),
|
|
31
|
-
subtitle: child_text(ti, "mods:subTitle"),
|
|
32
|
-
title: child_text(ti, "mods:title"),
|
|
33
|
-
part_name: child_text(ti, "mods:partName"),
|
|
34
|
-
part_number: child_text(ti, "mods:partNumber")
|
|
35
|
-
}
|
|
30
|
+
title_parts_of(primary_title_info)
|
|
36
31
|
end
|
|
37
32
|
|
|
33
|
+
# The variant titles, each composed and normalised like the main title.
|
|
34
|
+
# MODS repeats titleInfo, so a record may carry more than one of a type.
|
|
35
|
+
# These are what keeps the primary-title fallback's refusal to promote a
|
|
36
|
+
# variant from hiding anything: the variant still reaches a reader, under
|
|
37
|
+
# a label that says which kind of title it is.
|
|
38
|
+
def alternative_title = variant_titles("alternative")
|
|
39
|
+
def uniform_title = variant_titles("uniform")
|
|
40
|
+
def translated_title = variant_titles("translated")
|
|
41
|
+
def abbreviated_title = variant_titles("abbreviated")
|
|
42
|
+
|
|
38
43
|
# Composed display title (the former Atlas MODSDecoration#plain_title), driven
|
|
39
44
|
# off the scoped primary title.
|
|
40
45
|
def plain_title
|
|
@@ -86,10 +91,21 @@ module NEU
|
|
|
86
91
|
join_paragraphs(abstract_nodes)
|
|
87
92
|
end
|
|
88
93
|
|
|
94
|
+
# Every top-level accessCondition joined, regardless of @type. Retained
|
|
95
|
+
# because it is the only projection that carries an untyped or
|
|
96
|
+
# unrecognised accessCondition, which the two typed fields below cannot
|
|
97
|
+
# see -- a consumer that renders only those needs this as its fallback.
|
|
89
98
|
def access_condition
|
|
90
99
|
join_paragraphs(doc.xpath("/mods:mods/mods:accessCondition", NAMESPACE))
|
|
91
100
|
end
|
|
92
101
|
|
|
102
|
+
# The two @type values MODS defines, projected apart. Collapsing them into
|
|
103
|
+
# one value presented an access *restriction* to a reader as a *licence*,
|
|
104
|
+
# which is the one defect in this area that misinforms someone about their
|
|
105
|
+
# rights rather than merely hiding a field.
|
|
106
|
+
def use_and_reproduction = access_conditions_of_type("use and reproduction")
|
|
107
|
+
def restriction_on_access = access_conditions_of_type("restriction on access")
|
|
108
|
+
|
|
93
109
|
# --- Subjects ------------------------------------------------------------
|
|
94
110
|
|
|
95
111
|
# The editable free-text keyword set (Cerberus simple form): topics under the
|
|
@@ -104,6 +120,18 @@ module NEU
|
|
|
104
120
|
doc.xpath("/mods:mods/mods:subject/mods:topic", NAMESPACE).map { |t| clean(t.text) }
|
|
105
121
|
end
|
|
106
122
|
|
|
123
|
+
# The other subject axes. Cerberus's IPTC ingest writes subject/geographic
|
|
124
|
+
# from the IPTC City and State fields, so this one was also being written
|
|
125
|
+
# on every batch and read back by nothing.
|
|
126
|
+
def geographic_subjects = texts_at("/mods:mods/mods:subject/mods:geographic")
|
|
127
|
+
def temporal_subjects = texts_at("/mods:mods/mods:subject/mods:temporal")
|
|
128
|
+
|
|
129
|
+
# Name subjects compose through the same display-value port as #names, so
|
|
130
|
+
# one person reads the same whether they authored the work or are its
|
|
131
|
+
# subject.
|
|
132
|
+
def personal_name_subjects = name_subjects("personal")
|
|
133
|
+
def corporate_name_subjects = name_subjects("corporate")
|
|
134
|
+
|
|
107
135
|
# --- Names ---------------------------------------------------------------
|
|
108
136
|
|
|
109
137
|
# All top-level names as { name:, role: }. `name` reproduces the `mods` gem's
|
|
@@ -140,26 +168,99 @@ module NEU
|
|
|
140
168
|
|
|
141
169
|
# --- Scalars / simple arrays --------------------------------------------
|
|
142
170
|
|
|
171
|
+
# Prefer the type="text" term, and translate a code-only one through the
|
|
172
|
+
# ISO 639 registry. A record saying `eng` projects "English", so the
|
|
173
|
+
# display and the Solr language facet read the same value rather than the
|
|
174
|
+
# facet showing codes. An unrecognised code survives as itself.
|
|
143
175
|
def languages
|
|
144
|
-
doc.xpath("/mods:mods/mods:language", NAMESPACE).
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
176
|
+
doc.xpath("/mods:mods/mods:language", NAMESPACE).filter_map do |lang|
|
|
177
|
+
text = lang.at_xpath("mods:languageTerm[@type='text']", NAMESPACE)
|
|
178
|
+
next clean(text.text) if text
|
|
179
|
+
|
|
180
|
+
code = clean(lang.at_xpath("mods:languageTerm", NAMESPACE)&.text)
|
|
181
|
+
code && LanguageCodes.term(code)
|
|
182
|
+
end
|
|
149
183
|
end
|
|
150
184
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def
|
|
185
|
+
# MODS repeats typeOfResource, and repeats physicalDescription (and form and
|
|
186
|
+
# extent within one), so all four are :many. A record that is both text and
|
|
187
|
+
# a still image used to project as text alone.
|
|
188
|
+
def resource_type = texts_at("/mods:mods/mods:typeOfResource")
|
|
189
|
+
def format = texts_at("/mods:mods/mods:physicalDescription/mods:form")
|
|
190
|
+
def extent = texts_at("/mods:mods/mods:physicalDescription/mods:extent")
|
|
191
|
+
def digital_origin = texts_at("/mods:mods/mods:physicalDescription/mods:digitalOrigin")
|
|
155
192
|
|
|
156
193
|
def genres
|
|
157
194
|
doc.xpath("/mods:mods/mods:genre", NAMESPACE).map { |g| clean(g.text) }
|
|
158
195
|
end
|
|
159
196
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
197
|
+
# originInfo repeats, and so do publisher and edition within one. Cerberus's
|
|
198
|
+
# IPTC ingest writes the publisher from the IPTC Source field on every batch,
|
|
199
|
+
# so this element was being written into the preservation XML and then read
|
|
200
|
+
# back by nothing.
|
|
201
|
+
def publication_information = texts_at("/mods:mods/mods:originInfo/mods:publisher")
|
|
202
|
+
def edition = texts_at("/mods:mods/mods:originInfo/mods:edition")
|
|
203
|
+
|
|
204
|
+
# Every top-level note, keeping its @type. The type carries meaning -- a
|
|
205
|
+
# "statement of responsibility" is not a "funding" note -- so flattening
|
|
206
|
+
# them into bare strings would repeat the accessCondition mistake.
|
|
207
|
+
def notes
|
|
208
|
+
doc.xpath("/mods:mods/mods:note", NAMESPACE).filter_map do |node|
|
|
209
|
+
value = clean(node.text)
|
|
210
|
+
{ type: clean(node["type"]), value: value } if value
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# location repeats, and one location mixes kinds: a shelf mark and a URL
|
|
215
|
+
# are not interchangeable, and a consumer has to know which it holds
|
|
216
|
+
# before it can decide to linkify it. So the parts stay apart.
|
|
217
|
+
def location
|
|
218
|
+
doc.xpath("/mods:mods/mods:location", NAMESPACE).filter_map do |node|
|
|
219
|
+
entry = {
|
|
220
|
+
physical_location: child_text(node, "mods:physicalLocation"),
|
|
221
|
+
shelf_location: child_text(node, "mods:shelfLocation"),
|
|
222
|
+
url: child_text(node, "mods:url")
|
|
223
|
+
}
|
|
224
|
+
entry if entry.values.any?
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# subject/cartographics, kept structured. Composing "scale ; projection
|
|
229
|
+
# coordinates" into one string is display policy, and this gem does not own
|
|
230
|
+
# that -- a consumer that wants only the coordinates should not have to
|
|
231
|
+
# unpick a sentence to get them.
|
|
232
|
+
def map_data
|
|
233
|
+
doc.xpath("/mods:mods/mods:subject/mods:cartographics", NAMESPACE).filter_map do |node|
|
|
234
|
+
entry = {
|
|
235
|
+
scale: child_text(node, "mods:scale"),
|
|
236
|
+
projection: child_text(node, "mods:projection"),
|
|
237
|
+
coordinates: child_text(node, "mods:coordinates")
|
|
238
|
+
}
|
|
239
|
+
entry if entry.values.any?
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def related_series = related_item_titles("series")
|
|
244
|
+
def host_collections = related_item_titles("host")
|
|
245
|
+
|
|
246
|
+
# relatedItem @type values that already have a field of their own, so the
|
|
247
|
+
# catch-all below does not repeat them.
|
|
248
|
+
NAMED_RELATED_ITEM_TYPES = %w[series host].freeze
|
|
249
|
+
|
|
250
|
+
# Every other relatedItem, keeping its @type. MODS also defines
|
|
251
|
+
# constituent, otherFormat, original, preceding, succeeding, isReferencedBy
|
|
252
|
+
# and reviewOf, and a record carrying any of them projected nothing at all.
|
|
253
|
+
# The type rides along because "the print edition" and "reviewed in" are
|
|
254
|
+
# not the same relationship, and no consumer can recover which it holds
|
|
255
|
+
# from the title alone. An untyped relatedItem lands here with a nil type.
|
|
256
|
+
def related_items
|
|
257
|
+
doc.xpath("/mods:mods/mods:relatedItem", NAMESPACE).filter_map do |node|
|
|
258
|
+
type = clean(node["type"])
|
|
259
|
+
next if NAMED_RELATED_ITEM_TYPES.include?(type)
|
|
260
|
+
|
|
261
|
+
title = clean(node.at_xpath("mods:titleInfo/mods:title", NAMESPACE)&.text)
|
|
262
|
+
{ type: type, title: title } if title
|
|
263
|
+
end
|
|
163
264
|
end
|
|
164
265
|
|
|
165
266
|
def identifiers
|
|
@@ -177,56 +278,112 @@ module NEU
|
|
|
177
278
|
# instead of being guessed after it.
|
|
178
279
|
W3CDTF_DATE = /\A(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?\z/
|
|
179
280
|
|
|
180
|
-
#
|
|
181
|
-
# [value, precision]. value is nil if
|
|
182
|
-
# present but unparseable (mirrors Atlas's safe_date_parse
|
|
183
|
-
# precision is "year", "month" or "day", and nil whenever value
|
|
184
|
-
# DateTime.
|
|
281
|
+
# The three originInfo dates, each paired with the granularity the record
|
|
282
|
+
# declared, as [value, precision]. value is nil if the element is absent,
|
|
283
|
+
# or "" if present but unparseable (mirrors Atlas's safe_date_parse
|
|
284
|
+
# rescue). precision is "year", "month" or "day", and nil whenever value
|
|
285
|
+
# is not a DateTime.
|
|
185
286
|
#
|
|
186
287
|
# The precision has to be captured here, at the only point where the shape
|
|
187
288
|
# is still visible: a year-only date parses to January 1st, and no consumer
|
|
188
289
|
# downstream can tell that month and day from a record that claimed them.
|
|
189
290
|
# A preservation repository must not project a precision it was not given.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
291
|
+
#
|
|
292
|
+
# These stay :one where the sibling originInfo elements are :many. MODS
|
|
293
|
+
# repeats them, but a date carries a precision alongside it, and an array
|
|
294
|
+
# of values paired with an array of precisions is a shape no consumer
|
|
295
|
+
# wants. A record that ranges its dates uses point="start"/"end", which is
|
|
296
|
+
# its own projection question rather than a cardinality one.
|
|
297
|
+
def date_created_with_precision = date_with_precision("dateCreated")
|
|
298
|
+
def date_issued_with_precision = date_with_precision("dateIssued")
|
|
299
|
+
def copyright_date_with_precision = date_with_precision("copyrightDate")
|
|
199
300
|
|
|
200
301
|
def date_created = date_created_with_precision.first
|
|
201
302
|
def date_created_precision = date_created_with_precision.last
|
|
303
|
+
def date_issued = date_issued_with_precision.first
|
|
304
|
+
def date_issued_precision = date_issued_with_precision.last
|
|
305
|
+
def copyright_date = copyright_date_with_precision.first
|
|
306
|
+
def copyright_date_precision = copyright_date_with_precision.last
|
|
202
307
|
|
|
203
308
|
# --- Full projection -----------------------------------------------------
|
|
204
309
|
|
|
310
|
+
# The field registry: the single declaration of what this gem projects.
|
|
311
|
+
# Field name => cardinality, :one or :many. The projection method of the
|
|
312
|
+
# same name owns the XPath; this row says the field exists and whether it
|
|
313
|
+
# is single- or multi-valued. #to_h is derived from it, and Atlas derives
|
|
314
|
+
# its Metadata::MODS attr_json set from it, so a field cannot be projected
|
|
315
|
+
# here and go undeclared there (or the reverse).
|
|
316
|
+
#
|
|
317
|
+
# Cardinality is the half that earns its keep. The at_xpath-versus-xpath
|
|
318
|
+
# choice here and the single-versus-array column choice in Atlas used to be
|
|
319
|
+
# made independently in two repos with nothing tying them together, which is
|
|
320
|
+
# how repeatable MODS elements ended up truncated to their first match.
|
|
321
|
+
# #cardinality_of checks each method against its row.
|
|
322
|
+
FIELDS = {
|
|
323
|
+
# titles
|
|
324
|
+
main_title: :one,
|
|
325
|
+
alternative_title: :many,
|
|
326
|
+
uniform_title: :many,
|
|
327
|
+
translated_title: :many,
|
|
328
|
+
abbreviated_title: :many,
|
|
329
|
+
|
|
330
|
+
names: :many,
|
|
331
|
+
languages: :many,
|
|
332
|
+
abstract: :one,
|
|
333
|
+
|
|
334
|
+
# origin
|
|
335
|
+
publication_information: :many,
|
|
336
|
+
edition: :many,
|
|
337
|
+
date_created: :one,
|
|
338
|
+
date_created_precision: :one,
|
|
339
|
+
date_issued: :one,
|
|
340
|
+
date_issued_precision: :one,
|
|
341
|
+
copyright_date: :one,
|
|
342
|
+
copyright_date_precision: :one,
|
|
343
|
+
|
|
344
|
+
# physical description
|
|
345
|
+
resource_type: :many,
|
|
346
|
+
genres: :many,
|
|
347
|
+
format: :many,
|
|
348
|
+
extent: :many,
|
|
349
|
+
digital_origin: :many,
|
|
350
|
+
notes: :many,
|
|
351
|
+
|
|
352
|
+
# subjects
|
|
353
|
+
topical_subjects: :many,
|
|
354
|
+
geographic_subjects: :many,
|
|
355
|
+
temporal_subjects: :many,
|
|
356
|
+
personal_name_subjects: :many,
|
|
357
|
+
corporate_name_subjects: :many,
|
|
358
|
+
map_data: :many,
|
|
359
|
+
|
|
360
|
+
# related items
|
|
361
|
+
related_series: :many,
|
|
362
|
+
host_collections: :many,
|
|
363
|
+
related_items: :many,
|
|
364
|
+
|
|
365
|
+
# identifiers and location
|
|
366
|
+
identifiers: :many,
|
|
367
|
+
permanent_url: :one,
|
|
368
|
+
location: :many,
|
|
369
|
+
|
|
370
|
+
# access
|
|
371
|
+
access_condition: :one,
|
|
372
|
+
use_and_reproduction: :one,
|
|
373
|
+
restriction_on_access: :one
|
|
374
|
+
}.freeze
|
|
375
|
+
|
|
205
376
|
# The complete read projection, keyed to Atlas's Metadata::MODS attribute
|
|
206
377
|
# names -- a drop-in source for `convert_xml_to_json`.
|
|
207
378
|
def to_h
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
{
|
|
211
|
-
main_title: access_title_parts,
|
|
212
|
-
names: names,
|
|
213
|
-
languages: languages,
|
|
214
|
-
date_created: date,
|
|
215
|
-
date_created_precision: date_precision,
|
|
216
|
-
resource_type: resource_type,
|
|
217
|
-
genres: genres,
|
|
218
|
-
format: format,
|
|
219
|
-
extent: extent,
|
|
220
|
-
digital_origin: digital_origin,
|
|
221
|
-
abstract: abstract,
|
|
222
|
-
related_series: related_series,
|
|
223
|
-
topical_subjects: topical_subjects,
|
|
224
|
-
identifiers: identifiers,
|
|
225
|
-
permanent_url: permanent_url,
|
|
226
|
-
access_condition: access_condition
|
|
227
|
-
}
|
|
379
|
+
FIELDS.keys.to_h { |field| [field, public_send(field)] }
|
|
228
380
|
end
|
|
229
381
|
|
|
382
|
+
# The cardinality a projected value actually has, for checking a value
|
|
383
|
+
# against its FIELDS row. An Array is :many and anything else is :one, so a
|
|
384
|
+
# field declared :many that forgot to switch at_xpath for xpath is caught.
|
|
385
|
+
def self.cardinality_of(value) = value.is_a?(Array) ? :many : :one
|
|
386
|
+
|
|
230
387
|
# The title parts as the access copy wants them: normalised like the
|
|
231
388
|
# abstract, so a curly quote, an invisible format mark or a Windows-1252
|
|
232
389
|
# control cannot reach Solr or a display template. Titles and prose share
|
|
@@ -235,6 +392,11 @@ module NEU
|
|
|
235
392
|
title_parts.transform_values { |value| NEU::MODS.normalize(value.to_s) }
|
|
236
393
|
end
|
|
237
394
|
|
|
395
|
+
# Atlas names this field main_title; the registry requires a method per
|
|
396
|
+
# field name, and #access_title_parts is the descriptive name for what it
|
|
397
|
+
# returns. Kept as an alias rather than a rename so both read well.
|
|
398
|
+
def main_title = access_title_parts
|
|
399
|
+
|
|
238
400
|
private
|
|
239
401
|
|
|
240
402
|
# --- helpers -------------------------------------------------------------
|
|
@@ -259,11 +421,58 @@ module NEU
|
|
|
259
421
|
["", nil]
|
|
260
422
|
end
|
|
261
423
|
|
|
424
|
+
def date_with_precision(element)
|
|
425
|
+
node = doc.at_xpath("/mods:mods/mods:originInfo/mods:#{element}", NAMESPACE)
|
|
426
|
+
return [nil, nil] unless node
|
|
427
|
+
|
|
428
|
+
str = NEU::MODS.canonical_ws(node.text)
|
|
429
|
+
return [nil, nil] if str.empty?
|
|
430
|
+
|
|
431
|
+
parse_w3cdtf(str)
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# Byte-faithful title parts off any titleInfo node, shared by #title_parts
|
|
435
|
+
# (which Cerberus pre-fills its edit forms from) and the variant titles.
|
|
436
|
+
def title_parts_of(node)
|
|
437
|
+
{
|
|
438
|
+
non_sort: child_text(node, "mods:nonSort"),
|
|
439
|
+
subtitle: child_text(node, "mods:subTitle"),
|
|
440
|
+
title: child_text(node, "mods:title"),
|
|
441
|
+
part_name: child_text(node, "mods:partName"),
|
|
442
|
+
part_number: child_text(node, "mods:partNumber")
|
|
443
|
+
}
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
# A variant title composed the way the access copy wants it: normalised
|
|
447
|
+
# first, like #access_title_parts, so a curly quote or an invisible format
|
|
448
|
+
# mark cannot reach Solr or a display template through this route either.
|
|
449
|
+
def variant_titles(type)
|
|
450
|
+
doc.xpath("/mods:mods/mods:titleInfo[@type='#{type}']", NAMESPACE).filter_map do |node|
|
|
451
|
+
parts = title_parts_of(node).transform_values { |value| NEU::MODS.normalize(value.to_s) }
|
|
452
|
+
clean(Projection.compose_title(parts))
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def name_subjects(type)
|
|
457
|
+
doc.xpath("/mods:mods/mods:subject/mods:name[@type='#{type}']", NAMESPACE)
|
|
458
|
+
.filter_map { |node| name_display_value_w_date(node) }
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
def related_item_titles(type)
|
|
462
|
+
texts_at("/mods:mods/mods:relatedItem[@type='#{type}']/mods:titleInfo/mods:title")
|
|
463
|
+
end
|
|
464
|
+
|
|
262
465
|
def text_at(xpath)
|
|
263
466
|
node = doc.at_xpath(xpath, NAMESPACE)
|
|
264
467
|
node ? NEU::MODS.canonical_ws(node.text) : ""
|
|
265
468
|
end
|
|
266
469
|
|
|
470
|
+
# The :many counterpart of #text_at. Blank members drop out rather than
|
|
471
|
+
# arriving as nil, so a consumer mapping over the array cannot trip on one.
|
|
472
|
+
def texts_at(xpath)
|
|
473
|
+
doc.xpath(xpath, NAMESPACE).filter_map { |node| clean(node.text) }
|
|
474
|
+
end
|
|
475
|
+
|
|
267
476
|
def child_text(parent, xpath)
|
|
268
477
|
return nil unless parent
|
|
269
478
|
|
|
@@ -288,6 +497,15 @@ module NEU
|
|
|
288
497
|
NEU::MODS.canonical_ws(str)
|
|
289
498
|
end
|
|
290
499
|
|
|
500
|
+
# The schema leaves accessCondition/@type an open string, so match on the
|
|
501
|
+
# canonicalised, case-folded value rather than in the XPath: real records
|
|
502
|
+
# carry "Use and Reproduction" as readily as the MODS-recommended casing.
|
|
503
|
+
def access_conditions_of_type(type)
|
|
504
|
+
nodes = doc.xpath("/mods:mods/mods:accessCondition", NAMESPACE)
|
|
505
|
+
.select { |node| NEU::MODS.canonical_ws(node["type"].to_s).downcase == type }
|
|
506
|
+
join_paragraphs(nodes)
|
|
507
|
+
end
|
|
508
|
+
|
|
291
509
|
def join_paragraphs(nodes)
|
|
292
510
|
nodes.map { |n| NEU::MODS.normalize_paragraphs(n.text) }.reject(&:empty?).join("\n\n")
|
|
293
511
|
end
|
data/lib/neu/mods/selectors.rb
CHANGED
|
@@ -8,12 +8,28 @@ module NEU
|
|
|
8
8
|
# definition is the point: the node an editor changes is provably the node the
|
|
9
9
|
# projection reads. Mixed into Document; operates on `doc`.
|
|
10
10
|
module Selectors
|
|
11
|
-
# Top-level primary titleInfo, falling back to the first top-level titleInfo
|
|
12
|
-
# Scoped to direct children of <mods:mods> so a
|
|
13
|
-
# titleInfo (e.g. a series title) is never matched.
|
|
11
|
+
# Top-level primary titleInfo, falling back to the first top-level titleInfo
|
|
12
|
+
# that is NOT a variant. Scoped to direct children of <mods:mods> so a
|
|
13
|
+
# relatedItem's nested titleInfo (e.g. a series title) is never matched.
|
|
14
|
+
#
|
|
15
|
+
# MODS does not require usage="primary", so the fallback fires often, and
|
|
16
|
+
# an unfiltered one let document order decide the record's title. It also
|
|
17
|
+
# decided which node an edit form wrote to: MODSMerge overwrites the node
|
|
18
|
+
# this returns, so an alternative title reached here was destroyed on the
|
|
19
|
+
# next title edit, leaving the record with no primary title at all. nil is
|
|
20
|
+
# the right answer instead -- MODSMerge creates a proper primary titleInfo
|
|
21
|
+
# from nil, and each variant is projected under its own field.
|
|
14
22
|
def primary_title_info
|
|
15
23
|
doc.at_xpath("/mods:mods/mods:titleInfo[@usage='primary']", NAMESPACE) ||
|
|
16
|
-
doc.
|
|
24
|
+
doc.xpath("/mods:mods/mods:titleInfo", NAMESPACE).reject { |ti| variant_title?(ti) }.first
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# MODS enumerates titleInfo/@type as exactly abbreviated, translated,
|
|
28
|
+
# alternative and uniform -- every one of them a variant. So the presence
|
|
29
|
+
# of any @type marks a variant, which also keeps an unrecognised or
|
|
30
|
+
# misspelled value out of the write path rather than guessing at it.
|
|
31
|
+
def variant_title?(node)
|
|
32
|
+
!NEU::MODS.canonical_ws(node["type"].to_s).empty?
|
|
17
33
|
end
|
|
18
34
|
|
|
19
35
|
# All top-level <abstract> elements (MODS permits several).
|
data/lib/neu-mods.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "neu/mods/version"
|
|
4
4
|
require_relative "neu/mods/canonicalize"
|
|
5
|
+
require_relative "neu/mods/language_codes"
|
|
5
6
|
require_relative "neu/mods/selectors"
|
|
6
7
|
require_relative "neu/mods/projection"
|
|
7
8
|
require_relative "neu/mods/document"
|
|
@@ -24,6 +25,12 @@ module NEU
|
|
|
24
25
|
# The MODS v3 namespace, as a Nokogiri xpath namespace hash.
|
|
25
26
|
NAMESPACE = { "mods" => "http://www.loc.gov/mods/v3" }.freeze
|
|
26
27
|
|
|
28
|
+
# The projected field set and its cardinality (see Projection::FIELDS),
|
|
29
|
+
# surfaced here so a consumer deriving its own schema from it -- Atlas's
|
|
30
|
+
# Metadata::MODS attr_json set -- reads the shared contract off the top-level
|
|
31
|
+
# module instead of reaching into a mixin.
|
|
32
|
+
FIELDS = Projection::FIELDS
|
|
33
|
+
|
|
27
34
|
module_function
|
|
28
35
|
|
|
29
36
|
# Whitespace no-op guard (see Canonicalize).
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: neu-mods
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Cliff
|
|
@@ -68,7 +68,9 @@ files:
|
|
|
68
68
|
- Rakefile
|
|
69
69
|
- lib/neu-mods.rb
|
|
70
70
|
- lib/neu/mods/canonicalize.rb
|
|
71
|
+
- lib/neu/mods/data/iso639-2.txt
|
|
71
72
|
- lib/neu/mods/document.rb
|
|
73
|
+
- lib/neu/mods/language_codes.rb
|
|
72
74
|
- lib/neu/mods/projection.rb
|
|
73
75
|
- lib/neu/mods/selectors.rb
|
|
74
76
|
- lib/neu/mods/version.rb
|