@larsgw/formica 0.6.8 → 0.7.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 CHANGED
@@ -1,3 +1,31 @@
1
+ ## [0.7.1](https://github.com/identification-resources/formica/compare/v0.7.0...v0.7.1) (2025-03-25)
2
+
3
+
4
+ ### Features
5
+
6
+ * **resources:** add 'subphylum' rank ([8b5243b](https://github.com/identification-resources/formica/commit/8b5243bfd79b6486c613ca1178f1e1c9ec3f43a4))
7
+
8
+
9
+
10
+ # [0.7.0](https://github.com/identification-resources/formica/compare/v0.6.8...v0.7.0) (2025-03-14)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **resources:** improve diff regarding indet lines ([2615437](https://github.com/identification-resources/formica/commit/26154374f50d680c5f3e187cd6a1a5eadbac6b5d))
16
+
17
+
18
+ ### Features
19
+
20
+ * **resources:** change syntax of "indet" line ([d53363e](https://github.com/identification-resources/formica/commit/d53363e65bfa77a2eb247999316621f9ca0bff59))
21
+
22
+
23
+ ### BREAKING CHANGES
24
+
25
+ * **resources:** "indet" lines now have to be prefixed with "[indet]"
26
+
27
+
28
+
1
29
  ## [0.6.8](https://github.com/identification-resources/formica/compare/v0.6.7...v0.6.8) (2025-03-13)
2
30
 
3
31
 
@@ -4,4 +4,4 @@ export declare enum ResourceDiffType {
4
4
  Modified = "~",
5
5
  Unchanged = "="
6
6
  }
7
- export declare function createDiff(a: string, b: string, tokenize?: ResourceDiffTokenizer): ResourceDiffPart[];
7
+ export declare function createDiff(a: string, b: string, tokenize?: ResourceDiffTokenizer): ResourceDiff;
@@ -68,6 +68,7 @@ function gitTokenize(text) {
68
68
  return text.match(/\S+|\n|[\r\t\f\v \u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+/g);
69
69
  }
70
70
  function createDiff(a, b, tokenize) {
71
+ var _a, _b;
71
72
  if (tokenize === void 0) { tokenize = gitTokenize; }
72
73
  var X = tokenize(a.trimEnd());
73
74
  var Y = tokenize(b.trimEnd());
@@ -106,7 +107,7 @@ function createDiff(a, b, tokenize) {
106
107
  // Start of line
107
108
  if (line === null) {
108
109
  deletedNewlines = 0;
109
- line = { text: '', type: change.type };
110
+ line = { type: change.type };
110
111
  }
111
112
  // End of line (could be same token)
112
113
  if (change.text === '\n') {
@@ -132,7 +133,8 @@ function createDiff(a, b, tokenize) {
132
133
  // Add placeholders for deleted lines
133
134
  while (deletedNewlines--) {
134
135
  lines.push({
135
- text: '',
136
+ text: undefined,
137
+ original: '',
136
138
  type: ResourceDiffType.Deleted
137
139
  });
138
140
  }
@@ -144,7 +146,10 @@ function createDiff(a, b, tokenize) {
144
146
  line.type = ResourceDiffType.Modified;
145
147
  }
146
148
  if (change.type !== ResourceDiffType.Deleted) {
147
- line.text += change.text;
149
+ line.text = ((_a = line.text) !== null && _a !== void 0 ? _a : '') + change.text;
150
+ }
151
+ if (change.type !== ResourceDiffType.Added) {
152
+ line.original = ((_b = line.original) !== null && _b !== void 0 ? _b : '') + change.text;
148
153
  }
149
154
  }
150
155
  return lines;
@@ -17,6 +17,7 @@ var resource_1 = require("./resource");
17
17
  var diff_resource_1 = require("./diff-resource");
18
18
  var RANKS = [
19
19
  'phylum',
20
+ 'subphylum',
20
21
  'class',
21
22
  'infraclass',
22
23
  'superorder',
@@ -74,13 +75,6 @@ var TAXONOMIC_STATUS = {
74
75
  '+': 'heterotypic synonym',
75
76
  '=': 'synonym'
76
77
  };
77
- var INDET_SUFFIXES = new Set([
78
- 'sp.',
79
- 'spec.',
80
- 'indet.',
81
- 'sp. indet.',
82
- 'spec. indet.'
83
- ]);
84
78
  var RANK_LABELS = {
85
79
  'subspecies': 'subsp.',
86
80
  'variety': 'var.',
@@ -281,7 +275,7 @@ function parseName(name, rank, parent) {
281
275
  item.scientificNameAuthorship = capitalizeAuthors(citation);
282
276
  item.taxonRemarks = notes;
283
277
  item.taxonRank = rank;
284
- if (/[^\p{L}0-9\u{00D7}\- ]/u.test(taxon) && !INDET_SUFFIXES.has(taxon)) {
278
+ if (/[^\p{L}0-9\u{00D7}\- ]/u.test(taxon)) {
285
279
  throw new Error("Taxon name contains unexpected characters: \"".concat(taxon, "\""));
286
280
  }
287
281
  // Validate names and recompose binomial and trinomial names
@@ -457,6 +451,16 @@ function parseResource(resource) {
457
451
  var content = rest.join('');
458
452
  return [config, content];
459
453
  }
454
+ function getIndentation(line) {
455
+ return line.match(/^ */)[0].length;
456
+ }
457
+ function isIndetLine(line, indent) {
458
+ if (indent === undefined) {
459
+ indent = getIndentation(line);
460
+ }
461
+ line = line.slice(indent);
462
+ return line.startsWith('[indet]');
463
+ }
460
464
  function parseResourceContent(content, resource, oldIds) {
461
465
  var idBase = "".concat(resource.id, ":");
462
466
  var data = resource.taxa;
@@ -465,20 +469,19 @@ function parseResourceContent(content, resource, oldIds) {
465
469
  var groupIndent = 0;
466
470
  var previousId = '';
467
471
  var newIdOffset = Math.max.apply(Math, oldIds);
468
- var _loop_1 = function (line, type) {
469
- if (type === diff_resource_1.ResourceDiffType.Deleted) {
470
- id++;
471
- return "continue";
472
- }
473
- // Do not process "indet" lines further, as they only serve to indicate
474
- // that subtaxa are explicitely omitted
475
- if (Array.from(INDET_SUFFIXES).some(function (suffix) { return line.endsWith(' ' + suffix); })) {
476
- return "continue";
472
+ for (var _i = 0, content_1 = content; _i < content_1.length; _i++) {
473
+ var line = content_1[_i];
474
+ if (line.type === diff_resource_1.ResourceDiffType.Deleted) {
475
+ // Increase id counter for removed line unless it was an "indet line"
476
+ if (!isIndetLine(line.original)) {
477
+ id++;
478
+ }
479
+ continue;
477
480
  }
478
- var lineIndent = line.match(/^ */)[0].length;
481
+ var lineIndent = getIndentation(line.text);
479
482
  if (lineIndent > groupIndent) {
480
483
  // Do not count synonyms as parents (unless this is correcting a typo in the synonym)
481
- if (data[previousId] && data[previousId].taxonomicStatus === 'accepted' || /^( {2})+> /.test(line)) {
484
+ if (data[previousId] && data[previousId].taxonomicStatus === 'accepted' || /^( {2})+> /.test(line.text)) {
482
485
  parents.push(previousId);
483
486
  }
484
487
  else {
@@ -500,17 +503,26 @@ function parseResourceContent(content, resource, oldIds) {
500
503
  parents = parents.slice(0, lineIndent / 2);
501
504
  groupIndent = lineIndent;
502
505
  }
506
+ // Do not process "indet" lines further, as they only serve to indicate
507
+ // that subtaxa are explicitely omitted
508
+ if (isIndetLine(line.text, lineIndent)) {
509
+ // If the line was previously not and ndet line, increase the id counter
510
+ if (line.type === diff_resource_1.ResourceDiffType.Modified && !isIndetLine(line.original)) {
511
+ id++;
512
+ }
513
+ continue;
514
+ }
503
515
  var parentId = parents.reduce(function (grandparent, parent) { return parent || grandparent; }, null);
504
516
  var parent_1 = parentId === null ? {} : data[parentId];
505
- var name_1 = line.slice(groupIndent);
517
+ var name_1 = line.text.slice(groupIndent);
506
518
  var rank = resource.metadata.levels[groupIndent / 2];
507
519
  var item = parseName(name_1, rank, parent_1);
508
520
  var isSynonym = item.taxonomicStatus !== 'accepted';
509
521
  // Add higher classification info
510
522
  var itemAsObject = item;
511
523
  var parentAsObject = parent_1;
512
- for (var _b = 0, DWC_RANKS_1 = DWC_RANKS; _b < DWC_RANKS_1.length; _b++) {
513
- var rank_1 = DWC_RANKS_1[_b];
524
+ for (var _a = 0, DWC_RANKS_1 = DWC_RANKS; _a < DWC_RANKS_1.length; _a++) {
525
+ var rank_1 = DWC_RANKS_1[_a];
514
526
  itemAsObject[rank_1] = undefined;
515
527
  if (parentAsObject[rank_1]) {
516
528
  itemAsObject[rank_1] = parentAsObject[rank_1];
@@ -542,10 +554,14 @@ function parseResourceContent(content, resource, oldIds) {
542
554
  parentAsObject[key] = itemAsObject[key];
543
555
  }
544
556
  }
545
- return "continue";
557
+ continue;
546
558
  }
547
559
  // Set identifiers
548
- if (type === diff_resource_1.ResourceDiffType.Added) {
560
+ if (line.type === diff_resource_1.ResourceDiffType.Added) {
561
+ newIdOffset++;
562
+ item.scientificNameID = idBase + newIdOffset.toString();
563
+ }
564
+ else if (line.type === diff_resource_1.ResourceDiffType.Modified && isIndetLine(line.original)) {
549
565
  newIdOffset++;
550
566
  item.scientificNameID = idBase + newIdOffset.toString();
551
567
  }
@@ -560,10 +576,6 @@ function parseResourceContent(content, resource, oldIds) {
560
576
  item.acceptedNameUsage = isSynonym ? parent_1.scientificName : undefined;
561
577
  item.collectionCode = idBase.slice(0, -1);
562
578
  data[item.scientificNameID] = item;
563
- };
564
- for (var _i = 0, content_1 = content; _i < content_1.length; _i++) {
565
- var _a = content_1[_i], line = _a.text, type = _a.type;
566
- _loop_1(line, type);
567
579
  }
568
580
  return resource;
569
581
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@larsgw/formica",
3
- "version": "0.6.8",
3
+ "version": "0.7.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",
package/src/module.d.ts CHANGED
@@ -95,7 +95,8 @@ interface ResourceHistory {
95
95
  type ResourceDiff = ResourceDiffPart[]
96
96
 
97
97
  interface ResourceDiffPart {
98
- text: string,
98
+ text?: string,
99
+ original?: string,
99
100
  type: ResourceDiffType
100
101
  }
101
102
 
@@ -5,7 +5,12 @@ export enum ResourceDiffType {
5
5
  Unchanged = '='
6
6
  }
7
7
 
8
- function LCS (X: string[], Y: string[]): ResourceDiffPart[] {
8
+ interface DiffPart {
9
+ text: string,
10
+ type: ResourceDiffType
11
+ }
12
+
13
+ function LCS (X: string[], Y: string[]): DiffPart[] {
9
14
  const m = X.length
10
15
  const n = Y.length
11
16
 
@@ -57,7 +62,7 @@ function gitTokenize (text: string): string[] {
57
62
  return text.match(/\S+|\n|[\r\t\f\v \u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+/g) as string[]
58
63
  }
59
64
 
60
- export function createDiff (a: string, b: string, tokenize: ResourceDiffTokenizer = gitTokenize): ResourceDiffPart[] {
65
+ export function createDiff (a: string, b: string, tokenize: ResourceDiffTokenizer = gitTokenize): ResourceDiff {
61
66
  const X = tokenize(a.trimEnd())
62
67
  const Y = tokenize(b.trimEnd())
63
68
 
@@ -94,7 +99,7 @@ export function createDiff (a: string, b: string, tokenize: ResourceDiffTokenize
94
99
 
95
100
  // Convert word diff to line diff
96
101
  const lines: ResourceDiff = []
97
- let line = null
102
+ let line: ResourceDiffPart|null = null
98
103
  let deletedNewlines = 0
99
104
  let nextLineNew = false
100
105
 
@@ -102,7 +107,7 @@ export function createDiff (a: string, b: string, tokenize: ResourceDiffTokenize
102
107
  // Start of line
103
108
  if (line === null) {
104
109
  deletedNewlines = 0
105
- line = { text: '', type: change.type }
110
+ line = { type: change.type }
106
111
  }
107
112
 
108
113
  // End of line (could be same token)
@@ -132,7 +137,8 @@ export function createDiff (a: string, b: string, tokenize: ResourceDiffTokenize
132
137
  // Add placeholders for deleted lines
133
138
  while (deletedNewlines--) {
134
139
  lines.push({
135
- text: '',
140
+ text: undefined,
141
+ original: '',
136
142
  type: ResourceDiffType.Deleted
137
143
  })
138
144
  }
@@ -147,7 +153,11 @@ export function createDiff (a: string, b: string, tokenize: ResourceDiffTokenize
147
153
  }
148
154
 
149
155
  if (change.type !== ResourceDiffType.Deleted) {
150
- line.text += change.text
156
+ line.text = (line.text ?? '') + change.text
157
+ }
158
+
159
+ if (change.type !== ResourceDiffType.Added) {
160
+ line.original = (line.original ?? '') + change.text
151
161
  }
152
162
  }
153
163
 
@@ -4,6 +4,7 @@ import { createDiff, ResourceDiffType } from './diff-resource'
4
4
 
5
5
  const RANKS: Rank[] = [
6
6
  'phylum',
7
+ 'subphylum',
7
8
  'class',
8
9
  'infraclass',
9
10
  'superorder',
@@ -66,14 +67,6 @@ const TAXONOMIC_STATUS: Record<string, TaxonStatus> = {
66
67
  '=': 'synonym'
67
68
  }
68
69
 
69
- const INDET_SUFFIXES = new Set([
70
- 'sp.',
71
- 'spec.',
72
- 'indet.',
73
- 'sp. indet.',
74
- 'spec. indet.'
75
- ])
76
-
77
70
  const RANK_LABELS: Record<Rank, string> = {
78
71
  'subspecies': 'subsp.',
79
72
  'variety': 'var.',
@@ -301,7 +294,7 @@ function parseName (name: string, rank: Rank, parent: WorkingTaxon): WorkingTaxo
301
294
  item.taxonRemarks = notes
302
295
  item.taxonRank = rank
303
296
 
304
- if (/[^\p{L}0-9\u{00D7}\- ]/u.test(taxon) && !INDET_SUFFIXES.has(taxon)) {
297
+ if (/[^\p{L}0-9\u{00D7}\- ]/u.test(taxon)) {
305
298
  throw new Error(`Taxon name contains unexpected characters: "${taxon}"`)
306
299
  }
307
300
 
@@ -488,6 +481,20 @@ function parseResource (resource: string): [ResourceMetadata, string] {
488
481
  return [config, content]
489
482
  }
490
483
 
484
+ function getIndentation (line: string): number {
485
+ return (line.match(/^ */) as string[])[0].length
486
+ }
487
+
488
+ function isIndetLine (line: string, indent?: number): boolean {
489
+ if (indent === undefined) {
490
+ indent = getIndentation(line)
491
+ }
492
+
493
+ line = line.slice(indent)
494
+
495
+ return line.startsWith('[indet]')
496
+ }
497
+
491
498
  function parseResourceContent (content: ResourceDiff, resource: Resource, oldIds: number[]): Resource {
492
499
  const idBase = `${resource.id}:`
493
500
 
@@ -498,23 +505,19 @@ function parseResourceContent (content: ResourceDiff, resource: Resource, oldIds
498
505
  let previousId = ''
499
506
  let newIdOffset = Math.max(...oldIds)
500
507
 
501
- for (const { text: line, type } of content) {
502
- if (type === ResourceDiffType.Deleted) {
503
- id++
504
- continue
505
- }
506
-
507
- // Do not process "indet" lines further, as they only serve to indicate
508
- // that subtaxa are explicitely omitted
509
- if (Array.from(INDET_SUFFIXES).some(suffix => line.endsWith(' ' + suffix))) {
508
+ for (const line of content) {
509
+ if (line.type === ResourceDiffType.Deleted) {
510
+ // Increase id counter for removed line unless it was an "indet line"
511
+ if (!isIndetLine(line.original as string)) {
512
+ id++
513
+ }
510
514
  continue
511
515
  }
512
516
 
513
- const lineIndent = (line.match(/^ */) as string[])[0].length
514
-
517
+ const lineIndent = getIndentation(line.text as string)
515
518
  if (lineIndent > groupIndent) {
516
519
  // Do not count synonyms as parents (unless this is correcting a typo in the synonym)
517
- if (data[previousId] && data[previousId].taxonomicStatus === 'accepted' || /^( {2})+> /.test(line)) {
520
+ if (data[previousId] && data[previousId].taxonomicStatus === 'accepted' || /^( {2})+> /.test(line.text as string)) {
518
521
  parents.push(previousId)
519
522
  } else {
520
523
  parents.push(null)
@@ -533,10 +536,19 @@ function parseResourceContent (content: ResourceDiff, resource: Resource, oldIds
533
536
  groupIndent = lineIndent
534
537
  }
535
538
 
539
+ // Do not process "indet" lines further, as they only serve to indicate
540
+ // that subtaxa are explicitely omitted
541
+ if (isIndetLine(line.text as string, lineIndent)) {
542
+ // If the line was previously not and ndet line, increase the id counter
543
+ if (line.type === ResourceDiffType.Modified && !isIndetLine(line.original as string)) {
544
+ id++
545
+ }
546
+ continue
547
+ }
548
+
536
549
  const parentId = parents.reduce((grandparent, parent) => parent || grandparent, null)
537
550
  const parent = parentId === null ? {} as WorkingTaxon : data[parentId]
538
-
539
- const name = line.slice(groupIndent)
551
+ const name = (line.text as string).slice(groupIndent)
540
552
  const rank = resource.metadata.levels[groupIndent / 2]
541
553
  const item = parseName(name, rank, parent)
542
554
  const isSynonym = item.taxonomicStatus !== 'accepted'
@@ -581,7 +593,10 @@ function parseResourceContent (content: ResourceDiff, resource: Resource, oldIds
581
593
  }
582
594
 
583
595
  // Set identifiers
584
- if (type === ResourceDiffType.Added) {
596
+ if (line.type === ResourceDiffType.Added) {
597
+ newIdOffset++
598
+ item.scientificNameID = idBase + newIdOffset.toString()
599
+ } else if (line.type === ResourceDiffType.Modified && isIndetLine(line.original as string)) {
585
600
  newIdOffset++
586
601
  item.scientificNameID = idBase + newIdOffset.toString()
587
602
  } else {
package/test/resources.js CHANGED
@@ -26,9 +26,9 @@ levels: [family, genus, species]
26
26
 
27
27
  Dolichurus haemorrhous A. Costa, 1886
28
28
  Dolichurus A. Costa, 1886
29
- Dolichurus indet.
29
+ [indet]
30
30
  Sphecidae A. Costa, 1886
31
- Sphecidae indet.
31
+ [indet]
32
32
  `, 'T1')
33
33
  assert.deepStrictEqual(Object.values(resource.taxa).map(taxon => taxon.scientificNameAuthorship), Array(3).fill('A. Costa, 1886'))
34
34
  })
@@ -86,7 +86,7 @@ levels: [genus, species]
86
86
  ---
87
87
 
88
88
  Drymus
89
- Unknown sp.
89
+ [indet]
90
90
  `, 'T1')
91
91
  assert.strictEqual(resource.taxa['T1:1:1'].scientificName, 'Drymus')
92
92
  })
@@ -156,7 +156,7 @@ levels: [genus, species]
156
156
  ---
157
157
 
158
158
  x Triticosecale
159
- Triticosecale indet.
159
+ [indet]
160
160
  x Festulpia
161
161
  Festuca_rubra x Vulpia_bromoides
162
162
  `, 'T1')