@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.
- package/.eslintrc.js +16 -0
- package/LICENSE +21 -0
- package/README.md +19 -0
- package/lib/bin/process-resources-index.d.ts +1 -0
- package/lib/bin/process-resources-index.js +142 -0
- package/lib/bin/process-resources.d.ts +1 -0
- package/lib/bin/process-resources.js +594 -0
- package/lib/bin/util.d.ts +16 -0
- package/lib/bin/util.js +125 -0
- package/lib/bin/validate-catalog.d.ts +2 -0
- package/lib/bin/validate-catalog.js +92 -0
- package/lib/bin/validate-resources-text.d.ts +2 -0
- package/lib/bin/validate-resources-text.js +78 -0
- package/lib/catalog/entities.d.ts +12 -0
- package/lib/catalog/entities.js +111 -0
- package/lib/catalog/entity.d.ts +13 -0
- package/lib/catalog/entity.js +103 -0
- package/lib/catalog/index.d.ts +4 -0
- package/lib/catalog/index.js +33 -0
- package/lib/catalog/tables/author.d.ts +4 -0
- package/lib/catalog/tables/author.js +33 -0
- package/lib/catalog/tables/index.d.ts +2 -0
- package/lib/catalog/tables/index.js +13 -0
- package/lib/catalog/tables/place.d.ts +4 -0
- package/lib/catalog/tables/place.js +32 -0
- package/lib/catalog/tables/publisher.d.ts +4 -0
- package/lib/catalog/tables/publisher.js +33 -0
- package/lib/catalog/tables/work.d.ts +5 -0
- package/lib/catalog/tables/work.js +82 -0
- package/lib/catalog/value.d.ts +19 -0
- package/lib/catalog/value.js +49 -0
- package/lib/csv.d.ts +2 -0
- package/lib/csv.js +37 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +6 -0
- package/lib/resources/diff-resource.d.ts +7 -0
- package/lib/resources/diff-resource.js +152 -0
- package/lib/resources/index.d.ts +1 -0
- package/lib/resources/index.js +6 -0
- package/lib/resources/parse-text.d.ts +2 -0
- package/lib/resources/parse-text.js +499 -0
- package/lib/types.d.ts +62 -0
- package/lib/types.js +0 -0
- package/package.json +42 -0
- package/src/bin/process-resources-index.ts +73 -0
- package/src/bin/process-resources.ts +406 -0
- package/src/bin/util.ts +74 -0
- package/src/bin/validate-catalog.ts +37 -0
- package/src/bin/validate-resources-text.ts +25 -0
- package/src/catalog/entities.ts +62 -0
- package/src/catalog/entity.ts +113 -0
- package/src/catalog/index.ts +32 -0
- package/src/catalog/tables/author.ts +13 -0
- package/src/catalog/tables/index.ts +12 -0
- package/src/catalog/tables/place.ts +12 -0
- package/src/catalog/tables/publisher.ts +13 -0
- package/src/catalog/tables/work.ts +62 -0
- package/src/catalog/value.ts +48 -0
- package/src/csv.ts +33 -0
- package/src/index.ts +3 -0
- package/src/module.d.ts +105 -0
- package/src/resources/diff-resource.ts +155 -0
- package/src/resources/index.ts +4 -0
- package/src/resources/parse-text.ts +519 -0
- 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
|
+
})
|