@biblioteksentralen/marc 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -4
- package/dist/index.cjs +120 -49
- package/dist/index.d.cts +22 -3
- package/dist/index.d.ts +22 -3
- package/dist/index.js +119 -51
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Package for representating MARC records in TypeScript and serialize to/from MARC XML and JSON.
|
|
4
4
|
|
|
5
|
-
The JSON serialization is compatible with schema defined by <https://www.npmjs.com/package/@natlibfi/marc-record>
|
|
5
|
+
The JSON serialization is compatible with the schema defined by <https://www.npmjs.com/package/@natlibfi/marc-record>
|
|
6
6
|
|
|
7
7
|
## Usage
|
|
8
8
|
|
|
9
|
-
### Parsing XML
|
|
9
|
+
### Parsing and serializing XML
|
|
10
10
|
|
|
11
11
|
```ts
|
|
12
|
-
import { parseMarcXml } from "@biblioteksentralen/marc";
|
|
12
|
+
import { parseMarcXml, serializeMarcXml } from "@biblioteksentralen/marc";
|
|
13
13
|
|
|
14
14
|
const xmlRecord = `
|
|
15
15
|
<record xmlns="http://www.loc.gov/MARC21/slim">
|
|
@@ -38,9 +38,32 @@ const title = record
|
|
|
38
38
|
.getSubfieldValues("245", /a|b|n|p/)
|
|
39
39
|
.join(" ")
|
|
40
40
|
?.trim();
|
|
41
|
+
|
|
42
|
+
const xml = serializeMarcXml(record);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Serializing Line MARC
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import {
|
|
49
|
+
createControlField,
|
|
50
|
+
createDataField,
|
|
51
|
+
createSubfield,
|
|
52
|
+
serializeLineMarc
|
|
53
|
+
} from "@biblioteksentralen/marc";
|
|
54
|
+
|
|
55
|
+
const record = new MarcRecord({
|
|
56
|
+
leader: "...",
|
|
57
|
+
fields: [
|
|
58
|
+
createControlField(...),
|
|
59
|
+
createDataField(...),
|
|
60
|
+
]
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const lineMarc = serializeLineMarc(record);
|
|
41
64
|
```
|
|
42
65
|
|
|
43
|
-
### Serializing
|
|
66
|
+
### Serializing and deserializing as JSON
|
|
44
67
|
|
|
45
68
|
The MarcRecord class can be JSON serialized:
|
|
46
69
|
|
package/dist/index.cjs
CHANGED
|
@@ -248,73 +248,79 @@ var MarcRecord = class _MarcRecord {
|
|
|
248
248
|
fields: this.fields.map((field) => field.toJSON())
|
|
249
249
|
};
|
|
250
250
|
}
|
|
251
|
-
toString() {
|
|
252
|
-
return this.fields.map((field) => field.toString()).join("\n");
|
|
253
|
-
}
|
|
254
251
|
};
|
|
255
252
|
|
|
256
|
-
// src/marc-record/
|
|
257
|
-
var
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
// the same schema as the original MARC 21 XML schema, but weakens restrictions to support
|
|
262
|
-
// other dialects than MARC 21 (not excluding the most esoteric ones).
|
|
263
|
-
"info:lc/xmlns/marcxchange-v1",
|
|
264
|
-
// Version 2 of MarcXchange adds support of embedded data, one of the many
|
|
265
|
-
// advanced XML features that a poor developer hopes not to encounter in the wild.
|
|
266
|
-
// Also weakens restrictions even further so that even a completely empty record is valid.
|
|
267
|
-
"info:lc/xmlns/marcxchange-v2"
|
|
268
|
-
];
|
|
269
|
-
function detectNamespace(input) {
|
|
270
|
-
for (const possibleNamespace of marcXmlNamespaces) {
|
|
271
|
-
if (input.indexOf(possibleNamespace) !== -1) {
|
|
272
|
-
return possibleNamespace;
|
|
273
|
-
}
|
|
253
|
+
// src/marc-record/MarcParseError.ts
|
|
254
|
+
var MarcParseError = class extends Error {
|
|
255
|
+
constructor(message, record) {
|
|
256
|
+
super(message);
|
|
257
|
+
this.record = record;
|
|
274
258
|
}
|
|
275
|
-
|
|
276
|
-
}
|
|
259
|
+
};
|
|
277
260
|
|
|
278
261
|
// src/marc-record/parseMarcXml.ts
|
|
279
|
-
function parseMarcXml(input, options = {}) {
|
|
280
|
-
const
|
|
281
|
-
const xmlRecord = xmlUtils.parseXml(input, {
|
|
282
|
-
namespaces: {
|
|
283
|
-
marc: namespace
|
|
284
|
-
}
|
|
285
|
-
});
|
|
262
|
+
async function parseMarcXml(input, options = {}) {
|
|
263
|
+
const xmlRecord = typeof input === "string" ? await xmlUtils.parseXml(input) : input;
|
|
286
264
|
const processControlField = options.processControlField ?? ((field) => field);
|
|
287
265
|
const processDataField = options.processDataField ?? ((field) => field);
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
266
|
+
const { requireFields = true } = options;
|
|
267
|
+
const records = getRecords(xmlRecord);
|
|
268
|
+
return records.map((marcRecord) => {
|
|
269
|
+
const parseError = (message) => new MarcParseError(message, xmlUtils.serializeXml(marcRecord));
|
|
270
|
+
const leader = marcRecord.text("leader");
|
|
271
|
+
if (!leader) {
|
|
272
|
+
throw parseError("MARC record is missing leader");
|
|
273
|
+
}
|
|
274
|
+
const fields = marcRecord.children(/controlfield|datafield/).reduce((fields2, field) => {
|
|
291
275
|
const tag = field.attr("tag");
|
|
292
|
-
if (!tag)
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
276
|
+
if (!tag) return fields2;
|
|
277
|
+
if (field.name === "controlfield") {
|
|
278
|
+
const value = field.text();
|
|
279
|
+
if (!value && options.strict) {
|
|
280
|
+
throw parseError("MARC record control fields cannot be empty");
|
|
281
|
+
}
|
|
282
|
+
const newField = value ? processControlField(new ControlField(tag, value)) : void 0;
|
|
298
283
|
return newField ? [...fields2, newField] : fields2;
|
|
299
284
|
} else {
|
|
300
|
-
const subfields = field.
|
|
285
|
+
const subfields = field.children("subfield").reduce((subfields2, subfield) => {
|
|
301
286
|
const code = subfield.attr("code");
|
|
302
|
-
|
|
287
|
+
const value = subfield.text();
|
|
288
|
+
return code && value ? [...subfields2, new Subfield(code, value)] : subfields2;
|
|
303
289
|
}, []);
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
290
|
+
const ind1 = field.attr("ind1");
|
|
291
|
+
const ind2 = field.attr("ind2");
|
|
292
|
+
if (options.strict && (ind1 === void 0 || ind2 === void 0)) {
|
|
293
|
+
throw parseError("MARC record data fields must have indicators");
|
|
294
|
+
}
|
|
295
|
+
if (subfields.length === 0 && options.strict) {
|
|
296
|
+
throw parseError(
|
|
297
|
+
"MARC record data fields must have at least one subfield"
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
const newField = subfields.length ? processDataField(
|
|
301
|
+
new DataField(tag, ind1 ?? " ", ind2 ?? " ", subfields)
|
|
302
|
+
) : void 0;
|
|
312
303
|
return newField ? [...fields2, newField] : fields2;
|
|
313
304
|
}
|
|
314
305
|
}, []);
|
|
306
|
+
if (fields.length === 0 && requireFields) {
|
|
307
|
+
throw parseError("MARC record must have at least one field");
|
|
308
|
+
}
|
|
315
309
|
return new MarcRecord({ leader, fields, format: options.format });
|
|
316
310
|
});
|
|
317
311
|
}
|
|
312
|
+
function getRecords(node) {
|
|
313
|
+
switch (node.name) {
|
|
314
|
+
case "record":
|
|
315
|
+
return [node];
|
|
316
|
+
case "collection":
|
|
317
|
+
// MarcXchange
|
|
318
|
+
case "metadata":
|
|
319
|
+
return node.children("record");
|
|
320
|
+
default:
|
|
321
|
+
return [];
|
|
322
|
+
}
|
|
323
|
+
}
|
|
318
324
|
var controlFieldSchema = zod.z.object({
|
|
319
325
|
tag: zod.z.string().length(3, "MARC tag must be three characters long"),
|
|
320
326
|
value: zod.z.string()
|
|
@@ -331,11 +337,76 @@ var marcRecordZodSchema = zod.z.object({
|
|
|
331
337
|
fields: zod.z.array(zod.z.union([controlFieldSchema, dataFieldSchema]))
|
|
332
338
|
});
|
|
333
339
|
|
|
340
|
+
// src/marc-record/serializeLineMarc.ts
|
|
341
|
+
function serializeLineMarc(input) {
|
|
342
|
+
const leader = serializer.leader(input.leader);
|
|
343
|
+
const control = input.getControlFields().map(serializer.controlfield).join("");
|
|
344
|
+
const data = input.getDataFields().map(serializer.datafield).join("");
|
|
345
|
+
return `${leader}${control}${data}^
|
|
346
|
+
`;
|
|
347
|
+
}
|
|
348
|
+
var serializer = {
|
|
349
|
+
leader: (leader) => `*LDR ${leader}
|
|
350
|
+
`,
|
|
351
|
+
controlfield: (field) => `*${field.tag}${field.value}
|
|
352
|
+
`,
|
|
353
|
+
datafield: (field) => {
|
|
354
|
+
const ind1 = field.ind1 ?? " ";
|
|
355
|
+
const ind2 = field.ind2 ?? " ";
|
|
356
|
+
const subfields = field.subfields.map(
|
|
357
|
+
(subfield) => `$${subfield.code}${escapeSubfieldValue(subfield.value)}`
|
|
358
|
+
).join("");
|
|
359
|
+
return `*${field.tag}${ind1}${ind2}${subfields}
|
|
360
|
+
`;
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
var escapeSubfieldValue = (value) => {
|
|
364
|
+
return value.replace(/\$/g, "$$");
|
|
365
|
+
};
|
|
366
|
+
function serializeMarcXml(input, pretty = false) {
|
|
367
|
+
const fields = [
|
|
368
|
+
xmlUtils.createXmlElement("leader", { text: input.leader }),
|
|
369
|
+
...input.getControlFields().map(
|
|
370
|
+
(field) => xmlUtils.createXmlElement("controlfield", {
|
|
371
|
+
attributes: { tag: field.tag },
|
|
372
|
+
text: field.value
|
|
373
|
+
})
|
|
374
|
+
),
|
|
375
|
+
...input.getDataFields().map(
|
|
376
|
+
(field) => xmlUtils.createXmlElement("datafield", {
|
|
377
|
+
attributes: withoutEmptyValues({
|
|
378
|
+
tag: field.tag,
|
|
379
|
+
ind1: field.ind1,
|
|
380
|
+
ind2: field.ind2
|
|
381
|
+
}),
|
|
382
|
+
children: field.subfields.map(
|
|
383
|
+
(subfield) => xmlUtils.createXmlElement("subfield", {
|
|
384
|
+
attributes: { code: subfield.code },
|
|
385
|
+
text: subfield.value
|
|
386
|
+
})
|
|
387
|
+
)
|
|
388
|
+
})
|
|
389
|
+
)
|
|
390
|
+
];
|
|
391
|
+
const recordNode = xmlUtils.createXmlElement("record", {
|
|
392
|
+
attributes: { xmlns: "http://www.loc.gov/MARC21/slim" },
|
|
393
|
+
children: fields
|
|
394
|
+
});
|
|
395
|
+
return xmlUtils.serializeXml(recordNode, pretty);
|
|
396
|
+
}
|
|
397
|
+
var withoutEmptyValues = (obj) => Object.keys(obj).reduce(
|
|
398
|
+
(acc, key) => obj[key] === void 0 ? { ...acc } : { ...acc, [key]: obj[key] },
|
|
399
|
+
{}
|
|
400
|
+
);
|
|
401
|
+
|
|
334
402
|
exports.ControlField = ControlField;
|
|
335
403
|
exports.DataField = DataField;
|
|
404
|
+
exports.MarcParseError = MarcParseError;
|
|
336
405
|
exports.MarcRecord = MarcRecord;
|
|
337
406
|
exports.Subfield = Subfield;
|
|
338
407
|
exports.createControlField = createControlField;
|
|
339
408
|
exports.createDataField = createDataField;
|
|
340
409
|
exports.marcRecordZodSchema = marcRecordZodSchema;
|
|
341
410
|
exports.parseMarcXml = parseMarcXml;
|
|
411
|
+
exports.serializeLineMarc = serializeLineMarc;
|
|
412
|
+
exports.serializeMarcXml = serializeMarcXml;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { XmlElement } from '@biblioteksentralen/xml-utils';
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
|
|
3
4
|
type SerializedMarcField = SerializedDataField | SerializedControlField;
|
|
@@ -82,7 +83,6 @@ declare class MarcRecord {
|
|
|
82
83
|
getSubfieldValues(tag: string | RegExp, code: string | RegExp, indicators?: Indicators): string[];
|
|
83
84
|
getFirstSubfieldValue(tag: string | RegExp, code: string | RegExp, indicators?: Indicators): string | undefined;
|
|
84
85
|
toJSON(): SerializedMarcRecord;
|
|
85
|
-
toString(): string;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
interface MarcXmlOptions {
|
|
@@ -99,8 +99,18 @@ interface MarcXmlOptions {
|
|
|
99
99
|
* Free-form string that specifies the MARC record flavour.
|
|
100
100
|
*/
|
|
101
101
|
format?: string;
|
|
102
|
+
/**
|
|
103
|
+
* Whether to require at least one field to be present. Defaults to true.
|
|
104
|
+
*/
|
|
105
|
+
requireFields?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* If set to true, the parser throws an error if a record is missing indicators or have empty
|
|
108
|
+
* fields or subfields. If set to false, it will set default values for missing indicators and
|
|
109
|
+
* skip empty fields and subfields. Defaults to false.
|
|
110
|
+
*/
|
|
111
|
+
strict?: boolean;
|
|
102
112
|
}
|
|
103
|
-
declare function parseMarcXml(input: string, options?: MarcXmlOptions): MarcRecord[]
|
|
113
|
+
declare function parseMarcXml(input: string | XmlElement, options?: MarcXmlOptions): Promise<MarcRecord[]>;
|
|
104
114
|
|
|
105
115
|
declare const marcRecordZodSchema: z.ZodObject<{
|
|
106
116
|
format: z.ZodOptional<z.ZodString>;
|
|
@@ -177,4 +187,13 @@ declare const marcRecordZodSchema: z.ZodObject<{
|
|
|
177
187
|
format?: string | undefined;
|
|
178
188
|
}>;
|
|
179
189
|
|
|
180
|
-
|
|
190
|
+
declare function serializeLineMarc(input: MarcRecord): string;
|
|
191
|
+
|
|
192
|
+
declare function serializeMarcXml(input: MarcRecord, pretty?: boolean): string;
|
|
193
|
+
|
|
194
|
+
declare class MarcParseError extends Error {
|
|
195
|
+
readonly record: string;
|
|
196
|
+
constructor(message: string, record: string);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export { ControlField, DataField, type MarcField, MarcParseError, MarcRecord, type SerializedControlField, type SerializedDataField, type SerializedMarcField, type SerializedMarcRecord, Subfield, createControlField, createDataField, marcRecordZodSchema, parseMarcXml, serializeLineMarc, serializeMarcXml };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { XmlElement } from '@biblioteksentralen/xml-utils';
|
|
1
2
|
import { z } from 'zod';
|
|
2
3
|
|
|
3
4
|
type SerializedMarcField = SerializedDataField | SerializedControlField;
|
|
@@ -82,7 +83,6 @@ declare class MarcRecord {
|
|
|
82
83
|
getSubfieldValues(tag: string | RegExp, code: string | RegExp, indicators?: Indicators): string[];
|
|
83
84
|
getFirstSubfieldValue(tag: string | RegExp, code: string | RegExp, indicators?: Indicators): string | undefined;
|
|
84
85
|
toJSON(): SerializedMarcRecord;
|
|
85
|
-
toString(): string;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
interface MarcXmlOptions {
|
|
@@ -99,8 +99,18 @@ interface MarcXmlOptions {
|
|
|
99
99
|
* Free-form string that specifies the MARC record flavour.
|
|
100
100
|
*/
|
|
101
101
|
format?: string;
|
|
102
|
+
/**
|
|
103
|
+
* Whether to require at least one field to be present. Defaults to true.
|
|
104
|
+
*/
|
|
105
|
+
requireFields?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* If set to true, the parser throws an error if a record is missing indicators or have empty
|
|
108
|
+
* fields or subfields. If set to false, it will set default values for missing indicators and
|
|
109
|
+
* skip empty fields and subfields. Defaults to false.
|
|
110
|
+
*/
|
|
111
|
+
strict?: boolean;
|
|
102
112
|
}
|
|
103
|
-
declare function parseMarcXml(input: string, options?: MarcXmlOptions): MarcRecord[]
|
|
113
|
+
declare function parseMarcXml(input: string | XmlElement, options?: MarcXmlOptions): Promise<MarcRecord[]>;
|
|
104
114
|
|
|
105
115
|
declare const marcRecordZodSchema: z.ZodObject<{
|
|
106
116
|
format: z.ZodOptional<z.ZodString>;
|
|
@@ -177,4 +187,13 @@ declare const marcRecordZodSchema: z.ZodObject<{
|
|
|
177
187
|
format?: string | undefined;
|
|
178
188
|
}>;
|
|
179
189
|
|
|
180
|
-
|
|
190
|
+
declare function serializeLineMarc(input: MarcRecord): string;
|
|
191
|
+
|
|
192
|
+
declare function serializeMarcXml(input: MarcRecord, pretty?: boolean): string;
|
|
193
|
+
|
|
194
|
+
declare class MarcParseError extends Error {
|
|
195
|
+
readonly record: string;
|
|
196
|
+
constructor(message: string, record: string);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export { ControlField, DataField, type MarcField, MarcParseError, MarcRecord, type SerializedControlField, type SerializedDataField, type SerializedMarcField, type SerializedMarcRecord, Subfield, createControlField, createDataField, marcRecordZodSchema, parseMarcXml, serializeLineMarc, serializeMarcXml };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseXml } from '@biblioteksentralen/xml-utils';
|
|
1
|
+
import { parseXml, createXmlElement, serializeXml } from '@biblioteksentralen/xml-utils';
|
|
2
2
|
import { Ajv } from 'ajv';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
|
|
@@ -246,73 +246,79 @@ var MarcRecord = class _MarcRecord {
|
|
|
246
246
|
fields: this.fields.map((field) => field.toJSON())
|
|
247
247
|
};
|
|
248
248
|
}
|
|
249
|
-
toString() {
|
|
250
|
-
return this.fields.map((field) => field.toString()).join("\n");
|
|
251
|
-
}
|
|
252
249
|
};
|
|
253
250
|
|
|
254
|
-
// src/marc-record/
|
|
255
|
-
var
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
// the same schema as the original MARC 21 XML schema, but weakens restrictions to support
|
|
260
|
-
// other dialects than MARC 21 (not excluding the most esoteric ones).
|
|
261
|
-
"info:lc/xmlns/marcxchange-v1",
|
|
262
|
-
// Version 2 of MarcXchange adds support of embedded data, one of the many
|
|
263
|
-
// advanced XML features that a poor developer hopes not to encounter in the wild.
|
|
264
|
-
// Also weakens restrictions even further so that even a completely empty record is valid.
|
|
265
|
-
"info:lc/xmlns/marcxchange-v2"
|
|
266
|
-
];
|
|
267
|
-
function detectNamespace(input) {
|
|
268
|
-
for (const possibleNamespace of marcXmlNamespaces) {
|
|
269
|
-
if (input.indexOf(possibleNamespace) !== -1) {
|
|
270
|
-
return possibleNamespace;
|
|
271
|
-
}
|
|
251
|
+
// src/marc-record/MarcParseError.ts
|
|
252
|
+
var MarcParseError = class extends Error {
|
|
253
|
+
constructor(message, record) {
|
|
254
|
+
super(message);
|
|
255
|
+
this.record = record;
|
|
272
256
|
}
|
|
273
|
-
|
|
274
|
-
}
|
|
257
|
+
};
|
|
275
258
|
|
|
276
259
|
// src/marc-record/parseMarcXml.ts
|
|
277
|
-
function parseMarcXml(input, options = {}) {
|
|
278
|
-
const
|
|
279
|
-
const xmlRecord = parseXml(input, {
|
|
280
|
-
namespaces: {
|
|
281
|
-
marc: namespace
|
|
282
|
-
}
|
|
283
|
-
});
|
|
260
|
+
async function parseMarcXml(input, options = {}) {
|
|
261
|
+
const xmlRecord = typeof input === "string" ? await parseXml(input) : input;
|
|
284
262
|
const processControlField = options.processControlField ?? ((field) => field);
|
|
285
263
|
const processDataField = options.processDataField ?? ((field) => field);
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
264
|
+
const { requireFields = true } = options;
|
|
265
|
+
const records = getRecords(xmlRecord);
|
|
266
|
+
return records.map((marcRecord) => {
|
|
267
|
+
const parseError = (message) => new MarcParseError(message, serializeXml(marcRecord));
|
|
268
|
+
const leader = marcRecord.text("leader");
|
|
269
|
+
if (!leader) {
|
|
270
|
+
throw parseError("MARC record is missing leader");
|
|
271
|
+
}
|
|
272
|
+
const fields = marcRecord.children(/controlfield|datafield/).reduce((fields2, field) => {
|
|
289
273
|
const tag = field.attr("tag");
|
|
290
|
-
if (!tag)
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
274
|
+
if (!tag) return fields2;
|
|
275
|
+
if (field.name === "controlfield") {
|
|
276
|
+
const value = field.text();
|
|
277
|
+
if (!value && options.strict) {
|
|
278
|
+
throw parseError("MARC record control fields cannot be empty");
|
|
279
|
+
}
|
|
280
|
+
const newField = value ? processControlField(new ControlField(tag, value)) : void 0;
|
|
296
281
|
return newField ? [...fields2, newField] : fields2;
|
|
297
282
|
} else {
|
|
298
|
-
const subfields = field.
|
|
283
|
+
const subfields = field.children("subfield").reduce((subfields2, subfield) => {
|
|
299
284
|
const code = subfield.attr("code");
|
|
300
|
-
|
|
285
|
+
const value = subfield.text();
|
|
286
|
+
return code && value ? [...subfields2, new Subfield(code, value)] : subfields2;
|
|
301
287
|
}, []);
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
288
|
+
const ind1 = field.attr("ind1");
|
|
289
|
+
const ind2 = field.attr("ind2");
|
|
290
|
+
if (options.strict && (ind1 === void 0 || ind2 === void 0)) {
|
|
291
|
+
throw parseError("MARC record data fields must have indicators");
|
|
292
|
+
}
|
|
293
|
+
if (subfields.length === 0 && options.strict) {
|
|
294
|
+
throw parseError(
|
|
295
|
+
"MARC record data fields must have at least one subfield"
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
const newField = subfields.length ? processDataField(
|
|
299
|
+
new DataField(tag, ind1 ?? " ", ind2 ?? " ", subfields)
|
|
300
|
+
) : void 0;
|
|
310
301
|
return newField ? [...fields2, newField] : fields2;
|
|
311
302
|
}
|
|
312
303
|
}, []);
|
|
304
|
+
if (fields.length === 0 && requireFields) {
|
|
305
|
+
throw parseError("MARC record must have at least one field");
|
|
306
|
+
}
|
|
313
307
|
return new MarcRecord({ leader, fields, format: options.format });
|
|
314
308
|
});
|
|
315
309
|
}
|
|
310
|
+
function getRecords(node) {
|
|
311
|
+
switch (node.name) {
|
|
312
|
+
case "record":
|
|
313
|
+
return [node];
|
|
314
|
+
case "collection":
|
|
315
|
+
// MarcXchange
|
|
316
|
+
case "metadata":
|
|
317
|
+
return node.children("record");
|
|
318
|
+
default:
|
|
319
|
+
return [];
|
|
320
|
+
}
|
|
321
|
+
}
|
|
316
322
|
var controlFieldSchema = z.object({
|
|
317
323
|
tag: z.string().length(3, "MARC tag must be three characters long"),
|
|
318
324
|
value: z.string()
|
|
@@ -329,4 +335,66 @@ var marcRecordZodSchema = z.object({
|
|
|
329
335
|
fields: z.array(z.union([controlFieldSchema, dataFieldSchema]))
|
|
330
336
|
});
|
|
331
337
|
|
|
332
|
-
|
|
338
|
+
// src/marc-record/serializeLineMarc.ts
|
|
339
|
+
function serializeLineMarc(input) {
|
|
340
|
+
const leader = serializer.leader(input.leader);
|
|
341
|
+
const control = input.getControlFields().map(serializer.controlfield).join("");
|
|
342
|
+
const data = input.getDataFields().map(serializer.datafield).join("");
|
|
343
|
+
return `${leader}${control}${data}^
|
|
344
|
+
`;
|
|
345
|
+
}
|
|
346
|
+
var serializer = {
|
|
347
|
+
leader: (leader) => `*LDR ${leader}
|
|
348
|
+
`,
|
|
349
|
+
controlfield: (field) => `*${field.tag}${field.value}
|
|
350
|
+
`,
|
|
351
|
+
datafield: (field) => {
|
|
352
|
+
const ind1 = field.ind1 ?? " ";
|
|
353
|
+
const ind2 = field.ind2 ?? " ";
|
|
354
|
+
const subfields = field.subfields.map(
|
|
355
|
+
(subfield) => `$${subfield.code}${escapeSubfieldValue(subfield.value)}`
|
|
356
|
+
).join("");
|
|
357
|
+
return `*${field.tag}${ind1}${ind2}${subfields}
|
|
358
|
+
`;
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
var escapeSubfieldValue = (value) => {
|
|
362
|
+
return value.replace(/\$/g, "$$");
|
|
363
|
+
};
|
|
364
|
+
function serializeMarcXml(input, pretty = false) {
|
|
365
|
+
const fields = [
|
|
366
|
+
createXmlElement("leader", { text: input.leader }),
|
|
367
|
+
...input.getControlFields().map(
|
|
368
|
+
(field) => createXmlElement("controlfield", {
|
|
369
|
+
attributes: { tag: field.tag },
|
|
370
|
+
text: field.value
|
|
371
|
+
})
|
|
372
|
+
),
|
|
373
|
+
...input.getDataFields().map(
|
|
374
|
+
(field) => createXmlElement("datafield", {
|
|
375
|
+
attributes: withoutEmptyValues({
|
|
376
|
+
tag: field.tag,
|
|
377
|
+
ind1: field.ind1,
|
|
378
|
+
ind2: field.ind2
|
|
379
|
+
}),
|
|
380
|
+
children: field.subfields.map(
|
|
381
|
+
(subfield) => createXmlElement("subfield", {
|
|
382
|
+
attributes: { code: subfield.code },
|
|
383
|
+
text: subfield.value
|
|
384
|
+
})
|
|
385
|
+
)
|
|
386
|
+
})
|
|
387
|
+
)
|
|
388
|
+
];
|
|
389
|
+
const recordNode = createXmlElement("record", {
|
|
390
|
+
attributes: { xmlns: "http://www.loc.gov/MARC21/slim" },
|
|
391
|
+
children: fields
|
|
392
|
+
});
|
|
393
|
+
return serializeXml(recordNode, pretty);
|
|
394
|
+
}
|
|
395
|
+
var withoutEmptyValues = (obj) => Object.keys(obj).reduce(
|
|
396
|
+
(acc, key) => obj[key] === void 0 ? { ...acc } : { ...acc, [key]: obj[key] },
|
|
397
|
+
{}
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
export { ControlField, DataField, MarcParseError, MarcRecord, Subfield, createControlField, createDataField, marcRecordZodSchema, parseMarcXml, serializeLineMarc, serializeMarcXml };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@biblioteksentralen/marc",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "MARC record parser and serializer",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@vitest/coverage-v8": "^1.5.0",
|
|
38
38
|
"rimraf": "^5.0.5",
|
|
39
39
|
"tsup": "^8.0.2",
|
|
40
|
-
"typescript": "^5.
|
|
40
|
+
"typescript": "^5.6.2",
|
|
41
41
|
"vitest": "^1.5.0",
|
|
42
42
|
"@dataplattform/eslint-config": "1.0.0"
|
|
43
43
|
},
|