@natlibfi/marc-record-validators-melinda 12.0.6 → 12.0.7

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 (120) hide show
  1. package/dist/cyrillux.js +11 -11
  2. package/dist/cyrillux.js.map +2 -2
  3. package/dist/dataProvenanceUtils.js +19 -0
  4. package/dist/dataProvenanceUtils.js.map +7 -0
  5. package/dist/index.js +3 -1
  6. package/dist/index.js.map +2 -2
  7. package/dist/merge-fields/controlSubfields.js.map +2 -2
  8. package/dist/merge-fields/counterpartField.js +149 -152
  9. package/dist/merge-fields/counterpartField.js.map +3 -3
  10. package/dist/merge-fields/dataProvenance.js +6 -20
  11. package/dist/merge-fields/dataProvenance.js.map +2 -2
  12. package/dist/merge-fields/index.js +1 -1
  13. package/dist/merge-fields/index.js.map +2 -2
  14. package/dist/merge-fields/mergableIndicator.js +1 -2
  15. package/dist/merge-fields/mergableIndicator.js.map +2 -2
  16. package/dist/merge-fields/mergeConfig.js +2 -0
  17. package/dist/merge-fields/mergeConfig.js.map +2 -2
  18. package/dist/merge-fields/mergeConstraints.js +35 -32
  19. package/dist/merge-fields/mergeConstraints.js.map +3 -3
  20. package/dist/merge-fields/mergeField.js +4 -3
  21. package/dist/merge-fields/mergeField.js.map +2 -2
  22. package/dist/merge-fields/mergeOrAddSubfield.js +8 -7
  23. package/dist/merge-fields/mergeOrAddSubfield.js.map +2 -2
  24. package/dist/merge-fields/mergeSubfield.js +5 -1
  25. package/dist/merge-fields/mergeSubfield.js.map +2 -2
  26. package/dist/merge-fields/worldKnowledge.js +52 -0
  27. package/dist/merge-fields/worldKnowledge.js.map +2 -2
  28. package/dist/merge-fields.test.js +2 -2
  29. package/dist/merge-fields.test.js.map +2 -2
  30. package/dist/normalize-dashes.js +2 -2
  31. package/dist/normalize-dashes.js.map +2 -2
  32. package/dist/normalizeFieldForComparison.js +8 -14
  33. package/dist/normalizeFieldForComparison.js.map +2 -2
  34. package/dist/prepublicationUtils.js +1 -1
  35. package/dist/prepublicationUtils.js.map +2 -2
  36. package/dist/punctuation2.js +10 -10
  37. package/dist/punctuation2.js.map +2 -2
  38. package/dist/removeDuplicateDataFields.js +1 -24
  39. package/dist/removeDuplicateDataFields.js.map +2 -2
  40. package/dist/removeInferiorDataFields.js +3 -2
  41. package/dist/removeInferiorDataFields.js.map +2 -2
  42. package/dist/sortSubfields.js +19 -19
  43. package/dist/sortSubfields.js.map +2 -2
  44. package/dist/subfield6Utils.js +0 -1
  45. package/dist/subfield6Utils.js.map +2 -2
  46. package/dist/subfield8Utils.js +0 -5
  47. package/dist/subfield8Utils.js.map +2 -2
  48. package/dist/utils.js +29 -3
  49. package/dist/utils.js.map +2 -2
  50. package/package.json +4 -4
  51. package/src/cyrillux.js +11 -11
  52. package/src/dataProvenanceUtils.js +21 -0
  53. package/src/index.js +3 -1
  54. package/src/merge-fields/controlSubfields.js +0 -1
  55. package/src/merge-fields/counterpartField.js +191 -290
  56. package/src/merge-fields/dataProvenance.js +8 -25
  57. package/src/merge-fields/index.js +1 -1
  58. package/src/merge-fields/mergableIndicator.js +1 -2
  59. package/src/merge-fields/mergeConfig.js +2 -1
  60. package/src/merge-fields/mergeConstraints.js +39 -34
  61. package/src/merge-fields/mergeField.js +4 -7
  62. package/src/merge-fields/mergeOrAddSubfield.js +8 -7
  63. package/src/merge-fields/mergeSubfield.js +11 -2
  64. package/src/merge-fields/worldKnowledge.js +72 -3
  65. package/src/merge-fields.test.js +2 -2
  66. package/src/normalize-dashes.js +2 -2
  67. package/src/normalizeFieldForComparison.js +19 -20
  68. package/src/prepublicationUtils.js +1 -1
  69. package/src/punctuation2.js +10 -10
  70. package/src/removeDuplicateDataFields.js +24 -24
  71. package/src/removeInferiorDataFields.js +3 -2
  72. package/src/sortSubfields.js +19 -19
  73. package/src/subfield6Utils.js +1 -1
  74. package/src/subfield8Utils.js +5 -5
  75. package/src/utils.js +39 -12
  76. package/test-fixtures/cyrillux/f14/expectedResult.json +32 -0
  77. package/test-fixtures/cyrillux/f14/metadata.json +10 -0
  78. package/test-fixtures/cyrillux/f14/record.json +14 -0
  79. package/test-fixtures/merge-fields/f042_01/expectedResult.json +12 -0
  80. package/test-fixtures/merge-fields/f042_01/metadata.json +6 -0
  81. package/test-fixtures/merge-fields/f042_01/record.json +13 -0
  82. package/test-fixtures/merge-fields/f06/expectedResult.json +42 -0
  83. package/test-fixtures/merge-fields/f06/metadata.json +6 -0
  84. package/test-fixtures/merge-fields/f06/record.json +41 -0
  85. package/test-fixtures/merge-fields/f07/expectedResult.json +18 -0
  86. package/test-fixtures/merge-fields/f07/metadata.json +6 -0
  87. package/test-fixtures/merge-fields/f07/record.json +18 -0
  88. package/test-fixtures/merge-fields/f08/expectedResult.json +12 -0
  89. package/test-fixtures/merge-fields/f08/metadata.json +7 -0
  90. package/test-fixtures/merge-fields/f08/record.json +10 -0
  91. package/test-fixtures/merge-fields/f09/expectedResult.json +14 -0
  92. package/test-fixtures/merge-fields/f09/metadata.json +6 -0
  93. package/test-fixtures/merge-fields/f09/record.json +14 -0
  94. package/test-fixtures/merge-fields/f10/expectedResult.json +25 -0
  95. package/test-fixtures/merge-fields/f10/metadata.json +6 -0
  96. package/test-fixtures/merge-fields/f10/record.json +25 -0
  97. package/test-fixtures/merge-fields/f11/expectedResult.json +40 -0
  98. package/test-fixtures/merge-fields/f11/metadata.json +7 -0
  99. package/test-fixtures/merge-fields/f11/record.json +50 -0
  100. package/test-fixtures/merge-fields/f12/expectedResult.json +17 -0
  101. package/test-fixtures/merge-fields/f12/metadata.json +6 -0
  102. package/test-fixtures/merge-fields/f12/record.json +25 -0
  103. package/test-fixtures/merge-fields/f13/expectedResult.json +18 -0
  104. package/test-fixtures/merge-fields/f13/metadata.json +6 -0
  105. package/test-fixtures/merge-fields/f13/record.json +28 -0
  106. package/test-fixtures/merge-fields/f14/expectedResult.json +25 -0
  107. package/test-fixtures/merge-fields/f14/metadata.json +6 -0
  108. package/test-fixtures/merge-fields/f14/record.json +25 -0
  109. package/test-fixtures/merge-fields/f300_01/expectedResult.json +9 -0
  110. package/test-fixtures/merge-fields/f300_01/metadata.json +6 -0
  111. package/test-fixtures/merge-fields/f300_01/record.json +8 -0
  112. package/test-fixtures/merge-fields/f300_02/expectedResult.json +13 -0
  113. package/test-fixtures/merge-fields/f300_02/metadata.json +6 -0
  114. package/test-fixtures/merge-fields/f300_02/record.json +16 -0
  115. package/test-fixtures/merge-fields/f490_01/expectedResult.json +13 -0
  116. package/test-fixtures/merge-fields/f490_01/metadata.json +6 -0
  117. package/test-fixtures/merge-fields/f490_01/record.json +16 -0
  118. package/test-fixtures/remove-inferior-datafields/f17/expectedResult.json +11 -0
  119. package/test-fixtures/remove-inferior-datafields/f17/metadata.json +5 -0
  120. package/test-fixtures/remove-inferior-datafields/f17/record.json +15 -0
@@ -10,10 +10,10 @@
10
10
  * (They are jumped over when looking for next (non-controlfield subfield)
11
11
  */
12
12
  import {validateSingleField} from './ending-punctuation.js';
13
- import {tagToDataProvenanceSubfieldCode} from './merge-fields/dataProvenance.js';
13
+ import {tagToDataProvenanceSubfieldCode} from './dataProvenanceUtils.js';
14
14
  import {fieldGetUnambiguousTag} from './subfield6Utils.js';
15
15
  //import createDebugLogger from 'debug';
16
- import {fieldToString, isControlSubfieldCode, nvdebug} from './utils.js';
16
+ import {fieldToString, isContentSubfieldCode, nvdebug} from './utils.js';
17
17
  import clone from 'clone';
18
18
 
19
19
  //const debug = createDebugLogger('debug/punctuation2');
@@ -57,7 +57,7 @@ function isIrrelevantSubfield(subfield, tag) {
57
57
  if (subfield.code === dataProvenanceSubfieldCode) {
58
58
  return true;
59
59
  }
60
- return isControlSubfieldCode(subfield.code); // Currently this contains other stuff as well ($3, $4, $7, $9...)
60
+ return !isContentSubfieldCode(subfield.code); // Currently this contains other stuff as well ($3, $4, $7, $9...)
61
61
  }
62
62
 
63
63
 
@@ -123,16 +123,16 @@ const X00RemoveDotAfterBracket = {'code': 'cq', 'context': /\)\.$/u, 'remove': /
123
123
  // 390, 800, 810, 830...
124
124
  const cleanPuncBeforeLanguage = {'code': 'atvxyz', 'followedBy': 'l', 'context': puncIsProbablyPunc, 'remove': / *[.,:;]$/u};
125
125
 
126
- const addX00aComma = {'add': ',', 'code': 'abcqej', 'followedBy': 'cdeg', 'context': doesNotEndInPunc, 'contextRHS': allowsPuncRHS};
126
+ const addX00aComma = {'add': ',', 'code': 'abcqejt', 'followedBy': 'cdegnr', 'context': doesNotEndInPunc, 'contextRHS': allowsPuncRHS};
127
127
  const addX00dComma = {'name': 'X00$d ending in "-" does not get comma', 'add': ',', 'code': 'd', 'followedBy': 'cdeg', 'context': /[^-,.!]$/u, 'contextRHS': allowsPuncRHS};
128
128
  const addX00aComma2 = {'add': ',', 'code': 'abcdej', 'followedBy': 'cdeg', 'context': /(?:[A-Z]|Å|Ä|Ö)\.$/u, 'contextRHS': allowsPuncRHS};
129
129
  const addX00Dot = {'add': '.', 'code': 'abcdetv', 'followedBy': 'fklptu', 'context': needsPuncAfterAlphanumeric};
130
130
  const addEntryFieldFinalDot = {'name': 'X00 final dot', 'add': '.', 'code': 'abcdefghijklmnopqrstuvwxyz', 'followedBy': '#', 'context': /[^.)!?-]$/u};
131
131
 
132
132
 
133
- const addX10iColon = {name: 'Punctuate relationship information', add: ':', code: 'i', context: defaultNeedsPuncAfter2};
133
+ const addXX0iColon = {name: 'Punctuate relationship information', add: ':', code: 'i', context: defaultNeedsPuncAfter2}; // Not explicitly checking it, but this should always be followed by 'a' or 't'
134
134
  const addX10bDot = {'name': 'Add X10 pre-$b dot', 'add': '.', 'code': 'ab', 'followedBy': 'b', 'context': defaultNeedsPuncAfter2};
135
- const addX10eComma = {'add': ',', 'code': 'abe', 'followedBy': 'e', 'context': defaultNeedsPuncAfter2};
135
+ const addX10Comma = {'add': ',', 'code': 'abet', 'followedBy': 'en', 'context': defaultNeedsPuncAfter2};
136
136
  const addX10Dot = {'name': 'Add X10 final dot', 'add': '.', 'code': 'abet', 'followedBy': 'tu#', 'context': needsPuncAfterAlphanumeric};
137
137
  const addColonToRelationshipInformation = {'name': 'Add \':\' to 7X0 $i relationship info', 'add': ':', 'code': 'i', 'context': defaultNeedsPuncAfter2};
138
138
 
@@ -223,11 +223,11 @@ const cleanCrappyPunctuationRules = {
223
223
  '946': crappy24X
224
224
  };
225
225
 
226
- const cleanLegalX00Comma = {'code': 'abcde', 'followedBy': 'cdegj', 'context': /.,$/u, 'remove': /,$/u};
226
+ const cleanLegalX00Comma = {'code': 'abcdetn', 'followedBy': 'cdegjnr', 'context': /.,$/u, 'remove': /,$/u};
227
227
  // Accept upper case letters in X00$b, since they are probably Roman numerals.
228
228
  const cleanLegalX00bDot = {'code': 'b', 'followedBy': 't#', context: /^[IVXLCDM]+\.$/u, 'remove': /\.$/u};
229
229
  const cleanLegalX00iColon = {'code': 'i', 'followedBy': 'a', 'remove': / *:$/u}; // NB! context is not needed
230
- const cleanLegalX00Dot = {'code': 'abcdetvl', 'followedBy': 'tu#', 'context': /(?:[a-z0-9)]|å|ä|ö)\.$/u, 'remove': /\.$/u};
230
+ const cleanLegalX00Dot = {'code': 'abcdetkvl', 'followedBy': 'tklu#', 'context': /(?:[a-z0-9)]|å|ä|ö)\.$/u, 'remove': /\.$/u};
231
231
  const cleanDotBeforeLanguageSubfieldL = {'name': 'pre-language-$l dot', 'followedBy': 'l', 'context': /.\.$/u, 'remove': /\.$/u};
232
232
 
233
233
  const legalEntryField = [cleanDotBeforeLanguageSubfieldL];
@@ -313,8 +313,8 @@ const cleanValidPunctuationRules = {
313
313
  const addToAllEntryFields = [addDotBeforeLanguageSubfieldL, addSemicolonBeforeVolumeDesignation, addColonToRelationshipInformation, addEntryFieldFinalDot];
314
314
 
315
315
 
316
- const addX00 = [addX00aComma, addX00aComma2, addX00Dot, addX00dComma, ...addToAllEntryFields];
317
- const addX10 = [addX10iColon, addX10bDot, addX10eComma, addX10Dot, ...addToAllEntryFields];
316
+ const addX00 = [addXX0iColon, addX00aComma, addX00aComma2, addX00Dot, addX00dComma, ...addToAllEntryFields];
317
+ const addX10 = [addXX0iColon, addX10bDot, addX10Comma, addX10Dot, ...addToAllEntryFields];
318
318
  const addX11 = [...addToAllEntryFields, addX11Spacecolon];
319
319
  const addX30 = [...addToAllEntryFields];
320
320
 
@@ -1,5 +1,5 @@
1
1
  import createDebugLogger from 'debug';
2
- import {fieldsToString, fieldToString, nvdebug} from './utils.js';
2
+ import {fieldToString, nvdebug} from './utils.js';
3
3
  import {fieldHasValidSubfield6, fieldsGetOccurrenceNumbers, fieldsToNormalizedString, fieldToNormalizedString, get6s} from './subfield6Utils.js';
4
4
  import {add8s, fieldHasLinkingNumber, fieldHasValidSubfield8, fieldsGetAllSubfield8LinkingNumbers, getSubfield8LinkingNumber, recordGetAllSubfield8LinkingNumbers, recordGetFieldsWithSubfield8LinkingNumber} from './subfield8Utils.js';
5
5
 
@@ -139,21 +139,21 @@ export function removeDuplicateSubfield8Chains(record, fix = true) {
139
139
 
140
140
  let removables = []; // for validation
141
141
 
142
- nvdebug("CHAIN-8");
142
+ //nvdebug("CHAIN-8");
143
143
  const seenLinkingNumbers = recordGetAllSubfield8LinkingNumbers(record);
144
144
  if (seenLinkingNumbers.length === 0) {
145
145
  return removables;
146
146
  }
147
147
 
148
- nvdebug(`seen linking numbers ($8): ${seenLinkingNumbers.join(', ')}`, debug);
148
+ //nvdebug(`seen linking numbers ($8): ${seenLinkingNumbers.join(', ')}`, debug);
149
149
 
150
150
  seenLinkingNumbers.forEach(currLinkingNumber => {
151
151
  const linkedFields = recordGetFieldsWithSubfield8LinkingNumber(record, currLinkingNumber) //getFieldsWithSubfield8Index(base, baseIndex);
152
152
  // As/If there's just one occurrence number it should be fine to use normalizeOccurrenceNumber = true
153
153
  const normalizeOccurrenceNumber = true;
154
154
  const linkedFieldsAsString = fieldsToNormalizedString(linkedFields, currLinkingNumber, normalizeOccurrenceNumber, true);
155
- nvdebug(`Results for LINKING NUMBER ${currLinkingNumber}:`, debug);
156
- nvdebug(`${linkedFieldsAsString}`, debug);
155
+ //nvdebug(`Results for LINKING NUMBER ${currLinkingNumber}:`, debug);
156
+ //nvdebug(`${linkedFieldsAsString}`, debug);
157
157
 
158
158
  if (linkedFieldsAsString in seen) {
159
159
  if (!removables.includes(linkedFieldsAsString)) {
@@ -161,15 +161,15 @@ export function removeDuplicateSubfield8Chains(record, fix = true) {
161
161
  }
162
162
 
163
163
  if (fix) {
164
- nvdebug(`$8 CHAIN FIX: REMOVE $8 GROUP: ${fieldsToString(linkedFields)}`, debug);
164
+ //nvdebug(`$8 CHAIN FIX: REMOVE $8 GROUP: ${fieldsToString(linkedFields)}`, debug);
165
165
  linkedFields.forEach(field => recordRemoveFieldOrSubfield8(record, field, currLinkingNumber));
166
166
  return;
167
167
  }
168
168
 
169
- nvdebug(`$8 VALIDATION: DUPLICATE DETECTED ${linkedFieldsAsString}`, debug);
169
+ //nvdebug(`$8 VALIDATION: DUPLICATE DETECTED ${linkedFieldsAsString}`, debug);
170
170
  return;
171
171
  }
172
- nvdebug(`$8 DOUBLE REMOVAL OR VALIDATION: ADD2SEEN ${linkedFieldsAsString}`, debug);
172
+ //nvdebug(`$8 DOUBLE REMOVAL OR VALIDATION: ADD2SEEN ${linkedFieldsAsString}`, debug);
173
173
  seen[linkedFieldsAsString] = 1;
174
174
  return;
175
175
  });
@@ -190,28 +190,28 @@ export function handleDuplicateSubfield8Chains(record, fix) {
190
190
 
191
191
  let seen = {};
192
192
 
193
- nvdebug("CHAIN-8");
193
+ //nvdebug("CHAIN-8");
194
194
  const seenLinkingNumbers = recordGetAllSubfield8LinkingNumbers(record);
195
195
  if (seenLinkingNumbers.length === 0) {
196
196
  return;
197
197
  }
198
198
 
199
- nvdebug(`seen linking numbers ($8): ${seenLinkingNumbers.join(', ')}`, debug);
199
+ //nvdebug(`seen linking numbers ($8): ${seenLinkingNumbers.join(', ')}`, debug);
200
200
 
201
201
  seenLinkingNumbers.forEach(currLinkingNumber => {
202
202
  const linkedFields = recordGetFieldsWithSubfield8LinkingNumber(record, currLinkingNumber) //getFieldsWithSubfield8Index(base, baseIndex);
203
203
  // As/If there's just one occurrence number it should be fine to use normalizeOccurrenceNumber = true
204
204
  const normalizeOccurrenceNumber = false; //true;
205
205
  const linkedFieldsAsString = fieldsToNormalizedString(linkedFields, currLinkingNumber, normalizeOccurrenceNumber, true);
206
- nvdebug(`Results for LINKING NUMBER ${currLinkingNumber}:`, debug);
207
- nvdebug(`${linkedFieldsAsString}`, debug);
206
+ //nvdebug(`Results for LINKING NUMBER ${currLinkingNumber}:`, debug);
207
+ //nvdebug(`${linkedFieldsAsString}`, debug);
208
208
 
209
209
  if (linkedFieldsAsString in seen) {
210
- nvdebug(`$8 CHAIN FIX: REMOVE $8 GROUP: ${fieldsToString(linkedFields)}`, debug);
210
+ //nvdebug(`$8 CHAIN FIX: REMOVE $8 GROUP: ${fieldsToString(linkedFields)}`, debug);
211
211
  linkedFields.forEach(field => newRecordRemoveFieldOrSubfield8(record, field, currLinkingNumber, fix));
212
212
  return;
213
213
  }
214
- nvdebug(`$8 DOUBLE REMOVAL OR VALIDATION: ADD2SEEN ${linkedFieldsAsString}`, debug);
214
+ //nvdebug(`$8 DOUBLE REMOVAL OR VALIDATION: ADD2SEEN ${linkedFieldsAsString}`, debug);
215
215
  seen[linkedFieldsAsString] = 1;
216
216
  return;
217
217
  });
@@ -223,12 +223,12 @@ function markIdenticalSubfield6Chains(chain, record) {
223
223
  const normalizeTag = chain.some(field => field.tag.substring(0, 1) === '1'); // 1XX can delete 7XX as well!
224
224
  const chainAsString = fieldsToNormalizedString(chain, 0, normalizeOccurrenceNumber, normalizeTag);
225
225
 
226
- nvdebug(`markIdenticalSubfield6Chains: ${chainAsString}`);
226
+ //nvdebug(`markIdenticalSubfield6Chains: ${chainAsString}`);
227
227
  record.fields.forEach(f => compareWithChain(f));
228
228
 
229
229
 
230
230
  function compareWithChain(f) {
231
- nvdebug(`FIELD2CHAIN ${fieldToString(f)}`);
231
+ //nvdebug(`FIELD2CHAIN ${fieldToString(f)}`);
232
232
  const otherChain = fieldToChain(f, record);
233
233
  // Not a lone field or chain (head) or ... or is-same-chain
234
234
  if (otherChain.length === 0 || sameField(chain[0], otherChain[0])) {
@@ -239,7 +239,7 @@ function markIdenticalSubfield6Chains(chain, record) {
239
239
  // Mark other chain as deleted:
240
240
  if (chainAsString === otherChainAsString) {
241
241
  otherChain.forEach(f => {
242
- nvdebug(` mark ${fieldToString(f)} as deleted ($6-chain)...`);
242
+ //nvdebug(` mark ${fieldToString(f)} as deleted ($6-chain)...`);
243
243
  f.deleted = 1;
244
244
  });
245
245
  return;
@@ -260,7 +260,7 @@ function markIdenticalLoneFieldsAsDeletable(field, record) {
260
260
 
261
261
  // Mark fields as deleted:
262
262
  identicalLoneFields.forEach(f => {
263
- nvdebug(` mark ${fieldToString(f)} as deleted (lone field)...`);
263
+ //nvdebug(` mark ${fieldToString(f)} as deleted (lone field)...`);
264
264
  f.deleted = 1;
265
265
  });
266
266
 
@@ -300,7 +300,7 @@ function acceptFieldsWithSubfield8(fieldsWithSubfield8, requireSingleTag = false
300
300
 
301
301
  // If linking number
302
302
  function anomaly8(linkingNumber) {
303
- nvdebug(` Looking for anomalies in linkin number ${linkingNumber}`);
303
+ //nvdebug(` Looking for anomalies in linkin number ${linkingNumber}`);
304
304
  const relevantFields = fieldsWithSubfield8.filter(f => fieldHasLinkingNumber(f, linkingNumber));
305
305
  if (requireSingleTag) {
306
306
  return !isSingleTagLinkingNumber(linkingNumber, relevantFields, relevantFields[0].tag);
@@ -332,7 +332,7 @@ export function fieldToChain(field, record) {
332
332
  }
333
333
  const chain = newGetAllLinkedFields(field, record, true, true);
334
334
 
335
- nvdebug(` Chain contains ${chain.length} field(s)`);
335
+ // nvdebug(` Chain contains ${chain.length} field(s)`);
336
336
  if (!isChainHead(field, chain)) { // newGetAllLinkedFields() marks relevant record.fields!
337
337
  return [];
338
338
  }
@@ -366,7 +366,7 @@ export function fieldToChain(field, record) {
366
366
 
367
367
  function fieldHandleDuplicateDatafields(field, record) {
368
368
  const chain = fieldToChain(field, record);
369
- nvdebug(` TRY TO HANDLE DUPLICATES OF '${fieldsToString(chain)}'`);
369
+ //nvdebug(` TRY TO HANDLE DUPLICATES OF '${fieldsToString(chain)}'`);
370
370
 
371
371
  if (chain.length === 0) {
372
372
  return;
@@ -383,7 +383,7 @@ function fieldHandleDuplicateDatafields(field, record) {
383
383
  if (fieldsWithSubfield6.length === 0) {
384
384
 
385
385
  if (fieldsWithSubfield8.length === 0) { // chain.length === 1?
386
- nvdebug(` Trying to find duplicates of single field '${fieldToString(chain[0])}'`);
386
+ //nvdebug(` Trying to find duplicates of single field '${fieldToString(chain[0])}'`);
387
387
  markIdenticalLoneFieldsAsDeletable(chain[0], record);
388
388
  return;
389
389
  }
@@ -401,8 +401,8 @@ function fieldHandleDuplicateDatafields(field, record) {
401
401
  }
402
402
 
403
403
 
404
- nvdebug(` NO HANDLER FOUND FOR '${fieldsToString(chain)}'`);
405
- nvdebug(` N8s: ${fieldsWithSubfield6.length}`);
404
+ //nvdebug(` NO HANDLER FOUND FOR '${fieldsToString(chain)}'`);
405
+ //nvdebug(` N8s: ${fieldsWithSubfield6.length}`);
406
406
 
407
407
  }
408
408
 
@@ -359,12 +359,13 @@ function deriveIndividualDeletables(record) {
359
359
  }
360
360
 
361
361
  function getPrepublicationTerms(fieldAsString) {
362
+ const subfield7Prepub = `${fieldAsString} ‡7 Ennakkotieto`
362
363
  if (fieldAsString.match(/^653./u)) {
363
364
  // MET-528 (extented by MET-575)
364
- return [`${fieldAsString} ‡g ENNAKKOTIETO`, `${fieldAsString} ‡g Ennakkotieto`, `${fieldAsString} ‡g ennakkotieto`, `${fieldAsString} ‡g ENNAKKOTIETO.`, `${fieldAsString} ‡g Ennakkotieto.`, `${fieldAsString} ‡g ennakkotieto.`];
365
+ return [subfield7Prepub, `${fieldAsString} ‡g ENNAKKOTIETO`, `${fieldAsString} ‡g Ennakkotieto`, `${fieldAsString} ‡g ennakkotieto`, `${fieldAsString} ‡g ENNAKKOTIETO.`, `${fieldAsString} ‡g Ennakkotieto.`, `${fieldAsString} ‡g ennakkotieto.`];
365
366
  }
366
367
 
367
- return [];
368
+ return [subfield7Prepub];
368
369
  }
369
370
 
370
371
  }
@@ -8,8 +8,8 @@ const debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:sortSu
8
8
  //const debugData = debug.extend('data');
9
9
  const debugDev = debug.extend('dev');
10
10
 
11
- const defaultSortOrderStringFinns = '8673abcdefghijklmnopqrstuvwxyz420159'; // NB! We Finns like $2 before $0 in 6XX...
12
- const defaultSortOrderStringOthers = '8673abcdefghijklmnopqrstuvwxyz402159';
11
+ const defaultSortOrderStringFinns = '863abcdefghijklmnopqrstuvwxyz4201759'; // NB! We Finns like $2 before $0 in 6XX...
12
+ const defaultSortOrderStringOthers = '863abcdefghijklmnopqrstuvwxyz4021759'; // NB 760-788 have '7' in different position...
13
13
 
14
14
  const defaultSortOrderFinns = defaultSortOrderStringFinns.split('');
15
15
  const defaultSortOrderOthers = defaultSortOrderStringOthers.split('');
@@ -70,7 +70,7 @@ export default function (defaultTagPattern) {
70
70
  const sortOrderForX00 = ['i', 'a', 'b', 'q', 'c', 'd', 'e', 't', 'u', 'l', 'f', 'x', 'y', 'z', '0', '1', '5', '9']; // skip $g. Can't remember why, though...
71
71
  const sortOrderForX10 = ['i', 'a', 'b', 't', 'n', 'c', 'e', 'v', 'w', 'x', 'y', 'z', '0', '1', '5', '9']; // somewhat iffy
72
72
  const sortOrderForX11 = ['a', 'n', 'd', 'c', 'e', 'g', 'j', '0', '1', '5', '9'];
73
- const sortOrderFor7XX = ['8', '7', 'i', 'a', 's', 't', 'b', 'c', 'd', 'm', 'h', 'k', 'o', 'x', 'z', 'g', 'q', 'w'];
73
+ const sortOrderFor7XXLinkingEntries = ['8', '7', 'i', 'a', 's', 't', 'b', 'c', 'd', 'm', 'h', 'k', 'o', 'x', 'z', 'g', 'q', 'w', '0', '1', 'l', '9', '5'];
74
74
  const sortOrderFor246 = ['i', 'a', 'b', 'n', 'p', 'f', '5', '9']; // Used by field 946 as well
75
75
 
76
76
  // List *only* exceptional order here. Otherwise default order is used.
@@ -107,22 +107,22 @@ const subfieldSortOrder = [
107
107
  {'tag': '700', 'sortOrder': sortOrderForX00},
108
108
  {'tag': '710', 'sortOrder': sortOrderForX10},
109
109
  {'tag': '711', 'sortOrder': sortOrderForX11},
110
- {'tag': '760', 'sortOrder': sortOrderFor7XX},
111
- {'tag': '762', 'sortOrder': sortOrderFor7XX},
112
- {'tag': '765', 'sortOrder': sortOrderFor7XX},
113
- {'tag': '767', 'sortOrder': sortOrderFor7XX},
114
- {'tag': '770', 'sortOrder': sortOrderFor7XX},
115
- {'tag': '772', 'sortOrder': sortOrderFor7XX},
116
- {'tag': '773', 'sortOrder': sortOrderFor7XX},
117
- {'tag': '774', 'sortOrder': sortOrderFor7XX},
118
- {'tag': '775', 'sortOrder': sortOrderFor7XX},
119
- {'tag': '776', 'sortOrder': sortOrderFor7XX},
120
- {'tag': '777', 'sortOrder': sortOrderFor7XX},
121
- {'tag': '780', 'sortOrder': sortOrderFor7XX},
122
- {'tag': '785', 'sortOrder': sortOrderFor7XX},
123
- {'tag': '786', 'sortOrder': sortOrderFor7XX},
124
- {'tag': '787', 'sortOrder': sortOrderFor7XX},
125
- {'tag': '788', 'sortOrder': sortOrderFor7XX},
110
+ {'tag': '760', 'sortOrder': sortOrderFor7XXLinkingEntries},
111
+ {'tag': '762', 'sortOrder': sortOrderFor7XXLinkingEntries},
112
+ {'tag': '765', 'sortOrder': sortOrderFor7XXLinkingEntries},
113
+ {'tag': '767', 'sortOrder': sortOrderFor7XXLinkingEntries},
114
+ {'tag': '770', 'sortOrder': sortOrderFor7XXLinkingEntries},
115
+ {'tag': '772', 'sortOrder': sortOrderFor7XXLinkingEntries},
116
+ {'tag': '773', 'sortOrder': sortOrderFor7XXLinkingEntries},
117
+ {'tag': '774', 'sortOrder': sortOrderFor7XXLinkingEntries},
118
+ {'tag': '775', 'sortOrder': sortOrderFor7XXLinkingEntries},
119
+ {'tag': '776', 'sortOrder': sortOrderFor7XXLinkingEntries},
120
+ {'tag': '777', 'sortOrder': sortOrderFor7XXLinkingEntries},
121
+ {'tag': '780', 'sortOrder': sortOrderFor7XXLinkingEntries},
122
+ {'tag': '785', 'sortOrder': sortOrderFor7XXLinkingEntries},
123
+ {'tag': '786', 'sortOrder': sortOrderFor7XXLinkingEntries},
124
+ {'tag': '787', 'sortOrder': sortOrderFor7XXLinkingEntries},
125
+ {'tag': '788', 'sortOrder': sortOrderFor7XXLinkingEntries},
126
126
  {'tag': '800', 'sortOrder': sortOrderForX00},
127
127
  {'tag': '810', 'sortOrder': sortOrderForX10},
128
128
  {'tag': '811', 'sortOrder': sortOrderForX11},
@@ -219,7 +219,7 @@ export function fieldGetOccurrenceNumberPairs(field, candFields) {
219
219
  //nvdebug(` Trying to finds pair for ${fieldToString(field)} in ${candFields.length} fields`);
220
220
  const pairs = candFields.filter(otherField => isSubfield6Pair(field, otherField));
221
221
  if (pairs.length === 0) {
222
- nvdebug(`NO PAIRS FOUND FOR '${fieldToString(field)}'`);
222
+ //nvdebug(`NO PAIRS FOUND FOR '${fieldToString(field)}'`);
223
223
  return pairs;
224
224
  }
225
225
  //nvdebug(`${pairs.length} PAIR(S) FOUND FOR '${fieldToString(field)}'`);
@@ -1,7 +1,7 @@
1
1
  // import createDebugLogger from 'debug';
2
2
  // const debug = createDebugLogger('@natlibfi/marc-record-validator-melinda/subfield8Utils');
3
3
 
4
- import {fieldToString, nvdebug} from './utils.js';
4
+ // import {fieldToString, nvdebug} from './utils.js';
5
5
 
6
6
  const sf8Regexp = /^([1-9][0-9]*)(?:\.[0-9]+)?(?:\\[acprux])?$/u;
7
7
 
@@ -56,7 +56,7 @@ export function fieldsGetAllSubfield8LinkingNumbers(fields) {
56
56
  field.subfields.forEach(sf => {
57
57
  const linkingNumber = getSubfield8LinkingNumber(sf);
58
58
  if (linkingNumber > 0 && !subfield8LinkingNumbers.includes(linkingNumber)) {
59
- nvdebug(` LINK8: Add subfield \$8 ${linkingNumber} to seen values list`);
59
+ //nvdebug(` LINK8: Add subfield \$8 ${linkingNumber} to seen values list`);
60
60
  subfield8LinkingNumbers.push(linkingNumber);
61
61
  }
62
62
  });
@@ -76,10 +76,10 @@ export function add8s(fields, record) {
76
76
  return fields;
77
77
  }
78
78
 
79
- nvdebug(`Linking number(s): ${linkingNumbers.join(', ')}`);
79
+ //nvdebug(`Linking number(s): ${linkingNumbers.join(', ')}`);
80
80
  linkingNumbers.forEach(number => collectLinkingNumberFields(number));
81
81
 
82
- fields.forEach(f => nvdebug(`AFTER ADDING 8s: '${fieldToString(f)}'`));
82
+ //fields.forEach(f => nvdebug(`AFTER ADDING 8s: '${fieldToString(f)}'`));
83
83
 
84
84
  return fields;
85
85
 
@@ -88,7 +88,7 @@ export function add8s(fields, record) {
88
88
  fields = fields.filter(f => !fieldHasLinkingNumber(f, linkingNumber));
89
89
  // Add them and their "sisters" back:
90
90
  const addableFields = record.fields.filter(f => fieldHasLinkingNumber(f, linkingNumber));
91
- addableFields.forEach(f => nvdebug(`(RE-?)ADD ${fieldToString(f)}`));
91
+ //addableFields.forEach(f => nvdebug(`(RE-?)ADD ${fieldToString(f)}`));
92
92
  fields = fields.concat(addableFields);
93
93
 
94
94
  }
package/src/utils.js CHANGED
@@ -1,13 +1,12 @@
1
1
  import createDebugLogger from 'debug';
2
-
3
- //import fs from 'fs';
4
- //import path from 'path';
2
+ import {melindaFieldSpecs} from './melindaCustomMergeFields.js';
3
+ import {isDataProvenanceSubfieldCode} from './dataProvenanceUtils.js';
5
4
 
6
5
  const debug = createDebugLogger('@natlibfi/melinda-marc-record-merge-reducers:utils');
7
6
  //const debugData = debug.extend('data');
8
7
  const debugDev = debug.extend('dev');
9
8
 
10
- import {melindaFieldSpecs} from './melindaCustomMergeFields.js';
9
+
11
10
 
12
11
  //JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'src', 'melindaCustomMergeFields.json'), 'utf8'));
13
12
 
@@ -102,19 +101,38 @@ export function nvdebugFieldArray(fields, prefix = ' ', func = undefined) {
102
101
  fields.forEach(field => nvdebug(`${prefix}${fieldToString(field)}`, func));
103
102
  }
104
103
 
105
- export function isControlSubfieldCode(subfieldCode) {
104
+ function isControlSubfieldCode(subfieldCode, tag = undefined) {
106
105
  // NB! Only $w, $0, $1, $5, $6 and $8 are really control subfields. In Finland $9 is oft a control subfield
107
- // $3 material (part of the whole thing)
108
- // $4 means 'relationship' (similar to relator terms at least in X00 and similar)
109
- // $7 is usually provinance subfield. However, it can be stored in other subfields as well. See merge-fields/dataProvenance.js for details
110
- // However, change this only if needed. Maybe all provinance subfields should return true?
111
- // This may become relevant when AI starts to create stuff...
112
- if (['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'w'].includes(subfieldCode)) {
106
+ if (['0', '1', '2', '5', '6', '8', 'w'].includes(subfieldCode)) {
113
107
  return true;
114
108
  }
109
+ // Subfield '7' is control subfield for some tags:
110
+ if (tag && subfieldCode === '7') {
111
+ if ( tag.match(/^7[678]/u) || ['800', '810', '811', '830'].includes(tag) ) {
112
+ return true;
113
+ }
114
+ }
115
115
  return false;
116
116
  }
117
117
 
118
+ export function isContentSubfieldCode(subfieldCode, tag = undefined) {
119
+ if (isControlSubfieldCode(subfieldCode, tag)) { // 'w', '0', '1', '5', '6' and '8'. (Also '7' for ....)
120
+ return false;
121
+ }
122
+ if (tag && isDataProvenanceSubfieldCode(subfieldCode, tag)) { // Note that default '7' is handled below
123
+ return false;
124
+ }
125
+ // $7 contains typically data provenance, sometimes it's a control field, and for f533, f856 ja f857 it's something else, but it's never content!
126
+ if (['2', '3', '4', '7', '9'].includes(subfieldCode)) {
127
+ return false;
128
+ }
129
+
130
+
131
+ return true;
132
+ }
133
+
134
+
135
+
118
136
  export function getCatalogingLanguage(record, defaultCatalogingLanguage = undefined) {
119
137
  const [field040] = record.get(/^040$/u);
120
138
  if (!field040) {
@@ -247,4 +265,13 @@ export function subfieldArraysContainSameData(arr1, arr2) {
247
265
  }
248
266
 
249
267
  return arr2.every(sf2 => arr1.some(sf => subfieldsAreIdentical(sf, sf2)));
250
- }
268
+ }
269
+
270
+ export function tagIsRepeatable(tag) {
271
+ const fieldSpecs = melindaFieldSpecs.fields.filter(field => field.tag === tag);
272
+ if (fieldSpecs.length !== 1) {
273
+ debugDev(` WARNING! Getting field ${tag} data failed! Default to repeatable field.`);
274
+ return true;
275
+ }
276
+ return fieldSpecs[0].repeatable;
277
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "_validationOptions": {},
3
+ "leader": "12345cam 22123454i 4500",
4
+ "fields": [
5
+ { "tag": "008", "value": "01234567890123456789012345678901234ukr89"},
6
+ { "tag": "240", "ind1": " ", "ind2": " ", "subfields": [
7
+ { "code": "6", "value": "880-01" },
8
+ { "code": "a", "value": "Hohol." },
9
+ { "code": "l", "value": "Hohol." },
10
+ { "code": "9", "value": "SFS4900 <TRANS>" }
11
+ ]},
12
+ { "tag": "776", "ind1": " ", "ind2": " ", "subfields": [
13
+ { "code": "6", "value": "880-02" },
14
+ { "code": "t", "value": "Hohol." },
15
+ { "code": "l", "value": "Гоголь." },
16
+ { "code": "9", "value": "SFS4900 <TRANS>" }
17
+ ]},
18
+ { "tag": "880", "ind1": " ", "ind2": " ", "subfields": [
19
+ { "code": "6", "value": "240-01" },
20
+ { "code": "a", "value": "Гоголь." },
21
+ { "code": "l", "value": "Гоголь." },
22
+ { "code": "9", "value": "CYRILLIC <TRANS>" }
23
+ ]},
24
+ { "tag": "880", "ind1": " ", "ind2": " ", "subfields": [
25
+ { "code": "6", "value": "776-02" },
26
+ { "code": "t", "value": "Гоголь." },
27
+ { "code": "l", "value": "Гоголь." },
28
+ { "code": "9", "value": "CYRILLIC <TRANS>" }
29
+ ]}
30
+ ]
31
+ }
32
+
@@ -0,0 +1,10 @@
1
+ {
2
+ "description": "14: transliteration(SFS-4900 only) does not apply the provenance subfield $l",
3
+ "comment": "The point in this test is that 776$l is not transliterated as it is \"untouchable\" provenance subfield, while 240$l is transliterated.",
4
+ "only": false,
5
+ "fix": true,
6
+ "config": {
7
+ "doISO9Transliteration": false,
8
+ "doSFS4900Transliteration": true
9
+ }
10
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "leader": "12345cam 22123454i 4500",
3
+ "fields": [
4
+ { "tag": "008", "value": "01234567890123456789012345678901234ukr89"},
5
+ { "tag": "240", "ind1": " ", "ind2": " ", "subfields": [
6
+ { "code": "a", "value": "Гоголь." },
7
+ { "code": "l", "value": "Гоголь." }
8
+ ]},
9
+ { "tag": "776", "ind1": " ", "ind2": " ", "subfields": [
10
+ { "code": "t", "value": "Гоголь." },
11
+ { "code": "l", "value": "Гоголь." }
12
+ ]}
13
+ ]
14
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "leader": "01331cam a22003494i 4500",
3
+ "fields": [
4
+ { "tag": "042", "ind1": " ", "ind2": " ", "subfields": [
5
+ { "code": "a", "value": "lc" },
6
+ { "code": "a", "value": "nsdp" },
7
+ { "code": "a", "value": "lcnuc" }
8
+ ]}
9
+ ],
10
+
11
+ "_validationOptions": {}
12
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "description": "f042/01: merge f042, rare case where we don't need a pair of any kind",
3
+ "fix": true,
4
+ "tagPattern": "^042$",
5
+ "only": false
6
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "leader": "01331cam a22003494i 4500",
3
+ "fields": [
4
+ { "tag": "042", "ind1": " ", "ind2": " ", "subfields": [
5
+ { "code": "a", "value": "lc" }
6
+ ]},
7
+ { "tag": "042", "ind1": " ", "ind2": " ", "subfields": [
8
+ { "code": "a", "value": "nsdp" },
9
+ { "code": "a", "value": "lcnuc" }
10
+ ]}
11
+ ],
12
+ "_validationOptions": {}
13
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "_validationOptions": {},
3
+ "fields": [
4
+ { "tag": "700", "ind1": "1", "ind2": " ", "subfields": [
5
+ { "code": "a", "value": "Wuolijoki, Hella." },
6
+ { "code": "t", "value": "Heta Niskavuori." },
7
+ { "code": "k", "value": "Valikoima." },
8
+ { "code": "0", "value": "(FIN11)000063570"
9
+ }
10
+ ]
11
+ },
12
+ { "tag": "700", "ind1": "1", "ind2": " ", "subfields": [
13
+ { "code": "a", "value": "Wuolijoki, Hella." },
14
+ { "code": "t", "value": "Heta Niskavuori." },
15
+ { "code": "k", "value": "Valikoima." },
16
+ { "code": "l", "value": "Ruotsi." },
17
+ { "code": "0", "value": "(FIN11)000063570"
18
+ }
19
+ ]
20
+ },
21
+ { "tag": "700", "ind1": "1", "ind2": " ", "subfields": [
22
+ { "code": "a", "value": "Nuolijoki, Nella." },
23
+ { "code": "t", "value": "Beta Tiskivuori." },
24
+ { "code": "l", "value": "Suomi." },
25
+ { "code": "0", "value": "(FIN11)900063570"
26
+ }
27
+ ]
28
+ },
29
+ { "tag": "700", "ind1": "1", "ind2": " ", "subfields": [
30
+ { "code": "a", "value": "Nuolijoki, Nella." },
31
+ { "code": "t", "value": "Beta Tiskivuori." },
32
+ { "code": "k", "value": "Valikoima." },
33
+ { "code": "l", "value": "Ruotsi." },
34
+ { "code": "0", "value": "(FIN11)900063570"
35
+ }
36
+ ]
37
+ }
38
+
39
+ ],
40
+ "leader": "01331cam a22003498i 4500"
41
+
42
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "description": "Don't merge name-title with missing language, and don't merge different languages",
3
+ "fix": true,
4
+ "tagPattern": "^(700)$",
5
+ "only": false
6
+ }
@@ -0,0 +1,41 @@
1
+ {
2
+ "_validationOptions": {},
3
+ "fields": [
4
+ { "tag": "700", "ind1": "1", "ind2": " ", "subfields": [
5
+ { "code": "a", "value": "Wuolijoki, Hella." },
6
+ { "code": "t", "value": "Heta Niskavuori." },
7
+ { "code": "k", "value": "Valikoima." },
8
+ { "code": "0", "value": "(FIN11)000063570"
9
+ }
10
+ ]
11
+ },
12
+ { "tag": "700", "ind1": "1", "ind2": " ", "subfields": [
13
+ { "code": "a", "value": "Wuolijoki, Hella." },
14
+ { "code": "t", "value": "Heta Niskavuori." },
15
+ { "code": "k", "value": "Valikoima." },
16
+ { "code": "l", "value": "Ruotsi." },
17
+ { "code": "0", "value": "(FIN11)000063570"
18
+ }
19
+ ]
20
+ },
21
+ { "tag": "700", "ind1": "1", "ind2": " ", "subfields": [
22
+ { "code": "a", "value": "Nuolijoki, Nella." },
23
+ { "code": "t", "value": "Beta Tiskivuori." },
24
+ { "code": "l", "value": "Suomi." },
25
+ { "code": "0", "value": "(FIN11)900063570"
26
+ }
27
+ ]
28
+ },
29
+ { "tag": "700", "ind1": "1", "ind2": " ", "subfields": [
30
+ { "code": "a", "value": "Nuolijoki, Nella." },
31
+ { "code": "t", "value": "Beta Tiskivuori." },
32
+ { "code": "k", "value": "Valikoima." },
33
+ { "code": "l", "value": "Ruotsi." },
34
+ { "code": "0", "value": "(FIN11)900063570"
35
+ }
36
+ ]
37
+ }
38
+ ],
39
+ "leader": "01331cam a22003498i 4500"
40
+
41
+ }