@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,15 +1,4 @@
|
|
|
1
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
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
3
|
if (k2 === undefined) k2 = k;
|
|
15
4
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -46,11 +35,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
46
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
36
|
exports.parseFile = parseFile;
|
|
48
37
|
exports.parseFileHeader = parseFileHeader;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
38
|
+
const yaml = __importStar(require("js-yaml"));
|
|
39
|
+
const resource_1 = require("./resource");
|
|
40
|
+
const diff_resource_1 = require("./diff-resource");
|
|
41
|
+
const parse_name_1 = require("./parse-name");
|
|
42
|
+
const MAIN_RANKS = [
|
|
54
43
|
'kingdom',
|
|
55
44
|
'phylum',
|
|
56
45
|
'class',
|
|
@@ -59,7 +48,7 @@ var MAIN_RANKS = [
|
|
|
59
48
|
'genus',
|
|
60
49
|
'species'
|
|
61
50
|
];
|
|
62
|
-
|
|
51
|
+
const DWC_RANKS = [
|
|
63
52
|
'kingdom',
|
|
64
53
|
'phylum',
|
|
65
54
|
'class',
|
|
@@ -69,28 +58,27 @@ var DWC_RANKS = [
|
|
|
69
58
|
'genus',
|
|
70
59
|
'subgenus'
|
|
71
60
|
];
|
|
72
|
-
|
|
61
|
+
const FLAGS = [
|
|
73
62
|
'MISSING_TAXA',
|
|
74
63
|
'MISSING_PARENT_TAXA',
|
|
75
64
|
'MISSING_SYNONYMS',
|
|
76
65
|
'MISSING_AUTHORSHIP'
|
|
77
66
|
];
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
function makeParseError(message, line, column) {
|
|
81
|
-
|
|
82
|
-
return new SyntaxError("[".concat(line, ":").concat(column, "] ").concat(message));
|
|
67
|
+
const RESOURCE_DELIMITER = '\n\n===\n\n';
|
|
68
|
+
const INDENT = 2;
|
|
69
|
+
function makeParseError(message, line, column = 1) {
|
|
70
|
+
return new SyntaxError(`[${line}:${column}] ${message}`);
|
|
83
71
|
}
|
|
84
72
|
function mergeParserErrors(errors) {
|
|
85
|
-
return new SyntaxError(errors.map(
|
|
73
|
+
return new SyntaxError(errors.map(error => error.message).join('\n'));
|
|
86
74
|
}
|
|
87
75
|
function parseHeader(header) {
|
|
88
|
-
|
|
76
|
+
const config = yaml.load(header);
|
|
89
77
|
if (typeof config !== 'object' || Array.isArray(config) || config === null) {
|
|
90
78
|
throw new SyntaxError('yaml header should be an object');
|
|
91
79
|
}
|
|
92
80
|
// Invalid configuration
|
|
93
|
-
|
|
81
|
+
let levels;
|
|
94
82
|
if (!('levels' in config)) {
|
|
95
83
|
levels = [];
|
|
96
84
|
}
|
|
@@ -108,18 +96,18 @@ function parseHeader(header) {
|
|
|
108
96
|
throw new SyntaxError('Resource contains no taxa');
|
|
109
97
|
}
|
|
110
98
|
// Invalid taxon ranks
|
|
111
|
-
|
|
99
|
+
const invalidTaxonRanks = levels.filter(rank => !parse_name_1.RANKS.includes(rank));
|
|
112
100
|
if (invalidTaxonRanks.length) {
|
|
113
|
-
throw new SyntaxError("
|
|
101
|
+
throw new SyntaxError(`"levels" contains invalid values: ${invalidTaxonRanks.join(', ')}`);
|
|
114
102
|
}
|
|
115
|
-
|
|
103
|
+
const metadata = { levels };
|
|
116
104
|
if ('catalog' in config && typeof config.catalog === 'object' && config.catalog !== null) {
|
|
117
|
-
|
|
105
|
+
const catalog = {};
|
|
118
106
|
if ('id' in config.catalog) {
|
|
119
107
|
throw new SyntaxError('"catalog" should not contain id');
|
|
120
108
|
}
|
|
121
|
-
for (
|
|
122
|
-
|
|
109
|
+
for (const key in config.catalog) {
|
|
110
|
+
const value = config.catalog[key];
|
|
123
111
|
if (typeof value === 'number') {
|
|
124
112
|
catalog[key] = value.toString();
|
|
125
113
|
}
|
|
@@ -127,22 +115,16 @@ function parseHeader(header) {
|
|
|
127
115
|
catalog[key] = value;
|
|
128
116
|
}
|
|
129
117
|
else {
|
|
130
|
-
throw new SyntaxError("
|
|
118
|
+
throw new SyntaxError(`"catalog" should contain only strings ("${key}")`);
|
|
131
119
|
}
|
|
132
120
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
var error = _a.error;
|
|
136
|
-
return error !== 'Value(s) required but missing';
|
|
137
|
-
});
|
|
121
|
+
const work = new resource_1.WorkResource(catalog);
|
|
122
|
+
const errors = work.validate().filter(({ error }) => error !== 'Value(s) required but missing');
|
|
138
123
|
if (errors.length > 0) {
|
|
139
|
-
throw new SyntaxError("
|
|
140
|
-
var field = _a.field, error = _a.error;
|
|
141
|
-
return "[".concat(field, "] ").concat(error);
|
|
142
|
-
}).join('; ')));
|
|
124
|
+
throw new SyntaxError(`"catalog" contains errors: ${errors.map(({ field, error }) => `[${field}] ${error}`).join('; ')}`);
|
|
143
125
|
}
|
|
144
126
|
metadata.catalog = {};
|
|
145
|
-
for (
|
|
127
|
+
for (const key in work.fields) {
|
|
146
128
|
metadata.catalog[key] = work.get(key);
|
|
147
129
|
}
|
|
148
130
|
}
|
|
@@ -150,40 +132,39 @@ function parseHeader(header) {
|
|
|
150
132
|
if (!Array.isArray(config.flags)) {
|
|
151
133
|
throw new SyntaxError('"flags" should be an array if present');
|
|
152
134
|
}
|
|
153
|
-
|
|
135
|
+
const invalidFlags = config.flags.filter(flag => !FLAGS.includes(flag));
|
|
154
136
|
if (invalidFlags.length) {
|
|
155
|
-
throw new SyntaxError("
|
|
137
|
+
throw new SyntaxError(`"flags" contains invalid values: ${invalidFlags.join(', ')}`);
|
|
156
138
|
}
|
|
157
139
|
metadata.flags = config.flags;
|
|
158
140
|
}
|
|
159
141
|
return metadata;
|
|
160
142
|
}
|
|
161
143
|
function parseResource(resource) {
|
|
162
|
-
|
|
163
|
-
|
|
144
|
+
const [header, _, ...rest] = resource.content.split(/(\n---\n+)/);
|
|
145
|
+
let config;
|
|
164
146
|
try {
|
|
165
147
|
config = parseHeader(header);
|
|
166
148
|
}
|
|
167
149
|
catch (error) {
|
|
168
150
|
throw makeParseError(error.message, resource.offsetLine + 1);
|
|
169
151
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
return [config, { content
|
|
152
|
+
const content = rest.join('');
|
|
153
|
+
const offsetLine = resource.offsetLine + (header + _).split('\n').length - 1;
|
|
154
|
+
return [config, { content, offsetLine }];
|
|
173
155
|
}
|
|
174
156
|
function parseResourceContent(content, resource, oldIds, offsetLine) {
|
|
175
157
|
var _a, _b;
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
for (
|
|
185
|
-
|
|
186
|
-
var hasOriginalId = line.type !== diff_resource_1.ResourceDiffType.Added && !/^\s*(\[indet\]|> )/.test((_a = line.original) !== null && _a !== void 0 ? _a : line.text);
|
|
158
|
+
const leafTaxonIndex = resource.metadata.levels.reduce((last, rank, i) => MAIN_RANKS.includes(rank) ? i : last, 0);
|
|
159
|
+
const data = resource.taxa;
|
|
160
|
+
const errors = [];
|
|
161
|
+
let id = 0;
|
|
162
|
+
let newId = Math.max(...oldIds);
|
|
163
|
+
let lineNumber = offsetLine;
|
|
164
|
+
const parents = [];
|
|
165
|
+
const previous = { id: '', indent: 0, group: { isLeaf: false, indent: 0 }, errors: [] };
|
|
166
|
+
for (const line of content) {
|
|
167
|
+
const hasOriginalId = line.type !== diff_resource_1.ResourceDiffType.Added && !/^\s*(\[indet\]|> )/.test((_a = line.original) !== null && _a !== void 0 ? _a : line.text);
|
|
187
168
|
if (hasOriginalId) {
|
|
188
169
|
id++;
|
|
189
170
|
}
|
|
@@ -193,25 +174,24 @@ function parseResourceContent(content, resource, oldIds, offsetLine) {
|
|
|
193
174
|
else {
|
|
194
175
|
lineNumber++;
|
|
195
176
|
}
|
|
196
|
-
|
|
197
|
-
|
|
177
|
+
const [indentation, name] = line.text.match(/^(\s*)(.*)/).slice(1);
|
|
178
|
+
const lineIndent = indentation.length;
|
|
198
179
|
// Validate line
|
|
199
180
|
if (lineIndent % INDENT === 1) {
|
|
200
181
|
errors.push(makeParseError('Too much or little indentation', lineNumber));
|
|
201
182
|
continue;
|
|
202
183
|
}
|
|
203
|
-
else if (lineIndent / INDENT >= resource.metadata.levels.length && !/^[+=>] /.test(
|
|
184
|
+
else if (lineIndent / INDENT >= resource.metadata.levels.length && !/^[+=>] /.test(name)) {
|
|
204
185
|
errors.push(makeParseError('Too much indentation', lineNumber));
|
|
205
186
|
continue;
|
|
206
187
|
}
|
|
207
188
|
else if (lineIndent <= previous.group.indent && (data[previous.id] && !previous.group.isLeaf)) {
|
|
208
189
|
errors.push(makeParseError('Missing leaf taxon', lineNumber - 1));
|
|
209
|
-
continue;
|
|
210
190
|
}
|
|
211
191
|
// Update parentage
|
|
212
192
|
if (lineIndent > previous.indent) {
|
|
213
193
|
// Do not count synonyms as parents (unless this is correcting a typo in the synonym)
|
|
214
|
-
if (data[previous.id] && data[previous.id].taxonomicStatus === 'accepted' ||
|
|
194
|
+
if (data[previous.id] && data[previous.id].taxonomicStatus === 'accepted' || name.startsWith('> ')) {
|
|
215
195
|
parents.push(previous.id);
|
|
216
196
|
}
|
|
217
197
|
else {
|
|
@@ -221,7 +201,7 @@ function parseResourceContent(content, resource, oldIds, offsetLine) {
|
|
|
221
201
|
// e.g. if a certain genus has only species
|
|
222
202
|
// whereas other genera in the same key also
|
|
223
203
|
// have subgenera
|
|
224
|
-
for (
|
|
204
|
+
for (let i = previous.indent + INDENT; i < lineIndent; i += INDENT) {
|
|
225
205
|
parents.push(null);
|
|
226
206
|
}
|
|
227
207
|
}
|
|
@@ -231,32 +211,40 @@ function parseResourceContent(content, resource, oldIds, offsetLine) {
|
|
|
231
211
|
previous.indent = lineIndent;
|
|
232
212
|
// Do not process "indet" lines further, as they only serve to indicate
|
|
233
213
|
// that subtaxa are explicitely omitted
|
|
234
|
-
if (
|
|
214
|
+
if (name.startsWith('[indet]')) {
|
|
215
|
+
errors.push(...previous.errors);
|
|
216
|
+
previous.errors.length = 0;
|
|
235
217
|
previous.group.isLeaf = lineIndent / INDENT >= leafTaxonIndex;
|
|
236
218
|
continue;
|
|
237
219
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
220
|
+
const parentId = parents.reduce((grandparent, parent) => parent !== null && parent !== void 0 ? parent : grandparent, null);
|
|
221
|
+
const parent = parentId === null ? {} : data[parentId];
|
|
222
|
+
let item;
|
|
223
|
+
const itemErrors = [];
|
|
242
224
|
try {
|
|
243
|
-
|
|
225
|
+
const rank = resource.metadata.levels[parents.length];
|
|
226
|
+
item = (0, parse_name_1.parseName)(name, rank, parent);
|
|
244
227
|
}
|
|
245
228
|
catch (error) {
|
|
246
|
-
|
|
247
|
-
|
|
229
|
+
if (error instanceof parse_name_1.RecoverableSyntaxError) {
|
|
230
|
+
itemErrors.push(makeParseError(error.message, lineNumber));
|
|
231
|
+
item = error.result;
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
errors.push(makeParseError(error.message, lineNumber));
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
248
237
|
}
|
|
249
238
|
// Add higher classification info
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
for (
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
itemAsObject[rank_1] = parentAsObject[rank_1];
|
|
239
|
+
const itemAsObject = item;
|
|
240
|
+
const parentAsObject = parent;
|
|
241
|
+
for (const rank of DWC_RANKS) {
|
|
242
|
+
itemAsObject[rank] = undefined;
|
|
243
|
+
if (parentAsObject[rank]) {
|
|
244
|
+
itemAsObject[rank] = parentAsObject[rank];
|
|
257
245
|
}
|
|
258
|
-
if (item.taxonRank ===
|
|
259
|
-
itemAsObject[
|
|
246
|
+
if (item.taxonRank === rank) {
|
|
247
|
+
itemAsObject[rank] = item.scientificNameOnly;
|
|
260
248
|
}
|
|
261
249
|
}
|
|
262
250
|
if (item.genericName && !item.genus) {
|
|
@@ -267,82 +255,99 @@ function parseResourceContent(content, resource, oldIds, offsetLine) {
|
|
|
267
255
|
}
|
|
268
256
|
// Amend "parent" with corrections, exit
|
|
269
257
|
if (item.taxonomicStatus === 'incorrect') {
|
|
270
|
-
|
|
271
|
-
|
|
258
|
+
if (parent.taxonomicStatus !== 'accepted') {
|
|
259
|
+
// Remove corrected synonym from parentage
|
|
260
|
+
parents[parents.length - 1] = null;
|
|
261
|
+
}
|
|
262
|
+
if (parent.incorrect) {
|
|
263
|
+
errors.push(makeParseError('Cannot apply a correction to a previous correction', lineNumber));
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
else if (parentId === null) {
|
|
267
|
+
errors.push(makeParseError('Cannot apply a correction to nothing', lineNumber));
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
parent.incorrect = Object.assign({}, parent);
|
|
271
|
+
for (const key in item) {
|
|
272
272
|
if (key !== 'taxonomicStatus' && key !== 'verbatimIdentification') {
|
|
273
273
|
parentAsObject[key] = itemAsObject[key];
|
|
274
274
|
}
|
|
275
275
|
}
|
|
276
|
+
// If "parent" is corrected, its errors can be dropped
|
|
277
|
+
previous.errors.length = 0;
|
|
278
|
+
// ...but errors associated with the corrected name are added immediately
|
|
279
|
+
errors.push(...itemErrors);
|
|
276
280
|
continue;
|
|
277
281
|
}
|
|
278
282
|
// Add more classification info
|
|
279
|
-
|
|
283
|
+
const isSynonym = item.taxonomicStatus !== 'accepted';
|
|
280
284
|
if (isSynonym) {
|
|
281
|
-
item.higherClassification =
|
|
285
|
+
item.higherClassification = parent.higherClassification;
|
|
282
286
|
}
|
|
283
|
-
else if (
|
|
284
|
-
item.higherClassification =
|
|
287
|
+
else if (parent.higherClassification) {
|
|
288
|
+
item.higherClassification = parent.higherClassification + ` | ${parent.scientificNameOnly}`;
|
|
285
289
|
}
|
|
286
290
|
else if (parentId) {
|
|
287
|
-
item.higherClassification =
|
|
291
|
+
item.higherClassification = parent.scientificNameOnly;
|
|
288
292
|
}
|
|
289
293
|
// Set identifiers
|
|
290
|
-
item.scientificNameID =
|
|
291
|
-
item.parentNameUsageID = isSynonym ? undefined :
|
|
292
|
-
item.parentNameUsage = isSynonym ? undefined :
|
|
293
|
-
item.acceptedNameUsageID = isSynonym ?
|
|
294
|
-
item.acceptedNameUsage = isSynonym ?
|
|
294
|
+
item.scientificNameID = `${resource.id}:${hasOriginalId ? ((_b = oldIds[id - 1]) !== null && _b !== void 0 ? _b : id) : ++newId}`;
|
|
295
|
+
item.parentNameUsageID = isSynonym ? undefined : parent.scientificNameID;
|
|
296
|
+
item.parentNameUsage = isSynonym ? undefined : parent.scientificName;
|
|
297
|
+
item.acceptedNameUsageID = isSynonym ? parent.scientificNameID : undefined;
|
|
298
|
+
item.acceptedNameUsage = isSynonym ? parent.scientificName : undefined;
|
|
295
299
|
item.collectionCode = resource.id;
|
|
296
300
|
data[item.scientificNameID] = item;
|
|
297
301
|
// Update loop state
|
|
302
|
+
errors.push(...previous.errors);
|
|
303
|
+
previous.errors = itemErrors;
|
|
298
304
|
previous.id = item.scientificNameID;
|
|
299
305
|
if (item.taxonomicStatus === 'accepted') {
|
|
300
306
|
previous.group.indent = previous.indent;
|
|
301
307
|
previous.group.isLeaf = lineIndent / INDENT >= leafTaxonIndex;
|
|
302
308
|
}
|
|
303
309
|
}
|
|
310
|
+
errors.push(...previous.errors);
|
|
304
311
|
if (errors.length) {
|
|
305
312
|
throw mergeParserErrors(errors);
|
|
306
313
|
}
|
|
307
314
|
return resource;
|
|
308
315
|
}
|
|
309
316
|
function splitResources(file) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
for (
|
|
313
|
-
|
|
314
|
-
resources.push({ content: content, offsetLine: offsetLine });
|
|
317
|
+
const resources = [];
|
|
318
|
+
let offsetLine = 0;
|
|
319
|
+
for (const content of file.split(RESOURCE_DELIMITER)) {
|
|
320
|
+
resources.push({ content, offsetLine });
|
|
315
321
|
offsetLine += (content + RESOURCE_DELIMITER).split('\n').length - 1;
|
|
316
322
|
}
|
|
317
323
|
return resources;
|
|
318
324
|
}
|
|
319
325
|
function parseFile(file, id, old) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
for (
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
id:
|
|
328
|
-
file:
|
|
326
|
+
const oldResources = old ? splitResources(old.txt) : [];
|
|
327
|
+
const newResources = splitResources(file);
|
|
328
|
+
const resources = [];
|
|
329
|
+
const errors = [];
|
|
330
|
+
for (let index = 0; index < newResources.length; index++) {
|
|
331
|
+
const [config, content] = parseResource(newResources[index]);
|
|
332
|
+
const template = {
|
|
333
|
+
id: `${id}:${index + 1}`,
|
|
334
|
+
file: `${id}-${index + 1}`,
|
|
329
335
|
workId: id,
|
|
330
336
|
metadata: config,
|
|
331
337
|
taxa: {}
|
|
332
338
|
};
|
|
333
|
-
|
|
339
|
+
let diff;
|
|
334
340
|
if (oldResources[index]) {
|
|
335
341
|
diff = (0, diff_resource_1.createDiff)(content.content, parseResource(oldResources[index])[1].content);
|
|
336
342
|
// Ignore empty lines
|
|
337
|
-
diff = diff.filter(
|
|
343
|
+
diff = diff.filter(line => line.text !== '');
|
|
338
344
|
}
|
|
339
345
|
else {
|
|
340
346
|
diff = (0, diff_resource_1.createDiff)(content.content, content.content);
|
|
341
347
|
}
|
|
342
|
-
|
|
348
|
+
const oldIds = [];
|
|
343
349
|
if (old) {
|
|
344
|
-
for (
|
|
345
|
-
var row = _b[_i];
|
|
350
|
+
for (const row of old.dwc[index].slice(1)) {
|
|
346
351
|
oldIds.push(parseInt(row[0].split(':')[2]));
|
|
347
352
|
}
|
|
348
353
|
}
|
|
@@ -359,5 +364,5 @@ function parseFile(file, id, old) {
|
|
|
359
364
|
return resources;
|
|
360
365
|
}
|
|
361
366
|
function parseFileHeader(file) {
|
|
362
|
-
return splitResources(file).map(
|
|
367
|
+
return splitResources(file).map(resource => parseResource(resource)[0]);
|
|
363
368
|
}
|
|
@@ -1,30 +1,12 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
3
|
exports.WorkResource = void 0;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
_this.schema.duplicate_of.format = /^B[1-9]\d*:[1-9]\d*$/;
|
|
26
|
-
return _this;
|
|
4
|
+
const work_1 = require("../catalog/tables/work");
|
|
5
|
+
class WorkResource extends work_1.Work {
|
|
6
|
+
constructor(values) {
|
|
7
|
+
super(values);
|
|
8
|
+
this.schema.version_of.format = /^B[1-9]\d*:[1-9]\d*$/;
|
|
9
|
+
this.schema.duplicate_of.format = /^B[1-9]\d*:[1-9]\d*$/;
|
|
27
10
|
}
|
|
28
|
-
|
|
29
|
-
}(work_1.Work));
|
|
11
|
+
}
|
|
30
12
|
exports.WorkResource = WorkResource;
|
package/lib/taxon-names/index.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.groupNameMatches = groupNameMatches;
|
|
4
4
|
exports.amendResource = amendResource;
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
const MINIMUM_PREFIX_LENGTH = 3;
|
|
6
|
+
const VALID_COMMON_PREFIXES = new Set([
|
|
7
7
|
'Plantae|Tracheophyta',
|
|
8
8
|
'Fungi',
|
|
9
9
|
'Fungi|Ascomycota',
|
|
@@ -11,7 +11,7 @@ var VALID_COMMON_PREFIXES = new Set([
|
|
|
11
11
|
'Fungi|Zygomycota'
|
|
12
12
|
]);
|
|
13
13
|
function getCommonPrefix(a, b) {
|
|
14
|
-
for (
|
|
14
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
15
15
|
if (a[i] !== b[i]) {
|
|
16
16
|
return a.slice(0, i);
|
|
17
17
|
}
|
|
@@ -19,17 +19,17 @@ function getCommonPrefix(a, b) {
|
|
|
19
19
|
return a.slice();
|
|
20
20
|
}
|
|
21
21
|
function isValidPrefix(a, b) {
|
|
22
|
-
|
|
22
|
+
const prefix = getCommonPrefix(a, b);
|
|
23
23
|
return VALID_COMMON_PREFIXES.has(prefix.join('|')) || prefix.length >= MINIMUM_PREFIX_LENGTH;
|
|
24
24
|
}
|
|
25
25
|
function groupNameMatches(results) {
|
|
26
|
-
|
|
27
|
-
for (
|
|
28
|
-
|
|
26
|
+
const prefixes = {};
|
|
27
|
+
for (const scientificNameID in results) {
|
|
28
|
+
for (const result of results[scientificNameID]) {
|
|
29
29
|
if (!prefixes[result.source]) {
|
|
30
30
|
prefixes[result.source] = [];
|
|
31
31
|
}
|
|
32
|
-
|
|
32
|
+
let prefix = prefixes[result.source].find(prefix => isValidPrefix(prefix[0], result.classificationPath));
|
|
33
33
|
if (!prefix) {
|
|
34
34
|
prefix = [result.classificationPath, {}];
|
|
35
35
|
prefixes[result.source].push(prefix);
|
|
@@ -38,21 +38,16 @@ function groupNameMatches(results) {
|
|
|
38
38
|
prefix[0] = getCommonPrefix(prefix[0], result.classificationPath);
|
|
39
39
|
}
|
|
40
40
|
if (scientificNameID in prefix[1]) {
|
|
41
|
-
|
|
41
|
+
continue;
|
|
42
42
|
}
|
|
43
43
|
prefix[1][scientificNameID] = result;
|
|
44
|
-
};
|
|
45
|
-
for (var _i = 0, _a = results[scientificNameID]; _i < _a.length; _i++) {
|
|
46
|
-
var result = _a[_i];
|
|
47
|
-
_loop_1(result);
|
|
48
44
|
}
|
|
49
45
|
}
|
|
50
|
-
|
|
51
|
-
for (
|
|
46
|
+
const groupedNameMatches = {};
|
|
47
|
+
for (const source in prefixes) {
|
|
52
48
|
groupedNameMatches[source] = prefixes[source]
|
|
53
|
-
.sort(
|
|
54
|
-
.reduce(
|
|
55
|
-
var prefix = _a[0], taxa = _a[1];
|
|
49
|
+
.sort((a, b) => Object.keys(b[1]).length - Object.keys(a[1]).length)
|
|
50
|
+
.reduce((map, [prefix, taxa]) => {
|
|
56
51
|
map[prefix.join('|')] = taxa;
|
|
57
52
|
return map;
|
|
58
53
|
}, {});
|
|
@@ -60,8 +55,8 @@ function groupNameMatches(results) {
|
|
|
60
55
|
return groupedNameMatches;
|
|
61
56
|
}
|
|
62
57
|
function amendResource(resource, source, matches) {
|
|
63
|
-
for (
|
|
64
|
-
|
|
58
|
+
for (const id in matches) {
|
|
59
|
+
const match = matches[id];
|
|
65
60
|
if (source === '1') {
|
|
66
61
|
resource.taxa[id].colTaxonID = match.id;
|
|
67
62
|
if (match.currentId) {
|
package/package.json
CHANGED
|
@@ -189,34 +189,38 @@ function getWordTokensFromLines (lines: ResourceDiff): string[] {
|
|
|
189
189
|
return lines.flatMap(change => tokenizeWords(change.text as string).concat('\n'))
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
type MultilineDiffPart = { added: ResourceDiff, deleted: ResourceDiff }
|
|
193
|
+
|
|
194
|
+
function mergeDiffPart (diffPart: MultilineDiffPart): ResourceDiff {
|
|
195
|
+
if (diffPart.added.length && diffPart.deleted.length) {
|
|
196
|
+
return convertWordDiff(diffTokens(getWordTokensFromLines(diffPart.added), getWordTokensFromLines(diffPart.deleted)))
|
|
197
|
+
} else if (diffPart.added.length) {
|
|
198
|
+
return diffPart.added
|
|
199
|
+
} else if (diffPart.deleted.length) {
|
|
200
|
+
return diffPart.deleted.map(change => ({ text: undefined, original: change.text, type: change.type }))
|
|
201
|
+
} else {
|
|
202
|
+
return []
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
192
206
|
export function createDiff (a: string, b: string): ResourceDiff {
|
|
193
207
|
const lines: ResourceDiff = diffTokens(tokenizeLines(a.trimEnd()), tokenizeLines(b.trimEnd()))
|
|
194
208
|
|
|
195
209
|
const changes: ResourceDiff = []
|
|
196
|
-
const diffPart:
|
|
210
|
+
const diffPart: MultilineDiffPart = { added: [], deleted: [] }
|
|
197
211
|
for (let i = 0; i < lines.length; i++) {
|
|
198
212
|
if (lines[i].type === ResourceDiffType.Added) {
|
|
199
213
|
diffPart.added.push(lines[i])
|
|
200
|
-
continue
|
|
201
214
|
} else if (lines[i].type === ResourceDiffType.Deleted) {
|
|
202
215
|
diffPart.deleted.push(lines[i])
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (diffPart.added.length && diffPart.deleted.length) {
|
|
207
|
-
changes.push(...convertWordDiff(diffTokens(getWordTokensFromLines(diffPart.added), getWordTokensFromLines(diffPart.deleted))))
|
|
208
|
-
diffPart.added.length = 0
|
|
209
|
-
diffPart.deleted.length = 0
|
|
210
|
-
} else if (diffPart.added.length) {
|
|
211
|
-
changes.push(...diffPart.added)
|
|
216
|
+
} else {
|
|
217
|
+
changes.push(...mergeDiffPart(diffPart), lines[i])
|
|
212
218
|
diffPart.added.length = 0
|
|
213
|
-
} else if (diffPart.deleted.length) {
|
|
214
|
-
changes.push(...diffPart.deleted.map(change => ({ text: undefined, original: change.text, type: change.type })))
|
|
215
219
|
diffPart.deleted.length = 0
|
|
216
220
|
}
|
|
217
|
-
|
|
218
|
-
changes.push(lines[i])
|
|
219
221
|
}
|
|
220
222
|
|
|
223
|
+
changes.push(...mergeDiffPart(diffPart))
|
|
224
|
+
|
|
221
225
|
return changes
|
|
222
226
|
}
|