@natlibfi/marc-record-validators-melinda 10.14.0 → 10.15.0-alpha.1
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/dist/access-rights.spec.js +2 -2
- package/dist/access-rights.spec.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/normalize-dashes.js +70 -0
- package/dist/normalize-dashes.js.map +1 -0
- package/dist/normalize-dashes.spec.js +51 -0
- package/dist/normalize-dashes.spec.js.map +1 -0
- package/dist/resolvable-ext-references-melinda.spec.js +2 -2
- package/dist/resolvable-ext-references-melinda.spec.js.map +1 -1
- package/dist/sortFields.js +182 -263
- package/dist/sortFields.js.map +1 -1
- package/package.json +3 -3
- package/src/access-rights.spec.js +2 -2
- package/src/index.js +2 -0
- package/src/normalize-dashes.js +69 -0
- package/src/normalize-dashes.spec.js +52 -0
- package/src/sortFields.js +169 -258
- package/test-fixtures/normalize-dashes/01/expectedResult.json +6 -0
- package/test-fixtures/normalize-dashes/01/metadata.json +6 -0
- package/test-fixtures/normalize-dashes/01/record.json +8 -0
- package/test-fixtures/normalize-dashes/02/expectedResult.json +10 -0
- package/test-fixtures/normalize-dashes/02/metadata.json +5 -0
- package/test-fixtures/normalize-dashes/02/record.json +8 -0
- package/test-fixtures/normalize-dashes/03/expectedResult.json +5 -0
- package/test-fixtures/normalize-dashes/03/metadata.json +5 -0
- package/test-fixtures/normalize-dashes/03/record.json +9 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//import createDebugLogger from 'debug';
|
|
2
|
+
import clone from 'clone';
|
|
3
|
+
import {fieldToString, isControlSubfieldCode, nvdebug} from './utils';
|
|
4
|
+
|
|
5
|
+
// Author(s): Nicholas Volk
|
|
6
|
+
export default function () {
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
description: 'Normalize various dashes to "-"',
|
|
10
|
+
validate, fix
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function fix(record) {
|
|
14
|
+
nvdebug(`FIX ME`);
|
|
15
|
+
record.fields.forEach(field => {
|
|
16
|
+
fixDashes(field);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const res = {message: [], fix: [], valid: true};
|
|
20
|
+
return res;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function validate(record) {
|
|
24
|
+
const res = {message: []};
|
|
25
|
+
|
|
26
|
+
nvdebug(`VALIDATE ME`);
|
|
27
|
+
record.fields?.forEach(field => {
|
|
28
|
+
validateField(field, res);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
res.valid = !(res.message.length >= 1); // eslint-disable-line functional/immutable-data
|
|
32
|
+
return res;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function validateField(field, res) {
|
|
36
|
+
const orig = fieldToString(field);
|
|
37
|
+
nvdebug(` VALIDATE FIELD '${orig}'`);
|
|
38
|
+
|
|
39
|
+
const normalizedField = fixDashes(clone(field));
|
|
40
|
+
const mod = fieldToString(normalizedField);
|
|
41
|
+
if (orig === mod) { // Fail as the input is "broken"/"crap"/sumthing
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
res.message.push(`'TODO: ${orig}' => '${mod}'`); // eslint-disable-line functional/immutable-data
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
function fixDashes(field) {
|
|
51
|
+
if (!field.subfields) {
|
|
52
|
+
return field;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
nvdebug(`Dashing ${fieldToString(field)}`);
|
|
56
|
+
|
|
57
|
+
field.subfields.forEach(sf => subfieldFixDashes(sf));
|
|
58
|
+
|
|
59
|
+
return field;
|
|
60
|
+
|
|
61
|
+
function subfieldFixDashes(subfield) {
|
|
62
|
+
if (isControlSubfieldCode(subfield.code)) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
// Normalize dashes U+2010 ... U+2015 to '-':
|
|
66
|
+
subfield.value = subfield.value.replace(/[\u2010\u2011\u2012\u2013\u2014\u2015]/ug, '-'); // eslint-disable-line functional/immutable-data
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import {expect} from 'chai';
|
|
2
|
+
import {MarcRecord} from '@natlibfi/marc-record';
|
|
3
|
+
import validatorFactory from './normalize-dashes';
|
|
4
|
+
import {READERS} from '@natlibfi/fixura';
|
|
5
|
+
import generateTests from '@natlibfi/fixugen';
|
|
6
|
+
import createDebugLogger from 'debug';
|
|
7
|
+
|
|
8
|
+
generateTests({
|
|
9
|
+
callback,
|
|
10
|
+
path: [__dirname, '..', 'test-fixtures', 'normalize-dashes'],
|
|
11
|
+
useMetadataFile: true,
|
|
12
|
+
recurse: false,
|
|
13
|
+
fixura: {
|
|
14
|
+
reader: READERS.JSON
|
|
15
|
+
},
|
|
16
|
+
mocha: {
|
|
17
|
+
before: () => testValidatorFactory()
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
const debug = createDebugLogger('@natlibfi/marc-record-validators-melinda/normalize-dashes:test');
|
|
21
|
+
|
|
22
|
+
async function testValidatorFactory() {
|
|
23
|
+
const validator = await validatorFactory();
|
|
24
|
+
|
|
25
|
+
expect(validator)
|
|
26
|
+
.to.be.an('object')
|
|
27
|
+
.that.has.any.keys('description', 'validate');
|
|
28
|
+
|
|
29
|
+
expect(validator.description).to.be.a('string');
|
|
30
|
+
expect(validator.validate).to.be.a('function');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function callback({getFixture, enabled = true, fix = false}) {
|
|
34
|
+
if (enabled === false) {
|
|
35
|
+
debug('TEST SKIPPED!');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const validator = await validatorFactory();
|
|
40
|
+
const record = new MarcRecord(getFixture('record.json'));
|
|
41
|
+
const expectedResult = getFixture('expectedResult.json');
|
|
42
|
+
// console.log(expectedResult); // eslint-disable-line
|
|
43
|
+
|
|
44
|
+
if (!fix) {
|
|
45
|
+
const result = await validator.validate(record);
|
|
46
|
+
expect(result).to.eql(expectedResult);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await validator.fix(record);
|
|
51
|
+
expect(record).to.eql(expectedResult);
|
|
52
|
+
}
|
package/src/sortFields.js
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
// Taken from project marc-record-js, file marcSortFields.js as this contains more and more Melinda-specific rules.
|
|
2
2
|
|
|
3
3
|
import clone from 'clone';
|
|
4
|
-
import createDebugLogger from 'debug';
|
|
4
|
+
//import createDebugLogger from 'debug';
|
|
5
5
|
import {fieldHasSubfield, fieldToString} from './utils';
|
|
6
|
+
import {sortByTag, sortAlphabetically, fieldOrderComparator as globalFieldOrderComparator} from '@natlibfi/marc-record/dist/marcFieldSort';
|
|
6
7
|
import {isValidSubfield8} from './subfield8Utils';
|
|
7
8
|
import {isValidSubfield6, subfield6GetOccurrenceNumber} from './subfield6Utils';
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
const BIG_BAD_NUMBER = 999.99;
|
|
11
|
-
|
|
12
|
-
const debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:sortFields');
|
|
10
|
+
//const debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:sortFields');
|
|
13
11
|
//const debugData = debug.extend('data');
|
|
14
|
-
const debugDev = debug.extend('dev');
|
|
12
|
+
//const debugDev = debug.extend('dev');
|
|
15
13
|
|
|
14
|
+
const BIG_BAD_NUMBER = 999999999;
|
|
16
15
|
export default function () {
|
|
17
16
|
|
|
18
17
|
return {
|
|
19
|
-
description: 'Sort fields',
|
|
18
|
+
description: 'Sort fields using both generic and Melinda specific rules',
|
|
20
19
|
validate, fix
|
|
21
20
|
};
|
|
22
21
|
|
|
@@ -84,310 +83,222 @@ export function scoreRelatorTerm(value) { // sortRelatorTerms.js validator shoul
|
|
|
84
83
|
|
|
85
84
|
export function fieldOrderComparator(fieldA, fieldB) {
|
|
86
85
|
|
|
86
|
+
//const sorterFunctions = [sortByTag, sortByIndexTerms, sortAlphabetically, sortByRelatorTerm, sortByOccurrenceNumber, preferFenniKeep, sortByFieldLinkAndSequenceNumber];
|
|
87
|
+
|
|
87
88
|
const sorterFunctions = [sortByTag, sortByIndexTerms, sortAlphabetically, sortByRelatorTerm, sortByOccurrenceNumber, preferFenniKeep, sortByFieldLinkAndSequenceNumber];
|
|
89
|
+
//const sorterFunctions = [sortByIndexTerms, sortByRelatorTerm, sortByOccurrenceNumber, preferFenniKeep, sortByFieldLinkAndSequenceNumber];
|
|
88
90
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
debugDev(`${sortFn.name}: '${fieldToString(fieldA)}' vs '${fieldToString(fieldB)}': ${result}`);
|
|
92
|
-
if (result !== 0) {
|
|
93
|
-
return result;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
91
|
+
return globalFieldOrderComparator(fieldA, fieldB, sorterFunctions);
|
|
92
|
+
}
|
|
96
93
|
|
|
97
|
-
|
|
94
|
+
function sortByIndexTerms(fieldA, fieldB) { // eslint-disable-line complexity, max-statements
|
|
98
95
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
function getSortIndex(tag) {
|
|
102
|
-
const sortIndex = {
|
|
103
|
-
LDR: '000',
|
|
104
|
-
STA: '001.1', // STA comes now after 001. However 003+001 form a combo, so I'm not sure...
|
|
105
|
-
SID: '999.1',
|
|
106
|
-
LOW: '999.2',
|
|
107
|
-
CAT: '999.3',
|
|
108
|
-
HLI: '999.4'
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
if (tag in sortIndex) { // <- this allows weights for numeric values as well (not that we use them yet)
|
|
112
|
-
return sortIndex[tag];
|
|
113
|
-
}
|
|
114
|
-
if (isNaN(tag)) {
|
|
115
|
-
return '999.9';
|
|
116
|
-
}
|
|
117
|
-
return tag;
|
|
118
|
-
}
|
|
96
|
+
const indexTermFields = ['600', '610', '611', '630', '648', '650', '651', '652', '653', '654', '655', '656', '657', '658', '659', '662'];
|
|
119
97
|
|
|
120
|
-
|
|
121
|
-
const
|
|
98
|
+
function scoreInd2(val) {
|
|
99
|
+
const ind2Score = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 8, '5': 5, '6': 6, '7': 7};
|
|
122
100
|
|
|
123
|
-
if (
|
|
124
|
-
return
|
|
125
|
-
}
|
|
126
|
-
if (orderA < orderB) {
|
|
127
|
-
return -1;
|
|
101
|
+
if (val in ind2Score) {
|
|
102
|
+
return ind2Score[val];
|
|
128
103
|
}
|
|
104
|
+
return 9;
|
|
105
|
+
}
|
|
129
106
|
|
|
107
|
+
// ATM this is not needed.
|
|
108
|
+
// You may need this, if you change compare function order in sorterFunctions
|
|
109
|
+
// istanbul ignore next
|
|
110
|
+
if (fieldA.tag !== fieldB.tag) {
|
|
130
111
|
return 0;
|
|
131
112
|
}
|
|
132
113
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
114
|
+
if (!indexTermFields.includes(fieldA.tag)) {
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
136
117
|
|
|
137
|
-
|
|
138
|
-
|
|
118
|
+
// Puts ind2=4 last
|
|
119
|
+
if (scoreInd2(fieldA.ind2) > scoreInd2(fieldB.ind2)) {
|
|
120
|
+
return 1;
|
|
121
|
+
}
|
|
122
|
+
if (scoreInd2(fieldA.ind2) < scoreInd2(fieldB.ind2)) {
|
|
123
|
+
return -1;
|
|
124
|
+
}
|
|
139
125
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
126
|
+
function scoreDictionary(dictionary) {
|
|
127
|
+
const dictionarySortIndex = {
|
|
128
|
+
'yso/fin': 0,
|
|
129
|
+
'yso/swe': 1,
|
|
130
|
+
'yso/eng': 2,
|
|
131
|
+
'slm/fin': 0.1,
|
|
132
|
+
'slm/swe': 1.1,
|
|
133
|
+
'kauno/fin': 2.1,
|
|
134
|
+
'kauno/swe': 2.2,
|
|
135
|
+
'kaunokki': 4,
|
|
136
|
+
'bella': 5
|
|
137
|
+
};
|
|
145
138
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
// istanbul ignore next
|
|
149
|
-
if (fieldA.tag !== fieldB.tag) {
|
|
150
|
-
return 0;
|
|
139
|
+
if (dictionary in dictionarySortIndex) {
|
|
140
|
+
return dictionarySortIndex[dictionary];
|
|
151
141
|
}
|
|
142
|
+
return BIG_BAD_NUMBER;
|
|
143
|
+
}
|
|
152
144
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
145
|
+
const dictionaryA = selectFirstValue(fieldA, '2');
|
|
146
|
+
const dictionaryB = selectFirstValue(fieldB, '2');
|
|
156
147
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
148
|
+
const dictScoreA = scoreDictionary(dictionaryA);
|
|
149
|
+
const dictScoreB = scoreDictionary(dictionaryB);
|
|
150
|
+
// Use priority order for listed dictionaries:
|
|
151
|
+
if (dictScoreA > dictScoreB) {
|
|
152
|
+
return 1;
|
|
153
|
+
}
|
|
154
|
+
if (dictScoreA < dictScoreB) {
|
|
155
|
+
return -1;
|
|
156
|
+
}
|
|
157
|
+
// Unlisted dictionaries: sort $2 value alphabetically:
|
|
158
|
+
//if (dictScoreA === BIG_BAD_NUMBER) {
|
|
159
|
+
if (dictionaryA > dictionaryB) {
|
|
160
|
+
return 1;
|
|
161
|
+
}
|
|
162
|
+
if (dictionaryA < dictionaryB) {
|
|
163
|
+
return -1;
|
|
164
|
+
}
|
|
165
|
+
//}
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
164
168
|
|
|
165
|
-
function scoreDictionary(dictionary) {
|
|
166
|
-
const dictionarySortIndex = {
|
|
167
|
-
'yso/fin': 0,
|
|
168
|
-
'yso/swe': 1,
|
|
169
|
-
'yso/eng': 2,
|
|
170
|
-
'slm/fin': 0.1,
|
|
171
|
-
'slm/swe': 1.1,
|
|
172
|
-
'kauno/fin': 2.1,
|
|
173
|
-
'kauno/swe': 2.2,
|
|
174
|
-
'kaunokki': 4,
|
|
175
|
-
'bella': 5
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
if (dictionary in dictionarySortIndex) {
|
|
179
|
-
return dictionarySortIndex[dictionary];
|
|
180
|
-
}
|
|
181
|
-
return BIG_BAD_NUMBER;
|
|
182
|
-
}
|
|
183
169
|
|
|
184
|
-
|
|
185
|
-
|
|
170
|
+
function preferKeep(fieldA, fieldB, keepOwner = 'FENNI') {
|
|
171
|
+
const hasKeepA = fieldHasSubfield(fieldA, '9', `${keepOwner}<KEEP>`);
|
|
172
|
+
const hasKeepB = fieldHasSubfield(fieldB, '9', `${keepOwner}<KEEP>`);
|
|
186
173
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}
|
|
193
|
-
if (dictScoreA < dictScoreB) {
|
|
194
|
-
return -1;
|
|
195
|
-
}
|
|
196
|
-
// Unlisted dictionaries: sort $2 value alphabetically:
|
|
197
|
-
//if (dictScoreA === BIG_BAD_NUMBER) {
|
|
198
|
-
if (dictionaryA > dictionaryB) {
|
|
199
|
-
return 1;
|
|
200
|
-
}
|
|
201
|
-
if (dictionaryA < dictionaryB) {
|
|
202
|
-
return -1;
|
|
203
|
-
}
|
|
204
|
-
//}
|
|
205
|
-
return 0;
|
|
174
|
+
if (hasKeepA && !hasKeepB) {
|
|
175
|
+
return -1;
|
|
176
|
+
}
|
|
177
|
+
if (!hasKeepA && hasKeepB) {
|
|
178
|
+
return 1;
|
|
206
179
|
}
|
|
207
180
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const hasKeepB = fieldHasSubfield(fieldB, '9', `${keepOwner}<KEEP>`);
|
|
181
|
+
return 0;
|
|
182
|
+
}
|
|
211
183
|
|
|
212
|
-
if (hasKeepA && !hasKeepB) {
|
|
213
|
-
return -1;
|
|
214
|
-
}
|
|
215
|
-
if (!hasKeepA && hasKeepB) {
|
|
216
|
-
return 1;
|
|
217
|
-
}
|
|
218
184
|
|
|
219
|
-
|
|
185
|
+
function preferFenniKeep(fieldA, fieldB) {
|
|
186
|
+
const fenniPreference = preferKeep(fieldA, fieldB, 'FENNI');
|
|
187
|
+
if (fenniPreference !== 0) {
|
|
188
|
+
return fenniPreference;
|
|
189
|
+
}
|
|
190
|
+
const violaPreference = preferKeep(fieldA, fieldB, 'VIOLA');
|
|
191
|
+
if (violaPreference !== 0) {
|
|
192
|
+
return violaPreference;
|
|
220
193
|
}
|
|
194
|
+
return preferKeep(fieldA, fieldB, 'FIKKA');
|
|
195
|
+
}
|
|
221
196
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
const violaPreference = preferKeep(fieldA, fieldB, 'VIOLA');
|
|
228
|
-
if (violaPreference !== 0) {
|
|
229
|
-
return violaPreference;
|
|
230
|
-
}
|
|
231
|
-
return preferKeep(fieldA, fieldB, 'FIKKA');
|
|
197
|
+
function sortByRelatorTerm(fieldA, fieldB) {
|
|
198
|
+
// Should this be done to 6XX and 8XX fields as well? Does $t affect sorting?
|
|
199
|
+
if (!['700', '710', '711', '730'].includes(fieldA.tag)) {
|
|
200
|
+
return 0;
|
|
232
201
|
}
|
|
233
202
|
|
|
234
|
-
function
|
|
235
|
-
|
|
236
|
-
if (!['700', '710', '711', '730'].includes(fieldA.tag)) {
|
|
203
|
+
function fieldGetMaxRelatorTermScore(field) {
|
|
204
|
+
if (!field.subfields) {
|
|
237
205
|
return 0;
|
|
238
206
|
}
|
|
207
|
+
const e = field.subfields.filter(sf => sf.code === 'e');
|
|
208
|
+
const scores = e.map(sf => scoreRelatorTerm(sf.value));
|
|
209
|
+
//debugDev(`RELATOR SCORE FOR '${fieldToString(field)}': ${scores.join(', ')}`);
|
|
210
|
+
return Math.max(...scores);
|
|
211
|
+
}
|
|
239
212
|
|
|
213
|
+
const scoreA = fieldGetMaxRelatorTermScore(fieldA);
|
|
214
|
+
const scoreB = fieldGetMaxRelatorTermScore(fieldB);
|
|
240
215
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
216
|
+
if (scoreA < scoreB) {
|
|
217
|
+
return 1;
|
|
218
|
+
}
|
|
219
|
+
if (scoreA > scoreB) {
|
|
220
|
+
return -1;
|
|
221
|
+
}
|
|
222
|
+
return 0;
|
|
223
|
+
}
|
|
250
224
|
|
|
251
|
-
const scoreA = fieldGetMaxRelatorTermScore(fieldA);
|
|
252
|
-
const scoreB = fieldGetMaxRelatorTermScore(fieldB);
|
|
253
225
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
if (scoreA > scoreB) {
|
|
258
|
-
return -1;
|
|
259
|
-
}
|
|
260
|
-
return 0;
|
|
226
|
+
function fieldGetMinLinkAndSequenceNumber(field) {
|
|
227
|
+
if (!field.subfields) {
|
|
228
|
+
return BIG_BAD_NUMBER;
|
|
261
229
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const relevantSubfields = field.subfields.filter(sf => isValidSubfield8(sf));
|
|
268
|
-
// If val is something like "1.2\x" parseFloat() would give a syntax erro because of hex-like escape sequnce (at least on Chrome). Thus remove tail:
|
|
269
|
-
const scores = relevantSubfields.map(sf => parseFloat(sf.value.replace(/\\.*$/u, '')));
|
|
270
|
-
if (scores.length === 0) {
|
|
271
|
-
return BIG_BAD_NUMBER;
|
|
272
|
-
}
|
|
273
|
-
return Math.min(...scores);
|
|
230
|
+
const relevantSubfields = field.subfields.filter(sf => isValidSubfield8(sf));
|
|
231
|
+
// If val is something like "1.2\x" parseFloat() would give a syntax erro because of hex-like escape sequnce (at least on Chrome). Thus remove tail:
|
|
232
|
+
const scores = relevantSubfields.map(sf => parseFloat(sf.value.replace(/\\.*$/u, '')));
|
|
233
|
+
if (scores.length === 0) {
|
|
234
|
+
return BIG_BAD_NUMBER;
|
|
274
235
|
}
|
|
236
|
+
return Math.min(...scores);
|
|
237
|
+
}
|
|
275
238
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
return -1;
|
|
289
|
-
}
|
|
290
|
-
if (scoreA > scoreB) { // smaller is better
|
|
291
|
-
return 1;
|
|
292
|
-
}
|
|
239
|
+
function sortByFieldLinkAndSequenceNumber(fieldA, fieldB) { // Sort by subfield $8 that is...
|
|
240
|
+
const scoreA = fieldGetMinLinkAndSequenceNumber(fieldA);
|
|
241
|
+
const scoreB = fieldGetMinLinkAndSequenceNumber(fieldB);
|
|
242
|
+
//debugDev(` sf-8-A-score for '${fieldToString(fieldA)}: ${scoreA}`);
|
|
243
|
+
//debugDev(` sf-8-B-score for '${fieldToString(fieldB)}: ${scoreB}`);
|
|
244
|
+
if (scoreA === scoreB) {
|
|
245
|
+
return 0;
|
|
246
|
+
}
|
|
247
|
+
if (scoreB === 0) {
|
|
248
|
+
return 1;
|
|
249
|
+
}
|
|
250
|
+
if (scoreA === 0) {
|
|
293
251
|
return -1;
|
|
294
252
|
}
|
|
253
|
+
if (scoreA > scoreB) { // smaller is better
|
|
254
|
+
return 1;
|
|
255
|
+
}
|
|
256
|
+
return -1;
|
|
257
|
+
}
|
|
295
258
|
|
|
296
|
-
function sortByOccurrenceNumber(fieldA, fieldB) { // Sort by subfield $6
|
|
297
259
|
|
|
298
|
-
|
|
299
|
-
if (!field.subfields) {
|
|
300
|
-
return 0;
|
|
301
|
-
}
|
|
302
|
-
const subfield6 = field.subfields.find(sf => isValidSubfield6(sf));
|
|
303
|
-
if (subfield6 === undefined) {
|
|
304
|
-
return 0;
|
|
305
|
-
}
|
|
306
|
-
return parseInt(subfield6GetOccurrenceNumber(subfield6), 10);
|
|
307
|
-
}
|
|
260
|
+
function sortByOccurrenceNumber(fieldA, fieldB) { // Sort by subfield $6
|
|
308
261
|
|
|
309
|
-
|
|
262
|
+
function fieldGetOccurrenceNumber(field) { // should this function be exported? (based on validator sortRelatorFields.js)
|
|
263
|
+
if (!field.subfields) {
|
|
310
264
|
return 0;
|
|
311
265
|
}
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
//debugDev(`A: '${fieldToString(fieldA)}: ${scoreA}`);
|
|
316
|
-
//debugDev(`B: '${fieldToString(fieldB)}: ${scoreB}`);
|
|
317
|
-
|
|
318
|
-
if (scoreA === scoreB) {
|
|
266
|
+
const subfield6 = field.subfields.find(sf => isValidSubfield6(sf));
|
|
267
|
+
if (subfield6 === undefined) {
|
|
319
268
|
return 0;
|
|
320
269
|
}
|
|
321
|
-
|
|
322
|
-
return -1;
|
|
323
|
-
}
|
|
324
|
-
if (scoreA === 0) {
|
|
325
|
-
return 1;
|
|
326
|
-
}
|
|
327
|
-
if (scoreA > scoreB) { // smaller is better
|
|
328
|
-
return 1;
|
|
329
|
-
}
|
|
330
|
-
return -1;
|
|
270
|
+
return parseInt(subfield6GetOccurrenceNumber(subfield6), 10);
|
|
331
271
|
}
|
|
332
272
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
function scoreSubfieldsAlphabetically(setOfSubfields) {
|
|
341
|
-
if (setOfSubfields.length === 0) {
|
|
342
|
-
return 0;
|
|
343
|
-
}
|
|
344
|
-
const [subfieldCode, ...remainingSubfieldCodes] = setOfSubfields;
|
|
345
|
-
const valA = selectFirstValue(fieldA, subfieldCode);
|
|
346
|
-
const valB = selectFirstValue(fieldB, subfieldCode);
|
|
347
|
-
//debugDev(`CHECKING SUBFIELD '${subfieldCode}'`);
|
|
348
|
-
if (!valA) {
|
|
349
|
-
if (!valB) {
|
|
350
|
-
return scoreSubfieldsAlphabetically(remainingSubfieldCodes);
|
|
351
|
-
}
|
|
352
|
-
return -1;
|
|
353
|
-
}
|
|
354
|
-
if (!valB) {
|
|
355
|
-
return 1;
|
|
356
|
-
}
|
|
357
|
-
//debugDev(`CHECKING SUBFIELD '${subfieldCode}': '${valA}' vs '${valB}'`);
|
|
358
|
-
|
|
359
|
-
if (valA < valB) {
|
|
360
|
-
return -1;
|
|
361
|
-
}
|
|
362
|
-
if (valB < valA) {
|
|
363
|
-
return 1;
|
|
364
|
-
}
|
|
365
|
-
return scoreSubfieldsAlphabetically(remainingSubfieldCodes);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
if (fieldA.tag === fieldB.tag) {
|
|
369
|
-
if (!(fieldA.tag in tagToSortingSubfields)) {
|
|
370
|
-
return 0;
|
|
371
|
-
}
|
|
273
|
+
if (fieldA.tag !== '880') {
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
const scoreA = fieldGetOccurrenceNumber(fieldA);
|
|
277
|
+
const scoreB = fieldGetOccurrenceNumber(fieldB);
|
|
372
278
|
|
|
373
|
-
|
|
279
|
+
//debugDev(`A: '${fieldToString(fieldA)}: ${scoreA}`);
|
|
280
|
+
//debugDev(`B: '${fieldToString(fieldB)}: ${scoreB}`);
|
|
374
281
|
|
|
375
|
-
|
|
376
|
-
const result = scoreSubfieldsAlphabetically(subfieldsToCheck);
|
|
377
|
-
//debugDev(`RESULT ${result}`);
|
|
378
|
-
return result;
|
|
379
|
-
}
|
|
282
|
+
if (scoreA === scoreB) {
|
|
380
283
|
return 0;
|
|
381
284
|
}
|
|
382
|
-
|
|
383
|
-
|
|
285
|
+
if (scoreB === 0) {
|
|
286
|
+
return -1;
|
|
287
|
+
}
|
|
288
|
+
if (scoreA === 0) {
|
|
289
|
+
return 1;
|
|
290
|
+
}
|
|
291
|
+
if (scoreA > scoreB) { // smaller is better
|
|
292
|
+
return 1;
|
|
293
|
+
}
|
|
294
|
+
return -1;
|
|
295
|
+
}
|
|
384
296
|
|
|
385
297
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
}
|
|
298
|
+
function selectFirstValue(field, subcode) {
|
|
299
|
+
return field.subfields
|
|
300
|
+
.filter(subfield => subcode === subfield.code)
|
|
301
|
+
.map(subfield => subfield.value)
|
|
302
|
+
.slice(0, 1);
|
|
392
303
|
}
|
|
393
304
|
|