@larsgw/formica 0.8.4 → 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.
@@ -13,7 +13,7 @@ jobs:
13
13
 
14
14
  strategy:
15
15
  matrix:
16
- node-version: [18.x, 20.x]
16
+ node-version: [20.x, 22.x]
17
17
 
18
18
  steps:
19
19
  - uses: actions/checkout@v3
package/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## [0.8.5](https://github.com/identification-resources/formica/compare/v0.8.4...v0.8.5) (2025-10-28)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **catalog:** add mapping for typo 'male' in linked data ([a83d34e](https://github.com/identification-resources/formica/commit/a83d34e7aa57a78e410e2ea55adbf75e522d9028))
7
+ * **catalog:** fix dcmitype: for algorithms, checklists ([68e353d](https://github.com/identification-resources/formica/commit/68e353d71d632e092e3e79254e6b4f575c004f1c))
8
+ * **catalog:** fix typos in linked data generation ([9ad951f](https://github.com/identification-resources/formica/commit/9ad951f5c19978509c6aba7fd81470270707da99))
9
+ * **resources:** add scientific name for complexes, aggregates ([843afc3](https://github.com/identification-resources/formica/commit/843afc39df1952dda4bb6339ee47f2619432bb74))
10
+ * **resources:** omit generic error stack trace ([c831bc2](https://github.com/identification-resources/formica/commit/c831bc2a952ef17a9e57b012fe0568e2acc0c6ae))
11
+
12
+
13
+ ### Features
14
+
15
+ * **resources:** handle subgenus synonyms of genera ([4e61336](https://github.com/identification-resources/formica/commit/4e61336e36054ffd6df57ebad5479e7ceebcb235)), closes [#16](https://github.com/identification-resources/formica/issues/16)
16
+ * **resources:** rework parsing of resources ([cff7730](https://github.com/identification-resources/formica/commit/cff773000c5b9e3f990c619cdd45a76a18ee39a9)), closes [#15](https://github.com/identification-resources/formica/issues/15) [#7](https://github.com/identification-resources/formica/issues/7)
17
+
18
+
19
+
1
20
  ## [0.8.4](https://github.com/identification-resources/formica/compare/v0.8.3...v0.8.4) (2025-09-19)
2
21
 
3
22
 
@@ -126,6 +126,7 @@ var SCOPES = {
126
126
  // sex
127
127
  'females': ['dwciri:sex', 'http://rs.gbif.org/vocabulary/gbif/sex/female'],
128
128
  'males': ['dwciri:sex', 'http://rs.gbif.org/vocabulary/gbif/sex/male'],
129
+ 'male': ['dwciri:sex', 'http://rs.gbif.org/vocabulary/gbif/sex/male'],
129
130
  // caste
130
131
  'queens': ['dwc:caste', 'queen'],
131
132
  'workers': ['dwc:caste', 'worker'],
@@ -278,7 +279,7 @@ function makeLinkedDataForTaxon(taxon) {
278
279
  var node = {
279
280
  '@id': "".concat(PREFIX, "taxon/").concat(taxon.get('id')),
280
281
  '@type': 'dwc:Taxon',
281
- 'dwc:scientificName': taxon.get('display_name'),
282
+ 'dwc:scientificName': taxon.get('name'),
282
283
  };
283
284
  if (taxon.has('rank')) {
284
285
  node['dwc:taxonRank'] = makeTaxonRankUri(taxon.get('rank'));
@@ -372,7 +373,7 @@ function makeLinkedDataForResource(work, files, resourceId) {
372
373
  }
373
374
  var node = __assign(__assign({}, makeLinkedDataForWork(resource, files)), { '@id': "".concat(PREFIX, "resource/").concat(resourceId), '@type': 'bibo:DocumentPart' });
374
375
  var types = (_b = (_a = resource.get('key_type')) !== null && _a !== void 0 ? _a : work.get('key_type')) !== null && _b !== void 0 ? _b : [];
375
- if (types.includes('matrix')) {
376
+ if (types.includes('matrix') || types.includes('algorithm')) {
376
377
  node['dcterms:type'] = { '@id': 'http://purl.org/dc/dcmitype/Software' };
377
378
  }
378
379
  else if (types.includes('key') || types.includes('reference') || types.includes('supplement')) {
@@ -381,6 +382,9 @@ function makeLinkedDataForResource(work, files, resourceId) {
381
382
  else if (types.includes('gallery') || types.includes('collection')) {
382
383
  node['dcterms:type'] = { '@id': 'http://purl.org/dc/dcmitype/Collection' };
383
384
  }
385
+ else if (types.includes('checklist')) {
386
+ node['dcterms:type'] = { '@id': 'http://purl.org/dc/dcmitype/Dataset' };
387
+ }
384
388
  if (types.includes('key') || types.includes('matrix')) {
385
389
  node['ac:subtype'] = { '@id': 'http://rs.tdwg.org/acsubtype/values/IdentificationKey' };
386
390
  }
@@ -637,10 +641,10 @@ function makeLinkedDataForWorks(files) {
637
641
  node['dcterms:hasPart'] = [];
638
642
  }
639
643
  node['dcterms:hasPart'].push(resource);
640
- if (!Array.isArray(resource['dcterms:isPartof'])) {
641
- resource['dcterms:isPartof'] = [];
644
+ if (!Array.isArray(resource['dcterms:isPartOf'])) {
645
+ resource['dcterms:isPartOf'] = [];
642
646
  }
643
- resource['dcterms:isPartof'].push({ '@id': node['@id'] });
647
+ resource['dcterms:isPartOf'].push({ '@id': node['@id'] });
644
648
  }
645
649
  };
646
650
  for (var _b = 0, _c = works.entities; _b < _c.length; _b++) {
@@ -411,7 +411,7 @@ var ResourceProcessor = /** @class */ (function () {
411
411
  return [2 /*return*/, resources.parseTextFile(file, id, old)];
412
412
  case 10:
413
413
  error_1 = _g.sent();
414
- console.log(error_1);
414
+ console.log(error_1.message);
415
415
  return [4 /*yield*/, (0, util_1.prompt)("".concat(id, ": generating Darwin Core failed, retry? "))
416
416
  // Clear cache to re-import
417
417
  ];
@@ -94,7 +94,7 @@ function main(args) {
94
94
  index_1.resources.parseTextFile(file, id);
95
95
  }
96
96
  catch (error) {
97
- console.error(filePath + '\n ' + error.message + '\n');
97
+ console.error(filePath + '\n' + error.message.replace(/^/gm, ' ') + '\n');
98
98
  exitStatus = 1;
99
99
  }
100
100
  _a.label = 3;
@@ -0,0 +1,2 @@
1
+ export declare const RANKS: Rank[];
2
+ export declare function parseName(name: string, rank: Rank, parent: WorkingTaxon): WorkingTaxon;
@@ -0,0 +1,352 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RANKS = void 0;
4
+ exports.parseName = parseName;
5
+ exports.RANKS = [
6
+ 'phylum',
7
+ 'subphylum',
8
+ 'class',
9
+ 'infraclass',
10
+ 'superorder',
11
+ 'order',
12
+ 'suborder',
13
+ 'infraorder',
14
+ 'superfamily',
15
+ 'family',
16
+ 'subfamily',
17
+ 'tribe',
18
+ 'subtribe',
19
+ 'genus',
20
+ 'subgenus',
21
+ 'section', // not ICZN
22
+ 'subsection', // not ICZN
23
+ 'series', // not ICZN
24
+ 'group',
25
+ 'subgroup', // ...
26
+ 'aggregate', // not ICZN
27
+ 'complex', // not ICZN
28
+ 'species',
29
+ 'subspecies',
30
+ 'variety',
31
+ 'form',
32
+ 'aberration', // not ICZN
33
+ 'race', // not ICZN
34
+ 'stirps' // not ICZN
35
+ ];
36
+ var TAXONOMIC_STATUS = {
37
+ '>': 'incorrect',
38
+ '+': 'heterotypic synonym',
39
+ '=': 'synonym'
40
+ };
41
+ var RANK_LABELS = {
42
+ 'subspecies': 'subsp.',
43
+ 'variety': 'var.',
44
+ 'form': 'f.',
45
+ 'aberration': 'ab.',
46
+ 'race': 'r.',
47
+ 'stirps': 'st.'
48
+ };
49
+ var RANK_LABELS_REVERSE = {
50
+ 'st': 'stirps',
51
+ 'r': 'race',
52
+ 'ab': 'aberration',
53
+ 'f': 'form',
54
+ 'var': 'variety',
55
+ 'ssp': 'subspecies',
56
+ 'subsp': 'subspecies'
57
+ };
58
+ var HYBRID_SIGN = '\u00D7';
59
+ /**
60
+ * 1. Any number of
61
+ * - capitalized words
62
+ * - "&"
63
+ * - " in "
64
+ * - " ex "
65
+ * - lowercase name particles
66
+ * 2. Followed by a capitalized word
67
+ * 3. Optionally, followed by "et al."
68
+ */
69
+ var LOWERCASE_NAME_PARTICLES = ['y', 'der', 'den', 'de', 'van', 'von'].join('|');
70
+ var SIMPLE_AUTHOR_PATTERN = '(?:(?:\\p{Lu}\\S*|&|in|ex|' + LOWERCASE_NAME_PARTICLES + ')\\s*)*\\p{Lu}\\S+(?:\\s+et\\s+al\\.)?';
71
+ var NAME_PATTERN = new RegExp('^' +
72
+ // $1 main name part
73
+ '(\\S+)' +
74
+ // $2 optional author citation
75
+ '(?: ' +
76
+ // but not auct(t)., etc.
77
+ '(?!auctt?\\.|(?:syn|comb|sp|spec|nom|gen|subgen)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.)|in part|partim)' +
78
+ '(' +
79
+ // $2.1 anything in parentheses, followed by optional revising author(s)
80
+ '\\(.+?\\)(?:\\s+' + SIMPLE_AUTHOR_PATTERN + ')?' +
81
+ '|' +
82
+ // $2.2 anything followed by a year
83
+ '.+?\\d{4}\\)?' +
84
+ '|' +
85
+ // $2.3 author(s)
86
+ SIMPLE_AUTHOR_PATTERN +
87
+ '))?' +
88
+ // $3 optional notes
89
+ '(?:,? (.+))?' +
90
+ '$', 'u');
91
+ /**
92
+ * Structure
93
+ * $1 genus: ((?:x )?[A-Z]\S+)
94
+ * $2 subgenus: (?:\(([A-Z]\S+?)\) )?
95
+ */
96
+ var SUBGENUS_PATTERN = /^([A-Z]\S+) (?:\(([A-Z]\S+?)\))(?= |$)/;
97
+ /**
98
+ * Structure
99
+ * $1 genus+subgenus (+ trailing space): (?:([A-Z]\S+) (?:\(([A-Z]\S+?)\) )?)?
100
+ * $1.1 genus: ((?:x )?[A-Z]\S+)
101
+ * $1.2 subgenus: (?:\(([A-Z]\S+?)\) )?
102
+ * $2 species: (x [a-z-]+|[a-z-][^\s.]+(?: x [a-z-]+)?|[A-Z][a-z]+_[a-z-]+ x [A-Z][a-z]+_[a-z-]+)
103
+ * $2a: x [a-z-]+
104
+ * $2b hybrid: [a-z-][^\s.]+(?: x [a-z-]+)?
105
+ * $2c intergeneric hybrid: [A-Z][a-z]+_[a-z-]+ x [A-Z][a-z]+_[a-z-]+
106
+ */
107
+ 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-]+)(?= |$)/;
108
+ function compareRanks(a, b) {
109
+ return exports.RANKS.indexOf(a) - exports.RANKS.indexOf(b);
110
+ }
111
+ function capitalize(name) {
112
+ return name[0].toUpperCase() + name.slice(1).toLowerCase();
113
+ }
114
+ function capitalizeGenericName(name) {
115
+ if (name[0] === HYBRID_SIGN) {
116
+ return HYBRID_SIGN + capitalize(name.slice(1));
117
+ }
118
+ return capitalize(name);
119
+ }
120
+ function isUpperCase(name) {
121
+ return name === name.toUpperCase();
122
+ }
123
+ function getSynonymRank(name, rank) {
124
+ var rest = name.replace(BINAME_PATTERN, '');
125
+ var rankPrefix = rest.match(/^(?: |^)(st|r|ab|f|var|ssp|subsp)\. /);
126
+ if (rankPrefix) {
127
+ return RANK_LABELS_REVERSE[rankPrefix[1]];
128
+ }
129
+ else if (SUBGENUS_PATTERN.test(name)) {
130
+ return 'subgenus';
131
+ }
132
+ else if (!BINAME_PATTERN.test(name)) {
133
+ return rank;
134
+ }
135
+ else if (/^ (?!sensu)[a-z0-9-]+($| )/.test(rest)) {
136
+ return 'subspecies';
137
+ }
138
+ else {
139
+ return 'species';
140
+ }
141
+ }
142
+ function capitalizeAuthors(authors) {
143
+ return authors
144
+ .replace(/[^\x00-\x40\x5B-\x60\x7B-\x7F]+/g, // eslint-disable-line no-control-regex
145
+ function (// eslint-disable-line no-control-regex
146
+ name) { return isUpperCase(name) ? capitalize(name) : name; })
147
+ .replace(/ Y /g, ' y ');
148
+ }
149
+ function parseName(name, rank, parent) {
150
+ var _a, _b;
151
+ var item = {};
152
+ // Synonyms have the accepted name usage as 'parent'.
153
+ var isSynonym = /^[+=>] /.test(name);
154
+ if (isSynonym) {
155
+ item.taxonomicStatus = TAXONOMIC_STATUS[name[0]];
156
+ name = name.replace(/^[+=>] (\? ?)?/, '');
157
+ rank = getSynonymRank(name, parent.taxonRank);
158
+ }
159
+ else {
160
+ item.taxonomicStatus = 'accepted';
161
+ }
162
+ // Clusters
163
+ if (/^\[(_|\d+)\] /.test(name)) {
164
+ name = name.replace(/^\[(_|\d+)\] /, '');
165
+ }
166
+ // Set verbatim identification after subsequent syntax is removed.
167
+ item.verbatimIdentification = name.replace(/(?<=^| )x(?=$| )/g, HYBRID_SIGN).replace(/_/g, ' ');
168
+ // Parent context is used for parsing and formatting binomial names.
169
+ // For formatting, it needs to match external databases (i.e. be correct).
170
+ // For parsing, it needs to match the current file. If relevant parents
171
+ // (i.e. genus, species) had mistakes that were corrected, the uncorrected
172
+ // genus and species names need to be used.
173
+ var parentContext = {
174
+ genus: parent.genus,
175
+ subgenus: parent.subgenus,
176
+ specificEpithet: parent.specificEpithet,
177
+ incorrect: {
178
+ genus: parent.incorrect && parent.incorrect.genus,
179
+ specificEpithet: parent.incorrect && parent.incorrect.specificEpithet
180
+ }
181
+ };
182
+ // Both contexts should be amended in the two cases where binomial names
183
+ // are fully used: (1) synonyms and (2) multinomial taxa without parents to
184
+ // provide parts of the name (e.g. bare species without a genus parent, or
185
+ // even subspecies without a species or genus parent).
186
+ if (isSynonym || !parentContext.genus || (compareRanks('species', rank) < 0 && !parentContext.specificEpithet)) {
187
+ var _c = (_b = (_a = name.match(BINAME_PATTERN)) !== null && _a !== void 0 ? _a : name.match(SUBGENUS_PATTERN)) !== null && _b !== void 0 ? _b : [], genus = _c[1], subgenus = _c[2], species = _c[3];
188
+ if (genus) {
189
+ parentContext.incorrect.genus = genus;
190
+ parentContext.genus = capitalizeGenericName(genus.replace(/(^| )x /, HYBRID_SIGN));
191
+ }
192
+ if (subgenus) {
193
+ parentContext.subgenus = capitalize(subgenus);
194
+ }
195
+ else if (genus) {
196
+ // If a genus is given but no subgenus, remove any existing subgenus
197
+ // from the parent context.
198
+ delete parentContext.subgenus;
199
+ }
200
+ if (species && compareRanks('species', rank) < 0) {
201
+ parentContext.incorrect.specificEpithet = species;
202
+ parentContext.specificEpithet = species.replace(/(^| )x /, HYBRID_SIGN);
203
+ }
204
+ }
205
+ // In taxa of group, species or lower, the name should just contain the
206
+ // (infra)specific epithet and the author information & remarks when processing
207
+ // further.
208
+ if (compareRanks('group', rank) <= 0) {
209
+ // Remove genus
210
+ var genus = parentContext.incorrect.genus || parentContext.genus || '';
211
+ if (name[0] === genus[0] && name.toLowerCase().startsWith(genus.toLowerCase() + ' ')) {
212
+ name = name.slice(genus.length + 1);
213
+ }
214
+ // Remove subgenus
215
+ name = name.replace(/^\(.*?\) /, '');
216
+ // Infraspecific taxa
217
+ if (compareRanks('species', rank) < 0) {
218
+ // Remove specific epithet
219
+ var species = parentContext.incorrect.specificEpithet || parentContext.specificEpithet || '';
220
+ if (name.startsWith(species + ' ')) {
221
+ name = name.slice(species.length + 1);
222
+ }
223
+ // Remove rank abbreviations
224
+ name = name.replace(/^(st|r|ab|f|var|ssp|subsp)\. /, '');
225
+ }
226
+ }
227
+ else if (compareRanks('genus', rank) <= 0) {
228
+ // Remove genus
229
+ var genus = parentContext.incorrect.genus || parentContext.genus || '';
230
+ if (name[0] === genus[0] && name.toLowerCase().startsWith(genus.toLowerCase() + ' (')) {
231
+ name = name.slice(genus.length + 1);
232
+ }
233
+ // Remove subgenus parentheses
234
+ name = name.replace(/^\((.*?)\)/, '$1');
235
+ }
236
+ // Hybrids
237
+ if (rank === 'genus' && name.startsWith('x ')) {
238
+ name = HYBRID_SIGN + name.slice(2);
239
+ }
240
+ if (rank === 'species' && /(^| )x /.test(name)) {
241
+ name = name.replace(/(^| )x /, HYBRID_SIGN);
242
+ }
243
+ // Divide the name into the main scientific name (only the epithet for taxa
244
+ // lower than genus), the authorship information, and optionally remarks
245
+ var nameParts = name.match(NAME_PATTERN);
246
+ if (!nameParts) {
247
+ throw new Error("Taxon \"".concat(name, "\" could not be parsed"));
248
+ }
249
+ // To encode old names with spaces (e.g. "Orsillus pini canariensis Lindberg, 1953")
250
+ // underscores are used, which are replaced here. This is also used for undescribed
251
+ // species (e.g. "Leiobunum species A") and intergeneric hybrids (e.g. "×Festulpia
252
+ // Festuca rubra × Vulpia bromoides")
253
+ if (nameParts[1].includes('_')) {
254
+ nameParts[1] = nameParts[1].replace(/_/g, ' ');
255
+ }
256
+ var _ = nameParts[0], taxon = nameParts[1], _d = nameParts[2], citation = _d === void 0 ? '' : _d, notes = nameParts[3];
257
+ item.scientificNameAuthorship = capitalizeAuthors(citation);
258
+ item.taxonRemarks = notes;
259
+ item.taxonRank = rank;
260
+ // @ts-expect-error TS1501: This regular expression flag is only available when targeting 'es6' or later.
261
+ if (/[^\p{L}0-9\u{00D7}\- ]/u.test(taxon)) {
262
+ throw new Error("Taxon name contains unexpected characters: \"".concat(taxon, "\""));
263
+ }
264
+ // Validate names and recompose binomial and trinomial names
265
+ if (compareRanks('genus', rank) > 0) {
266
+ item.scientificName = capitalize(taxon);
267
+ if (taxon[0].toUpperCase() !== taxon[0]) {
268
+ throw new Error("Taxon name (".concat(rank, ") should be capitalized: \"").concat(taxon, "\""));
269
+ }
270
+ }
271
+ else if (rank === 'genus') {
272
+ item.scientificName = capitalizeGenericName(taxon);
273
+ if (taxon[0].toUpperCase() !== taxon[0] || (taxon[0] === HYBRID_SIGN && taxon[1].toUpperCase() !== taxon[1])) {
274
+ throw new Error("Generic epithet should be capitalized: \"".concat(taxon, "\""));
275
+ }
276
+ }
277
+ else if (compareRanks('group', rank) > 0) {
278
+ item.genericName = parentContext.genus;
279
+ item.infragenericEpithet = parentContext.subgenus;
280
+ item.scientificName = capitalize(taxon);
281
+ if (taxon[0].toUpperCase() !== taxon[0]) {
282
+ throw new Error("Infrageneric epithet should be capitalized: \"".concat(taxon, "\""));
283
+ }
284
+ }
285
+ else if (rank === 'group') {
286
+ item.genericName = parentContext.genus;
287
+ item.infragenericEpithet = parentContext.subgenus;
288
+ var specificEpithet = taxon.toLowerCase().replace(/(-group)?$/, '');
289
+ item.scientificName = "".concat(item.genericName, " ").concat(specificEpithet, "-group");
290
+ if (taxon.toLowerCase() !== taxon) {
291
+ console.log(item, taxon);
292
+ throw new Error("Group name should be lowercase: \"".concat(taxon, "\""));
293
+ }
294
+ }
295
+ else if (rank === 'subgroup') {
296
+ item.genericName = parentContext.genus;
297
+ item.infragenericEpithet = parentContext.subgenus;
298
+ var specificEpithet = taxon.toLowerCase().replace(/(-subgroup)?$/, '');
299
+ item.scientificName = "".concat(item.genericName, " ").concat(specificEpithet, "-subgroup");
300
+ if (taxon.toLowerCase() !== taxon) {
301
+ console.log(item, taxon);
302
+ throw new Error("Subgroup name should be lowercase: \"".concat(taxon, "\""));
303
+ }
304
+ }
305
+ else if (compareRanks('species', rank) > 0) {
306
+ item.genericName = parentContext.genus;
307
+ item.infragenericEpithet = parentContext.subgenus;
308
+ var specificEpithet = taxon.toLowerCase();
309
+ item.scientificName = "".concat(item.genericName, " ").concat(specificEpithet);
310
+ if (specificEpithet !== taxon) {
311
+ console.log(item, taxon);
312
+ throw new Error("Subgroup name should be lowercase: \"".concat(taxon, "\""));
313
+ }
314
+ }
315
+ else if (rank === 'species') {
316
+ item.genericName = parentContext.genus;
317
+ item.infragenericEpithet = parentContext.subgenus;
318
+ if (taxon.toLowerCase() !== taxon && !/^[A-Z][a-z]+ [a-z]+\xD7[A-Z][a-z]+ [a-z]+$/.test(taxon)) {
319
+ console.log(item, taxon);
320
+ throw new Error("Specific epithet should be lowercase: \"".concat(taxon, "\""));
321
+ }
322
+ item.specificEpithet = taxon;
323
+ item.scientificName = "".concat(item.genericName, " ").concat(item.specificEpithet);
324
+ }
325
+ else if (compareRanks('species', rank) < 0) {
326
+ item.genericName = parentContext.genus;
327
+ item.infragenericEpithet = parentContext.subgenus;
328
+ item.specificEpithet = parentContext.specificEpithet;
329
+ item.infraspecificEpithet = taxon.toLowerCase();
330
+ // If possible, names below species should have abbreviations for ranks,
331
+ // like "subsp."
332
+ var nameParts_1 = [
333
+ item.genericName,
334
+ item.specificEpithet,
335
+ item.infraspecificEpithet
336
+ ];
337
+ if (item.taxonRank in RANK_LABELS) {
338
+ nameParts_1.splice(2, 0, RANK_LABELS[item.taxonRank]);
339
+ }
340
+ item.scientificName = nameParts_1.join(' ');
341
+ if (item.infraspecificEpithet !== taxon) {
342
+ console.log(item, taxon);
343
+ throw new Error("Infraspecific epithet should be lowercase: \"".concat(taxon, "\""));
344
+ }
345
+ }
346
+ // Re-add authorship information
347
+ item.scientificNameOnly = item.scientificName;
348
+ if (item.scientificNameAuthorship) {
349
+ item.scientificName += " ".concat(item.scientificNameAuthorship);
350
+ }
351
+ return item;
352
+ }