@natlibfi/marc-record
Version:
MARC record implementation in JavaScript
372 lines (366 loc) • 11.2 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.MarcRecord = void 0;
Object.defineProperty(exports, "MarcRecordError", {
enumerable: true,
get: function () {
return _error.default;
}
});
var _marcFieldSort = require("./marcFieldSort");
var _utils = require("./utils");
var _error = _interopRequireDefault(require("./error"));
var _debug = _interopRequireDefault(require("debug"));
/* eslint-disable functional/no-this-expressions */
const debug = (0, _debug.default)('@natlibfi/marc-record');
//const debugData = debug.extend('data');
const debugDev = debug.extend('dev');
// Default setting for validationOptions:
// These default validationOptions are (mostly) backwards compatible with marc-record-js < 7.3.0
//
// strict: false // All validationOptions below are set to true
// noFailValidation: false // Do not error if validation fails, return validationResults instead
//
// fields: true, // Do not allow record without fields
// subfields: true, // Do not allow empty subfields
// subfieldValues: true, // Do not allow subfields without value
// controlFieldValues: true // Do not allow controlFields without value
// leader: false, // Do not allow record without leader, with empty leader or with leader with length != 24
// characters: false // Do not allow erronous characters in tags, indicators and subfield codes
// noControlCharacters: false, // Do not allow ASCII control characters in field/subfield values
// noAdditionalProperties: false // Do not allow additional properties in fields
const validationOptionsDefaults = {
strict: false,
noFailValidation: false,
fields: true,
subfields: true,
subfieldValues: true,
controlFieldValues: true,
leader: false,
characters: false,
noControlCharacters: false,
noAdditionalProperties: false
};
let globalValidationOptions = {
...validationOptionsDefaults
}; // eslint-disable-line functional/no-let
class MarcRecord {
static setValidationOptions(options) {
globalValidationOptions = {
...validationOptionsDefaults,
...options
};
}
static getValidationOptions() {
return (0, _utils.clone)(globalValidationOptions);
}
constructor(record) {
let validationOptions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
this._validationOptions = validationOptions;
if (record) {
const recordClone = (0, _utils.clone)(record);
recordClone.leader = recordClone.leader || '';
recordClone.fields = recordClone.fields || [];
recordClone.fields.filter(_ref => {
let {
subfields
} = _ref;
return subfields;
}).forEach(field => {
field.ind1 = field.ind1 || ' ';
field.ind2 = field.ind2 || ' ';
});
this.leader = recordClone.leader;
this.fields = recordClone.fields;
this._validationErrors = (0, _utils.validateRecord)(recordClone, {
...globalValidationOptions,
...this._validationOptions
});
if (!this._validationOptions.noFailValidation) {
// eslint-disable-next-line functional/immutable-data
delete this._validationErrors;
return;
}
debugDev(`${JSON.stringify(this)}`);
return;
}
this.leader = '';
this.fields = [];
}
getValidationErrors() {
debugDev(`getting validationErrors: ${this._validationErrors} <-`);
if (!this._validationOptions.noFailValidation) {
return [];
}
return this._validationErrors || [];
}
get(query) {
return this.fields.filter(field => field.tag.match(query));
}
pop(query) {
const fields = this.get(query);
this.removeFields(fields);
return fields;
}
sortFields() {
this.fields.sort(_marcFieldSort.fieldOrderComparator); // eslint-disable-line functional/immutable-data
return this;
}
removeField(field) {
const index = this.fields.indexOf(field);
if (index !== -1) {
const {
fields: keepLastField
} = {
...globalValidationOptions,
...this._validationOptions
};
if (this.fields.length === 1 && keepLastField) {
throw new _error.default('Cannot remove last field');
}
this.fields.splice(index, 1); // eslint-disable-line functional/immutable-data
return this;
}
return this;
}
removeFields(fields) {
fields.forEach(f => this.removeField(f));
return this;
}
removeSubfield(subfield, field) {
// eslint-disable-line class-methods-use-this
const index = field.subfields.indexOf(subfield);
field.subfields.splice(index, 1); // eslint-disable-line functional/immutable-data
if (field.subfields.length === 0) {
return this.removeField(field);
}
return this;
}
appendField(field) {
this.insertField(field, this.fields.length);
return this;
}
appendFields(fields) {
fields.forEach(f => this.appendField(f));
return this;
}
insertField(field, index) {
const newField = Array.isArray(field) ? format(convertFromArray(field)) : format(field);
(0, _utils.validateField)(newField, {
...globalValidationOptions,
...this._validationOptions
});
this.fields.splice(index ?? this.findPosition(newField), 0, newField); //eslint-disable-line functional/immutable-data
return this;
function format(field) {
const cloned = (0, _utils.clone)(field);
if ('subfields' in field) {
return {
...cloned,
ind1: cloned.ind1 || ' ',
ind2: cloned.ind2 || ' '
};
}
return cloned;
}
function convertFromArray(args) {
if (field.length === 2) {
const [tag, value] = args;
return {
tag,
value
};
}
const [tag, ind1, ind2] = args;
const subfields = parseSubfields(args.slice(3));
return {
tag,
ind1,
ind2,
subfields
};
function parseSubfields(args) {
let subfields = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
const [code, value] = args;
if (code) {
return parseSubfields(args.slice(2), subfields.concat({
code,
value
}));
}
return subfields;
}
}
}
insertFields(fields) {
fields.forEach(f => this.insertField(f));
return this;
}
findPosition(fieldA) {
const index = this.fields.findIndex(fieldB => (0, _marcFieldSort.fieldOrderComparator)(fieldB, fieldA) > 0);
return index < 0 ? this.fields.length : index;
}
getControlfields() {
return this.fields.filter(field => 'value' in field);
}
getDatafields() {
return this.fields.filter(field => 'subfields' in field);
}
getFields(tag, query) {
const fields = this.fields.filter(f => f.tag === tag);
if (typeof query === 'string') {
return fields.filter(f => f.value === query);
}
if (Array.isArray(query)) {
return fields.filter(field => query.every(sfQuery => field.subfields.some(sf => sf.code === sfQuery.code && sf.value === sfQuery.value)));
}
return fields;
}
containsFieldWithValue(tag, query) {
return this.getFields(tag, query).length > 0;
}
getTypeOfRecord() {
return this.leader[6];
}
getBibliograpicLevel() {
return this.leader[7];
}
isBK() {
return ['a', 't'].includes(this.getTypeOfRecord()) && !this._bibliographicLevelIsBis();
}
isCF() {
return this.getTypeOfRecord() === 'm';
}
isCR() {
return ['a', 't'].includes(this.getTypeOfRecord()) && this._bibliographicLevelIsBis();
}
isMP() {
return ['e', 'f'].includes(this.getTypeOfRecord());
}
isMU() {
return ['c', 'd', 'i', 'j'].includes(this.getTypeOfRecord());
}
isMX() {
return this.getTypeOfRecord() === 'p';
}
isVM() {
return ['g', 'k', 'o', 'r'].includes(this.getTypeOfRecord());
}
getTypeOfMaterial() {
if (this.isBK()) {
return 'BK';
}
if (this.isCF()) {
return 'CF';
}
if (this.isCR()) {
return 'CR';
}
if (this.isMP()) {
return 'MP';
}
if (this.isMU()) {
return 'MU';
}
if (this.isMX()) {
return 'MX';
}
if (this.isVM()) {
return 'VM';
}
return false;
}
_bibliographicLevelIsBis() {
return ['b', 'i', 's'].includes(this.getBibliograpicLevel());
}
equalsTo(record) {
return MarcRecord.isEqual(this, record);
}
toString() {
return [].concat(`LDR ${this.leader}`, this.getControlfields().map(f => `${f.tag} ${f.value}`), this.getDatafields().map(mapDatafield)).join('\n');
function mapDatafield(f) {
return `${f.tag} ${f.ind1}${f.ind2} ‡${formatSubfields(f)}`;
function formatSubfields(field) {
return field.subfields.map(sf => `${sf.code}${sf.value || ''}`).join('‡');
}
}
}
toObject() {
return Object.entries((0, _utils.clone)(this)).filter(_ref2 => {
let [k] = _ref2;
return k.startsWith('_') === false;
}).reduce((acc, _ref3) => {
let [k, v] = _ref3;
return {
...acc,
[k]: v
};
}, {});
}
static fromString(str, validationOptions) {
const record = new MarcRecord(undefined, validationOptions);
str.split('\n').map(ln => ({
tag: ln.substr(0, 3),
ind1: ln.substr(4, 1),
ind2: ln.substr(5, 1),
data: ln.substr(7)
})).forEach(field => {
const {
tag,
ind1,
ind2,
data
} = field;
if (tag === 'LDR') {
record.leader = data; // eslint-disable-line functional/immutable-data
return;
}
if (data.substr(0, 1) === '‡') {
record.appendField({
tag,
ind1,
ind2,
subfields: parseSubfields(data)
});
return;
}
record.appendField({
tag,
value: data
});
});
return record;
function parseSubfields(str) {
return str.substr(1).split('‡').map(data => {
const code = data.substr(0, 1);
const value = data.substr(1);
return value ? {
code,
value
} : {
code
};
});
}
}
static clone(record) {
let validationOptions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
return new MarcRecord(record, validationOptions);
}
// This uses a strict string-to-string check but re-orders the object keys beforehand (MARC fields should be in same order, but the instance's properties order doesn't matter)
static isEqual(r1, r2) {
return JSON.stringify(reorder(r1)) === JSON.stringify(reorder(r2));
function reorder(obj) {
return Object.keys(obj).sort().reduce((acc, key) => ({
// eslint-disable-line functional/immutable-data
...acc,
[key]: typeof obj[key] === 'object' ? reorder(obj[key]) : obj[key]
}), {});
}
}
}
exports.MarcRecord = MarcRecord;
//# sourceMappingURL=index.js.map