@larsgw/formica 0.6.3 → 0.6.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.
- package/CHANGELOG.md +20 -0
- package/lib/bin/process-resources.js +41 -31
- package/lib/resources/parse-text.js +18 -10
- package/package.json +1 -1
- package/src/bin/process-resources.ts +26 -1
- package/src/resources/parse-text.ts +19 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## [0.6.5](https://github.com/identification-resources/formica/compare/v0.6.4...v0.6.5) (2024-12-20)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **resources:** ignore empty lines in diff mode ([3fb027e](https://github.com/identification-resources/formica/commit/3fb027e8968196910fff2e574e700ac0abee996c))
|
|
7
|
+
* **resources:** improve (ICBN) author parsing ([d3e3faf](https://github.com/identification-resources/formica/commit/d3e3faf411d1a7dccd528a5ee78bd0823a5883b4)), closes [#13](https://github.com/identification-resources/formica/issues/13) [#14](https://github.com/identification-resources/formica/issues/14)
|
|
8
|
+
* **resources:** support "et al." in author name ([82993bf](https://github.com/identification-resources/formica/commit/82993bffac5e7f3ed00d00c28c836f347b264a43))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## [0.6.4](https://github.com/identification-resources/formica/compare/v0.6.3...v0.6.4) (2024-12-16)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **resources:** fix matching of some ICBN names ([3c18458](https://github.com/identification-resources/formica/commit/3c184586b64e2a31f1eb7298bcd7c80c1d119069))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
1
21
|
## [0.6.3](https://github.com/identification-resources/formica/compare/v0.6.2...v0.6.3) (2024-04-12)
|
|
2
22
|
|
|
3
23
|
|
|
@@ -398,9 +398,9 @@ var ResourceProcessor = /** @class */ (function () {
|
|
|
398
398
|
};
|
|
399
399
|
ResourceProcessor.prototype.processResourceDwc = function (resource, config) {
|
|
400
400
|
return __awaiter(this, void 0, void 0, function () {
|
|
401
|
-
var file, _a, header_1, rows, _b, _c, _i, rows_1, row, oldTaxon, taxon, filteredResults, taxonNames, names, id, name_1, result, _d, _e, results, _f,
|
|
402
|
-
return __generator(this, function (
|
|
403
|
-
switch (
|
|
401
|
+
var file, _a, header_1, rows, _b, _c, _i, rows_1, row, oldTaxon, taxon, filteredResults, taxonNames, names, id, name_1, result, _loop_2, _d, _e, results, _f, amendResource, groupNameMatches, groupedNameMatches, amendedResource, _g, _h, _j, _k, source, matches;
|
|
402
|
+
return __generator(this, function (_l) {
|
|
403
|
+
switch (_l.label) {
|
|
404
404
|
case 0:
|
|
405
405
|
console.log("".concat(resource.workId, ": matching ").concat(resource.id));
|
|
406
406
|
if (!!config.updateMappings) return [3 /*break*/, 3];
|
|
@@ -409,7 +409,7 @@ var ResourceProcessor = /** @class */ (function () {
|
|
|
409
409
|
_c = (_b = index_1.csv).parseCsv;
|
|
410
410
|
return [4 /*yield*/, fs_1.promises.readFile(file, 'utf-8')];
|
|
411
411
|
case 1:
|
|
412
|
-
_a = _c.apply(_b, [
|
|
412
|
+
_a = _c.apply(_b, [_l.sent()]), header_1 = _a[0], rows = _a.slice(1);
|
|
413
413
|
for (_i = 0, rows_1 = rows; _i < rows_1.length; _i++) {
|
|
414
414
|
row = rows_1[_i];
|
|
415
415
|
oldTaxon = row.reduce(function (taxon, value, index) {
|
|
@@ -424,7 +424,7 @@ var ResourceProcessor = /** @class */ (function () {
|
|
|
424
424
|
taxon.gbifAcceptedTaxonID = oldTaxon.gbifAcceptedTaxonID;
|
|
425
425
|
}
|
|
426
426
|
}
|
|
427
|
-
|
|
427
|
+
_l.label = 2;
|
|
428
428
|
case 2: return [2 /*return*/, resource];
|
|
429
429
|
case 3:
|
|
430
430
|
filteredResults = {};
|
|
@@ -441,17 +441,23 @@ var ResourceProcessor = /** @class */ (function () {
|
|
|
441
441
|
}
|
|
442
442
|
return [4 /*yield*/, runGnverifier(Array.from(names).join('\n'))];
|
|
443
443
|
case 4:
|
|
444
|
-
result =
|
|
445
|
-
|
|
446
|
-
results =
|
|
447
|
-
_f = JSON.parse(results), name_2 = _f.name, matches = _f.results;
|
|
444
|
+
result = _l.sent();
|
|
445
|
+
_loop_2 = function (results) {
|
|
446
|
+
var _m = JSON.parse(results), name_2 = _m.name, matches = _m.results;
|
|
448
447
|
if (!matches) {
|
|
449
|
-
continue;
|
|
448
|
+
return "continue";
|
|
450
449
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
450
|
+
// Fix author scoring for some species, see https://github.com/gnames/gnverifier/issues/129
|
|
451
|
+
matches.sort(function (a, b) {
|
|
452
|
+
if (a.sortScore !== b.sortScore) {
|
|
453
|
+
return b.sortScore - a.sortScore;
|
|
454
|
+
}
|
|
455
|
+
return name_2 === a.matchedName ? -1 : name_2 === b.matchedName ? 1 : 0;
|
|
456
|
+
});
|
|
457
|
+
for (var _o = 0, _p = matches; _o < _p.length; _o++) {
|
|
458
|
+
var match = _p[_o];
|
|
459
|
+
var source = match.dataSourceId;
|
|
460
|
+
var currentRank = match.classificationRanks.split('|').pop();
|
|
455
461
|
if (match.scoreDetails.cardinalityScore === 0) {
|
|
456
462
|
// Rank mismatch
|
|
457
463
|
continue;
|
|
@@ -460,9 +466,9 @@ var ResourceProcessor = /** @class */ (function () {
|
|
|
460
466
|
// GBIF species like "Nomada spec"
|
|
461
467
|
continue;
|
|
462
468
|
}
|
|
463
|
-
for (
|
|
464
|
-
loirId =
|
|
465
|
-
taxon = resource.taxa[loirId];
|
|
469
|
+
for (var _q = 0, _r = taxonNames[name_2]; _q < _r.length; _q++) {
|
|
470
|
+
var loirId = _r[_q];
|
|
471
|
+
var taxon = resource.taxa[loirId];
|
|
466
472
|
if (source === 11 && !GBIF_RANKS.includes(taxon.taxonRank)) {
|
|
467
473
|
// Exclude GBIF matches for ranks that are not in GBIF
|
|
468
474
|
continue;
|
|
@@ -483,30 +489,34 @@ var ResourceProcessor = /** @class */ (function () {
|
|
|
483
489
|
});
|
|
484
490
|
}
|
|
485
491
|
}
|
|
492
|
+
};
|
|
493
|
+
for (_d = 0, _e = result.trim().split('\n'); _d < _e.length; _d++) {
|
|
494
|
+
results = _e[_d];
|
|
495
|
+
_loop_2(results);
|
|
486
496
|
}
|
|
487
497
|
return [4 /*yield*/, Promise.resolve().then(function () { return require('../index'); })];
|
|
488
498
|
case 5:
|
|
489
|
-
|
|
499
|
+
_f = (_l.sent()).taxonNames, amendResource = _f.amendResource, groupNameMatches = _f.groupNameMatches;
|
|
490
500
|
groupedNameMatches = groupNameMatches(filteredResults);
|
|
491
501
|
amendedResource = __assign(__assign({}, resource), { taxa: __assign({}, resource.taxa) });
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
for (
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
502
|
+
_g = groupedNameMatches;
|
|
503
|
+
_h = [];
|
|
504
|
+
for (_j in _g)
|
|
505
|
+
_h.push(_j);
|
|
506
|
+
_k = 0;
|
|
507
|
+
_l.label = 6;
|
|
498
508
|
case 6:
|
|
499
|
-
if (!(
|
|
500
|
-
|
|
501
|
-
if (!(
|
|
502
|
-
source =
|
|
509
|
+
if (!(_k < _h.length)) return [3 /*break*/, 9];
|
|
510
|
+
_j = _h[_k];
|
|
511
|
+
if (!(_j in _g)) return [3 /*break*/, 8];
|
|
512
|
+
source = _j;
|
|
503
513
|
return [4 /*yield*/, this.selectPrefixes(resource, groupedNameMatches, source)];
|
|
504
514
|
case 7:
|
|
505
|
-
matches =
|
|
515
|
+
matches = _l.sent();
|
|
506
516
|
amendResource(amendedResource, source, matches);
|
|
507
|
-
|
|
517
|
+
_l.label = 8;
|
|
508
518
|
case 8:
|
|
509
|
-
|
|
519
|
+
_k++;
|
|
510
520
|
return [3 /*break*/, 6];
|
|
511
521
|
case 9: return [2 /*return*/, amendedResource];
|
|
512
522
|
}
|
|
@@ -93,6 +93,18 @@ var RANK_LABELS_REVERSE = {
|
|
|
93
93
|
'ssp': 'subspecies',
|
|
94
94
|
'subsp': 'subspecies'
|
|
95
95
|
};
|
|
96
|
+
/**
|
|
97
|
+
* 1. Any number of
|
|
98
|
+
* - capitalized words
|
|
99
|
+
* - "&"
|
|
100
|
+
* - " in "
|
|
101
|
+
* - " ex "
|
|
102
|
+
* - lowercase name particles
|
|
103
|
+
* 2. Followed by a capitalized word
|
|
104
|
+
* 3. Optionally, followed by "et al."
|
|
105
|
+
*/
|
|
106
|
+
var LOWERCASE_NAME_PARTICLES = ['y', 'der', 'den', 'de', 'van'].join('|');
|
|
107
|
+
var SIMPLE_AUTHOR_PATTERN = '(?:(?:\\p{Lu}\\S*|&|in|ex|' + LOWERCASE_NAME_PARTICLES + ')\\s*)*\\p{Lu}\\S+(?:\\s+et\\s+al\\.)?';
|
|
96
108
|
var NAME_PATTERN = new RegExp('^' +
|
|
97
109
|
// $1 main name part
|
|
98
110
|
'(\\S+)' +
|
|
@@ -101,20 +113,14 @@ var NAME_PATTERN = new RegExp('^' +
|
|
|
101
113
|
// but not auct(t)., etc.
|
|
102
114
|
'(?!auctt?\\.|(?:syn|comb|sp|spec)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.))' +
|
|
103
115
|
'(' +
|
|
104
|
-
// $2.1 anything in parentheses
|
|
105
|
-
'\\(.+?\\)' +
|
|
116
|
+
// $2.1 anything in parentheses, followed by optional revising author(s)
|
|
117
|
+
'\\(.+?\\)(?:\\s+' + SIMPLE_AUTHOR_PATTERN + ')?' +
|
|
106
118
|
'|' +
|
|
107
119
|
// $2.2 anything followed by a year
|
|
108
120
|
'.+?\\d{4}\\)?' +
|
|
109
121
|
'|' +
|
|
110
|
-
// $2.3
|
|
111
|
-
|
|
112
|
-
'|' +
|
|
113
|
-
// $2.4 name y name
|
|
114
|
-
'\\S+ [yY] \\S+' +
|
|
115
|
-
'|' +
|
|
116
|
-
// $2.5 name( in name)
|
|
117
|
-
'\\p{Lu}\\S*(?: in \\S+)?' +
|
|
122
|
+
// $2.3 author(s)
|
|
123
|
+
SIMPLE_AUTHOR_PATTERN +
|
|
118
124
|
'))?' +
|
|
119
125
|
// $3 optional notes
|
|
120
126
|
'(?:,? (.+))?' +
|
|
@@ -541,6 +547,8 @@ function parseFile(file, id, old) {
|
|
|
541
547
|
var diff;
|
|
542
548
|
if (oldResources[index]) {
|
|
543
549
|
diff = (0, diff_resource_1.createDiff)(content, parseResource(oldResources[index])[1]);
|
|
550
|
+
// Ignore empty lines
|
|
551
|
+
diff = diff.filter(function (line) { return line.text !== ''; });
|
|
544
552
|
}
|
|
545
553
|
else {
|
|
546
554
|
diff = (0, diff_resource_1.createDiff)(content, content);
|
package/package.json
CHANGED
|
@@ -287,13 +287,38 @@ class ResourceProcessor {
|
|
|
287
287
|
|
|
288
288
|
const result = await runGnverifier(Array.from(names).join('\n'))
|
|
289
289
|
for (const results of result.trim().split('\n')) {
|
|
290
|
+
interface MatchScoreDetails {
|
|
291
|
+
cardinalityScore: number;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
interface Match {
|
|
295
|
+
currentRecordId: string;
|
|
296
|
+
dataSourceId: number;
|
|
297
|
+
matchedName: string;
|
|
298
|
+
recordId: string;
|
|
299
|
+
sortScore: number;
|
|
300
|
+
isSynonym: boolean;
|
|
301
|
+
classificationPath: string;
|
|
302
|
+
classificationRanks: string;
|
|
303
|
+
scoreDetails: MatchScoreDetails;
|
|
304
|
+
}
|
|
305
|
+
|
|
290
306
|
const { name, results: matches } = JSON.parse(results)
|
|
291
307
|
|
|
292
308
|
if (!matches) {
|
|
293
309
|
continue
|
|
294
310
|
}
|
|
295
311
|
|
|
296
|
-
for
|
|
312
|
+
// Fix author scoring for some species, see https://github.com/gnames/gnverifier/issues/129
|
|
313
|
+
matches.sort((a: Match, b: Match) => {
|
|
314
|
+
if (a.sortScore !== b.sortScore) {
|
|
315
|
+
return b.sortScore - a.sortScore
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return name === a.matchedName ? -1 : name === b.matchedName ? 1 : 0
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
for (const match of matches as Match[]) {
|
|
297
322
|
const source = match.dataSourceId
|
|
298
323
|
const currentRank = match.classificationRanks.split('|').pop()
|
|
299
324
|
|
|
@@ -87,6 +87,19 @@ const RANK_LABELS_REVERSE: Record<string, Rank> = {
|
|
|
87
87
|
'subsp': 'subspecies'
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* 1. Any number of
|
|
92
|
+
* - capitalized words
|
|
93
|
+
* - "&"
|
|
94
|
+
* - " in "
|
|
95
|
+
* - " ex "
|
|
96
|
+
* - lowercase name particles
|
|
97
|
+
* 2. Followed by a capitalized word
|
|
98
|
+
* 3. Optionally, followed by "et al."
|
|
99
|
+
*/
|
|
100
|
+
const LOWERCASE_NAME_PARTICLES = ['y', 'der', 'den', 'de', 'van'].join('|')
|
|
101
|
+
const SIMPLE_AUTHOR_PATTERN = '(?:(?:\\p{Lu}\\S*|&|in|ex|' + LOWERCASE_NAME_PARTICLES + ')\\s*)*\\p{Lu}\\S+(?:\\s+et\\s+al\\.)?'
|
|
102
|
+
|
|
90
103
|
const NAME_PATTERN = new RegExp(
|
|
91
104
|
'^' +
|
|
92
105
|
// $1 main name part
|
|
@@ -96,20 +109,14 @@ const NAME_PATTERN = new RegExp(
|
|
|
96
109
|
// but not auct(t)., etc.
|
|
97
110
|
'(?!auctt?\\.|(?:syn|comb|sp|spec)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.))' +
|
|
98
111
|
'(' +
|
|
99
|
-
// $2.1 anything in parentheses
|
|
100
|
-
'\\(.+?\\)' +
|
|
112
|
+
// $2.1 anything in parentheses, followed by optional revising author(s)
|
|
113
|
+
'\\(.+?\\)(?:\\s+' + SIMPLE_AUTHOR_PATTERN + ')?' +
|
|
101
114
|
'|' +
|
|
102
115
|
// $2.2 anything followed by a year
|
|
103
116
|
'.+?\\d{4}\\)?' +
|
|
104
117
|
'|' +
|
|
105
|
-
// $2.3
|
|
106
|
-
|
|
107
|
-
'|' +
|
|
108
|
-
// $2.4 name y name
|
|
109
|
-
'\\S+ [yY] \\S+' +
|
|
110
|
-
'|' +
|
|
111
|
-
// $2.5 name( in name)
|
|
112
|
-
'\\p{Lu}\\S*(?: in \\S+)?' +
|
|
118
|
+
// $2.3 author(s)
|
|
119
|
+
SIMPLE_AUTHOR_PATTERN +
|
|
113
120
|
'))?' +
|
|
114
121
|
// $3 optional notes
|
|
115
122
|
'(?:,? (.+))?' +
|
|
@@ -573,6 +580,8 @@ export function parseFile (file: string, id: WorkId, old?: ResourceHistory): Res
|
|
|
573
580
|
let diff
|
|
574
581
|
if (oldResources[index]) {
|
|
575
582
|
diff = createDiff(content, parseResource(oldResources[index])[1])
|
|
583
|
+
// Ignore empty lines
|
|
584
|
+
diff = diff.filter(line => line.text !== '')
|
|
576
585
|
} else {
|
|
577
586
|
diff = createDiff(content, content)
|
|
578
587
|
}
|