@larsgw/formica 0.6.5 → 0.6.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 CHANGED
@@ -1,3 +1,28 @@
1
+ ## [0.6.7](https://github.com/identification-resources/formica/compare/v0.6.6...v0.6.7) (2025-02-26)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **resources:** keep uncorrected name in verbatimIdentification ([dc4d5d1](https://github.com/identification-resources/formica/commit/dc4d5d17e7a412a94538f6162dfd839d808cbc30))
7
+ * **resources:** update heuristics for recognizing author citations ([bca6a7f](https://github.com/identification-resources/formica/commit/bca6a7f5a6c2dc3dc421d885f6270f5464fc5ea5))
8
+ * **resources:** use corrected generic name in output ([b472384](https://github.com/identification-resources/formica/commit/b4723842e87515f076230c603246082bc3bbec9d))
9
+
10
+
11
+ ### Features
12
+
13
+ * **resources:** add support for completeness flags ([55b5427](https://github.com/identification-resources/formica/commit/55b5427125b4f1072191cadbc461ba31d37e6f2e))
14
+
15
+
16
+
17
+ ## [0.6.6](https://github.com/identification-resources/formica/compare/v0.6.5...v0.6.6) (2025-02-16)
18
+
19
+
20
+ ### Features
21
+
22
+ * **resources:** allow other resources in version_of ([85947b0](https://github.com/identification-resources/formica/commit/85947b0dcfbdb4bfd6537c628b34087a6e6326f7))
23
+
24
+
25
+
1
26
  ## [0.6.5](https://github.com/identification-resources/formica/compare/v0.6.4...v0.6.5) (2024-12-20)
2
27
 
3
28
 
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __assign = (this && this.__assign) || function () {
4
+ __assign = Object.assign || function(t) {
5
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
6
+ s = arguments[i];
7
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
8
+ t[p] = s[p];
9
+ }
10
+ return t;
11
+ };
12
+ return __assign.apply(this, arguments);
13
+ };
14
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
15
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
16
+ return new (P || (P = Promise))(function (resolve, reject) {
17
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
18
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
19
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
20
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
21
+ });
22
+ };
23
+ var __generator = (this && this.__generator) || function (thisArg, body) {
24
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
25
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
26
+ function verb(n) { return function (v) { return step([n, v]); }; }
27
+ function step(op) {
28
+ if (f) throw new TypeError("Generator is already executing.");
29
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
30
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
31
+ if (y = 0, t) op = [op[0] & 2, t.value];
32
+ switch (op[0]) {
33
+ case 0: case 1: t = op; break;
34
+ case 4: _.label++; return { value: op[1], done: false };
35
+ case 5: _.label++; y = op[1]; op = [0]; continue;
36
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
37
+ default:
38
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
39
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
40
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
41
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
42
+ if (t[2]) _.ops.pop();
43
+ _.trys.pop(); continue;
44
+ }
45
+ op = body.call(thisArg, _);
46
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
47
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
48
+ }
49
+ };
50
+ Object.defineProperty(exports, "__esModule", { value: true });
51
+ var fs_1 = require("fs");
52
+ var path = require("path");
53
+ var util = require("util");
54
+ var index_1 = require("../index");
55
+ var GBIF_RANKS = [
56
+ 'kingdom',
57
+ 'phyllum',
58
+ 'class',
59
+ 'order',
60
+ 'family',
61
+ 'genus',
62
+ 'species',
63
+ 'subspecies',
64
+ 'variety'
65
+ ];
66
+ var DISPLAY_FIELDS = [
67
+ 'scientificNameID',
68
+ 'taxonRank',
69
+ 'scientificName',
70
+ 'taxonomicStatus',
71
+ 'taxonRemarks',
72
+ 'colTaxonID',
73
+ 'gbifTaxonID'
74
+ ];
75
+ var ResourceProblemsProcessor = /** @class */ (function () {
76
+ function ResourceProblemsProcessor(collectionPath) {
77
+ this.DIR_ROOT = path.resolve(collectionPath);
78
+ this.DIR_DWC = path.join(this.DIR_ROOT, 'dwc');
79
+ this.FILE_INDEX = path.join(this.DIR_ROOT, 'index.json');
80
+ this.FILE_PROBLEMS = path.join(this.DIR_ROOT, 'problems.csv');
81
+ }
82
+ ResourceProblemsProcessor.prototype.run = function () {
83
+ return __awaiter(this, void 0, void 0, function () {
84
+ var problems, _a, _b, resources, _c, _d, _i, problems_1, problem, _work, resourceId, _problemText, resource, missing;
85
+ return __generator(this, function (_e) {
86
+ switch (_e.label) {
87
+ case 0:
88
+ _b = (_a = index_1.csv).parseCsv;
89
+ return [4 /*yield*/, fs_1.promises.readFile(this.FILE_PROBLEMS, 'utf8')];
90
+ case 1:
91
+ problems = _b.apply(_a, [_e.sent()]).slice(1);
92
+ _d = (_c = JSON).parse;
93
+ return [4 /*yield*/, fs_1.promises.readFile(this.FILE_INDEX, 'utf8')];
94
+ case 2:
95
+ resources = _d.apply(_c, [_e.sent()]);
96
+ _i = 0, problems_1 = problems;
97
+ _e.label = 3;
98
+ case 3:
99
+ if (!(_i < problems_1.length)) return [3 /*break*/, 6];
100
+ problem = problems_1[_i];
101
+ _work = problem[0], resourceId = problem[1], _problemText = problem[2];
102
+ return [4 /*yield*/, this.readAmendedResource(resourceId, resources)];
103
+ case 4:
104
+ resource = _e.sent();
105
+ missing = this.getMissingMappings(resource);
106
+ if (missing.length === 0) {
107
+ this.removeProblem(resourceId);
108
+ }
109
+ _e.label = 5;
110
+ case 5:
111
+ _i++;
112
+ return [3 /*break*/, 3];
113
+ case 6: return [2 /*return*/];
114
+ }
115
+ });
116
+ });
117
+ };
118
+ ResourceProblemsProcessor.prototype.readAmendedResource = function (resourceId, resources) {
119
+ return __awaiter(this, void 0, void 0, function () {
120
+ var resource, resourceFileId, resourceFile, _a, header, taxonRows, _b, _c, taxa, _i, taxonRows_1, row, taxon;
121
+ return __generator(this, function (_d) {
122
+ switch (_d.label) {
123
+ case 0:
124
+ resource = resources[resourceId];
125
+ resourceFileId = resourceId.split(':').join('-');
126
+ resourceFile = path.join(this.DIR_DWC, "".concat(resourceFileId, ".csv"));
127
+ _c = (_b = index_1.csv).parseCsv;
128
+ return [4 /*yield*/, fs_1.promises.readFile(resourceFile, 'utf8')];
129
+ case 1:
130
+ _a = _c.apply(_b, [_d.sent()]), header = _a[0], taxonRows = _a.slice(1);
131
+ taxa = {};
132
+ for (_i = 0, taxonRows_1 = taxonRows; _i < taxonRows_1.length; _i++) {
133
+ row = taxonRows_1[_i];
134
+ taxon = row.reduce(function (taxon, value, index) {
135
+ taxon[header[index]] = value;
136
+ return taxon;
137
+ }, {});
138
+ taxa[taxon.scientificNameID] = taxon;
139
+ }
140
+ return [2 /*return*/, __assign(__assign({}, resource), { taxa: taxa })];
141
+ }
142
+ });
143
+ });
144
+ };
145
+ ResourceProblemsProcessor.prototype.getMissingMappings = function (resource) {
146
+ var missing = [];
147
+ for (var id in resource.taxa) {
148
+ var taxon = resource.taxa[id];
149
+ if (taxon.taxonomicStatus !== 'accepted') {
150
+ continue;
151
+ }
152
+ var missingCol = false; // !taxon.colTaxonID
153
+ var missingGbif = GBIF_RANKS.includes(taxon.taxonRank) && !taxon.gbifTaxonID;
154
+ if (missingCol || missingGbif) {
155
+ missing.push(taxon);
156
+ }
157
+ }
158
+ return missing;
159
+ };
160
+ ResourceProblemsProcessor.prototype.removeProblem = function (resourceId) {
161
+ resourceId;
162
+ //
163
+ };
164
+ return ResourceProblemsProcessor;
165
+ }());
166
+ function main() {
167
+ var args = util.parseArgs({ allowPositionals: true });
168
+ var processor = new ResourceProblemsProcessor(args.positionals[0]);
169
+ process.on('exit', function () {
170
+ process.stdout.write('\n');
171
+ });
172
+ processor.run().catch(function (error) {
173
+ console.error(error);
174
+ process.exit(1);
175
+ });
176
+ }
177
+ main();
@@ -13,7 +13,7 @@ var __assign = (this && this.__assign) || function () {
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
14
  exports.parseFileHeader = exports.parseFile = void 0;
15
15
  var yaml = require("js-yaml");
16
- var work_1 = require("../catalog/tables/work");
16
+ var resource_1 = require("./resource");
17
17
  var diff_resource_1 = require("./diff-resource");
18
18
  var RANKS = [
19
19
  'phylum',
@@ -64,6 +64,11 @@ var DWC_RANKS = [
64
64
  'genus',
65
65
  'subgenus'
66
66
  ];
67
+ var FLAGS = [
68
+ 'MISSING_TAXA',
69
+ 'MISSING_PARENT_TAXA',
70
+ 'MISSING_SYNONYMS'
71
+ ];
67
72
  var TAXONOMIC_STATUS = {
68
73
  '>': 'incorrect',
69
74
  '+': 'heterotypic synonym',
@@ -103,7 +108,7 @@ var RANK_LABELS_REVERSE = {
103
108
  * 2. Followed by a capitalized word
104
109
  * 3. Optionally, followed by "et al."
105
110
  */
106
- var LOWERCASE_NAME_PARTICLES = ['y', 'der', 'den', 'de', 'van'].join('|');
111
+ var LOWERCASE_NAME_PARTICLES = ['y', 'der', 'den', 'de', 'van', 'von'].join('|');
107
112
  var SIMPLE_AUTHOR_PATTERN = '(?:(?:\\p{Lu}\\S*|&|in|ex|' + LOWERCASE_NAME_PARTICLES + ')\\s*)*\\p{Lu}\\S+(?:\\s+et\\s+al\\.)?';
108
113
  var NAME_PATTERN = new RegExp('^' +
109
114
  // $1 main name part
@@ -111,7 +116,7 @@ var NAME_PATTERN = new RegExp('^' +
111
116
  // $2 optional author citation
112
117
  '(?: ' +
113
118
  // but not auct(t)., etc.
114
- '(?!auctt?\\.|(?:syn|comb|sp|spec)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.))' +
119
+ '(?!auctt?\\.|(?:syn|comb|sp|spec|nom)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.)|in part|partim)' +
115
120
  '(' +
116
121
  // $2.1 anything in parentheses, followed by optional revising author(s)
117
122
  '\\(.+?\\)(?:\\s+' + SIMPLE_AUTHOR_PATTERN + ')?' +
@@ -326,17 +331,6 @@ function parseName(name, rank, parent) {
326
331
  if (item.scientificNameAuthorship) {
327
332
  item.scientificName += " ".concat(item.scientificNameAuthorship);
328
333
  }
329
- // Amend "parent" with corrections
330
- if (item.taxonomicStatus === 'incorrect') {
331
- var itemAsObject = item;
332
- var parentAsObject = parent;
333
- parent.incorrect = __assign({}, parent);
334
- for (var key in item) {
335
- if (key !== 'taxonomicStatus') {
336
- parentAsObject[key] = itemAsObject[key];
337
- }
338
- }
339
- }
340
334
  return item;
341
335
  }
342
336
  function parseHeader(header) {
@@ -370,6 +364,9 @@ function parseHeader(header) {
370
364
  var metadata = { levels: levels };
371
365
  if ('catalog' in config && typeof config.catalog === 'object' && config.catalog !== null) {
372
366
  var catalog = {};
367
+ if ('id' in config.catalog) {
368
+ throw new SyntaxError('"catalog" should not contain id');
369
+ }
373
370
  for (var key in config.catalog) {
374
371
  var value = config.catalog[key];
375
372
  if (typeof value === 'number') {
@@ -382,7 +379,7 @@ function parseHeader(header) {
382
379
  throw new SyntaxError("\"catalog\" should contain only strings (\"".concat(key, "\")"));
383
380
  }
384
381
  }
385
- var work = new work_1.Work(catalog);
382
+ var work = new resource_1.WorkResource(catalog);
386
383
  var errors = work.validate().filter(function (_a) {
387
384
  var error = _a.error;
388
385
  return error !== 'Value(s) required but missing';
@@ -398,6 +395,16 @@ function parseHeader(header) {
398
395
  metadata.catalog[key] = work.get(key);
399
396
  }
400
397
  }
398
+ if ('flags' in config) {
399
+ if (!Array.isArray(config.flags)) {
400
+ throw new SyntaxError('"flags" should be an array if present');
401
+ }
402
+ var invalidFlags = config.flags.filter(function (flag) { return !FLAGS.includes(flag); });
403
+ if (invalidFlags.length) {
404
+ throw new SyntaxError("\"flags\" contains invalid values: ".concat(invalidFlags.join(', ')));
405
+ }
406
+ metadata.flags = config.flags;
407
+ }
401
408
  return metadata;
402
409
  }
403
410
  function validateResource(config, content) {
@@ -477,27 +484,11 @@ function parseResourceContent(content, resource, oldIds) {
477
484
  var rank = resource.metadata.levels[groupIndent / 2];
478
485
  var item = parseName(name_1, rank, parent_1);
479
486
  var isSynonym = item.taxonomicStatus !== 'accepted';
480
- if (item.taxonomicStatus === 'incorrect') {
481
- return "continue";
482
- }
483
- if (type === diff_resource_1.ResourceDiffType.Added) {
484
- newIdOffset++;
485
- item.scientificNameID = idBase + newIdOffset.toString();
486
- }
487
- else {
488
- id++;
489
- item.scientificNameID = idBase + (oldIds[id - 1] || id).toString();
490
- }
491
- previousId = item.scientificNameID;
492
- item.parentNameUsageID = isSynonym ? undefined : parent_1.scientificNameID;
493
- item.parentNameUsage = isSynonym ? undefined : parent_1.scientificName;
494
- item.acceptedNameUsageID = isSynonym ? parent_1.scientificNameID : undefined;
495
- item.acceptedNameUsage = isSynonym ? parent_1.scientificName : undefined;
496
- item.collectionCode = idBase.slice(0, -1);
487
+ // Add higher classification info
488
+ var itemAsObject = item;
489
+ var parentAsObject = parent_1;
497
490
  for (var _b = 0, DWC_RANKS_1 = DWC_RANKS; _b < DWC_RANKS_1.length; _b++) {
498
491
  var rank_1 = DWC_RANKS_1[_b];
499
- var itemAsObject = item;
500
- var parentAsObject = parent_1;
501
492
  itemAsObject[rank_1] = undefined;
502
493
  if (parentAsObject[rank_1]) {
503
494
  itemAsObject[rank_1] = parentAsObject[rank_1];
@@ -521,6 +512,31 @@ function parseResourceContent(content, resource, oldIds) {
521
512
  else if (parentId) {
522
513
  item.higherClassification = parent_1.scientificNameOnly;
523
514
  }
515
+ // Amend "parent" with corrections
516
+ if (item.taxonomicStatus === 'incorrect') {
517
+ parent_1.incorrect = __assign({}, parent_1);
518
+ for (var key in item) {
519
+ if (key !== 'taxonomicStatus' && key !== 'verbatimIdentification') {
520
+ parentAsObject[key] = itemAsObject[key];
521
+ }
522
+ }
523
+ return "continue";
524
+ }
525
+ // Set identifiers
526
+ if (type === diff_resource_1.ResourceDiffType.Added) {
527
+ newIdOffset++;
528
+ item.scientificNameID = idBase + newIdOffset.toString();
529
+ }
530
+ else {
531
+ id++;
532
+ item.scientificNameID = idBase + (oldIds[id - 1] || id).toString();
533
+ }
534
+ previousId = item.scientificNameID;
535
+ item.parentNameUsageID = isSynonym ? undefined : parent_1.scientificNameID;
536
+ item.parentNameUsage = isSynonym ? undefined : parent_1.scientificName;
537
+ item.acceptedNameUsageID = isSynonym ? parent_1.scientificNameID : undefined;
538
+ item.acceptedNameUsage = isSynonym ? parent_1.scientificName : undefined;
539
+ item.collectionCode = idBase.slice(0, -1);
524
540
  data[item.scientificNameID] = item;
525
541
  };
526
542
  for (var _i = 0, content_1 = content; _i < content_1.length; _i++) {
@@ -0,0 +1,4 @@
1
+ import { Work } from '../catalog/tables/work';
2
+ export declare class WorkResource extends Work {
3
+ constructor(values: Record<string, string>);
4
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ var __extends = (this && this.__extends) || (function () {
3
+ var extendStatics = function (d, b) {
4
+ extendStatics = Object.setPrototypeOf ||
5
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
6
+ function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
7
+ return extendStatics(d, b);
8
+ };
9
+ return function (d, b) {
10
+ if (typeof b !== "function" && b !== null)
11
+ throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
12
+ extendStatics(d, b);
13
+ function __() { this.constructor = d; }
14
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
15
+ };
16
+ })();
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.WorkResource = void 0;
19
+ var work_1 = require("../catalog/tables/work");
20
+ var WorkResource = /** @class */ (function (_super) {
21
+ __extends(WorkResource, _super);
22
+ function WorkResource(values) {
23
+ var _this = _super.call(this, values) || this;
24
+ _this.schema.version_of.format = /^B[1-9]\d*:[1-9]\d*$/;
25
+ _this.schema.duplicate_of.format = /^B[1-9]\d*:[1-9]\d*$/;
26
+ return _this;
27
+ }
28
+ return WorkResource;
29
+ }(work_1.Work));
30
+ exports.WorkResource = WorkResource;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@larsgw/formica",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "description": "SDK and tools for data from the Library of Identification Resources",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -16,7 +16,7 @@
16
16
  "lint": "eslint src",
17
17
  "build": "tsc -d",
18
18
  "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
19
- "preversion": "npm run lint",
19
+ "preversion": "npm run lint && npm test",
20
20
  "version": "npm run changelog && git add CHANGELOG.md",
21
21
  "prepublishOnly": "npm run build"
22
22
  },
@@ -175,7 +175,6 @@ class ResourceProcessor {
175
175
  const skip = await this.shouldBeSkipped(resource.id)
176
176
 
177
177
  if (!skip) {
178
- // TODO const correct = checkResults(results, classifications)
179
178
  const correct = this.checkResults(results)
180
179
  if (!correct) {
181
180
  const choice = await promptForAnswers(
package/src/module.d.ts CHANGED
@@ -71,8 +71,11 @@ interface Taxon extends WorkingTaxon {
71
71
  verbatimIdentification: string
72
72
  }
73
73
 
74
+ type ResourceFlag = string;
75
+
74
76
  interface ResourceMetadata {
75
77
  levels: Rank[],
78
+ flags?: ResourceFlag[],
76
79
  catalog?: Record<string, Value>
77
80
  }
78
81
 
@@ -1,5 +1,5 @@
1
1
  import * as yaml from 'js-yaml'
2
- import { Work } from '../catalog/tables/work'
2
+ import { WorkResource } from './resource'
3
3
  import { createDiff, ResourceDiffType } from './diff-resource'
4
4
 
5
5
  const RANKS: Rank[] = [
@@ -54,6 +54,12 @@ const DWC_RANKS: DwcRank[] = [
54
54
  'subgenus'
55
55
  ]
56
56
 
57
+ const FLAGS: ResourceFlag[] = [
58
+ 'MISSING_TAXA',
59
+ 'MISSING_PARENT_TAXA',
60
+ 'MISSING_SYNONYMS'
61
+ ]
62
+
57
63
  const TAXONOMIC_STATUS: Record<string, TaxonStatus> = {
58
64
  '>': 'incorrect',
59
65
  '+': 'heterotypic synonym',
@@ -97,7 +103,7 @@ const RANK_LABELS_REVERSE: Record<string, Rank> = {
97
103
  * 2. Followed by a capitalized word
98
104
  * 3. Optionally, followed by "et al."
99
105
  */
100
- const LOWERCASE_NAME_PARTICLES = ['y', 'der', 'den', 'de', 'van'].join('|')
106
+ const LOWERCASE_NAME_PARTICLES = ['y', 'der', 'den', 'de', 'van', 'von'].join('|')
101
107
  const SIMPLE_AUTHOR_PATTERN = '(?:(?:\\p{Lu}\\S*|&|in|ex|' + LOWERCASE_NAME_PARTICLES + ')\\s*)*\\p{Lu}\\S+(?:\\s+et\\s+al\\.)?'
102
108
 
103
109
  const NAME_PATTERN = new RegExp(
@@ -107,7 +113,7 @@ const NAME_PATTERN = new RegExp(
107
113
  // $2 optional author citation
108
114
  '(?: ' +
109
115
  // but not auct(t)., etc.
110
- '(?!auctt?\\.|(?:syn|comb|sp|spec)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.))' +
116
+ '(?!auctt?\\.|(?:syn|comb|sp|spec|nom)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.)|in part|partim)' +
111
117
  '(' +
112
118
  // $2.1 anything in parentheses, followed by optional revising author(s)
113
119
  '\\(.+?\\)(?:\\s+' + SIMPLE_AUTHOR_PATTERN + ')?' +
@@ -342,19 +348,6 @@ function parseName (name: string, rank: Rank, parent: WorkingTaxon): WorkingTaxo
342
348
  item.scientificName += ` ${item.scientificNameAuthorship}`
343
349
  }
344
350
 
345
- // Amend "parent" with corrections
346
- if (item.taxonomicStatus === 'incorrect') {
347
- const itemAsObject = item as { [index: string]: unknown }
348
- const parentAsObject = parent as { [index: string]: unknown }
349
-
350
- parent.incorrect = { ...parent }
351
- for (const key in item) {
352
- if (key !== 'taxonomicStatus') {
353
- parentAsObject[key] = itemAsObject[key]
354
- }
355
- }
356
- }
357
-
358
351
  return item
359
352
  }
360
353
 
@@ -394,6 +387,9 @@ function parseHeader (header: string): ResourceMetadata {
394
387
 
395
388
  if ('catalog' in config && typeof config.catalog === 'object' && config.catalog !== null) {
396
389
  const catalog: Record<string, string> = {}
390
+ if ('id' in config.catalog) {
391
+ throw new SyntaxError('"catalog" should not contain id')
392
+ }
397
393
  for (const key in config.catalog) {
398
394
  const value = config.catalog[key as keyof object]
399
395
  if (typeof value === 'number') {
@@ -404,7 +400,7 @@ function parseHeader (header: string): ResourceMetadata {
404
400
  throw new SyntaxError(`"catalog" should contain only strings ("${key}")`)
405
401
  }
406
402
  }
407
- const work = new Work(catalog)
403
+ const work = new WorkResource(catalog)
408
404
  const errors = work.validate().filter(({ error }) => error !== 'Value(s) required but missing')
409
405
  if (errors.length > 0) {
410
406
  throw new SyntaxError(`"catalog" contains errors: ${errors.map(({ field, error }) => `[${field}] ${error}`).join('; ')}`)
@@ -415,6 +411,19 @@ function parseHeader (header: string): ResourceMetadata {
415
411
  }
416
412
  }
417
413
 
414
+ if ('flags' in config) {
415
+ if (!Array.isArray(config.flags)) {
416
+ throw new SyntaxError('"flags" should be an array if present')
417
+ }
418
+
419
+ const invalidFlags = config.flags.filter(flag => !FLAGS.includes(flag))
420
+ if (invalidFlags.length) {
421
+ throw new SyntaxError(`"flags" contains invalid values: ${invalidFlags.join(', ')}`)
422
+ }
423
+
424
+ metadata.flags = config.flags
425
+ }
426
+
418
427
  return metadata
419
428
  }
420
429
 
@@ -507,29 +516,10 @@ function parseResourceContent (content: ResourceDiff, resource: Resource, oldIds
507
516
  const item = parseName(name, rank, parent)
508
517
  const isSynonym = item.taxonomicStatus !== 'accepted'
509
518
 
510
- if (item.taxonomicStatus === 'incorrect') {
511
- continue
512
- }
513
-
514
- if (type === ResourceDiffType.Added) {
515
- newIdOffset++
516
- item.scientificNameID = idBase + newIdOffset.toString()
517
- } else {
518
- id++
519
- item.scientificNameID = idBase + (oldIds[id - 1] || id).toString()
520
- }
521
- previousId = item.scientificNameID
522
-
523
- item.parentNameUsageID = isSynonym ? undefined : parent.scientificNameID
524
- item.parentNameUsage = isSynonym ? undefined : parent.scientificName
525
- item.acceptedNameUsageID = isSynonym ? parent.scientificNameID : undefined
526
- item.acceptedNameUsage = isSynonym ? parent.scientificName : undefined
527
- item.collectionCode = idBase.slice(0, -1)
528
-
519
+ // Add higher classification info
520
+ const itemAsObject = item as { [index: string]: unknown }
521
+ const parentAsObject = parent as { [index: string]: unknown }
529
522
  for (const rank of DWC_RANKS) {
530
- const itemAsObject = item as { [index: string]: unknown }
531
- const parentAsObject = parent as { [index: string]: unknown }
532
-
533
523
  itemAsObject[rank] = undefined
534
524
  if (parentAsObject[rank]) {
535
525
  itemAsObject[rank] = parentAsObject[rank]
@@ -554,6 +544,33 @@ function parseResourceContent (content: ResourceDiff, resource: Resource, oldIds
554
544
  item.higherClassification = parent.scientificNameOnly
555
545
  }
556
546
 
547
+ // Amend "parent" with corrections
548
+ if (item.taxonomicStatus === 'incorrect') {
549
+ parent.incorrect = { ...parent }
550
+ for (const key in item) {
551
+ if (key !== 'taxonomicStatus' && key !== 'verbatimIdentification') {
552
+ parentAsObject[key] = itemAsObject[key]
553
+ }
554
+ }
555
+ continue
556
+ }
557
+
558
+ // Set identifiers
559
+ if (type === ResourceDiffType.Added) {
560
+ newIdOffset++
561
+ item.scientificNameID = idBase + newIdOffset.toString()
562
+ } else {
563
+ id++
564
+ item.scientificNameID = idBase + (oldIds[id - 1] || id).toString()
565
+ }
566
+ previousId = item.scientificNameID
567
+
568
+ item.parentNameUsageID = isSynonym ? undefined : parent.scientificNameID
569
+ item.parentNameUsage = isSynonym ? undefined : parent.scientificName
570
+ item.acceptedNameUsageID = isSynonym ? parent.scientificNameID : undefined
571
+ item.acceptedNameUsage = isSynonym ? parent.scientificName : undefined
572
+ item.collectionCode = idBase.slice(0, -1)
573
+
557
574
  data[item.scientificNameID] = item
558
575
  }
559
576
 
@@ -0,0 +1,10 @@
1
+ import { Work } from '../catalog/tables/work'
2
+
3
+ export class WorkResource extends Work {
4
+ constructor (values: Record<string, string>) {
5
+ super(values)
6
+
7
+ this.schema.version_of.format = /^B[1-9]\d*:[1-9]\d*$/
8
+ this.schema.duplicate_of.format = /^B[1-9]\d*:[1-9]\d*$/
9
+ }
10
+ }
package/test/resources.js CHANGED
@@ -136,4 +136,17 @@ Tilia x vulgaris
136
136
  assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Rumex conglomeratus×maritimus')
137
137
  assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Tilia ×vulgaris')
138
138
  })
139
+
140
+ await t.test('outputs corrected generic names', (t) => {
141
+ const [resource] = resources.parseTextFile(`---
142
+ levels: [genus, species]
143
+ ---
144
+
145
+ Bogdania Kerzhner, 1964
146
+ > Bogdiana Kerzhner, 1964
147
+ myrmica Kerzhner, 1964
148
+ `, 'T1')
149
+ assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Bogdiana Kerzhner, 1964')
150
+ assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Bogdiana myrmica Kerzhner, 1964')
151
+ })
139
152
  })