@larsgw/formica 0.8.6 → 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 +9 -0
- package/lib/resources/parse-text.js +4 -0
- package/package.json +1 -1
- package/src/resources/parse-text.ts +5 -0
- package/test/resources.js +144 -113
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## [0.8.7](https://github.com/identification-resources/formica/compare/v0.8.6...v0.8.7) (2025-11-01)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **resources:** fix handling of corrections ([49c05b4](https://github.com/identification-resources/formica/commit/49c05b499262e64514b832313360d1e6a4cc6378))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
1
10
|
## [0.8.6](https://github.com/identification-resources/formica/compare/v0.8.5...v0.8.6) (2025-10-28)
|
|
2
11
|
|
|
3
12
|
|
|
@@ -255,6 +255,10 @@ function parseResourceContent(content, resource, oldIds, offsetLine) {
|
|
|
255
255
|
}
|
|
256
256
|
// Amend "parent" with corrections, exit
|
|
257
257
|
if (item.taxonomicStatus === 'incorrect') {
|
|
258
|
+
if (parent.taxonomicStatus !== 'accepted') {
|
|
259
|
+
// Remove corrected synonym from parentage
|
|
260
|
+
parents[parents.length - 1] = null;
|
|
261
|
+
}
|
|
258
262
|
if (parent.incorrect) {
|
|
259
263
|
errors.push(makeParseError('Cannot apply a correction to a previous correction', lineNumber));
|
|
260
264
|
continue;
|
package/package.json
CHANGED
|
@@ -243,6 +243,11 @@ function parseResourceContent (content: ResourceDiff, resource: Resource, oldIds
|
|
|
243
243
|
|
|
244
244
|
// Amend "parent" with corrections, exit
|
|
245
245
|
if (item.taxonomicStatus === 'incorrect') {
|
|
246
|
+
if (parent.taxonomicStatus !== 'accepted') {
|
|
247
|
+
// Remove corrected synonym from parentage
|
|
248
|
+
parents[parents.length - 1] = null
|
|
249
|
+
}
|
|
250
|
+
|
|
246
251
|
if (parent.incorrect) {
|
|
247
252
|
errors.push(makeParseError('Cannot apply a correction to a previous correction', lineNumber))
|
|
248
253
|
continue
|
package/test/resources.js
CHANGED
|
@@ -3,8 +3,8 @@ const assert = require('assert')
|
|
|
3
3
|
|
|
4
4
|
const { catalog, resources } = require('../lib')
|
|
5
5
|
|
|
6
|
-
suite('catalog',
|
|
7
|
-
|
|
6
|
+
suite('catalog', () => {
|
|
7
|
+
test('reports missing required fields', () => {
|
|
8
8
|
const errors = catalog.loadData(`id
|
|
9
9
|
B1`, 'catalog').validate()
|
|
10
10
|
assert.deepStrictEqual(errors, [
|
|
@@ -18,8 +18,8 @@ B1`, 'catalog').validate()
|
|
|
18
18
|
})
|
|
19
19
|
})
|
|
20
20
|
|
|
21
|
-
suite('resources',
|
|
22
|
-
|
|
21
|
+
suite('resources', () => {
|
|
22
|
+
test('parses author with initials', () => {
|
|
23
23
|
const [resource] = resources.parseTextFile(`---
|
|
24
24
|
levels: [family, genus, species]
|
|
25
25
|
---
|
|
@@ -33,32 +33,7 @@ Sphecidae A. Costa, 1886
|
|
|
33
33
|
assert.deepStrictEqual(Object.values(resource.taxa).map(taxon => taxon.scientificNameAuthorship), Array(3).fill('A. Costa, 1886'))
|
|
34
34
|
})
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
const [resource] = resources.parseTextFile(`---
|
|
38
|
-
levels: [species]
|
|
39
|
-
---
|
|
40
|
-
|
|
41
|
-
Clytochrysus lapidarius (Panzer, 1804)
|
|
42
|
-
= Crabo chrysostomus Lepeletier & Brullé, 1835
|
|
43
|
-
> Crabro chrysostomus Lepeletier & Brullé, 1835
|
|
44
|
-
`, 'T1')
|
|
45
|
-
assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Clytochrysus lapidarius (Panzer, 1804)')
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
await test('errors for missing leaf taxa', (t) => {
|
|
49
|
-
assert.throws(() => {
|
|
50
|
-
resources.parseTextFile(`---
|
|
51
|
-
levels: [family, genus, species]
|
|
52
|
-
---
|
|
53
|
-
|
|
54
|
-
Cydnidae
|
|
55
|
-
Cydnidae
|
|
56
|
-
Legnotus
|
|
57
|
-
limbosus`, 'T1')
|
|
58
|
-
})
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
await test('parses synonyms starting with intraspecific ranks', (t) => {
|
|
36
|
+
test('parses synonyms starting with intraspecific ranks', () => {
|
|
62
37
|
const [resource] = resources.parseTextFile(`---
|
|
63
38
|
levels: [species]
|
|
64
39
|
---
|
|
@@ -69,7 +44,7 @@ Lygaeus equestris (Linnaeus, 1758)
|
|
|
69
44
|
assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Lygaeus equestris f. lactans Horváth, 1899')
|
|
70
45
|
})
|
|
71
46
|
|
|
72
|
-
|
|
47
|
+
test('parses accepted taxa starting with intraspecific ranks', () => {
|
|
73
48
|
const [resource] = resources.parseTextFile(`---
|
|
74
49
|
levels: [species, form]
|
|
75
50
|
---
|
|
@@ -80,18 +55,7 @@ Lygaeus equestris (Linnaeus, 1758)
|
|
|
80
55
|
assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Lygaeus equestris f. lactans Horváth, 1899')
|
|
81
56
|
})
|
|
82
57
|
|
|
83
|
-
|
|
84
|
-
const [resource] = resources.parseTextFile(`---
|
|
85
|
-
levels: [genus, species]
|
|
86
|
-
---
|
|
87
|
-
|
|
88
|
-
Drymus
|
|
89
|
-
[indet]
|
|
90
|
-
`, 'T1')
|
|
91
|
-
assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Drymus')
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
await test('parses names containing non-ASCII characters', (t) => {
|
|
58
|
+
test('parses names containing non-ASCII characters', () => {
|
|
95
59
|
const [resource] = resources.parseTextFile(`---
|
|
96
60
|
levels: [species]
|
|
97
61
|
---
|
|
@@ -101,7 +65,7 @@ Nematus fåhraei Thomson
|
|
|
101
65
|
assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Nematus fåhraei Thomson')
|
|
102
66
|
})
|
|
103
67
|
|
|
104
|
-
|
|
68
|
+
test('parses synonyms in different genera', () => {
|
|
105
69
|
const [resource] = resources.parseTextFile(`---
|
|
106
70
|
levels: [species]
|
|
107
71
|
---
|
|
@@ -113,7 +77,7 @@ Katamenes arbustorum subsp. burlinii
|
|
|
113
77
|
assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Eumenes arbustorum var. burlinii')
|
|
114
78
|
})
|
|
115
79
|
|
|
116
|
-
|
|
80
|
+
test('parses species without generic names', () => {
|
|
117
81
|
const [resource] = resources.parseTextFile(`---
|
|
118
82
|
levels: [genus, subgenus, species]
|
|
119
83
|
---
|
|
@@ -125,7 +89,7 @@ Microdynerus Thomson, 1874
|
|
|
125
89
|
assert.strictEqual(resource.taxa['T1:1:3'].scientificName, 'Microdynerus microdynerus (Dalla Torre, 1889)')
|
|
126
90
|
})
|
|
127
91
|
|
|
128
|
-
|
|
92
|
+
test('parses hybrids', () => {
|
|
129
93
|
const [resource] = resources.parseTextFile(`---
|
|
130
94
|
levels: [species]
|
|
131
95
|
---
|
|
@@ -137,20 +101,7 @@ Tilia x vulgaris
|
|
|
137
101
|
assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Tilia ×vulgaris')
|
|
138
102
|
})
|
|
139
103
|
|
|
140
|
-
|
|
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
|
-
})
|
|
152
|
-
|
|
153
|
-
await test('parses cross-genus hybrids', (t) => {
|
|
104
|
+
test('parses cross-genus hybrids', () => {
|
|
154
105
|
const [resource] = resources.parseTextFile(`---
|
|
155
106
|
levels: [genus, species]
|
|
156
107
|
---
|
|
@@ -165,7 +116,7 @@ x Festulpia
|
|
|
165
116
|
assert.strictEqual(resource.taxa['T1:1:3'].scientificName, '×Festulpia Festuca rubra×Vulpia bromoides')
|
|
166
117
|
})
|
|
167
118
|
|
|
168
|
-
|
|
119
|
+
test('parses cross-genus hybrids without parent context', () => {
|
|
169
120
|
const [resource] = resources.parseTextFile(`---
|
|
170
121
|
levels: [species]
|
|
171
122
|
---
|
|
@@ -176,7 +127,7 @@ x Festulpia Festuca_rubra x Vulpia_bromoides
|
|
|
176
127
|
assert.strictEqual(resource.taxa['T1:1:1'].verbatimIdentification, '× Festulpia Festuca rubra × Vulpia bromoides')
|
|
177
128
|
})
|
|
178
129
|
|
|
179
|
-
|
|
130
|
+
test('handles skips in ranks', () => {
|
|
180
131
|
const [resource] = resources.parseTextFile(`---
|
|
181
132
|
levels: [family, genus, species]
|
|
182
133
|
---
|
|
@@ -189,8 +140,59 @@ Apidae
|
|
|
189
140
|
assert.strictEqual(resource.taxa['T1:1:2'].taxonRank, 'species')
|
|
190
141
|
})
|
|
191
142
|
|
|
192
|
-
|
|
143
|
+
test('parses genera with subgenus-rank synonyms', () => {
|
|
193
144
|
const [resource] = resources.parseTextFile(`---
|
|
145
|
+
levels: [genus]
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
Ectemnius Dahlbom
|
|
149
|
+
= Crabro (Ectemnius) Dahlbom
|
|
150
|
+
`, 'T1')
|
|
151
|
+
assert.strictEqual(resource.taxa['T1:1:2'].taxonRank, 'subgenus')
|
|
152
|
+
assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Ectemnius Dahlbom')
|
|
153
|
+
assert.strictEqual(resource.taxa['T1:1:2'].genericName, 'Crabro')
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test('parses genera with like-name subgenera', () => {
|
|
157
|
+
const [resource] = resources.parseTextFile(`---
|
|
158
|
+
levels: [genus, subgenus]
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
Polistes Latreille, 1802
|
|
162
|
+
Polistes Latreille, 1802
|
|
163
|
+
`, 'T1')
|
|
164
|
+
assert.strictEqual(resource.taxa['T1:1:2'].taxonRank, 'subgenus')
|
|
165
|
+
assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Polistes Latreille, 1802')
|
|
166
|
+
assert.strictEqual(resource.taxa['T1:1:2'].genericName, 'Polistes')
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
suite('leaf taxa checks', () => {
|
|
170
|
+
test('errors for missing leaf taxa', () => {
|
|
171
|
+
assert.throws(() => {
|
|
172
|
+
resources.parseTextFile(`---
|
|
173
|
+
levels: [family, genus, species]
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
Cydnidae
|
|
177
|
+
Cydnidae
|
|
178
|
+
Legnotus
|
|
179
|
+
limbosus`, 'T1')
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
test('does not validate "indet." lines', () => {
|
|
184
|
+
const [resource] = resources.parseTextFile(`---
|
|
185
|
+
levels: [genus, species]
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
Drymus
|
|
189
|
+
[indet]
|
|
190
|
+
`, 'T1')
|
|
191
|
+
assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Drymus')
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
test('synonyms do not break recognition of missing leaf taxa (1)', () => {
|
|
195
|
+
const [resource] = resources.parseTextFile(`---
|
|
194
196
|
levels: [genus, subgenus, species]
|
|
195
197
|
---
|
|
196
198
|
|
|
@@ -199,12 +201,12 @@ Bombus
|
|
|
199
201
|
Bombus
|
|
200
202
|
pascuorum
|
|
201
203
|
`, 'T1')
|
|
202
|
-
|
|
203
|
-
|
|
204
|
+
assert.strictEqual(resource.taxa['T1:1:4'].scientificName, 'Bombus pascuorum')
|
|
205
|
+
})
|
|
204
206
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
207
|
+
test('synonyms do not break recognition of missing leaf taxa (2)', () => {
|
|
208
|
+
assert.throws(() => {
|
|
209
|
+
resources.parseTextFile(`---
|
|
208
210
|
levels: [genus, subgenus, species]
|
|
209
211
|
---
|
|
210
212
|
|
|
@@ -214,12 +216,12 @@ Bombus
|
|
|
214
216
|
Bombus
|
|
215
217
|
pascuorum
|
|
216
218
|
`, 'T1')
|
|
219
|
+
})
|
|
217
220
|
})
|
|
218
|
-
})
|
|
219
221
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
222
|
+
test('synonyms do not break recognition of missing leaf taxa (3)', () => {
|
|
223
|
+
assert.throws(() => {
|
|
224
|
+
resources.parseTextFile(`---
|
|
223
225
|
levels: [family, genus, species]
|
|
224
226
|
---
|
|
225
227
|
|
|
@@ -229,50 +231,51 @@ Cydnidae
|
|
|
229
231
|
Legnotus
|
|
230
232
|
limbosus
|
|
231
233
|
`, 'T1')
|
|
234
|
+
})
|
|
232
235
|
})
|
|
233
236
|
})
|
|
234
237
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
+
suite('corrections', () => {
|
|
239
|
+
test('outputs corrected generic names', () => {
|
|
240
|
+
const [resource] = resources.parseTextFile(`---
|
|
241
|
+
levels: [genus, species]
|
|
238
242
|
---
|
|
239
243
|
|
|
240
|
-
|
|
241
|
-
|
|
244
|
+
Bogdania Kerzhner, 1964
|
|
245
|
+
> Bogdiana Kerzhner, 1964
|
|
246
|
+
myrmica Kerzhner, 1964
|
|
242
247
|
`, 'T1')
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
})
|
|
248
|
+
assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Bogdiana Kerzhner, 1964')
|
|
249
|
+
assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Bogdiana myrmica Kerzhner, 1964')
|
|
250
|
+
})
|
|
247
251
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
levels: [
|
|
252
|
+
test('does not validate name with correction', () => {
|
|
253
|
+
const [resource] = resources.parseTextFile(`---
|
|
254
|
+
levels: [species]
|
|
251
255
|
---
|
|
252
256
|
|
|
253
|
-
|
|
254
|
-
|
|
257
|
+
Clytochrysus lapidarius (Panzer, 1804)
|
|
258
|
+
= Crabo chrysostomus Lepeletier & Brullé, 1835
|
|
259
|
+
> Crabro chrysostomus Lepeletier & Brullé, 1835
|
|
255
260
|
`, 'T1')
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
assert.strictEqual(resource.taxa['T1:1:2'].genericName, 'Polistes')
|
|
259
|
-
})
|
|
261
|
+
assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Clytochrysus lapidarius (Panzer, 1804)')
|
|
262
|
+
})
|
|
260
263
|
|
|
261
|
-
|
|
262
|
-
|
|
264
|
+
test('parses invalid but corrected name', () => {
|
|
265
|
+
const [resource] = resources.parseTextFile(`---
|
|
263
266
|
levels: [species]
|
|
264
267
|
---
|
|
265
268
|
|
|
266
269
|
Crabro Kiesenwetteri A. Morawitz. 1866
|
|
267
270
|
> Crabro kiesenwetteri A. Morawitz. 1866
|
|
268
271
|
`, 'T1')
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
272
|
+
assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Crabro kiesenwetteri A. Morawitz. 1866')
|
|
273
|
+
assert.strictEqual(resource.taxa['T1:1:1'].scientificNameAuthorship, 'A. Morawitz. 1866')
|
|
274
|
+
assert.strictEqual(resource.taxa['T1:1:1'].verbatimIdentification, 'Crabro Kiesenwetteri A. Morawitz. 1866')
|
|
275
|
+
})
|
|
273
276
|
|
|
274
|
-
|
|
275
|
-
|
|
277
|
+
test('parses children of invalid but corrected name', () => {
|
|
278
|
+
const [resource] = resources.parseTextFile(`---
|
|
276
279
|
levels: [genus, species]
|
|
277
280
|
---
|
|
278
281
|
|
|
@@ -283,40 +286,68 @@ Pirus L.
|
|
|
283
286
|
Pirus aria Ehrh.
|
|
284
287
|
> Pyrus aria Ehrh.
|
|
285
288
|
`, 'T1')
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
289
|
+
assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Pyrus aucuparia Gaertn.')
|
|
290
|
+
assert.strictEqual(resource.taxa['T1:1:3'].scientificName, 'Pyrus domestica Sm.')
|
|
291
|
+
assert.strictEqual(resource.taxa['T1:1:4'].scientificName, 'Pyrus aria Ehrh.')
|
|
292
|
+
})
|
|
290
293
|
|
|
291
|
-
|
|
292
|
-
|
|
294
|
+
test('does not keep parts of invalid but corrected name', () => {
|
|
295
|
+
const [resource] = resources.parseTextFile(`---
|
|
293
296
|
levels: [species]
|
|
294
297
|
---
|
|
295
298
|
|
|
296
299
|
Scolia 5-punctata FABRICIUS, 1781
|
|
297
300
|
> Scolia quinquepunctata FABRICIUS, 1781
|
|
298
301
|
`, 'T1')
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
+
assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Scolia quinquepunctata Fabricius, 1781')
|
|
303
|
+
assert.strictEqual(resource.taxa['T1:1:1'].verbatimIdentification, 'Scolia 5-punctata FABRICIUS, 1781')
|
|
304
|
+
})
|
|
302
305
|
|
|
303
|
-
|
|
304
|
-
|
|
306
|
+
test('make correct diff when last line changes', () => {
|
|
307
|
+
const newText = `---
|
|
305
308
|
levels: [species]
|
|
306
309
|
---
|
|
307
310
|
|
|
308
311
|
Bittacus Hageni Brauer
|
|
309
312
|
> Bittacus hageni Brauer
|
|
310
313
|
`
|
|
311
|
-
|
|
314
|
+
const oldText = `---
|
|
312
315
|
levels: [genus, species]
|
|
313
316
|
---
|
|
314
317
|
|
|
315
318
|
Bittacus hageni Brauer
|
|
316
319
|
`
|
|
317
320
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
+
const [resource] = resources.parseTextFile(newText, 'T1', { txt: oldText, dwc: [[null, ['T:1:1'], ['T:1:2']]] })
|
|
322
|
+
assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Bittacus hageni Brauer')
|
|
323
|
+
assert.strictEqual(resource.taxa['T1:1:1'].verbatimIdentification, 'Bittacus Hageni Brauer')
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
test('corrections of synonyms are correctly applied', () => {
|
|
327
|
+
const [resource] = resources.parseTextFile(`---
|
|
328
|
+
levels: [genus, subgenus, species]
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
Lasius F.
|
|
332
|
+
= Domisthorpea Mor. & Drnt., 1915
|
|
333
|
+
> Donisthorpea Mor. & Drnt., 1915
|
|
334
|
+
fuliginosus Latr.
|
|
335
|
+
`, 'T1')
|
|
336
|
+
assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Donisthorpea Mor. & Drnt., 1915')
|
|
337
|
+
assert.strictEqual(resource.taxa['T1:1:3'].scientificName, 'Lasius fuliginosus Latr.')
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
test('corrections are correctly applied', () => {
|
|
341
|
+
const [resource] = resources.parseTextFile(`---
|
|
342
|
+
levels: [genus, species]
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
Neopachygaster Austin, 1901
|
|
346
|
+
> Neopachygaster Austen, 1901
|
|
347
|
+
meromelas (Dufour, 1841)
|
|
348
|
+
`, 'T1')
|
|
349
|
+
assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Neopachygaster Austen, 1901')
|
|
350
|
+
assert.strictEqual(resource.taxa['T1:1:2'].scientificName, 'Neopachygaster meromelas (Dufour, 1841)')
|
|
351
|
+
})
|
|
321
352
|
})
|
|
322
353
|
})
|