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 }