@larsgw/formica 0.10.2 → 0.10.4
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.
- package/README.md +2 -0
- package/lib/bin/generate-linked-data.js +209 -84
- package/lib/bin/validate-integrity.d.ts +2 -0
- package/lib/bin/validate-integrity.js +135 -0
- package/lib/resources/parse-name.js +11 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -20,6 +20,8 @@ Run locally:
|
|
|
20
20
|
- `loir-validate-resources [./B1234.txt]`:
|
|
21
21
|
Validate the `.txt` files containing information on the taxonomic scope of keys
|
|
22
22
|
([documentation](https://github.com/identification-resources/catalog/blob/main/docs/resources-txt.md)).
|
|
23
|
+
- `loir-validate-integrity .`:
|
|
24
|
+
Check if all values are mapped as expected.
|
|
23
25
|
- `loir-resources-process [./resources]`:
|
|
24
26
|
Interactive tool to convert the aforementioned `.txt` files to Darwin Core archives
|
|
25
27
|
([documentation](https://github.com/identification-resources/catalog/blob/main/docs/resources-dwc.md)),
|
|
@@ -69,6 +69,9 @@ const SCOPES = {
|
|
|
69
69
|
'nypmhs': ['dwciri:lifeStage', 'http://rs.gbif.org/vocabulary/gbif/life_stage/larva'],
|
|
70
70
|
'nymphs (instar V)': ['dwciri:lifeStage', 'http://rs.gbif.org/vocabulary/gbif/life_stage/larva'],
|
|
71
71
|
'nymphs (instar IV)': ['dwciri:lifeStage', 'http://rs.gbif.org/vocabulary/gbif/life_stage/larva'],
|
|
72
|
+
'nymphs (instar III)': ['dwciri:lifeStage', 'http://rs.gbif.org/vocabulary/gbif/life_stage/larva'],
|
|
73
|
+
'nymphs (instar II)': ['dwciri:lifeStage', 'http://rs.gbif.org/vocabulary/gbif/life_stage/larva'],
|
|
74
|
+
'nymphs (instar I)': ['dwciri:lifeStage', 'http://rs.gbif.org/vocabulary/gbif/life_stage/larva'],
|
|
72
75
|
'eggs': ['dwciri:lifeStage', 'http://rs.gbif.org/vocabulary/gbif/life_stage/embryo'],
|
|
73
76
|
// plant life stage
|
|
74
77
|
'flowering plants': ['dwciri:lifeStage', 'http://rs.gbif.org/vocabulary/gbif/life_stage/adult'],
|
|
@@ -87,6 +90,9 @@ const SCOPES = {
|
|
|
87
90
|
'alatae': ['dwc:caste', 'alate'],
|
|
88
91
|
'apterae': ['dwc:caste', 'aptera'],
|
|
89
92
|
'viviparae': ['dwc:caste', 'vivipara'],
|
|
93
|
+
'brachypterae': ['dwc:caste', 'brachypterae'],
|
|
94
|
+
'macropterae': ['dwc:caste', 'macropterae'],
|
|
95
|
+
'micropterae': ['dwc:caste', 'micropterae'],
|
|
90
96
|
// evidence
|
|
91
97
|
'nests': ['ac:subjectPartLiteral', 'nest'],
|
|
92
98
|
'galls': ['ac:subjectPartLiteral', 'gall'],
|
|
@@ -96,6 +102,7 @@ const SCOPES = {
|
|
|
96
102
|
'bones (skulls)': ['ac:subjectPart', 'http://rs.tdwg.org/acpart/values/p0027'],
|
|
97
103
|
'bones (upper jaws)': ['ac:subjectPart', 'http://rs.tdwg.org/acpart/values/p0028'],
|
|
98
104
|
'bones (lower jaws)': ['ac:subjectPart', 'http://rs.tdwg.org/acpart/values/p0029'],
|
|
105
|
+
'spermathecae': ['ac:subjectPartLiteral', 'spermathecae'],
|
|
99
106
|
};
|
|
100
107
|
const PREFIXES = {
|
|
101
108
|
'ac': 'http://rs.tdwg.org/ac/terms/',
|
|
@@ -129,15 +136,18 @@ const STATUSES = {
|
|
|
129
136
|
'proparte synonym': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/proParteSynonym',
|
|
130
137
|
'synonym': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/synonym',
|
|
131
138
|
};
|
|
132
|
-
function getCoveringTaxon(taxa) {
|
|
133
|
-
if (taxa.length === 1 && !taxa[0].has('
|
|
139
|
+
function getCoveringTaxon(taxa, allTaxa) {
|
|
140
|
+
if (taxa.length === 1 && !taxa[0].has('col')) {
|
|
134
141
|
return `${PREFIX}taxon/${taxa[0].get('id')}`;
|
|
135
142
|
}
|
|
136
143
|
function getAncestors(taxon) {
|
|
137
144
|
var _a;
|
|
138
|
-
const [...parents] = ((_a = taxon.get('
|
|
139
|
-
if (taxon.has('
|
|
140
|
-
parents.push(taxon.get('
|
|
145
|
+
const [...parents] = ((_a = taxon.get('ancestors_col')) !== null && _a !== void 0 ? _a : []);
|
|
146
|
+
if (taxon.has('accepted_col')) {
|
|
147
|
+
parents.push(taxon.get('accepted_col'));
|
|
148
|
+
}
|
|
149
|
+
else if (taxon.has('col')) {
|
|
150
|
+
parents.push(taxon.get('col'));
|
|
141
151
|
}
|
|
142
152
|
return parents;
|
|
143
153
|
}
|
|
@@ -150,7 +160,15 @@ function getCoveringTaxon(taxa) {
|
|
|
150
160
|
}
|
|
151
161
|
}
|
|
152
162
|
}
|
|
153
|
-
|
|
163
|
+
if (!ancestors.length) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
const ancestor = ancestors[ancestors.length - 1];
|
|
167
|
+
const ancestorEntity = allTaxa.find(taxon => taxon.get('col') === ancestor);
|
|
168
|
+
if (ancestorEntity) {
|
|
169
|
+
return makeTaxonUri(ancestorEntity.get('id'))['@id'];
|
|
170
|
+
}
|
|
171
|
+
return makeColUri(ancestor)['@id'];
|
|
154
172
|
}
|
|
155
173
|
function mapEntities(names, entities) {
|
|
156
174
|
const result = [];
|
|
@@ -171,11 +189,17 @@ function makeWikidataUri(qid) {
|
|
|
171
189
|
return { '@id': `http://www.wikidata.org/entity/${qid}` };
|
|
172
190
|
}
|
|
173
191
|
function makeGbifUri(id) {
|
|
174
|
-
return { '@id': `https://gbif.org/species/${id}` };
|
|
192
|
+
return { '@id': `https://www.gbif.org/species/${id}` };
|
|
193
|
+
}
|
|
194
|
+
function makeColUri(id) {
|
|
195
|
+
return { '@id': `https://www.catalogueoflife.org/data/taxon/${id}` };
|
|
175
196
|
}
|
|
176
197
|
function makeWorkUri(id) {
|
|
177
198
|
return { '@id': `${PREFIX}catalog/${id}` };
|
|
178
199
|
}
|
|
200
|
+
function makeTaxonUri(id) {
|
|
201
|
+
return { '@id': `${PREFIX}taxon/${id}` };
|
|
202
|
+
}
|
|
179
203
|
function makeScientificNameUri(id) {
|
|
180
204
|
const resource = id.split(':').slice(0, -1).join(':');
|
|
181
205
|
return { '@id': `${PREFIX}resource/${resource}#${id}` };
|
|
@@ -226,7 +250,7 @@ function makeLinkedDataForPublisher(publisher) {
|
|
|
226
250
|
}
|
|
227
251
|
function makeLinkedDataForTaxon(taxon) {
|
|
228
252
|
const node = {
|
|
229
|
-
'@id':
|
|
253
|
+
'@id': makeTaxonUri(taxon.get('id'))['@id'],
|
|
230
254
|
'@type': 'dwc:Taxon',
|
|
231
255
|
'dwc:scientificName': taxon.get('name'),
|
|
232
256
|
};
|
|
@@ -237,6 +261,9 @@ function makeLinkedDataForTaxon(taxon) {
|
|
|
237
261
|
if (taxon.has('qid')) {
|
|
238
262
|
ids.push(makeWikidataUri(taxon.get('qid')));
|
|
239
263
|
}
|
|
264
|
+
if (taxon.has('col')) {
|
|
265
|
+
ids.push(makeColUri(taxon.get('col')));
|
|
266
|
+
}
|
|
240
267
|
if (taxon.has('gbif')) {
|
|
241
268
|
ids.push(makeGbifUri(taxon.get('gbif')));
|
|
242
269
|
}
|
|
@@ -247,32 +274,89 @@ function makeLinkedDataForTaxon(taxon) {
|
|
|
247
274
|
}
|
|
248
275
|
function makeLinkedDataForTaxa(files) {
|
|
249
276
|
var _a;
|
|
250
|
-
const nodes =
|
|
251
|
-
const
|
|
277
|
+
const nodes = {};
|
|
278
|
+
const parents = {};
|
|
279
|
+
const colIndex = files.taxa.entities.reduce((index, taxon) => {
|
|
280
|
+
if (taxon.has('col')) {
|
|
281
|
+
index[taxon.get('col')] = taxon.get('id');
|
|
282
|
+
}
|
|
283
|
+
return index;
|
|
284
|
+
}, {});
|
|
285
|
+
const getUriFromCol = (col) => {
|
|
286
|
+
if (col == null) {
|
|
287
|
+
// No parent -> Biota
|
|
288
|
+
return makeTaxonUri('T141');
|
|
289
|
+
}
|
|
290
|
+
else if (col in colIndex) {
|
|
291
|
+
return makeTaxonUri(colIndex[col]);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
return makeColUri(col);
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
const getUriFromGbif = (gbif) => {
|
|
298
|
+
if (gbif == null) {
|
|
299
|
+
// No parent -> Biota
|
|
300
|
+
return makeTaxonUri('T141');
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
return makeGbifUri(gbif);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
const addParent = (child, parent) => {
|
|
307
|
+
if (!parents[child]) {
|
|
308
|
+
parents[child] = new Set();
|
|
309
|
+
}
|
|
310
|
+
parents[child].add(parent);
|
|
311
|
+
};
|
|
252
312
|
for (const taxon of files.taxa.entities) {
|
|
253
313
|
const node = makeLinkedDataForTaxon(taxon);
|
|
254
|
-
const ancestors = (_a = taxon.get('
|
|
255
|
-
if (
|
|
256
|
-
node['dwc:parentNameUsageID'] =
|
|
314
|
+
const ancestors = (_a = taxon.get('ancestors_col')) !== null && _a !== void 0 ? _a : [];
|
|
315
|
+
if (taxon.get('id') !== 'T141') {
|
|
316
|
+
node['dwc:parentNameUsageID'] = getUriFromCol(ancestors.length ? ancestors[ancestors.length - 1] : null);
|
|
257
317
|
}
|
|
258
|
-
|
|
259
|
-
node['dwc:
|
|
318
|
+
if (taxon.has('accepted_col')) {
|
|
319
|
+
node['dwc:acceptedNameUsageID'] = getUriFromCol(taxon.get('accepted_col'));
|
|
260
320
|
}
|
|
261
321
|
for (let i = 0; i < ancestors.length; i++) {
|
|
262
|
-
|
|
322
|
+
const parent = getUriFromCol(i ? ancestors[i - 1] : null);
|
|
323
|
+
addParent(getUriFromCol(ancestors[i])['@id'], parent['@id']);
|
|
324
|
+
}
|
|
325
|
+
if (taxon.has('children_col')) {
|
|
326
|
+
const children = taxon.get('children_col');
|
|
327
|
+
for (const child of children) {
|
|
328
|
+
addParent(getUriFromCol(child)['@id'], node['@id']);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// GBIF
|
|
332
|
+
if (taxon.has('ancestors_gbif')) {
|
|
333
|
+
const ancestors = taxon.get('ancestors_gbif');
|
|
334
|
+
for (let i = 0; i < ancestors.length; i++) {
|
|
335
|
+
const ancestor = makeGbifUri(ancestors[i])['@id'];
|
|
336
|
+
addParent(ancestor, getUriFromGbif(ancestors[i - 1])['@id']);
|
|
337
|
+
nodes[ancestor] = { '@id': ancestor, 'dwc:taxonRank': makeTaxonRankUri(GBIF_RANKS[i]) };
|
|
338
|
+
}
|
|
263
339
|
}
|
|
264
340
|
if (taxon.has('children_gbif')) {
|
|
265
341
|
const children = taxon.get('children_gbif');
|
|
266
|
-
node['@reverse'] = { 'dwc:parentNameUsageID': children.map(makeGbifUri) };
|
|
267
342
|
const childRank = GBIF_RANKS[ancestors.length];
|
|
268
343
|
for (const child of children) {
|
|
269
|
-
|
|
344
|
+
const childUri = makeGbifUri(child)['@id'];
|
|
345
|
+
addParent(childUri, node['@id']);
|
|
346
|
+
nodes[childUri] = { '@id': childUri, 'dwc:taxonRank': makeTaxonRankUri(childRank) };
|
|
270
347
|
}
|
|
271
348
|
}
|
|
272
|
-
nodes
|
|
349
|
+
nodes[node['@id']] = node;
|
|
350
|
+
}
|
|
351
|
+
for (const child in parents) {
|
|
352
|
+
if (!nodes[child]) {
|
|
353
|
+
nodes[child] = { '@id': child };
|
|
354
|
+
}
|
|
355
|
+
for (const parent of parents[child]) {
|
|
356
|
+
nodes[child]['dwc:parentNameUsageID'] = { '@id': parent };
|
|
357
|
+
}
|
|
273
358
|
}
|
|
274
|
-
|
|
275
|
-
return nodes;
|
|
359
|
+
return Object.values(nodes);
|
|
276
360
|
}
|
|
277
361
|
function makeLinkedDataForScientificName(name) {
|
|
278
362
|
const node = Object.assign(Object.assign({}, makeScientificNameUri(name.scientificNameID)), { '@type': 'dwc:Taxon' });
|
|
@@ -298,12 +382,12 @@ function makeLinkedDataForScientificName(name) {
|
|
|
298
382
|
node['dwc:parentNameUsageID'] = makeScientificNameUri(name.parentNameUsageID);
|
|
299
383
|
}
|
|
300
384
|
const identifiers = [];
|
|
385
|
+
if (name.colTaxonID) {
|
|
386
|
+
identifiers.push(makeColUri(name.colTaxonID));
|
|
387
|
+
}
|
|
301
388
|
if (name.gbifTaxonID) {
|
|
302
389
|
identifiers.push(makeGbifUri(name.gbifTaxonID));
|
|
303
390
|
}
|
|
304
|
-
if (name.colTaxonID) {
|
|
305
|
-
identifiers.push({ '@id': `https://www.checklistbank.org/dataset/309120/taxon/${name.colTaxonID}` });
|
|
306
|
-
}
|
|
307
391
|
if (identifiers.length) {
|
|
308
392
|
node['dwc:taxonID'] = identifiers;
|
|
309
393
|
}
|
|
@@ -341,7 +425,7 @@ function makeLinkedDataForResource(work, files, resourceId) {
|
|
|
341
425
|
const taxonNames = (_c = resource.get('taxon')) !== null && _c !== void 0 ? _c : work.get('taxon');
|
|
342
426
|
if (taxonNames) {
|
|
343
427
|
const taxa = mapEntities(taxonNames, files.taxa.entities);
|
|
344
|
-
const coveringTaxon = taxa.length ? getCoveringTaxon(taxa) : null;
|
|
428
|
+
const coveringTaxon = taxa.length ? getCoveringTaxon(taxa, files.taxa.entities) : null;
|
|
345
429
|
if (coveringTaxon !== null) {
|
|
346
430
|
node['ac:taxonCoverage'] = { '@id': coveringTaxon };
|
|
347
431
|
}
|
|
@@ -408,6 +492,7 @@ function makeLinkedDataForResource(work, files, resourceId) {
|
|
|
408
492
|
function makeLinkedDataForWork(work, files) {
|
|
409
493
|
const id = work.get('id');
|
|
410
494
|
const node = makeWorkUri(id);
|
|
495
|
+
node['@reverse'] = {};
|
|
411
496
|
const languages = work.get('language');
|
|
412
497
|
node['dcterms:language'] = languages.map(language => ({ '@id': `http://id.loc.gov/vocabulary/iso639-1/${language}` }));
|
|
413
498
|
if (work.has('title')) {
|
|
@@ -536,46 +621,60 @@ function makeLinkedDataForWork(work, files) {
|
|
|
536
621
|
}
|
|
537
622
|
});
|
|
538
623
|
}
|
|
624
|
+
if (work.has('part_of')) {
|
|
625
|
+
const containers = work.get('part_of');
|
|
626
|
+
if (work.get('key_type').includes('supplement')) {
|
|
627
|
+
node['bibo:annotates'] = containers.map(makeWorkUri);
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
node['dcterms:isPartOf'] = containers.map(makeWorkUri);
|
|
631
|
+
for (const container of containers) {
|
|
632
|
+
if (!Array.isArray(node['@reverse']['dcterms:hasPart'])) {
|
|
633
|
+
node['@reverse']['dcterms:hasPart'] = [];
|
|
634
|
+
}
|
|
635
|
+
node['@reverse']['dcterms:hasPart'].push(makeWorkUri(container));
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
if (work.has('listed_in')) {
|
|
640
|
+
const referers = work.get('listed_in');
|
|
641
|
+
node['bibo:citedBy'] = node['dcterms:isReferencedBy'] = referers.map(makeWorkUri);
|
|
642
|
+
for (const referer of referers) {
|
|
643
|
+
if (!Array.isArray(node['@reverse']['bibo:cites'])) {
|
|
644
|
+
node['@reverse']['bibo:cites'] = [];
|
|
645
|
+
}
|
|
646
|
+
node['@reverse']['bibo:cites'].push(makeWorkUri(referer));
|
|
647
|
+
if (!Array.isArray(node['@reverse']['dcterms:references'])) {
|
|
648
|
+
node['@reverse']['dcterms:references'] = [];
|
|
649
|
+
}
|
|
650
|
+
node['@reverse']['dcterms:references'].push(makeWorkUri(referer));
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (work.has('version_of')) {
|
|
654
|
+
const originals = work.get('version_of');
|
|
655
|
+
const originalLanguages = originals.map(id => {
|
|
656
|
+
var _a;
|
|
657
|
+
const [workId, resourceId] = id.split(':');
|
|
658
|
+
const original = files.catalog.get(workId);
|
|
659
|
+
const language = original.get('language').join();
|
|
660
|
+
if (resourceId) {
|
|
661
|
+
const resourceLanguage = (_a = files.resources[id].metadata.catalog) === null || _a === void 0 ? void 0 : _a.language;
|
|
662
|
+
if (resourceLanguage) {
|
|
663
|
+
return resourceLanguage.join(',');
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return language;
|
|
667
|
+
});
|
|
668
|
+
node['bibo:translationOf'] = originals.filter((_, i) => originalLanguages[i] !== languages.join()).map(makeWorkUri);
|
|
669
|
+
node['dcterms:isVersionOf'] = originals.filter(id => work.get('id') !== id).map(makeWorkUri);
|
|
670
|
+
}
|
|
539
671
|
return node;
|
|
540
672
|
}
|
|
541
|
-
function makeLinkedDataForWorks(files) {
|
|
542
|
-
const nodes = {};
|
|
673
|
+
function* makeLinkedDataForWorks(files) {
|
|
543
674
|
const works = files.catalog;
|
|
544
|
-
for (const work of works.entities) {
|
|
545
|
-
nodes[work.get('id')] = makeLinkedDataForWork(work, files);
|
|
546
|
-
}
|
|
547
675
|
for (const work of works.entities) {
|
|
548
676
|
const id = work.get('id');
|
|
549
|
-
const node =
|
|
550
|
-
const language = work.get('language');
|
|
551
|
-
if (work.has('part_of')) {
|
|
552
|
-
const containers = work.get('part_of');
|
|
553
|
-
if (work.get('key_type').includes('supplement')) {
|
|
554
|
-
node['bibo:annotates'] = containers.map(makeWorkUri);
|
|
555
|
-
}
|
|
556
|
-
else {
|
|
557
|
-
node['dcterms:isPartOf'] = containers.map(makeWorkUri);
|
|
558
|
-
for (const container of containers) {
|
|
559
|
-
if (!Array.isArray(nodes[container]['dcterms:hasPart'])) {
|
|
560
|
-
nodes[container]['dcterms:hasPart'] = [];
|
|
561
|
-
}
|
|
562
|
-
nodes[container]['dcterms:hasPart'].push(makeWorkUri(id));
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
if (work.has('listed_in')) {
|
|
567
|
-
const referers = work.get('listed_in');
|
|
568
|
-
node['bibo:citedBy'] = node['dcterms:isReferencedBy'] = referers.map(makeWorkUri);
|
|
569
|
-
for (const referer of referers) {
|
|
570
|
-
nodes[referer]['bibo:cites'] = nodes[referer]['dcterms:references'] = makeWorkUri(id);
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
if (work.has('version_of')) {
|
|
574
|
-
const originals = work.get('version_of');
|
|
575
|
-
const originalLanguages = originals.map(id => works.get(id).get('language').join());
|
|
576
|
-
node['bibo:translationOf'] = originals.filter((_, i) => originalLanguages[i] !== language.join()).map(makeWorkUri);
|
|
577
|
-
node['dcterms:isVersionOf'] = originals.filter(id => work.get('id') !== id).map(makeWorkUri);
|
|
578
|
-
}
|
|
677
|
+
const node = makeLinkedDataForWork(work, files);
|
|
579
678
|
let resourceIndex = 1;
|
|
580
679
|
let resourceId;
|
|
581
680
|
while ((resourceId = `${id}:${resourceIndex++}`) in files.resources) {
|
|
@@ -589,26 +688,52 @@ function makeLinkedDataForWorks(files) {
|
|
|
589
688
|
}
|
|
590
689
|
resource['dcterms:isPartOf'].push({ '@id': node['@id'] });
|
|
591
690
|
}
|
|
691
|
+
yield node;
|
|
592
692
|
}
|
|
593
|
-
return Object.values(nodes);
|
|
594
693
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
process.stdout.write(
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
694
|
+
class Writer {
|
|
695
|
+
constructor(format) {
|
|
696
|
+
this.format = format;
|
|
697
|
+
this.issuer = new jsonld.util.IdentifierIssuer('_:b');
|
|
698
|
+
this.n3parser = new N3.StreamParser();
|
|
699
|
+
this.n3writer = new N3.StreamWriter({ prefixes: PREFIXES });
|
|
700
|
+
if (this.format === 'jsonld') {
|
|
701
|
+
const [start] = JSON.stringify({ '@context': PREFIXES, '@graph': [] }, null, 2).split(/(?<="@graph": \[)/);
|
|
702
|
+
process.stdout.write(start + '\n');
|
|
703
|
+
}
|
|
704
|
+
else if (this.format === 'nquads') {
|
|
705
|
+
// no preparation required
|
|
706
|
+
}
|
|
707
|
+
else {
|
|
708
|
+
this.n3parser.pipe(this.n3writer);
|
|
709
|
+
this.n3writer.pipe(process.stdout);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
write(graph) {
|
|
713
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
714
|
+
if (this.format === 'jsonld') {
|
|
715
|
+
for (const entity of graph) {
|
|
716
|
+
process.stdout.write(JSON.stringify(entity, null, 2).replace(/^/gm, ' ') + ',\n');
|
|
717
|
+
}
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
const document = {
|
|
721
|
+
'@context': PREFIXES,
|
|
722
|
+
'@graph': graph
|
|
723
|
+
};
|
|
724
|
+
const nquads = (yield jsonld.toRDF(document, { format: 'application/n-quads', issuer: this.issuer }));
|
|
725
|
+
if (this.format === 'nquads') {
|
|
726
|
+
process.stdout.write(nquads);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
this.n3parser.write(nquads);
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
end() {
|
|
733
|
+
if (this.format === 'jsonld') {
|
|
734
|
+
process.stdout.write(' null\n ]\n}\n');
|
|
735
|
+
}
|
|
736
|
+
}
|
|
612
737
|
}
|
|
613
738
|
function main() {
|
|
614
739
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -644,8 +769,11 @@ function main() {
|
|
|
644
769
|
}, {}));
|
|
645
770
|
files.resources[id] = { metadata: resources[id], taxa };
|
|
646
771
|
}
|
|
772
|
+
const writer = new Writer(args.values.format);
|
|
773
|
+
for (const entity of makeLinkedDataForWorks(files)) {
|
|
774
|
+
yield writer.write([entity]);
|
|
775
|
+
}
|
|
647
776
|
const graph = [
|
|
648
|
-
...makeLinkedDataForWorks(files),
|
|
649
777
|
...makeLinkedDataForTaxa(files),
|
|
650
778
|
];
|
|
651
779
|
for (const entity of files.authors.entities) {
|
|
@@ -657,11 +785,8 @@ function main() {
|
|
|
657
785
|
for (const entity of files.publishers.entities) {
|
|
658
786
|
graph.push(makeLinkedDataForPublisher(entity));
|
|
659
787
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
'@graph': graph
|
|
663
|
-
};
|
|
664
|
-
writeOutput(document, args.values.format);
|
|
788
|
+
yield writer.write(graph);
|
|
789
|
+
writer.end();
|
|
665
790
|
});
|
|
666
791
|
}
|
|
667
792
|
main().catch((error) => {
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
37
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
38
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
39
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
40
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
41
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
42
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
const fs_1 = require("fs");
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const index_1 = require("../index");
|
|
49
|
+
function loadCatalogFile(file, base) {
|
|
50
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
51
|
+
const contents = yield fs_1.promises.readFile(path.join(base, `${file}.csv`), 'utf8');
|
|
52
|
+
return index_1.catalog.loadData(contents, file);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function loadResourcesIndex(base) {
|
|
56
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
57
|
+
const contents = yield fs_1.promises.readFile(path.join(base, 'resources', 'index.json'), 'utf8');
|
|
58
|
+
return JSON.parse(contents);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function getAll(entity, field) {
|
|
62
|
+
const value = entity.get(field);
|
|
63
|
+
if (Array.isArray(value)) {
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
else if (value) {
|
|
67
|
+
return [value];
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function checkIntegrity(catalog, resources, field, sheet) {
|
|
74
|
+
const errors = [];
|
|
75
|
+
const index = new Set();
|
|
76
|
+
for (const entity of sheet) {
|
|
77
|
+
for (const value of getAll(entity, 'name')) {
|
|
78
|
+
index.add(value);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
for (const entity of catalog) {
|
|
82
|
+
const id = entity.get('id');
|
|
83
|
+
for (const value of getAll(entity, field)) {
|
|
84
|
+
if (!index.has(value)) {
|
|
85
|
+
errors.push({
|
|
86
|
+
entity: id,
|
|
87
|
+
field: field,
|
|
88
|
+
error: `Value "${value}" is not mapped`
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
for (const id in resources) {
|
|
94
|
+
const resource = resources[id];
|
|
95
|
+
if (!resource.catalog || !Object.prototype.hasOwnProperty.call(resource.catalog, field)) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const value = resource.catalog[field];
|
|
99
|
+
const values = Array.isArray(value) ? value : [value];
|
|
100
|
+
for (const value of values) {
|
|
101
|
+
if (!index.has(value)) {
|
|
102
|
+
errors.push({
|
|
103
|
+
entity: id,
|
|
104
|
+
field: field,
|
|
105
|
+
error: `Value "${value}" is not mapped`
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return errors;
|
|
111
|
+
}
|
|
112
|
+
function main(args) {
|
|
113
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
114
|
+
const base = path.resolve(args[0]);
|
|
115
|
+
const [catalog, authors, places, publishers, taxa, resources] = yield Promise.all([
|
|
116
|
+
loadCatalogFile('catalog', base),
|
|
117
|
+
loadCatalogFile('authors', base),
|
|
118
|
+
loadCatalogFile('places', base),
|
|
119
|
+
loadCatalogFile('publishers', base),
|
|
120
|
+
loadCatalogFile('taxa', base),
|
|
121
|
+
loadResourcesIndex(base)
|
|
122
|
+
]);
|
|
123
|
+
const errors = [
|
|
124
|
+
...checkIntegrity(catalog, resources, 'author', authors),
|
|
125
|
+
...checkIntegrity(catalog, resources, 'publisher', publishers),
|
|
126
|
+
...checkIntegrity(catalog, resources, 'taxon', taxa),
|
|
127
|
+
...checkIntegrity(catalog, resources, 'region', places)
|
|
128
|
+
];
|
|
129
|
+
if (errors.length) {
|
|
130
|
+
console.table(errors);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
main(process.argv.slice(2));
|
|
@@ -233,7 +233,17 @@ function parseName(name, rank, parent) {
|
|
|
233
233
|
name = name.slice(species.length + 1);
|
|
234
234
|
}
|
|
235
235
|
// Remove rank abbreviations
|
|
236
|
-
|
|
236
|
+
for (const abbreviation in RANK_LABELS_REVERSE) {
|
|
237
|
+
const prefix = abbreviation + '. ';
|
|
238
|
+
if (name.startsWith(prefix)) {
|
|
239
|
+
const abbreviationRank = RANK_LABELS_REVERSE[abbreviation];
|
|
240
|
+
if (rank !== abbreviationRank) {
|
|
241
|
+
throw new RecoverableSyntaxError(`Infraspecific rank label has an unexpected rank: "${abbreviation}." meaning ${abbreviationRank}, expected ${rank}`, item);
|
|
242
|
+
}
|
|
243
|
+
name = name.slice(prefix.length);
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
237
247
|
}
|
|
238
248
|
}
|
|
239
249
|
else if (compareRanks('genus', rank) <= 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@larsgw/formica",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.4",
|
|
4
4
|
"description": "SDK and tools for data from the Library of Identification Resources",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"bin": {
|
|
11
11
|
"loir-validate-catalog": "lib/bin/validate-catalog.js",
|
|
12
12
|
"loir-validate-resources": "lib/bin/validate-resources-text.js",
|
|
13
|
+
"loir-validate-integrity": "lib/bin/validate-integrity.js",
|
|
13
14
|
"loir-resources-process": "lib/bin/process-resources.js",
|
|
14
15
|
"loir-resources-index": "lib/bin/process-resources-index.js",
|
|
15
16
|
"loir-resources-check-diff": "lib/bin/check-resources-diff.js",
|
|
@@ -52,6 +53,7 @@
|
|
|
52
53
|
"@typescript-eslint/parser": "^8.32.0",
|
|
53
54
|
"conventional-changelog-cli": "^5.0.0",
|
|
54
55
|
"eslint": "^10.6.0",
|
|
56
|
+
"patch-package": "^8.0.1",
|
|
55
57
|
"typescript": "^6.0.3"
|
|
56
58
|
}
|
|
57
59
|
}
|