geonetwork-ui 2.3.0-dev.28c8df17 → 2.3.0-dev.376e0e90

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.
Files changed (114) hide show
  1. package/esm2022/libs/api/metadata-converter/src/index.mjs +5 -5
  2. package/esm2022/libs/api/metadata-converter/src/lib/base.converter.mjs +14 -0
  3. package/esm2022/libs/api/metadata-converter/src/lib/find-converter.mjs +15 -0
  4. package/esm2022/libs/api/metadata-converter/src/lib/gn4/atomic-operations.mjs +3 -3
  5. package/esm2022/libs/api/metadata-converter/src/lib/gn4/gn4.converter.mjs +52 -0
  6. package/esm2022/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.mjs +3 -3
  7. package/esm2022/libs/api/metadata-converter/src/lib/gn4/index.mjs +4 -0
  8. package/esm2022/libs/api/metadata-converter/src/lib/iso19115-3/index.mjs +2 -0
  9. package/esm2022/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.mjs +123 -0
  10. package/esm2022/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.mjs +116 -0
  11. package/esm2022/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.mjs +138 -0
  12. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/index.mjs +2 -0
  13. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.mjs +242 -0
  14. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/read-parts.mjs +58 -62
  15. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/utils/individual-name.mjs +18 -0
  16. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/utils/keyword.mapper.mjs +14 -0
  17. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/utils/role.mapper.mjs +48 -0
  18. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/utils/status.mapper.mjs +18 -0
  19. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/utils/update-frequency.mapper.mjs +64 -0
  20. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/write-parts.mjs +75 -58
  21. package/esm2022/libs/api/metadata-converter/src/lib/xml-utils.mjs +76 -14
  22. package/esm2022/libs/api/repository/src/lib/gn4/gn4-repository.mjs +4 -4
  23. package/esm2022/libs/common/domain/src/lib/model/record/metadata.model.mjs +1 -1
  24. package/esm2022/libs/feature/editor/src/lib/services/editor.service.mjs +8 -8
  25. package/fesm2022/geonetwork-ui.mjs +822 -245
  26. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  27. package/libs/api/metadata-converter/src/index.d.ts +4 -4
  28. package/libs/api/metadata-converter/src/index.d.ts.map +1 -1
  29. package/libs/api/metadata-converter/src/lib/{metadata-base.mapper.d.ts → base.converter.d.ts} +3 -3
  30. package/libs/api/metadata-converter/src/lib/base.converter.d.ts.map +1 -0
  31. package/libs/api/metadata-converter/src/lib/find-converter.d.ts +3 -0
  32. package/libs/api/metadata-converter/src/lib/find-converter.d.ts.map +1 -0
  33. package/libs/api/metadata-converter/src/lib/gn4/{gn4.metadata.mapper.d.ts → gn4.converter.d.ts} +5 -5
  34. package/libs/api/metadata-converter/src/lib/gn4/gn4.converter.d.ts.map +1 -0
  35. package/libs/api/metadata-converter/src/lib/gn4/index.d.ts +4 -0
  36. package/libs/api/metadata-converter/src/lib/gn4/index.d.ts.map +1 -0
  37. package/libs/api/metadata-converter/src/lib/iso19115-3/index.d.ts +2 -0
  38. package/libs/api/metadata-converter/src/lib/iso19115-3/index.d.ts.map +1 -0
  39. package/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.d.ts +9 -0
  40. package/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.d.ts.map +1 -0
  41. package/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.d.ts +20 -0
  42. package/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.d.ts.map +1 -0
  43. package/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.d.ts +21 -0
  44. package/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.d.ts.map +1 -0
  45. package/libs/api/metadata-converter/src/lib/iso19139/index.d.ts +2 -0
  46. package/libs/api/metadata-converter/src/lib/iso19139/index.d.ts.map +1 -0
  47. package/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.d.ts +11 -0
  48. package/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.d.ts.map +1 -0
  49. package/libs/api/metadata-converter/src/lib/iso19139/read-parts.d.ts +34 -6
  50. package/libs/api/metadata-converter/src/lib/iso19139/read-parts.d.ts.map +1 -1
  51. package/libs/api/metadata-converter/src/lib/iso19139/utils/individual-name.d.ts +8 -0
  52. package/libs/api/metadata-converter/src/lib/iso19139/utils/individual-name.d.ts.map +1 -0
  53. package/libs/api/metadata-converter/src/lib/iso19139/utils/keyword.mapper.d.ts.map +1 -0
  54. package/libs/api/metadata-converter/src/lib/iso19139/utils/role.mapper.d.ts.map +1 -0
  55. package/libs/api/metadata-converter/src/lib/iso19139/utils/status.mapper.d.ts.map +1 -0
  56. package/libs/api/metadata-converter/src/lib/iso19139/utils/update-frequency.mapper.d.ts.map +1 -0
  57. package/libs/api/metadata-converter/src/lib/iso19139/write-parts.d.ts +39 -3
  58. package/libs/api/metadata-converter/src/lib/iso19139/write-parts.d.ts.map +1 -1
  59. package/libs/api/metadata-converter/src/lib/xml-utils.d.ts +14 -1
  60. package/libs/api/metadata-converter/src/lib/xml-utils.d.ts.map +1 -1
  61. package/libs/api/repository/src/lib/gn4/gn4-repository.d.ts +2 -2
  62. package/libs/api/repository/src/lib/gn4/gn4-repository.d.ts.map +1 -1
  63. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts +6 -4
  64. package/libs/common/domain/src/lib/model/record/metadata.model.d.ts.map +1 -1
  65. package/libs/feature/editor/src/lib/services/editor.service.d.ts.map +1 -1
  66. package/package.json +1 -1
  67. package/src/libs/api/metadata-converter/src/index.ts +4 -4
  68. package/src/libs/api/metadata-converter/src/lib/{metadata-base.mapper.ts → base.converter.ts} +2 -2
  69. package/src/libs/api/metadata-converter/src/lib/find-converter.ts +16 -0
  70. package/src/libs/api/metadata-converter/src/lib/fixtures/generic.records.ts +32 -5
  71. package/src/libs/api/metadata-converter/src/lib/fixtures/geo2france.records.ts +11 -4
  72. package/src/libs/api/metadata-converter/src/lib/fixtures/geocat-ch.records.ts +33 -7
  73. package/src/libs/api/metadata-converter/src/lib/fixtures/metawal.records.ts +580 -0
  74. package/src/libs/api/metadata-converter/src/lib/gn4/atomic-operations.ts +2 -2
  75. package/src/libs/api/metadata-converter/src/lib/gn4/{gn4.metadata.mapper.ts → gn4.converter.ts} +2 -2
  76. package/src/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +2 -2
  77. package/src/libs/api/metadata-converter/src/lib/gn4/index.ts +3 -0
  78. package/src/libs/api/metadata-converter/src/lib/iso19115-3/index.ts +1 -0
  79. package/src/libs/api/metadata-converter/src/lib/iso19115-3/iso19115-3.converter.ts +176 -0
  80. package/src/libs/api/metadata-converter/src/lib/iso19115-3/read-parts.ts +329 -0
  81. package/src/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts +513 -0
  82. package/src/libs/api/metadata-converter/src/lib/iso19139/index.ts +1 -0
  83. package/src/libs/api/metadata-converter/src/lib/iso19139/iso19139.converter.ts +327 -0
  84. package/src/libs/api/metadata-converter/src/lib/iso19139/read-parts.ts +121 -82
  85. package/src/libs/api/metadata-converter/src/lib/iso19139/utils/individual-name.ts +20 -0
  86. package/src/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts +175 -95
  87. package/src/libs/api/metadata-converter/src/lib/xml-utils.ts +84 -16
  88. package/src/libs/api/repository/src/lib/gn4/gn4-repository.ts +2 -2
  89. package/src/libs/common/domain/src/lib/model/record/metadata.model.ts +9 -4
  90. package/src/libs/feature/editor/src/lib/services/editor.service.ts +27 -16
  91. package/esm2022/libs/api/metadata-converter/src/lib/gn4/gn4.metadata.mapper.mjs +0 -52
  92. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/codelists/keyword.mapper.mjs +0 -14
  93. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/codelists/role.mapper.mjs +0 -48
  94. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/codelists/status.mapper.mjs +0 -18
  95. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/codelists/update-frequency.mapper.mjs +0 -64
  96. package/esm2022/libs/api/metadata-converter/src/lib/iso19139/converter.mjs +0 -130
  97. package/esm2022/libs/api/metadata-converter/src/lib/metadata-base.mapper.mjs +0 -14
  98. package/libs/api/metadata-converter/src/lib/gn4/gn4.metadata.mapper.d.ts.map +0 -1
  99. package/libs/api/metadata-converter/src/lib/iso19139/codelists/keyword.mapper.d.ts.map +0 -1
  100. package/libs/api/metadata-converter/src/lib/iso19139/codelists/role.mapper.d.ts.map +0 -1
  101. package/libs/api/metadata-converter/src/lib/iso19139/codelists/status.mapper.d.ts.map +0 -1
  102. package/libs/api/metadata-converter/src/lib/iso19139/codelists/update-frequency.mapper.d.ts.map +0 -1
  103. package/libs/api/metadata-converter/src/lib/iso19139/converter.d.ts +0 -4
  104. package/libs/api/metadata-converter/src/lib/iso19139/converter.d.ts.map +0 -1
  105. package/libs/api/metadata-converter/src/lib/metadata-base.mapper.d.ts.map +0 -1
  106. package/src/libs/api/metadata-converter/src/lib/iso19139/converter.ts +0 -196
  107. /package/libs/api/metadata-converter/src/lib/iso19139/{codelists → utils}/keyword.mapper.d.ts +0 -0
  108. /package/libs/api/metadata-converter/src/lib/iso19139/{codelists → utils}/role.mapper.d.ts +0 -0
  109. /package/libs/api/metadata-converter/src/lib/iso19139/{codelists → utils}/status.mapper.d.ts +0 -0
  110. /package/libs/api/metadata-converter/src/lib/iso19139/{codelists → utils}/update-frequency.mapper.d.ts +0 -0
  111. /package/src/libs/api/metadata-converter/src/lib/iso19139/{codelists → utils}/keyword.mapper.ts +0 -0
  112. /package/src/libs/api/metadata-converter/src/lib/iso19139/{codelists → utils}/role.mapper.ts +0 -0
  113. /package/src/libs/api/metadata-converter/src/lib/iso19139/{codelists → utils}/status.mapper.ts +0 -0
  114. /package/src/libs/api/metadata-converter/src/lib/iso19139/{codelists → utils}/update-frequency.mapper.ts +0 -0
@@ -36,6 +36,7 @@ import {
36
36
  } from '../xml-utils'
37
37
  import {
38
38
  ChainableFunction,
39
+ combine,
39
40
  fallback,
40
41
  filterArray,
41
42
  getAtIndex,
@@ -47,8 +48,9 @@ import {
47
48
  } from '../function-utils'
48
49
  import format from 'date-fns/format'
49
50
  import { readKind } from './read-parts'
51
+ import { namePartsToFull } from './utils/individual-name'
50
52
 
51
- function writeCharacterString(
53
+ export function writeCharacterString(
52
54
  text: string
53
55
  ): ChainableFunction<XmlElement, XmlElement> {
54
56
  return tap(
@@ -56,7 +58,9 @@ function writeCharacterString(
56
58
  )
57
59
  }
58
60
 
59
- function writeLinkage(url: URL): ChainableFunction<XmlElement, XmlElement> {
61
+ export function writeLinkage(
62
+ url: URL
63
+ ): ChainableFunction<XmlElement, XmlElement> {
60
64
  return tap(
61
65
  pipe(
62
66
  findNestedChildOrCreate('gmd:linkage', 'gmd:URL'),
@@ -65,7 +69,7 @@ function writeLinkage(url: URL): ChainableFunction<XmlElement, XmlElement> {
65
69
  )
66
70
  }
67
71
 
68
- function writeAnchor(
72
+ export function writeAnchor(
69
73
  url: URL,
70
74
  text?: string
71
75
  ): ChainableFunction<XmlElement, XmlElement> {
@@ -78,7 +82,9 @@ function writeAnchor(
78
82
  )
79
83
  }
80
84
 
81
- function writeDateTime(date: Date): ChainableFunction<XmlElement, XmlElement> {
85
+ export function writeDateTime(
86
+ date: Date
87
+ ): ChainableFunction<XmlElement, XmlElement> {
82
88
  return tap(
83
89
  pipe(
84
90
  findChildOrCreate('gco:DateTime'),
@@ -87,7 +93,9 @@ function writeDateTime(date: Date): ChainableFunction<XmlElement, XmlElement> {
87
93
  )
88
94
  }
89
95
 
90
- function writeDate(date: Date): ChainableFunction<XmlElement, XmlElement> {
96
+ export function writeDate(
97
+ date: Date
98
+ ): ChainableFunction<XmlElement, XmlElement> {
91
99
  return tap(
92
100
  pipe(
93
101
  findChildOrCreate('gco:Date'),
@@ -96,7 +104,7 @@ function writeDate(date: Date): ChainableFunction<XmlElement, XmlElement> {
96
104
  )
97
105
  }
98
106
 
99
- function getProgressCode(status: RecordStatus): string {
107
+ export function getProgressCode(status: RecordStatus): string {
100
108
  switch (status) {
101
109
  case 'completed':
102
110
  return 'completed'
@@ -115,7 +123,7 @@ function getProgressCode(status: RecordStatus): string {
115
123
  }
116
124
  }
117
125
 
118
- function getRoleCode(role: Role): string {
126
+ export function getRoleCode(role: Role): string {
119
127
  switch (role) {
120
128
  case 'author':
121
129
  return 'author'
@@ -164,7 +172,7 @@ function getRoleCode(role: Role): string {
164
172
  }
165
173
  }
166
174
 
167
- function getDistributionProtocol(
175
+ export function getDistributionProtocol(
168
176
  distribution: DatasetServiceDistribution
169
177
  ): string {
170
178
  switch (distribution.accessServiceProtocol.toLowerCase()) {
@@ -179,7 +187,7 @@ function getDistributionProtocol(
179
187
  }
180
188
  }
181
189
 
182
- function getMaintenanceFrequencyCode(
190
+ export function getMaintenanceFrequencyCode(
183
191
  updateFrequency: UpdateFrequencyCode
184
192
  ): string | null {
185
193
  switch (updateFrequency) {
@@ -198,7 +206,7 @@ function getMaintenanceFrequencyCode(
198
206
  }
199
207
  }
200
208
 
201
- function getISODuration(updateFrequency: UpdateFrequencyCustom): string {
209
+ export function getISODuration(updateFrequency: UpdateFrequencyCustom): string {
202
210
  const duration = {
203
211
  years: 0,
204
212
  months: 0,
@@ -227,19 +235,61 @@ function getISODuration(updateFrequency: UpdateFrequencyCustom): string {
227
235
  return `P${duration.years}Y${duration.months}M${duration.days}D${hours}`
228
236
  }
229
237
 
230
- function appendResponsibleParty(contact: Individual) {
231
- const name =
232
- contact.lastName && contact.firstName
233
- ? `${contact.firstName} ${contact.lastName}`
234
- : contact.lastName || contact.firstName || null
238
+ export function appendResponsibleParty(contact: Individual) {
239
+ const fullName = namePartsToFull(contact.firstName, contact.lastName)
240
+
241
+ const createAddress = pipe(
242
+ createElement('gmd:address'),
243
+ createChild('gmd:CI_Address'),
244
+ appendChildren(
245
+ pipe(
246
+ createElement('gmd:electronicMailAddress'),
247
+ writeCharacterString(contact.email)
248
+ )
249
+ ),
250
+ contact.address
251
+ ? appendChildren(
252
+ pipe(
253
+ createElement('gmd:deliveryPoint'),
254
+ writeCharacterString(contact.address)
255
+ )
256
+ )
257
+ : noop
258
+ )
259
+
260
+ const createContact = pipe(
261
+ createElement('gmd:contactInfo'),
262
+ createChild('gmd:CI_Contact'),
263
+ contact.phone
264
+ ? appendChildren(
265
+ pipe(
266
+ createElement('gmd:phone'),
267
+ createChild('gmd:CI_Telephone'),
268
+ createChild('gmd:voice'),
269
+ writeCharacterString(contact.phone)
270
+ )
271
+ )
272
+ : noop,
273
+ appendChildren(createAddress),
274
+ 'website' in contact.organization
275
+ ? appendChildren(
276
+ pipe(
277
+ createElement('gmd:onlineResource'),
278
+ createChild('gmd:CI_OnlineResource'),
279
+ writeLinkage(contact.organization.website)
280
+ )
281
+ )
282
+ : noop
283
+ )
284
+
235
285
  return appendChildren(
236
286
  pipe(
237
287
  createElement('gmd:CI_ResponsibleParty'),
238
- name
288
+ fullName
239
289
  ? appendChildren(
240
290
  pipe(
241
291
  createElement('gmd:individualName'),
242
- writeCharacterString(name)
292
+ writeCharacterString(fullName)
243
293
  )
244
294
  )
245
295
  : noop,
@@ -256,27 +306,7 @@ function appendResponsibleParty(contact: Individual) {
256
306
  createElement('gmd:organisationName'),
257
307
  writeCharacterString(contact.organization.name)
258
308
  ),
259
- pipe(
260
- createElement('gmd:contactInfo'),
261
- createChild('gmd:CI_Contact'),
262
- appendChildren(
263
- pipe(
264
- createElement('gmd:address'),
265
- createChild('gmd:CI_Address'),
266
- createChild('gmd:electronicMailAddress'),
267
- writeCharacterString(contact.email)
268
- )
269
- ),
270
- 'website' in contact.organization
271
- ? appendChildren(
272
- pipe(
273
- createElement('gmd:onlineResource'),
274
- createChild('gmd:CI_OnlineResource'),
275
- writeLinkage(contact.organization.website)
276
- )
277
- )
278
- : noop
279
- ),
309
+ createContact,
280
310
  pipe(
281
311
  createElement('gmd:role'),
282
312
  createChild('gmd:CI_RoleCode'),
@@ -291,7 +321,7 @@ function appendResponsibleParty(contact: Individual) {
291
321
  )
292
322
  }
293
323
 
294
- function updateCitationDate(
324
+ export function updateCitationDate(
295
325
  date: Date,
296
326
  type: 'revision' | 'creation' | 'publication'
297
327
  ) {
@@ -311,8 +341,8 @@ function updateCitationDate(
311
341
  )
312
342
  }
313
343
 
314
- function appendCitationDate(
315
- date,
344
+ export function appendCitationDate(
345
+ date: Date,
316
346
  type: 'revision' | 'creation' | 'publication'
317
347
  ) {
318
348
  return appendChildren(
@@ -335,12 +365,12 @@ function appendCitationDate(
335
365
  )
336
366
  }
337
367
 
338
- function removeKeywords() {
368
+ export function removeKeywords() {
339
369
  return removeChildren(pipe(findNestedElements('gmd:descriptiveKeywords')))
340
370
  }
341
371
 
342
372
  // returns a <gmd:thesaurusName> element
343
- function createThesaurus(thesaurus: KeywordThesaurus) {
373
+ export function createThesaurus(thesaurus: KeywordThesaurus) {
344
374
  return pipe(
345
375
  createElement('gmd:thesaurusName'),
346
376
  createChild('gmd:CI_Citation'),
@@ -365,14 +395,15 @@ function createThesaurus(thesaurus: KeywordThesaurus) {
365
395
  )
366
396
  }
367
397
 
368
- function appendKeywords(keywords: Keyword[]) {
398
+ export function appendKeywords(keywords: Keyword[]) {
399
+ // keywords are grouped by thesaurus if they have one, otherwise by type
369
400
  const keywordsByThesaurus: Keyword[][] = keywords.reduce((acc, keyword) => {
370
401
  const thesaurusId = keyword.thesaurus?.id
371
402
  const type = keyword.type
372
403
  let existingGroup = acc.find((group) =>
373
- group[0].thesaurus
374
- ? group[0].thesaurus.id === thesaurusId
375
- : group[0].type === type
404
+ thesaurusId
405
+ ? group[0].thesaurus?.id === thesaurusId
406
+ : group[0].type === type && !group[0].thesaurus
376
407
  )
377
408
  if (!existingGroup) {
378
409
  existingGroup = []
@@ -413,7 +444,7 @@ function appendKeywords(keywords: Keyword[]) {
413
444
  )
414
445
  }
415
446
 
416
- function createConstraint(
447
+ export function createConstraint(
417
448
  constraint: Constraint,
418
449
  type: 'legal' | 'security' | 'other'
419
450
  ) {
@@ -471,7 +502,7 @@ function createConstraint(
471
502
  )
472
503
  }
473
504
 
474
- function removeOtherConstraints() {
505
+ export function removeOtherConstraints() {
475
506
  return removeChildren(
476
507
  pipe(
477
508
  findChildrenElement('gmd:resourceConstraints'),
@@ -485,7 +516,7 @@ function removeOtherConstraints() {
485
516
  )
486
517
  }
487
518
 
488
- function removeSecurityConstraints() {
519
+ export function removeSecurityConstraints() {
489
520
  return removeChildren(
490
521
  pipe(
491
522
  findChildrenElement('gmd:resourceConstraints'),
@@ -499,7 +530,7 @@ function removeSecurityConstraints() {
499
530
  )
500
531
  }
501
532
 
502
- function removeLegalConstraints() {
533
+ export function removeLegalConstraints() {
503
534
  return removeChildren(
504
535
  pipe(
505
536
  findChildrenElement('gmd:resourceConstraints'),
@@ -519,7 +550,7 @@ function removeLegalConstraints() {
519
550
  )
520
551
  }
521
552
 
522
- function removeLicenses() {
553
+ export function removeLicenses() {
523
554
  return removeChildren(
524
555
  pipe(
525
556
  findChildrenElement('gmd:resourceConstraints'),
@@ -539,7 +570,7 @@ function removeLicenses() {
539
570
  )
540
571
  }
541
572
 
542
- function createLicense(license: Constraint) {
573
+ export function createLicense(license: Constraint) {
543
574
  return pipe(
544
575
  createElement('gmd:resourceConstraints'),
545
576
  createChild('gmd:MD_LegalConstraints'),
@@ -572,44 +603,52 @@ function createLicense(license: Constraint) {
572
603
  )
573
604
  }
574
605
 
575
- function removeDistributions() {
606
+ export function removeDistributions() {
576
607
  return pipe(removeChildrenByName('gmd:distributionInfo'))
577
608
  }
578
609
 
579
- function createDistribution(distribution: DatasetDistribution) {
580
- const appendDistributionFormat =
581
- 'mimeType' in distribution
582
- ? appendChildren(
583
- pipe(
584
- createElement('gmd:distributionFormat'),
585
- createChild('gmd:MD_Format'),
586
- appendChildren(
587
- pipe(
588
- createElement('gmd:name'),
589
- writeCharacterString(distribution.mimeType)
590
- ),
591
- pipe(
592
- createElement('gmd:version'),
593
- writeCharacterString('1.0') // hardcoding this as it most likely won't be used but is mandatory
594
- )
595
- )
596
- )
610
+ function appendDistributionFormat(mimeType: string) {
611
+ return appendChildren(
612
+ pipe(
613
+ createElement('gmd:distributionFormat'),
614
+ createChild('gmd:MD_Format'),
615
+ appendChildren(
616
+ pipe(createElement('gmd:name'), writeCharacterString(mimeType)),
617
+ pipe(
618
+ createElement('gmd:version'),
619
+ writeCharacterString('1.0') // hardcoding this as it most likely won't be used but is mandatory
597
620
  )
598
- : noop
621
+ )
622
+ )
623
+ )
624
+ }
625
+
626
+ export function createDistributionInfo() {
627
+ return pipe(
628
+ createElement('gmd:distributionInfo'),
629
+ createChild('gmd:MD_Distribution')
630
+ )
631
+ }
599
632
 
600
- let linkageUrl, name, functionCode, protocol
633
+ // apply to MD_Distribution
634
+ export function appendDistribution(
635
+ distribution: DatasetDistribution,
636
+ appendFormatFn: (
637
+ mimeType: string
638
+ ) => ChainableFunction<XmlElement, XmlElement>
639
+ ) {
640
+ let name: string
641
+ let functionCode: string
642
+ let protocol: string
601
643
  if (distribution.type === 'service') {
602
- linkageUrl = distribution.url.toString()
603
644
  name = distribution.identifierInService // this is for GeoNetwork to know the layer name
604
645
  functionCode = 'download'
605
646
  protocol = getDistributionProtocol(distribution)
606
647
  } else if (distribution.type === 'download') {
607
- linkageUrl = distribution.url.toString()
608
648
  name = distribution.name
609
649
  functionCode = 'download'
610
650
  protocol = 'WWW:DOWNLOAD'
611
651
  } else {
612
- linkageUrl = distribution.url.toString()
613
652
  name = distribution.name
614
653
  functionCode = 'information'
615
654
  protocol = 'WWW:LINK'
@@ -620,7 +659,7 @@ function createDistribution(distribution: DatasetDistribution) {
620
659
  createChild('gmd:MD_DigitalTransferOptions'),
621
660
  createChild('gmd:onLine'),
622
661
  createChild('gmd:CI_OnlineResource'),
623
- writeLinkage(linkageUrl),
662
+ writeLinkage(distribution.url),
624
663
  'description' in distribution
625
664
  ? appendChildren(
626
665
  pipe(
@@ -649,9 +688,7 @@ function createDistribution(distribution: DatasetDistribution) {
649
688
  )
650
689
  )
651
690
  return pipe(
652
- createElement('gmd:distributionInfo'),
653
- createChild('gmd:MD_Distribution'),
654
- appendDistributionFormat,
691
+ 'mimeType' in distribution ? appendFormatFn(distribution.mimeType) : noop,
655
692
  appendTransferOptions
656
693
  )
657
694
  }
@@ -660,7 +697,7 @@ function createDistribution(distribution: DatasetDistribution) {
660
697
  * Looks for srv:SV_ServiceIdentification or gmd:MD_DataIdentification element
661
698
  * depending on record type, create if missing
662
699
  */
663
- function findOrCreateIdentification() {
700
+ export function findOrCreateIdentification() {
664
701
  return (rootEl: XmlElement) => {
665
702
  const kind = readKind(rootEl)
666
703
  let eltName = 'gmd:MD_DataIdentification'
@@ -669,7 +706,7 @@ function findOrCreateIdentification() {
669
706
  }
670
707
  }
671
708
 
672
- function findOrCreateDistribution() {
709
+ export function findOrCreateDistribution() {
673
710
  return (rootEl: XmlElement) => {
674
711
  return findNestedChildOrCreate(
675
712
  'gmd:distributionInfo',
@@ -763,11 +800,26 @@ export function writeStatus(record: DatasetRecord, rootEl: XmlElement) {
763
800
  }
764
801
 
765
802
  export function writeContacts(record: CatalogRecord, rootEl: XmlElement) {
803
+ pipe(
804
+ removeChildrenByName('gmd:contact'),
805
+ appendChildren(
806
+ ...record.contacts.map((contact) =>
807
+ pipe(createElement('gmd:contact'), appendResponsibleParty(contact))
808
+ )
809
+ )
810
+ )(rootEl)
811
+ }
812
+
813
+ export function writeContactsForResource(
814
+ record: CatalogRecord,
815
+ rootEl: XmlElement
816
+ ) {
766
817
  pipe(
767
818
  findOrCreateIdentification(),
768
819
  removeChildrenByName('gmd:pointOfContact'),
820
+ removeChildrenByName('gmd:contact'),
769
821
  appendChildren(
770
- ...record.contacts.map((contact) =>
822
+ ...record.contactsForResource.map((contact) =>
771
823
  pipe(
772
824
  createElement('gmd:pointOfContact'),
773
825
  appendResponsibleParty(contact)
@@ -878,26 +930,47 @@ export function writeUpdateFrequency(
878
930
  )(rootEl)
879
931
  }
880
932
 
881
- export function writeDatasetCreated(record: DatasetRecord, rootEl: XmlElement) {
882
- if (!('datasetCreated' in record)) return
933
+ export function writeResourceCreated(
934
+ record: DatasetRecord,
935
+ rootEl: XmlElement
936
+ ) {
937
+ if (!('resourceCreated' in record)) return
883
938
  pipe(
884
939
  findOrCreateIdentification(),
885
940
  findNestedChildOrCreate('gmd:citation', 'gmd:CI_Citation'),
886
941
  fallback(
887
- updateCitationDate(record.datasetCreated, 'creation'),
888
- appendCitationDate(record.datasetCreated, 'creation')
942
+ updateCitationDate(record.resourceCreated, 'creation'),
943
+ appendCitationDate(record.resourceCreated, 'creation')
889
944
  )
890
945
  )(rootEl)
891
946
  }
892
947
 
893
- export function writeDatasetUpdated(record: DatasetRecord, rootEl: XmlElement) {
894
- if (!('datasetUpdated' in record)) return
948
+ export function writeResourceUpdated(
949
+ record: DatasetRecord,
950
+ rootEl: XmlElement
951
+ ) {
952
+ if (!('resourceUpdated' in record)) return
895
953
  pipe(
896
954
  findOrCreateIdentification(),
897
955
  findNestedChildOrCreate('gmd:citation', 'gmd:CI_Citation'),
898
956
  fallback(
899
- updateCitationDate(record.datasetUpdated, 'revision'),
900
- appendCitationDate(record.datasetUpdated, 'revision')
957
+ updateCitationDate(record.resourceUpdated, 'revision'),
958
+ appendCitationDate(record.resourceUpdated, 'revision')
959
+ )
960
+ )(rootEl)
961
+ }
962
+
963
+ export function writeResourcePublished(
964
+ record: DatasetRecord,
965
+ rootEl: XmlElement
966
+ ) {
967
+ if (!('resourcePublished' in record)) return
968
+ pipe(
969
+ findOrCreateIdentification(),
970
+ findNestedChildOrCreate('gmd:citation', 'gmd:CI_Citation'),
971
+ fallback(
972
+ updateCitationDate(record.resourcePublished, 'publication'),
973
+ appendCitationDate(record.resourcePublished, 'publication')
901
974
  )
902
975
  )(rootEl)
903
976
  }
@@ -955,7 +1028,14 @@ export function writeGraphicOverviews(
955
1028
  export function writeDistributions(record: DatasetRecord, rootEl: XmlElement) {
956
1029
  pipe(
957
1030
  removeDistributions(),
958
- appendChildren(...record.distributions.map(createDistribution))
1031
+ appendChildren(
1032
+ ...record.distributions.map((d) =>
1033
+ pipe(
1034
+ createDistributionInfo(),
1035
+ appendDistribution(d, appendDistributionFormat)
1036
+ )
1037
+ )
1038
+ )
959
1039
  )(rootEl)
960
1040
  }
961
1041
 
@@ -972,7 +1052,7 @@ export function writeLineage(record: DatasetRecord, rootEl: XmlElement) {
972
1052
  )(rootEl)
973
1053
  }
974
1054
 
975
- function getServiceEndpointProtocol(endpoint: ServiceEndpoint): string {
1055
+ export function getServiceEndpointProtocol(endpoint: ServiceEndpoint): string {
976
1056
  switch (endpoint.protocol.toLowerCase()) {
977
1057
  case 'wfs':
978
1058
  return 'OGC:WFS'
@@ -985,7 +1065,7 @@ function getServiceEndpointProtocol(endpoint: ServiceEndpoint): string {
985
1065
  }
986
1066
  }
987
1067
 
988
- function createOnlineResource(onlineResource: ServiceOnlineResource) {
1068
+ export function createOnlineResource(onlineResource: ServiceOnlineResource) {
989
1069
  let linkageUrl, functionCode, protocol
990
1070
  if (onlineResource.type === 'endpoint') {
991
1071
  linkageUrl = onlineResource.endpointUrl.toString()
@@ -33,6 +33,7 @@ export function createDocument(rootEl: XmlElement): XmlDocument {
33
33
  function collectNamespaceFromName(name: string) {
34
34
  const namespace = extractNamespace(name)
35
35
  if (namespace === 'xmlns' || namespace === null) return
36
+ if (rootEl.attributes[`xmlns:${namespace}`]) return
36
37
  if (!NAMESPACES[namespace]) {
37
38
  throw new Error(`No known URI for namespace ${namespace}`)
38
39
  }
@@ -55,11 +56,16 @@ export function createDocument(rootEl: XmlElement): XmlDocument {
55
56
  /**
56
57
  * Will do nothing if no namespace present
57
58
  */
58
- function stripNamespace(name: string): string {
59
+ export function stripNamespace(name: string): string {
59
60
  const colon = name.indexOf(':')
60
61
  return colon > -1 ? name.substring(colon + 1) : name
61
62
  }
62
63
 
64
+ export function getNamespace(name: string): string {
65
+ const colon = name.indexOf(':')
66
+ return colon > -1 ? name.substring(0, colon) : ''
67
+ }
68
+
63
69
  function getElementName(element: XmlElement): string {
64
70
  return element.name || ''
65
71
  }
@@ -115,7 +121,7 @@ export function allChildrenElement(element: XmlElement): Array<XmlElement> {
115
121
  * returns an empty array if no matching element
116
122
  */
117
123
  export function findNestedElements(
118
- ...elementNames
124
+ ...elementNames: string[]
119
125
  ): ChainableFunction<XmlElement, Array<XmlElement>> {
120
126
  return (el) => {
121
127
  function lookFor(elNameIndex: number) {
@@ -248,6 +254,29 @@ const NAMESPACES = {
248
254
  gsr: 'http://www.isotc211.org/2005/gsr',
249
255
  gmi: 'http://www.isotc211.org/2005/gmi',
250
256
  xlink: 'http://www.w3.org/1999/xlink',
257
+ mdb: 'http://standards.iso.org/iso/19115/-3/mdb/2.0',
258
+ mdq: 'http://standards.iso.org/iso/19157/-2/mdq/1.0',
259
+ msr: 'http://standards.iso.org/iso/19115/-3/msr/2.0',
260
+ mrs: 'http://standards.iso.org/iso/19115/-3/mrs/1.0',
261
+ mmi: 'http://standards.iso.org/iso/19115/-3/mmi/1.0',
262
+ mrl: 'http://standards.iso.org/iso/19115/-3/mrl/2.0',
263
+ mdt: 'http://standards.iso.org/iso/19115/-3/mdt/2.0',
264
+ mrd: 'http://standards.iso.org/iso/19115/-3/mrd/1.0',
265
+ mds: 'http://standards.iso.org/iso/19115/-3/mds/2.0',
266
+ mpc: 'http://standards.iso.org/iso/19115/-3/mpc/1.0',
267
+ mcc: 'http://standards.iso.org/iso/19115/-3/mcc/1.0',
268
+ mac: 'http://standards.iso.org/iso/19115/-3/mac/2.0',
269
+ mco: 'http://standards.iso.org/iso/19115/-3/mco/1.0',
270
+ mda: 'http://standards.iso.org/iso/19115/-3/mda/1.0',
271
+ mex: 'http://standards.iso.org/iso/19115/-3/mex/1.0',
272
+ gex: 'http://standards.iso.org/iso/19115/-3/gex/1.0',
273
+ gcx: 'http://standards.iso.org/iso/19115/-3/gcx/1.0',
274
+ mas: 'http://standards.iso.org/iso/19115/-3/mas/1.0',
275
+ mri: 'http://standards.iso.org/iso/19115/-3/mri/1.0',
276
+ cit: 'http://standards.iso.org/iso/19115/-3/cit/2.0',
277
+ cat: 'http://standards.iso.org/iso/19115/-3/cat/1.0',
278
+ lan: 'http://standards.iso.org/iso/19115/-3/lan/1.0',
279
+ mrc: 'http://standards.iso.org/iso/19115/-3/mrc/2.0',
251
280
  }
252
281
 
253
282
  /**
@@ -268,6 +297,13 @@ export function addAttribute(
268
297
  return element
269
298
  }
270
299
  }
300
+ function getTreeRoot(element: XmlElement): XmlElement {
301
+ let root = element
302
+ while (root.parent instanceof XmlElement) {
303
+ root = root.parent
304
+ }
305
+ return root
306
+ }
271
307
 
272
308
  // stays on the parent element
273
309
  // if the given elements are part of a subtree, will add the root of subtree
@@ -276,22 +312,26 @@ export function appendChildren(
276
312
  ): ChainableFunction<XmlElement, XmlElement> {
277
313
  return (element) => {
278
314
  if (!element) return null
279
- element.children.push(
280
- ...childrenFns
281
- .map((fn) => fn())
282
- .map((el) => {
283
- let root = el
284
- while (root.parent instanceof XmlElement) {
285
- root = root.parent
286
- }
287
- return root
288
- })
289
- )
315
+ element.children.push(...childrenFns.map((fn) => fn()).map(getTreeRoot))
290
316
  element.children.forEach((el) => (el.parent = element))
291
317
  return element
292
318
  }
293
319
  }
294
320
 
321
+ // switch to the tip of the subtree
322
+ export function appendChildTree(
323
+ childrenFn: ChainableFunction<void, XmlElement>
324
+ ): ChainableFunction<XmlElement, XmlElement> {
325
+ return (element) => {
326
+ if (!element) return null
327
+ const treeTip = childrenFn()
328
+ const treeRoot = getTreeRoot(treeTip)
329
+ element.children.push(treeRoot)
330
+ treeRoot.parent = element
331
+ return treeTip
332
+ }
333
+ }
334
+
295
335
  // switches to the child element
296
336
  export function createChild(
297
337
  childName: string
@@ -349,6 +389,7 @@ export function removeAllChildren(): ChainableFunction<XmlElement, XmlElement> {
349
389
  }
350
390
  }
351
391
 
392
+ // stays on the same element
352
393
  export function removeChildrenByName(
353
394
  name: string
354
395
  ): ChainableFunction<XmlElement, XmlElement> {
@@ -372,11 +413,38 @@ export function removeChildren(
372
413
  childrenFn: ChainableFunction<XmlElement, Array<XmlElement>>
373
414
  ): ChainableFunction<XmlElement, XmlElement> {
374
415
  return (element) => {
375
- const children = childrenFn(element)
376
- children.forEach((child) => (child.parent = null))
416
+ const childrenToRemove = childrenFn(element)
417
+ childrenToRemove.forEach((child) => (child.parent = null))
377
418
  element.children = element.children.filter(
378
- (child) => child instanceof XmlElement && children.indexOf(child) === -1
419
+ (child) =>
420
+ child instanceof XmlElement && childrenToRemove.indexOf(child) === -1
379
421
  )
380
422
  return element
381
423
  }
382
424
  }
425
+
426
+ /**
427
+ * Renames elements in the XML tree according to the map
428
+ * Either specify a full element name like 'gmd:MD_Metadata' or simply a namespace like 'gmd'
429
+ * @param rootElement
430
+ * @param replaceMap
431
+ */
432
+ export function renameElements(
433
+ rootElement: XmlElement,
434
+ replaceMap: Record<string, string>
435
+ ) {
436
+ function doReplace(element: XmlElement) {
437
+ if (element.name in replaceMap) {
438
+ element.name = replaceMap[element.name]
439
+ } else if (element.name && getNamespace(element.name) in replaceMap) {
440
+ element.name = `${
441
+ replaceMap[getNamespace(element.name)]
442
+ }:${stripNamespace(element.name)}`
443
+ }
444
+ if (element.children) {
445
+ element.children.forEach(doReplace)
446
+ }
447
+ }
448
+ doReplace(rootElement)
449
+ return rootElement
450
+ }