@larsgw/formica 0.8.0 → 0.8.2

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,28 @@
1
+ ## [0.8.2](https://github.com/identification-resources/formica/compare/v0.8.1...v0.8.2) (2025-06-26)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **resources:** add support for documented flag ([6cb89e4](https://github.com/identification-resources/formica/commit/6cb89e428bde8665b7e8f03b5c7f47a44e15f58e))
7
+ * **resources:** allow hyphens in biname pattern ([6a58a77](https://github.com/identification-resources/formica/commit/6a58a77757b8b6ae8c2d7d01440775776d183d05))
8
+
9
+
10
+
11
+ ## [0.8.1](https://github.com/identification-resources/formica/compare/v0.8.0...v0.8.1) (2025-05-30)
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * **catalog:** fix aspects of linked data ([1b2964f](https://github.com/identification-resources/formica/commit/1b2964f46b8409c6a67f0475faf067a80734288c))
17
+ * **catalog:** set display_name to required, as documented ([3f36909](https://github.com/identification-resources/formica/commit/3f36909bbe6261cbee022139050875de37174611))
18
+
19
+
20
+ ### Features
21
+
22
+ * **resources:** add support for "(sub)gen. nov" pattern ([d2598eb](https://github.com/identification-resources/formica/commit/d2598ebfd2f80d9c2bcc00ce145b9fc229316e5b))
23
+
24
+
25
+
1
26
  # [0.8.0](https://github.com/identification-resources/formica/compare/v0.7.3...v0.8.0) (2025-05-07)
2
27
 
3
28
 
@@ -129,7 +129,6 @@ var SCOPES = {
129
129
  'queens': ['dwc:caste', 'queen'],
130
130
  'workers': ['dwc:caste', 'worker'],
131
131
  'soldiers': ['dwc:caste', 'soldier'],
132
- 'alates': ['dwc:caste', 'alate'],
133
132
  'alatae': ['dwc:caste', 'alate'],
134
133
  'apterae': ['dwc:caste', 'aptera'],
135
134
  'viviparae': ['dwc:caste', 'vivipara'],
@@ -151,7 +150,9 @@ var PREFIXES = {
151
150
  'dwciri': 'http://rs.tdwg.org/dwc/iri/',
152
151
  'foaf': 'http://xmlns.com/foaf/0.1/',
153
152
  'owl': 'http://www.w3.org/2002/07/owl#',
153
+ 'rdfs': 'http://www.w3.org/2000/01/rdf-schema#',
154
154
  'schema': 'https://schema.org/',
155
+ 'xsd': 'http://www.w3.org/2001/XMLSchema#',
155
156
  };
156
157
  var DWC_FIELDS = {
157
158
  scientificName: 'dwc:scientificName',
@@ -166,6 +167,12 @@ var DWC_FIELDS = {
166
167
  verbatimIdentification: 'dwc:verbatimIdentification',
167
168
  };
168
169
  var GBIF_RANKS = ['kingdom', 'phylum', 'class', 'order', 'family', 'genus', 'species', 'subspecies'];
170
+ var 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'];
171
+ var STATUSES = {
172
+ 'accepted': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/accepted',
173
+ 'heterotypic synonym': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/heterotypicSynonym',
174
+ 'synonym': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/synonym',
175
+ };
169
176
  function getCoveringTaxon(taxa) {
170
177
  if (taxa.length === 1 && !taxa[0].has('gbif')) {
171
178
  return "".concat(PREFIX, "taxon/").concat(taxa[0].get('id'));
@@ -222,6 +229,17 @@ function makeScientificNameUri(id) {
222
229
  var resource = id.split(':').slice(0, -1).join(':');
223
230
  return { '@id': "".concat(PREFIX, "resource/").concat(resource, "#").concat(id) };
224
231
  }
232
+ function makeTaxonRankUri(rank) {
233
+ if (GBIF_VOCAB_RANKS.includes(rank)) {
234
+ return { '@id': "http://rs.gbif.org/vocabulary/gbif/rank/".concat(rank) };
235
+ }
236
+ else {
237
+ return rank;
238
+ }
239
+ }
240
+ function makeTaxonomicStatusUri(status) {
241
+ return { '@id': STATUSES[status] };
242
+ }
225
243
  function makeLinkedDataForAuthor(author) {
226
244
  var node = {
227
245
  '@id': "".concat(PREFIX, "author/").concat(author.get('id')),
@@ -246,7 +264,7 @@ function makeLinkedDataForPlace(place) {
246
264
  }
247
265
  function makeLinkedDataForPublisher(publisher) {
248
266
  var node = {
249
- '@id': "".concat(PREFIX, "author/").concat(publisher.get('id')),
267
+ '@id': "".concat(PREFIX, "publisher/").concat(publisher.get('id')),
250
268
  '@type': 'foaf:Organization',
251
269
  'foaf:name': publisher.get('display_name')
252
270
  };
@@ -262,7 +280,7 @@ function makeLinkedDataForTaxon(taxon) {
262
280
  'dwc:scientificName': taxon.get('display_name'),
263
281
  };
264
282
  if (taxon.has('rank')) {
265
- node['dwc:taxonRank'] = taxon.get('rank');
283
+ node['dwc:taxonRank'] = makeTaxonRankUri(taxon.get('rank'));
266
284
  }
267
285
  var ids = [];
268
286
  if (taxon.has('qid')) {
@@ -291,7 +309,7 @@ function makeLinkedDataForTaxa(files) {
291
309
  node['dwc:parentNameUsageID'] = { '@id': "".concat(PREFIX, "taxon/T141") };
292
310
  }
293
311
  for (var i = 0; i < ancestors.length; i++) {
294
- gbifTaxa[ancestors[i]] = __assign(__assign({}, makeGbifUri(ancestors[i])), { 'dwc:taxonRank': GBIF_RANKS[i], 'dwc:parentNameUsageID': i ? makeGbifUri(ancestors[i - 1]) : { '@id': "".concat(PREFIX, "taxon/T141") } });
312
+ gbifTaxa[ancestors[i]] = __assign(__assign({}, makeGbifUri(ancestors[i])), { 'dwc:taxonRank': makeTaxonRankUri(GBIF_RANKS[i]), 'dwc:parentNameUsageID': i ? makeGbifUri(ancestors[i - 1]) : { '@id': "".concat(PREFIX, "taxon/T141") } });
295
313
  }
296
314
  if (taxon.has('children_gbif')) {
297
315
  var children = taxon.get('children_gbif');
@@ -299,7 +317,7 @@ function makeLinkedDataForTaxa(files) {
299
317
  var childRank = GBIF_RANKS[ancestors.length];
300
318
  for (var _c = 0, children_1 = children; _c < children_1.length; _c++) {
301
319
  var child = children_1[_c];
302
- gbifTaxa[child] = __assign(__assign({}, makeGbifUri(child)), { 'dwc:taxonRank': childRank });
320
+ gbifTaxa[child] = __assign(__assign({}, makeGbifUri(child)), { 'dwc:taxonRank': makeTaxonRankUri(childRank) });
303
321
  }
304
322
  }
305
323
  nodes.push(node);
@@ -315,6 +333,12 @@ function makeLinkedDataForScientificName(name) {
315
333
  node[DWC_FIELDS[field]] = value;
316
334
  }
317
335
  }
336
+ if (GBIF_VOCAB_RANKS.includes(name.taxonRank)) {
337
+ node[DWC_FIELDS.taxonRank] = makeTaxonRankUri(name.taxonRank);
338
+ }
339
+ if (name.taxonomicStatus) {
340
+ node[DWC_FIELDS.taxonomicStatus] = makeTaxonomicStatusUri(name.taxonomicStatus);
341
+ }
318
342
  if (name.acceptedNameUsageID) {
319
343
  node['dwc:acceptedNameUsageID'] = makeScientificNameUri(name.acceptedNameUsageID);
320
344
  }
@@ -436,7 +460,10 @@ function makeLinkedDataForWork(work, files) {
436
460
  node['dcterms:language'] = languages.map(function (language) { return ({ '@id': "http://id.loc.gov/vocabulary/iso639-1/".concat(language) }); });
437
461
  if (work.has('title')) {
438
462
  var title = work.get('title');
439
- node['dcterms:title'] = title.map(function (value, i) { return ({ '@value': value, '@lang': languages[i] }); });
463
+ if (title.length > languages.length) {
464
+ title.splice(0, title.length, title.join('; '));
465
+ }
466
+ node['dcterms:title'] = title.map(function (value, i) { return ({ '@value': value, '@language': languages[i] }); });
440
467
  }
441
468
  if (work.has('pages') && work.get('pages').includes('-')) {
442
469
  var containers = work.has('part_of') ? work.get('part_of').map(function (id) { return files.catalog.get(id); }) : [];
@@ -462,7 +489,7 @@ function makeLinkedDataForWork(work, files) {
462
489
  var urls = work.get('url');
463
490
  var handle = urls.find(function (url) { return url.startsWith(HANDLE_PREFIX); });
464
491
  if (handle) {
465
- node['bibo:handle'] = handle.slice(HANDLE_PREFIX.length);
492
+ node['bibo:handle'] = { '@id': handle.slice(HANDLE_PREFIX.length) };
466
493
  }
467
494
  node['schema:url'] = urls.map(function (url) { return ({ '@id': url }); });
468
495
  }
@@ -474,7 +501,18 @@ function makeLinkedDataForWork(work, files) {
474
501
  node['schema:archivedAt'] = work.get('archive_url').map(function (url) { return ({ '@id': url }); });
475
502
  }
476
503
  if (work.has('date')) {
477
- node['dcterms:issued'] = work.get('date');
504
+ var date = work.get('date');
505
+ var dateType = 'rdfs:Literal';
506
+ if (date.match(/^\d{4}-\d{2}-\d{2}$/)) {
507
+ dateType = 'xsd:date';
508
+ }
509
+ else if (date.match(/^\d{4}-\d{2}$/)) {
510
+ dateType = 'xsd:gYearMonth';
511
+ }
512
+ else if (date.match(/^\d{4}$/)) {
513
+ dateType = 'xsd:gYear';
514
+ }
515
+ node['dcterms:issued'] = { '@value': work.get('date'), '@type': dateType };
478
516
  }
479
517
  if (work.has('publisher')) {
480
518
  var publishers = mapEntities(work.get('publisher'), files.publishers.entities).map(function (publisher) { return ({ '@id': "".concat(PREFIX, "publisher/").concat(publisher.get('id')) }); });
@@ -519,9 +557,15 @@ function makeLinkedDataForWork(work, files) {
519
557
  else if (range.length === 2) {
520
558
  node['bibo:pages'] = pages;
521
559
  var _a = range.map(function (part) { return parseInt(part); }), start = _a[0], end = _a[1];
522
- node['bibo:pageStart'] = start;
523
- node['bibo:pageEnd'] = end;
524
- node['bibo:numPages'] = end - start + 1;
560
+ if (!isNaN(start)) {
561
+ node['bibo:pageStart'] = start;
562
+ }
563
+ if (!isNaN(end)) {
564
+ node['bibo:pageEnd'] = end;
565
+ }
566
+ if (!isNaN(start) && !isNaN(end)) {
567
+ node['bibo:numPages'] = end - start + 1;
568
+ }
525
569
  }
526
570
  else {
527
571
  node['bibo:pages'] = pages;
@@ -582,7 +626,7 @@ function makeLinkedDataForWorks(files) {
582
626
  var originals = work.get('version_of');
583
627
  var originalLanguages_1 = originals.map(function (id) { return works.get(id).get('language').join(); });
584
628
  node['bibo:translationOf'] = originals.filter(function (_, i) { return originalLanguages_1[i] !== language.join(); }).map(makeWorkUri);
585
- node['dcterms:isVersionOf'] = originals.map(makeWorkUri);
629
+ node['dcterms:isVersionOf'] = originals.filter(function (id) { return work.get('id') !== id; }).map(makeWorkUri);
586
630
  }
587
631
  var resourceIndex = 1;
588
632
  var resourceId = void 0;
File without changes
@@ -25,7 +25,7 @@ var Author = /** @class */ (function (_super) {
25
25
  name: { required: true, multiple: true },
26
26
  id: { required: true, multiple: false, format: value_1.FORMATS.AUTHOR_ID },
27
27
  qid: { required: false, multiple: false, format: value_1.FORMATS.QID },
28
- display_name: { required: false, multiple: false },
28
+ display_name: { required: true, multiple: false },
29
29
  full_names: { required: false, multiple: true },
30
30
  duplicate_of: { required: false, multiple: false, format: value_1.FORMATS.AUTHOR_ID }
31
31
  }) || this;
@@ -25,7 +25,7 @@ var Place = /** @class */ (function (_super) {
25
25
  name: { required: true, multiple: false },
26
26
  id: { required: true, multiple: false, format: value_1.FORMATS.PLACE_ID },
27
27
  qid: { required: false, multiple: false, format: value_1.FORMATS.QID },
28
- display_name: { required: false, multiple: false },
28
+ display_name: { required: true, multiple: false },
29
29
  duplicate_of: { required: false, multiple: false, format: value_1.FORMATS.PLACE_ID }
30
30
  }) || this;
31
31
  }
@@ -25,7 +25,7 @@ var Publisher = /** @class */ (function (_super) {
25
25
  name: { required: true, multiple: true },
26
26
  id: { required: true, multiple: false, format: value_1.FORMATS.PUBLISHER_ID },
27
27
  qid: { required: false, multiple: false, format: value_1.FORMATS.QID },
28
- display_name: { required: false, multiple: false },
28
+ display_name: { required: true, multiple: false },
29
29
  full_names: { required: false, multiple: true },
30
30
  duplicate_of: { required: false, multiple: false, format: value_1.FORMATS.PUBLISHER_ID }
31
31
  }) || this;
@@ -102,7 +102,8 @@ var DWC_RANKS = [
102
102
  var FLAGS = [
103
103
  'MISSING_TAXA',
104
104
  'MISSING_PARENT_TAXA',
105
- 'MISSING_SYNONYMS'
105
+ 'MISSING_SYNONYMS',
106
+ 'MISSING_AUTHORSHIP'
106
107
  ];
107
108
  var TAXONOMIC_STATUS = {
108
109
  '>': 'incorrect',
@@ -145,7 +146,7 @@ var NAME_PATTERN = new RegExp('^' +
145
146
  // $2 optional author citation
146
147
  '(?: ' +
147
148
  // but not auct(t)., etc.
148
- '(?!auctt?\\.|(?:syn|comb|sp|spec|nom)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.)|in part|partim)' +
149
+ '(?!auctt?\\.|(?:syn|comb|sp|spec|nom|gen|subgen)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.)|in part|partim)' +
149
150
  '(' +
150
151
  // $2.1 anything in parentheses, followed by optional revising author(s)
151
152
  '\\(.+?\\)(?:\\s+' + SIMPLE_AUTHOR_PATTERN + ')?' +
@@ -164,12 +165,12 @@ var NAME_PATTERN = new RegExp('^' +
164
165
  * $1 genus+subgenus (+ trailing space): (?:([A-Z]\S+) (?:\(([A-Z]\S+?)\) )?)?
165
166
  * $1.1 genus: ((?:x )?[A-Z]\S+)
166
167
  * $1.2 subgenus: (?:\(([A-Z]\S+?)\) )?
167
- * $2 species: (x [a-z]+|[a-z][^\s.]+(?: x [a-z]+)?|[A-Z][a-z]+_[a-z]+ x [A-Z][a-z]+_[a-z]+)
168
- * $2a: x [a-z]+
169
- * $2b hybrid: [a-z][^\s.]+(?: x [a-z]+)?
170
- * $2c intergeneric hybrid: [A-Z][a-z]+_[a-z]+ x [A-Z][a-z]+_[a-z]+
168
+ * $2 species: (x [a-z-]+|[a-z-][^\s.]+(?: x [a-z-]+)?|[A-Z][a-z]+_[a-z-]+ x [A-Z][a-z]+_[a-z-]+)
169
+ * $2a: x [a-z-]+
170
+ * $2b hybrid: [a-z-][^\s.]+(?: x [a-z-]+)?
171
+ * $2c intergeneric hybrid: [A-Z][a-z]+_[a-z-]+ x [A-Z][a-z]+_[a-z-]+
171
172
  */
172
- 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]+)(?= |$)/;
173
+ 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-]+)(?= |$)/;
173
174
  function compareRanks(a, b) {
174
175
  return RANKS.indexOf(a) - RANKS.indexOf(b);
175
176
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@larsgw/formica",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
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",
@@ -62,7 +62,6 @@ const SCOPES: Record<string, [string, string]> = {
62
62
  'queens': ['dwc:caste', 'queen'],
63
63
  'workers': ['dwc:caste', 'worker'],
64
64
  'soldiers': ['dwc:caste', 'soldier'],
65
- 'alates': ['dwc:caste', 'alate'],
66
65
  'alatae': ['dwc:caste', 'alate'],
67
66
  'apterae': ['dwc:caste', 'aptera'],
68
67
  'viviparae': ['dwc:caste', 'vivipara'],
@@ -85,7 +84,9 @@ const PREFIXES = {
85
84
  'dwciri': 'http://rs.tdwg.org/dwc/iri/',
86
85
  'foaf': 'http://xmlns.com/foaf/0.1/',
87
86
  'owl': 'http://www.w3.org/2002/07/owl#',
87
+ 'rdfs': 'http://www.w3.org/2000/01/rdf-schema#',
88
88
  'schema': 'https://schema.org/',
89
+ 'xsd': 'http://www.w3.org/2001/XMLSchema#',
89
90
  }
90
91
  const DWC_FIELDS: Record<string, string> = {
91
92
  scientificName: 'dwc:scientificName',
@@ -100,6 +101,12 @@ const DWC_FIELDS: Record<string, string> = {
100
101
  verbatimIdentification: 'dwc:verbatimIdentification',
101
102
  }
102
103
  const GBIF_RANKS = ['kingdom', 'phylum', 'class', 'order', 'family', 'genus', 'species', 'subspecies']
104
+ 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']
105
+ const STATUSES: Record<string, string> = {
106
+ 'accepted': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/accepted',
107
+ 'heterotypic synonym': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/heterotypicSynonym',
108
+ 'synonym': 'http://rs.gbif.org/vocabulary/gbif/taxonomicStatus/synonym',
109
+ }
103
110
 
104
111
  function getCoveringTaxon (taxa: catalog.Entity[]): string|null {
105
112
  if (taxa.length === 1 && !taxa[0].has('gbif')) {
@@ -165,6 +172,18 @@ function makeScientificNameUri (id: string): NodeObject {
165
172
  return { '@id': `${PREFIX}resource/${resource}#${id}` }
166
173
  }
167
174
 
175
+ function makeTaxonRankUri (rank: string): NodeObject|string {
176
+ if (GBIF_VOCAB_RANKS.includes(rank)) {
177
+ return { '@id': `http://rs.gbif.org/vocabulary/gbif/rank/${rank}` }
178
+ } else {
179
+ return rank
180
+ }
181
+ }
182
+
183
+ function makeTaxonomicStatusUri (status: string): NodeObject {
184
+ return { '@id': STATUSES[status] as string }
185
+ }
186
+
168
187
  function makeLinkedDataForAuthor (author: catalog.Entity): NodeObject {
169
188
  const node: NodeObject = {
170
189
  '@id': `${PREFIX}author/${author.get('id')}`,
@@ -195,7 +214,7 @@ function makeLinkedDataForPlace (place: catalog.Entity): NodeObject {
195
214
 
196
215
  function makeLinkedDataForPublisher (publisher: catalog.Entity): NodeObject {
197
216
  const node: NodeObject = {
198
- '@id': `${PREFIX}author/${publisher.get('id')}`,
217
+ '@id': `${PREFIX}publisher/${publisher.get('id')}`,
199
218
  '@type': 'foaf:Organization',
200
219
  'foaf:name': publisher.get('display_name')
201
220
  }
@@ -215,7 +234,7 @@ function makeLinkedDataForTaxon (taxon: catalog.Entity): NodeObject {
215
234
  }
216
235
 
217
236
  if (taxon.has('rank')) {
218
- node['dwc:taxonRank'] = taxon.get('rank')
237
+ node['dwc:taxonRank'] = makeTaxonRankUri(taxon.get('rank') as string)
219
238
  }
220
239
 
221
240
  const ids = []
@@ -249,7 +268,7 @@ function makeLinkedDataForTaxa (files: Catalog): NodeObject[] {
249
268
  for (let i = 0; i < ancestors.length; i++) {
250
269
  gbifTaxa[ancestors[i]] = {
251
270
  ...makeGbifUri(ancestors[i]),
252
- 'dwc:taxonRank': GBIF_RANKS[i],
271
+ 'dwc:taxonRank': makeTaxonRankUri(GBIF_RANKS[i]),
253
272
  'dwc:parentNameUsageID': i ? makeGbifUri(ancestors[i - 1]) : { '@id': `${PREFIX}taxon/T141` }
254
273
  }
255
274
  }
@@ -262,7 +281,7 @@ function makeLinkedDataForTaxa (files: Catalog): NodeObject[] {
262
281
  for (const child of children) {
263
282
  gbifTaxa[child] = {
264
283
  ...makeGbifUri(child),
265
- 'dwc:taxonRank': childRank,
284
+ 'dwc:taxonRank': makeTaxonRankUri(childRank),
266
285
  }
267
286
  }
268
287
  }
@@ -288,6 +307,14 @@ function makeLinkedDataForScientificName (name: AmendedTaxon): NodeObject {
288
307
  }
289
308
  }
290
309
 
310
+ if (GBIF_VOCAB_RANKS.includes(name.taxonRank)) {
311
+ node[DWC_FIELDS.taxonRank] = makeTaxonRankUri(name.taxonRank)
312
+ }
313
+
314
+ if (name.taxonomicStatus) {
315
+ node[DWC_FIELDS.taxonomicStatus] = makeTaxonomicStatusUri(name.taxonomicStatus)
316
+ }
317
+
291
318
  if (name.acceptedNameUsageID) {
292
319
  node['dwc:acceptedNameUsageID'] = makeScientificNameUri(name.acceptedNameUsageID)
293
320
  }
@@ -435,7 +462,10 @@ function makeLinkedDataForWork (work: catalog.Entity, files: Catalog): NodeObjec
435
462
 
436
463
  if (work.has('title')) {
437
464
  const title = work.get('title') as string[]
438
- node['dcterms:title'] = title.map((value, i) => ({ '@value': value, '@lang': languages[i] }))
465
+ if (title.length > languages.length) {
466
+ title.splice(0, title.length, title.join('; '))
467
+ }
468
+ node['dcterms:title'] = title.map((value, i) => ({ '@value': value, '@language': languages[i] }))
439
469
  }
440
470
 
441
471
  if (work.has('pages') && (work.get('pages') as string).includes('-')) {
@@ -462,7 +492,7 @@ function makeLinkedDataForWork (work: catalog.Entity, files: Catalog): NodeObjec
462
492
 
463
493
  const handle = urls.find(url => url.startsWith(HANDLE_PREFIX))
464
494
  if (handle) {
465
- node['bibo:handle'] = handle.slice(HANDLE_PREFIX.length)
495
+ node['bibo:handle'] = { '@id': handle.slice(HANDLE_PREFIX.length) }
466
496
  }
467
497
 
468
498
  node['schema:url'] = urls.map(url => ({ '@id': url }))
@@ -479,7 +509,17 @@ function makeLinkedDataForWork (work: catalog.Entity, files: Catalog): NodeObjec
479
509
  }
480
510
 
481
511
  if (work.has('date')) {
482
- node['dcterms:issued'] = work.get('date')
512
+ const date = work.get('date') as string
513
+ let dateType = 'rdfs:Literal'
514
+ if (date.match(/^\d{4}-\d{2}-\d{2}$/)) {
515
+ dateType = 'xsd:date'
516
+ } else if (date.match(/^\d{4}-\d{2}$/)) {
517
+ dateType = 'xsd:gYearMonth'
518
+ } else if (date.match(/^\d{4}$/)) {
519
+ dateType = 'xsd:gYear'
520
+ }
521
+
522
+ node['dcterms:issued'] = { '@value': work.get('date'), '@type': dateType }
483
523
  }
484
524
 
485
525
  if (work.has('publisher')) {
@@ -531,9 +571,15 @@ function makeLinkedDataForWork (work: catalog.Entity, files: Catalog): NodeObjec
531
571
  node['bibo:pages'] = pages
532
572
 
533
573
  const [start, end] = range.map(part => parseInt(part))
534
- node['bibo:pageStart'] = start
535
- node['bibo:pageEnd'] = end
536
- node['bibo:numPages'] = end - start + 1
574
+ if (!isNaN(start)) {
575
+ node['bibo:pageStart'] = start
576
+ }
577
+ if (!isNaN(end)) {
578
+ node['bibo:pageEnd'] = end
579
+ }
580
+ if (!isNaN(start) && !isNaN(end)) {
581
+ node['bibo:numPages'] = end - start + 1
582
+ }
537
583
  } else {
538
584
  node['bibo:pages'] = pages
539
585
  }
@@ -600,7 +646,7 @@ function makeLinkedDataForWorks (files: Catalog): NodeObject[] {
600
646
  const originals = work.get('version_of') as string[]
601
647
  const originalLanguages = originals.map(id => ((works.get(id) as catalog.Entity).get('language') as string[]).join())
602
648
  node['bibo:translationOf'] = originals.filter((_, i) => originalLanguages[i] !== language.join()).map(makeWorkUri)
603
- node['dcterms:isVersionOf'] = originals.map(makeWorkUri)
649
+ node['dcterms:isVersionOf'] = originals.filter(id => work.get('id') !== id).map(makeWorkUri)
604
650
  }
605
651
 
606
652
  let resourceIndex = 1
@@ -7,7 +7,7 @@ export class Author extends Entity {
7
7
  name: { required: true, multiple: true },
8
8
  id: { required: true, multiple: false, format: FORMATS.AUTHOR_ID },
9
9
  qid: { required: false, multiple: false, format: FORMATS.QID },
10
- display_name: { required: false, multiple: false },
10
+ display_name: { required: true, multiple: false },
11
11
  full_names: { required: false, multiple: true },
12
12
  duplicate_of: { required: false, multiple: false, format: FORMATS.AUTHOR_ID }
13
13
  })
@@ -7,7 +7,7 @@ export class Place extends Entity {
7
7
  name: { required: true, multiple: false },
8
8
  id: { required: true, multiple: false, format: FORMATS.PLACE_ID },
9
9
  qid: { required: false, multiple: false, format: FORMATS.QID },
10
- display_name: { required: false, multiple: false },
10
+ display_name: { required: true, multiple: false },
11
11
  duplicate_of: { required: false, multiple: false, format: FORMATS.PLACE_ID }
12
12
  })
13
13
  }
@@ -7,7 +7,7 @@ export class Publisher extends Entity {
7
7
  name: { required: true, multiple: true },
8
8
  id: { required: true, multiple: false, format: FORMATS.PUBLISHER_ID },
9
9
  qid: { required: false, multiple: false, format: FORMATS.QID },
10
- display_name: { required: false, multiple: false },
10
+ display_name: { required: true, multiple: false },
11
11
  full_names: { required: false, multiple: true },
12
12
  duplicate_of: { required: false, multiple: false, format: FORMATS.PUBLISHER_ID }
13
13
  })
@@ -58,7 +58,8 @@ const DWC_RANKS: DwcRank[] = [
58
58
  const FLAGS: ResourceFlag[] = [
59
59
  'MISSING_TAXA',
60
60
  'MISSING_PARENT_TAXA',
61
- 'MISSING_SYNONYMS'
61
+ 'MISSING_SYNONYMS',
62
+ 'MISSING_AUTHORSHIP'
62
63
  ]
63
64
 
64
65
  const TAXONOMIC_STATUS: Record<string, TaxonStatus> = {
@@ -108,7 +109,7 @@ const NAME_PATTERN = new RegExp(
108
109
  // $2 optional author citation
109
110
  '(?: ' +
110
111
  // but not auct(t)., etc.
111
- '(?!auctt?\\.|(?:syn|comb|sp|spec|nom)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.)|in part|partim)' +
112
+ '(?!auctt?\\.|(?:syn|comb|sp|spec|nom|gen|subgen)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.)|in part|partim)' +
112
113
  '(' +
113
114
  // $2.1 anything in parentheses, followed by optional revising author(s)
114
115
  '\\(.+?\\)(?:\\s+' + SIMPLE_AUTHOR_PATTERN + ')?' +
@@ -130,12 +131,12 @@ const NAME_PATTERN = new RegExp(
130
131
  * $1 genus+subgenus (+ trailing space): (?:([A-Z]\S+) (?:\(([A-Z]\S+?)\) )?)?
131
132
  * $1.1 genus: ((?:x )?[A-Z]\S+)
132
133
  * $1.2 subgenus: (?:\(([A-Z]\S+?)\) )?
133
- * $2 species: (x [a-z]+|[a-z][^\s.]+(?: x [a-z]+)?|[A-Z][a-z]+_[a-z]+ x [A-Z][a-z]+_[a-z]+)
134
- * $2a: x [a-z]+
135
- * $2b hybrid: [a-z][^\s.]+(?: x [a-z]+)?
136
- * $2c intergeneric hybrid: [A-Z][a-z]+_[a-z]+ x [A-Z][a-z]+_[a-z]+
134
+ * $2 species: (x [a-z-]+|[a-z-][^\s.]+(?: x [a-z-]+)?|[A-Z][a-z]+_[a-z-]+ x [A-Z][a-z]+_[a-z-]+)
135
+ * $2a: x [a-z-]+
136
+ * $2b hybrid: [a-z-][^\s.]+(?: x [a-z-]+)?
137
+ * $2c intergeneric hybrid: [A-Z][a-z]+_[a-z-]+ x [A-Z][a-z]+_[a-z-]+
137
138
  */
138
- 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]+)(?= |$)/
139
+ 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-]+)(?= |$)/
139
140
 
140
141
  function compareRanks (a: Rank, b: Rank): number {
141
142
  return RANKS.indexOf(a) - RANKS.indexOf(b)