@larsgw/formica 0.6.4 → 0.6.6
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 +4 -4
- package/lib/resources/parse-text.js +23 -12
- 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 +18 -2
- package/src/resources/parse-text.ts +24 -12
- package/src/resources/resource.ts +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## [0.6.6](https://github.com/identification-resources/formica/compare/v0.6.5...v0.6.6) (2025-02-16)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **resources:** allow other resources in version_of ([85947b0](https://github.com/identification-resources/formica/commit/85947b0dcfbdb4bfd6537c628b34087a6e6326f7))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [0.6.5](https://github.com/identification-resources/formica/compare/v0.6.4...v0.6.5) (2024-12-20)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **resources:** ignore empty lines in diff mode ([3fb027e](https://github.com/identification-resources/formica/commit/3fb027e8968196910fff2e574e700ac0abee996c))
|
|
16
|
+
* **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)
|
|
17
|
+
* **resources:** support "et al." in author name ([82993bf](https://github.com/identification-resources/formica/commit/82993bffac5e7f3ed00d00c28c836f347b264a43))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
1
21
|
## [0.6.4](https://github.com/identification-resources/formica/compare/v0.6.3...v0.6.4) (2024-12-16)
|
|
2
22
|
|
|
3
23
|
|
|
@@ -454,8 +454,8 @@ var ResourceProcessor = /** @class */ (function () {
|
|
|
454
454
|
}
|
|
455
455
|
return name_2 === a.matchedName ? -1 : name_2 === b.matchedName ? 1 : 0;
|
|
456
456
|
});
|
|
457
|
-
for (var _o = 0,
|
|
458
|
-
var match =
|
|
457
|
+
for (var _o = 0, _p = matches; _o < _p.length; _o++) {
|
|
458
|
+
var match = _p[_o];
|
|
459
459
|
var source = match.dataSourceId;
|
|
460
460
|
var currentRank = match.classificationRanks.split('|').pop();
|
|
461
461
|
if (match.scoreDetails.cardinalityScore === 0) {
|
|
@@ -466,8 +466,8 @@ var ResourceProcessor = /** @class */ (function () {
|
|
|
466
466
|
// GBIF species like "Nomada spec"
|
|
467
467
|
continue;
|
|
468
468
|
}
|
|
469
|
-
for (var
|
|
470
|
-
var loirId = _q
|
|
469
|
+
for (var _q = 0, _r = taxonNames[name_2]; _q < _r.length; _q++) {
|
|
470
|
+
var loirId = _r[_q];
|
|
471
471
|
var taxon = resource.taxa[loirId];
|
|
472
472
|
if (source === 11 && !GBIF_RANKS.includes(taxon.taxonRank)) {
|
|
473
473
|
// Exclude GBIF matches for ranks that are not in GBIF
|
|
@@ -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',
|
|
@@ -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
|
'(?:,? (.+))?' +
|
|
@@ -364,6 +370,9 @@ function parseHeader(header) {
|
|
|
364
370
|
var metadata = { levels: levels };
|
|
365
371
|
if ('catalog' in config && typeof config.catalog === 'object' && config.catalog !== null) {
|
|
366
372
|
var catalog = {};
|
|
373
|
+
if ('id' in config.catalog) {
|
|
374
|
+
throw new SyntaxError('"catalog" should not contain id');
|
|
375
|
+
}
|
|
367
376
|
for (var key in config.catalog) {
|
|
368
377
|
var value = config.catalog[key];
|
|
369
378
|
if (typeof value === 'number') {
|
|
@@ -376,7 +385,7 @@ function parseHeader(header) {
|
|
|
376
385
|
throw new SyntaxError("\"catalog\" should contain only strings (\"".concat(key, "\")"));
|
|
377
386
|
}
|
|
378
387
|
}
|
|
379
|
-
var work = new
|
|
388
|
+
var work = new resource_1.WorkResource(catalog);
|
|
380
389
|
var errors = work.validate().filter(function (_a) {
|
|
381
390
|
var error = _a.error;
|
|
382
391
|
return error !== 'Value(s) required but missing';
|
|
@@ -541,6 +550,8 @@ function parseFile(file, id, old) {
|
|
|
541
550
|
var diff;
|
|
542
551
|
if (oldResources[index]) {
|
|
543
552
|
diff = (0, diff_resource_1.createDiff)(content, parseResource(oldResources[index])[1]);
|
|
553
|
+
// Ignore empty lines
|
|
554
|
+
diff = diff.filter(function (line) { return line.text !== ''; });
|
|
544
555
|
}
|
|
545
556
|
else {
|
|
546
557
|
diff = (0, diff_resource_1.createDiff)(content, content);
|
|
@@ -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.6",
|
|
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
|
},
|
|
@@ -287,6 +287,22 @@ 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) {
|
|
@@ -294,7 +310,7 @@ class ResourceProcessor {
|
|
|
294
310
|
}
|
|
295
311
|
|
|
296
312
|
// Fix author scoring for some species, see https://github.com/gnames/gnverifier/issues/129
|
|
297
|
-
matches.sort((a:
|
|
313
|
+
matches.sort((a: Match, b: Match) => {
|
|
298
314
|
if (a.sortScore !== b.sortScore) {
|
|
299
315
|
return b.sortScore - a.sortScore
|
|
300
316
|
}
|
|
@@ -302,7 +318,7 @@ class ResourceProcessor {
|
|
|
302
318
|
return name === a.matchedName ? -1 : name === b.matchedName ? 1 : 0
|
|
303
319
|
})
|
|
304
320
|
|
|
305
|
-
for (const match of matches) {
|
|
321
|
+
for (const match of matches as Match[]) {
|
|
306
322
|
const source = match.dataSourceId
|
|
307
323
|
const currentRank = match.classificationRanks.split('|').pop()
|
|
308
324
|
|
|
@@ -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[] = [
|
|
@@ -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
|
'(?:,? (.+))?' +
|
|
@@ -387,6 +394,9 @@ function parseHeader (header: string): ResourceMetadata {
|
|
|
387
394
|
|
|
388
395
|
if ('catalog' in config && typeof config.catalog === 'object' && config.catalog !== null) {
|
|
389
396
|
const catalog: Record<string, string> = {}
|
|
397
|
+
if ('id' in config.catalog) {
|
|
398
|
+
throw new SyntaxError('"catalog" should not contain id')
|
|
399
|
+
}
|
|
390
400
|
for (const key in config.catalog) {
|
|
391
401
|
const value = config.catalog[key as keyof object]
|
|
392
402
|
if (typeof value === 'number') {
|
|
@@ -397,7 +407,7 @@ function parseHeader (header: string): ResourceMetadata {
|
|
|
397
407
|
throw new SyntaxError(`"catalog" should contain only strings ("${key}")`)
|
|
398
408
|
}
|
|
399
409
|
}
|
|
400
|
-
const work = new
|
|
410
|
+
const work = new WorkResource(catalog)
|
|
401
411
|
const errors = work.validate().filter(({ error }) => error !== 'Value(s) required but missing')
|
|
402
412
|
if (errors.length > 0) {
|
|
403
413
|
throw new SyntaxError(`"catalog" contains errors: ${errors.map(({ field, error }) => `[${field}] ${error}`).join('; ')}`)
|
|
@@ -573,6 +583,8 @@ export function parseFile (file: string, id: WorkId, old?: ResourceHistory): Res
|
|
|
573
583
|
let diff
|
|
574
584
|
if (oldResources[index]) {
|
|
575
585
|
diff = createDiff(content, parseResource(oldResources[index])[1])
|
|
586
|
+
// Ignore empty lines
|
|
587
|
+
diff = diff.filter(line => line.text !== '')
|
|
576
588
|
} else {
|
|
577
589
|
diff = createDiff(content, content)
|
|
578
590
|
}
|
|
@@ -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
|
+
}
|