@larsgw/formica 0.8.0 → 0.8.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,18 @@
1
+ ## [0.8.1](https://github.com/identification-resources/formica/compare/v0.8.0...v0.8.1) (2025-05-30)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **catalog:** fix aspects of linked data ([1b2964f](https://github.com/identification-resources/formica/commit/1b2964f46b8409c6a67f0475faf067a80734288c))
7
+ * **catalog:** set display_name to required, as documented ([3f36909](https://github.com/identification-resources/formica/commit/3f36909bbe6261cbee022139050875de37174611))
8
+
9
+
10
+ ### Features
11
+
12
+ * **resources:** add support for "(sub)gen. nov" pattern ([d2598eb](https://github.com/identification-resources/formica/commit/d2598ebfd2f80d9c2bcc00ce145b9fc229316e5b))
13
+
14
+
15
+
1
16
  # [0.8.0](https://github.com/identification-resources/formica/compare/v0.7.3...v0.8.0) (2025-05-07)
2
17
 
3
18
 
@@ -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;
@@ -145,7 +145,7 @@ var NAME_PATTERN = new RegExp('^' +
145
145
  // $2 optional author citation
146
146
  '(?: ' +
147
147
  // but not auct(t)., etc.
148
- '(?!auctt?\\.|(?:syn|comb|sp|spec|nom)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.)|in part|partim)' +
148
+ '(?!auctt?\\.|(?:syn|comb|sp|spec|nom|gen|subgen)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.)|in part|partim)' +
149
149
  '(' +
150
150
  // $2.1 anything in parentheses, followed by optional revising author(s)
151
151
  '\\(.+?\\)(?:\\s+' + SIMPLE_AUTHOR_PATTERN + ')?' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@larsgw/formica",
3
- "version": "0.8.0",
3
+ "version": "0.8.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",
@@ -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
  })
@@ -108,7 +108,7 @@ const NAME_PATTERN = new RegExp(
108
108
  // $2 optional author citation
109
109
  '(?: ' +
110
110
  // but not auct(t)., etc.
111
- '(?!auctt?\\.|(?:syn|comb|sp|spec|nom)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.)|in part|partim)' +
111
+ '(?!auctt?\\.|(?:syn|comb|sp|spec|nom|gen|subgen)\\. n(?:ov)?\\.|s(?:ens[.u]|\\.)|in part|partim)' +
112
112
  '(' +
113
113
  // $2.1 anything in parentheses, followed by optional revising author(s)
114
114
  '\\(.+?\\)(?:\\s+' + SIMPLE_AUTHOR_PATTERN + ')?' +