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/xml-utils.spec.ts b/libs/api/metadata-converter/src/lib/xml-utils.spec.ts index 4a4815c547..7a0b635ac7 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,14 @@ end. const doc = parseXmlString(input) expect(xmlToString(doc)).toEqual(input) }) + + it('should properly escape special characters in text', () => { + const textNode = 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 }