diff --git a/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.spec.ts b/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.spec.ts
index f6160ffb1a..953f9a37d7 100644
--- a/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.spec.ts
+++ b/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.spec.ts
@@ -35,9 +35,9 @@ describe('DashboardMenuComponent', () => {
recordsRepository.draftsChanged$ = hot('-a-|', {
a: void 0,
})
- recordsRepository.getAllDrafts = jest
+ recordsRepository.getDraftsCount = jest
.fn()
- .mockReturnValue(hot('ab-|', { a: [], b: [{}] }))
+ .mockReturnValue(hot('ab-|', { a: 0, b: 1 }))
// Define the expected marble diagram
const expected = cold('ab-|', { a: 0, b: 1 })
diff --git a/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.ts b/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.ts
index e602f6d713..a1db676004 100644
--- a/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.ts
+++ b/apps/metadata-editor/src/app/dashboard/dashboard-menu/dashboard-menu.component.ts
@@ -4,7 +4,7 @@ import { MatIconModule } from '@angular/material/icon'
import { RouterModule } from '@angular/router'
import { TranslateModule } from '@ngx-translate/core'
import { RecordsRepositoryInterface } from '@geonetwork-ui/common/domain/repository/records-repository.interface'
-import { map, startWith, switchMap } from 'rxjs/operators'
+import { startWith, switchMap } from 'rxjs/operators'
import { BadgeComponent } from '@geonetwork-ui/ui/inputs'
@Component({
@@ -23,9 +23,8 @@ import { BadgeComponent } from '@geonetwork-ui/ui/inputs'
})
export class DashboardMenuComponent {
draftsCount$ = this.recordsRepository.draftsChanged$.pipe(
- startWith(void 0),
- switchMap(() => this.recordsRepository.getAllDrafts()),
- map((drafts) => drafts.length)
+ startWith(0),
+ switchMap(() => this.recordsRepository.getDraftsCount())
)
activeLink = false
diff --git a/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.spec.ts b/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.spec.ts
index 0a92d0800c..e85eb58cf5 100644
--- a/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.spec.ts
+++ b/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.spec.ts
@@ -7,6 +7,7 @@ import {
writeResourceCreated,
writeResourcePublished,
writeResourceUpdated,
+ writeSpatialRepresentation,
} from './write-parts'
import {
createElement,
@@ -605,6 +606,34 @@ describe('write parts', () => {
+`)
+ })
+ })
+
+ describe('writeSpatialRepresentation', () => {
+ it('writes the corresponding element', () => {
+ writeSpatialRepresentation(datasetRecord, rootEl)
+ expect(rootAsString()).toEqual(`
+
+
+
+ grid
+
+
+
+`)
+ })
+ it('clears the corresponding element if the record has no spatial representation', () => {
+ writeSpatialRepresentation(datasetRecord, rootEl)
+ const modified: DatasetRecord = {
+ ...datasetRecord,
+ spatialRepresentation: null,
+ }
+ writeSpatialRepresentation(modified, rootEl)
+ expect(rootAsString()).toEqual(`
+
+
+
`)
})
})
diff --git a/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts b/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts
index feb3443acd..c0e9a34032 100644
--- a/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts
+++ b/libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts
@@ -409,6 +409,13 @@ export function writeSpatialRepresentation(
record: DatasetRecord,
rootEl: XmlElement
) {
+ if (!record.spatialRepresentation) {
+ pipe(
+ findOrCreateIdentification(),
+ removeChildrenByName('mri:spatialRepresentationType')
+ )(rootEl)
+ return
+ }
pipe(
findOrCreateIdentification(),
findNestedChildOrCreate(
diff --git a/libs/api/metadata-converter/src/lib/iso19139/write-parts.spec.ts b/libs/api/metadata-converter/src/lib/iso19139/write-parts.spec.ts
index 820c0edb5a..5138819570 100644
--- a/libs/api/metadata-converter/src/lib/iso19139/write-parts.spec.ts
+++ b/libs/api/metadata-converter/src/lib/iso19139/write-parts.spec.ts
@@ -17,6 +17,7 @@ import {
writeResourcePublished,
writeResourceUpdated,
writeSpatialExtents,
+ writeSpatialRepresentation,
writeTemporalExtents,
} from './write-parts'
@@ -926,4 +927,32 @@ describe('write parts', () => {
).toEqual('P0Y0M3D')
})
})
+
+ describe('writeSpatialRepresentation', () => {
+ it('writes the corresponding element', () => {
+ writeSpatialRepresentation(datasetRecord, rootEl)
+ expect(rootAsString()).toEqual(`
+
+
+
+
+
+
+
+`)
+ })
+ it('clears the corresponding element if the record has no spatial representation', () => {
+ writeSpatialRepresentation(datasetRecord, rootEl)
+ const modified: DatasetRecord = {
+ ...datasetRecord,
+ spatialRepresentation: null,
+ }
+ writeSpatialRepresentation(modified, rootEl)
+ expect(rootAsString()).toEqual(`
+
+
+
+`)
+ })
+ })
})
diff --git a/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts b/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts
index 34a80e1b80..0cbe91ee43 100644
--- a/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts
+++ b/libs/api/metadata-converter/src/lib/iso19139/write-parts.ts
@@ -953,6 +953,13 @@ export function writeSpatialRepresentation(
record: DatasetRecord,
rootEl: XmlElement
) {
+ if (!record.spatialRepresentation) {
+ pipe(
+ findOrCreateIdentification(),
+ removeChildrenByName('gmd:spatialRepresentationType')
+ )(rootEl)
+ return
+ }
pipe(
findOrCreateIdentification(),
findNestedChildOrCreate(
diff --git a/libs/api/metadata-converter/src/lib/xml-utils.spec.ts b/libs/api/metadata-converter/src/lib/xml-utils.spec.ts
index 4a4815c547..33a8b3db02 100644
--- a/libs/api/metadata-converter/src/lib/xml-utils.spec.ts
+++ b/libs/api/metadata-converter/src/lib/xml-utils.spec.ts
@@ -1,4 +1,4 @@
-import { XmlElement } from '@rgrove/parse-xml'
+import { XmlElement, XmlText } from '@rgrove/parse-xml'
import {
assertValidXml,
createDocument,
@@ -41,6 +41,24 @@ end.
const doc = parseXmlString(input)
expect(xmlToString(doc)).toEqual(input)
})
+
+ it('should properly escape special characters in text', () => {
+ const textNode = new XmlElement(
+ 'test',
+ {
+ 'my-attribute': ' & ',
+ },
+ [new XmlText('Text with <, >, &')]
+ )
+
+ const result = xmlToString(textNode)
+
+ expect(result).toBe(
+ `
+Text with <, >, &
+`
+ )
+ })
})
describe('replaceNamespace', () => {
diff --git a/libs/api/metadata-converter/src/lib/xml-utils.ts b/libs/api/metadata-converter/src/lib/xml-utils.ts
index 3b6146a90a..c67e190f38 100644
--- a/libs/api/metadata-converter/src/lib/xml-utils.ts
+++ b/libs/api/metadata-converter/src/lib/xml-utils.ts
@@ -199,6 +199,12 @@ export function xmlToString(
el: XmlElement | XmlText | XmlComment | XmlDocument,
indentationLevel = 0
) {
+ const encodeEntities = (text: string) => {
+ return text
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ }
if (el instanceof XmlDocument)
return `${xmlToString(
el.children[0]
@@ -207,10 +213,7 @@ export function xmlToString(
const text = el.text
const isEmpty = !text || text.replace(/^\s+|\s+$/g, '') === ''
if (isEmpty) return ''
- return text
- .replace(/&/g, '&')
- .replace(//g, '>')
+ return encodeEntities(text)
}
if (!(el instanceof XmlElement)) return ``
@@ -225,7 +228,7 @@ export function xmlToString(
.join('')
: ''
const attrs = Object.keys(el.attributes).reduce(
- (prev, curr) => prev + ` ${curr}="${el.attributes[curr]}"`,
+ (prev, curr) => prev + ` ${curr}="${encodeEntities(el.attributes[curr])}"`,
''
)
const parentPadding = ' '.repeat(Math.max(0, indentationLevel - 1))
diff --git a/libs/api/repository/src/lib/gn4/gn4-repository.spec.ts b/libs/api/repository/src/lib/gn4/gn4-repository.spec.ts
index 64105393e7..2a9e1e71cb 100644
--- a/libs/api/repository/src/lib/gn4/gn4-repository.spec.ts
+++ b/libs/api/repository/src/lib/gn4/gn4-repository.spec.ts
@@ -551,6 +551,35 @@ describe('Gn4Repository', () => {
})
})
+ describe('#getDraftsCount', () => {
+ beforeEach(async () => {
+ window.localStorage.clear()
+ // save 3 drafts
+ await firstValueFrom(
+ repository.saveRecordAsDraft({
+ ...simpleDatasetRecordFixture(),
+ uniqueIdentifier: 'DRAFT-1',
+ })
+ )
+ await firstValueFrom(
+ repository.saveRecordAsDraft({
+ ...simpleDatasetRecordFixture(),
+ uniqueIdentifier: 'DRAFT-2',
+ })
+ )
+ await firstValueFrom(
+ repository.saveRecordAsDraft({
+ ...simpleDatasetRecordFixture(),
+ uniqueIdentifier: 'DRAFT-3',
+ })
+ )
+ })
+ it('returns all drafts', async () => {
+ const draftCount = await lastValueFrom(repository.getDraftsCount())
+ expect(draftCount).toBe(3)
+ })
+ })
+
describe('importRecordFromExternalFileUrlAsDraft', () => {
const recordDownloadUrl = 'https://example.com/record/xml'
const mockXml = simpleDatasetRecordAsXmlFixture()
diff --git a/libs/api/repository/src/lib/gn4/gn4-repository.ts b/libs/api/repository/src/lib/gn4/gn4-repository.ts
index 9b6fd06a30..14046dc246 100644
--- a/libs/api/repository/src/lib/gn4/gn4-repository.ts
+++ b/libs/api/repository/src/lib/gn4/gn4-repository.ts
@@ -340,11 +340,22 @@ export class Gn4Repository implements RecordsRepositoryInterface {
.filter((draft) => draft !== null)
return from(
Promise.all(
- drafts.map((draft) => findConverterForDocument(draft).readRecord(draft))
+ drafts.map((draft) => {
+ return findConverterForDocument(draft).readRecord(draft)
+ })
)
)
}
+ getDraftsCount(): Observable {
+ const items = { ...window.localStorage }
+ const draftCount = Object.keys(items)
+ .filter((key) => key.startsWith('geonetwork-ui-draft-'))
+ .map((key) => window.localStorage.getItem(key))
+ .filter((draft) => draft !== null).length
+ return of(draftCount)
+ }
+
private getRecordAsXml(uniqueIdentifier: string): Observable {
return this.gn4RecordsApi
.getRecordAs(
diff --git a/libs/common/domain/src/lib/repository/records-repository.interface.ts b/libs/common/domain/src/lib/repository/records-repository.interface.ts
index e17c833407..b2640d069c 100644
--- a/libs/common/domain/src/lib/repository/records-repository.interface.ts
+++ b/libs/common/domain/src/lib/repository/records-repository.interface.ts
@@ -86,5 +86,6 @@ export abstract class RecordsRepositoryInterface {
/** will return all pending drafts, both published and not published */
abstract getAllDrafts(): Observable
+ abstract getDraftsCount(): Observable
abstract draftsChanged$: Observable
}