@larsgw/formica 0.6.5 → 0.6.7
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/CHANGELOG.md +25 -0
- package/lib/bin/process-resources-problems.js +177 -0
- package/lib/resources/parse-text.js +50 -34
- package/lib/resources/resource.d.ts +4 -0
- package/lib/resources/resource.js +30 -0
- package/package.json +2 -2
- package/src/bin/process-resources.ts +0 -1
- package/src/module.d.ts +3 -0
- package/src/resources/parse-text.ts +56 -39
- package/src/resources/resource.ts +10 -0
- package/test/resources.js +13 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
## [0.6.7](https://github.com/identification-resources/formica/compare/v0.6.6...v0.6.7) (2025-02-26)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **resources:** keep uncorrected name in verbatimIdentification ([dc4d5d1](https://github.com/identification-resources/formica/commit/dc4d5d17e7a412a94538f6162dfd839d808cbc30))
|
|
7
|
+
* **resources:** update heuristics for recognizing author citations ([bca6a7f](https://github.com/identification-resources/formica/commit/bca6a7f5a6c2dc3dc421d885f6270f5464fc5ea5))
|
|
8
|
+
* **resources:** use corrected generic name in output ([b472384](https://github.com/identification-resources/formica/commit/b4723842e87515f076230c603246082bc3bbec9d))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **resources:** add support for completeness flags ([55b5427](https://github.com/identification-resources/formica/commit/55b5427125b4f1072191cadbc461ba31d37e6f2e))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## [0.6.6](https://github.com/identification-resources/formica/compare/v0.6.5...v0.6.6) (2025-02-16)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* **resources:** allow other resources in version_of ([85947b0](https://github.com/identification-resources/formica/commit/85947b0dcfbdb4bfd6537c628b34087a6e6326f7))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
1
26
|
## [0.6.5](https://github.com/identification-resources/formica/compare/v0.6.4...v0.6.5) (2024-12-20)
|
|
2
27
|
|
|
3
28
|
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __assign = (this && this.__assign) || function () {
|
|
4
|
+
__assign = Object.assign || function(t) {
|
|
5
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
6
|
+
s = arguments[i];
|
|
7
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
8
|
+
t[p] = s[p];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
return __assign.apply(this, arguments);
|
|
13
|
+
};
|
|
14
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
15
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
16
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
17
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
18
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
19
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
20
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
24
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
25
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
26
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
27
|
+
function step(op) {
|
|
28
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
29
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
30
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
31
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
32
|
+
switch (op[0]) {
|
|
33
|
+
case 0: case 1: t = op; break;
|
|
34
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
35
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
36
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
37
|
+
default:
|
|
38
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
39
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
40
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
41
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
42
|
+
if (t[2]) _.ops.pop();
|
|
43
|
+
_.trys.pop(); continue;
|
|
44
|
+
}
|
|
45
|
+
op = body.call(thisArg, _);
|
|
46
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
47
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
|
+
var fs_1 = require("fs");
|
|
52
|
+
var path = require("path");
|
|
53
|
+
var util = require("util");
|
|
54
|
+
var index_1 = require("../index");
|
|
55
|
+
var GBIF_RANKS = [
|
|
56
|
+
'kingdom',
|
|
57
|
+
'phyllum',
|
|
58
|
+
'class',
|
|
59
|
+
'order',
|
|
60
|
+
'family',
|
|
61
|
+
'genus',
|
|
62
|
+
'species',
|
|
63
|
+
'subspecies',
|
|
64
|
+
'variety'
|
|
65
|
+
];
|
|
66
|
+
var DISPLAY_FIELDS = [
|
|
67
|
+
'scientificNameID',
|
|
68
|
+
'taxonRank',
|
|
69
|
+
'scientificName',
|
|
70
|
+
'taxonomicStatus',
|
|
71
|
+
'taxonRemarks',
|
|
72
|
+
'colTaxonID',
|
|
73
|
+
'gbifTaxonID'
|
|
74
|
+
];
|
|
75
|
+
var ResourceProblemsProcessor = /** @class */ (function () {
|
|
76
|
+
function ResourceProblemsProcessor(collectionPath) {
|
|
77
|
+
this.DIR_ROOT = path.resolve(collectionPath);
|
|
78
|
+
this.DIR_DWC = path.join(this.DIR_ROOT, 'dwc');
|
|
79
|
+
this.FILE_INDEX = path.join(this.DIR_ROOT, 'index.json');
|
|
80
|
+
this.FILE_PROBLEMS = path.join(this.DIR_ROOT, 'problems.csv');
|
|
81
|
+
}
|
|
82
|
+
ResourceProblemsProcessor.prototype.run = function () {
|
|
83
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
84
|
+
var problems, _a, _b, resources, _c, _d, _i, problems_1, problem, _work, resourceId, _problemText, resource, missing;
|
|
85
|
+
return __generator(this, function (_e) {
|
|
86
|
+
switch (_e.label) {
|
|
87
|
+
case 0:
|
|
88
|
+
_b = (_a = index_1.csv).parseCsv;
|
|
89
|
+
return [4 /*yield*/, fs_1.promises.readFile(this.FILE_PROBLEMS, 'utf8')];
|
|
90
|
+
case 1:
|
|
91
|
+
problems = _b.apply(_a, [_e.sent()]).slice(1);
|
|
92
|
+
_d = (_c = JSON).parse;
|
|
93
|
+
return [4 /*yield*/, fs_1.promises.readFile(this.FILE_INDEX, 'utf8')];
|
|
94
|
+
case 2:
|
|
95
|
+
resources = _d.apply(_c, [_e.sent()]);
|
|
96
|
+
_i = 0, problems_1 = problems;
|
|
97
|
+
_e.label = 3;
|
|
98
|
+
case 3:
|
|
99
|
+
if (!(_i < problems_1.length)) return [3 /*break*/, 6];
|
|
100
|
+
problem = problems_1[_i];
|
|
101
|
+
_work = problem[0], resourceId = problem[1], _problemText = problem[2];
|
|
102
|
+
return [4 /*yield*/, this.readAmendedResource(resourceId, resources)];
|
|
103
|
+
case 4:
|
|
104
|
+
resource = _e.sent();
|
|
105
|
+
missing = this.getMissingMappings(resource);
|
|
106
|
+
if (missing.length === 0) {
|
|
107
|
+
this.removeProblem(resourceId);
|
|
108
|
+
}
|
|
109
|
+
_e.label = 5;
|
|
110
|
+
case 5:
|
|
111
|
+
_i++;
|
|
112
|
+
return [3 /*break*/, 3];
|
|
113
|
+
case 6: return [2 /*return*/];
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
ResourceProblemsProcessor.prototype.readAmendedResource = function (resourceId, resources) {
|
|
119
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
120
|
+
var resource, resourceFileId, resourceFile, _a, header, taxonRows, _b, _c, taxa, _i, taxonRows_1, row, taxon;
|
|
121
|
+
return __generator(this, function (_d) {
|
|
122
|
+
switch (_d.label) {
|
|
123
|
+
case 0:
|
|
124
|
+
resource = resources[resourceId];
|
|
125
|
+
resourceFileId = resourceId.split(':').join('-');
|
|
126
|
+
resourceFile = path.join(this.DIR_DWC, "".concat(resourceFileId, ".csv"));
|
|
127
|
+
_c = (_b = index_1.csv).parseCsv;
|
|
128
|
+
return [4 /*yield*/, fs_1.promises.readFile(resourceFile, 'utf8')];
|
|
129
|
+
case 1:
|
|
130
|
+
_a = _c.apply(_b, [_d.sent()]), header = _a[0], taxonRows = _a.slice(1);
|
|
131
|
+
taxa = {};
|
|
132
|
+
for (_i = 0, taxonRows_1 = taxonRows; _i < taxonRows_1.length; _i++) {
|
|
133
|
+
row = taxonRows_1[_i];
|
|
134
|
+
taxon = row.reduce(function (taxon, value, index) {
|
|
135
|
+
taxon[header[index]] = value;
|
|
136
|
+
return taxon;
|
|
137
|
+
}, {});
|
|
138
|
+
taxa[taxon.scientificNameID] = taxon;
|
|
139
|
+
}
|
|
140
|
+
return [2 /*return*/, __assign(__assign({}, resource), { taxa: taxa })];
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
ResourceProblemsProcessor.prototype.getMissingMappings = function (resource) {
|
|
146
|
+
var missing = [];
|
|
147
|
+
for (var id in resource.taxa) {
|
|
148
|
+
var taxon = resource.taxa[id];
|
|
149
|
+
if (taxon.taxonomicStatus !== 'accepted') {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
var missingCol = false; // !taxon.colTaxonID
|
|
153
|
+
var missingGbif = GBIF_RANKS.includes(taxon.taxonRank) && !taxon.gbifTaxonID;
|
|
154
|
+
if (missingCol || missingGbif) {
|
|
155
|
+
missing.push(taxon);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return missing;
|
|
159
|
+
};
|
|
160
|
+
ResourceProblemsProcessor.prototype.removeProblem = function (resourceId) {
|
|
161
|
+
resourceId;
|
|
162
|
+
//
|
|
163
|
+
};
|
|
164
|
+
return ResourceProblemsProcessor;
|
|
165
|
+
}());
|
|
166
|
+
function main() {
|
|
167
|
+
var args = util.parseArgs({ allowPositionals: true });
|
|
168
|
+
var processor = new ResourceProblemsProcessor(args.positionals[0]);
|
|
169
|
+
process.on('exit', function () {
|
|
170
|
+
process.stdout.write('\n');
|
|
171
|
+
});
|
|
172
|
+
processor.run().catch(function (error) {
|
|
173
|
+
console.error(error);
|
|
174
|
+
process.exit(1);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
main();
|
|
@@ -13,7 +13,7 @@ var __assign = (this && this.__assign) || function () {
|
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
14
|
exports.parseFileHeader = exports.parseFile = void 0;
|
|
15
15
|
var yaml = require("js-yaml");
|
|
16
|
-
var
|
|
16
|
+
var resource_1 = require("./resource");
|
|
17
17
|
var diff_resource_1 = require("./diff-resource");
|
|
18
18
|
var RANKS = [
|
|
19
19
|
'phylum',
|
|
@@ -64,6 +64,11 @@ var DWC_RANKS = [
|
|
|
64
64
|
'genus',
|
|
65
65
|
'subgenus'
|
|
66
66
|
];
|
|
67
|
+
var FLAGS = [
|
|
68
|
+
'MISSING_TAXA',
|
|
69
|
+
'MISSING_PARENT_TAXA',
|
|
70
|
+
'MISSING_SYNONYMS'
|
|
71
|
+
];
|
|
67
72
|
var TAXONOMIC_STATUS = {
|
|
68
73
|
'>': 'incorrect',
|
|
69
74
|
'+': 'heterotypic synonym',
|
|
@@ -103,7 +108,7 @@ var RANK_LABELS_REVERSE = {
|
|
|
103
108
|
* 2. Followed by a capitalized word
|
|
104
109
|
* 3. Optionally, followed by "et al."
|
|
105
110
|
*/
|
|
106
|
-
var LOWERCASE_NAME_PARTICLES = ['y', 'der', 'den', 'de', 'van'].join('|');
|
|
111
|
+
var LOWERCASE_NAME_PARTICLES = ['y', 'der', 'den', 'de', 'van', 'von'].join('|');
|
|
107
112
|
var SIMPLE_AUTHOR_PATTERN = '(?:(?:\\p{Lu}\\S*|&|in|ex|' + LOWERCASE_NAME_PARTICLES + ')\\s*)*\\p{Lu}\\S+(?:\\s+et\\s+al\\.)?';
|
|
108
113
|
var NAME_PATTERN = new RegExp('^' +
|
|
109
114
|
// $1 main name part
|
|
@@ -111,7 +116,7 @@ var NAME_PATTERN = new RegExp('^' +
|
|
|
111
116
|
// $2 optional author citation
|
|
112
117
|
'(?: ' +
|
|
113
118
|
// but not auct(t)., etc.
|
|
114
|
-
'(?!auctt?\\.|(?:syn|comb|sp|spec)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.))' +
|
|
119
|
+
'(?!auctt?\\.|(?:syn|comb|sp|spec|nom)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.)|in part|partim)' +
|
|
115
120
|
'(' +
|
|
116
121
|
// $2.1 anything in parentheses, followed by optional revising author(s)
|
|
117
122
|
'\\(.+?\\)(?:\\s+' + SIMPLE_AUTHOR_PATTERN + ')?' +
|
|
@@ -326,17 +331,6 @@ function parseName(name, rank, parent) {
|
|
|
326
331
|
if (item.scientificNameAuthorship) {
|
|
327
332
|
item.scientificName += " ".concat(item.scientificNameAuthorship);
|
|
328
333
|
}
|
|
329
|
-
// Amend "parent" with corrections
|
|
330
|
-
if (item.taxonomicStatus === 'incorrect') {
|
|
331
|
-
var itemAsObject = item;
|
|
332
|
-
var parentAsObject = parent;
|
|
333
|
-
parent.incorrect = __assign({}, parent);
|
|
334
|
-
for (var key in item) {
|
|
335
|
-
if (key !== 'taxonomicStatus') {
|
|
336
|
-
parentAsObject[key] = itemAsObject[key];
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
334
|
return item;
|
|
341
335
|
}
|
|
342
336
|
function parseHeader(header) {
|
|
@@ -370,6 +364,9 @@ function parseHeader(header) {
|
|
|
370
364
|
var metadata = { levels: levels };
|
|
371
365
|
if ('catalog' in config && typeof config.catalog === 'object' && config.catalog !== null) {
|
|
372
366
|
var catalog = {};
|
|
367
|
+
if ('id' in config.catalog) {
|
|
368
|
+
throw new SyntaxError('"catalog" should not contain id');
|
|
369
|
+
}
|
|
373
370
|
for (var key in config.catalog) {
|
|
374
371
|
var value = config.catalog[key];
|
|
375
372
|
if (typeof value === 'number') {
|
|
@@ -382,7 +379,7 @@ function parseHeader(header) {
|
|
|
382
379
|
throw new SyntaxError("\"catalog\" should contain only strings (\"".concat(key, "\")"));
|
|
383
380
|
}
|
|
384
381
|
}
|
|
385
|
-
var work = new
|
|
382
|
+
var work = new resource_1.WorkResource(catalog);
|
|
386
383
|
var errors = work.validate().filter(function (_a) {
|
|
387
384
|
var error = _a.error;
|
|
388
385
|
return error !== 'Value(s) required but missing';
|
|
@@ -398,6 +395,16 @@ function parseHeader(header) {
|
|
|
398
395
|
metadata.catalog[key] = work.get(key);
|
|
399
396
|
}
|
|
400
397
|
}
|
|
398
|
+
if ('flags' in config) {
|
|
399
|
+
if (!Array.isArray(config.flags)) {
|
|
400
|
+
throw new SyntaxError('"flags" should be an array if present');
|
|
401
|
+
}
|
|
402
|
+
var invalidFlags = config.flags.filter(function (flag) { return !FLAGS.includes(flag); });
|
|
403
|
+
if (invalidFlags.length) {
|
|
404
|
+
throw new SyntaxError("\"flags\" contains invalid values: ".concat(invalidFlags.join(', ')));
|
|
405
|
+
}
|
|
406
|
+
metadata.flags = config.flags;
|
|
407
|
+
}
|
|
401
408
|
return metadata;
|
|
402
409
|
}
|
|
403
410
|
function validateResource(config, content) {
|
|
@@ -477,27 +484,11 @@ function parseResourceContent(content, resource, oldIds) {
|
|
|
477
484
|
var rank = resource.metadata.levels[groupIndent / 2];
|
|
478
485
|
var item = parseName(name_1, rank, parent_1);
|
|
479
486
|
var isSynonym = item.taxonomicStatus !== 'accepted';
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
if (type === diff_resource_1.ResourceDiffType.Added) {
|
|
484
|
-
newIdOffset++;
|
|
485
|
-
item.scientificNameID = idBase + newIdOffset.toString();
|
|
486
|
-
}
|
|
487
|
-
else {
|
|
488
|
-
id++;
|
|
489
|
-
item.scientificNameID = idBase + (oldIds[id - 1] || id).toString();
|
|
490
|
-
}
|
|
491
|
-
previousId = item.scientificNameID;
|
|
492
|
-
item.parentNameUsageID = isSynonym ? undefined : parent_1.scientificNameID;
|
|
493
|
-
item.parentNameUsage = isSynonym ? undefined : parent_1.scientificName;
|
|
494
|
-
item.acceptedNameUsageID = isSynonym ? parent_1.scientificNameID : undefined;
|
|
495
|
-
item.acceptedNameUsage = isSynonym ? parent_1.scientificName : undefined;
|
|
496
|
-
item.collectionCode = idBase.slice(0, -1);
|
|
487
|
+
// Add higher classification info
|
|
488
|
+
var itemAsObject = item;
|
|
489
|
+
var parentAsObject = parent_1;
|
|
497
490
|
for (var _b = 0, DWC_RANKS_1 = DWC_RANKS; _b < DWC_RANKS_1.length; _b++) {
|
|
498
491
|
var rank_1 = DWC_RANKS_1[_b];
|
|
499
|
-
var itemAsObject = item;
|
|
500
|
-
var parentAsObject = parent_1;
|
|
501
492
|
itemAsObject[rank_1] = undefined;
|
|
502
493
|
if (parentAsObject[rank_1]) {
|
|
503
494
|
itemAsObject[rank_1] = parentAsObject[rank_1];
|
|
@@ -521,6 +512,31 @@ function parseResourceContent(content, resource, oldIds) {
|
|
|
521
512
|
else if (parentId) {
|
|
522
513
|
item.higherClassification = parent_1.scientificNameOnly;
|
|
523
514
|
}
|
|
515
|
+
// Amend "parent" with corrections
|
|
516
|
+
if (item.taxonomicStatus === 'incorrect') {
|
|
517
|
+
parent_1.incorrect = __assign({}, parent_1);
|
|
518
|
+
for (var key in item) {
|
|
519
|
+
if (key !== 'taxonomicStatus' && key !== 'verbatimIdentification') {
|
|
520
|
+
parentAsObject[key] = itemAsObject[key];
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return "continue";
|
|
524
|
+
}
|
|
525
|
+
// Set identifiers
|
|
526
|
+
if (type === diff_resource_1.ResourceDiffType.Added) {
|
|
527
|
+
newIdOffset++;
|
|
528
|
+
item.scientificNameID = idBase + newIdOffset.toString();
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
id++;
|
|
532
|
+
item.scientificNameID = idBase + (oldIds[id - 1] || id).toString();
|
|
533
|
+
}
|
|
534
|
+
previousId = item.scientificNameID;
|
|
535
|
+
item.parentNameUsageID = isSynonym ? undefined : parent_1.scientificNameID;
|
|
536
|
+
item.parentNameUsage = isSynonym ? undefined : parent_1.scientificName;
|
|
537
|
+
item.acceptedNameUsageID = isSynonym ? parent_1.scientificNameID : undefined;
|
|
538
|
+
item.acceptedNameUsage = isSynonym ? parent_1.scientificName : undefined;
|
|
539
|
+
item.collectionCode = idBase.slice(0, -1);
|
|
524
540
|
data[item.scientificNameID] = item;
|
|
525
541
|
};
|
|
526
542
|
for (var _i = 0, content_1 = content; _i < content_1.length; _i++) {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __extends = (this && this.__extends) || (function () {
|
|
3
|
+
var extendStatics = function (d, b) {
|
|
4
|
+
extendStatics = Object.setPrototypeOf ||
|
|
5
|
+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
6
|
+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
|
7
|
+
return extendStatics(d, b);
|
|
8
|
+
};
|
|
9
|
+
return function (d, b) {
|
|
10
|
+
if (typeof b !== "function" && b !== null)
|
|
11
|
+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
|
12
|
+
extendStatics(d, b);
|
|
13
|
+
function __() { this.constructor = d; }
|
|
14
|
+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
15
|
+
};
|
|
16
|
+
})();
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.WorkResource = void 0;
|
|
19
|
+
var work_1 = require("../catalog/tables/work");
|
|
20
|
+
var WorkResource = /** @class */ (function (_super) {
|
|
21
|
+
__extends(WorkResource, _super);
|
|
22
|
+
function WorkResource(values) {
|
|
23
|
+
var _this = _super.call(this, values) || this;
|
|
24
|
+
_this.schema.version_of.format = /^B[1-9]\d*:[1-9]\d*$/;
|
|
25
|
+
_this.schema.duplicate_of.format = /^B[1-9]\d*:[1-9]\d*$/;
|
|
26
|
+
return _this;
|
|
27
|
+
}
|
|
28
|
+
return WorkResource;
|
|
29
|
+
}(work_1.Work));
|
|
30
|
+
exports.WorkResource = WorkResource;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@larsgw/formica",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.7",
|
|
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",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"lint": "eslint src",
|
|
17
17
|
"build": "tsc -d",
|
|
18
18
|
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
|
|
19
|
-
"preversion": "npm run lint",
|
|
19
|
+
"preversion": "npm run lint && npm test",
|
|
20
20
|
"version": "npm run changelog && git add CHANGELOG.md",
|
|
21
21
|
"prepublishOnly": "npm run build"
|
|
22
22
|
},
|
|
@@ -175,7 +175,6 @@ class ResourceProcessor {
|
|
|
175
175
|
const skip = await this.shouldBeSkipped(resource.id)
|
|
176
176
|
|
|
177
177
|
if (!skip) {
|
|
178
|
-
// TODO const correct = checkResults(results, classifications)
|
|
179
178
|
const correct = this.checkResults(results)
|
|
180
179
|
if (!correct) {
|
|
181
180
|
const choice = await promptForAnswers(
|
package/src/module.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as yaml from 'js-yaml'
|
|
2
|
-
import {
|
|
2
|
+
import { WorkResource } from './resource'
|
|
3
3
|
import { createDiff, ResourceDiffType } from './diff-resource'
|
|
4
4
|
|
|
5
5
|
const RANKS: Rank[] = [
|
|
@@ -54,6 +54,12 @@ const DWC_RANKS: DwcRank[] = [
|
|
|
54
54
|
'subgenus'
|
|
55
55
|
]
|
|
56
56
|
|
|
57
|
+
const FLAGS: ResourceFlag[] = [
|
|
58
|
+
'MISSING_TAXA',
|
|
59
|
+
'MISSING_PARENT_TAXA',
|
|
60
|
+
'MISSING_SYNONYMS'
|
|
61
|
+
]
|
|
62
|
+
|
|
57
63
|
const TAXONOMIC_STATUS: Record<string, TaxonStatus> = {
|
|
58
64
|
'>': 'incorrect',
|
|
59
65
|
'+': 'heterotypic synonym',
|
|
@@ -97,7 +103,7 @@ const RANK_LABELS_REVERSE: Record<string, Rank> = {
|
|
|
97
103
|
* 2. Followed by a capitalized word
|
|
98
104
|
* 3. Optionally, followed by "et al."
|
|
99
105
|
*/
|
|
100
|
-
const LOWERCASE_NAME_PARTICLES = ['y', 'der', 'den', 'de', 'van'].join('|')
|
|
106
|
+
const LOWERCASE_NAME_PARTICLES = ['y', 'der', 'den', 'de', 'van', 'von'].join('|')
|
|
101
107
|
const SIMPLE_AUTHOR_PATTERN = '(?:(?:\\p{Lu}\\S*|&|in|ex|' + LOWERCASE_NAME_PARTICLES + ')\\s*)*\\p{Lu}\\S+(?:\\s+et\\s+al\\.)?'
|
|
102
108
|
|
|
103
109
|
const NAME_PATTERN = new RegExp(
|
|
@@ -107,7 +113,7 @@ const NAME_PATTERN = new RegExp(
|
|
|
107
113
|
// $2 optional author citation
|
|
108
114
|
'(?: ' +
|
|
109
115
|
// but not auct(t)., etc.
|
|
110
|
-
'(?!auctt?\\.|(?:syn|comb|sp|spec)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.))' +
|
|
116
|
+
'(?!auctt?\\.|(?:syn|comb|sp|spec|nom)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.)|in part|partim)' +
|
|
111
117
|
'(' +
|
|
112
118
|
// $2.1 anything in parentheses, followed by optional revising author(s)
|
|
113
119
|
'\\(.+?\\)(?:\\s+' + SIMPLE_AUTHOR_PATTERN + ')?' +
|
|
@@ -342,19 +348,6 @@ function parseName (name: string, rank: Rank, parent: WorkingTaxon): WorkingTaxo
|
|
|
342
348
|
item.scientificName += ` ${item.scientificNameAuthorship}`
|
|
343
349
|
}
|
|
344
350
|
|
|
345
|
-
// Amend "parent" with corrections
|
|
346
|
-
if (item.taxonomicStatus === 'incorrect') {
|
|
347
|
-
const itemAsObject = item as { [index: string]: unknown }
|
|
348
|
-
const parentAsObject = parent as { [index: string]: unknown }
|
|
349
|
-
|
|
350
|
-
parent.incorrect = { ...parent }
|
|
351
|
-
for (const key in item) {
|
|
352
|
-
if (key !== 'taxonomicStatus') {
|
|
353
|
-
parentAsObject[key] = itemAsObject[key]
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
351
|
return item
|
|
359
352
|
}
|
|
360
353
|
|
|
@@ -394,6 +387,9 @@ function parseHeader (header: string): ResourceMetadata {
|
|
|
394
387
|
|
|
395
388
|
if ('catalog' in config && typeof config.catalog === 'object' && config.catalog !== null) {
|
|
396
389
|
const catalog: Record<string, string> = {}
|
|
390
|
+
if ('id' in config.catalog) {
|
|
391
|
+
throw new SyntaxError('"catalog" should not contain id')
|
|
392
|
+
}
|
|
397
393
|
for (const key in config.catalog) {
|
|
398
394
|
const value = config.catalog[key as keyof object]
|
|
399
395
|
if (typeof value === 'number') {
|
|
@@ -404,7 +400,7 @@ function parseHeader (header: string): ResourceMetadata {
|
|
|
404
400
|
throw new SyntaxError(`"catalog" should contain only strings ("${key}")`)
|
|
405
401
|
}
|
|
406
402
|
}
|
|
407
|
-
const work = new
|
|
403
|
+
const work = new WorkResource(catalog)
|
|
408
404
|
const errors = work.validate().filter(({ error }) => error !== 'Value(s) required but missing')
|
|
409
405
|
if (errors.length > 0) {
|
|
410
406
|
throw new SyntaxError(`"catalog" contains errors: ${errors.map(({ field, error }) => `[${field}] ${error}`).join('; ')}`)
|
|
@@ -415,6 +411,19 @@ function parseHeader (header: string): ResourceMetadata {
|
|
|
415
411
|
}
|
|
416
412
|
}
|
|
417
413
|
|
|
414
|
+
if ('flags' in config) {
|
|
415
|
+
if (!Array.isArray(config.flags)) {
|
|
416
|
+
throw new SyntaxError('"flags" should be an array if present')
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const invalidFlags = config.flags.filter(flag => !FLAGS.includes(flag))
|
|
420
|
+
if (invalidFlags.length) {
|
|
421
|
+
throw new SyntaxError(`"flags" contains invalid values: ${invalidFlags.join(', ')}`)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
metadata.flags = config.flags
|
|
425
|
+
}
|
|
426
|
+
|
|
418
427
|
return metadata
|
|
419
428
|
}
|
|
420
429
|
|
|
@@ -507,29 +516,10 @@ function parseResourceContent (content: ResourceDiff, resource: Resource, oldIds
|
|
|
507
516
|
const item = parseName(name, rank, parent)
|
|
508
517
|
const isSynonym = item.taxonomicStatus !== 'accepted'
|
|
509
518
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
if (type === ResourceDiffType.Added) {
|
|
515
|
-
newIdOffset++
|
|
516
|
-
item.scientificNameID = idBase + newIdOffset.toString()
|
|
517
|
-
} else {
|
|
518
|
-
id++
|
|
519
|
-
item.scientificNameID = idBase + (oldIds[id - 1] || id).toString()
|
|
520
|
-
}
|
|
521
|
-
previousId = item.scientificNameID
|
|
522
|
-
|
|
523
|
-
item.parentNameUsageID = isSynonym ? undefined : parent.scientificNameID
|
|
524
|
-
item.parentNameUsage = isSynonym ? undefined : parent.scientificName
|
|
525
|
-
item.acceptedNameUsageID = isSynonym ? parent.scientificNameID : undefined
|
|
526
|
-
item.acceptedNameUsage = isSynonym ? parent.scientificName : undefined
|
|
527
|
-
item.collectionCode = idBase.slice(0, -1)
|
|
528
|
-
|
|
519
|
+
// Add higher classification info
|
|
520
|
+
const itemAsObject = item as { [index: string]: unknown }
|
|
521
|
+
const parentAsObject = parent as { [index: string]: unknown }
|
|
529
522
|
for (const rank of DWC_RANKS) {
|
|
530
|
-
const itemAsObject = item as { [index: string]: unknown }
|
|
531
|
-
const parentAsObject = parent as { [index: string]: unknown }
|
|
532
|
-
|
|
533
523
|
itemAsObject[rank] = undefined
|
|
534
524
|
if (parentAsObject[rank]) {
|
|
535
525
|
itemAsObject[rank] = parentAsObject[rank]
|
|
@@ -554,6 +544,33 @@ function parseResourceContent (content: ResourceDiff, resource: Resource, oldIds
|
|
|
554
544
|
item.higherClassification = parent.scientificNameOnly
|
|
555
545
|
}
|
|
556
546
|
|
|
547
|
+
// Amend "parent" with corrections
|
|
548
|
+
if (item.taxonomicStatus === 'incorrect') {
|
|
549
|
+
parent.incorrect = { ...parent }
|
|
550
|
+
for (const key in item) {
|
|
551
|
+
if (key !== 'taxonomicStatus' && key !== 'verbatimIdentification') {
|
|
552
|
+
parentAsObject[key] = itemAsObject[key]
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
continue
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Set identifiers
|
|
559
|
+
if (type === ResourceDiffType.Added) {
|
|
560
|
+
newIdOffset++
|
|
561
|
+
item.scientificNameID = idBase + newIdOffset.toString()
|
|
562
|
+
} else {
|
|
563
|
+
id++
|
|
564
|
+
item.scientificNameID = idBase + (oldIds[id - 1] || id).toString()
|
|
565
|
+
}
|
|
566
|
+
previousId = item.scientificNameID
|
|
567
|
+
|
|
568
|
+
item.parentNameUsageID = isSynonym ? undefined : parent.scientificNameID
|
|
569
|
+
item.parentNameUsage = isSynonym ? undefined : parent.scientificName
|
|
570
|
+
item.acceptedNameUsageID = isSynonym ? parent.scientificNameID : undefined
|
|
571
|
+
item.acceptedNameUsage = isSynonym ? parent.scientificName : undefined
|
|
572
|
+
item.collectionCode = idBase.slice(0, -1)
|
|
573
|
+
|
|
557
574
|
data[item.scientificNameID] = item
|
|
558
575
|
}
|
|
559
576
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Work } from '../catalog/tables/work'
|
|
2
|
+
|
|
3
|
+
export class WorkResource extends Work {
|
|
4
|
+
constructor (values: Record<string, string>) {
|
|
5
|
+
super(values)
|
|
6
|
+
|
|
7
|
+
this.schema.version_of.format = /^B[1-9]\d*:[1-9]\d*$/
|
|
8
|
+
this.schema.duplicate_of.format = /^B[1-9]\d*:[1-9]\d*$/
|
|
9
|
+
}
|
|
10
|
+
}
|
package/test/resources.js
CHANGED
|
@@ -136,4 +136,17 @@ Tilia x vulgaris
|
|
|
136
136
|
assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Rumex conglomeratus×maritimus')
|
|
137
137
|
assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Tilia ×vulgaris')
|
|
138
138
|
})
|
|
139
|
+
|
|
140
|
+
await t.test('outputs corrected generic names', (t) => {
|
|
141
|
+
const [resource] = resources.parseTextFile(`---
|
|
142
|
+
levels: [genus, species]
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
Bogdania Kerzhner, 1964
|
|
146
|
+
> Bogdiana Kerzhner, 1964
|
|
147
|
+
myrmica Kerzhner, 1964
|
|
148
|
+
`, 'T1')
|
|
149
|
+
assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Bogdiana Kerzhner, 1964')
|
|
150
|
+
assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Bogdiana myrmica Kerzhner, 1964')
|
|
151
|
+
})
|
|
139
152
|
})
|