@larsgw/formica 0.6.8 → 0.7.0

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,22 @@
1
+ # [0.7.0](https://github.com/identification-resources/formica/compare/v0.6.8...v0.7.0) (2025-03-14)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **resources:** improve diff regarding indet lines ([2615437](https://github.com/identification-resources/formica/commit/26154374f50d680c5f3e187cd6a1a5eadbac6b5d))
7
+
8
+
9
+ ### Features
10
+
11
+ * **resources:** change syntax of "indet" line ([d53363e](https://github.com/identification-resources/formica/commit/d53363e65bfa77a2eb247999316621f9ca0bff59))
12
+
13
+
14
+ ### BREAKING CHANGES
15
+
16
+ * **resources:** "indet" lines now have to be prefixed with "[indet]"
17
+
18
+
19
+
1
20
  ## [0.6.8](https://github.com/identification-resources/formica/compare/v0.6.7...v0.6.8) (2025-03-13)
2
21
 
3
22
 
@@ -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;
@@ -74,13 +74,6 @@ var TAXONOMIC_STATUS = {
74
74
  '+': 'heterotypic synonym',
75
75
  '=': 'synonym'
76
76
  };
77
- var INDET_SUFFIXES = new Set([
78
- 'sp.',
79
- 'spec.',
80
- 'indet.',
81
- 'sp. indet.',
82
- 'spec. indet.'
83
- ]);
84
77
  var RANK_LABELS = {
85
78
  'subspecies': 'subsp.',
86
79
  'variety': 'var.',
@@ -281,7 +274,7 @@ function parseName(name, rank, parent) {
281
274
  item.scientificNameAuthorship = capitalizeAuthors(citation);
282
275
  item.taxonRemarks = notes;
283
276
  item.taxonRank = rank;
284
- if (/[^\p{L}0-9\u{00D7}\- ]/u.test(taxon) && !INDET_SUFFIXES.has(taxon)) {
277
+ if (/[^\p{L}0-9\u{00D7}\- ]/u.test(taxon)) {
285
278
  throw new Error("Taxon name contains unexpected characters: \"".concat(taxon, "\""));
286
279
  }
287
280
  // Validate names and recompose binomial and trinomial names
@@ -457,6 +450,16 @@ function parseResource(resource) {
457
450
  var content = rest.join('');
458
451
  return [config, content];
459
452
  }
453
+ function getIndentation(line) {
454
+ return line.match(/^ */)[0].length;
455
+ }
456
+ function isIndetLine(line, indent) {
457
+ if (indent === undefined) {
458
+ indent = getIndentation(line);
459
+ }
460
+ line = line.slice(indent);
461
+ return line.startsWith('[indet]');
462
+ }
460
463
  function parseResourceContent(content, resource, oldIds) {
461
464
  var idBase = "".concat(resource.id, ":");
462
465
  var data = resource.taxa;
@@ -465,20 +468,19 @@ function parseResourceContent(content, resource, oldIds) {
465
468
  var groupIndent = 0;
466
469
  var previousId = '';
467
470
  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";
471
+ for (var _i = 0, content_1 = content; _i < content_1.length; _i++) {
472
+ var line = content_1[_i];
473
+ if (line.type === diff_resource_1.ResourceDiffType.Deleted) {
474
+ // Increase id counter for removed line unless it was an "indet line"
475
+ if (!isIndetLine(line.original)) {
476
+ id++;
477
+ }
478
+ continue;
477
479
  }
478
- var lineIndent = line.match(/^ */)[0].length;
480
+ var lineIndent = getIndentation(line.text);
479
481
  if (lineIndent > groupIndent) {
480
482
  // 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)) {
483
+ if (data[previousId] && data[previousId].taxonomicStatus === 'accepted' || /^( {2})+> /.test(line.text)) {
482
484
  parents.push(previousId);
483
485
  }
484
486
  else {
@@ -500,17 +502,26 @@ function parseResourceContent(content, resource, oldIds) {
500
502
  parents = parents.slice(0, lineIndent / 2);
501
503
  groupIndent = lineIndent;
502
504
  }
505
+ // Do not process "indet" lines further, as they only serve to indicate
506
+ // that subtaxa are explicitely omitted
507
+ if (isIndetLine(line.text, lineIndent)) {
508
+ // If the line was previously not and ndet line, increase the id counter
509
+ if (line.type === diff_resource_1.ResourceDiffType.Modified && !isIndetLine(line.original)) {
510
+ id++;
511
+ }
512
+ continue;
513
+ }
503
514
  var parentId = parents.reduce(function (grandparent, parent) { return parent || grandparent; }, null);
504
515
  var parent_1 = parentId === null ? {} : data[parentId];
505
- var name_1 = line.slice(groupIndent);
516
+ var name_1 = line.text.slice(groupIndent);
506
517
  var rank = resource.metadata.levels[groupIndent / 2];
507
518
  var item = parseName(name_1, rank, parent_1);
508
519
  var isSynonym = item.taxonomicStatus !== 'accepted';
509
520
  // Add higher classification info
510
521
  var itemAsObject = item;
511
522
  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];
523
+ for (var _a = 0, DWC_RANKS_1 = DWC_RANKS; _a < DWC_RANKS_1.length; _a++) {
524
+ var rank_1 = DWC_RANKS_1[_a];
514
525
  itemAsObject[rank_1] = undefined;
515
526
  if (parentAsObject[rank_1]) {
516
527
  itemAsObject[rank_1] = parentAsObject[rank_1];
@@ -542,10 +553,14 @@ function parseResourceContent(content, resource, oldIds) {
542
553
  parentAsObject[key] = itemAsObject[key];
543
554
  }
544
555
  }
545
- return "continue";
556
+ continue;
546
557
  }
547
558
  // Set identifiers
548
- if (type === diff_resource_1.ResourceDiffType.Added) {
559
+ if (line.type === diff_resource_1.ResourceDiffType.Added) {
560
+ newIdOffset++;
561
+ item.scientificNameID = idBase + newIdOffset.toString();
562
+ }
563
+ else if (line.type === diff_resource_1.ResourceDiffType.Modified && isIndetLine(line.original)) {
549
564
  newIdOffset++;
550
565
  item.scientificNameID = idBase + newIdOffset.toString();
551
566
  }
@@ -560,10 +575,6 @@ function parseResourceContent(content, resource, oldIds) {
560
575
  item.acceptedNameUsage = isSynonym ? parent_1.scientificName : undefined;
561
576
  item.collectionCode = idBase.slice(0, -1);
562
577
  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
578
  }
568
579
  return resource;
569
580
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@larsgw/formica",
3
- "version": "0.6.8",
3
+ "version": "0.7.0",
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
 
@@ -66,14 +66,6 @@ const TAXONOMIC_STATUS: Record<string, TaxonStatus> = {
66
66
  '=': 'synonym'
67
67
  }
68
68
 
69
- const INDET_SUFFIXES = new Set([
70
- 'sp.',
71
- 'spec.',
72
- 'indet.',
73
- 'sp. indet.',
74
- 'spec. indet.'
75
- ])
76
-
77
69
  const RANK_LABELS: Record<Rank, string> = {
78
70
  'subspecies': 'subsp.',
79
71
  'variety': 'var.',
@@ -301,7 +293,7 @@ function parseName (name: string, rank: Rank, parent: WorkingTaxon): WorkingTaxo
301
293
  item.taxonRemarks = notes
302
294
  item.taxonRank = rank
303
295
 
304
- if (/[^\p{L}0-9\u{00D7}\- ]/u.test(taxon) && !INDET_SUFFIXES.has(taxon)) {
296
+ if (/[^\p{L}0-9\u{00D7}\- ]/u.test(taxon)) {
305
297
  throw new Error(`Taxon name contains unexpected characters: "${taxon}"`)
306
298
  }
307
299
 
@@ -488,6 +480,20 @@ function parseResource (resource: string): [ResourceMetadata, string] {
488
480
  return [config, content]
489
481
  }
490
482
 
483
+ function getIndentation (line: string): number {
484
+ return (line.match(/^ */) as string[])[0].length
485
+ }
486
+
487
+ function isIndetLine (line: string, indent?: number): boolean {
488
+ if (indent === undefined) {
489
+ indent = getIndentation(line)
490
+ }
491
+
492
+ line = line.slice(indent)
493
+
494
+ return line.startsWith('[indet]')
495
+ }
496
+
491
497
  function parseResourceContent (content: ResourceDiff, resource: Resource, oldIds: number[]): Resource {
492
498
  const idBase = `${resource.id}:`
493
499
 
@@ -498,23 +504,19 @@ function parseResourceContent (content: ResourceDiff, resource: Resource, oldIds
498
504
  let previousId = ''
499
505
  let newIdOffset = Math.max(...oldIds)
500
506
 
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))) {
507
+ for (const line of content) {
508
+ if (line.type === ResourceDiffType.Deleted) {
509
+ // Increase id counter for removed line unless it was an "indet line"
510
+ if (!isIndetLine(line.original as string)) {
511
+ id++
512
+ }
510
513
  continue
511
514
  }
512
515
 
513
- const lineIndent = (line.match(/^ */) as string[])[0].length
514
-
516
+ const lineIndent = getIndentation(line.text as string)
515
517
  if (lineIndent > groupIndent) {
516
518
  // 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)) {
519
+ if (data[previousId] && data[previousId].taxonomicStatus === 'accepted' || /^( {2})+> /.test(line.text as string)) {
518
520
  parents.push(previousId)
519
521
  } else {
520
522
  parents.push(null)
@@ -533,10 +535,19 @@ function parseResourceContent (content: ResourceDiff, resource: Resource, oldIds
533
535
  groupIndent = lineIndent
534
536
  }
535
537
 
538
+ // Do not process "indet" lines further, as they only serve to indicate
539
+ // that subtaxa are explicitely omitted
540
+ if (isIndetLine(line.text as string, lineIndent)) {
541
+ // If the line was previously not and ndet line, increase the id counter
542
+ if (line.type === ResourceDiffType.Modified && !isIndetLine(line.original as string)) {
543
+ id++
544
+ }
545
+ continue
546
+ }
547
+
536
548
  const parentId = parents.reduce((grandparent, parent) => parent || grandparent, null)
537
549
  const parent = parentId === null ? {} as WorkingTaxon : data[parentId]
538
-
539
- const name = line.slice(groupIndent)
550
+ const name = (line.text as string).slice(groupIndent)
540
551
  const rank = resource.metadata.levels[groupIndent / 2]
541
552
  const item = parseName(name, rank, parent)
542
553
  const isSynonym = item.taxonomicStatus !== 'accepted'
@@ -581,7 +592,10 @@ function parseResourceContent (content: ResourceDiff, resource: Resource, oldIds
581
592
  }
582
593
 
583
594
  // Set identifiers
584
- if (type === ResourceDiffType.Added) {
595
+ if (line.type === ResourceDiffType.Added) {
596
+ newIdOffset++
597
+ item.scientificNameID = idBase + newIdOffset.toString()
598
+ } else if (line.type === ResourceDiffType.Modified && isIndetLine(line.original as string)) {
585
599
  newIdOffset++
586
600
  item.scientificNameID = idBase + newIdOffset.toString()
587
601
  } 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')