@larsgw/formica 0.8.5 → 0.8.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 +24 -0
- package/lib/bin/generate-linked-data.js +200 -323
- package/lib/bin/process-resources-index.js +59 -130
- package/lib/bin/process-resources.js +303 -524
- package/lib/bin/util.js +21 -56
- package/lib/bin/validate-catalog.js +26 -70
- package/lib/bin/validate-resources-text.js +17 -61
- package/lib/catalog/entities.js +28 -84
- package/lib/catalog/entity.js +36 -50
- package/lib/catalog/index.js +11 -12
- package/lib/catalog/tables/author.js +7 -24
- package/lib/catalog/tables/index.js +5 -5
- package/lib/catalog/tables/place.js +7 -24
- package/lib/catalog/tables/publisher.js +7 -24
- package/lib/catalog/tables/taxon.js +7 -24
- package/lib/catalog/tables/work.js +13 -30
- package/lib/catalog/value.js +11 -11
- package/lib/csv.js +7 -8
- package/lib/resources/diff-resource.js +52 -55
- package/lib/resources/parse-name.d.ts +4 -0
- package/lib/resources/parse-name.js +56 -54
- package/lib/resources/parse-text.js +121 -116
- package/lib/resources/resource.js +7 -25
- package/lib/taxon-names/index.js +15 -20
- package/package.json +1 -1
- package/src/resources/diff-resource.ts +19 -15
- package/src/resources/parse-name.ts +24 -19
- package/src/resources/parse-text.ts +35 -6
- package/test/resources.js +182 -89
- package/tsconfig.json +4 -1
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RANKS = void 0;
|
|
3
|
+
exports.RANKS = exports.RecoverableSyntaxError = void 0;
|
|
4
4
|
exports.parseName = parseName;
|
|
5
|
+
class RecoverableSyntaxError extends SyntaxError {
|
|
6
|
+
constructor(message, result) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.result = result;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
exports.RecoverableSyntaxError = RecoverableSyntaxError;
|
|
5
12
|
exports.RANKS = [
|
|
6
13
|
'phylum',
|
|
7
14
|
'subphylum',
|
|
@@ -33,12 +40,12 @@ exports.RANKS = [
|
|
|
33
40
|
'race', // not ICZN
|
|
34
41
|
'stirps' // not ICZN
|
|
35
42
|
];
|
|
36
|
-
|
|
43
|
+
const TAXONOMIC_STATUS = {
|
|
37
44
|
'>': 'incorrect',
|
|
38
45
|
'+': 'heterotypic synonym',
|
|
39
46
|
'=': 'synonym'
|
|
40
47
|
};
|
|
41
|
-
|
|
48
|
+
const RANK_LABELS = {
|
|
42
49
|
'subspecies': 'subsp.',
|
|
43
50
|
'variety': 'var.',
|
|
44
51
|
'form': 'f.',
|
|
@@ -46,7 +53,7 @@ var RANK_LABELS = {
|
|
|
46
53
|
'race': 'r.',
|
|
47
54
|
'stirps': 'st.'
|
|
48
55
|
};
|
|
49
|
-
|
|
56
|
+
const RANK_LABELS_REVERSE = {
|
|
50
57
|
'st': 'stirps',
|
|
51
58
|
'r': 'race',
|
|
52
59
|
'ab': 'aberration',
|
|
@@ -55,7 +62,7 @@ var RANK_LABELS_REVERSE = {
|
|
|
55
62
|
'ssp': 'subspecies',
|
|
56
63
|
'subsp': 'subspecies'
|
|
57
64
|
};
|
|
58
|
-
|
|
65
|
+
const HYBRID_SIGN = '\u00D7';
|
|
59
66
|
/**
|
|
60
67
|
* 1. Any number of
|
|
61
68
|
* - capitalized words
|
|
@@ -66,9 +73,9 @@ var HYBRID_SIGN = '\u00D7';
|
|
|
66
73
|
* 2. Followed by a capitalized word
|
|
67
74
|
* 3. Optionally, followed by "et al."
|
|
68
75
|
*/
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
const LOWERCASE_NAME_PARTICLES = ['y', 'der', 'den', 'de', 'van', 'von'].join('|');
|
|
77
|
+
const SIMPLE_AUTHOR_PATTERN = '(?:(?:\\p{Lu}\\S*|&|in|ex|' + LOWERCASE_NAME_PARTICLES + ')\\s*)*\\p{Lu}\\S+(?:\\s+et\\s+al\\.)?';
|
|
78
|
+
const NAME_PATTERN = new RegExp('^' +
|
|
72
79
|
// $1 main name part
|
|
73
80
|
'(\\S+)' +
|
|
74
81
|
// $2 optional author citation
|
|
@@ -93,7 +100,7 @@ var NAME_PATTERN = new RegExp('^' +
|
|
|
93
100
|
* $1 genus: ((?:x )?[A-Z]\S+)
|
|
94
101
|
* $2 subgenus: (?:\(([A-Z]\S+?)\) )?
|
|
95
102
|
*/
|
|
96
|
-
|
|
103
|
+
const SUBGENUS_PATTERN = /^([A-Z]\S+) (?:\(([A-Z]\S+?)\))(?= |$)/;
|
|
97
104
|
/**
|
|
98
105
|
* Structure
|
|
99
106
|
* $1 genus+subgenus (+ trailing space): (?:([A-Z]\S+) (?:\(([A-Z]\S+?)\) )?)?
|
|
@@ -104,7 +111,7 @@ var SUBGENUS_PATTERN = /^([A-Z]\S+) (?:\(([A-Z]\S+?)\))(?= |$)/;
|
|
|
104
111
|
* $2b hybrid: [a-z-][^\s.]+(?: x [a-z-]+)?
|
|
105
112
|
* $2c intergeneric hybrid: [A-Z][a-z]+_[a-z-]+ x [A-Z][a-z]+_[a-z-]+
|
|
106
113
|
*/
|
|
107
|
-
|
|
114
|
+
const BINAME_PATTERN = /^(?:((?:x )?[A-Z]\S+) (?:\(([A-Z]\S+?)\) )?)?(x [a-z-]+|[a-z-][^\s.]+(?: x [a-z-]+)?|[A-Z][a-z]+_[a-z-]+ x [A-Z][a-z]+_[a-z-]+)(?= |$)/;
|
|
108
115
|
function compareRanks(a, b) {
|
|
109
116
|
return exports.RANKS.indexOf(a) - exports.RANKS.indexOf(b);
|
|
110
117
|
}
|
|
@@ -121,16 +128,13 @@ function isUpperCase(name) {
|
|
|
121
128
|
return name === name.toUpperCase();
|
|
122
129
|
}
|
|
123
130
|
function getSynonymRank(name, rank) {
|
|
124
|
-
|
|
125
|
-
|
|
131
|
+
const rest = name.replace(BINAME_PATTERN, '');
|
|
132
|
+
const rankPrefix = rest.match(/^(?: |^)(st|r|ab|f|var|ssp|subsp)\. /);
|
|
126
133
|
if (rankPrefix) {
|
|
127
134
|
return RANK_LABELS_REVERSE[rankPrefix[1]];
|
|
128
135
|
}
|
|
129
|
-
else if (SUBGENUS_PATTERN.test(name)) {
|
|
130
|
-
return 'subgenus';
|
|
131
|
-
}
|
|
132
136
|
else if (!BINAME_PATTERN.test(name)) {
|
|
133
|
-
return rank;
|
|
137
|
+
return SUBGENUS_PATTERN.test(name) ? 'subgenus' : rank;
|
|
134
138
|
}
|
|
135
139
|
else if (/^ (?!sensu)[a-z0-9-]+($| )/.test(rest)) {
|
|
136
140
|
return 'subspecies';
|
|
@@ -142,15 +146,15 @@ function getSynonymRank(name, rank) {
|
|
|
142
146
|
function capitalizeAuthors(authors) {
|
|
143
147
|
return authors
|
|
144
148
|
.replace(/[^\x00-\x40\x5B-\x60\x7B-\x7F]+/g, // eslint-disable-line no-control-regex
|
|
145
|
-
|
|
146
|
-
name
|
|
149
|
+
// eslint-disable-line no-control-regex
|
|
150
|
+
name => isUpperCase(name) ? capitalize(name) : name)
|
|
147
151
|
.replace(/ Y /g, ' y ');
|
|
148
152
|
}
|
|
149
153
|
function parseName(name, rank, parent) {
|
|
150
154
|
var _a, _b;
|
|
151
|
-
|
|
155
|
+
const item = {};
|
|
152
156
|
// Synonyms have the accepted name usage as 'parent'.
|
|
153
|
-
|
|
157
|
+
const isSynonym = /^[+=>] /.test(name);
|
|
154
158
|
if (isSynonym) {
|
|
155
159
|
item.taxonomicStatus = TAXONOMIC_STATUS[name[0]];
|
|
156
160
|
name = name.replace(/^[+=>] (\? ?)?/, '');
|
|
@@ -170,7 +174,7 @@ function parseName(name, rank, parent) {
|
|
|
170
174
|
// For parsing, it needs to match the current file. If relevant parents
|
|
171
175
|
// (i.e. genus, species) had mistakes that were corrected, the uncorrected
|
|
172
176
|
// genus and species names need to be used.
|
|
173
|
-
|
|
177
|
+
const parentContext = {
|
|
174
178
|
genus: parent.genus,
|
|
175
179
|
subgenus: parent.subgenus,
|
|
176
180
|
specificEpithet: parent.specificEpithet,
|
|
@@ -184,7 +188,7 @@ function parseName(name, rank, parent) {
|
|
|
184
188
|
// provide parts of the name (e.g. bare species without a genus parent, or
|
|
185
189
|
// even subspecies without a species or genus parent).
|
|
186
190
|
if (isSynonym || !parentContext.genus || (compareRanks('species', rank) < 0 && !parentContext.specificEpithet)) {
|
|
187
|
-
|
|
191
|
+
const [, genus, subgenus, species] = (_b = (_a = name.match(BINAME_PATTERN)) !== null && _a !== void 0 ? _a : name.match(SUBGENUS_PATTERN)) !== null && _b !== void 0 ? _b : [];
|
|
188
192
|
if (genus) {
|
|
189
193
|
parentContext.incorrect.genus = genus;
|
|
190
194
|
parentContext.genus = capitalizeGenericName(genus.replace(/(^| )x /, HYBRID_SIGN));
|
|
@@ -207,7 +211,7 @@ function parseName(name, rank, parent) {
|
|
|
207
211
|
// further.
|
|
208
212
|
if (compareRanks('group', rank) <= 0) {
|
|
209
213
|
// Remove genus
|
|
210
|
-
|
|
214
|
+
const genus = parentContext.incorrect.genus || parentContext.genus || '';
|
|
211
215
|
if (name[0] === genus[0] && name.toLowerCase().startsWith(genus.toLowerCase() + ' ')) {
|
|
212
216
|
name = name.slice(genus.length + 1);
|
|
213
217
|
}
|
|
@@ -216,7 +220,7 @@ function parseName(name, rank, parent) {
|
|
|
216
220
|
// Infraspecific taxa
|
|
217
221
|
if (compareRanks('species', rank) < 0) {
|
|
218
222
|
// Remove specific epithet
|
|
219
|
-
|
|
223
|
+
const species = parentContext.incorrect.specificEpithet || parentContext.specificEpithet || '';
|
|
220
224
|
if (name.startsWith(species + ' ')) {
|
|
221
225
|
name = name.slice(species.length + 1);
|
|
222
226
|
}
|
|
@@ -226,7 +230,7 @@ function parseName(name, rank, parent) {
|
|
|
226
230
|
}
|
|
227
231
|
else if (compareRanks('genus', rank) <= 0) {
|
|
228
232
|
// Remove genus
|
|
229
|
-
|
|
233
|
+
const genus = parentContext.incorrect.genus || parentContext.genus || '';
|
|
230
234
|
if (name[0] === genus[0] && name.toLowerCase().startsWith(genus.toLowerCase() + ' (')) {
|
|
231
235
|
name = name.slice(genus.length + 1);
|
|
232
236
|
}
|
|
@@ -242,9 +246,9 @@ function parseName(name, rank, parent) {
|
|
|
242
246
|
}
|
|
243
247
|
// Divide the name into the main scientific name (only the epithet for taxa
|
|
244
248
|
// lower than genus), the authorship information, and optionally remarks
|
|
245
|
-
|
|
249
|
+
const nameParts = name.match(NAME_PATTERN);
|
|
246
250
|
if (!nameParts) {
|
|
247
|
-
throw new Error(
|
|
251
|
+
throw new Error(`Taxon "${name}" could not be parsed`);
|
|
248
252
|
}
|
|
249
253
|
// To encode old names with spaces (e.g. "Orsillus pini canariensis Lindberg, 1953")
|
|
250
254
|
// underscores are used, which are replaced here. This is also used for undescribed
|
|
@@ -253,25 +257,28 @@ function parseName(name, rank, parent) {
|
|
|
253
257
|
if (nameParts[1].includes('_')) {
|
|
254
258
|
nameParts[1] = nameParts[1].replace(/_/g, ' ');
|
|
255
259
|
}
|
|
256
|
-
|
|
260
|
+
const [_, taxon, citation = '', notes] = nameParts;
|
|
257
261
|
item.scientificNameAuthorship = capitalizeAuthors(citation);
|
|
258
262
|
item.taxonRemarks = notes;
|
|
259
263
|
item.taxonRank = rank;
|
|
260
|
-
|
|
264
|
+
item.genericName = undefined;
|
|
265
|
+
item.infragenericEpithet = undefined;
|
|
266
|
+
item.specificEpithet = undefined;
|
|
267
|
+
item.infraspecificEpithet = undefined;
|
|
261
268
|
if (/[^\p{L}0-9\u{00D7}\- ]/u.test(taxon)) {
|
|
262
|
-
throw new
|
|
269
|
+
throw new RecoverableSyntaxError(`Taxon name contains unexpected characters: "${taxon}"`, item);
|
|
263
270
|
}
|
|
264
271
|
// Validate names and recompose binomial and trinomial names
|
|
265
272
|
if (compareRanks('genus', rank) > 0) {
|
|
266
273
|
item.scientificName = capitalize(taxon);
|
|
267
274
|
if (taxon[0].toUpperCase() !== taxon[0]) {
|
|
268
|
-
throw new
|
|
275
|
+
throw new RecoverableSyntaxError(`Taxon name (${rank}) should be capitalized: "${taxon}"`, item);
|
|
269
276
|
}
|
|
270
277
|
}
|
|
271
278
|
else if (rank === 'genus') {
|
|
272
279
|
item.scientificName = capitalizeGenericName(taxon);
|
|
273
280
|
if (taxon[0].toUpperCase() !== taxon[0] || (taxon[0] === HYBRID_SIGN && taxon[1].toUpperCase() !== taxon[1])) {
|
|
274
|
-
throw new
|
|
281
|
+
throw new RecoverableSyntaxError(`Generic epithet should be capitalized: "${taxon}"`, item);
|
|
275
282
|
}
|
|
276
283
|
}
|
|
277
284
|
else if (compareRanks('group', rank) > 0) {
|
|
@@ -279,48 +286,44 @@ function parseName(name, rank, parent) {
|
|
|
279
286
|
item.infragenericEpithet = parentContext.subgenus;
|
|
280
287
|
item.scientificName = capitalize(taxon);
|
|
281
288
|
if (taxon[0].toUpperCase() !== taxon[0]) {
|
|
282
|
-
throw new
|
|
289
|
+
throw new RecoverableSyntaxError(`Infrageneric epithet should be capitalized: "${taxon}"`, item);
|
|
283
290
|
}
|
|
284
291
|
}
|
|
285
292
|
else if (rank === 'group') {
|
|
286
293
|
item.genericName = parentContext.genus;
|
|
287
294
|
item.infragenericEpithet = parentContext.subgenus;
|
|
288
|
-
|
|
289
|
-
item.scientificName =
|
|
295
|
+
const specificEpithet = taxon.toLowerCase().replace(/(-group)?$/, '');
|
|
296
|
+
item.scientificName = `${item.genericName} ${specificEpithet}-group`;
|
|
290
297
|
if (taxon.toLowerCase() !== taxon) {
|
|
291
|
-
|
|
292
|
-
throw new Error("Group name should be lowercase: \"".concat(taxon, "\""));
|
|
298
|
+
throw new RecoverableSyntaxError(`Group name should be lowercase: "${taxon}"`, item);
|
|
293
299
|
}
|
|
294
300
|
}
|
|
295
301
|
else if (rank === 'subgroup') {
|
|
296
302
|
item.genericName = parentContext.genus;
|
|
297
303
|
item.infragenericEpithet = parentContext.subgenus;
|
|
298
|
-
|
|
299
|
-
item.scientificName =
|
|
304
|
+
const specificEpithet = taxon.toLowerCase().replace(/(-subgroup)?$/, '');
|
|
305
|
+
item.scientificName = `${item.genericName} ${specificEpithet}-subgroup`;
|
|
300
306
|
if (taxon.toLowerCase() !== taxon) {
|
|
301
|
-
|
|
302
|
-
throw new Error("Subgroup name should be lowercase: \"".concat(taxon, "\""));
|
|
307
|
+
throw new RecoverableSyntaxError(`Subgroup name should be lowercase: "${taxon}"`, item);
|
|
303
308
|
}
|
|
304
309
|
}
|
|
305
310
|
else if (compareRanks('species', rank) > 0) {
|
|
306
311
|
item.genericName = parentContext.genus;
|
|
307
312
|
item.infragenericEpithet = parentContext.subgenus;
|
|
308
|
-
|
|
309
|
-
item.scientificName =
|
|
313
|
+
const specificEpithet = taxon.toLowerCase();
|
|
314
|
+
item.scientificName = `${item.genericName} ${specificEpithet}`;
|
|
310
315
|
if (specificEpithet !== taxon) {
|
|
311
|
-
|
|
312
|
-
throw new Error("Subgroup name should be lowercase: \"".concat(taxon, "\""));
|
|
316
|
+
throw new RecoverableSyntaxError(`Taxon name should be lowercase: "${taxon}"`, item);
|
|
313
317
|
}
|
|
314
318
|
}
|
|
315
319
|
else if (rank === 'species') {
|
|
316
320
|
item.genericName = parentContext.genus;
|
|
317
321
|
item.infragenericEpithet = parentContext.subgenus;
|
|
318
322
|
if (taxon.toLowerCase() !== taxon && !/^[A-Z][a-z]+ [a-z]+\xD7[A-Z][a-z]+ [a-z]+$/.test(taxon)) {
|
|
319
|
-
|
|
320
|
-
throw new Error("Specific epithet should be lowercase: \"".concat(taxon, "\""));
|
|
323
|
+
throw new RecoverableSyntaxError(`Specific epithet should be lowercase: "${taxon}"`, item);
|
|
321
324
|
}
|
|
322
325
|
item.specificEpithet = taxon;
|
|
323
|
-
item.scientificName =
|
|
326
|
+
item.scientificName = `${item.genericName} ${item.specificEpithet}`;
|
|
324
327
|
}
|
|
325
328
|
else if (compareRanks('species', rank) < 0) {
|
|
326
329
|
item.genericName = parentContext.genus;
|
|
@@ -329,24 +332,23 @@ function parseName(name, rank, parent) {
|
|
|
329
332
|
item.infraspecificEpithet = taxon.toLowerCase();
|
|
330
333
|
// If possible, names below species should have abbreviations for ranks,
|
|
331
334
|
// like "subsp."
|
|
332
|
-
|
|
335
|
+
const nameParts = [
|
|
333
336
|
item.genericName,
|
|
334
337
|
item.specificEpithet,
|
|
335
338
|
item.infraspecificEpithet
|
|
336
339
|
];
|
|
337
340
|
if (item.taxonRank in RANK_LABELS) {
|
|
338
|
-
|
|
341
|
+
nameParts.splice(2, 0, RANK_LABELS[item.taxonRank]);
|
|
339
342
|
}
|
|
340
|
-
item.scientificName =
|
|
343
|
+
item.scientificName = nameParts.join(' ');
|
|
341
344
|
if (item.infraspecificEpithet !== taxon) {
|
|
342
|
-
|
|
343
|
-
throw new Error("Infraspecific epithet should be lowercase: \"".concat(taxon, "\""));
|
|
345
|
+
throw new RecoverableSyntaxError(`Infraspecific epithet should be lowercase: "${taxon}"`, item);
|
|
344
346
|
}
|
|
345
347
|
}
|
|
346
348
|
// Re-add authorship information
|
|
347
349
|
item.scientificNameOnly = item.scientificName;
|
|
348
350
|
if (item.scientificNameAuthorship) {
|
|
349
|
-
item.scientificName +=
|
|
351
|
+
item.scientificName += ` ${item.scientificNameAuthorship}`;
|
|
350
352
|
}
|
|
351
353
|
return item;
|
|
352
354
|
}
|