Skip to content

Commit

Permalink
Merge pull request #1010 from geonetwork/me-fix-xml-utils-entities-en…
Browse files Browse the repository at this point in the history
…coding

[ME]: Error when trying to import a record from Geocat.ch
  • Loading branch information
jahow authored Sep 30, 2024
2 parents 3b6b95e + bd40e8b commit e550c0b
Show file tree
Hide file tree
Showing 11 changed files with 146 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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

Expand Down
29 changes: 29 additions & 0 deletions libs/api/metadata-converter/src/lib/iso19115-3/write-parts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
writeResourceCreated,
writeResourcePublished,
writeResourceUpdated,
writeSpatialRepresentation,
} from './write-parts'
import {
createElement,
Expand Down Expand Up @@ -605,6 +606,34 @@ describe('write parts', () => {
</mri:pointOfContact>
</gmd:MD_DataIdentification>
</gmd:identificationInfo>
</root>`)
})
})

describe('writeSpatialRepresentation', () => {
it('writes the corresponding element', () => {
writeSpatialRepresentation(datasetRecord, rootEl)
expect(rootAsString()).toEqual(`<root>
<gmd:identificationInfo>
<gmd:MD_DataIdentification>
<mri:spatialRepresentationType>
<mcc:MD_SpatialRepresentationTypeCode codeList="https://standards.iso.org/iso/19115/resources/Codelists/cat/codelists.xml#MD_SpatialRepresentationTypeCode" codeListValue="grid">grid</mcc:MD_SpatialRepresentationTypeCode>
</mri:spatialRepresentationType>
</gmd:MD_DataIdentification>
</gmd:identificationInfo>
</root>`)
})
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(`<root>
<gmd:identificationInfo>
<gmd:MD_DataIdentification/>
</gmd:identificationInfo>
</root>`)
})
})
Expand Down
7 changes: 7 additions & 0 deletions libs/api/metadata-converter/src/lib/iso19115-3/write-parts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,13 @@ export function writeSpatialRepresentation(
record: DatasetRecord,
rootEl: XmlElement
) {
if (!record.spatialRepresentation) {
pipe(
findOrCreateIdentification(),
removeChildrenByName('mri:spatialRepresentationType')
)(rootEl)
return
}
pipe(
findOrCreateIdentification(),
findNestedChildOrCreate(
Expand Down
29 changes: 29 additions & 0 deletions libs/api/metadata-converter/src/lib/iso19139/write-parts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
writeResourcePublished,
writeResourceUpdated,
writeSpatialExtents,
writeSpatialRepresentation,
writeTemporalExtents,
} from './write-parts'

Expand Down Expand Up @@ -926,4 +927,32 @@ describe('write parts', () => {
).toEqual('P0Y0M3D')
})
})

describe('writeSpatialRepresentation', () => {
it('writes the corresponding element', () => {
writeSpatialRepresentation(datasetRecord, rootEl)
expect(rootAsString()).toEqual(`<root>
<gmd:identificationInfo>
<gmd:MD_DataIdentification>
<gmd:spatialRepresentationType>
<gmd:MD_SpatialRepresentationTypeCode codeList="http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_SpatialRepresentationTypeCode" codeListValue="grid"/>
</gmd:spatialRepresentationType>
</gmd:MD_DataIdentification>
</gmd:identificationInfo>
</root>`)
})
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(`<root>
<gmd:identificationInfo>
<gmd:MD_DataIdentification/>
</gmd:identificationInfo>
</root>`)
})
})
})
7 changes: 7 additions & 0 deletions libs/api/metadata-converter/src/lib/iso19139/write-parts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -953,6 +953,13 @@ export function writeSpatialRepresentation(
record: DatasetRecord,
rootEl: XmlElement
) {
if (!record.spatialRepresentation) {
pipe(
findOrCreateIdentification(),
removeChildrenByName('gmd:spatialRepresentationType')
)(rootEl)
return
}
pipe(
findOrCreateIdentification(),
findNestedChildOrCreate(
Expand Down
20 changes: 19 additions & 1 deletion libs/api/metadata-converter/src/lib/xml-utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { XmlElement } from '@rgrove/parse-xml'
import { XmlElement, XmlText } from '@rgrove/parse-xml'
import {
assertValidXml,
createDocument,
Expand Down Expand Up @@ -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': '<Attribute> & <value>',
},
[new XmlText('Text with <, >, &')]
)

const result = xmlToString(textNode)

expect(result).toBe(
`
<test my-attribute="&lt;Attribute&gt; &amp; &lt;value&gt;">Text with &lt;, &gt;, &amp;</test>
`
)
})
})

describe('replaceNamespace', () => {
Expand Down
13 changes: 8 additions & 5 deletions libs/api/metadata-converter/src/lib/xml-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,12 @@ export function xmlToString(
el: XmlElement | XmlText | XmlComment | XmlDocument,
indentationLevel = 0
) {
const encodeEntities = (text: string) => {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
}
if (el instanceof XmlDocument)
return `<?xml version="1.0" encoding="UTF-8"?>${xmlToString(
el.children[0]
Expand All @@ -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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
return encodeEntities(text)
}
if (!(el instanceof XmlElement)) return `<!-- unknown -->`

Expand All @@ -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))
Expand Down
29 changes: 29 additions & 0 deletions libs/api/repository/src/lib/gn4/gn4-repository.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
13 changes: 12 additions & 1 deletion libs/api/repository/src/lib/gn4/gn4-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<number> {
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<string | null> {
return this.gn4RecordsApi
.getRecordAs(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,5 +86,6 @@ export abstract class RecordsRepositoryInterface {

/** will return all pending drafts, both published and not published */
abstract getAllDrafts(): Observable<CatalogRecord[]>
abstract getDraftsCount(): Observable<number>
abstract draftsChanged$: Observable<void>
}

0 comments on commit e550c0b

Please sign in to comment.