@larsgw/formica 0.1.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.
Files changed (65) hide show
  1. package/.eslintrc.js +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +19 -0
  4. package/lib/bin/process-resources-index.d.ts +1 -0
  5. package/lib/bin/process-resources-index.js +142 -0
  6. package/lib/bin/process-resources.d.ts +1 -0
  7. package/lib/bin/process-resources.js +594 -0
  8. package/lib/bin/util.d.ts +16 -0
  9. package/lib/bin/util.js +125 -0
  10. package/lib/bin/validate-catalog.d.ts +2 -0
  11. package/lib/bin/validate-catalog.js +92 -0
  12. package/lib/bin/validate-resources-text.d.ts +2 -0
  13. package/lib/bin/validate-resources-text.js +78 -0
  14. package/lib/catalog/entities.d.ts +12 -0
  15. package/lib/catalog/entities.js +111 -0
  16. package/lib/catalog/entity.d.ts +13 -0
  17. package/lib/catalog/entity.js +103 -0
  18. package/lib/catalog/index.d.ts +4 -0
  19. package/lib/catalog/index.js +33 -0
  20. package/lib/catalog/tables/author.d.ts +4 -0
  21. package/lib/catalog/tables/author.js +33 -0
  22. package/lib/catalog/tables/index.d.ts +2 -0
  23. package/lib/catalog/tables/index.js +13 -0
  24. package/lib/catalog/tables/place.d.ts +4 -0
  25. package/lib/catalog/tables/place.js +32 -0
  26. package/lib/catalog/tables/publisher.d.ts +4 -0
  27. package/lib/catalog/tables/publisher.js +33 -0
  28. package/lib/catalog/tables/work.d.ts +5 -0
  29. package/lib/catalog/tables/work.js +82 -0
  30. package/lib/catalog/value.d.ts +19 -0
  31. package/lib/catalog/value.js +49 -0
  32. package/lib/csv.d.ts +2 -0
  33. package/lib/csv.js +37 -0
  34. package/lib/index.d.ts +3 -0
  35. package/lib/index.js +6 -0
  36. package/lib/resources/diff-resource.d.ts +7 -0
  37. package/lib/resources/diff-resource.js +152 -0
  38. package/lib/resources/index.d.ts +1 -0
  39. package/lib/resources/index.js +6 -0
  40. package/lib/resources/parse-text.d.ts +2 -0
  41. package/lib/resources/parse-text.js +499 -0
  42. package/lib/types.d.ts +62 -0
  43. package/lib/types.js +0 -0
  44. package/package.json +42 -0
  45. package/src/bin/process-resources-index.ts +73 -0
  46. package/src/bin/process-resources.ts +406 -0
  47. package/src/bin/util.ts +74 -0
  48. package/src/bin/validate-catalog.ts +37 -0
  49. package/src/bin/validate-resources-text.ts +25 -0
  50. package/src/catalog/entities.ts +62 -0
  51. package/src/catalog/entity.ts +113 -0
  52. package/src/catalog/index.ts +32 -0
  53. package/src/catalog/tables/author.ts +13 -0
  54. package/src/catalog/tables/index.ts +12 -0
  55. package/src/catalog/tables/place.ts +12 -0
  56. package/src/catalog/tables/publisher.ts +13 -0
  57. package/src/catalog/tables/work.ts +62 -0
  58. package/src/catalog/value.ts +48 -0
  59. package/src/csv.ts +33 -0
  60. package/src/index.ts +3 -0
  61. package/src/module.d.ts +105 -0
  62. package/src/resources/diff-resource.ts +155 -0
  63. package/src/resources/index.ts +4 -0
  64. package/src/resources/parse-text.ts +519 -0
  65. package/tsconfig.json +11 -0
@@ -0,0 +1,499 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.parseFileHeader = exports.parseFile = void 0;
15
+ var yaml = require("js-yaml");
16
+ var diff_resource_1 = require("./diff-resource");
17
+ var RANKS = [
18
+ 'class',
19
+ 'infraclass',
20
+ 'superorder',
21
+ 'order',
22
+ 'suborder',
23
+ 'infraorder',
24
+ 'superfamily',
25
+ 'family',
26
+ 'subfamily',
27
+ 'tribe',
28
+ 'subtribe',
29
+ 'genus',
30
+ 'subgenus',
31
+ 'section',
32
+ 'subsection',
33
+ 'series',
34
+ 'group',
35
+ 'subgroup',
36
+ 'aggregate',
37
+ 'complex',
38
+ 'species',
39
+ 'subspecies',
40
+ 'variety',
41
+ 'form',
42
+ 'aberration',
43
+ 'race',
44
+ 'stirps' // not ICZN
45
+ ];
46
+ var DWC_RANKS = [
47
+ 'kingdom',
48
+ 'phylum',
49
+ 'class',
50
+ 'order',
51
+ 'family',
52
+ 'subfamily',
53
+ 'genus',
54
+ 'subgenus'
55
+ ];
56
+ var TAXONOMIC_STATUS = {
57
+ '>': 'incorrect',
58
+ '+': 'heterotypic synonym',
59
+ '=': 'synonym'
60
+ };
61
+ var INDET_SUFFIXES = new Set([
62
+ 'sp.',
63
+ 'spec.',
64
+ 'indet.',
65
+ 'sp. indet.',
66
+ 'spec. indet.'
67
+ ]);
68
+ var RANK_LABELS = {
69
+ 'subspecies': 'subsp.',
70
+ 'variety': 'var.',
71
+ 'form': 'f.',
72
+ 'aberration': 'ab.',
73
+ 'race': 'r.',
74
+ 'stirps': 'st.'
75
+ };
76
+ var RANK_LABELS_REVERSE = {
77
+ 'st': 'stirps',
78
+ 'r': 'race',
79
+ 'ab': 'aberration',
80
+ 'f': 'form',
81
+ 'var': 'variety',
82
+ 'ssp': 'subspecies',
83
+ 'subsp': 'subspecies'
84
+ };
85
+ var NAME_PATTERN = new RegExp('^' +
86
+ // $1 main name part
87
+ '(\\S+)' +
88
+ // $2 optional author citation
89
+ '(?: ' +
90
+ // but not auct(t)., etc.
91
+ '(?!auctt?\\.|(?:syn|comb|sp|spec)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.))' +
92
+ '(' +
93
+ // $2.1 anything in parentheses
94
+ '\\(.+?\\)' +
95
+ '|' +
96
+ // $2.2 anything followed by a year
97
+ '.+?\\d{4}\\)?' +
98
+ '|' +
99
+ // $2.3 name(, name)* & name
100
+ '.+(?:, .+)* & \\S+' +
101
+ '|' +
102
+ // $2.4 name y name
103
+ '\\S+ [yY] \\S+' +
104
+ '|' +
105
+ // $2.5 name( in name)
106
+ '\\p{Lu}\\S*(?: in \\S+)?' +
107
+ '))?' +
108
+ // $3 optional notes
109
+ '(?:,? (.+))?' +
110
+ '$', 'u');
111
+ /**
112
+ * Structure
113
+ * $1 genus+subgenus (+ trailing space): (?:([A-Z]\S+) (?:\(([A-Z]\S+?)\) )?)?
114
+ * $1.1 genus: ([A-Z]\S+)
115
+ * $1.2 subgenus: (?:\(([A-Z]\S+?)\) )?
116
+ * $2 species: ([a-z]\S+)
117
+ */
118
+ var BINAME_PATTERN = /^(?:([A-Z]\S+) (?:\(([A-Z]\S+?)\) )?)?([a-z]\S+) ?/;
119
+ function compareRanks(a, b) {
120
+ return RANKS.indexOf(a) - RANKS.indexOf(b);
121
+ }
122
+ function capitalize(name) {
123
+ return name[0].toUpperCase() + name.slice(1).toLowerCase();
124
+ }
125
+ function isUpperCase(name) {
126
+ return name === name.toUpperCase();
127
+ }
128
+ function getSynonymRank(name, rank) {
129
+ var BINAME_PATTERN = /^([A-Z]\S+ (\([A-Z]\S+\) )?)?(x )?[a-z0-9-]+(?= |$)/;
130
+ if (!BINAME_PATTERN.test(name)) {
131
+ return rank;
132
+ }
133
+ var rest = name.replace(BINAME_PATTERN, '');
134
+ var rankPrefix = rest.match(/^ (st|r|ab|f|var|ssp|subsp)\. /);
135
+ if (rankPrefix) {
136
+ return RANK_LABELS_REVERSE[rankPrefix[1]];
137
+ }
138
+ else if (/^ (?!sensu)[a-z0-9-]+($| )/.test(rest)) {
139
+ return 'subspecies';
140
+ }
141
+ else {
142
+ return 'species';
143
+ }
144
+ }
145
+ function capitalizeAuthors(authors) {
146
+ return authors
147
+ .replace(/[^\x00-\x40\x5B-\x60\x7B-\x7F]+/g, // eslint-disable-line no-control-regex
148
+ function (// eslint-disable-line no-control-regex
149
+ name) { return isUpperCase(name) ? capitalize(name) : name; })
150
+ .replace(/ Y /g, ' y ');
151
+ }
152
+ function parseName(name, rank, parent) {
153
+ var item = {};
154
+ // Synonyms have the accepted name usage as 'parent'.
155
+ var isSynonym = /^[+=>] /.test(name);
156
+ if (isSynonym) {
157
+ item.taxonomicStatus = TAXONOMIC_STATUS[name[0]];
158
+ name = name.replace(/^[+=>] (\? ?)?/, '');
159
+ rank = getSynonymRank(name, parent.taxonRank);
160
+ }
161
+ else {
162
+ item.taxonomicStatus = 'accepted';
163
+ }
164
+ // Clusters
165
+ if (/^\[(_|\d+)\] /.test(name)) {
166
+ name = name.replace(/^\[(_|\d+)\] /, '');
167
+ }
168
+ // Parent context is used for parsing and formatting binomial names.
169
+ var parentContext = __assign({}, parent);
170
+ if (parent.incorrect) {
171
+ parentContext.incorrect = __assign({}, parent.incorrect);
172
+ }
173
+ // The parent context should be amended in the two cases where binomial names
174
+ // are truly accepted: synonyms and species (and below) without parents (resp.
175
+ // genera and genera and species) to provide parts of the name.
176
+ if (isSynonym || !parentContext.genus || (compareRanks('species', rank) < 0 && !parentContext.specificEpithet)) {
177
+ var _a = name.match(BINAME_PATTERN) || [], genus = _a[1], subgenus = _a[2], species = _a[3];
178
+ if (genus) {
179
+ parentContext.genus = capitalize(genus);
180
+ if (parentContext.incorrect)
181
+ parentContext.incorrect.genus = capitalize(genus);
182
+ }
183
+ if (subgenus) {
184
+ parentContext.subgenus = capitalize(subgenus);
185
+ if (parentContext.incorrect)
186
+ parentContext.incorrect.subgenus = capitalize(subgenus);
187
+ }
188
+ else if (genus) {
189
+ // If a genus is given but no subgenus, remove it from the parent context
190
+ delete parentContext.subgenus;
191
+ if (parentContext.incorrect)
192
+ delete parentContext.incorrect.subgenus;
193
+ }
194
+ if (species) {
195
+ parentContext.specificEpithet = species;
196
+ if (parentContext.incorrect)
197
+ parentContext.incorrect.specificEpithet = species;
198
+ }
199
+ }
200
+ // In taxa of group, species or lower, the name should just contain the
201
+ // (inter)specific epithet and the author information & remarks when processing
202
+ // further.
203
+ if (compareRanks('group', rank) <= 0) {
204
+ var parseContext = parentContext.incorrect || parentContext;
205
+ if (!parseContext.genus) {
206
+ parseContext.genus = name.split(' ', 1)[0];
207
+ }
208
+ var genusPrefix = new RegExp("^".concat(parentContext.genus, " (\\(.*?\\) )?"), 'i');
209
+ if (name[0] === parentContext.genus[0]) {
210
+ name = name.replace(genusPrefix, '');
211
+ }
212
+ if (compareRanks('species', rank) < 0) {
213
+ var speciesPrefix = parseContext.specificEpithet + ' ';
214
+ if (name.startsWith(speciesPrefix)) {
215
+ name = name.slice(speciesPrefix.length).replace(/^(st|r|ab|f|var|ssp|subsp)\. /, '');
216
+ }
217
+ }
218
+ }
219
+ // Hybrids
220
+ if (rank === 'species' && /^x /.test(name)) {
221
+ name = '\u00D7' + name.slice(2);
222
+ }
223
+ // Divide the name into the main scientific name (only the epithet for taxa
224
+ // lower than genus), the authorship information, and optionally remarks
225
+ var nameParts = name.match(NAME_PATTERN);
226
+ if (!nameParts) {
227
+ throw new Error("Taxon \"".concat(name, "\" could not be parsed"));
228
+ }
229
+ var _ = nameParts[0], taxon = nameParts[1], _b = nameParts[2], citation = _b === void 0 ? '' : _b, notes = nameParts[3];
230
+ item.scientificNameAuthorship = capitalizeAuthors(citation);
231
+ item.taxonRemarks = notes;
232
+ item.taxonRank = rank;
233
+ if (/[^\p{L}0-9\u{00D7}\- ]/u.test(taxon) && !INDET_SUFFIXES.has(taxon)) {
234
+ throw new Error("Taxon name contains unexpected characters: \"".concat(taxon, "\""));
235
+ }
236
+ // Validate names and recompose binomial and trinomial names
237
+ if (compareRanks('group', rank) > 0) {
238
+ item.scientificName = capitalize(taxon);
239
+ if (item.scientificName[0] !== taxon[0]) {
240
+ throw new Error("Taxon name (".concat(rank, ") should be capitalized: \"").concat(taxon, "\""));
241
+ }
242
+ }
243
+ else if (rank === 'group') {
244
+ item.genericName = parentContext.genus;
245
+ item.infragenericEpithet = parentContext.subgenus;
246
+ var specificEpithet = taxon.toLowerCase().replace(/(-group)?$/, '');
247
+ item.scientificName = "".concat(item.genericName, " ").concat(specificEpithet, "-group");
248
+ if (taxon.toLowerCase() !== taxon) {
249
+ console.log(item, taxon);
250
+ throw new Error("Group name should be lowercase: \"".concat(taxon, "\""));
251
+ }
252
+ }
253
+ else if (rank === 'subgroup') {
254
+ item.genericName = parentContext.genus;
255
+ item.infragenericEpithet = parentContext.subgenus;
256
+ var specificEpithet = taxon.toLowerCase().replace(/(-subgroup)?$/, '');
257
+ item.scientificName = "".concat(item.genericName, " ").concat(specificEpithet, "-subgroup");
258
+ if (taxon.toLowerCase() !== taxon) {
259
+ console.log(item, taxon);
260
+ throw new Error("Subgroup name should be lowercase: \"".concat(taxon, "\""));
261
+ }
262
+ }
263
+ else if (rank === 'species') {
264
+ item.genericName = parentContext.genus;
265
+ item.infragenericEpithet = parentContext.subgenus;
266
+ item.specificEpithet = taxon.toLowerCase();
267
+ item.scientificName = "".concat(item.genericName, " ").concat(item.specificEpithet);
268
+ if (item.specificEpithet !== taxon) {
269
+ console.log(item, taxon);
270
+ throw new Error("Specific epithet should be lowercase: \"".concat(taxon, "\""));
271
+ }
272
+ }
273
+ else if (compareRanks('species', rank) < 0) {
274
+ item.genericName = parentContext.genus;
275
+ item.infragenericEpithet = parentContext.subgenus;
276
+ item.specificEpithet = parentContext.specificEpithet;
277
+ item.intraspecificEpithet = taxon.toLowerCase();
278
+ // If possible, names below species should have abbreviations for ranks,
279
+ // like "subsp."
280
+ var nameParts_1 = [
281
+ item.genericName,
282
+ item.specificEpithet,
283
+ item.intraspecificEpithet
284
+ ];
285
+ if (item.taxonRank in RANK_LABELS) {
286
+ nameParts_1.splice(2, 0, RANK_LABELS[item.taxonRank]);
287
+ }
288
+ item.scientificName = nameParts_1.join(' ');
289
+ if (item.intraspecificEpithet !== taxon) {
290
+ console.log(item, taxon);
291
+ throw new Error("Intraspecific epithet should be lowercase: \"".concat(taxon, "\""));
292
+ }
293
+ }
294
+ // Re-add authorship information
295
+ item.scientificNameOnly = item.scientificName;
296
+ if (item.scientificNameAuthorship) {
297
+ item.scientificName += " ".concat(item.scientificNameAuthorship);
298
+ }
299
+ // Amend "parent" with corrections
300
+ if (item.taxonomicStatus === 'incorrect') {
301
+ var itemAsObject = item;
302
+ var parentAsObject = parent;
303
+ parent.incorrect = __assign({}, parent);
304
+ for (var key in item) {
305
+ if (key !== 'taxonomicStatus') {
306
+ parentAsObject[key] = itemAsObject[key];
307
+ }
308
+ }
309
+ }
310
+ return item;
311
+ }
312
+ function parseHeader(header) {
313
+ var config = yaml.load(header);
314
+ if (typeof config !== 'object' || Array.isArray(config) || config === null) {
315
+ throw new SyntaxError('yaml header should be an object');
316
+ }
317
+ // Invalid configuration
318
+ var levels;
319
+ if (!('levels' in config)) {
320
+ levels = [];
321
+ }
322
+ else if (!Array.isArray(config.levels)) {
323
+ throw new SyntaxError('"levels" should be an array');
324
+ }
325
+ else {
326
+ levels = config.levels;
327
+ }
328
+ var scope;
329
+ if (!('scope' in config)) {
330
+ scope = [];
331
+ }
332
+ else if (!Array.isArray(config.scope)) {
333
+ throw new SyntaxError('"scope" should be an array');
334
+ }
335
+ else {
336
+ scope = config.scope;
337
+ }
338
+ // No taxon ranks
339
+ if (levels.length === 0) {
340
+ throw new SyntaxError('Resource contains no taxa');
341
+ }
342
+ // Invalid taxon ranks
343
+ var invalidTaxonRanks = levels.filter(function (rank) { return !RANKS.includes(rank); });
344
+ if (invalidTaxonRanks.length) {
345
+ throw new SyntaxError("\"levels\" contains invalid values: ".concat(invalidTaxonRanks.join(', ')));
346
+ }
347
+ var metadata = { levels: levels, scope: scope };
348
+ if ('catalog' in config && typeof config.catalog === 'object' && config.catalog !== null) {
349
+ metadata.catalog = config.catalog;
350
+ }
351
+ return metadata;
352
+ }
353
+ function parseResource(resource) {
354
+ var _a = resource.split(/(\n---\n+)/), header = _a[0], _ = _a[1], rest = _a.slice(2);
355
+ var config = parseHeader(header);
356
+ var content = rest.join('');
357
+ // Check for too much indentation
358
+ var longerIndent = new RegExp("^( ){".concat(config.levels.length - 1, "}(?! [+=>] ) "), 'm');
359
+ var longerIndentMatch = content.match(longerIndent);
360
+ if (longerIndentMatch !== null) {
361
+ var offset = longerIndentMatch.index;
362
+ var line = (content.slice(0, offset).match(/\n/g) || []).length + 1;
363
+ throw new SyntaxError("Too much indentation at ".concat(line, ":0\n").concat(content.slice(offset).split('\n', 1), "\n^"));
364
+ }
365
+ return [config, content];
366
+ }
367
+ function parseResourceContent(content, resource, oldIds) {
368
+ var idBase = "".concat(resource.id, ":");
369
+ var data = resource.taxa;
370
+ var id = 0;
371
+ var parents = [];
372
+ var groupIndent = 0;
373
+ var previousId = '';
374
+ var newIdOffset = Math.max.apply(Math, oldIds);
375
+ var _loop_1 = function (line, type) {
376
+ if (type === diff_resource_1.ResourceDiffType.Deleted) {
377
+ id++;
378
+ return "continue";
379
+ }
380
+ var lineIndent = line.match(/^ */)[0].length;
381
+ if (lineIndent > groupIndent) {
382
+ // Do not count synonyms as parents
383
+ if (data[previousId] && data[previousId].taxonomicStatus === 'accepted') {
384
+ parents.push(previousId);
385
+ }
386
+ else {
387
+ parents.push(null);
388
+ }
389
+ // Handle skips in indentation levels,
390
+ // e.g. if a certain genus has only species
391
+ // whereas other genera in the same key also
392
+ // have subgenera
393
+ if ((lineIndent - groupIndent) > 2) {
394
+ var gap = (lineIndent - groupIndent - 2) / 2;
395
+ for (var i = 0; i < gap; i++) {
396
+ parents.push(null);
397
+ }
398
+ }
399
+ groupIndent = lineIndent;
400
+ }
401
+ else if (lineIndent < groupIndent) {
402
+ parents = parents.slice(0, lineIndent / 2);
403
+ groupIndent = lineIndent;
404
+ }
405
+ var parentId = parents.reduce(function (grandparent, parent) { return parent || grandparent; }, null);
406
+ var parent_1 = parentId === null ? {} : data[parentId];
407
+ var name_1 = line.slice(groupIndent);
408
+ var rank = resource.metadata.levels[groupIndent / 2];
409
+ var item = parseName(name_1, rank, parent_1);
410
+ var isSynonym = item.taxonomicStatus !== 'accepted';
411
+ var isIndet = Array.from(INDET_SUFFIXES).some(function (suffix) { return name_1.endsWith(' ' + suffix); });
412
+ if (item.taxonomicStatus === 'incorrect' || isIndet) {
413
+ return "continue";
414
+ }
415
+ if (type === diff_resource_1.ResourceDiffType.Added) {
416
+ newIdOffset++;
417
+ item.scientificNameID = idBase + newIdOffset.toString();
418
+ }
419
+ else {
420
+ id++;
421
+ item.scientificNameID = idBase + (oldIds[id - 1] || id).toString();
422
+ }
423
+ previousId = item.scientificNameID;
424
+ item.parentNameUsageID = isSynonym ? undefined : parent_1.scientificNameID;
425
+ item.parentNameUsage = isSynonym ? undefined : parent_1.scientificName;
426
+ item.acceptedNameUsageID = isSynonym ? parent_1.scientificNameID : undefined;
427
+ item.acceptedNameUsage = isSynonym ? parent_1.scientificName : undefined;
428
+ item.collectionCode = idBase.slice(0, -1);
429
+ for (var _b = 0, DWC_RANKS_1 = DWC_RANKS; _b < DWC_RANKS_1.length; _b++) {
430
+ var rank_1 = DWC_RANKS_1[_b];
431
+ var itemAsObject = item;
432
+ var parentAsObject = parent_1;
433
+ itemAsObject[rank_1] = undefined;
434
+ if (parentAsObject[rank_1]) {
435
+ itemAsObject[rank_1] = parentAsObject[rank_1];
436
+ }
437
+ if (item.taxonRank === rank_1) {
438
+ itemAsObject[rank_1] = item.scientificNameOnly;
439
+ }
440
+ }
441
+ if (item.genericName && !item.genus) {
442
+ item.genus = item.genericName;
443
+ }
444
+ if (item.infragenericEpithet && !item.subgenus) {
445
+ item.subgenus = item.infragenericEpithet;
446
+ }
447
+ if (isSynonym) {
448
+ item.higherClassification = parent_1.higherClassification;
449
+ }
450
+ else if (parent_1.higherClassification) {
451
+ item.higherClassification = parent_1.higherClassification + " | ".concat(parent_1.scientificNameOnly);
452
+ }
453
+ else if (parentId) {
454
+ item.higherClassification = parent_1.scientificNameOnly;
455
+ }
456
+ data[item.scientificNameID] = item;
457
+ };
458
+ for (var _i = 0, content_1 = content; _i < content_1.length; _i++) {
459
+ var _a = content_1[_i], line = _a.text, type = _a.type;
460
+ _loop_1(line, type);
461
+ }
462
+ return resource;
463
+ }
464
+ function splitResources(file) {
465
+ return file.split('\n\n===\n\n');
466
+ }
467
+ function parseFile(file, id, old) {
468
+ var oldResources = old ? splitResources(old.txt) : [];
469
+ return splitResources(file).map(function (resource, index) {
470
+ var _a = parseResource(resource), config = _a[0], content = _a[1];
471
+ var template = {
472
+ id: "".concat(id, ":").concat(index + 1),
473
+ file: "".concat(id, "-").concat(index + 1),
474
+ workId: id,
475
+ metadata: config,
476
+ taxa: {}
477
+ };
478
+ var diff;
479
+ if (oldResources[index]) {
480
+ diff = (0, diff_resource_1.createDiff)(content, parseResource(oldResources[index])[1]);
481
+ }
482
+ else {
483
+ diff = (0, diff_resource_1.createDiff)(content, content);
484
+ }
485
+ var oldIds = [];
486
+ if (old) {
487
+ for (var _i = 0, _b = old.dwc[index].slice(1); _i < _b.length; _i++) {
488
+ var row = _b[_i];
489
+ oldIds.push(parseInt(row[0].split(':')[2]));
490
+ }
491
+ }
492
+ return parseResourceContent(diff, template, oldIds);
493
+ });
494
+ }
495
+ exports.parseFile = parseFile;
496
+ function parseFileHeader(file) {
497
+ return splitResources(file).map(function (resource) { return parseResource(resource)[0]; });
498
+ }
499
+ exports.parseFileHeader = parseFileHeader;
package/lib/types.d.ts ADDED
@@ -0,0 +1,62 @@
1
+ type Value = string[] | string;
2
+ type SingleValue = string;
3
+ interface FieldError {
4
+ field: string;
5
+ error: string;
6
+ }
7
+ interface WorkError extends FieldError {
8
+ entity: WorkId;
9
+ }
10
+ type Rank = string;
11
+ type DwcRank = Rank;
12
+ type TaxonStatus = string;
13
+ type TaxonId = string;
14
+ type ResourceId = string;
15
+ type WorkId = string;
16
+ interface WorkingTaxon {
17
+ scientificNameID?: TaxonId;
18
+ scientificName?: string;
19
+ scientificNameAuthorship?: string;
20
+ genericName?: string;
21
+ infragenericEpithet?: string;
22
+ specificEpithet?: string;
23
+ intraspecificEpithet?: string;
24
+ taxonRank?: Rank;
25
+ taxonRemarks?: string;
26
+ collectionCode?: ResourceId;
27
+ taxonomicStatus?: TaxonStatus;
28
+ acceptedNameUsageID?: TaxonId;
29
+ acceptedNameUsage?: string;
30
+ parentNameUsageID?: TaxonId;
31
+ parentNameUsage?: string;
32
+ kingdom?: string;
33
+ phylum?: string;
34
+ class?: string;
35
+ order?: string;
36
+ family?: string;
37
+ subfamily?: string;
38
+ genus?: string;
39
+ subgenus?: string;
40
+ higherClassification?: string;
41
+ scientificNameOnly?: string;
42
+ incorrect?: WorkingTaxon;
43
+ }
44
+ interface Taxon extends WorkingTaxon {
45
+ scientificNameID: TaxonId;
46
+ scientificName: string;
47
+ taxonRank: Rank;
48
+ collectionCode: ResourceId;
49
+ taxonomicStatus: string;
50
+ }
51
+ interface ResourceMetadata {
52
+ levels: Rank[];
53
+ scope: string[];
54
+ catalog?: object;
55
+ }
56
+ interface Resource {
57
+ id: string;
58
+ file: string;
59
+ workId: string;
60
+ metadata: ResourceMetadata;
61
+ taxa: Record<TaxonId, Taxon>;
62
+ }
package/lib/types.js ADDED
File without changes
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@larsgw/formica",
3
+ "version": "0.1.0",
4
+ "description": "SDK and tools for data from the Library of Identification Resources",
5
+ "main": "lib/index.js",
6
+ "types": "lib/index.d.ts",
7
+ "bin": {
8
+ "loir-validate-catalog": "./lib/bin/validate-catalog.js",
9
+ "loir-validate-resources": "./lib/bin/validate-resources-text.js",
10
+ "loir-resources-process": "./lib/bin/process-resources.js",
11
+ "loir-resources-index": "./lib/bin/process-resources-index.js"
12
+ },
13
+ "scripts": {
14
+ "lint": "eslint src",
15
+ "build": "tsc -d",
16
+ "preversion": "npm run lint",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/identification-resources/formica.git"
22
+ },
23
+ "author": "Lars Willighagen (https://larsgw.github.io/)",
24
+ "license": "MIT",
25
+ "bugs": {
26
+ "url": "https://github.com/identification-resources/formica/issues"
27
+ },
28
+ "homepage": "https://github.com/identification-resources/formica#readme",
29
+ "dependencies": {
30
+ "ietf-language-tag-regex": "^0.0.5",
31
+ "js-yaml": "^4.1.0",
32
+ "spdx-license-list": "^6.6.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/js-yaml": "^4.0.5",
36
+ "@types/node": "^18.14.1",
37
+ "@typescript-eslint/eslint-plugin": "^5.54.0",
38
+ "@typescript-eslint/parser": "^5.54.0",
39
+ "eslint": "^8.35.0",
40
+ "typescript": "^4.9.5"
41
+ }
42
+ }
@@ -0,0 +1,73 @@
1
+ import { promises as fs, existsSync as fileExists } from 'fs'
2
+ import * as path from 'path'
3
+
4
+ import { csv, resources } from '../index'
5
+ import { numericSort } from './util'
6
+
7
+ interface AmendedResourceMetadata extends ResourceMetadata {
8
+ id: ResourceId,
9
+ taxonCount: number
10
+ }
11
+
12
+ /* eslint-disable @typescript-eslint/no-explicit-any */
13
+ function sortObject (object: Record<string, any>): Record<string, any> {
14
+ const sorted: Record<string, any> = {}
15
+ for (const key of Object.keys(object).sort(numericSort)) {
16
+ sorted[key] = object[key]
17
+ }
18
+ return sorted
19
+ }
20
+ /* eslint-enable @typescript-eslint/no-explicit-any */
21
+
22
+ async function main (args: string[]): Promise<void> {
23
+ const REPO_ROOT = path.resolve(args[0])
24
+
25
+ const files = await fs.readdir(path.join(REPO_ROOT, 'txt'))
26
+
27
+ const gbifIndex: Record<string, TaxonId[]> = {}
28
+ const resourceIndex: Record<TaxonId, AmendedResourceMetadata> = {}
29
+
30
+ await Promise.all(files.map(async function (fileName) {
31
+ if (!fileName.endsWith('.txt')) { return }
32
+ const id = fileName.slice(0, -4)
33
+ const file = await fs.readFile(path.join(REPO_ROOT, 'txt', fileName), 'utf-8')
34
+
35
+ return Promise.all(resources.parseTextFileHeader(file).map(async function (resource, index) {
36
+ const amendedResource = {
37
+ ...resource,
38
+ id: `${id}:${index + 1}`,
39
+ taxonCount: 0
40
+ }
41
+
42
+ const dwcFile = path.join(REPO_ROOT, 'dwc', `${id}-${index + 1}.csv`)
43
+ if (!fileExists(dwcFile)) {
44
+ return
45
+ }
46
+
47
+ const [_header, ...dwc] = csv.parseCsv(await fs.readFile(dwcFile, 'utf-8'))
48
+ for (const taxon of dwc) {
49
+ const gbifId = taxon[25]
50
+ if (gbifId) {
51
+ if (!(gbifId in gbifIndex)) {
52
+ gbifIndex[gbifId] = []
53
+ }
54
+ gbifIndex[gbifId].push(taxon[0])
55
+ gbifIndex[gbifId].sort(numericSort)
56
+ }
57
+ amendedResource.taxonCount += 1
58
+ }
59
+
60
+ resourceIndex[amendedResource.id] = amendedResource
61
+ }))
62
+ }))
63
+
64
+ await Promise.all([
65
+ fs.writeFile(path.join(REPO_ROOT, 'gbif.index.json'), JSON.stringify(sortObject(gbifIndex), null, 2)),
66
+ fs.writeFile(path.join(REPO_ROOT, 'index.json'), JSON.stringify(sortObject(resourceIndex), null, 2))
67
+ ])
68
+ }
69
+
70
+ main(process.argv.slice(2)).catch(error => {
71
+ console.error(error)
72
+ process.exit(1)
73
+ })