@larsgw/formica 0.8.3 → 0.8.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.
@@ -49,37 +49,7 @@ exports.parseFileHeader = parseFileHeader;
49
49
  var yaml = __importStar(require("js-yaml"));
50
50
  var resource_1 = require("./resource");
51
51
  var diff_resource_1 = require("./diff-resource");
52
- var RANKS = [
53
- 'phylum',
54
- 'subphylum',
55
- 'class',
56
- 'infraclass',
57
- 'superorder',
58
- 'order',
59
- 'suborder',
60
- 'infraorder',
61
- 'superfamily',
62
- 'family',
63
- 'subfamily',
64
- 'tribe',
65
- 'subtribe',
66
- 'genus',
67
- 'subgenus',
68
- 'section', // not ICZN
69
- 'subsection', // not ICZN
70
- 'series', // not ICZN
71
- 'group',
72
- 'subgroup', // ...
73
- 'aggregate', // not ICZN
74
- 'complex', // not ICZN
75
- 'species',
76
- 'subspecies',
77
- 'variety',
78
- 'form',
79
- 'aberration', // not ICZN
80
- 'race', // not ICZN
81
- 'stirps' // not ICZN
82
- ];
52
+ var parse_name_1 = require("./parse-name");
83
53
  var MAIN_RANKS = [
84
54
  'kingdom',
85
55
  'phylum',
@@ -105,285 +75,14 @@ var FLAGS = [
105
75
  'MISSING_SYNONYMS',
106
76
  'MISSING_AUTHORSHIP'
107
77
  ];
108
- var TAXONOMIC_STATUS = {
109
- '>': 'incorrect',
110
- '+': 'heterotypic synonym',
111
- '=': 'synonym'
112
- };
113
- var RANK_LABELS = {
114
- 'subspecies': 'subsp.',
115
- 'variety': 'var.',
116
- 'form': 'f.',
117
- 'aberration': 'ab.',
118
- 'race': 'r.',
119
- 'stirps': 'st.'
120
- };
121
- var RANK_LABELS_REVERSE = {
122
- 'st': 'stirps',
123
- 'r': 'race',
124
- 'ab': 'aberration',
125
- 'f': 'form',
126
- 'var': 'variety',
127
- 'ssp': 'subspecies',
128
- 'subsp': 'subspecies'
129
- };
130
- var HYBRID_SIGN = '\u00D7';
131
- /**
132
- * 1. Any number of
133
- * - capitalized words
134
- * - "&"
135
- * - " in "
136
- * - " ex "
137
- * - lowercase name particles
138
- * 2. Followed by a capitalized word
139
- * 3. Optionally, followed by "et al."
140
- */
141
- var LOWERCASE_NAME_PARTICLES = ['y', 'der', 'den', 'de', 'van', 'von'].join('|');
142
- var SIMPLE_AUTHOR_PATTERN = '(?:(?:\\p{Lu}\\S*|&|in|ex|' + LOWERCASE_NAME_PARTICLES + ')\\s*)*\\p{Lu}\\S+(?:\\s+et\\s+al\\.)?';
143
- var NAME_PATTERN = new RegExp('^' +
144
- // $1 main name part
145
- '(\\S+)' +
146
- // $2 optional author citation
147
- '(?: ' +
148
- // but not auct(t)., etc.
149
- '(?!auctt?\\.|(?:syn|comb|sp|spec|nom|gen|subgen)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.)|in part|partim)' +
150
- '(' +
151
- // $2.1 anything in parentheses, followed by optional revising author(s)
152
- '\\(.+?\\)(?:\\s+' + SIMPLE_AUTHOR_PATTERN + ')?' +
153
- '|' +
154
- // $2.2 anything followed by a year
155
- '.+?\\d{4}\\)?' +
156
- '|' +
157
- // $2.3 author(s)
158
- SIMPLE_AUTHOR_PATTERN +
159
- '))?' +
160
- // $3 optional notes
161
- '(?:,? (.+))?' +
162
- '$', 'u');
163
- /**
164
- * Structure
165
- * $1 genus+subgenus (+ trailing space): (?:([A-Z]\S+) (?:\(([A-Z]\S+?)\) )?)?
166
- * $1.1 genus: ((?:x )?[A-Z]\S+)
167
- * $1.2 subgenus: (?:\(([A-Z]\S+?)\) )?
168
- * $2 species: (x [a-z-]+|[a-z-][^\s.]+(?: x [a-z-]+)?|[A-Z][a-z]+_[a-z-]+ x [A-Z][a-z]+_[a-z-]+)
169
- * $2a: x [a-z-]+
170
- * $2b hybrid: [a-z-][^\s.]+(?: x [a-z-]+)?
171
- * $2c intergeneric hybrid: [A-Z][a-z]+_[a-z-]+ x [A-Z][a-z]+_[a-z-]+
172
- */
173
- var 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-]+)(?= |$)/;
174
- function compareRanks(a, b) {
175
- return RANKS.indexOf(a) - RANKS.indexOf(b);
176
- }
177
- function capitalize(name) {
178
- return name[0].toUpperCase() + name.slice(1).toLowerCase();
179
- }
180
- function capitalizeGenericName(name) {
181
- if (name[0] === HYBRID_SIGN) {
182
- return HYBRID_SIGN + capitalize(name.slice(1));
183
- }
184
- return capitalize(name);
185
- }
186
- function isUpperCase(name) {
187
- return name === name.toUpperCase();
78
+ var RESOURCE_DELIMITER = '\n\n===\n\n';
79
+ var INDENT = 2;
80
+ function makeParseError(message, line, column) {
81
+ if (column === void 0) { column = 1; }
82
+ return new SyntaxError("[".concat(line, ":").concat(column, "] ").concat(message));
188
83
  }
189
- function getSynonymRank(name, rank) {
190
- var rest = name.replace(BINAME_PATTERN, '');
191
- var rankPrefix = rest.match(/^(?: |^)(st|r|ab|f|var|ssp|subsp)\. /);
192
- if (rankPrefix) {
193
- return RANK_LABELS_REVERSE[rankPrefix[1]];
194
- }
195
- else if (!BINAME_PATTERN.test(name)) {
196
- return rank;
197
- }
198
- else if (/^ (?!sensu)[a-z0-9-]+($| )/.test(rest)) {
199
- return 'subspecies';
200
- }
201
- else {
202
- return 'species';
203
- }
204
- }
205
- function capitalizeAuthors(authors) {
206
- return authors
207
- .replace(/[^\x00-\x40\x5B-\x60\x7B-\x7F]+/g, // eslint-disable-line no-control-regex
208
- function (// eslint-disable-line no-control-regex
209
- name) { return isUpperCase(name) ? capitalize(name) : name; })
210
- .replace(/ Y /g, ' y ');
211
- }
212
- function parseName(name, rank, parent) {
213
- var item = {};
214
- // Synonyms have the accepted name usage as 'parent'.
215
- var isSynonym = /^[+=>] /.test(name);
216
- if (isSynonym) {
217
- item.taxonomicStatus = TAXONOMIC_STATUS[name[0]];
218
- name = name.replace(/^[+=>] (\? ?)?/, '');
219
- rank = getSynonymRank(name, parent.taxonRank);
220
- }
221
- else {
222
- item.taxonomicStatus = 'accepted';
223
- }
224
- // Clusters
225
- if (/^\[(_|\d+)\] /.test(name)) {
226
- name = name.replace(/^\[(_|\d+)\] /, '');
227
- }
228
- // Set verbatim identification after subsequent syntax is removed.
229
- item.verbatimIdentification = name.replace(/(?<=^| )x(?=$| )/g, HYBRID_SIGN).replace(/_/g, ' ');
230
- // Parent context is used for parsing and formatting binomial names.
231
- // For formatting, it needs to match external databases (i.e. be correct).
232
- // For parsing, it needs to match the current file. If relevant parents
233
- // (i.e. genus, species) had mistakes that were corrected, the uncorrected
234
- // genus and species names need to be used.
235
- var parentContext = {
236
- genus: parent.genus,
237
- subgenus: parent.subgenus,
238
- specificEpithet: parent.specificEpithet,
239
- incorrect: {
240
- genus: parent.incorrect && parent.incorrect.genus,
241
- specificEpithet: parent.incorrect && parent.incorrect.specificEpithet
242
- }
243
- };
244
- // Both contexts should be amended in the two cases where binomial names
245
- // are fully used: (1) synonyms and (2) multinomial taxa without parents to
246
- // provide parts of the name (e.g. bare species without a genus parent, or
247
- // even subspecies without a species or genus parent).
248
- if (isSynonym || !parentContext.genus || (compareRanks('species', rank) < 0 && !parentContext.specificEpithet)) {
249
- var _a = name.match(BINAME_PATTERN) || [], genus = _a[1], subgenus = _a[2], species = _a[3];
250
- if (genus) {
251
- parentContext.incorrect.genus = genus;
252
- parentContext.genus = capitalizeGenericName(genus.replace(/(^| )x /, HYBRID_SIGN));
253
- }
254
- if (subgenus) {
255
- parentContext.subgenus = capitalize(subgenus);
256
- }
257
- else if (genus) {
258
- // If a genus is given but no subgenus, remove any existing subgenus
259
- // from the parent context.
260
- delete parentContext.subgenus;
261
- }
262
- if (species && compareRanks('species', rank) < 0) {
263
- parentContext.incorrect.specificEpithet = species;
264
- parentContext.specificEpithet = species.replace(/(^| )x /, HYBRID_SIGN);
265
- }
266
- }
267
- // In taxa of group, species or lower, the name should just contain the
268
- // (infra)specific epithet and the author information & remarks when processing
269
- // further.
270
- if (compareRanks('group', rank) <= 0) {
271
- // Remove genus
272
- var genus = parentContext.incorrect.genus || parentContext.genus || '';
273
- if (name[0] === genus[0] && name.toLowerCase().startsWith(genus.toLowerCase() + ' ')) {
274
- name = name.slice(genus.length + 1);
275
- }
276
- // Remove subgenus
277
- name = name.replace(/^\(.*?\) /, '');
278
- // Infraspecific taxa
279
- if (compareRanks('species', rank) < 0) {
280
- // Remove specific epithet
281
- var species = parentContext.incorrect.specificEpithet || parentContext.specificEpithet || '';
282
- if (name.startsWith(species + ' ')) {
283
- name = name.slice(species.length + 1);
284
- }
285
- // Remove rank abbreviations
286
- name = name.replace(/^(st|r|ab|f|var|ssp|subsp)\. /, '');
287
- }
288
- }
289
- // Hybrids
290
- if (rank === 'genus' && name.startsWith('x ')) {
291
- name = HYBRID_SIGN + name.slice(2);
292
- }
293
- if (rank === 'species' && /(^| )x /.test(name)) {
294
- name = name.replace(/(^| )x /, HYBRID_SIGN);
295
- }
296
- // Divide the name into the main scientific name (only the epithet for taxa
297
- // lower than genus), the authorship information, and optionally remarks
298
- var nameParts = name.match(NAME_PATTERN);
299
- if (!nameParts) {
300
- throw new Error("Taxon \"".concat(name, "\" could not be parsed"));
301
- }
302
- // To encode old names with spaces (e.g. "Orsillus pini canariensis Lindberg, 1953")
303
- // underscores are used, which are replaced here. This is also used for undescribed
304
- // species (e.g. "Leiobunum species A") and intergeneric hybrids (e.g. "×Festulpia
305
- // Festuca rubra × Vulpia bromoides")
306
- if (nameParts[1].includes('_')) {
307
- nameParts[1] = nameParts[1].replace(/_/g, ' ');
308
- }
309
- var _ = nameParts[0], taxon = nameParts[1], _b = nameParts[2], citation = _b === void 0 ? '' : _b, notes = nameParts[3];
310
- item.scientificNameAuthorship = capitalizeAuthors(citation);
311
- item.taxonRemarks = notes;
312
- item.taxonRank = rank;
313
- // @ts-expect-error TS1501: This regular expression flag is only available when targeting 'es6' or later.
314
- if (/[^\p{L}0-9\u{00D7}\- ]/u.test(taxon)) {
315
- throw new Error("Taxon name contains unexpected characters: \"".concat(taxon, "\""));
316
- }
317
- // Validate names and recompose binomial and trinomial names
318
- if (rank === 'genus') {
319
- item.scientificName = capitalizeGenericName(taxon);
320
- if (taxon[0].toUpperCase() !== taxon[0] || (taxon[0] === HYBRID_SIGN && taxon[1].toUpperCase() !== taxon[1])) {
321
- throw new Error("Generic epithet should be capitalized: \"".concat(taxon, "\""));
322
- }
323
- }
324
- else if (compareRanks('group', rank) > 0) {
325
- item.scientificName = capitalize(taxon);
326
- if (taxon[0].toUpperCase() !== taxon[0]) {
327
- throw new Error("Taxon name (".concat(rank, ") should be capitalized: \"").concat(taxon, "\""));
328
- }
329
- }
330
- else if (rank === 'group') {
331
- item.genericName = parentContext.genus;
332
- item.infragenericEpithet = parentContext.subgenus;
333
- var specificEpithet = taxon.toLowerCase().replace(/(-group)?$/, '');
334
- item.scientificName = "".concat(item.genericName, " ").concat(specificEpithet, "-group");
335
- if (taxon.toLowerCase() !== taxon) {
336
- console.log(item, taxon);
337
- throw new Error("Group name should be lowercase: \"".concat(taxon, "\""));
338
- }
339
- }
340
- else if (rank === 'subgroup') {
341
- item.genericName = parentContext.genus;
342
- item.infragenericEpithet = parentContext.subgenus;
343
- var specificEpithet = taxon.toLowerCase().replace(/(-subgroup)?$/, '');
344
- item.scientificName = "".concat(item.genericName, " ").concat(specificEpithet, "-subgroup");
345
- if (taxon.toLowerCase() !== taxon) {
346
- console.log(item, taxon);
347
- throw new Error("Subgroup name should be lowercase: \"".concat(taxon, "\""));
348
- }
349
- }
350
- else if (rank === 'species') {
351
- item.genericName = parentContext.genus;
352
- item.infragenericEpithet = parentContext.subgenus;
353
- if (taxon.toLowerCase() !== taxon && !/^[A-Z][a-z]+ [a-z]+\xD7[A-Z][a-z]+ [a-z]+$/.test(taxon)) {
354
- console.log(item, taxon);
355
- throw new Error("Specific epithet should be lowercase: \"".concat(taxon, "\""));
356
- }
357
- item.specificEpithet = taxon;
358
- item.scientificName = "".concat(item.genericName, " ").concat(item.specificEpithet);
359
- }
360
- else if (compareRanks('species', rank) < 0) {
361
- item.genericName = parentContext.genus;
362
- item.infragenericEpithet = parentContext.subgenus;
363
- item.specificEpithet = parentContext.specificEpithet;
364
- item.infraspecificEpithet = taxon.toLowerCase();
365
- // If possible, names below species should have abbreviations for ranks,
366
- // like "subsp."
367
- var nameParts_1 = [
368
- item.genericName,
369
- item.specificEpithet,
370
- item.infraspecificEpithet
371
- ];
372
- if (item.taxonRank in RANK_LABELS) {
373
- nameParts_1.splice(2, 0, RANK_LABELS[item.taxonRank]);
374
- }
375
- item.scientificName = nameParts_1.join(' ');
376
- if (item.infraspecificEpithet !== taxon) {
377
- console.log(item, taxon);
378
- throw new Error("Infraspecific epithet should be lowercase: \"".concat(taxon, "\""));
379
- }
380
- }
381
- // Re-add authorship information
382
- item.scientificNameOnly = item.scientificName;
383
- if (item.scientificNameAuthorship) {
384
- item.scientificName += " ".concat(item.scientificNameAuthorship);
385
- }
386
- return item;
84
+ function mergeParserErrors(errors) {
85
+ return new SyntaxError(errors.map(function (error) { return error.message; }).join('\n'));
387
86
  }
388
87
  function parseHeader(header) {
389
88
  var config = yaml.load(header);
@@ -409,7 +108,7 @@ function parseHeader(header) {
409
108
  throw new SyntaxError('Resource contains no taxa');
410
109
  }
411
110
  // Invalid taxon ranks
412
- var invalidTaxonRanks = levels.filter(function (rank) { return !RANKS.includes(rank); });
111
+ var invalidTaxonRanks = levels.filter(function (rank) { return !parse_name_1.RANKS.includes(rank); });
413
112
  if (invalidTaxonRanks.length) {
414
113
  throw new SyntaxError("\"levels\" contains invalid values: ".concat(invalidTaxonRanks.join(', ')));
415
114
  }
@@ -459,66 +158,61 @@ function parseHeader(header) {
459
158
  }
460
159
  return metadata;
461
160
  }
462
- function validateResource(config, content) {
463
- // Check for too much indentation
464
- var longerIndent = new RegExp("^( ){".concat(config.levels.length - 1, "}(?! [+=>] | > ) "), 'm');
465
- var longerIndentMatch = content.match(longerIndent);
466
- if (longerIndentMatch !== null) {
467
- var offset = longerIndentMatch.index;
468
- var line = (content.slice(0, offset).match(/\n/g) || []).length + 1;
469
- throw new SyntaxError("Too much indentation at ".concat(line, ":0\n").concat(content.slice(offset).split('\n', 1), "\n^"));
161
+ function parseResource(resource) {
162
+ var _a = resource.content.split(/(\n---\n+)/), header = _a[0], _ = _a[1], rest = _a.slice(2);
163
+ var config;
164
+ try {
165
+ config = parseHeader(header);
470
166
  }
471
- // Check for missing leaf taxa
472
- var leafTaxonRank = config.levels.filter(function (rank) { return MAIN_RANKS.includes(rank); }).pop();
473
- var leafTaxonParentIndent = config.levels.indexOf(leafTaxonRank) - 1;
474
- if (leafTaxonRank && leafTaxonParentIndent >= 0) {
475
- var missingLeafTaxa = new RegExp("^((?: ){0,".concat(leafTaxonParentIndent, "})(?![+=> ] ).*\\n(\\1( )+[+=>].*\\n)*(?!\\1 )"), 'm');
476
- var missingLeafTaxaMatch = content.match(missingLeafTaxa);
477
- if (missingLeafTaxaMatch !== null) {
478
- var offset = missingLeafTaxaMatch.index;
479
- var line = (content.slice(0, offset).match(/\n/g) || []).length + 1;
480
- throw new SyntaxError("Missing leaf taxon at ".concat(line, ":0\n").concat(content.slice(offset).split('\n', 1), "\n^"));
481
- }
167
+ catch (error) {
168
+ throw makeParseError(error.message, resource.offsetLine + 1);
482
169
  }
483
- }
484
- function parseResource(resource) {
485
- var _a = resource.split(/(\n---\n+)/), header = _a[0], _ = _a[1], rest = _a.slice(2);
486
- var config = parseHeader(header);
487
170
  var content = rest.join('');
488
- return [config, content];
489
- }
490
- function getIndentation(line) {
491
- return line.match(/^ */)[0].length;
171
+ var offsetLine = resource.offsetLine + (header + _).split('\n').length - 1;
172
+ return [config, { content: content, offsetLine: offsetLine }];
492
173
  }
493
- function isIndetLine(line, indent) {
494
- if (indent === undefined) {
495
- indent = getIndentation(line);
496
- }
497
- line = line.slice(indent);
498
- return line.startsWith('[indet]');
499
- }
500
- function parseResourceContent(content, resource, oldIds) {
501
- var idBase = "".concat(resource.id, ":");
174
+ function parseResourceContent(content, resource, oldIds, offsetLine) {
175
+ var _a, _b;
176
+ var leafTaxonIndex = resource.metadata.levels.reduce(function (last, rank, i) { return MAIN_RANKS.includes(rank) ? i : last; }, 0);
502
177
  var data = resource.taxa;
178
+ var errors = [];
503
179
  var id = 0;
180
+ var newId = Math.max.apply(Math, oldIds);
181
+ var lineNumber = offsetLine;
504
182
  var parents = [];
505
- var groupIndent = 0;
506
- var previousId = '';
507
- var newIdOffset = Math.max.apply(Math, oldIds);
183
+ var previous = { id: '', indent: 0, group: { isLeaf: false, indent: 0 } };
508
184
  for (var _i = 0, content_1 = content; _i < content_1.length; _i++) {
509
185
  var line = content_1[_i];
186
+ var hasOriginalId = line.type !== diff_resource_1.ResourceDiffType.Added && !/^\s*(\[indet\]|> )/.test((_a = line.original) !== null && _a !== void 0 ? _a : line.text);
187
+ if (hasOriginalId) {
188
+ id++;
189
+ }
510
190
  if (line.type === diff_resource_1.ResourceDiffType.Deleted) {
511
- // Increase id counter for removed line unless it was an "indet line"
512
- if (!isIndetLine(line.original)) {
513
- id++;
514
- }
515
191
  continue;
516
192
  }
517
- var lineIndent = getIndentation(line.text);
518
- if (lineIndent > groupIndent) {
193
+ else {
194
+ lineNumber++;
195
+ }
196
+ var _c = line.text.match(/^(\s*)(.*)/).slice(1), indentation = _c[0], name_1 = _c[1];
197
+ var lineIndent = indentation.length;
198
+ // Validate line
199
+ if (lineIndent % INDENT === 1) {
200
+ errors.push(makeParseError('Too much or little indentation', lineNumber));
201
+ continue;
202
+ }
203
+ else if (lineIndent / INDENT >= resource.metadata.levels.length && !/^[+=>] /.test(name_1)) {
204
+ errors.push(makeParseError('Too much indentation', lineNumber));
205
+ continue;
206
+ }
207
+ else if (lineIndent <= previous.group.indent && (data[previous.id] && !previous.group.isLeaf)) {
208
+ errors.push(makeParseError('Missing leaf taxon', lineNumber - 1));
209
+ continue;
210
+ }
211
+ // Update parentage
212
+ if (lineIndent > previous.indent) {
519
213
  // Do not count synonyms as parents (unless this is correcting a typo in the synonym)
520
- if (data[previousId] && data[previousId].taxonomicStatus === 'accepted' || /^( {2})+> /.test(line.text)) {
521
- parents.push(previousId);
214
+ if (data[previous.id] && data[previous.id].taxonomicStatus === 'accepted' || name_1.startsWith('> ')) {
215
+ parents.push(previous.id);
522
216
  }
523
217
  else {
524
218
  parents.push(null);
@@ -527,38 +221,36 @@ function parseResourceContent(content, resource, oldIds) {
527
221
  // e.g. if a certain genus has only species
528
222
  // whereas other genera in the same key also
529
223
  // have subgenera
530
- if ((lineIndent - groupIndent) > 2) {
531
- var gap = (lineIndent - groupIndent - 2) / 2;
532
- for (var i = 0; i < gap; i++) {
533
- parents.push(null);
534
- }
224
+ for (var i = previous.indent + INDENT; i < lineIndent; i += INDENT) {
225
+ parents.push(null);
535
226
  }
536
- groupIndent = lineIndent;
537
227
  }
538
- else if (lineIndent < groupIndent) {
539
- parents = parents.slice(0, lineIndent / 2);
540
- groupIndent = lineIndent;
228
+ else if (lineIndent < previous.indent) {
229
+ parents.splice(lineIndent / INDENT);
541
230
  }
231
+ previous.indent = lineIndent;
542
232
  // Do not process "indet" lines further, as they only serve to indicate
543
233
  // that subtaxa are explicitely omitted
544
- if (isIndetLine(line.text, lineIndent)) {
545
- // If the line was previously not and ndet line, increase the id counter
546
- if (line.type === diff_resource_1.ResourceDiffType.Modified && !isIndetLine(line.original)) {
547
- id++;
548
- }
234
+ if (name_1.startsWith('[indet]')) {
235
+ previous.group.isLeaf = lineIndent / INDENT >= leafTaxonIndex;
549
236
  continue;
550
237
  }
551
- var parentId = parents.reduce(function (grandparent, parent) { return parent || grandparent; }, null);
238
+ var parentId = parents.reduce(function (grandparent, parent) { return parent !== null && parent !== void 0 ? parent : grandparent; }, null);
552
239
  var parent_1 = parentId === null ? {} : data[parentId];
553
- var name_1 = line.text.slice(groupIndent);
554
- var rank = resource.metadata.levels[groupIndent / 2];
555
- var item = parseName(name_1, rank, parent_1);
556
- var isSynonym = item.taxonomicStatus !== 'accepted';
240
+ var rank = resource.metadata.levels[parents.length];
241
+ var item = void 0;
242
+ try {
243
+ item = (0, parse_name_1.parseName)(name_1, rank, parent_1);
244
+ }
245
+ catch (error) {
246
+ errors.push(makeParseError(error.message, lineNumber));
247
+ continue;
248
+ }
557
249
  // Add higher classification info
558
250
  var itemAsObject = item;
559
251
  var parentAsObject = parent_1;
560
- for (var _a = 0, DWC_RANKS_1 = DWC_RANKS; _a < DWC_RANKS_1.length; _a++) {
561
- var rank_1 = DWC_RANKS_1[_a];
252
+ for (var _d = 0, DWC_RANKS_1 = DWC_RANKS; _d < DWC_RANKS_1.length; _d++) {
253
+ var rank_1 = DWC_RANKS_1[_d];
562
254
  itemAsObject[rank_1] = undefined;
563
255
  if (parentAsObject[rank_1]) {
564
256
  itemAsObject[rank_1] = parentAsObject[rank_1];
@@ -573,16 +265,7 @@ function parseResourceContent(content, resource, oldIds) {
573
265
  if (item.infragenericEpithet && !item.subgenus) {
574
266
  item.subgenus = item.infragenericEpithet;
575
267
  }
576
- if (isSynonym) {
577
- item.higherClassification = parent_1.higherClassification;
578
- }
579
- else if (parent_1.higherClassification) {
580
- item.higherClassification = parent_1.higherClassification + " | ".concat(parent_1.scientificNameOnly);
581
- }
582
- else if (parentId) {
583
- item.higherClassification = parent_1.scientificNameOnly;
584
- }
585
- // Amend "parent" with corrections
268
+ // Amend "parent" with corrections, exit
586
269
  if (item.taxonomicStatus === 'incorrect') {
587
270
  parent_1.incorrect = __assign({}, parent_1);
588
271
  for (var key in item) {
@@ -592,37 +275,54 @@ function parseResourceContent(content, resource, oldIds) {
592
275
  }
593
276
  continue;
594
277
  }
595
- // Set identifiers
596
- if (line.type === diff_resource_1.ResourceDiffType.Added) {
597
- newIdOffset++;
598
- item.scientificNameID = idBase + newIdOffset.toString();
278
+ // Add more classification info
279
+ var isSynonym = item.taxonomicStatus !== 'accepted';
280
+ if (isSynonym) {
281
+ item.higherClassification = parent_1.higherClassification;
599
282
  }
600
- else if (line.type === diff_resource_1.ResourceDiffType.Modified && isIndetLine(line.original)) {
601
- newIdOffset++;
602
- item.scientificNameID = idBase + newIdOffset.toString();
283
+ else if (parent_1.higherClassification) {
284
+ item.higherClassification = parent_1.higherClassification + " | ".concat(parent_1.scientificNameOnly);
603
285
  }
604
- else {
605
- id++;
606
- item.scientificNameID = idBase + (oldIds[id - 1] || id).toString();
286
+ else if (parentId) {
287
+ item.higherClassification = parent_1.scientificNameOnly;
607
288
  }
608
- previousId = item.scientificNameID;
289
+ // Set identifiers
290
+ item.scientificNameID = "".concat(resource.id, ":").concat(hasOriginalId ? ((_b = oldIds[id - 1]) !== null && _b !== void 0 ? _b : id) : ++newId);
609
291
  item.parentNameUsageID = isSynonym ? undefined : parent_1.scientificNameID;
610
292
  item.parentNameUsage = isSynonym ? undefined : parent_1.scientificName;
611
293
  item.acceptedNameUsageID = isSynonym ? parent_1.scientificNameID : undefined;
612
294
  item.acceptedNameUsage = isSynonym ? parent_1.scientificName : undefined;
613
- item.collectionCode = idBase.slice(0, -1);
295
+ item.collectionCode = resource.id;
614
296
  data[item.scientificNameID] = item;
297
+ // Update loop state
298
+ previous.id = item.scientificNameID;
299
+ if (item.taxonomicStatus === 'accepted') {
300
+ previous.group.indent = previous.indent;
301
+ previous.group.isLeaf = lineIndent / INDENT >= leafTaxonIndex;
302
+ }
303
+ }
304
+ if (errors.length) {
305
+ throw mergeParserErrors(errors);
615
306
  }
616
307
  return resource;
617
308
  }
618
309
  function splitResources(file) {
619
- return file.split('\n\n===\n\n');
310
+ var resources = [];
311
+ var offsetLine = 0;
312
+ for (var _i = 0, _a = file.split(RESOURCE_DELIMITER); _i < _a.length; _i++) {
313
+ var content = _a[_i];
314
+ resources.push({ content: content, offsetLine: offsetLine });
315
+ offsetLine += (content + RESOURCE_DELIMITER).split('\n').length - 1;
316
+ }
317
+ return resources;
620
318
  }
621
319
  function parseFile(file, id, old) {
622
320
  var oldResources = old ? splitResources(old.txt) : [];
623
- return splitResources(file).map(function (resource, index) {
624
- var _a = parseResource(resource), config = _a[0], content = _a[1];
625
- validateResource(config, content);
321
+ var newResources = splitResources(file);
322
+ var resources = [];
323
+ var errors = [];
324
+ for (var index = 0; index < newResources.length; index++) {
325
+ var _a = parseResource(newResources[index]), config = _a[0], content = _a[1];
626
326
  var template = {
627
327
  id: "".concat(id, ":").concat(index + 1),
628
328
  file: "".concat(id, "-").concat(index + 1),
@@ -630,14 +330,14 @@ function parseFile(file, id, old) {
630
330
  metadata: config,
631
331
  taxa: {}
632
332
  };
633
- var diff;
333
+ var diff = void 0;
634
334
  if (oldResources[index]) {
635
- diff = (0, diff_resource_1.createDiff)(content, parseResource(oldResources[index])[1]);
335
+ diff = (0, diff_resource_1.createDiff)(content.content, parseResource(oldResources[index])[1].content);
636
336
  // Ignore empty lines
637
337
  diff = diff.filter(function (line) { return line.text !== ''; });
638
338
  }
639
339
  else {
640
- diff = (0, diff_resource_1.createDiff)(content, content);
340
+ diff = (0, diff_resource_1.createDiff)(content.content, content.content);
641
341
  }
642
342
  var oldIds = [];
643
343
  if (old) {
@@ -646,8 +346,17 @@ function parseFile(file, id, old) {
646
346
  oldIds.push(parseInt(row[0].split(':')[2]));
647
347
  }
648
348
  }
649
- return parseResourceContent(diff, template, oldIds);
650
- });
349
+ try {
350
+ resources.push(parseResourceContent(diff, template, oldIds, content.offsetLine));
351
+ }
352
+ catch (error) {
353
+ errors.push(error);
354
+ }
355
+ }
356
+ if (errors.length) {
357
+ throw mergeParserErrors(errors);
358
+ }
359
+ return resources;
651
360
  }
652
361
  function parseFileHeader(file) {
653
362
  return splitResources(file).map(function (resource) { return parseResource(resource)[0]; });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@larsgw/formica",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
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",
@@ -9,6 +9,7 @@
9
9
  "loir-validate-resources": "./lib/bin/validate-resources-text.js",
10
10
  "loir-resources-process": "./lib/bin/process-resources.js",
11
11
  "loir-resources-index": "./lib/bin/process-resources-index.js",
12
+ "loir-generate-linked-data": "./lib/bin/generate-linked-data.js",
12
13
  "loir-catalog-clean-links": "./lib/bin/clean-links.js"
13
14
  },
14
15
  "scripts": {