@natlibfi/marc-record-validators-melinda 11.3.1 → 11.3.2

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.
Files changed (80) hide show
  1. package/dist/index.js +7 -0
  2. package/dist/index.js.map +1 -1
  3. package/dist/melindaCustomMergeFields.json +5120 -0
  4. package/dist/merge-fields/config.json +83 -0
  5. package/dist/merge-fields/controlSubfields.js +278 -0
  6. package/dist/merge-fields/controlSubfields.js.map +1 -0
  7. package/dist/merge-fields/counterpartField.js +674 -0
  8. package/dist/merge-fields/counterpartField.js.map +1 -0
  9. package/dist/merge-fields/index.js +76 -0
  10. package/dist/merge-fields/index.js.map +1 -0
  11. package/dist/merge-fields/mergableIndicator.js +95 -0
  12. package/dist/merge-fields/mergableIndicator.js.map +1 -0
  13. package/dist/merge-fields/mergableTag.js +33 -0
  14. package/dist/merge-fields/mergableTag.js.map +1 -0
  15. package/dist/merge-fields/mergeConstraints.js +1225 -0
  16. package/dist/merge-fields/mergeConstraints.js.map +1 -0
  17. package/dist/merge-fields/mergeField.js +190 -0
  18. package/dist/merge-fields/mergeField.js.map +1 -0
  19. package/dist/merge-fields/mergeIndicator.js +171 -0
  20. package/dist/merge-fields/mergeIndicator.js.map +1 -0
  21. package/dist/merge-fields/mergeOrAddPostprocess.js +57 -0
  22. package/dist/merge-fields/mergeOrAddPostprocess.js.map +1 -0
  23. package/dist/merge-fields/mergeOrAddSubfield.js +203 -0
  24. package/dist/merge-fields/mergeOrAddSubfield.js.map +1 -0
  25. package/dist/merge-fields/mergeSubfield.js +277 -0
  26. package/dist/merge-fields/mergeSubfield.js.map +1 -0
  27. package/dist/merge-fields/removeDuplicateSubfields.js +48 -0
  28. package/dist/merge-fields/removeDuplicateSubfields.js.map +1 -0
  29. package/dist/merge-fields/worldKnowledge.js +98 -0
  30. package/dist/merge-fields/worldKnowledge.js.map +1 -0
  31. package/dist/merge-fields.spec.js +51 -0
  32. package/dist/merge-fields.spec.js.map +1 -0
  33. package/dist/subfield6Utils.js +16 -1
  34. package/dist/subfield6Utils.js.map +1 -1
  35. package/dist/utils.js +108 -0
  36. package/dist/utils.js.map +1 -1
  37. package/package.json +6 -6
  38. package/src/index.js +3 -1
  39. package/src/melindaCustomMergeFields.json +5120 -0
  40. package/src/merge-fields/config.json +83 -0
  41. package/src/merge-fields/controlSubfields.js +307 -0
  42. package/src/merge-fields/counterpartField.js +736 -0
  43. package/src/merge-fields/index.js +69 -0
  44. package/src/merge-fields/mergableIndicator.js +90 -0
  45. package/src/merge-fields/mergableTag.js +89 -0
  46. package/src/merge-fields/mergeConstraints.js +309 -0
  47. package/src/merge-fields/mergeField.js +187 -0
  48. package/src/merge-fields/mergeIndicator.js +185 -0
  49. package/src/merge-fields/mergeOrAddPostprocess.js +56 -0
  50. package/src/merge-fields/mergeOrAddSubfield.js +218 -0
  51. package/src/merge-fields/mergeSubfield.js +306 -0
  52. package/src/merge-fields/removeDuplicateSubfields.js +50 -0
  53. package/src/merge-fields/worldKnowledge.js +104 -0
  54. package/src/merge-fields.spec.js +52 -0
  55. package/src/subfield6Utils.js +14 -1
  56. package/src/utils.js +119 -0
  57. package/test-fixtures/merge-fields/f01/expectedResult.json +11 -0
  58. package/test-fixtures/merge-fields/f01/metadata.json +5 -0
  59. package/test-fixtures/merge-fields/f01/record.json +13 -0
  60. package/test-fixtures/merge-fields/f02/expectedResult.json +14 -0
  61. package/test-fixtures/merge-fields/f02/metadata.json +6 -0
  62. package/test-fixtures/merge-fields/f02/record.json +16 -0
  63. package/test-fixtures/merge-fields/f03/expectedResult.json +17 -0
  64. package/test-fixtures/merge-fields/f03/metadata.json +7 -0
  65. package/test-fixtures/merge-fields/f03/record.json +23 -0
  66. package/test-fixtures/merge-fields/f04/expectedResult.json +14 -0
  67. package/test-fixtures/merge-fields/f04/metadata.json +5 -0
  68. package/test-fixtures/merge-fields/f04/record.json +19 -0
  69. package/test-fixtures/merge-fields/v01/expectedResult.json +6 -0
  70. package/test-fixtures/merge-fields/v01/metadata.json +5 -0
  71. package/test-fixtures/merge-fields/v01/record.json +13 -0
  72. package/test-fixtures/merge-fields/v02/expectedResult.json +4 -0
  73. package/test-fixtures/merge-fields/v02/metadata.json +5 -0
  74. package/test-fixtures/merge-fields/v02/record.json +13 -0
  75. package/test-fixtures/merge-fields/v03/expectedResult.json +6 -0
  76. package/test-fixtures/merge-fields/v03/metadata.json +6 -0
  77. package/test-fixtures/merge-fields/v03/record.json +16 -0
  78. package/test-fixtures/merge-fields/v04/expectedResult.json +4 -0
  79. package/test-fixtures/merge-fields/v04/metadata.json +6 -0
  80. package/test-fixtures/merge-fields/v04/record.json +16 -0
@@ -0,0 +1,83 @@
1
+ {
2
+ "mergeConfiguration" :
3
+ {
4
+ "comment #1" : "Meaningless indicators (=indicators having but one legal value) are derived from melindaCustomMergeFields.",
5
+ "comment #2" : "Meaningless indicators and non-filing indicators never prevent merge. (Hard-coded in mergableIndicator.js)",
6
+ "comment #3" : "When merging, indicator preference defaults are defined in mergeIndicators.js. However, these can be overridden here.",
7
+ "indicator1PreferredValues": {
8
+ "022" : {"0": 1, "1": 1, " ": 2},
9
+ "041" : {"0": 1, "1": 1, " ": 2},
10
+ "246" : {"0": 1, "1": 1, "2": 1, "3": 1, " ": 2},
11
+ "341" : {"0": 1, "1": 1, " ": 2},
12
+ "363" : {"0": 1, "1": 1, " ": 2},
13
+ "382" : {"0": 1, "1": 1, "2": 1, "3": 1, " ": 2},
14
+ "384" : {"0": 1, "1": 1, " ": 2},
15
+ "388" : {"0": 1, "1": 1, " ": 2},
16
+ "490" : [ "1", "0" ],
17
+ "505" : [ "8", "0", "2", "1" ],
18
+ "506" : {"0": 1, "1": 1, " ": 2},
19
+ "541" : {"0": 1, "1": 1, " ": 2},
20
+ "542" : {"0": 1, "1": 1, " ": 2},
21
+ "544" : {"0": 1, "1": 1, " ": 2},
22
+ "545" : {"0": 1, "1": 1, " ": 2},
23
+ "561" : {"0": 1, "1": 1, " ": 2},
24
+ "583" : {"0": 1, "1": 1, " ": 2},
25
+ "588" : {"0": 1, "1": 1, " ": 2},
26
+ "650" : [ " ", "1", "2", "0" ]
27
+ },
28
+ "indicator2PreferredValues": {
29
+ "024" : {"0": 1, "1": 1, " ": 2},
30
+ "033" : {"0": 1, "1": 1, "2": 1, " ": 2},
31
+ "246" : {"0": 1, "1": 1, "2": 1, "3": 1, "4": 1, "5": 1, "6": 1, "7": 1, "8": 1, " ": 2},
32
+ "363" : {"0": 1, "1": 1, " ": 2},
33
+ "382" : {"0": 1, "1": 1, " ": 2},
34
+ "730" : [ "2", " " ]
35
+ },
36
+ "comment #4" : "List indicators that don't block merge here. Non-filing indicators do not prevent field merge (their support is hard-coded). They are mainly listed here as an example.",
37
+ "ignoreIndicator1" : ["100", "110", "111", "130",
38
+ "210", "242", "245", "246", "247", "307", "490",
39
+ "505", "506", "510", "511", "516", "520", "521", "522", "524", "526", "583", "586",
40
+ "600", "610", "630", "650", "651", "655",
41
+ "700", "710", "730", "740", "760", "762", "765", "767", "770", "772", "773", "774", "775", "776",
42
+ "777", "780", "785", "786", "787", "788", "800", "810"],
43
+ "ignoreIndicator2" : ["017", "222", "240", "242", "243", "245",
44
+ "760", "762", "765", "767", "770", "773", "774", "775", "776", "777", "786", "787", "788", "830"],
45
+ "comment #5" : "If one indicator has value, and the other has not, it does not necessarily mean mismatch",
46
+ "tolerateBlankIndicator1": ["022", "037", "041", "046", "050", "055", "060", "070", "080",
47
+ "246", "260", "264",
48
+ "341", "363", "382", "384", "388",
49
+ "541", "542", "544", "545", "561", "583", "588", "856"],
50
+ "tolerateBlankIndicator2": ["024", "033", "082", "246", "363", "382", "856" ],
51
+ "preprocessorDirectives" : [
52
+ {
53
+ "operation": "retag",
54
+ "recordType": "source",
55
+ "fieldSpecification": {"tag": "100"},
56
+ "comment": "NB! Retags should check corresponding 880 fields as well.",
57
+ "newTag": "700"
58
+ },
59
+
60
+ {
61
+ "operation": "retag",
62
+ "recordType": "source",
63
+ "fieldSpecification": {"tag": "110"},
64
+ "newTag": "710"
65
+ },
66
+
67
+ {
68
+ "operation": "retag",
69
+ "recordType": "source",
70
+ "fieldSpecification": {"tag": "111"},
71
+ "newTag": "711"
72
+ },
73
+
74
+ {
75
+ "operation": "retag",
76
+ "recordType": "source",
77
+ "fieldSpecification": {"tag": "130"},
78
+ "newTag": "730"
79
+ }
80
+ ]
81
+ }
82
+
83
+ }
@@ -0,0 +1,307 @@
1
+ import {MarcRecord} from '@natlibfi/marc-record';
2
+ import createDebugLogger from 'debug';
3
+ import {fieldHasSubfield, fieldToString, nvdebug, nvdebugSubfieldArray, subfieldIsRepeatable, subfieldsAreIdentical} from '../utils.js';
4
+
5
+ //import {normalizeControlSubfieldValue} from './normalizeIdentifier';
6
+ import {normalizeControlSubfieldValue} from '../normalize-identifiers';
7
+
8
+ const debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:merge-fields:controlSubfields');
9
+ //const debugData = debug.extend('data');
10
+ const debugDev = debug.extend('dev');
11
+
12
+ function subfieldsAreEqual(field1, field2, subfieldCode) {
13
+ // Check OK if neither one has given subfield.
14
+ // Check fails if one field has given subfield and the other one does not
15
+ if (!fieldHasSubfield(field1, subfieldCode)) {
16
+ return !fieldHasSubfield(field2, subfieldCode);
17
+ }
18
+ if (!fieldHasSubfield(field2, subfieldCode)) {
19
+ return false;
20
+ }
21
+ // Compare $3 subfields. If everything matches, OK, else FAIL:
22
+ const sfSet1 = field1.subfields.filter(subfield => subfield.code === subfieldCode);
23
+ const sfSet2 = field2.subfields.filter(subfield => subfield.code === subfieldCode);
24
+ return MarcRecord.isEqual(sfSet1, sfSet2);
25
+ }
26
+
27
+ function subfieldsAreEmpty(field1, field2, subfieldCode) {
28
+ if (!fieldHasSubfield(field1, subfieldCode) && !fieldHasSubfield(field2, subfieldCode)) {
29
+ return true;
30
+ }
31
+ return false;
32
+ }
33
+
34
+
35
+ function sixlessIsSubset(fieldWith6, fieldWithout6) {
36
+ // Remove $0 and $1, and then check that remaining $6-less field is a subset of the one with $6.
37
+ // No need to check indicators.
38
+ // NB! We could use punctuation-stripping here.
39
+ const subset = fieldWithout6.subfields.filter(subfield => !['0', '1'].includes(subfield.code));
40
+ return subset.every(sf => fieldWith6.subfields.some(sf2 => subfieldsAreIdentical(sf, sf2)));
41
+ //return MarcRecord.isEqual(strippedField1, strippedField2);
42
+ }
43
+
44
+ function controlSubfield6PermitsMerge(field1, field2) {
45
+ if (subfieldsAreEmpty(field1, field2, '6')) {
46
+ return true;
47
+ }
48
+
49
+ // Handle cases where one has a $6 and the other has not:
50
+ // Should this accept $0 (FI-ASTERI-N) vs none?
51
+ if (!fieldHasSubfield(field1, '6') && fieldHasSubfield(field2, '6') && sixlessIsSubset(field2, field1)) {
52
+ return true;
53
+ }
54
+ if (!fieldHasSubfield(field2, '6') && fieldHasSubfield(field1, '6') && sixlessIsSubset(field1, field2)) {
55
+ return true;
56
+ }
57
+
58
+ // There are at least two (plus) fields involved (Field XXX (one) and field 880 (one plus).
59
+ // Thus this generic solution can't handle them. Postprocess step removes some chains instead!
60
+ debugDev(` controlSubfield6PermitsMerge() fails always on generic part (feature).`);
61
+ return false;
62
+ }
63
+
64
+ function controlSubfield5PermitsMerge(field1, field2) {
65
+ // field1.$5 XOR field2.$5 means false, NEITHER and BOTH mean true, regardless of value
66
+ if (!fieldHasSubfield(field1, '5')) {
67
+ if (!fieldHasSubfield(field2, '5')) {
68
+ return true; // If neither one has $5, it's ok to merge
69
+ }
70
+ // If $5 contents are same, merge can be perfomed:
71
+ const fives1 = field1.subfields.filter(sf => sf.code === '5');
72
+ const fives2 = field2.subfields.filter(sf => sf.code === '5');
73
+ if (fives1.every(sf1 => fives2.some(sf2 => sf1.value === sf2.value)) && fives2.every(sf2 => fives1.some(sf1 => sf1.value === sf2.value))) {
74
+ return true;
75
+ }
76
+ return false;
77
+ }
78
+ if (!fieldHasSubfield(field2, '5')) {
79
+ return false;
80
+ }
81
+ return true;
82
+ }
83
+
84
+ function controlSubfield9PermitsMerge(baseField, sourceField) {
85
+ const baseFieldSubfields9 = baseField.subfields.filter(sf => sf.code === '9');
86
+ const sourceFieldSubfields9 = sourceField.subfields.filter(sf => sf.code === '9');
87
+
88
+ //nvdebug('CHECK $9', debugDev);
89
+ // There are no $9s. Skip:
90
+ if (baseFieldSubfields9.length === 0 && sourceFieldSubfields9.length === 0) {
91
+ //nvdebug(` No subfield $9 detected`, debugDev);
92
+ return true;
93
+ }
94
+
95
+ if (keepOrDropPreventsMerge()) {
96
+ nvdebug(` Subfield $9 KEEPs and DROPs disallow merge`, debugDev);
97
+ return false;
98
+ }
99
+
100
+ if (transPreventsMerge()) {
101
+ nvdebug(` Subfield $9 <TRANS> mismatch disallows merge`, debugDev);
102
+ return false;
103
+ }
104
+
105
+ //nvdebug('CHECK $9 OK', debugDev);
106
+
107
+ return true;
108
+
109
+ function subfieldHasKeepOrDrop(subfield) {
110
+ // nvdebug(`Has <KEEP>? ${subfieldToString(subfield)}`, debugDev);
111
+ return subfield.code === '9' && (/(?:<KEEP>|<DROP>)/u).test(subfield.value);
112
+ }
113
+
114
+ function subfieldHasTrans(subfield) {
115
+ return subfield.code === '9' && (/<TRANS>/u).test(subfield.value);
116
+ }
117
+
118
+ function transPreventsMerge() {
119
+ const trans1 = baseFieldSubfields9.filter(sf => subfieldHasTrans(sf));
120
+ const trans2 = sourceFieldSubfields9.filter(sf => subfieldHasTrans(sf));
121
+ if (trans1.length > 0 && trans2.length > 0) {
122
+ if (!MarcRecord.isEqual(trans1, trans2)) {
123
+ return true;
124
+ }
125
+ }
126
+ return false;
127
+ }
128
+
129
+ function retainSubfieldForKeepComparison(subfield) {
130
+ // Don't compare <KEEP>, <DROP> nor <TRANS> here (<TRANS> has it's own check)
131
+ if (subfieldHasKeepOrDrop(subfield) || subfieldHasTrans(subfield)) {
132
+ return false;
133
+ }
134
+
135
+ if (['0', '1'].includes(subfield.code)) {
136
+ return false;
137
+ }
138
+ if (['100', '600', '700', '800'].includes(baseField.tag)) {
139
+ // Despite $9 KEEP/DROP, we are interested in merging $d years (better than two separate fields)
140
+ if (['d'].includes(subfield.code)) {
141
+ return false;
142
+ }
143
+ }
144
+
145
+
146
+ return true;
147
+ }
148
+
149
+ function acceptKeeplessSourceSubfield(sourceSubfield, tag, subfieldCode, subfieldValue) {
150
+ if (sourceSubfield.code !== subfieldCode) {
151
+ return false;
152
+ }
153
+ // In this context, there's no need to check the value of a non-repeatable subfield.
154
+ // If value is different, pairing will fail when comparing the subfield itself.
155
+ // This allows us to tolerate little differences in punctuation: different punctuation does not get copied to base,
156
+ // so they don't alter base and and thus redundant when comparing.
157
+ if (!subfieldIsRepeatable(tag, subfieldCode)) {
158
+ return true;
159
+ }
160
+ return sourceSubfield.value === subfieldValue;
161
+ }
162
+
163
+ function keepOrDropPreventsMerge() {
164
+ const keepOrDrop1 = baseFieldSubfields9.filter(sf => subfieldHasKeepOrDrop(sf));
165
+ const keepOrDrop2 = sourceFieldSubfields9.filter(sf => subfieldHasKeepOrDrop(sf));
166
+
167
+ if (keepOrDrop1.length === 0 && keepOrDrop2.length === 0) {
168
+ return false;
169
+ }
170
+
171
+ if (baseField.tag.charAt(0) === '1' && !keepOrDrop2.some(sf => (/<DROP>/u).test(sf.value))) {
172
+ return false;
173
+ }
174
+
175
+ const sf9lessField1 = baseField.subfields.filter(subfield => retainSubfieldForKeepComparison(subfield));
176
+ const sf9lessField2 = sourceField.subfields.filter(subfield => retainSubfieldForKeepComparison(subfield));
177
+
178
+ nvdebugSubfieldArray(baseField.subfields, 'FIELD ', debugDev);
179
+ nvdebugSubfieldArray(sf9lessField1, 'FILTER ', debugDev);
180
+
181
+ nvdebugSubfieldArray(sourceField.subfields, 'FIELD2 ', debugDev);
182
+ nvdebugSubfieldArray(sf9lessField2, 'FILTER2 ', debugDev);
183
+
184
+ // Keepless field can be a subset field with <KEEP>/<DROP>! Note that punctuation still causes remnants to fail.
185
+ if (keepOrDrop1.length === 0) {
186
+ return !sf9lessField1.every(sf => sf9lessField2.some(sf2 => subfieldsAreIdentical(sf, sf2)));
187
+ }
188
+ // However, to alleviate the above-mentioned punctuation problem, we can check keep/drop-less *source* subfields
189
+ if (keepOrDrop2.length === 0) {
190
+ const unhandledSubfield = sf9lessField2.find(sf2 => !sf9lessField1.some(sf => acceptKeeplessSourceSubfield(sf2, baseField.tag, sf.code, sf.value)));
191
+ if (unhandledSubfield) {
192
+ //nvdebug(`Failed to pair ${subfieldToString(unhandledSubfield)}`, debugDev);
193
+ return true;
194
+ }
195
+ //return !sf9lessField2.every(sf2 => sf9lessField1.some(sf => subfieldsAreIdentical(sf, sf2)));
196
+ return false;
197
+ }
198
+
199
+ //nvdebugSubfieldArray(sf9lessField2, 'SOURCE(?)', debugDev);
200
+ //nvdebugSubfieldArray(sf9lessField1, 'BASE(?) ', debugDev);
201
+
202
+ // $9 <KEEP> or <DROP> detected on both fields.
203
+ // Non-keeps and non-drops must be equal, otherwise fail:
204
+ if (MarcRecord.isEqual(sf9lessField1, sf9lessField2)) {
205
+ return false;
206
+ }
207
+ // Prevent:
208
+ return true;
209
+ }
210
+ }
211
+
212
+ function getPrefix(value) {
213
+ const normalizedValue = normalizeControlSubfieldValue(value);
214
+
215
+ if (normalizedValue.match(/^\([^)]+\)[0-9]+$/u)) {
216
+ return normalizedValue.substr(0, normalizedValue.indexOf(')') + 1);
217
+ }
218
+
219
+ if (value.match(/^https?:\/\//u)) {
220
+ return normalizedValue.substr(0, normalizedValue.lastIndexOf('/') + 1);
221
+ }
222
+
223
+ return '';
224
+ }
225
+
226
+ function isMatchAfterNormalization(currSubfield, otherField) {
227
+ // NB! Add implement isni normalizations (to normalize.js) and apply here:
228
+ const normalizedCurrSubfieldValue = normalizeControlSubfieldValue(currSubfield.value);
229
+ const prefix = getPrefix(normalizedCurrSubfieldValue);
230
+
231
+ //debug(`FFS-PREFIX '${prefix}'`);
232
+ // Look for same prefix + different identifier
233
+ const hits = otherField.subfields.filter(sf2 => sf2.code === currSubfield.code && normalizeControlSubfieldValue(sf2.value).indexOf(prefix) === 0);
234
+ if (hits.length === 0 || // <-- Nothing found, so it can't be a mismatch
235
+ // Every opposing subfields match:
236
+ hits.every(sf2 => normalizedCurrSubfieldValue === normalizeControlSubfieldValue(sf2.value))) {
237
+ debugDev(`Subfield ‡${currSubfield.code} check OK: No opposing ${prefix} prefixes found.`);
238
+ return true;
239
+ }
240
+
241
+ debugDev(`Subfield ‡${currSubfield.code} check FAILED: ‡${currSubfield.code} '${currSubfield.value}' vs ‡${currSubfield.code} '${hits[0].value}'.`);
242
+ return false;
243
+ }
244
+
245
+ function controlSubfieldContainingIdentifierPermitsMerge(field1, field2, subfieldCode) {
246
+ if (!fieldHasSubfield(field1, subfieldCode, null) || !fieldHasSubfield(field2, subfieldCode, null)) {
247
+ return true;
248
+ }
249
+
250
+ const result = field1.subfields.every(subfield => {
251
+ if (subfield.code !== subfieldCode) {
252
+ return true;
253
+ }
254
+
255
+ debugDev(`Compare ‡${subfieldCode} '${subfield.value}' with '${fieldToString(field2)}'.`);
256
+ if (fieldHasSubfield(field2, field1.code, field1.value)) {
257
+ return true;
258
+ }
259
+
260
+ return isMatchAfterNormalization(subfield, field2, subfieldCode);
261
+ });
262
+
263
+ if (!result) {
264
+ debugDev(`Control subfield '${subfieldCode}' check failed.`);
265
+ return false;
266
+ }
267
+ return true;
268
+ }
269
+
270
+ const controlSubfieldsContainingIdentifier = ['w', '0', '1', '2']; // 2 ain't identifier, but the logic can be applied here as well
271
+
272
+ export function controlSubfieldsPermitMerge(baseField, sourceField) {
273
+ // Check $w, $0, $1, $2 (which isn't an identifier per se, but the sama logic can be applied)
274
+ if (!controlSubfieldsContainingIdentifier.every(subfieldCode => controlSubfieldContainingIdentifierPermitsMerge(baseField, sourceField, subfieldCode))) {
275
+ //debug(' control subfields with identifiers failed');
276
+ return false;
277
+ }
278
+
279
+ if (!subfieldsAreEqual(baseField, sourceField, '3')) {
280
+ //debug(' similar control subfield fails');
281
+ return false;
282
+ }
283
+
284
+ if (!controlSubfield5PermitsMerge(baseField, sourceField) || !controlSubfield6PermitsMerge(baseField, sourceField) || !controlSubfield9PermitsMerge(baseField, sourceField)) {
285
+ return false;
286
+ }
287
+ // We fully prevent merging $8 subfields here, as they affect multiple fields! Also these would get screwed:
288
+ // 38211 |8 3\u |a kuoro |2 seko
289
+ // 38211 |8 6\u |a kuoro |2 seko |9 VIOLA<DROP>
290
+ // Thus only copy works with $8...
291
+ if (!subfieldsAreEmpty(baseField, sourceField, '8')) {
292
+ // We could alleviate this a bit esp. for non-repeatable fields.
293
+ // At least, if the source has '8' and otherwise the two fields are identical...
294
+ const subsetOfSourceField = {
295
+ 'tag': sourceField.tag,
296
+ 'ind1': sourceField.ind1,
297
+ 'ind2': sourceField.ind2, subfields: sourceField.subfields.filter(sf => sf.code !== '8')
298
+ };
299
+ if (fieldToString(baseField) === fieldToString(subsetOfSourceField)) {
300
+ return true;
301
+ }
302
+ //debug(' csf8 failed');
303
+ return false;
304
+ }
305
+
306
+ return true;
307
+ }