@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('gbif')) {
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('parent_taxa')) !== null && _a !== void 0 ? _a : []);
139
- if (taxon.has('gbif')) {
140
- parents.push(taxon.get('gbif'));
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
- return ancestors.length ? makeGbifUri(ancestors[ancestors.length - 1])['@id'] : null;
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': `${PREFIX}taxon/${taxon.get('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 gbifTaxa = {};
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('ancestors_gbif')) !== null && _a !== void 0 ? _a : [];
255
- if (ancestors.length) {
256
- node['dwc:parentNameUsageID'] = makeGbifUri(ancestors[ancestors.length - 1]);
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
- else if (taxon.get('id') !== 'T141') {
259
- node['dwc:parentNameUsageID'] = { '@id': `${PREFIX}taxon/T141` };
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
- gbifTaxa[ancestors[i]] = Object.assign(Object.assign({}, makeGbifUri(ancestors[i])), { 'dwc:taxonRank': makeTaxonRankUri(GBIF_RANKS[i]), 'dwc:parentNameUsageID': i ? makeGbifUri(ancestors[i - 1]) : { '@id': `${PREFIX}taxon/T141` } });
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
- gbifTaxa[child] = Object.assign(Object.assign({}, makeGbifUri(child)), { 'dwc:taxonRank': makeTaxonRankUri(childRank) });
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.push(node);
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
- nodes.push(...Object.values(gbifTaxa));
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
- const node = Object.assign(Object.assign({}, makeLinkedDataForWork(resource, files)), { '@id': `${PREFIX}resource/${resourceId}`, '@type': 'bibo:DocumentPart' });
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 = makeWorkUri(id);
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 = nodes[id];
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
- function writeOutput(document_1) {
596
- return __awaiter(this, arguments, void 0, function* (document, format = 'jsonld') {
597
- if (format === 'jsonld') {
598
- process.stdout.write(JSON.stringify(document, null, 2));
599
- return;
600
- }
601
- const nquads = yield jsonld.toRDF(document, { format: 'application/n-quads' });
602
- if (format === 'nquads') {
603
- process.stdout.write(nquads);
604
- return;
605
- }
606
- const parser = new N3.StreamParser();
607
- const writer = new N3.StreamWriter({ prefixes: PREFIXES });
608
- parser.write(nquads);
609
- parser.pipe(writer);
610
- writer.pipe(process.stdout);
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
- const document = {
661
- '@context': PREFIXES,
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
- console.table(errors);
130
- process.exit(errors.length ? 1 : 0);
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,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -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
- name = name.replace(/^(st|r|ab|f|var|ssp|subsp)\. /, '');
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",
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
  }