Skip to content

Commit 34a674f

Browse files
Added Dublin Core metadata for entries
1 parent a2c5f87 commit 34a674f

File tree

9 files changed

+1829
-10
lines changed

9 files changed

+1829
-10
lines changed

src/tests/utils/xml/AtomEntryBuilder.dcMetadata.test.ts

Lines changed: 503 additions & 0 deletions
Large diffs are not rendered by default.

src/tests/utils/xml/dcMetadata.xmlParsing.test.ts

Lines changed: 418 additions & 0 deletions
Large diffs are not rendered by default.

src/tests/v1_2/dcMetadata.test.ts

Lines changed: 521 additions & 0 deletions
Large diffs are not rendered by default.

src/utils/xml/AtomEntryBuilder.ts

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import { create } from 'xmlbuilder2';
2-
import { EntryOptions } from '../../versions/v1_2/types';
2+
import { EntryOptions, simpleDcTextFields } from '../../versions/v1_2/types';
33
import { BaseAtomBuilder } from './BaseAtomBuilder';
44

55
export class AtomEntryBuilder extends BaseAtomBuilder<EntryOptions> {
66
constructor() {
77
super('entry');
88
}
99

10-
protected initializeRoot(): void {
10+
protected override initializeRoot(): void {
1111
this.root = create().ele(this.elementName);
1212
}
1313

14-
protected addCoreMetadata(options: EntryOptions): void {
14+
protected override addCoreMetadata(options: EntryOptions): void {
1515
this.root.ele('id').txt(options.id).up();
1616
this.root.ele('title').txt(options.title).up();
1717
this.root
@@ -26,10 +26,58 @@ export class AtomEntryBuilder extends BaseAtomBuilder<EntryOptions> {
2626
.up();
2727
}
2828

29+
this.addDcMetadata(options);
30+
2931
this.addAuthorIfPresent(options.author);
3032
}
3133

32-
protected addExtraContent(options: EntryOptions): void {
34+
private addDcMetadata(options: EntryOptions): void {
35+
const dc = options.dcMetadata || {};
36+
37+
for (const field of simpleDcTextFields) {
38+
const value = dc[field];
39+
if (value) {
40+
this.root.ele(`dc:${field}`).txt(value).up();
41+
}
42+
}
43+
44+
if (dc.identifiers) {
45+
for (const id of dc.identifiers) {
46+
this.root.ele('dc:identifier').txt(id).up();
47+
}
48+
}
49+
50+
if (dc.subjects) {
51+
for (const subject of dc.subjects) {
52+
this.root.ele('dc:subject').txt(subject).up();
53+
}
54+
}
55+
56+
if (dc.references) {
57+
for (const ref of dc.references) {
58+
this.root.ele('dc:references').txt(ref).up();
59+
}
60+
}
61+
62+
if (dc.isReferencedBy) {
63+
for (const refBy of dc.isReferencedBy) {
64+
this.root.ele('dc:isReferencedBy').txt(refBy).up();
65+
}
66+
}
67+
68+
if (dc.contributors) {
69+
for (const contributor of dc.contributors) {
70+
this.root
71+
.ele('dc:contributor')
72+
.ele('name')
73+
.txt(contributor)
74+
.up()
75+
.up();
76+
}
77+
}
78+
}
79+
80+
protected override addExtraContent(options: EntryOptions): void {
3381
if (options.summary) {
3482
this.root
3583
.ele('summary', { type: 'text' })

src/utils/xml/AtomFeedBuilder.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@ export class AtomFeedBuilder extends BaseAtomBuilder<FeedOptions> {
1212
});
1313
}
1414

15-
protected initializeRoot(): void {
15+
protected override initializeRoot(): void {
1616
this.root = create({ version: '1.0', encoding: 'utf-8' }).ele(
1717
this.elementName,
1818
this.namespaces
1919
);
2020
}
2121

22-
protected addCoreMetadata(options: FeedOptions): void {
22+
protected override addCoreMetadata(options: FeedOptions): void {
2323
if (options.lang) {
2424
this.root.att('xml:lang', options.lang);
2525
}
@@ -38,7 +38,7 @@ export class AtomFeedBuilder extends BaseAtomBuilder<FeedOptions> {
3838
this.addAuthorIfPresent(authorName);
3939
}
4040

41-
protected addExtraContent(options: FeedOptions): void {}
41+
protected override addExtraContent(options: FeedOptions): void {}
4242

4343
/**
4444
* Adds entries to the feed.

src/utils/xml/AtomXmlParser.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,27 @@ export class AtomXmlParser {
6161
return undefined;
6262
}
6363

64+
/**
65+
* Extarcts multiple text from multiple elements. (e.g. multiple dc:identifier)
66+
* @param element - The element(s) to extract text from.
67+
* @returns The array of text content or undefined.
68+
*/
69+
extractMultipleText(element: any): string[] | undefined {
70+
if (!element) return undefined;
71+
72+
const elements = Array.isArray(element) ? element : [element];
73+
const texts: string[] = [];
74+
75+
for (const el of elements) {
76+
const text = this.extractText(el);
77+
if (text) {
78+
texts.push(text);
79+
}
80+
}
81+
82+
return texts.length > 0 ? texts : undefined;
83+
}
84+
6485
/**
6586
* Extracts attribute value from an element.
6687
* @param element - The element to extract attribute from.

src/utils/xml/EntryXmlParser.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { EntryOptions } from '../../versions/v1_2/types';
1+
import {
2+
DCMetadata,
3+
EntryOptions,
4+
simpleDcTextFields,
5+
} from '../../versions/v1_2/types';
26
import { Entry } from '../../versions/v1_2/entry';
37
import { AtomXmlParser } from './AtomXmlParser';
48

@@ -74,6 +78,9 @@ export class EntryXmlParser extends AtomXmlParser {
7478
const rights = this.extractText(entryElement.rights);
7579
if (rights) options.rights = rights;
7680

81+
const dcMetadata = this.parseDcMetadata(entryElement);
82+
if (dcMetadata) options.dcMetadata = dcMetadata;
83+
7784
if (entryElement.content) {
7885
const contentType =
7986
this.extractAttribute(entryElement.content, 'type') || 'text';
@@ -110,4 +117,63 @@ export class EntryXmlParser extends AtomXmlParser {
110117

111118
return options;
112119
}
120+
121+
private parseDcMetadata(entryElement: any): DCMetadata | undefined {
122+
const dc: DCMetadata = {};
123+
124+
for (const field of simpleDcTextFields) {
125+
const value = this.extractText(entryElement[`dc:${field}`]);
126+
if (value) {
127+
(dc as any)[field] = value;
128+
}
129+
}
130+
131+
const identifiers = this.extractMultipleText(
132+
entryElement['dc:identifier']
133+
);
134+
if (identifiers && identifiers.length > 0) {
135+
dc.identifiers = identifiers;
136+
}
137+
138+
const subjects = this.extractMultipleText(entryElement['dc:subject']);
139+
if (subjects && subjects.length > 0) {
140+
dc.subjects = subjects;
141+
}
142+
143+
const references = this.extractMultipleText(
144+
entryElement['dc:references']
145+
);
146+
if (references && references.length > 0) {
147+
dc.references = references;
148+
}
149+
150+
const isReferencedBy = this.extractMultipleText(
151+
entryElement['dc:isReferencedBy']
152+
);
153+
if (isReferencedBy && isReferencedBy.length > 0) {
154+
dc.isReferencedBy = isReferencedBy;
155+
}
156+
157+
const contributorElements = this.ensureArray(
158+
entryElement['dc:contributor']
159+
);
160+
if (contributorElements && contributorElements.length > 0) {
161+
const contributors: string[] = [];
162+
for (const contributorEl of contributorElements) {
163+
const name = this.extractText(contributorEl.name);
164+
if (name) {
165+
contributors.push(name);
166+
}
167+
}
168+
if (contributors.length > 0) {
169+
dc.contributors = contributors;
170+
}
171+
}
172+
173+
if (Object.keys(dc).length === 0) {
174+
return undefined;
175+
}
176+
177+
return dc;
178+
}
113179
}

src/versions/v1_2/entry.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
AcquisitionRel,
3+
DCMetadata,
34
EntryOptions,
45
FeedKind,
56
Link,
@@ -29,9 +30,9 @@ export class Entry {
2930

3031
constructor(param1: EntryOptions | string, param2?: string) {
3132
if (typeof param1 === 'string' && param2 !== undefined) {
32-
this.options = { id: param1, title: param2 };
33+
this.options = { id: param1, title: param2, dcMetadata: {} };
3334
} else if (typeof param1 === 'object') {
34-
this.options = { ...param1 };
35+
this.options = { ...param1, dcMetadata: param1.dcMetadata || {} };
3536
} else {
3637
throw new Error('Invalid constructor arguments');
3738
}
@@ -249,6 +250,41 @@ export class Entry {
249250
return this.options.rights;
250251
}
251252

253+
/**
254+
* Sets the DC metadata of the entry.
255+
* @param dcMetadata - The DC metadata object.
256+
* @returns The Entry instance (for chaining).
257+
*/
258+
setDcMetadata(dcMetadata: DCMetadata) {
259+
this.options.dcMetadata = dcMetadata;
260+
return this;
261+
}
262+
263+
/**
264+
* Sets a specific field in the DC metadata of the entry.
265+
* @param key - The key of the DC metadata field.
266+
* @param value - The value to set for the specified field.
267+
* @returns The Entry instance (for chaining).
268+
*/
269+
setDcMetadataField<K extends keyof DCMetadata>(
270+
key: K,
271+
value: DCMetadata[K]
272+
) {
273+
if (!this.options.dcMetadata) {
274+
this.options.dcMetadata = {};
275+
}
276+
this.options.dcMetadata[key] = value;
277+
return this;
278+
}
279+
280+
/**
281+
* Gets the DC metadata of the entry.
282+
* @returns The DC metadata.
283+
*/
284+
getDcMetadata(): DCMetadata {
285+
return this.options.dcMetadata || {};
286+
}
287+
252288
/**
253289
* Adds extra metadata to the entry.
254290
* @param key - The key for the extra metadata.

0 commit comments

Comments
 (0)