@larsgw/formica 0.10.3 → 0.10.5
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.
|
@@ -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 makeTaxonUri('T141')['@id'];
|
|
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,20 @@ 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 makeResourceUri(id) {
|
|
201
|
+
return { '@id': `${PREFIX}resource/${id}` };
|
|
202
|
+
}
|
|
203
|
+
function makeTaxonUri(id) {
|
|
204
|
+
return { '@id': `${PREFIX}taxon/${id}` };
|
|
205
|
+
}
|
|
179
206
|
function makeScientificNameUri(id) {
|
|
180
207
|
const resource = id.split(':').slice(0, -1).join(':');
|
|
181
208
|
return { '@id': `${PREFIX}resource/${resource}#${id}` };
|
|
@@ -226,7 +253,7 @@ function makeLinkedDataForPublisher(publisher) {
|
|
|
226
253
|
}
|
|
227
254
|
function makeLinkedDataForTaxon(taxon) {
|
|
228
255
|
const node = {
|
|
229
|
-
'@id':
|
|
256
|
+
'@id': makeTaxonUri(taxon.get('id'))['@id'],
|
|
230
257
|
'@type': 'dwc:Taxon',
|
|
231
258
|
'dwc:scientificName': taxon.get('name'),
|
|
232
259
|
};
|
|
@@ -237,6 +264,9 @@ function makeLinkedDataForTaxon(taxon) {
|
|
|
237
264
|
if (taxon.has('qid')) {
|
|
238
265
|
ids.push(makeWikidataUri(taxon.get('qid')));
|
|
239
266
|
}
|
|
267
|
+
if (taxon.has('col')) {
|
|
268
|
+
ids.push(makeColUri(taxon.get('col')));
|
|
269
|
+
}
|
|
240
270
|
if (taxon.has('gbif')) {
|
|
241
271
|
ids.push(makeGbifUri(taxon.get('gbif')));
|
|
242
272
|
}
|
|
@@ -247,32 +277,89 @@ function makeLinkedDataForTaxon(taxon) {
|
|
|
247
277
|
}
|
|
248
278
|
function makeLinkedDataForTaxa(files) {
|
|
249
279
|
var _a;
|
|
250
|
-
const nodes =
|
|
251
|
-
const
|
|
280
|
+
const nodes = {};
|
|
281
|
+
const parents = {};
|
|
282
|
+
const colIndex = files.taxa.entities.reduce((index, taxon) => {
|
|
283
|
+
if (taxon.has('col')) {
|
|
284
|
+
index[taxon.get('col')] = taxon.get('id');
|
|
285
|
+
}
|
|
286
|
+
return index;
|
|
287
|
+
}, {});
|
|
288
|
+
const getUriFromCol = (col) => {
|
|
289
|
+
if (col == null) {
|
|
290
|
+
// No parent -> Biota
|
|
291
|
+
return makeTaxonUri('T141');
|
|
292
|
+
}
|
|
293
|
+
else if (col in colIndex) {
|
|
294
|
+
return makeTaxonUri(colIndex[col]);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
return makeColUri(col);
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
const getUriFromGbif = (gbif) => {
|
|
301
|
+
if (gbif == null) {
|
|
302
|
+
// No parent -> Biota
|
|
303
|
+
return makeTaxonUri('T141');
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
return makeGbifUri(gbif);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
const addParent = (child, parent) => {
|
|
310
|
+
if (!parents[child]) {
|
|
311
|
+
parents[child] = new Set();
|
|
312
|
+
}
|
|
313
|
+
parents[child].add(parent);
|
|
314
|
+
};
|
|
252
315
|
for (const taxon of files.taxa.entities) {
|
|
253
316
|
const node = makeLinkedDataForTaxon(taxon);
|
|
254
|
-
const ancestors = (_a = taxon.get('
|
|
255
|
-
if (
|
|
256
|
-
node['dwc:parentNameUsageID'] =
|
|
317
|
+
const ancestors = (_a = taxon.get('ancestors_col')) !== null && _a !== void 0 ? _a : [];
|
|
318
|
+
if (taxon.get('id') !== 'T141') {
|
|
319
|
+
node['dwc:parentNameUsageID'] = getUriFromCol(ancestors.length ? ancestors[ancestors.length - 1] : null);
|
|
257
320
|
}
|
|
258
|
-
|
|
259
|
-
node['dwc:
|
|
321
|
+
if (taxon.has('accepted_col')) {
|
|
322
|
+
node['dwc:acceptedNameUsageID'] = getUriFromCol(taxon.get('accepted_col'));
|
|
260
323
|
}
|
|
261
324
|
for (let i = 0; i < ancestors.length; i++) {
|
|
262
|
-
|
|
325
|
+
const parent = getUriFromCol(i ? ancestors[i - 1] : null);
|
|
326
|
+
addParent(getUriFromCol(ancestors[i])['@id'], parent['@id']);
|
|
327
|
+
}
|
|
328
|
+
if (taxon.has('children_col')) {
|
|
329
|
+
const children = taxon.get('children_col');
|
|
330
|
+
for (const child of children) {
|
|
331
|
+
addParent(getUriFromCol(child)['@id'], node['@id']);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
// GBIF
|
|
335
|
+
if (taxon.has('ancestors_gbif')) {
|
|
336
|
+
const ancestors = taxon.get('ancestors_gbif');
|
|
337
|
+
for (let i = 0; i < ancestors.length; i++) {
|
|
338
|
+
const ancestor = makeGbifUri(ancestors[i])['@id'];
|
|
339
|
+
addParent(ancestor, getUriFromGbif(ancestors[i - 1])['@id']);
|
|
340
|
+
nodes[ancestor] = { '@id': ancestor, 'dwc:taxonRank': makeTaxonRankUri(GBIF_RANKS[i]) };
|
|
341
|
+
}
|
|
263
342
|
}
|
|
264
343
|
if (taxon.has('children_gbif')) {
|
|
265
344
|
const children = taxon.get('children_gbif');
|
|
266
|
-
node['@reverse'] = { 'dwc:parentNameUsageID': children.map(makeGbifUri) };
|
|
267
345
|
const childRank = GBIF_RANKS[ancestors.length];
|
|
268
346
|
for (const child of children) {
|
|
269
|
-
|
|
347
|
+
const childUri = makeGbifUri(child)['@id'];
|
|
348
|
+
addParent(childUri, node['@id']);
|
|
349
|
+
nodes[childUri] = { '@id': childUri, 'dwc:taxonRank': makeTaxonRankUri(childRank) };
|
|
270
350
|
}
|
|
271
351
|
}
|
|
272
|
-
nodes
|
|
352
|
+
nodes[node['@id']] = node;
|
|
353
|
+
}
|
|
354
|
+
for (const child in parents) {
|
|
355
|
+
if (!nodes[child]) {
|
|
356
|
+
nodes[child] = { '@id': child };
|
|
357
|
+
}
|
|
358
|
+
for (const parent of parents[child]) {
|
|
359
|
+
nodes[child]['dwc:parentNameUsageID'] = { '@id': parent };
|
|
360
|
+
}
|
|
273
361
|
}
|
|
274
|
-
|
|
275
|
-
return nodes;
|
|
362
|
+
return Object.values(nodes);
|
|
276
363
|
}
|
|
277
364
|
function makeLinkedDataForScientificName(name) {
|
|
278
365
|
const node = Object.assign(Object.assign({}, makeScientificNameUri(name.scientificNameID)), { '@type': 'dwc:Taxon' });
|
|
@@ -298,12 +385,12 @@ function makeLinkedDataForScientificName(name) {
|
|
|
298
385
|
node['dwc:parentNameUsageID'] = makeScientificNameUri(name.parentNameUsageID);
|
|
299
386
|
}
|
|
300
387
|
const identifiers = [];
|
|
388
|
+
if (name.colTaxonID) {
|
|
389
|
+
identifiers.push(makeColUri(name.colTaxonID));
|
|
390
|
+
}
|
|
301
391
|
if (name.gbifTaxonID) {
|
|
302
392
|
identifiers.push(makeGbifUri(name.gbifTaxonID));
|
|
303
393
|
}
|
|
304
|
-
if (name.colTaxonID) {
|
|
305
|
-
identifiers.push({ '@id': `https://www.checklistbank.org/dataset/309120/taxon/${name.colTaxonID}` });
|
|
306
|
-
}
|
|
307
394
|
if (identifiers.length) {
|
|
308
395
|
node['dwc:taxonID'] = identifiers;
|
|
309
396
|
}
|
|
@@ -316,12 +403,13 @@ function makeLinkedDataForResource(work, files, resourceId) {
|
|
|
316
403
|
resourceId = `${work.get('id')}:0`;
|
|
317
404
|
}
|
|
318
405
|
if (files.resources[resourceId] && files.resources[resourceId].metadata.catalog) {
|
|
319
|
-
resource.fields = files.resources[resourceId].metadata.catalog;
|
|
406
|
+
resource.fields = Object.assign({}, files.resources[resourceId].metadata.catalog);
|
|
320
407
|
}
|
|
321
408
|
if (!resource.has('language')) {
|
|
322
409
|
resource.fields.language = work.get('language');
|
|
323
410
|
}
|
|
324
|
-
|
|
411
|
+
resource.fields.id = resourceId;
|
|
412
|
+
const node = Object.assign(Object.assign({}, makeLinkedDataForWork(resource, files, makeResourceUri)), { '@type': 'bibo:DocumentPart' });
|
|
325
413
|
const types = (_b = (_a = resource.get('key_type')) !== null && _a !== void 0 ? _a : work.get('key_type')) !== null && _b !== void 0 ? _b : [];
|
|
326
414
|
if (types.includes('matrix') || types.includes('algorithm')) {
|
|
327
415
|
node['dcterms:type'] = { '@id': 'http://purl.org/dc/dcmitype/Software' };
|
|
@@ -341,7 +429,7 @@ function makeLinkedDataForResource(work, files, resourceId) {
|
|
|
341
429
|
const taxonNames = (_c = resource.get('taxon')) !== null && _c !== void 0 ? _c : work.get('taxon');
|
|
342
430
|
if (taxonNames) {
|
|
343
431
|
const taxa = mapEntities(taxonNames, files.taxa.entities);
|
|
344
|
-
const coveringTaxon = taxa.length ? getCoveringTaxon(taxa) : null;
|
|
432
|
+
const coveringTaxon = taxa.length ? getCoveringTaxon(taxa, files.taxa.entities) : null;
|
|
345
433
|
if (coveringTaxon !== null) {
|
|
346
434
|
node['ac:taxonCoverage'] = { '@id': coveringTaxon };
|
|
347
435
|
}
|
|
@@ -405,9 +493,10 @@ function makeLinkedDataForResource(work, files, resourceId) {
|
|
|
405
493
|
}
|
|
406
494
|
return node;
|
|
407
495
|
}
|
|
408
|
-
function makeLinkedDataForWork(work, files) {
|
|
496
|
+
function makeLinkedDataForWork(work, files, makeUri = makeWorkUri) {
|
|
409
497
|
const id = work.get('id');
|
|
410
|
-
const node =
|
|
498
|
+
const node = makeUri(id);
|
|
499
|
+
node['@reverse'] = {};
|
|
411
500
|
const languages = work.get('language');
|
|
412
501
|
node['dcterms:language'] = languages.map(language => ({ '@id': `http://id.loc.gov/vocabulary/iso639-1/${language}` }));
|
|
413
502
|
if (work.has('title')) {
|
|
@@ -536,46 +625,60 @@ function makeLinkedDataForWork(work, files) {
|
|
|
536
625
|
}
|
|
537
626
|
});
|
|
538
627
|
}
|
|
628
|
+
if (work.has('part_of')) {
|
|
629
|
+
const containers = work.get('part_of');
|
|
630
|
+
if (work.get('key_type').includes('supplement')) {
|
|
631
|
+
node['bibo:annotates'] = containers.map(makeUri);
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
node['dcterms:isPartOf'] = containers.map(makeUri);
|
|
635
|
+
for (const container of containers) {
|
|
636
|
+
if (!Array.isArray(node['@reverse']['dcterms:hasPart'])) {
|
|
637
|
+
node['@reverse']['dcterms:hasPart'] = [];
|
|
638
|
+
}
|
|
639
|
+
node['@reverse']['dcterms:hasPart'].push(makeUri(container));
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
if (work.has('listed_in')) {
|
|
644
|
+
const referers = work.get('listed_in');
|
|
645
|
+
node['bibo:citedBy'] = node['dcterms:isReferencedBy'] = referers.map(makeUri);
|
|
646
|
+
for (const referer of referers) {
|
|
647
|
+
if (!Array.isArray(node['@reverse']['bibo:cites'])) {
|
|
648
|
+
node['@reverse']['bibo:cites'] = [];
|
|
649
|
+
}
|
|
650
|
+
node['@reverse']['bibo:cites'].push(makeUri(referer));
|
|
651
|
+
if (!Array.isArray(node['@reverse']['dcterms:references'])) {
|
|
652
|
+
node['@reverse']['dcterms:references'] = [];
|
|
653
|
+
}
|
|
654
|
+
node['@reverse']['dcterms:references'].push(makeUri(referer));
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if (work.has('version_of')) {
|
|
658
|
+
const originals = work.get('version_of');
|
|
659
|
+
const originalLanguages = originals.map(id => {
|
|
660
|
+
var _a;
|
|
661
|
+
const [workId, resourceId] = id.split(':');
|
|
662
|
+
const original = files.catalog.get(workId);
|
|
663
|
+
const language = original.get('language').join();
|
|
664
|
+
if (resourceId) {
|
|
665
|
+
const resourceLanguage = (_a = files.resources[id].metadata.catalog) === null || _a === void 0 ? void 0 : _a.language;
|
|
666
|
+
if (resourceLanguage) {
|
|
667
|
+
return resourceLanguage.join(',');
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return language;
|
|
671
|
+
});
|
|
672
|
+
node['bibo:translationOf'] = originals.filter((_, i) => originalLanguages[i] !== languages.join()).map(makeUri);
|
|
673
|
+
node['dcterms:isVersionOf'] = originals.filter(id => work.get('id') !== id).map(makeUri);
|
|
674
|
+
}
|
|
539
675
|
return node;
|
|
540
676
|
}
|
|
541
|
-
function makeLinkedDataForWorks(files) {
|
|
542
|
-
const nodes = {};
|
|
677
|
+
function* makeLinkedDataForWorks(files) {
|
|
543
678
|
const works = files.catalog;
|
|
544
|
-
for (const work of works.entities) {
|
|
545
|
-
nodes[work.get('id')] = makeLinkedDataForWork(work, files);
|
|
546
|
-
}
|
|
547
679
|
for (const work of works.entities) {
|
|
548
680
|
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
|
-
}
|
|
681
|
+
const node = makeLinkedDataForWork(work, files);
|
|
579
682
|
let resourceIndex = 1;
|
|
580
683
|
let resourceId;
|
|
581
684
|
while ((resourceId = `${id}:${resourceIndex++}`) in files.resources) {
|
|
@@ -589,26 +692,58 @@ function makeLinkedDataForWorks(files) {
|
|
|
589
692
|
}
|
|
590
693
|
resource['dcterms:isPartOf'].push({ '@id': node['@id'] });
|
|
591
694
|
}
|
|
695
|
+
yield node;
|
|
592
696
|
}
|
|
593
|
-
return Object.values(nodes);
|
|
594
697
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
process.stdout.write(
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
698
|
+
class Writer {
|
|
699
|
+
constructor(format) {
|
|
700
|
+
this.format = format;
|
|
701
|
+
this.issuer = new jsonld.util.IdentifierIssuer('_:b');
|
|
702
|
+
this.n3parser = new N3.StreamParser();
|
|
703
|
+
this.n3writer = new N3.StreamWriter({ prefixes: PREFIXES });
|
|
704
|
+
if (this.format === 'jsonld') {
|
|
705
|
+
const [start] = JSON.stringify({ '@context': PREFIXES, '@graph': [] }, null, 2).split(/(?<="@graph": \[)/);
|
|
706
|
+
process.stdout.write(start + '\n');
|
|
707
|
+
}
|
|
708
|
+
else if (this.format === 'nquads') {
|
|
709
|
+
// no preparation required
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
this.n3parser.pipe(this.n3writer);
|
|
713
|
+
this.n3writer.pipe(process.stdout);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
write(graph) {
|
|
717
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
718
|
+
if (this.format === 'jsonld') {
|
|
719
|
+
for (const entity of graph) {
|
|
720
|
+
process.stdout.write(JSON.stringify(entity, null, 2).replace(/^/gm, ' ') + ',\n');
|
|
721
|
+
}
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
const document = {
|
|
725
|
+
'@context': PREFIXES,
|
|
726
|
+
'@graph': graph
|
|
727
|
+
};
|
|
728
|
+
const nquads = (yield jsonld.toRDF(document, { format: 'application/n-quads', issuer: this.issuer }));
|
|
729
|
+
if (this.format === 'nquads') {
|
|
730
|
+
process.stdout.write(nquads);
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
this.n3parser.write(nquads);
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
end() {
|
|
737
|
+
if (this.format === 'jsonld') {
|
|
738
|
+
process.stdout.write(' null\n ]\n}\n');
|
|
739
|
+
}
|
|
740
|
+
else if (this.format === 'nquads') {
|
|
741
|
+
// no ending required
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
this.n3parser.end();
|
|
745
|
+
}
|
|
746
|
+
}
|
|
612
747
|
}
|
|
613
748
|
function main() {
|
|
614
749
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -644,8 +779,11 @@ function main() {
|
|
|
644
779
|
}, {}));
|
|
645
780
|
files.resources[id] = { metadata: resources[id], taxa };
|
|
646
781
|
}
|
|
782
|
+
const writer = new Writer(args.values.format);
|
|
783
|
+
for (const entity of makeLinkedDataForWorks(files)) {
|
|
784
|
+
yield writer.write([entity]);
|
|
785
|
+
}
|
|
647
786
|
const graph = [
|
|
648
|
-
...makeLinkedDataForWorks(files),
|
|
649
787
|
...makeLinkedDataForTaxa(files),
|
|
650
788
|
];
|
|
651
789
|
for (const entity of files.authors.entities) {
|
|
@@ -657,11 +795,8 @@ function main() {
|
|
|
657
795
|
for (const entity of files.publishers.entities) {
|
|
658
796
|
graph.push(makeLinkedDataForPublisher(entity));
|
|
659
797
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
'@graph': graph
|
|
663
|
-
};
|
|
664
|
-
writeOutput(document, args.values.format);
|
|
798
|
+
yield writer.write(graph);
|
|
799
|
+
writer.end();
|
|
665
800
|
});
|
|
666
801
|
}
|
|
667
802
|
main().catch((error) => {
|
|
@@ -85,7 +85,7 @@ function checkIntegrity(catalog, resources, field, sheet) {
|
|
|
85
85
|
errors.push({
|
|
86
86
|
entity: id,
|
|
87
87
|
field: field,
|
|
88
|
-
error: `Value "${value}" not mapped`
|
|
88
|
+
error: `Value "${value}" is not mapped`
|
|
89
89
|
});
|
|
90
90
|
}
|
|
91
91
|
}
|
|
@@ -126,8 +126,10 @@ function main(args) {
|
|
|
126
126
|
...checkIntegrity(catalog, resources, 'taxon', taxa),
|
|
127
127
|
...checkIntegrity(catalog, resources, 'region', places)
|
|
128
128
|
];
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
if (errors.length) {
|
|
130
|
+
console.table(errors);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
131
133
|
});
|
|
132
134
|
}
|
|
133
135
|
main(process.argv.slice(2));
|
|
@@ -0,0 +1,132 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
46
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
47
|
+
};
|
|
48
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
const fs_1 = require("fs");
|
|
51
|
+
const n3_1 = __importDefault(require("n3"));
|
|
52
|
+
const shape_map_1 = require("shape-map");
|
|
53
|
+
const shex_1 = __importDefault(require("shex"));
|
|
54
|
+
const neighborhood_rdfjs_1 = require("@shexjs/neighborhood-rdfjs");
|
|
55
|
+
const ShExParser = __importStar(require("@shexjs/parser"));
|
|
56
|
+
const SCHEMA_PATH = 'docs/linked-data/shape.shex';
|
|
57
|
+
const SHAPEMAP_PATH = 'docs/linked-data/shapeMap.sm';
|
|
58
|
+
const BASE = 'https://identification-resources.github.io/assets/data/';
|
|
59
|
+
const SCHEMA_BASE = BASE + SCHEMA_PATH;
|
|
60
|
+
const SHAPEMAP_BASE = BASE + SHAPEMAP_PATH;
|
|
61
|
+
const DATA_BASE = BASE;
|
|
62
|
+
function loadData(path) {
|
|
63
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
64
|
+
const parser = new n3_1.default.Parser();
|
|
65
|
+
const file = yield fs_1.promises.readFile(path, 'utf8');
|
|
66
|
+
const data = new n3_1.default.Store(parser.parse(file));
|
|
67
|
+
return (0, neighborhood_rdfjs_1.ctor)(data);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function loadSchema(path) {
|
|
71
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
72
|
+
const parser = ShExParser.construct(SCHEMA_BASE);
|
|
73
|
+
const file = yield fs_1.promises.readFile(path, 'utf8');
|
|
74
|
+
const schema = parser.parse(file, SCHEMA_BASE);
|
|
75
|
+
return schema;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function loadShapeMap(path) {
|
|
79
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
80
|
+
const parser = shape_map_1.Parser.construct(SHAPEMAP_BASE, { base: SCHEMA_BASE }, { base: DATA_BASE });
|
|
81
|
+
const file = yield fs_1.promises.readFile(path, 'utf8');
|
|
82
|
+
const shapeMap = parser.parse(file);
|
|
83
|
+
return shapeMap;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function resolveShapeMap(shapeMap, db) {
|
|
87
|
+
var _a;
|
|
88
|
+
const pairs = [];
|
|
89
|
+
for (const part of shapeMap) {
|
|
90
|
+
if (typeof part.node === 'string') {
|
|
91
|
+
pairs.push(part);
|
|
92
|
+
}
|
|
93
|
+
else if (part.node.type === 'TriplePattern') {
|
|
94
|
+
const focusSubject = typeof part.node.subject === 'object' && ((_a = part.node.subject) === null || _a === void 0 ? void 0 : _a.term) === shape_map_1.Focus.term;
|
|
95
|
+
const subject = typeof part.node.subject === 'string' ? part.node.subject : null;
|
|
96
|
+
const object = typeof part.node.object === 'string' ? part.node.object : null;
|
|
97
|
+
const quads = db.getQuads(subject, part.node.predicate, object, null);
|
|
98
|
+
for (const quad of quads) {
|
|
99
|
+
pairs.push({
|
|
100
|
+
node: focusSubject ? quad.subject.id : quad.object.id,
|
|
101
|
+
shape: part.shape
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
throw new Error(`Shape map type not supported: "${part.node.type}"`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return pairs;
|
|
110
|
+
}
|
|
111
|
+
function main(args) {
|
|
112
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
113
|
+
const [base, data] = args.map(arg => path.resolve(arg));
|
|
114
|
+
const db = yield loadData(data);
|
|
115
|
+
const schema = yield loadSchema(path.join(base, SCHEMA_PATH));
|
|
116
|
+
const shapeMap = yield loadShapeMap(path.join(base, SHAPEMAP_PATH));
|
|
117
|
+
const nodePairs = resolveShapeMap(shapeMap, db);
|
|
118
|
+
const validator = new shex_1.default.Validator(schema, db);
|
|
119
|
+
const results = validator.validateShapeMap(nodePairs);
|
|
120
|
+
let successes = 0;
|
|
121
|
+
for (const result of results) {
|
|
122
|
+
if (!result.appinfo.errors) {
|
|
123
|
+
successes++;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
console.error('===', result.node, '===');
|
|
127
|
+
console.error(JSON.stringify(result.appinfo, null, 2));
|
|
128
|
+
}
|
|
129
|
+
console.error(`Success: ${successes}/${nodePairs.length}`);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
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.5",
|
|
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",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"loir-validate-catalog": "lib/bin/validate-catalog.js",
|
|
12
12
|
"loir-validate-resources": "lib/bin/validate-resources-text.js",
|
|
13
13
|
"loir-validate-integrity": "lib/bin/validate-integrity.js",
|
|
14
|
+
"loir-validate-linked-data": "lib/bin/validate-linked-data.js",
|
|
14
15
|
"loir-resources-process": "lib/bin/process-resources.js",
|
|
15
16
|
"loir-resources-index": "lib/bin/process-resources-index.js",
|
|
16
17
|
"loir-resources-check-diff": "lib/bin/check-resources-diff.js",
|
|
@@ -40,6 +41,7 @@
|
|
|
40
41
|
"js-yaml": "^5.2.1",
|
|
41
42
|
"jsonld": "^9.0.0",
|
|
42
43
|
"n3": "^2.1.1",
|
|
44
|
+
"shex": "^1.0.0-alpha.30",
|
|
43
45
|
"spdx-license-list": "^6.6.0"
|
|
44
46
|
},
|
|
45
47
|
"devDependencies": {
|
|
@@ -53,6 +55,7 @@
|
|
|
53
55
|
"@typescript-eslint/parser": "^8.32.0",
|
|
54
56
|
"conventional-changelog-cli": "^5.0.0",
|
|
55
57
|
"eslint": "^10.6.0",
|
|
58
|
+
"patch-package": "^8.0.1",
|
|
56
59
|
"typescript": "^6.0.3"
|
|
57
60
|
}
|
|
58
61
|
}
|