@larsgw/formica 0.8.8 → 0.9.1
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 +25 -71
- package/lib/bin/generate-linked-data.js +6 -2
- package/lib/bin/process-resources.js +1 -0
- package/lib/bin/validate-catalog.js +55 -1
- package/lib/resources/parse-name.js +14 -4
- package/lib/resources/parse-text.js +27 -0
- package/package.json +2 -2
- package/src/bin/generate-linked-data.ts +7 -4
- package/src/bin/process-resources.ts +2 -0
- package/src/bin/validate-catalog.ts +71 -1
- package/src/module.d.ts +3 -1
- package/src/resources/parse-name.ts +16 -4
- package/src/resources/parse-text.ts +32 -0
- package/test/resources.js +21 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,35 @@
|
|
|
1
|
-
## [0.
|
|
1
|
+
## [0.9.1](https://github.com/identification-resources/formica/compare/v0.9.0...v0.9.1) (2026-04-21)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **resources:** fix RDF mapping of taxonomic status ([c65383e](https://github.com/identification-resources/formica/commit/c65383e4d64c674c6c3b9f69e8cae925fcdf6896))
|
|
2
7
|
|
|
3
8
|
|
|
4
9
|
### Features
|
|
5
10
|
|
|
6
|
-
* **
|
|
11
|
+
* **catalog:** add more validation for taxa ([2bbc017](https://github.com/identification-resources/formica/commit/2bbc0177f8d4a6909339cfee812c33873770f725))
|
|
7
12
|
|
|
13
|
+
# [0.9.0](https://github.com/identification-resources/formica/compare/v0.8.8...v0.9.0) (2026-02-04)
|
|
8
14
|
|
|
9
15
|
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* **resources:** improve taxonomic status ([0ea8fd2](https://github.com/identification-resources/formica/commit/0ea8fd220571f5f6ff05f517069aba10148c6888))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Features
|
|
22
|
+
|
|
23
|
+
* **resources:** include cluster data in DwC files ([60a28ac](https://github.com/identification-resources/formica/commit/60a28ace18b0760696735ca416a2e8d014d1c928))
|
|
24
|
+
* **resources:** include uncertainty of synonymy in DwC if specified ([03b6734](https://github.com/identification-resources/formica/commit/03b6734851dc9c331ec88fab21e90ad3f0303c3c))
|
|
25
|
+
|
|
26
|
+
## [0.8.8](https://github.com/identification-resources/formica/compare/v0.8.7...v0.8.8) (2025-12-25)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Features
|
|
30
|
+
|
|
31
|
+
* **resources:** add 'subclass' rank ([4b10405](https://github.com/identification-resources/formica/commit/4b10405b32840c1675caf7ef24bf2bbfd821a236))
|
|
32
|
+
|
|
10
33
|
## [0.8.7](https://github.com/identification-resources/formica/compare/v0.8.6...v0.8.7) (2025-11-01)
|
|
11
34
|
|
|
12
35
|
|
|
@@ -14,8 +37,6 @@
|
|
|
14
37
|
|
|
15
38
|
* **resources:** fix handling of corrections ([49c05b4](https://github.com/identification-resources/formica/commit/49c05b499262e64514b832313360d1e6a4cc6378))
|
|
16
39
|
|
|
17
|
-
|
|
18
|
-
|
|
19
40
|
## [0.8.6](https://github.com/identification-resources/formica/compare/v0.8.5...v0.8.6) (2025-10-28)
|
|
20
41
|
|
|
21
42
|
|
|
@@ -29,8 +50,6 @@
|
|
|
29
50
|
|
|
30
51
|
* **resources:** ignore errors in later-corrected names ([4c1f69e](https://github.com/identification-resources/formica/commit/4c1f69e81450664f122cdf00e68cb918248790e6)), closes [#5](https://github.com/identification-resources/formica/issues/5)
|
|
31
52
|
|
|
32
|
-
|
|
33
|
-
|
|
34
53
|
## [0.8.5](https://github.com/identification-resources/formica/compare/v0.8.4...v0.8.5) (2025-10-28)
|
|
35
54
|
|
|
36
55
|
|
|
@@ -48,8 +67,6 @@
|
|
|
48
67
|
* **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)
|
|
49
68
|
* **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)
|
|
50
69
|
|
|
51
|
-
|
|
52
|
-
|
|
53
70
|
## [0.8.4](https://github.com/identification-resources/formica/compare/v0.8.3...v0.8.4) (2025-09-19)
|
|
54
71
|
|
|
55
72
|
|
|
@@ -63,8 +80,6 @@
|
|
|
63
80
|
|
|
64
81
|
* **catalog:** map additional scope value ([80c6d26](https://github.com/identification-resources/formica/commit/80c6d269a7d5b03600991fb353280e81afcefb0e))
|
|
65
82
|
|
|
66
|
-
|
|
67
|
-
|
|
68
83
|
## [0.8.3](https://github.com/identification-resources/formica/compare/v0.8.2...v0.8.3) (2025-07-15)
|
|
69
84
|
|
|
70
85
|
|
|
@@ -72,8 +87,6 @@
|
|
|
72
87
|
|
|
73
88
|
* **catalog:** add catalog field key_characteristics ([6529cfa](https://github.com/identification-resources/formica/commit/6529cfab81f759f926b81bdd9d5a36e84b526284))
|
|
74
89
|
|
|
75
|
-
|
|
76
|
-
|
|
77
90
|
## [0.8.2](https://github.com/identification-resources/formica/compare/v0.8.1...v0.8.2) (2025-06-26)
|
|
78
91
|
|
|
79
92
|
|
|
@@ -82,8 +95,6 @@
|
|
|
82
95
|
* **resources:** add support for documented flag ([6cb89e4](https://github.com/identification-resources/formica/commit/6cb89e428bde8665b7e8f03b5c7f47a44e15f58e))
|
|
83
96
|
* **resources:** allow hyphens in biname pattern ([6a58a77](https://github.com/identification-resources/formica/commit/6a58a77757b8b6ae8c2d7d01440775776d183d05))
|
|
84
97
|
|
|
85
|
-
|
|
86
|
-
|
|
87
98
|
## [0.8.1](https://github.com/identification-resources/formica/compare/v0.8.0...v0.8.1) (2025-05-30)
|
|
88
99
|
|
|
89
100
|
|
|
@@ -97,8 +108,6 @@
|
|
|
97
108
|
|
|
98
109
|
* **resources:** add support for "(sub)gen. nov" pattern ([d2598eb](https://github.com/identification-resources/formica/commit/d2598ebfd2f80d9c2bcc00ce145b9fc229316e5b))
|
|
99
110
|
|
|
100
|
-
|
|
101
|
-
|
|
102
111
|
# [0.8.0](https://github.com/identification-resources/formica/compare/v0.7.3...v0.8.0) (2025-05-07)
|
|
103
112
|
|
|
104
113
|
|
|
@@ -111,8 +120,6 @@
|
|
|
111
120
|
|
|
112
121
|
* **catalog:** implement generation of linked data ([334616e](https://github.com/identification-resources/formica/commit/334616e2e12c0d91e49f84f06bdd00ab7a238b8e))
|
|
113
122
|
|
|
114
|
-
|
|
115
|
-
|
|
116
123
|
## [0.7.3](https://github.com/identification-resources/formica/compare/v0.7.2...v0.7.3) (2025-04-17)
|
|
117
124
|
|
|
118
125
|
|
|
@@ -120,8 +127,6 @@
|
|
|
120
127
|
|
|
121
128
|
* **catalog:** update validation for identifiers ([156a703](https://github.com/identification-resources/formica/commit/156a7036ab09c411d091bc9d0f628831a30ba3f5))
|
|
122
129
|
|
|
123
|
-
|
|
124
|
-
|
|
125
130
|
## [0.7.2](https://github.com/identification-resources/formica/compare/v0.7.1...v0.7.2) (2025-04-02)
|
|
126
131
|
|
|
127
132
|
|
|
@@ -129,8 +134,6 @@
|
|
|
129
134
|
|
|
130
135
|
* **catalog:** add validation for taxa.csv ([59cfcf2](https://github.com/identification-resources/formica/commit/59cfcf28ebbef42e28e648e800c178a7d528f37a))
|
|
131
136
|
|
|
132
|
-
|
|
133
|
-
|
|
134
137
|
## [0.7.1](https://github.com/identification-resources/formica/compare/v0.7.0...v0.7.1) (2025-03-25)
|
|
135
138
|
|
|
136
139
|
|
|
@@ -138,8 +141,6 @@
|
|
|
138
141
|
|
|
139
142
|
* **resources:** add 'subphylum' rank ([8b5243b](https://github.com/identification-resources/formica/commit/8b5243bfd79b6486c613ca1178f1e1c9ec3f43a4))
|
|
140
143
|
|
|
141
|
-
|
|
142
|
-
|
|
143
144
|
# [0.7.0](https://github.com/identification-resources/formica/compare/v0.6.8...v0.7.0) (2025-03-14)
|
|
144
145
|
|
|
145
146
|
|
|
@@ -157,8 +158,6 @@
|
|
|
157
158
|
|
|
158
159
|
* **resources:** "indet" lines now have to be prefixed with "[indet]"
|
|
159
160
|
|
|
160
|
-
|
|
161
|
-
|
|
162
161
|
## [0.6.8](https://github.com/identification-resources/formica/compare/v0.6.7...v0.6.8) (2025-03-13)
|
|
163
162
|
|
|
164
163
|
|
|
@@ -168,8 +167,6 @@
|
|
|
168
167
|
* **resources:** create index of CoL identifiers ([3792903](https://github.com/identification-resources/formica/commit/37929035374bc0c67ed2403e775e06a904bdbd4d))
|
|
169
168
|
* **resources:** support intergeneric hybrids ([0a6696c](https://github.com/identification-resources/formica/commit/0a6696c5fd0603a0366c80c2298b6544636ed0c6))
|
|
170
169
|
|
|
171
|
-
|
|
172
|
-
|
|
173
170
|
## [0.6.7](https://github.com/identification-resources/formica/compare/v0.6.6...v0.6.7) (2025-02-26)
|
|
174
171
|
|
|
175
172
|
|
|
@@ -184,8 +181,6 @@
|
|
|
184
181
|
|
|
185
182
|
* **resources:** add support for completeness flags ([55b5427](https://github.com/identification-resources/formica/commit/55b5427125b4f1072191cadbc461ba31d37e6f2e))
|
|
186
183
|
|
|
187
|
-
|
|
188
|
-
|
|
189
184
|
## [0.6.6](https://github.com/identification-resources/formica/compare/v0.6.5...v0.6.6) (2025-02-16)
|
|
190
185
|
|
|
191
186
|
|
|
@@ -193,8 +188,6 @@
|
|
|
193
188
|
|
|
194
189
|
* **resources:** allow other resources in version_of ([85947b0](https://github.com/identification-resources/formica/commit/85947b0dcfbdb4bfd6537c628b34087a6e6326f7))
|
|
195
190
|
|
|
196
|
-
|
|
197
|
-
|
|
198
191
|
## [0.6.5](https://github.com/identification-resources/formica/compare/v0.6.4...v0.6.5) (2024-12-20)
|
|
199
192
|
|
|
200
193
|
|
|
@@ -204,8 +197,6 @@
|
|
|
204
197
|
* **resources:** improve (ICBN) author parsing ([d3e3faf](https://github.com/identification-resources/formica/commit/d3e3faf411d1a7dccd528a5ee78bd0823a5883b4)), closes [#13](https://github.com/identification-resources/formica/issues/13) [#14](https://github.com/identification-resources/formica/issues/14)
|
|
205
198
|
* **resources:** support "et al." in author name ([82993bf](https://github.com/identification-resources/formica/commit/82993bffac5e7f3ed00d00c28c836f347b264a43))
|
|
206
199
|
|
|
207
|
-
|
|
208
|
-
|
|
209
200
|
## [0.6.4](https://github.com/identification-resources/formica/compare/v0.6.3...v0.6.4) (2024-12-16)
|
|
210
201
|
|
|
211
202
|
|
|
@@ -213,8 +204,6 @@
|
|
|
213
204
|
|
|
214
205
|
* **resources:** fix matching of some ICBN names ([3c18458](https://github.com/identification-resources/formica/commit/3c184586b64e2a31f1eb7298bcd7c80c1d119069))
|
|
215
206
|
|
|
216
|
-
|
|
217
|
-
|
|
218
207
|
## [0.6.3](https://github.com/identification-resources/formica/compare/v0.6.2...v0.6.3) (2024-04-12)
|
|
219
208
|
|
|
220
209
|
|
|
@@ -222,8 +211,6 @@
|
|
|
222
211
|
|
|
223
212
|
* **resources:** allow 'phylum' rank ([ff9be66](https://github.com/identification-resources/formica/commit/ff9be66abab79a86263764b54d6a2709833b14d4))
|
|
224
213
|
|
|
225
|
-
|
|
226
|
-
|
|
227
214
|
## [0.6.2](https://github.com/identification-resources/formica/compare/v0.6.1...v0.6.2) (2024-04-08)
|
|
228
215
|
|
|
229
216
|
|
|
@@ -236,8 +223,6 @@
|
|
|
236
223
|
|
|
237
224
|
* **catalog:** add script to de-duplicate links ([b719348](https://github.com/identification-resources/formica/commit/b719348635e1b35ee80758c0cdd9938625d79ebf))
|
|
238
225
|
|
|
239
|
-
|
|
240
|
-
|
|
241
226
|
## [0.6.1](https://github.com/identification-resources/formica/compare/v0.6.0...v0.6.1) (2024-03-20)
|
|
242
227
|
|
|
243
228
|
|
|
@@ -245,8 +230,6 @@
|
|
|
245
230
|
|
|
246
231
|
* **resources:** allow two-part hybrid names ([8e3b836](https://github.com/identification-resources/formica/commit/8e3b8368985d1db35b62d306d833121150e91865))
|
|
247
232
|
|
|
248
|
-
|
|
249
|
-
|
|
250
233
|
# [0.6.0](https://github.com/identification-resources/formica/compare/v0.5.2...v0.6.0) (2024-01-05)
|
|
251
234
|
|
|
252
235
|
|
|
@@ -254,8 +237,6 @@
|
|
|
254
237
|
|
|
255
238
|
* **catalog:** add entry type, key type values ([ed6e4fb](https://github.com/identification-resources/formica/commit/ed6e4fb545e09cddb5d466f602bffb8a8483f043))
|
|
256
239
|
|
|
257
|
-
|
|
258
|
-
|
|
259
240
|
## [0.5.2](https://github.com/identification-resources/formica/compare/v0.5.1...v0.5.2) (2023-11-27)
|
|
260
241
|
|
|
261
242
|
|
|
@@ -263,8 +244,6 @@
|
|
|
263
244
|
|
|
264
245
|
* **resources:** fix GBIF index generation ([c88e2d2](https://github.com/identification-resources/formica/commit/c88e2d2b306b930ca0b00b978e3eb89b8adfb656))
|
|
265
246
|
|
|
266
|
-
|
|
267
|
-
|
|
268
247
|
## [0.5.1](https://github.com/identification-resources/formica/compare/v0.5.0...v0.5.1) (2023-11-03)
|
|
269
248
|
|
|
270
249
|
|
|
@@ -272,8 +251,6 @@
|
|
|
272
251
|
|
|
273
252
|
* **resources:** fix file listing in processor ([009b980](https://github.com/identification-resources/formica/commit/009b98091b0f597e91213ace82252facbe3b5fed))
|
|
274
253
|
|
|
275
|
-
|
|
276
|
-
|
|
277
254
|
# [0.5.0](https://github.com/identification-resources/formica/compare/v0.4.3...v0.5.0) (2023-10-24)
|
|
278
255
|
|
|
279
256
|
|
|
@@ -304,8 +281,6 @@
|
|
|
304
281
|
does no longer pass validation. Use the "taxon_scope" and "scope" fields
|
|
305
282
|
in "catalog" instead.
|
|
306
283
|
|
|
307
|
-
|
|
308
|
-
|
|
309
284
|
## [0.4.3](https://github.com/identification-resources/formica/compare/v0.4.2...v0.4.3) (2023-09-30)
|
|
310
285
|
|
|
311
286
|
|
|
@@ -313,8 +288,6 @@ in "catalog" instead.
|
|
|
313
288
|
|
|
314
289
|
* **resource:** fix regression in taxon parsing ([098477b](https://github.com/identification-resources/formica/commit/098477b5914324a3c15ec885617d4cf30996f30d))
|
|
315
290
|
|
|
316
|
-
|
|
317
|
-
|
|
318
291
|
## [0.4.2](https://github.com/identification-resources/formica/compare/v0.4.1...v0.4.2) (2023-09-30)
|
|
319
292
|
|
|
320
293
|
|
|
@@ -324,8 +297,6 @@ in "catalog" instead.
|
|
|
324
297
|
* **resources:** correct synonym rank determintation ([766ea2a](https://github.com/identification-resources/formica/commit/766ea2af14ff3aa64a7a2e19ed096836d38182cf))
|
|
325
298
|
* **resources:** do not parse "indet." lines as taxa ([f4debf6](https://github.com/identification-resources/formica/commit/f4debf695b0cdb375bfb60ab5ce942fa2f7dee85)), closes [#8](https://github.com/identification-resources/formica/issues/8)
|
|
326
299
|
|
|
327
|
-
|
|
328
|
-
|
|
329
300
|
## [0.4.1](https://github.com/identification-resources/formica/compare/v0.4.0...v0.4.1) (2023-09-07)
|
|
330
301
|
|
|
331
302
|
|
|
@@ -333,8 +304,6 @@ in "catalog" instead.
|
|
|
333
304
|
|
|
334
305
|
* **catalog:** fix typo in duplicate_of schema ([fbb6333](https://github.com/identification-resources/formica/commit/fbb6333d10dd6f5846856ea8de81d941ce539d21))
|
|
335
306
|
|
|
336
|
-
|
|
337
|
-
|
|
338
307
|
# [0.4.0](https://github.com/identification-resources/formica/compare/v0.3.1...v0.4.0) (2023-09-07)
|
|
339
308
|
|
|
340
309
|
|
|
@@ -348,8 +317,6 @@ in "catalog" instead.
|
|
|
348
317
|
|
|
349
318
|
* **catalog:** add duplicate_of field ([e4a8bbe](https://github.com/identification-resources/formica/commit/e4a8bbecef9c6a40d0f2cc5d4e8bb67685a453ff))
|
|
350
319
|
|
|
351
|
-
|
|
352
|
-
|
|
353
320
|
## [0.3.1](https://github.com/identification-resources/formica/compare/v0.3.0...v0.3.1) (2023-09-04)
|
|
354
321
|
|
|
355
322
|
|
|
@@ -359,8 +326,6 @@ in "catalog" instead.
|
|
|
359
326
|
* **resources:** fix regression in c737d5b ([d4e52e9](https://github.com/identification-resources/formica/commit/d4e52e95ed1a35dabbd80b9f9aace1a824fdae98))
|
|
360
327
|
* **resources:** handle corrections to synonyms ([21ed9b7](https://github.com/identification-resources/formica/commit/21ed9b79f77e1e6d0639d96ed9822d5212a85a2e)), closes [#4](https://github.com/identification-resources/formica/issues/4)
|
|
361
328
|
|
|
362
|
-
|
|
363
|
-
|
|
364
329
|
# [0.3.0](https://github.com/identification-resources/formica/compare/v0.2.1...v0.3.0) (2023-08-20)
|
|
365
330
|
|
|
366
331
|
|
|
@@ -372,8 +337,6 @@ in "catalog" instead.
|
|
|
372
337
|
* **resources:** improve taxon name matching ([78ff480](https://github.com/identification-resources/formica/commit/78ff480485ca42bd1d1f2893f230e61db6cb6be8)), closes [#2](https://github.com/identification-resources/formica/issues/2)
|
|
373
338
|
* **resources:** test for rank mismatch ([0e69b7f](https://github.com/identification-resources/formica/commit/0e69b7f0a6654fa5255e797be7516cd06edb4df3)), closes [#2](https://github.com/identification-resources/formica/issues/2)
|
|
374
339
|
|
|
375
|
-
|
|
376
|
-
|
|
377
340
|
## [0.2.1](https://github.com/identification-resources/formica/compare/v0.2.0...v0.2.1) (2023-08-10)
|
|
378
341
|
|
|
379
342
|
|
|
@@ -382,8 +345,6 @@ in "catalog" instead.
|
|
|
382
345
|
* **resources:** do not validate old versions ([1a8dd2e](https://github.com/identification-resources/formica/commit/1a8dd2e0e373489e287d1fd89dbb7443245c5214))
|
|
383
346
|
* **resources:** fix check for missing leaf taxa ([035f1ad](https://github.com/identification-resources/formica/commit/035f1ad20efa80b388ec3828d4ace4518c7b344e))
|
|
384
347
|
|
|
385
|
-
|
|
386
|
-
|
|
387
348
|
# [0.2.0](https://github.com/identification-resources/formica/compare/v0.1.1...v0.2.0) (2023-08-09)
|
|
388
349
|
|
|
389
350
|
|
|
@@ -392,8 +353,6 @@ in "catalog" instead.
|
|
|
392
353
|
* **catalog:** add taxon_scope column ([6a06c37](https://github.com/identification-resources/formica/commit/6a06c37ec0640a8ffbc258ad6a4d81d84d35fee9))
|
|
393
354
|
* **resources:** check for missing leaf taxa ([387c47d](https://github.com/identification-resources/formica/commit/387c47d10bb7f1fb09a68c10dfa869bb924b7633))
|
|
394
355
|
|
|
395
|
-
|
|
396
|
-
|
|
397
356
|
## [0.1.1](https://github.com/identification-resources/formica/compare/v0.1.0...v0.1.1) (2023-05-10)
|
|
398
357
|
|
|
399
358
|
|
|
@@ -401,8 +360,6 @@ in "catalog" instead.
|
|
|
401
360
|
|
|
402
361
|
* **processor:** add Node hashbang ([51dee57](https://github.com/identification-resources/formica/commit/51dee57b8547afba449b2b846bd7d65e7927368f))
|
|
403
362
|
|
|
404
|
-
|
|
405
|
-
|
|
406
363
|
# [0.1.0](https://github.com/identification-resources/formica/compare/fed91fd6f350c47bd067d221a4d0e2278a199dae...v0.1.0) (2023-03-01)
|
|
407
364
|
|
|
408
365
|
|
|
@@ -424,6 +381,3 @@ in "catalog" instead.
|
|
|
424
381
|
* **resources:** add support for hybrids ([e49e96a](https://github.com/identification-resources/formica/commit/e49e96af9c3958bab2a3b0508b1fc8a018b80716))
|
|
425
382
|
* **validate:** add catalog validation script ([4cdf785](https://github.com/identification-resources/formica/commit/4cdf785afb2c77320cd9b5b76483e3464c0f1cff))
|
|
426
383
|
* **validate:** add script to validate resources ([246fb5d](https://github.com/identification-resources/formica/commit/246fb5dd8da03e736884850874cc51a11be9985c))
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
@@ -125,7 +125,8 @@ const GBIF_RANKS = ['kingdom', 'phylum', 'class', 'order', 'family', 'genus', 's
|
|
|
125
125
|
const GBIF_VOCAB_RANKS = ['domain', 'kingdom', 'subkingdom', 'superphylum', 'phylum', 'subphylum', 'superclass', 'class', 'subclass', 'supercohort', 'cohort', 'subcohort', 'superorder', 'order', 'suborder', 'infraorder', 'superfamily', 'family', 'subfamily', 'tribe', 'subtribe', 'genus', 'subgenus', 'section', 'subsection', 'series', 'subseries', 'speciesAggregate', 'species', 'subspecificAggregate', 'subspecies', 'variety', 'subvariety', 'form', 'subform', 'cultivarGroup', 'cultivar', 'strain'];
|
|
126
126
|
const STATUSES = {
|
|
127
127
|
'accepted': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/accepted',
|
|
128
|
-
'
|
|
128
|
+
'misapplied': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/misapplied',
|
|
129
|
+
'proparte synonym': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/proParteSynonym',
|
|
129
130
|
'synonym': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/synonym',
|
|
130
131
|
};
|
|
131
132
|
function getCoveringTaxon(taxa) {
|
|
@@ -284,9 +285,12 @@ function makeLinkedDataForScientificName(name) {
|
|
|
284
285
|
if (GBIF_VOCAB_RANKS.includes(name.taxonRank)) {
|
|
285
286
|
node[DWC_FIELDS.taxonRank] = makeTaxonRankUri(name.taxonRank);
|
|
286
287
|
}
|
|
287
|
-
if (name.taxonomicStatus) {
|
|
288
|
+
if (name.taxonomicStatus in STATUSES) {
|
|
288
289
|
node[DWC_FIELDS.taxonomicStatus] = makeTaxonomicStatusUri(name.taxonomicStatus);
|
|
289
290
|
}
|
|
291
|
+
else {
|
|
292
|
+
console.error('Unmapped taxonomic status:', name.taxonomicStatus);
|
|
293
|
+
}
|
|
290
294
|
if (name.acceptedNameUsageID) {
|
|
291
295
|
node['dwc:acceptedNameUsageID'] = makeScientificNameUri(name.acceptedNameUsageID);
|
|
292
296
|
}
|
|
@@ -46,12 +46,66 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
46
46
|
const fs_1 = require("fs");
|
|
47
47
|
const path = __importStar(require("path"));
|
|
48
48
|
const index_1 = require("../index");
|
|
49
|
+
class TaxaValidator {
|
|
50
|
+
constructor(taxa) {
|
|
51
|
+
this.taxa = Array.from(taxa).filter(taxon => taxon.has('ancestors_gbif'));
|
|
52
|
+
this.errors = [];
|
|
53
|
+
this.parentIndex = {};
|
|
54
|
+
}
|
|
55
|
+
validate() {
|
|
56
|
+
for (const taxon of this.taxa) {
|
|
57
|
+
const parents = taxon.get('ancestors_gbif');
|
|
58
|
+
if (parents.length > 1) {
|
|
59
|
+
const id = taxon.get('id');
|
|
60
|
+
for (let i = 1; i < parents.length; i++) {
|
|
61
|
+
this.addToIndex(id, 'ancestors_gbif', parents[i], parents[i - 1]);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (const taxon of this.taxa) {
|
|
66
|
+
if (taxon.has('gbif')) {
|
|
67
|
+
const id = taxon.get('id');
|
|
68
|
+
const parents = taxon.get('ancestors_gbif');
|
|
69
|
+
const parent = parents[parents.length - 1];
|
|
70
|
+
this.addToIndex(id, 'gbif', taxon.get('gbif'), parent);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
for (const taxon of this.taxa) {
|
|
74
|
+
if (taxon.has('children_gbif')) {
|
|
75
|
+
const id = taxon.get('id');
|
|
76
|
+
const parents = taxon.get('ancestors_gbif');
|
|
77
|
+
const parent = parents[parents.length - 1];
|
|
78
|
+
const childIds = taxon.get('children_gbif');
|
|
79
|
+
for (const childId of childIds) {
|
|
80
|
+
this.addToIndex(id, 'gbif', childId, parent);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return this.errors;
|
|
85
|
+
}
|
|
86
|
+
addToIndex(entity, field, childId, parentId) {
|
|
87
|
+
const actualParentId = this.parentIndex[childId];
|
|
88
|
+
if (!actualParentId) {
|
|
89
|
+
this.parentIndex[childId] = parentId;
|
|
90
|
+
}
|
|
91
|
+
else if (actualParentId !== parentId) {
|
|
92
|
+
const error = `Inconsistent ancestor of ${childId}, expected ${actualParentId} but got ${parentId}`;
|
|
93
|
+
this.errors.push({ entity, field, error });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
49
97
|
function validateFile(arg) {
|
|
50
98
|
return __awaiter(this, void 0, void 0, function* () {
|
|
51
99
|
const filePath = path.resolve(arg);
|
|
52
100
|
const file = yield fs_1.promises.readFile(filePath, 'utf8');
|
|
53
101
|
const sheet = path.basename(filePath, '.csv');
|
|
54
|
-
|
|
102
|
+
const entities = index_1.catalog.loadData(file, sheet);
|
|
103
|
+
const errors = entities.validate();
|
|
104
|
+
if (sheet === 'taxa') {
|
|
105
|
+
const validator = new TaxaValidator(entities);
|
|
106
|
+
errors.push(...validator.validate());
|
|
107
|
+
}
|
|
108
|
+
return errors;
|
|
55
109
|
});
|
|
56
110
|
}
|
|
57
111
|
function main(args) {
|
|
@@ -43,7 +43,7 @@ exports.RANKS = [
|
|
|
43
43
|
];
|
|
44
44
|
const TAXONOMIC_STATUS = {
|
|
45
45
|
'>': 'incorrect',
|
|
46
|
-
'+': '
|
|
46
|
+
'+': 'proparte synonym',
|
|
47
47
|
'=': 'synonym'
|
|
48
48
|
};
|
|
49
49
|
const RANK_LABELS = {
|
|
@@ -113,6 +113,7 @@ const SUBGENUS_PATTERN = /^([A-Z]\S+) (?:\(([A-Z]\S+?)\))(?= |$)/;
|
|
|
113
113
|
* $2c intergeneric hybrid: [A-Z][a-z]+_[a-z-]+ x [A-Z][a-z]+_[a-z-]+
|
|
114
114
|
*/
|
|
115
115
|
const 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-]+)(?= |$)/;
|
|
116
|
+
const CLUSTER_PATTERN = /^\[(_|\d+)\] /;
|
|
116
117
|
function compareRanks(a, b) {
|
|
117
118
|
return exports.RANKS.indexOf(a) - exports.RANKS.indexOf(b);
|
|
118
119
|
}
|
|
@@ -158,15 +159,21 @@ function parseName(name, rank, parent) {
|
|
|
158
159
|
const isSynonym = /^[+=>] /.test(name);
|
|
159
160
|
if (isSynonym) {
|
|
160
161
|
item.taxonomicStatus = TAXONOMIC_STATUS[name[0]];
|
|
161
|
-
name = name.
|
|
162
|
+
name = name.slice(2);
|
|
163
|
+
if (/^\? ?/.test(name)) {
|
|
164
|
+
item.dynamicProperties = '{"synonymUncertain":true}';
|
|
165
|
+
name = name.replace(/^\? ?/, '');
|
|
166
|
+
}
|
|
162
167
|
rank = getSynonymRank(name, parent.taxonRank);
|
|
163
168
|
}
|
|
164
169
|
else {
|
|
165
170
|
item.taxonomicStatus = 'accepted';
|
|
166
171
|
}
|
|
167
172
|
// Clusters
|
|
168
|
-
if (
|
|
169
|
-
|
|
173
|
+
if (CLUSTER_PATTERN.test(name)) {
|
|
174
|
+
const [match, cluster] = name.match(CLUSTER_PATTERN);
|
|
175
|
+
item.cluster = cluster;
|
|
176
|
+
name = name.slice(match.length);
|
|
170
177
|
}
|
|
171
178
|
// Set verbatim identification after subsequent syntax is removed.
|
|
172
179
|
item.verbatimIdentification = name.replace(/(?<=^| )x(?=$| )/g, HYBRID_SIGN).replace(/_/g, ' ');
|
|
@@ -351,5 +358,8 @@ function parseName(name, rank, parent) {
|
|
|
351
358
|
if (item.scientificNameAuthorship) {
|
|
352
359
|
item.scientificName += ` ${item.scientificNameAuthorship}`;
|
|
353
360
|
}
|
|
361
|
+
if (isSynonym && /^auctt?\./.test(item.taxonRemarks)) {
|
|
362
|
+
item.taxonomicStatus = 'misapplied';
|
|
363
|
+
}
|
|
354
364
|
return item;
|
|
355
365
|
}
|
|
@@ -153,6 +153,32 @@ function parseResource(resource) {
|
|
|
153
153
|
const offsetLine = resource.offsetLine + (header + _).split('\n').length - 1;
|
|
154
154
|
return [config, { content, offsetLine }];
|
|
155
155
|
}
|
|
156
|
+
function getTaxonChildren(parent, taxa) {
|
|
157
|
+
const children = [];
|
|
158
|
+
for (const id in taxa) {
|
|
159
|
+
if (taxa[id].parentNameUsageID === parent) {
|
|
160
|
+
children.push(taxa[id]);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return children;
|
|
164
|
+
}
|
|
165
|
+
function processClusters(taxa) {
|
|
166
|
+
for (const id in taxa) {
|
|
167
|
+
const taxon = taxa[id];
|
|
168
|
+
if (taxon.taxonomicStatus !== 'accepted' || !taxon.cluster) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const dynamicProperties = taxon.dynamicProperties ? JSON.parse(taxon.dynamicProperties) : {};
|
|
172
|
+
if (taxon.cluster === '_') {
|
|
173
|
+
dynamicProperties.identifiable = false;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
const siblings = getTaxonChildren(taxon.parentNameUsageID, taxa).filter(sibling => sibling.scientificNameID !== id);
|
|
177
|
+
dynamicProperties.indistinguishableFrom = siblings.filter(sibling => sibling.cluster === taxon.cluster).map(sibling => sibling.scientificNameID);
|
|
178
|
+
}
|
|
179
|
+
taxon.dynamicProperties = JSON.stringify(dynamicProperties);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
156
182
|
function parseResourceContent(content, resource, oldIds, offsetLine) {
|
|
157
183
|
var _a, _b;
|
|
158
184
|
const leafTaxonIndex = resource.metadata.levels.reduce((last, rank, i) => MAIN_RANKS.includes(rank) ? i : last, 0);
|
|
@@ -311,6 +337,7 @@ function parseResourceContent(content, resource, oldIds, offsetLine) {
|
|
|
311
337
|
if (errors.length) {
|
|
312
338
|
throw mergeParserErrors(errors);
|
|
313
339
|
}
|
|
340
|
+
processClusters(data);
|
|
314
341
|
return resource;
|
|
315
342
|
}
|
|
316
343
|
function splitResources(file) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@larsgw/formica",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
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",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"ietf-language-tag-regex": "^0.0.5",
|
|
36
36
|
"js-yaml": "^4.1.0",
|
|
37
|
-
"jsonld": "^
|
|
37
|
+
"jsonld": "^9.0.0",
|
|
38
38
|
"n3": "^1.25.2",
|
|
39
39
|
"spdx-license-list": "^6.6.0"
|
|
40
40
|
},
|
|
@@ -106,7 +106,8 @@ const GBIF_RANKS = ['kingdom', 'phylum', 'class', 'order', 'family', 'genus', 's
|
|
|
106
106
|
const GBIF_VOCAB_RANKS = ['domain', 'kingdom', 'subkingdom', 'superphylum', 'phylum', 'subphylum', 'superclass', 'class', 'subclass', 'supercohort', 'cohort', 'subcohort', 'superorder', 'order', 'suborder', 'infraorder', 'superfamily', 'family', 'subfamily', 'tribe', 'subtribe', 'genus', 'subgenus', 'section', 'subsection', 'series', 'subseries', 'speciesAggregate', 'species', 'subspecificAggregate', 'subspecies', 'variety', 'subvariety', 'form', 'subform', 'cultivarGroup', 'cultivar', 'strain']
|
|
107
107
|
const STATUSES: Record<string, string> = {
|
|
108
108
|
'accepted': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/accepted',
|
|
109
|
-
'
|
|
109
|
+
'misapplied': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/misapplied',
|
|
110
|
+
'proparte synonym': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/proParteSynonym',
|
|
110
111
|
'synonym': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/synonym',
|
|
111
112
|
}
|
|
112
113
|
|
|
@@ -182,8 +183,8 @@ function makeTaxonRankUri (rank: string): NodeObject|string {
|
|
|
182
183
|
}
|
|
183
184
|
}
|
|
184
185
|
|
|
185
|
-
function makeTaxonomicStatusUri (status:
|
|
186
|
-
return { '@id': STATUSES[status]
|
|
186
|
+
function makeTaxonomicStatusUri (status: keyof typeof STATUSES): NodeObject {
|
|
187
|
+
return { '@id': STATUSES[status] }
|
|
187
188
|
}
|
|
188
189
|
|
|
189
190
|
function makeLinkedDataForAuthor (author: catalog.Entity): NodeObject {
|
|
@@ -313,8 +314,10 @@ function makeLinkedDataForScientificName (name: AmendedTaxon): NodeObject {
|
|
|
313
314
|
node[DWC_FIELDS.taxonRank] = makeTaxonRankUri(name.taxonRank)
|
|
314
315
|
}
|
|
315
316
|
|
|
316
|
-
if (name.taxonomicStatus) {
|
|
317
|
+
if (name.taxonomicStatus in STATUSES) {
|
|
317
318
|
node[DWC_FIELDS.taxonomicStatus] = makeTaxonomicStatusUri(name.taxonomicStatus)
|
|
319
|
+
} else {
|
|
320
|
+
console.error('Unmapped taxonomic status:', name.taxonomicStatus)
|
|
318
321
|
}
|
|
319
322
|
|
|
320
323
|
if (name.acceptedNameUsageID) {
|
|
@@ -4,11 +4,81 @@ import { promises as fs } from 'fs'
|
|
|
4
4
|
import * as path from 'path'
|
|
5
5
|
import { catalog } from '../index'
|
|
6
6
|
|
|
7
|
+
class TaxaValidator {
|
|
8
|
+
taxa: catalog.Entity[];
|
|
9
|
+
errors: WorkError[];
|
|
10
|
+
parentIndex: Record<string, string>;
|
|
11
|
+
|
|
12
|
+
constructor (taxa: catalog.Entities) {
|
|
13
|
+
this.taxa = Array.from(taxa).filter(taxon => taxon.has('ancestors_gbif'))
|
|
14
|
+
this.errors = []
|
|
15
|
+
this.parentIndex = {}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
validate (): WorkError[] {
|
|
19
|
+
for (const taxon of this.taxa) {
|
|
20
|
+
const parents = taxon.get('ancestors_gbif') as string[]
|
|
21
|
+
|
|
22
|
+
if (parents.length > 1) {
|
|
23
|
+
const id = taxon.get('id') as string
|
|
24
|
+
|
|
25
|
+
for (let i = 1; i < parents.length; i++) {
|
|
26
|
+
this.addToIndex(id, 'ancestors_gbif', parents[i], parents[i - 1])
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (const taxon of this.taxa) {
|
|
32
|
+
if (taxon.has('gbif')) {
|
|
33
|
+
const id = taxon.get('id') as string
|
|
34
|
+
const parents = taxon.get('ancestors_gbif') as string[]
|
|
35
|
+
const parent = parents[parents.length - 1] as string
|
|
36
|
+
|
|
37
|
+
this.addToIndex(id, 'gbif', taxon.get('gbif') as string, parent)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (const taxon of this.taxa) {
|
|
42
|
+
if (taxon.has('children_gbif')) {
|
|
43
|
+
const id = taxon.get('id') as string
|
|
44
|
+
const parents = taxon.get('ancestors_gbif') as string[]
|
|
45
|
+
const parent = parents[parents.length - 1] as string
|
|
46
|
+
|
|
47
|
+
const childIds = taxon.get('children_gbif') as string[]
|
|
48
|
+
for (const childId of childIds) {
|
|
49
|
+
this.addToIndex(id, 'gbif', childId, parent)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return this.errors
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
addToIndex (entity: string, field: string, childId: string, parentId: string) {
|
|
58
|
+
const actualParentId = this.parentIndex[childId]
|
|
59
|
+
|
|
60
|
+
if (!actualParentId) {
|
|
61
|
+
this.parentIndex[childId] = parentId
|
|
62
|
+
} else if (actualParentId !== parentId) {
|
|
63
|
+
const error = `Inconsistent ancestor of ${childId}, expected ${actualParentId} but got ${parentId}`
|
|
64
|
+
this.errors.push({ entity, field, error })
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
7
69
|
async function validateFile (arg: string): Promise<WorkError[]> {
|
|
8
70
|
const filePath = path.resolve(arg)
|
|
9
71
|
const file = await fs.readFile(filePath, 'utf8')
|
|
10
72
|
const sheet = path.basename(filePath, '.csv')
|
|
11
|
-
|
|
73
|
+
const entities = catalog.loadData(file, sheet)
|
|
74
|
+
const errors = entities.validate()
|
|
75
|
+
|
|
76
|
+
if (sheet === 'taxa') {
|
|
77
|
+
const validator = new TaxaValidator(entities)
|
|
78
|
+
errors.push(...validator.validate())
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return errors
|
|
12
82
|
}
|
|
13
83
|
|
|
14
84
|
async function main (args: string[]): Promise<void> {
|
package/src/module.d.ts
CHANGED
|
@@ -56,12 +56,14 @@ interface TaxonBase {
|
|
|
56
56
|
subgenus?: string,
|
|
57
57
|
higherClassification?: string,
|
|
58
58
|
verbatimIdentification?: string,
|
|
59
|
+
dynamicProperties?: string,
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
interface WorkingTaxon extends TaxonBase {
|
|
62
63
|
// Non-standard
|
|
63
64
|
scientificNameOnly?: string,
|
|
64
|
-
incorrect?: WorkingTaxon
|
|
65
|
+
incorrect?: WorkingTaxon,
|
|
66
|
+
cluster?: string
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
interface Taxon extends TaxonBase {
|
|
@@ -42,7 +42,7 @@ export const RANKS: Rank[] = [
|
|
|
42
42
|
|
|
43
43
|
const TAXONOMIC_STATUS: Record<string, TaxonStatus> = {
|
|
44
44
|
'>': 'incorrect',
|
|
45
|
-
'+': '
|
|
45
|
+
'+': 'proparte synonym',
|
|
46
46
|
'=': 'synonym'
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -123,6 +123,8 @@ const SUBGENUS_PATTERN = /^([A-Z]\S+) (?:\(([A-Z]\S+?)\))(?= |$)/
|
|
|
123
123
|
*/
|
|
124
124
|
const 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-]+)(?= |$)/
|
|
125
125
|
|
|
126
|
+
const CLUSTER_PATTERN = /^\[(_|\d+)\] /
|
|
127
|
+
|
|
126
128
|
function compareRanks (a: Rank, b: Rank): number {
|
|
127
129
|
return RANKS.indexOf(a) - RANKS.indexOf(b)
|
|
128
130
|
}
|
|
@@ -173,15 +175,21 @@ export function parseName (name: string, rank: Rank, parent: WorkingTaxon): Work
|
|
|
173
175
|
const isSynonym = /^[+=>] /.test(name)
|
|
174
176
|
if (isSynonym) {
|
|
175
177
|
item.taxonomicStatus = TAXONOMIC_STATUS[name[0]]
|
|
176
|
-
name = name.
|
|
178
|
+
name = name.slice(2)
|
|
179
|
+
if (/^\? ?/.test(name)) {
|
|
180
|
+
item.dynamicProperties = '{"synonymUncertain":true}'
|
|
181
|
+
name = name.replace(/^\? ?/, '')
|
|
182
|
+
}
|
|
177
183
|
rank = getSynonymRank(name, parent.taxonRank as Rank)
|
|
178
184
|
} else {
|
|
179
185
|
item.taxonomicStatus = 'accepted'
|
|
180
186
|
}
|
|
181
187
|
|
|
182
188
|
// Clusters
|
|
183
|
-
if (
|
|
184
|
-
|
|
189
|
+
if (CLUSTER_PATTERN.test(name)) {
|
|
190
|
+
const [match, cluster] = name.match(CLUSTER_PATTERN) as string[]
|
|
191
|
+
item.cluster = cluster
|
|
192
|
+
name = name.slice(match.length)
|
|
185
193
|
}
|
|
186
194
|
|
|
187
195
|
// Set verbatim identification after subsequent syntax is removed.
|
|
@@ -376,5 +384,9 @@ export function parseName (name: string, rank: Rank, parent: WorkingTaxon): Work
|
|
|
376
384
|
item.scientificName += ` ${item.scientificNameAuthorship}`
|
|
377
385
|
}
|
|
378
386
|
|
|
387
|
+
if (isSynonym && /^auctt?\./.test(item.taxonRemarks)) {
|
|
388
|
+
item.taxonomicStatus = 'misapplied'
|
|
389
|
+
}
|
|
390
|
+
|
|
379
391
|
return item
|
|
380
392
|
}
|
|
@@ -134,6 +134,36 @@ function parseResource (resource: FilePart): [ResourceMetadata, FilePart] {
|
|
|
134
134
|
return [config, { content, offsetLine }]
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
function getTaxonChildren (parent: TaxonId|undefined, taxa: Record<TaxonId, WorkingTaxon>): WorkingTaxon[] {
|
|
138
|
+
const children = []
|
|
139
|
+
for (const id in taxa) {
|
|
140
|
+
if (taxa[id].parentNameUsageID === parent) {
|
|
141
|
+
children.push(taxa[id])
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return children
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function processClusters (taxa: Record<TaxonId, WorkingTaxon>) {
|
|
148
|
+
for (const id in taxa) {
|
|
149
|
+
const taxon = taxa[id]
|
|
150
|
+
if (taxon.taxonomicStatus !== 'accepted' || !taxon.cluster) {
|
|
151
|
+
continue
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const dynamicProperties = taxon.dynamicProperties ? JSON.parse(taxon.dynamicProperties) : {}
|
|
155
|
+
|
|
156
|
+
if (taxon.cluster === '_') {
|
|
157
|
+
dynamicProperties.identifiable = false
|
|
158
|
+
} else {
|
|
159
|
+
const siblings = getTaxonChildren(taxon.parentNameUsageID, taxa).filter(sibling => sibling.scientificNameID !== id)
|
|
160
|
+
dynamicProperties.indistinguishableFrom = siblings.filter(sibling => sibling.cluster === taxon.cluster).map(sibling => sibling.scientificNameID)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
taxon.dynamicProperties = JSON.stringify(dynamicProperties)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
137
167
|
function parseResourceContent (content: ResourceDiff, resource: Resource, oldIds: number[], offsetLine: number): Resource {
|
|
138
168
|
const leafTaxonIndex = resource.metadata.levels.reduce((last, rank, i) => MAIN_RANKS.includes(rank) ? i : last, 0)
|
|
139
169
|
const data = resource.taxa as Record<TaxonId, WorkingTaxon>
|
|
@@ -307,6 +337,8 @@ function parseResourceContent (content: ResourceDiff, resource: Resource, oldIds
|
|
|
307
337
|
throw mergeParserErrors(errors)
|
|
308
338
|
}
|
|
309
339
|
|
|
340
|
+
processClusters(data)
|
|
341
|
+
|
|
310
342
|
return resource
|
|
311
343
|
}
|
|
312
344
|
|
package/test/resources.js
CHANGED
|
@@ -166,6 +166,27 @@ Polistes Latreille, 1802
|
|
|
166
166
|
assert.strictEqual(resource.taxa['T1:1:2'].genericName, 'Polistes')
|
|
167
167
|
})
|
|
168
168
|
|
|
169
|
+
test('handles clusters', () => {
|
|
170
|
+
const [resource] = resources.parseTextFile(`---
|
|
171
|
+
levels: [genus, species]
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
Eurydema Laporte, 1833
|
|
175
|
+
oleracea (Linnaeus, 1758)
|
|
176
|
+
[1] rotundicollis (Dohrn, 1860)
|
|
177
|
+
[1] fieberi Schummel, 1837
|
|
178
|
+
[2] ornata (Linnaeus, 1758)
|
|
179
|
+
[2] ventralis Kolenati, 1846
|
|
180
|
+
[_] eckerleini Josifov, 1961
|
|
181
|
+
`, 'T1')
|
|
182
|
+
assert.strictEqual(resource.taxa['T1:1:2'].dynamicProperties, undefined)
|
|
183
|
+
assert.strictEqual(resource.taxa['T1:1:3'].dynamicProperties, '{"indistinguishableFrom":["T1:1:4"]}')
|
|
184
|
+
assert.strictEqual(resource.taxa['T1:1:4'].dynamicProperties, '{"indistinguishableFrom":["T1:1:3"]}')
|
|
185
|
+
assert.strictEqual(resource.taxa['T1:1:5'].dynamicProperties, '{"indistinguishableFrom":["T1:1:6"]}')
|
|
186
|
+
assert.strictEqual(resource.taxa['T1:1:6'].dynamicProperties, '{"indistinguishableFrom":["T1:1:5"]}')
|
|
187
|
+
assert.strictEqual(resource.taxa['T1:1:7'].dynamicProperties, '{"identifiable":false}')
|
|
188
|
+
})
|
|
189
|
+
|
|
169
190
|
suite('leaf taxa checks', () => {
|
|
170
191
|
test('errors for missing leaf taxa', () => {
|
|
171
192
|
assert.throws(() => {
|