@natlibfi/marc-record-validators-melinda 12.0.0-alpha.4 → 12.0.0-alpha.5

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 (31) hide show
  1. package/dist/merge-fields/counterpartField.js +5 -0
  2. package/dist/merge-fields/counterpartField.js.map +2 -2
  3. package/dist/merge-fields/dataProvenance.js +29 -0
  4. package/dist/merge-fields/dataProvenance.js.map +7 -0
  5. package/dist/merge-fields/index.js +11 -2
  6. package/dist/merge-fields/index.js.map +2 -2
  7. package/dist/merge-fields/mergeField.js +1 -1
  8. package/dist/merge-fields/mergeField.js.map +2 -2
  9. package/dist/merge-fields.test.js +4 -2
  10. package/dist/merge-fields.test.js.map +2 -2
  11. package/dist/removeInferiorDataFields.js +2 -1
  12. package/dist/removeInferiorDataFields.js.map +2 -2
  13. package/dist/sortSubfields.js +5 -5
  14. package/dist/sortSubfields.js.map +2 -2
  15. package/dist/utils.js +6 -0
  16. package/dist/utils.js.map +2 -2
  17. package/package.json +2 -2
  18. package/src/merge-fields/counterpartField.js +6 -0
  19. package/src/merge-fields/dataProvenance.js +41 -0
  20. package/src/merge-fields/index.js +11 -2
  21. package/src/merge-fields/mergeField.js +2 -2
  22. package/src/merge-fields.test.js +6 -2
  23. package/src/removeInferiorDataFields.js +4 -1
  24. package/src/sortSubfields.js +7 -5
  25. package/src/utils.js +10 -0
  26. package/test-fixtures/merge-fields/f05/expectedResult.json +24 -0
  27. package/test-fixtures/merge-fields/f05/metadata.json +6 -0
  28. package/test-fixtures/merge-fields/f05/record.json +30 -0
  29. package/test-fixtures/remove-inferior-datafields/f16/expectedResult.json +12 -0
  30. package/test-fixtures/remove-inferior-datafields/f16/metadata.json +5 -0
  31. package/test-fixtures/remove-inferior-datafields/f16/record.json +14 -0
@@ -7,6 +7,7 @@ import { controlSubfieldsPermitMerge } from "./controlSubfields.js";
7
7
  import { mergableIndicator1, mergableIndicator2 } from "./mergableIndicator.js";
8
8
  import { partsAgree } from "../normalizeSubfieldValueForComparison.js";
9
9
  import { normalizeForSamenessCheck, valueCarriesMeaning } from "./worldKnowledge.js";
10
+ import { provenanceSubfieldsPermitMerge } from "./dataProvenance.js";
10
11
  const debug = createDebugLogger("@natlibfi/marc-record-validators-melinda:mergeField:counterpart");
11
12
  const debugDev = debug.extend("dev");
12
13
  const irrelevantSubfieldsInNameAndTitlePartComparison = "5689";
@@ -238,6 +239,10 @@ function syntacticallyMergablePair(baseField, sourceField, config) {
238
239
  nvdebug("non-mergable (reason: control subfield)", debugDev);
239
240
  return false;
240
241
  }
242
+ if (!provenanceSubfieldsPermitMerge(baseField, sourceField)) {
243
+ nvdebug("non-mergable (reason: data provenance subfield)", debugDev);
244
+ return false;
245
+ }
241
246
  if (!areRequiredSubfieldsPresent(baseField) || !areRequiredSubfieldsPresent(sourceField)) {
242
247
  nvdebug("non-mergable (reason: missing subfields)", debugDev);
243
248
  return false;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/merge-fields/counterpartField.js"],
4
- "sourcesContent": ["// For each incoming field that\n\nimport createDebugLogger from 'debug';\nimport {fieldHasSubfield, fieldHasNSubfields, fieldHasMultipleSubfields, fieldToString, nvdebug, removeCopyright} from '../utils.js';\nimport {cloneAndNormalizeFieldForComparison, cloneAndRemovePunctuation} from '../normalizeFieldForComparison.js';\n// This should be done via our own normalizer:\nimport {normalizeControlSubfieldValue} from '../normalize-identifiers.js';\n\nimport {getMergeConstraintsForTag} from './mergeConstraints.js';\nimport {controlSubfieldsPermitMerge} from './controlSubfields.js';\nimport {mergableIndicator1, mergableIndicator2} from './mergableIndicator.js';\nimport {partsAgree} from '../normalizeSubfieldValueForComparison.js';\nimport {normalizeForSamenessCheck, valueCarriesMeaning} from './worldKnowledge.js';\n\nconst debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:mergeField:counterpart');\n//const debugData = debug.extend('data');\nconst debugDev = debug.extend('dev');\n\nconst irrelevantSubfieldsInNameAndTitlePartComparison = '5689';\n\nconst counterpartRegexps = { // NB! tag is from source!\n // Note that in the normal case, all source 1XX fields have been converted to 7XX fields.\n '100': /^[17]00$/u, '110': /^[17]10$/u, '111': /^[17]11$/u, '130': /^[17]30$/u,\n '260': /^26[04]$/u, '264': /^26[04]$/u,\n '700': /^[17]00$/u, '710': /^[17]10$/u, '711': /^[17]11$/u, '730': /^[17]30$/u,\n // Hacks:\n '940': /^[29]40$/u, '973': /^[79]73$/u\n};\n\nconst counterpartRegexpsSingle = {\n // when base===source, never merge 1XX to 7XX, always 7XX to 1XX! Also, don't merge 264 to 260.\n '260': /^26[04]$/u,\n '700': /^[17]00$/u, '110': /^[17]10$/u, '111': /^[17]11$/u, '130': /^[17]30$/u,\n // Hacks:\n '940': /^[29]40$/u, '973': /^[79]73$/u\n};\n\n/*\nfunction differentPublisherSubfields(field1, field2) {\n if (field1.tag === '260' && field2.tag === '264' && field2.ind2 === '3') {\n return true;\n }\n if (field1.tag === '264' && field1.ind2 === '3' && field2.tag === '260') {\n return true;\n }\n return false;\n}\n*/\n\nexport function splitToNameAndQualifier(value) {\n if (value.match(/^.* \\([^()]+\\)$/u)) {\n const name = value.replace(/^(.*) \\([^()]+\\)$/u, '$1');\n const qualifier = value.replace(/^.* (\\([^()]+\\))$/u, '$1');\n return [name, qualifier];\n }\n return [value, undefined];\n}\n\nexport function splitToNameAndQualifierAndProcessName(name) {\n //const nameOnly = name.replace(/(?: \\([^)]+\\)| abp?| Kustannus| Kustannus Oy|, kustannusosakeyhti\u00F6| oyj?| ry)$/ugi, '');\n const [qualifierlessName, qualifier] = splitToNameAndQualifier(name);\n\n const [prefix, basename, suffix] = stripPrefixAndSuffix(qualifierlessName);\n\n return {name: getBestName(basename).toLowerCase(), prefix, suffix, qualifier};\n\n function stripPrefixAndSuffix(companyName) {\n const [nameOnly, suffix] = extractSuffix(companyName);\n const [nameOnly2, prefix] = extractPrefix(nameOnly);\n return [prefix, nameOnly2, suffix];\n }\n\n function extractSuffix(name) {\n const nameOnly = name.replace(/(?: \\([^)]+\\)| abp?| Kustannus| Kustannus Oy|, kustannusosakeyhti\u00F6| oyj?| ry)$/ugi, '');\n if (nameOnly === name) {\n return [name, undefined];\n }\n return [nameOnly, name.substring(nameOnly.length).replace(/^,? /u, '')];\n }\n\n function extractPrefix(name) {\n const nameOnly = name.replace(/^(?:Ab|Kustannusosakeyhti\u00F6|Kustannus Oy|Oy) /ugi, '');\n if (nameOnly === name) {\n return [name, undefined];\n }\n return [nameOnly, name.substring(0, name.length - nameOnly.length - 1)]; // -1 removes final space\n }\n\n function getBestName(name) {\n const NAME = name.toUpperCase();\n\n if (NAME === 'WSOY') {\n return 'Werner S\u00F6derstr\u00F6m osakeyhti\u00F6';\n }\n if (NAME === 'NTAMO') {\n return 'ntamo';\n }\n return name;\n }\n}\n\nexport function canContainOptionalQualifier(tag, subfieldCode) {\n // We have made 300$a NON-repeatable (against specs), as we newer want there to repeat (probably near-duplicates)\n if (tag === '300' && subfieldCode === 'a') {\n return true;\n }\n // 776$i is actually not needed for counterpart stuff (since it's repeatable), but it is needed in merge subfield stage.\n if (tag === '776' && subfieldCode === 'i') {\n return true;\n }\n return false;\n}\n\nfunction withAndWithoutQualifierAgree(value1, value2, tag, subfieldCode) {\n if (!canContainOptionalQualifier(tag, subfieldCode)) {\n return false;\n }\n\n const [name1, qualifier1] = splitToNameAndQualifier(value1);\n const [name2, qualifier2] = splitToNameAndQualifier(value2);\n\n //nvdebug(`CN1: '${name1}', '${qualifier1}'`, debugDev);\n //nvdebug(`CN2: '${name2}', '${qualifier2}'`, debugDev);\n\n if (name1.toLowerCase() !== name2.toLowerCase()) {\n return false;\n }\n\n // If either value does not have a qualifier, they are considered equals:\n if (qualifier1 === undefined || qualifier2 === undefined || qualifier1.toLowerCase() === qualifier2.toLowerCase()) {\n return true;\n }\n\n return false;\n\n\n}\n\n\nfunction corporateNamesAgree(value1, value2, tag, subfieldCode) {\n if (subfieldCode !== 'a' || !['110', '610', '710', '810'].includes(tag)) {\n return false;\n }\n const nameData1 = splitToNameAndQualifierAndProcessName(value1);\n const nameData2 = splitToNameAndQualifierAndProcessName(value2);\n\n nvdebug(`CN1: '${nameData1.name}', '${nameData1.qualifier}'`, debugDev);\n nvdebug(`CN2: '${nameData2.name}', '${nameData2.qualifier}'`, debugDev);\n\n if (nameData1.name !== nameData2.name) {\n return false;\n }\n\n if (nameData1.qualifier && nameData2.qualifier && nameData1.qualifier !== nameData2.qualifier) {\n return false;\n }\n // Currently all prefixes and suffixes are publisher information, so there's no point comparing them any further...\n\n return true;\n\n /*\n function isKustantaja(nameData) {\n if (nameData.suffix.match(/^(?:Kustannus|Kustannus oy|kustannusosakeyhti\u00F6)$/iu)) {\n return true;\n }\n if (nameData.prefix.match(/^Kustannus Oy$/i)) {\n return true;\n }\n return false;\n }\n */\n}\n\n\nfunction pairableValue(tag, subfieldCode, value1, value2) {\n // This function could just return true or false.\n // I thought of preference when I wrote this, but preference implemented *here* (modularity). mergeFields.js should handle preference.\n if (withAndWithoutQualifierAgree(value1, value2, tag, subfieldCode)) {\n // 300$a \"whatever\" and \"whatever (123 sivua)\"\n return value1;\n }\n if (partsAgree(value1, value2, tag, subfieldCode) || corporateNamesAgree(value1, value2, tag, subfieldCode)) {\n // Pure baseness: here we assume that base's value1 is better than source's value2.\n return value1;\n }\n\n return undefined;\n}\n\n\nfunction counterpartExtraNormalize(tag, subfieldCode, value) {\n\n // Remove trailing punctuation:\n value = value.replace(/(\\S)(?:,|\\.|\\?|!|\\. -| *:| *;| =| \\/)$/u, '$1');\n // Remove brackets:\n value = value.replace(/^\\(([^()]+)\\)$/u, '$1'); // Remove initial '(' and final ')' if both exist.\n value = value.replace(/^\\[([^[\\]]+)\\]$/u, '$1'); // Remove initial '[' and final ']' if both exist.\n // Mainly for field 260$c:\n value = removeCopyright(value);\n\n value = value.replace(/http:\\/\\//ug, 'https://'); // MET-501: http vs https\n value = normalizeForSamenessCheck(tag, subfieldCode, value);\n\n return value;\n}\n\nfunction uniqueKeyMatches(baseField, sourceField, forcedKeyString = null) {\n // NB! Assume that field1 and field2 have same relevant subfields.\n // What to do if if base\n // const keySubfieldsAsString = forcedKeyString || getUniqueKeyFields(field1);\n const keySubfieldsAsString = forcedKeyString || getMergeConstraintsForTag(baseField.tag, 'key');\n //return mandatorySubfieldComparison(baseField, sourceField, keySubfieldsAsString);\n return optionalSubfieldComparison(baseField, sourceField, keySubfieldsAsString);\n}\n\n\nfunction optionalSubfieldComparison(originalBaseField, originalSourceField, keySubfieldsAsString) {\n // Here \"optional subfield\" means a subfield, that needs not to be present, but if present, it must be identical...\n // (Think of a better name...)\n // We use clones here, since these changes done below are not intented to appear on the actual records.\n const field1 = cloneAndNormalizeFieldForComparison(originalBaseField);\n const field2 = cloneAndNormalizeFieldForComparison(originalSourceField);\n\n if (keySubfieldsAsString === null) { // does not currently happen\n // If keySubfieldsAsString is undefined, (practically) everything is the string.\n // When everything is the string, the strings need to be (practically) identical.\n // (NB! Here order matters. We should probably make it matter everywhere.)\n // (However, keySubfieldsAsString === '' will always succeed. Used by 040 at least.)\n // NB! substring(6) skips \"TAG II\" (I=indicator. Thus we skip indicators)\n return fieldToString(field1).substring(6) === fieldToString(field2).substring(6);\n }\n const subfieldArray = keySubfieldsAsString.split('');\n\n // Long forgotten, but my educated guess about this: if 'key' is defined in merge constraints\n // for this field, then at least one of the subfield codes in 'key' must be present in both fields.\n // However, this is not necessarily right.\n if (subfieldArray.length > 0 && !subfieldArray.some(sfCode => hasCommonNominator(sfCode))) {\n return false;\n }\n\n\n return subfieldArray.every(subfieldCode => testOptionalSubfield(originalBaseField.tag, subfieldCode));\n\n\n function hasCommonNominator(subfieldCode) {\n //nvdebug(`hasCommonNominator(${subfieldCode})? '${fieldToString(originalBaseField)}' vs '${fieldToString(originalSourceField)}'`, debugDev);\n\n // If base has $a and source has $b, there's no common nominator, thus fail...\n const subfields1 = field1.subfields.filter(subfield => subfield.code === subfieldCode && valueCarriesMeaning(field1.tag, subfield.code, subfield.value));\n const subfields2 = field2.subfields.filter(subfield => subfield.code === subfieldCode && valueCarriesMeaning(field2.tag, subfield.code, subfield.value));\n\n return subfields1.length > 0 && subfields2.length > 0;\n }\n\n function testOptionalSubfield(tag, subfieldCode) {\n // NB! Don't compare non-meaningful subfields\n const subfields1 = field1.subfields.filter(subfield => subfield.code === subfieldCode && valueCarriesMeaning(field1.tag, subfield.code, subfield.value));\n const subfields2 = field2.subfields.filter(subfield => subfield.code === subfieldCode && valueCarriesMeaning(field2.tag, subfield.code, subfield.value));\n\n // If one side is empty, all is good\n if (subfields1.length === 0 || subfields2.length === 0) {\n return true;\n }\n\n //nvdebugSubfieldArray(subfields1, 'SF1', debugDev);\n //nvdebugSubfieldArray(subfields2, 'SF2', debugDev);\n\n // When pairing we can use stronger normalizations than the generic one:\n const subfieldValues1 = subfields1.map(sf => counterpartExtraNormalize(tag, subfieldCode, sf.value));\n const subfieldValues2 = subfields2.map(sf => counterpartExtraNormalize(tag, subfieldCode, sf.value));\n\n //nvdebug(`SF1 NORM: ${subfieldValues1.join(' --')}`, debugDev);\n //nvdebug(`SF2 NORM: ${subfieldValues2.join(' --')}`, debugDev);\n\n // If one set is a subset of the other, all is probably good (how about 653$a, 505...)\n if (subfieldValues1.every(val => subfieldValues2.includes(val)) || subfieldValues2.every(val => subfieldValues1.includes(val))) {\n return true;\n }\n\n if (subfieldValues1.length === 1 && subfieldValues2.length === 1) {\n return pairableValue(field1.tag, subfieldCode, subfieldValues1[0], subfieldValues2[0]) !== undefined;\n }\n\n return false;\n\n }\n}\n\n\nfunction mandatorySubfieldComparison(originalField1, originalField2, keySubfieldsAsString) {\n // NB! We use clones here, since these changes done below are not intented to appear on the actual records.\n const field1 = cloneAndNormalizeFieldForComparison(originalField1);\n const field2 = cloneAndNormalizeFieldForComparison(originalField2);\n if (keySubfieldsAsString === null) { // does not currently happen\n // If keySubfieldsAsString is undefined, (practically) everything is the string.\n // When everything is the string, the strings need to be (practically) identical.\n // (NB! Here order matters. We should probably make it matter everywhere.)\n // (However, keySubfieldsAsString === '' will always succeed. Used by 040 at least.)\n return fieldToString(field1) === fieldToString(field2);\n }\n const subfieldArray = keySubfieldsAsString.split('');\n\n //const differentSubfieldCodes = differentPublisherSubfields(originalField1, originalField2);\n\n return subfieldArray.every(subfieldCode => mandatorySingleSubfieldComparison(subfieldCode));\n\n function mandatorySingleSubfieldComparison(subfieldCode) {\n //const otherSubfieldCode = getOtherSubfieldCode(subfieldCode);\n const subfieldValues1 = field1.subfields.filter(subfield => subfield.code === subfieldCode).map(sf => sf.value);\n const subfieldValues2 = field2.subfields.filter(subfield => subfield.code === subfieldCode).map(sf => sf.value);\n // Assume that at least 1 instance must exist and that all instances must match\n if (subfieldValues1.length !== subfieldValues2.length) {\n debugDev(`mSC: Unique key: subfield ${subfieldCode} issues...`);\n return false;\n }\n\n return subfieldValues1.every(value => subfieldValues2.includes(value));\n }\n\n}\n\nfunction tagToRegexp(tag, internalMerge = false) {\n if (internalMerge && tag in counterpartRegexpsSingle) {\n return counterpartRegexpsSingle[tag];\n }\n if (!internalMerge && tag in counterpartRegexps) { // eg. 700 looks for tag /^[17]00$/...\n const regexp = counterpartRegexps[tag];\n //nvdebug(`regexp for ${tag} found: ${regexp}`, debugDev);\n return regexp;\n }\n //nvdebug(`WARNING: tagToRegexp(${tag}): no precompiled regexp found.`, debugDev);\n return new RegExp(`^${tag}$`, 'u');\n}\n\nfunction areRequiredSubfieldsPresent(field) {\n const subfieldString = getMergeConstraintsForTag(field.tag, 'required');\n if (subfieldString === null) {\n return true;\n } // nothing is required\n const subfieldArray = subfieldString.split('');\n return subfieldArray.every(sfcode => {\n const result = fieldHasSubfield(field, sfcode);\n if (!result) {\n debugDev(`Required subfield \u2021${sfcode} not found in '${fieldToString(field)}'!`);\n return false;\n }\n return true;\n });\n}\n\nfunction arePairedSubfieldsInBalance(field1, field2) {\n const subfieldString = getMergeConstraintsForTag(field1.tag, 'paired');\n if (subfieldString === null) {\n return true;\n }\n const subfieldArray = subfieldString.split('');\n\n return subfieldArray.every(sfcode => fieldHasNSubfields(field1, sfcode) === fieldHasNSubfields(field2, sfcode));\n}\n\nfunction syntacticallyMergablePair(baseField, sourceField, config) {\n // Indicators must typically be equal (there are exceptions such as non-filing characters though):\n if (!mergableIndicator1(baseField, sourceField, config)) {\n nvdebug(`non-mergable (reason: indicator1): ${JSON.stringify(config)}`, debugDev);\n return false;\n }\n\n if (!mergableIndicator2(baseField, sourceField, config)) {\n nvdebug(`non-mergable (reason: indicator2): ${JSON.stringify(config)}`, debugDev);\n return false;\n }\n\n if (!controlSubfieldsPermitMerge(baseField, sourceField)) {\n nvdebug('non-mergable (reason: control subfield)', debugDev);\n return false;\n }\n\n // NB! field1.tag and field2.tag might differ (1XX vs 7XX). Therefore required subfields might theoretically differ as well.\n // Note: Theoretically 260 $efg vs 264 with IND2=3 has already been handled by the preprocessor.\n // Thus check both:\n if (!areRequiredSubfieldsPresent(baseField) || !areRequiredSubfieldsPresent(sourceField)) {\n nvdebug('non-mergable (reason: missing subfields)', debugDev);\n return false;\n }\n\n // Stuff of Hacks! Eg. require that both fields either have or have not X00$t:\n if (!arePairedSubfieldsInBalance(baseField, sourceField)) {\n nvdebug('required subfield pair check failed.', debugDev);\n return false;\n }\n\n return true;\n}\n\nfunction mergablePair(baseField, sourceField, config) {\n if (!syntacticallyMergablePair(baseField, sourceField, config)) {\n return false;\n }\n\n //debug('Test semantics...');\n if (!semanticallyMergablePair(baseField, sourceField)) {\n nvdebug('non-mergable (reason: semantics)', debugDev);\n return false;\n }\n\n nvdebug(`MERGABLE PAIR:\\n B: ${fieldToString(baseField)}\\n S: ${fieldToString(sourceField)}`, debugDev);\n return true;\n}\n\n\nfunction pairableAsteriIDs(baseField, sourceField) {\n //nvdebug(`ASTERI1 ${fieldToString(baseField)}`, debugDev); // eslint-disable-line\n //nvdebug(`ASTERI2 ${fieldToString(sourceField)}`, debugDev); // eslint-disable-line\n\n // Check that relevant control subfield(s) exist in both records (as controlSubfieldsPermitMerge() doesn't check it):\n const fin11a = getAsteriIDs(baseField);\n if (fin11a.length === 0) {\n return false;\n }\n const fin11b = getAsteriIDs(sourceField);\n if (fin11b.length === 0) {\n return false;\n }\n //nvdebug(`ASTERI WP3:\\n${fin11a.join(\", \")}\\n${fin11b.join(\", \")}`, debugDev); // eslint-disable-line\n\n // Check that found control subfields agree. Use pre-existing generic function to reduce code.\n // (NB! We could optimize and just return true here, as control subfield check is done elsewhere as well.\n // However, explicitly checking them here makes the code more robust.)\n if (!controlSubfieldsPermitMerge(baseField, sourceField)) {\n return false;\n }\n //console.log(`ASTERI PAIR ${fieldToString(sourceField)}`); // eslint-disable-line\n return true;\n\n // NB! This boldly assumes that the default prefix for Asteri is '(FIN11)', not '(FI-ASTERI-N)' nor a finaf urn...\n function getAsteriIDs(field) {\n return field.subfields.filter(sf => sf.code === '0')\n .map(sf => normalizeControlSubfieldValue(sf.value))\n .filter(val => val.substring(0, 7) === '(FIN11)');\n }\n}\n\n\nfunction hasRepeatableSubfieldThatShouldBeTreatedAsNonRepeatable(field) {\n if (field.tag === '260' || field.tag === '264') {\n return ['a', 'b', 'c', 'e', 'f', 'g'].some(subfieldCode => fieldHasMultipleSubfields(field, subfieldCode));\n }\n if (field.tag === '382') {\n return ['a', 'b', 'd', 'e', 'n', 'p'].some(subfieldCode => fieldHasMultipleSubfields(field, subfieldCode));\n }\n if (field.tag === '505') {\n return ['t', 'r', 'g'].some(subfieldCode => fieldHasMultipleSubfields(field, subfieldCode));\n }\n\n return false;\n}\n\nfunction pairableName(baseField, sourceField) {\n // 100$a$t: remove $t and everything after that\n const reducedField1 = fieldToNamePart(baseField);\n const reducedField2 = fieldToNamePart(sourceField);\n\n const string1 = fieldToString(reducedField1);\n const string2 = fieldToString(reducedField2);\n\n //nvdebug(`IN: pairableName():\\n '${string1}' vs\\n '${string2}'`, debugDev);\n if (string1 === string2) {\n return true;\n }\n\n // Essentially these are too hard to handle with field-merge (eg. multi-505$g)\n if (hasRepeatableSubfieldThatShouldBeTreatedAsNonRepeatable(reducedField1) || hasRepeatableSubfieldThatShouldBeTreatedAsNonRepeatable(reducedField2)) {\n return false;\n }\n\n // Compare the remaining subsets...\n // First check that name matches...\n if (uniqueKeyMatches(reducedField1, reducedField2)) {\n nvdebug(` name match: '${fieldToString(reducedField1)}'`, debugDev);\n return true;\n }\n\n // However, name mismatch is not critical! If Asteri ID matches, it's still a match! *NOT* sure whether this a good idea.\n // 2023-01-24 Disable this. Caretaker can fix these later on. Not a job for merge. We can't be sure that $0 pair is corrent, nor which version (base or source) to use.\n // 2023-03-07: Enable this again!\n if (pairableAsteriIDs(baseField, sourceField)) {\n //nvdebug(` name match based on ASTERI $0'`, debugDev);\n return true;\n }\n\n nvdebug(` name mismatch:`, debugDev);\n nvdebug(` '${fieldToString(reducedField1)}' vs`, debugDev);\n nvdebug(` '${fieldToString(reducedField2)}'`, debugDev);\n return false;\n}\n\n\nfunction semanticallyMergablePair(baseField, sourceField) {\n // On rare occasions a field contains also a title part. For these name part (= normally everything) and title part\n // must be checked separately:\n if (!titlePartsMatch(baseField, sourceField)) {\n nvdebug(` ${baseField.tag} is unmergable: Title part mismatch.`, debugDev);\n return false;\n }\n\n // Hmm... we should check lifespan here, $d YYYY\n\n // Handle the field specific \"unique key\" (=set of fields that make the field unique\n if (!pairableName(baseField, sourceField)) {\n nvdebug('Unmergable: Name part mismatch', debugDev);\n return false;\n }\n //debug(' Semantic checks passed! We are MERGABLE!');\n\n return true;\n}\n\n\nfunction namePartThreshold(field) {\n // Threshold is only applicaple to some tags..\n if (!(/[10]0$/u).test(field.tag)) {\n return -1;\n }\n const t = field.subfields.findIndex(currSubfield => currSubfield.code === 't');\n const u = t; // field.subfields.findIndex(currSubfield => currSubfield.code === 'u');\n if (t === -1) {\n return u;\n }\n if (u === -1) {\n return t;\n }\n return t > u ? u : t;\n}\n\nfunction fieldToNamePart(field) {\n const index = namePartThreshold(field);\n const relevantSubfields = field.subfields.filter((sf, i) => i < index || index === -1).filter(sf => !irrelevantSubfieldsInNameAndTitlePartComparison.includes(sf.code));\n\n const subsetField = {'tag': field.tag, 'ind1': field.ind1, 'ind2': field.ind2, subfields: relevantSubfields};\n\n /*\n if (index > -1) {\n debugDev(`Name subset: ${fieldToString(subsetField)}`);\n }\n */\n\n // Ummm... Sometimes $0 comes after $t but belongs to name part\n\n return subsetField;\n}\n\nfunction fieldToTitlePart(field) {\n // Take everything after 1st subfield $t...\n const index = field.subfields.findIndex(currSubfield => currSubfield.code === 't');\n const relevantSubfields = field.subfields.filter((sf, i) => i >= index).filter(sf => !irrelevantSubfieldsInNameAndTitlePartComparison.includes(sf.code));\n const subsetField = {'tag': field.tag, 'ind1': field.ind1, 'ind2': field.ind2, subfields: relevantSubfields};\n debugDev(`Title subset: ${fieldToString(subsetField)}`);\n return subsetField;\n}\n\nfunction containsTitlePart(field) {\n return fieldCanHaveTitlePart(field) && fieldHasSubfield(field, 't');\n\n function fieldCanHaveTitlePart(field) {\n return ['100', '110', '111', '700', '710', '711'].includes(field.tag);\n }\n}\n\nfunction titlePartsMatch(field1, field2) {\n if (!containsTitlePart(field1)) {\n return !containsTitlePart(field2);\n }\n if (!containsTitlePart(field2)) {\n return false;\n }\n\n debugDev(`TITLE PARTS NEED TO BE COMPARED`);\n\n // 100$a$t: remove $t and everything after that\n const subset1 = fieldToTitlePart(field1);\n const subset2 = fieldToTitlePart(field2);\n // Easter Egg, ffs. Hardcoded exception\n return mandatorySubfieldComparison(subset1, subset2, 'dfhklmnoprstxvg');\n}\n\n\nfunction getAlternativeNamesFrom9XX(record, field) {\n // Should we support 6XX and 8XX as well? Prolly not...\n if (!field.tag.match(/^(?:100|110|111|600|610|611|700|710|711)$/u)) {\n return [];\n }\n const tag = `9${field.tag.substring(1)}`;\n const cands = record.get(tag).filter(f => fieldHasSubfield(f, 'a') && fieldHasSubfield(f, 'y'));\n if (cands.length === 0) {\n return [];\n }\n const punctuationlessField = cloneAndRemovePunctuation(field);\n const [name] = punctuationlessField.subfields.filter(sf => sf.code === 'a').map(sf => sf.value);\n\n return cands.map(candField => getAltName(candField)).filter(val => val !== undefined);\n\n\n function getAltName(altField) {\n const [altA] = altField.subfields.filter(sf => sf.code === 'a').map(sf => sf.value);\n const [altY] = altField.subfields.filter(sf => sf.code === 'y').map(sf => sf.value);\n nvdebug(`Compare '${name}' vs '${altA}'/'${altY}'`, debugDev);\n if (name === altA) {\n return altY;\n }\n if (name === altY) {\n return altA;\n }\n nvdebug(` miss`, debugDev);\n return undefined;\n }\n\n}\n\n\nfunction mergablePairWithAltName(normCandField, normalizedField, altName, config) {\n // Replace source field $a name with alternative name and then compare:\n const [a] = normalizedField.subfields.filter(sf => sf.code === 'a');\n if (!a) {\n return false;\n }\n a.value = altName;\n\n return mergablePair(normCandField, normalizedField, config);\n}\n\nfunction getCounterpartIndex(field, counterpartCands, altNames, config) {\n const normalizedField = cloneAndNormalizeFieldForComparison(field);\n const normalizedCounterpartCands = counterpartCands.map(f => cloneAndNormalizeFieldForComparison(f));\n const index = normalizedCounterpartCands.findIndex(normCandField => mergablePair(normCandField, normalizedField, config));\n if (index > -1) {\n return index;\n }\n\n return normalizedCounterpartCands.findIndex(normCandField => altNames.some(altName => mergablePairWithAltName(normCandField, normalizedField, altName, config)));\n}\n\n\nfunction field264Exception(baseField, sourceRecord, sourceField, config) {\n nvdebug('Field 264 exception as per MET-456', debugDev);\n if (baseField.tag !== '264') {\n return false;\n }\n\n if (sourceField.tag !== '264' || sourceRecord.get('264').length !== 1) {\n return false;\n }\n\n // Don't worry about semantics:\n return syntacticallyMergablePair(sourceField, baseField, config);\n}\n\nfunction getCounterpartCandidates(field, record) {\n const counterpartCands = record.get(tagToRegexp(field.tag, record.internalMerge));\n\n // MELKEHITYS-2969: copyright years should not merge with non-copyright years\n if (field.tag === '260' && isNotCopyrightYear(field)) {\n return counterpartCands.filter(candField => !isCopyrightField264(candField));\n }\n\n if (field.tag === '264' && isCopyrightField264(field)) { // Copyright year\n return counterpartCands.filter(candField => !isNotCopyrightYear(candField));\n }\n\n function isCopyrightField264(field) {\n return field.tag === '264' && field.ind2 === '4';\n }\n function isNotCopyrightYear(field) {\n if (field.tag === '264') {\n return !isCopyrightField264(field);\n }\n // Field 260: copyright year does not contain $a or $b:\n return !field.subfields.some(sf => sf.code === 'a' && sf.code === 'b');\n }\n\n return counterpartCands;\n\n}\n\nexport function baseIsSource(base, source) {\n base.localTest = true;\n const result = source.localTest;\n delete base.localTest;\n return result;\n}\n\nexport function getCounterpart(baseRecord, sourceRecord, field, config) {\n // First get relevant candidate fields. Note that 1XX and corresponding 7XX are considered equal, and tags 260 and 264 are lumped together.\n // (<= Note that self-merge behaves differently from two records here.)\n // Hacks: 973 can merge with 773, 940 can merge with 240 (but not the other way around)\n //nvdebug(`COUNTERPART FOR '${fieldToString(field)}'?`, debugDev);\n const counterpartCands = getCounterpartCandidates(field, baseRecord).filter(f => !f.mergeCandidate);\n\n if (!counterpartCands || counterpartCands.length === 0) {\n //nvdebug(`No counterpart(s) found for ${fieldToString(field)}`, debugDev);\n return null;\n }\n\n nvdebug(`Compare incoming '${fieldToString(field)}' with (up to) ${counterpartCands.length} existing field(s)`, debugDev);\n\n const normalizedField = cloneAndNormalizeFieldForComparison(field); // mainly strip punctuation here\n\n nvdebug(`Norm to: '${fieldToString(normalizedField)}'`, debugDev);\n\n const uniqueAlternativeNames = getUniqueAlernativeNames();\n\n function getUniqueAlernativeNames() {\n if (baseIsSource(baseRecord, sourceRecord)) {\n return [];\n }\n // Try to look for alternative names from base and source record's 9XX fields:\n const alternativeNames = getAlternativeNamesFrom9XX(baseRecord, field).concat(getAlternativeNamesFrom9XX(sourceRecord, field));\n return alternativeNames.filter((name, i) => alternativeNames.indexOf(name) === i);\n }\n\n //nvdebug(` S: ${fieldToString(normalizedField)}`, debugDev);\n // Then find (the index of) the first mathing candidate field and return it.\n const index = getCounterpartIndex(normalizedField, counterpartCands, uniqueAlternativeNames, config);\n\n if (index > -1) {\n return counterpartCands[index];\n }\n\n // MET-456 exception\n if (counterpartCands.length === 1 && field264Exception(counterpartCands[0], sourceRecord, field, config)) {\n return counterpartCands[0];\n }\n\n return null;\n}\n\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["// For each incoming field that\n\nimport createDebugLogger from 'debug';\nimport {fieldHasSubfield, fieldHasNSubfields, fieldHasMultipleSubfields, fieldToString, nvdebug, removeCopyright} from '../utils.js';\nimport {cloneAndNormalizeFieldForComparison, cloneAndRemovePunctuation} from '../normalizeFieldForComparison.js';\n// This should be done via our own normalizer:\nimport {normalizeControlSubfieldValue} from '../normalize-identifiers.js';\n\nimport {getMergeConstraintsForTag} from './mergeConstraints.js';\nimport {controlSubfieldsPermitMerge} from './controlSubfields.js';\nimport {mergableIndicator1, mergableIndicator2} from './mergableIndicator.js';\nimport {partsAgree} from '../normalizeSubfieldValueForComparison.js';\nimport {normalizeForSamenessCheck, valueCarriesMeaning} from './worldKnowledge.js';\nimport { provenanceSubfieldsPermitMerge } from './dataProvenance.js';\n\nconst debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:mergeField:counterpart');\n//const debugData = debug.extend('data');\nconst debugDev = debug.extend('dev');\n\nconst irrelevantSubfieldsInNameAndTitlePartComparison = '5689';\n\nconst counterpartRegexps = { // NB! tag is from source!\n // Note that in the normal case, all source 1XX fields have been converted to 7XX fields.\n '100': /^[17]00$/u, '110': /^[17]10$/u, '111': /^[17]11$/u, '130': /^[17]30$/u,\n '260': /^26[04]$/u, '264': /^26[04]$/u,\n '700': /^[17]00$/u, '710': /^[17]10$/u, '711': /^[17]11$/u, '730': /^[17]30$/u,\n // Hacks:\n '940': /^[29]40$/u, '973': /^[79]73$/u\n};\n\nconst counterpartRegexpsSingle = {\n // when base===source, never merge 1XX to 7XX, always 7XX to 1XX! Also, don't merge 264 to 260.\n '260': /^26[04]$/u,\n '700': /^[17]00$/u, '110': /^[17]10$/u, '111': /^[17]11$/u, '130': /^[17]30$/u,\n // Hacks:\n '940': /^[29]40$/u, '973': /^[79]73$/u\n};\n\n/*\nfunction differentPublisherSubfields(field1, field2) {\n if (field1.tag === '260' && field2.tag === '264' && field2.ind2 === '3') {\n return true;\n }\n if (field1.tag === '264' && field1.ind2 === '3' && field2.tag === '260') {\n return true;\n }\n return false;\n}\n*/\n\nexport function splitToNameAndQualifier(value) {\n if (value.match(/^.* \\([^()]+\\)$/u)) {\n const name = value.replace(/^(.*) \\([^()]+\\)$/u, '$1');\n const qualifier = value.replace(/^.* (\\([^()]+\\))$/u, '$1');\n return [name, qualifier];\n }\n return [value, undefined];\n}\n\nexport function splitToNameAndQualifierAndProcessName(name) {\n //const nameOnly = name.replace(/(?: \\([^)]+\\)| abp?| Kustannus| Kustannus Oy|, kustannusosakeyhti\u00F6| oyj?| ry)$/ugi, '');\n const [qualifierlessName, qualifier] = splitToNameAndQualifier(name);\n\n const [prefix, basename, suffix] = stripPrefixAndSuffix(qualifierlessName);\n\n return {name: getBestName(basename).toLowerCase(), prefix, suffix, qualifier};\n\n function stripPrefixAndSuffix(companyName) {\n const [nameOnly, suffix] = extractSuffix(companyName);\n const [nameOnly2, prefix] = extractPrefix(nameOnly);\n return [prefix, nameOnly2, suffix];\n }\n\n function extractSuffix(name) {\n const nameOnly = name.replace(/(?: \\([^)]+\\)| abp?| Kustannus| Kustannus Oy|, kustannusosakeyhti\u00F6| oyj?| ry)$/ugi, '');\n if (nameOnly === name) {\n return [name, undefined];\n }\n return [nameOnly, name.substring(nameOnly.length).replace(/^,? /u, '')];\n }\n\n function extractPrefix(name) {\n const nameOnly = name.replace(/^(?:Ab|Kustannusosakeyhti\u00F6|Kustannus Oy|Oy) /ugi, '');\n if (nameOnly === name) {\n return [name, undefined];\n }\n return [nameOnly, name.substring(0, name.length - nameOnly.length - 1)]; // -1 removes final space\n }\n\n function getBestName(name) {\n const NAME = name.toUpperCase();\n\n if (NAME === 'WSOY') {\n return 'Werner S\u00F6derstr\u00F6m osakeyhti\u00F6';\n }\n if (NAME === 'NTAMO') {\n return 'ntamo';\n }\n return name;\n }\n}\n\nexport function canContainOptionalQualifier(tag, subfieldCode) {\n // We have made 300$a NON-repeatable (against specs), as we newer want there to repeat (probably near-duplicates)\n if (tag === '300' && subfieldCode === 'a') {\n return true;\n }\n // 776$i is actually not needed for counterpart stuff (since it's repeatable), but it is needed in merge subfield stage.\n if (tag === '776' && subfieldCode === 'i') {\n return true;\n }\n return false;\n}\n\nfunction withAndWithoutQualifierAgree(value1, value2, tag, subfieldCode) {\n if (!canContainOptionalQualifier(tag, subfieldCode)) {\n return false;\n }\n\n const [name1, qualifier1] = splitToNameAndQualifier(value1);\n const [name2, qualifier2] = splitToNameAndQualifier(value2);\n\n //nvdebug(`CN1: '${name1}', '${qualifier1}'`, debugDev);\n //nvdebug(`CN2: '${name2}', '${qualifier2}'`, debugDev);\n\n if (name1.toLowerCase() !== name2.toLowerCase()) {\n return false;\n }\n\n // If either value does not have a qualifier, they are considered equals:\n if (qualifier1 === undefined || qualifier2 === undefined || qualifier1.toLowerCase() === qualifier2.toLowerCase()) {\n return true;\n }\n\n return false;\n\n\n}\n\n\nfunction corporateNamesAgree(value1, value2, tag, subfieldCode) {\n if (subfieldCode !== 'a' || !['110', '610', '710', '810'].includes(tag)) {\n return false;\n }\n const nameData1 = splitToNameAndQualifierAndProcessName(value1);\n const nameData2 = splitToNameAndQualifierAndProcessName(value2);\n\n nvdebug(`CN1: '${nameData1.name}', '${nameData1.qualifier}'`, debugDev);\n nvdebug(`CN2: '${nameData2.name}', '${nameData2.qualifier}'`, debugDev);\n\n if (nameData1.name !== nameData2.name) {\n return false;\n }\n\n if (nameData1.qualifier && nameData2.qualifier && nameData1.qualifier !== nameData2.qualifier) {\n return false;\n }\n // Currently all prefixes and suffixes are publisher information, so there's no point comparing them any further...\n\n return true;\n\n /*\n function isKustantaja(nameData) {\n if (nameData.suffix.match(/^(?:Kustannus|Kustannus oy|kustannusosakeyhti\u00F6)$/iu)) {\n return true;\n }\n if (nameData.prefix.match(/^Kustannus Oy$/i)) {\n return true;\n }\n return false;\n }\n */\n}\n\n\nfunction pairableValue(tag, subfieldCode, value1, value2) {\n // This function could just return true or false.\n // I thought of preference when I wrote this, but preference implemented *here* (modularity). mergeFields.js should handle preference.\n if (withAndWithoutQualifierAgree(value1, value2, tag, subfieldCode)) {\n // 300$a \"whatever\" and \"whatever (123 sivua)\"\n return value1;\n }\n if (partsAgree(value1, value2, tag, subfieldCode) || corporateNamesAgree(value1, value2, tag, subfieldCode)) {\n // Pure baseness: here we assume that base's value1 is better than source's value2.\n return value1;\n }\n\n return undefined;\n}\n\n\nfunction counterpartExtraNormalize(tag, subfieldCode, value) {\n\n // Remove trailing punctuation:\n value = value.replace(/(\\S)(?:,|\\.|\\?|!|\\. -| *:| *;| =| \\/)$/u, '$1');\n // Remove brackets:\n value = value.replace(/^\\(([^()]+)\\)$/u, '$1'); // Remove initial '(' and final ')' if both exist.\n value = value.replace(/^\\[([^[\\]]+)\\]$/u, '$1'); // Remove initial '[' and final ']' if both exist.\n // Mainly for field 260$c:\n value = removeCopyright(value);\n\n value = value.replace(/http:\\/\\//ug, 'https://'); // MET-501: http vs https\n value = normalizeForSamenessCheck(tag, subfieldCode, value);\n\n return value;\n}\n\nfunction uniqueKeyMatches(baseField, sourceField, forcedKeyString = null) {\n // NB! Assume that field1 and field2 have same relevant subfields.\n // What to do if if base\n // const keySubfieldsAsString = forcedKeyString || getUniqueKeyFields(field1);\n const keySubfieldsAsString = forcedKeyString || getMergeConstraintsForTag(baseField.tag, 'key');\n //return mandatorySubfieldComparison(baseField, sourceField, keySubfieldsAsString);\n return optionalSubfieldComparison(baseField, sourceField, keySubfieldsAsString);\n}\n\n\nfunction optionalSubfieldComparison(originalBaseField, originalSourceField, keySubfieldsAsString) {\n // Here \"optional subfield\" means a subfield, that needs not to be present, but if present, it must be identical...\n // (Think of a better name...)\n // We use clones here, since these changes done below are not intented to appear on the actual records.\n const field1 = cloneAndNormalizeFieldForComparison(originalBaseField);\n const field2 = cloneAndNormalizeFieldForComparison(originalSourceField);\n\n if (keySubfieldsAsString === null) { // does not currently happen\n // If keySubfieldsAsString is undefined, (practically) everything is the string.\n // When everything is the string, the strings need to be (practically) identical.\n // (NB! Here order matters. We should probably make it matter everywhere.)\n // (However, keySubfieldsAsString === '' will always succeed. Used by 040 at least.)\n // NB! substring(6) skips \"TAG II\" (I=indicator. Thus we skip indicators)\n return fieldToString(field1).substring(6) === fieldToString(field2).substring(6);\n }\n const subfieldArray = keySubfieldsAsString.split('');\n\n // Long forgotten, but my educated guess about this: if 'key' is defined in merge constraints\n // for this field, then at least one of the subfield codes in 'key' must be present in both fields.\n // However, this is not necessarily right.\n if (subfieldArray.length > 0 && !subfieldArray.some(sfCode => hasCommonNominator(sfCode))) {\n return false;\n }\n\n\n return subfieldArray.every(subfieldCode => testOptionalSubfield(originalBaseField.tag, subfieldCode));\n\n\n function hasCommonNominator(subfieldCode) {\n //nvdebug(`hasCommonNominator(${subfieldCode})? '${fieldToString(originalBaseField)}' vs '${fieldToString(originalSourceField)}'`, debugDev);\n\n // If base has $a and source has $b, there's no common nominator, thus fail...\n const subfields1 = field1.subfields.filter(subfield => subfield.code === subfieldCode && valueCarriesMeaning(field1.tag, subfield.code, subfield.value));\n const subfields2 = field2.subfields.filter(subfield => subfield.code === subfieldCode && valueCarriesMeaning(field2.tag, subfield.code, subfield.value));\n\n return subfields1.length > 0 && subfields2.length > 0;\n }\n\n function testOptionalSubfield(tag, subfieldCode) {\n // NB! Don't compare non-meaningful subfields\n const subfields1 = field1.subfields.filter(subfield => subfield.code === subfieldCode && valueCarriesMeaning(field1.tag, subfield.code, subfield.value));\n const subfields2 = field2.subfields.filter(subfield => subfield.code === subfieldCode && valueCarriesMeaning(field2.tag, subfield.code, subfield.value));\n\n // If one side is empty, all is good\n if (subfields1.length === 0 || subfields2.length === 0) {\n return true;\n }\n\n //nvdebugSubfieldArray(subfields1, 'SF1', debugDev);\n //nvdebugSubfieldArray(subfields2, 'SF2', debugDev);\n\n // When pairing we can use stronger normalizations than the generic one:\n const subfieldValues1 = subfields1.map(sf => counterpartExtraNormalize(tag, subfieldCode, sf.value));\n const subfieldValues2 = subfields2.map(sf => counterpartExtraNormalize(tag, subfieldCode, sf.value));\n\n //nvdebug(`SF1 NORM: ${subfieldValues1.join(' --')}`, debugDev);\n //nvdebug(`SF2 NORM: ${subfieldValues2.join(' --')}`, debugDev);\n\n // If one set is a subset of the other, all is probably good (how about 653$a, 505...)\n if (subfieldValues1.every(val => subfieldValues2.includes(val)) || subfieldValues2.every(val => subfieldValues1.includes(val))) {\n return true;\n }\n\n if (subfieldValues1.length === 1 && subfieldValues2.length === 1) {\n return pairableValue(field1.tag, subfieldCode, subfieldValues1[0], subfieldValues2[0]) !== undefined;\n }\n\n return false;\n\n }\n}\n\n\nfunction mandatorySubfieldComparison(originalField1, originalField2, keySubfieldsAsString) {\n // NB! We use clones here, since these changes done below are not intented to appear on the actual records.\n const field1 = cloneAndNormalizeFieldForComparison(originalField1);\n const field2 = cloneAndNormalizeFieldForComparison(originalField2);\n if (keySubfieldsAsString === null) { // does not currently happen\n // If keySubfieldsAsString is undefined, (practically) everything is the string.\n // When everything is the string, the strings need to be (practically) identical.\n // (NB! Here order matters. We should probably make it matter everywhere.)\n // (However, keySubfieldsAsString === '' will always succeed. Used by 040 at least.)\n return fieldToString(field1) === fieldToString(field2);\n }\n const subfieldArray = keySubfieldsAsString.split('');\n\n //const differentSubfieldCodes = differentPublisherSubfields(originalField1, originalField2);\n\n return subfieldArray.every(subfieldCode => mandatorySingleSubfieldComparison(subfieldCode));\n\n function mandatorySingleSubfieldComparison(subfieldCode) {\n //const otherSubfieldCode = getOtherSubfieldCode(subfieldCode);\n const subfieldValues1 = field1.subfields.filter(subfield => subfield.code === subfieldCode).map(sf => sf.value);\n const subfieldValues2 = field2.subfields.filter(subfield => subfield.code === subfieldCode).map(sf => sf.value);\n // Assume that at least 1 instance must exist and that all instances must match\n if (subfieldValues1.length !== subfieldValues2.length) {\n debugDev(`mSC: Unique key: subfield ${subfieldCode} issues...`);\n return false;\n }\n\n return subfieldValues1.every(value => subfieldValues2.includes(value));\n }\n\n}\n\nfunction tagToRegexp(tag, internalMerge = false) {\n if (internalMerge && tag in counterpartRegexpsSingle) {\n return counterpartRegexpsSingle[tag];\n }\n if (!internalMerge && tag in counterpartRegexps) { // eg. 700 looks for tag /^[17]00$/...\n const regexp = counterpartRegexps[tag];\n //nvdebug(`regexp for ${tag} found: ${regexp}`, debugDev);\n return regexp;\n }\n //nvdebug(`WARNING: tagToRegexp(${tag}): no precompiled regexp found.`, debugDev);\n return new RegExp(`^${tag}$`, 'u');\n}\n\nfunction areRequiredSubfieldsPresent(field) {\n const subfieldString = getMergeConstraintsForTag(field.tag, 'required');\n if (subfieldString === null) {\n return true;\n } // nothing is required\n const subfieldArray = subfieldString.split('');\n return subfieldArray.every(sfcode => {\n const result = fieldHasSubfield(field, sfcode);\n if (!result) {\n debugDev(`Required subfield \u2021${sfcode} not found in '${fieldToString(field)}'!`);\n return false;\n }\n return true;\n });\n}\n\nfunction arePairedSubfieldsInBalance(field1, field2) {\n const subfieldString = getMergeConstraintsForTag(field1.tag, 'paired');\n if (subfieldString === null) {\n return true;\n }\n const subfieldArray = subfieldString.split('');\n\n return subfieldArray.every(sfcode => fieldHasNSubfields(field1, sfcode) === fieldHasNSubfields(field2, sfcode));\n}\n\nfunction syntacticallyMergablePair(baseField, sourceField, config) {\n // Indicators must typically be equal (there are exceptions such as non-filing characters though):\n if (!mergableIndicator1(baseField, sourceField, config)) {\n nvdebug(`non-mergable (reason: indicator1): ${JSON.stringify(config)}`, debugDev);\n return false;\n }\n\n if (!mergableIndicator2(baseField, sourceField, config)) {\n nvdebug(`non-mergable (reason: indicator2): ${JSON.stringify(config)}`, debugDev);\n return false;\n }\n\n if (!controlSubfieldsPermitMerge(baseField, sourceField)) {\n nvdebug('non-mergable (reason: control subfield)', debugDev);\n return false;\n }\n\n if (!provenanceSubfieldsPermitMerge(baseField, sourceField)) {\n nvdebug('non-mergable (reason: data provenance subfield)', debugDev);\n return false;\n }\n\n // NB! field1.tag and field2.tag might differ (1XX vs 7XX). Therefore required subfields might theoretically differ as well.\n // Note: Theoretically 260 $efg vs 264 with IND2=3 has already been handled by the preprocessor.\n // Thus check both:\n if (!areRequiredSubfieldsPresent(baseField) || !areRequiredSubfieldsPresent(sourceField)) {\n nvdebug('non-mergable (reason: missing subfields)', debugDev);\n return false;\n }\n\n // Stuff of Hacks! Eg. require that both fields either have or have not X00$t:\n if (!arePairedSubfieldsInBalance(baseField, sourceField)) {\n nvdebug('required subfield pair check failed.', debugDev);\n return false;\n }\n\n return true;\n}\n\nfunction mergablePair(baseField, sourceField, config) {\n if (!syntacticallyMergablePair(baseField, sourceField, config)) {\n return false;\n }\n\n //debug('Test semantics...');\n if (!semanticallyMergablePair(baseField, sourceField)) {\n nvdebug('non-mergable (reason: semantics)', debugDev);\n return false;\n }\n\n nvdebug(`MERGABLE PAIR:\\n B: ${fieldToString(baseField)}\\n S: ${fieldToString(sourceField)}`, debugDev);\n return true;\n}\n\n\nfunction pairableAsteriIDs(baseField, sourceField) {\n //nvdebug(`ASTERI1 ${fieldToString(baseField)}`, debugDev); // eslint-disable-line\n //nvdebug(`ASTERI2 ${fieldToString(sourceField)}`, debugDev); // eslint-disable-line\n\n // Check that relevant control subfield(s) exist in both records (as controlSubfieldsPermitMerge() doesn't check it):\n const fin11a = getAsteriIDs(baseField);\n if (fin11a.length === 0) {\n return false;\n }\n const fin11b = getAsteriIDs(sourceField);\n if (fin11b.length === 0) {\n return false;\n }\n //nvdebug(`ASTERI WP3:\\n${fin11a.join(\", \")}\\n${fin11b.join(\", \")}`, debugDev); // eslint-disable-line\n\n // Check that found control subfields agree. Use pre-existing generic function to reduce code.\n // (NB! We could optimize and just return true here, as control subfield check is done elsewhere as well.\n // However, explicitly checking them here makes the code more robust.)\n if (!controlSubfieldsPermitMerge(baseField, sourceField)) {\n return false;\n }\n //console.log(`ASTERI PAIR ${fieldToString(sourceField)}`); // eslint-disable-line\n return true;\n\n // NB! This boldly assumes that the default prefix for Asteri is '(FIN11)', not '(FI-ASTERI-N)' nor a finaf urn...\n function getAsteriIDs(field) {\n return field.subfields.filter(sf => sf.code === '0')\n .map(sf => normalizeControlSubfieldValue(sf.value))\n .filter(val => val.substring(0, 7) === '(FIN11)');\n }\n}\n\n\nfunction hasRepeatableSubfieldThatShouldBeTreatedAsNonRepeatable(field) {\n if (field.tag === '260' || field.tag === '264') {\n return ['a', 'b', 'c', 'e', 'f', 'g'].some(subfieldCode => fieldHasMultipleSubfields(field, subfieldCode));\n }\n if (field.tag === '382') {\n return ['a', 'b', 'd', 'e', 'n', 'p'].some(subfieldCode => fieldHasMultipleSubfields(field, subfieldCode));\n }\n if (field.tag === '505') {\n return ['t', 'r', 'g'].some(subfieldCode => fieldHasMultipleSubfields(field, subfieldCode));\n }\n\n return false;\n}\n\nfunction pairableName(baseField, sourceField) {\n // 100$a$t: remove $t and everything after that\n const reducedField1 = fieldToNamePart(baseField);\n const reducedField2 = fieldToNamePart(sourceField);\n\n const string1 = fieldToString(reducedField1);\n const string2 = fieldToString(reducedField2);\n\n //nvdebug(`IN: pairableName():\\n '${string1}' vs\\n '${string2}'`, debugDev);\n if (string1 === string2) {\n return true;\n }\n\n // Essentially these are too hard to handle with field-merge (eg. multi-505$g)\n if (hasRepeatableSubfieldThatShouldBeTreatedAsNonRepeatable(reducedField1) || hasRepeatableSubfieldThatShouldBeTreatedAsNonRepeatable(reducedField2)) {\n return false;\n }\n\n // Compare the remaining subsets...\n // First check that name matches...\n if (uniqueKeyMatches(reducedField1, reducedField2)) {\n nvdebug(` name match: '${fieldToString(reducedField1)}'`, debugDev);\n return true;\n }\n\n // However, name mismatch is not critical! If Asteri ID matches, it's still a match! *NOT* sure whether this a good idea.\n // 2023-01-24 Disable this. Caretaker can fix these later on. Not a job for merge. We can't be sure that $0 pair is corrent, nor which version (base or source) to use.\n // 2023-03-07: Enable this again!\n if (pairableAsteriIDs(baseField, sourceField)) {\n //nvdebug(` name match based on ASTERI $0'`, debugDev);\n return true;\n }\n\n nvdebug(` name mismatch:`, debugDev);\n nvdebug(` '${fieldToString(reducedField1)}' vs`, debugDev);\n nvdebug(` '${fieldToString(reducedField2)}'`, debugDev);\n return false;\n}\n\n\nfunction semanticallyMergablePair(baseField, sourceField) {\n // On rare occasions a field contains also a title part. For these name part (= normally everything) and title part\n // must be checked separately:\n if (!titlePartsMatch(baseField, sourceField)) {\n nvdebug(` ${baseField.tag} is unmergable: Title part mismatch.`, debugDev);\n return false;\n }\n\n // Hmm... we should check lifespan here, $d YYYY\n\n // Handle the field specific \"unique key\" (=set of fields that make the field unique\n if (!pairableName(baseField, sourceField)) {\n nvdebug('Unmergable: Name part mismatch', debugDev);\n return false;\n }\n //debug(' Semantic checks passed! We are MERGABLE!');\n\n return true;\n}\n\n\nfunction namePartThreshold(field) {\n // Threshold is only applicaple to some tags..\n if (!(/[10]0$/u).test(field.tag)) {\n return -1;\n }\n const t = field.subfields.findIndex(currSubfield => currSubfield.code === 't');\n const u = t; // field.subfields.findIndex(currSubfield => currSubfield.code === 'u');\n if (t === -1) {\n return u;\n }\n if (u === -1) {\n return t;\n }\n return t > u ? u : t;\n}\n\nfunction fieldToNamePart(field) {\n const index = namePartThreshold(field);\n const relevantSubfields = field.subfields.filter((sf, i) => i < index || index === -1).filter(sf => !irrelevantSubfieldsInNameAndTitlePartComparison.includes(sf.code));\n\n const subsetField = {'tag': field.tag, 'ind1': field.ind1, 'ind2': field.ind2, subfields: relevantSubfields};\n\n /*\n if (index > -1) {\n debugDev(`Name subset: ${fieldToString(subsetField)}`);\n }\n */\n\n // Ummm... Sometimes $0 comes after $t but belongs to name part\n\n return subsetField;\n}\n\nfunction fieldToTitlePart(field) {\n // Take everything after 1st subfield $t...\n const index = field.subfields.findIndex(currSubfield => currSubfield.code === 't');\n const relevantSubfields = field.subfields.filter((sf, i) => i >= index).filter(sf => !irrelevantSubfieldsInNameAndTitlePartComparison.includes(sf.code));\n const subsetField = {'tag': field.tag, 'ind1': field.ind1, 'ind2': field.ind2, subfields: relevantSubfields};\n debugDev(`Title subset: ${fieldToString(subsetField)}`);\n return subsetField;\n}\n\nfunction containsTitlePart(field) {\n return fieldCanHaveTitlePart(field) && fieldHasSubfield(field, 't');\n\n function fieldCanHaveTitlePart(field) {\n return ['100', '110', '111', '700', '710', '711'].includes(field.tag);\n }\n}\n\nfunction titlePartsMatch(field1, field2) {\n if (!containsTitlePart(field1)) {\n return !containsTitlePart(field2);\n }\n if (!containsTitlePart(field2)) {\n return false;\n }\n\n debugDev(`TITLE PARTS NEED TO BE COMPARED`);\n\n // 100$a$t: remove $t and everything after that\n const subset1 = fieldToTitlePart(field1);\n const subset2 = fieldToTitlePart(field2);\n // Easter Egg, ffs. Hardcoded exception\n return mandatorySubfieldComparison(subset1, subset2, 'dfhklmnoprstxvg');\n}\n\n\nfunction getAlternativeNamesFrom9XX(record, field) {\n // Should we support 6XX and 8XX as well? Prolly not...\n if (!field.tag.match(/^(?:100|110|111|600|610|611|700|710|711)$/u)) {\n return [];\n }\n const tag = `9${field.tag.substring(1)}`;\n const cands = record.get(tag).filter(f => fieldHasSubfield(f, 'a') && fieldHasSubfield(f, 'y'));\n if (cands.length === 0) {\n return [];\n }\n const punctuationlessField = cloneAndRemovePunctuation(field);\n const [name] = punctuationlessField.subfields.filter(sf => sf.code === 'a').map(sf => sf.value);\n\n return cands.map(candField => getAltName(candField)).filter(val => val !== undefined);\n\n\n function getAltName(altField) {\n const [altA] = altField.subfields.filter(sf => sf.code === 'a').map(sf => sf.value);\n const [altY] = altField.subfields.filter(sf => sf.code === 'y').map(sf => sf.value);\n nvdebug(`Compare '${name}' vs '${altA}'/'${altY}'`, debugDev);\n if (name === altA) {\n return altY;\n }\n if (name === altY) {\n return altA;\n }\n nvdebug(` miss`, debugDev);\n return undefined;\n }\n\n}\n\n\nfunction mergablePairWithAltName(normCandField, normalizedField, altName, config) {\n // Replace source field $a name with alternative name and then compare:\n const [a] = normalizedField.subfields.filter(sf => sf.code === 'a');\n if (!a) {\n return false;\n }\n a.value = altName;\n\n return mergablePair(normCandField, normalizedField, config);\n}\n\nfunction getCounterpartIndex(field, counterpartCands, altNames, config) {\n const normalizedField = cloneAndNormalizeFieldForComparison(field);\n const normalizedCounterpartCands = counterpartCands.map(f => cloneAndNormalizeFieldForComparison(f));\n const index = normalizedCounterpartCands.findIndex(normCandField => mergablePair(normCandField, normalizedField, config));\n if (index > -1) {\n return index;\n }\n\n return normalizedCounterpartCands.findIndex(normCandField => altNames.some(altName => mergablePairWithAltName(normCandField, normalizedField, altName, config)));\n}\n\n\nfunction field264Exception(baseField, sourceRecord, sourceField, config) {\n nvdebug('Field 264 exception as per MET-456', debugDev);\n if (baseField.tag !== '264') {\n return false;\n }\n\n if (sourceField.tag !== '264' || sourceRecord.get('264').length !== 1) {\n return false;\n }\n\n // Don't worry about semantics:\n return syntacticallyMergablePair(sourceField, baseField, config);\n}\n\nfunction getCounterpartCandidates(field, record) {\n const counterpartCands = record.get(tagToRegexp(field.tag, record.internalMerge));\n\n // MELKEHITYS-2969: copyright years should not merge with non-copyright years\n if (field.tag === '260' && isNotCopyrightYear(field)) {\n return counterpartCands.filter(candField => !isCopyrightField264(candField));\n }\n\n if (field.tag === '264' && isCopyrightField264(field)) { // Copyright year\n return counterpartCands.filter(candField => !isNotCopyrightYear(candField));\n }\n\n function isCopyrightField264(field) {\n return field.tag === '264' && field.ind2 === '4';\n }\n function isNotCopyrightYear(field) {\n if (field.tag === '264') {\n return !isCopyrightField264(field);\n }\n // Field 260: copyright year does not contain $a or $b:\n return !field.subfields.some(sf => sf.code === 'a' && sf.code === 'b');\n }\n\n return counterpartCands;\n\n}\n\nexport function baseIsSource(base, source) {\n base.localTest = true;\n const result = source.localTest;\n delete base.localTest;\n return result;\n}\n\nexport function getCounterpart(baseRecord, sourceRecord, field, config) {\n // First get relevant candidate fields. Note that 1XX and corresponding 7XX are considered equal, and tags 260 and 264 are lumped together.\n // (<= Note that self-merge behaves differently from two records here.)\n // Hacks: 973 can merge with 773, 940 can merge with 240 (but not the other way around)\n //nvdebug(`COUNTERPART FOR '${fieldToString(field)}'?`, debugDev);\n const counterpartCands = getCounterpartCandidates(field, baseRecord).filter(f => !f.mergeCandidate);\n\n if (!counterpartCands || counterpartCands.length === 0) {\n //nvdebug(`No counterpart(s) found for ${fieldToString(field)}`, debugDev);\n return null;\n }\n\n nvdebug(`Compare incoming '${fieldToString(field)}' with (up to) ${counterpartCands.length} existing field(s)`, debugDev);\n\n const normalizedField = cloneAndNormalizeFieldForComparison(field); // mainly strip punctuation here\n\n nvdebug(`Norm to: '${fieldToString(normalizedField)}'`, debugDev);\n\n const uniqueAlternativeNames = getUniqueAlernativeNames();\n\n function getUniqueAlernativeNames() {\n if (baseIsSource(baseRecord, sourceRecord)) {\n return [];\n }\n // Try to look for alternative names from base and source record's 9XX fields:\n const alternativeNames = getAlternativeNamesFrom9XX(baseRecord, field).concat(getAlternativeNamesFrom9XX(sourceRecord, field));\n return alternativeNames.filter((name, i) => alternativeNames.indexOf(name) === i);\n }\n\n //nvdebug(` S: ${fieldToString(normalizedField)}`, debugDev);\n // Then find (the index of) the first mathing candidate field and return it.\n const index = getCounterpartIndex(normalizedField, counterpartCands, uniqueAlternativeNames, config);\n\n if (index > -1) {\n return counterpartCands[index];\n }\n\n // MET-456 exception\n if (counterpartCands.length === 1 && field264Exception(counterpartCands[0], sourceRecord, field, config)) {\n return counterpartCands[0];\n }\n\n return null;\n}\n\n"],
5
+ "mappings": "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",
6
6
  "names": ["suffix", "prefix", "name", "field"]
7
7
  }
@@ -0,0 +1,29 @@
1
+ import { subfieldArraysContainSameData } from "../utils.js";
2
+ function tagToDataProvenanceSubfieldCode(tag) {
3
+ if (["533", "800", "810", "811", "830"].includes(tag)) {
4
+ return "y";
5
+ }
6
+ if (tag === "856" || tag === "857") {
7
+ return "e";
8
+ }
9
+ if (tag.match(/^7[678]/u)) {
10
+ return "l";
11
+ }
12
+ if (tag.match(/^00/u)) {
13
+ return void 0;
14
+ }
15
+ return "7";
16
+ }
17
+ export function provenanceSubfieldsPermitMerge(baseField, sourceField) {
18
+ const provinanceSubfieldCode = tagToDataProvenanceSubfieldCode(baseField.tag);
19
+ if (!baseField.subfields) {
20
+ return true;
21
+ }
22
+ if (provinanceSubfieldCode === void 0) {
23
+ return false;
24
+ }
25
+ const baseProvinanceSubfields = baseField.subfields.filter((sf) => sf.code === provinanceSubfieldCode);
26
+ const sourceProvinanceSubfields = sourceField.subfields.filter((sf) => sf.code === provinanceSubfieldCode);
27
+ return subfieldArraysContainSameData(baseProvinanceSubfields, sourceProvinanceSubfields);
28
+ }
29
+ //# sourceMappingURL=dataProvenance.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/merge-fields/dataProvenance.js"],
4
+ "sourcesContent": ["// See https://www.loc.gov/marc/bibliographic/bdapndxj.html for details\n\nimport {subfieldArraysContainSameData} from \"../utils.js\";\n\nfunction tagToDataProvenanceSubfieldCode(tag) {\n if ( ['533', '800', '810', '811', '830'].includes(tag)) {\n return 'y';\n }\n if ( tag === '856' || tag === '857' ) {\n return 'e';\n }\n\n if ( tag.match(/^7[678]/u) ) {\n return 'l'\n }\n\n if ( tag.match(/^00/u)) {\n return undefined;\n }\n return '7';\n}\n\n\nexport function provenanceSubfieldsPermitMerge(baseField, sourceField) {\n const provinanceSubfieldCode = tagToDataProvenanceSubfieldCode(baseField.tag);\n if (!baseField.subfields) {\n return true;\n }\n if (provinanceSubfieldCode === undefined) {\n return false;\n }\n\n const baseProvinanceSubfields = baseField.subfields.filter(sf => sf.code === provinanceSubfieldCode);\n const sourceProvinanceSubfields = sourceField.subfields.filter(sf => sf.code === provinanceSubfieldCode);\n\n // Currently we just compare two arrays. Later on we might do something more sophisticated with specific $7 data provenance category/relationship codes,\n // or actual values.\n\n return subfieldArraysContainSameData(baseProvinanceSubfields, sourceProvinanceSubfields);\n\n}"],
5
+ "mappings": "AAEA,SAAQ,qCAAoC;AAE5C,SAAS,gCAAgC,KAAK;AAC1C,MAAK,CAAC,OAAO,OAAO,OAAO,OAAO,KAAK,EAAE,SAAS,GAAG,GAAG;AACpD,WAAO;AAAA,EACX;AACA,MAAK,QAAQ,SAAS,QAAQ,OAAQ;AAClC,WAAO;AAAA,EACX;AAEA,MAAK,IAAI,MAAM,UAAU,GAAI;AACzB,WAAO;AAAA,EACX;AAEA,MAAK,IAAI,MAAM,MAAM,GAAG;AACpB,WAAO;AAAA,EACX;AACA,SAAO;AACX;AAGO,gBAAS,+BAA+B,WAAW,aAAa;AACnE,QAAM,yBAAyB,gCAAgC,UAAU,GAAG;AAC5E,MAAI,CAAC,UAAU,WAAW;AACtB,WAAO;AAAA,EACX;AACA,MAAI,2BAA2B,QAAW;AACtC,WAAO;AAAA,EACX;AAEA,QAAM,0BAA0B,UAAU,UAAU,OAAO,QAAM,GAAG,SAAS,sBAAsB;AACnG,QAAM,4BAA4B,YAAY,UAAU,OAAO,QAAM,GAAG,SAAS,sBAAsB;AAKvG,SAAO,8BAA8B,yBAAyB,yBAAyB;AAE3F;",
6
+ "names": []
7
+ }
@@ -3,14 +3,23 @@ import { MarcRecord } from "@natlibfi/marc-record";
3
3
  import { postprocessRecords } from "./mergeOrAddPostprocess.js";
4
4
  const description = "Merge fields within record";
5
5
  import { mergeConfig as defaultConfig } from "./mergeConfig.js";
6
- export default function() {
6
+ export default function(defaultTagPattern = void 0) {
7
7
  return {
8
8
  description,
9
9
  validate,
10
10
  fix
11
11
  };
12
+ function getTagPattern(config) {
13
+ if (config && config.tagPattern) {
14
+ return config.tagPattern;
15
+ }
16
+ if (defaultTagPattern) {
17
+ return defaultTagPattern;
18
+ }
19
+ return "^[1678](?:00|10|11|30)$";
20
+ }
12
21
  function mergeFieldsWithinRecord(record, config) {
13
- const fields = config && config.tagPattern ? record.get(config.tagPattern) : record.get(/^[1678](?:00|10|11|30)$/u);
22
+ const fields = record.get(getTagPattern(config));
14
23
  fields.reverse();
15
24
  const mergedField = fields.find((f) => mergeField(record, record, f, config));
16
25
  if (!mergedField) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/merge-fields/index.js"],
4
- "sourcesContent": ["// NB! This validator is build on code that merged two different records originally in marc-record-merge-reducers.\n// The idea was to write a validator that can merge fields in one record. (This is a good idea at least for field tags /^[1678](00|10|11|30)$/.)\n// As we don't want copypaste coding everything was moved here, and marc-record-merge-reducers exports mergeField() function.\n// That function uses a lot of stuff that is meant for the two-record-merge case only. Thus the tests for this validator are pretty limited here,\n// and the test coverage is low. The extensive set of tests in in marc-record-merge-reducers for this code.\n\n\n//import createDebugLogger from 'debug';\n//import fs from 'fs';\n//import path from 'path';\nimport {mergeField} from './mergeField.js';\nimport {MarcRecord} from '@natlibfi/marc-record';\nimport {postprocessRecords} from './mergeOrAddPostprocess.js';\n\nconst description = 'Merge fields within record';\n\nimport {mergeConfig as defaultConfig} from './mergeConfig.js';\n\n//const defaultConfig = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'src', 'merge-fields', 'config.json'), 'utf8'));\n\nexport default function () {\n\n return {\n description, validate, fix\n };\n\n\n function mergeFieldsWithinRecord(record, config) {\n //const candFields = record.fields.toReversed(); // Node 20+ only! Filter via config?\n const fields = config && config.tagPattern ? record.get(config.tagPattern) : record.get(/^[1678](?:00|10|11|30)$/u);\n\n fields.reverse();\n const mergedField = fields.find(f => mergeField(record, record, f, config));\n if (!mergedField) {\n return;\n }\n record.removeField(mergedField);\n mergeFieldsWithinRecord(record, config);\n\n }\n\n function fix(record, config = undefined) {\n const config2 = config || defaultConfig;\n record.internalMerge = true;\n mergeFieldsWithinRecord(record, config2);\n delete record.internalMerge;\n // Remove deleted fields and field.merged marks:\n postprocessRecords(record, record);\n\n const res = {message: [], fix: [], valid: true};\n return res;\n }\n\n function validate(record, config) {\n //nvdebug(`VALIDATE ${description}...`);\n\n const nFields = record.fields.length;\n const clonedRecord = new MarcRecord(record, {subfieldValues: false});\n fix(clonedRecord, config);\n\n const nFields2 = clonedRecord.fields.length;\n if (nFields === nFields2) {\n return {message: [], valid: true};\n }\n\n const msg = `${description}: number of fields changes from ${nFields} to ${nFields2}`;\n return {message: [msg], valid: false};\n }\n\n}\n\n"],
5
- "mappings": "AAUA,SAAQ,kBAAiB;AACzB,SAAQ,kBAAiB;AACzB,SAAQ,0BAAyB;AAEjC,MAAM,cAAc;AAEpB,SAAQ,eAAe,qBAAoB;AAI3C,0BAA2B;AAEzB,SAAO;AAAA,IACL;AAAA,IAAa;AAAA,IAAU;AAAA,EACzB;AAGA,WAAS,wBAAwB,QAAQ,QAAQ;AAE/C,UAAM,SAAS,UAAU,OAAO,aAAa,OAAO,IAAI,OAAO,UAAU,IAAI,OAAO,IAAI,0BAA0B;AAElH,WAAO,QAAQ;AACf,UAAM,cAAc,OAAO,KAAK,OAAK,WAAW,QAAQ,QAAQ,GAAG,MAAM,CAAC;AAC1E,QAAI,CAAC,aAAa;AAChB;AAAA,IACF;AACA,WAAO,YAAY,WAAW;AAC9B,4BAAwB,QAAQ,MAAM;AAAA,EAExC;AAEA,WAAS,IAAI,QAAQ,SAAS,QAAW;AACvC,UAAM,UAAU,UAAU;AAC1B,WAAO,gBAAgB;AACvB,4BAAwB,QAAQ,OAAO;AACvC,WAAO,OAAO;AAEd,uBAAmB,QAAQ,MAAM;AAEjC,UAAM,MAAM,EAAC,SAAS,CAAC,GAAG,KAAK,CAAC,GAAG,OAAO,KAAI;AAC9C,WAAO;AAAA,EACT;AAEA,WAAS,SAAS,QAAQ,QAAQ;AAGhC,UAAM,UAAU,OAAO,OAAO;AAC9B,UAAM,eAAe,IAAI,WAAW,QAAQ,EAAC,gBAAgB,MAAK,CAAC;AACnE,QAAI,cAAc,MAAM;AAExB,UAAM,WAAW,aAAa,OAAO;AACrC,QAAI,YAAY,UAAU;AACxB,aAAO,EAAC,SAAS,CAAC,GAAG,OAAO,KAAI;AAAA,IAClC;AAEA,UAAM,MAAM,GAAG,WAAW,mCAAmC,OAAO,OAAO,QAAQ;AACnF,WAAO,EAAC,SAAS,CAAC,GAAG,GAAG,OAAO,MAAK;AAAA,EACtC;AAEF;",
4
+ "sourcesContent": ["// NB! This validator is build on code that merged two different records originally in marc-record-merge-reducers.\n// The idea was to write a validator that can merge fields in one record. (This is a good idea at least for field tags /^[1678](00|10|11|30)$/.)\n// As we don't want copypaste coding everything was moved here, and marc-record-merge-reducers exports mergeField() function.\n// That function uses a lot of stuff that is meant for the two-record-merge case only. Thus the tests for this validator are pretty limited here,\n// and the test coverage is low. The extensive set of tests in in marc-record-merge-reducers for this code.\n\n\n//import createDebugLogger from 'debug';\n//import fs from 'fs';\n//import path from 'path';\nimport {mergeField} from './mergeField.js';\nimport {MarcRecord} from '@natlibfi/marc-record';\nimport {postprocessRecords} from './mergeOrAddPostprocess.js';\n\nconst description = 'Merge fields within record';\n\nimport {mergeConfig as defaultConfig} from './mergeConfig.js';\n\n//const defaultConfig = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'src', 'merge-fields', 'config.json'), 'utf8'));\n\nexport default function (defaultTagPattern = undefined) {\n\n return {\n description, validate, fix\n };\n\n function getTagPattern(config) {\n if (config && config.tagPattern) {\n return config.tagPattern;\n }\n if (defaultTagPattern) { // Used by tests\n return defaultTagPattern;\n }\n return '^[1678](?:00|10|11|30)$';\n }\n\n function mergeFieldsWithinRecord(record, config) {\n //const candFields = record.fields.toReversed(); // Node 20+ only! Filter via config?\n const fields = record.get(getTagPattern(config)); // config && config.tagPattern ? record.get(config.tagPattern) : record.get(/^[1678](?:00|10|11|30)$/u);\n\n fields.reverse();\n const mergedField = fields.find(f => mergeField(record, record, f, config));\n if (!mergedField) {\n return;\n }\n record.removeField(mergedField);\n mergeFieldsWithinRecord(record, config);\n\n }\n\n function fix(record, config = undefined) {\n const config2 = config || defaultConfig;\n record.internalMerge = true;\n mergeFieldsWithinRecord(record, config2);\n delete record.internalMerge;\n // Remove deleted fields and field.merged marks:\n postprocessRecords(record, record);\n\n const res = {message: [], fix: [], valid: true};\n return res;\n }\n\n function validate(record, config) {\n //nvdebug(`VALIDATE ${description}...`);\n\n const nFields = record.fields.length;\n const clonedRecord = new MarcRecord(record, {subfieldValues: false});\n fix(clonedRecord, config);\n\n const nFields2 = clonedRecord.fields.length;\n if (nFields === nFields2) {\n return {message: [], valid: true};\n }\n\n const msg = `${description}: number of fields changes from ${nFields} to ${nFields2}`;\n return {message: [msg], valid: false};\n }\n\n}\n\n"],
5
+ "mappings": "AAUA,SAAQ,kBAAiB;AACzB,SAAQ,kBAAiB;AACzB,SAAQ,0BAAyB;AAEjC,MAAM,cAAc;AAEpB,SAAQ,eAAe,qBAAoB;AAI3C,wBAAyB,oBAAoB,QAAW;AAEtD,SAAO;AAAA,IACL;AAAA,IAAa;AAAA,IAAU;AAAA,EACzB;AAEA,WAAS,cAAc,QAAQ;AAC7B,QAAI,UAAU,OAAO,YAAY;AAC/B,aAAO,OAAO;AAAA,IAChB;AACA,QAAI,mBAAmB;AACrB,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAEA,WAAS,wBAAwB,QAAQ,QAAQ;AAE/C,UAAM,SAAS,OAAO,IAAI,cAAc,MAAM,CAAC;AAE/C,WAAO,QAAQ;AACf,UAAM,cAAc,OAAO,KAAK,OAAK,WAAW,QAAQ,QAAQ,GAAG,MAAM,CAAC;AAC1E,QAAI,CAAC,aAAa;AAChB;AAAA,IACF;AACA,WAAO,YAAY,WAAW;AAC9B,4BAAwB,QAAQ,MAAM;AAAA,EAExC;AAEA,WAAS,IAAI,QAAQ,SAAS,QAAW;AACvC,UAAM,UAAU,UAAU;AAC1B,WAAO,gBAAgB;AACvB,4BAAwB,QAAQ,OAAO;AACvC,WAAO,OAAO;AAEd,uBAAmB,QAAQ,MAAM;AAEjC,UAAM,MAAM,EAAC,SAAS,CAAC,GAAG,KAAK,CAAC,GAAG,OAAO,KAAI;AAC9C,WAAO;AAAA,EACT;AAEA,WAAS,SAAS,QAAQ,QAAQ;AAGhC,UAAM,UAAU,OAAO,OAAO;AAC9B,UAAM,eAAe,IAAI,WAAW,QAAQ,EAAC,gBAAgB,MAAK,CAAC;AACnE,QAAI,cAAc,MAAM;AAExB,UAAM,WAAW,aAAa,OAAO;AACrC,QAAI,YAAY,UAAU;AACxB,aAAO,EAAC,SAAS,CAAC,GAAG,OAAO,KAAI;AAAA,IAClC;AAEA,UAAM,MAAM,GAAG,WAAW,mCAAmC,OAAO,OAAO,QAAQ;AACnF,WAAO,EAAC,SAAS,CAAC,GAAG,GAAG,OAAO,MAAK;AAAA,EACtC;AAEF;",
6
6
  "names": []
7
7
  }
@@ -56,7 +56,7 @@ function skipMergeField(baseRecord, sourceField, config) {
56
56
  nvdebug(`skipMergeField(): field '${fieldToString(sourceField)}' listed as skippable!`, debugDev);
57
57
  return true;
58
58
  }
59
- if (baseRecord.fields.some((baseField) => !baseField.mergeCandidate && fieldsAreIdentical(sourceField, baseField))) {
59
+ if (!baseRecord.internalMerge && baseRecord.fields.some((baseField) => !baseField.mergeCandidate && fieldsAreIdentical(sourceField, baseField))) {
60
60
  nvdebug(`skipMergeField(): field '${fieldToString(sourceField)}' already exists! No merge required!`, debugDev);
61
61
  sourceField.deleted = 1;
62
62
  return true;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/merge-fields/mergeField.js"],
4
- "sourcesContent": ["//import {MarcRecord} from '@natlibfi/marc-record';\nimport createDebugLogger from 'debug';\nimport {fieldToString, fieldsToString, fieldsAreIdentical, nvdebug, hasCopyright, removeCopyright, subfieldToString} from '../utils.js';\nimport {fieldGetOccurrenceNumberPairs} from '../subfield6Utils.js';\nimport {cloneAndNormalizeFieldForComparison, cloneAndRemovePunctuation, isEnnakkotietoSubfieldG} from '../normalizeFieldForComparison.js';\nimport {mergeOrAddSubfield} from './mergeOrAddSubfield.js';\nimport {mergeIndicators} from './mergeIndicator.js';\nimport {mergableTag} from './mergableTag.js';\nimport {getCounterpart} from './counterpartField.js';\n//import {default as normalizeEncoding} from '@natlibfi/marc-record-validators-melinda/dist/normalize-utf8-diacritics';\n//import {postprocessRecords} from './mergeOrAddPostprocess.js';\n//import {preprocessBeforeAdd} from './processFilter.js';\n\n//import fs from 'fs';\n//import path from 'path';\n\n\n//const defaultConfig = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'src', 'reducers', 'config.json'), 'utf8'));\n\n// Specs: https://workgroups.helsinki.fi/x/K1ohCw (though we occasionally differ from them)...\n\nconst debug = createDebugLogger('@natlibfi/melinda-marc-record-merge-reducers:mergeField');\n//const debugData = debug.extend('data');\nconst debugDev = debug.extend('dev');\n\n// NB! Can we do this via config.json?\nfunction removeEnnakkotieto(field) {\n const tmp = field.subfields.filter(subfield => !isEnnakkotietoSubfieldG(subfield));\n // remove only iff some other subfield remains\n if (tmp.length > 0) {\n field.subfields = tmp;\n }\n}\n\n\nfunction copyrightYearHack(baseRecord, baseField, sourceField) {\n if (baseField.tag !== '264' || sourceField.tag !== '260') {\n return;\n }\n const relevantSubfields = sourceField.subfields.filter(sf => sf.code === 'c' && hasCopyright(sf.value));\n\n relevantSubfields.forEach(sf => {\n // Add new:\n const value = sf.value.replace(/\\.$/u, '');\n baseRecord.insertField({'tag': '264', 'ind1': ' ', 'ind2': '4', 'subfields': [{'code': 'c', value}]});\n // Modify original subfield:\n sf.value = removeCopyright(sf.value);\n });\n}\n\nfunction mergeField2(baseRecord, baseField, sourceField, config, candFieldPairs880 = []) {\n //// Identical fields\n // No need to check every subfield separately.\n // Also no need to postprocess the resulting field.\n if (fieldToString(baseField) === fieldToString(sourceField)) {\n return baseRecord;\n }\n\n // If a base ennakkotieto is merged with real data, remove ennakkotieto subfield:\n // (If our prepub normalizations are ok, this should not be needed.\n // However, it's simple and works well enough, so let's keep it here.)\n if (baseField.subfields?.find(sf => isEnnakkotietoSubfieldG(sf)) && !sourceField.subfields?.find(sf => isEnnakkotietoSubfieldG(sf))) {\n removeEnnakkotieto(baseField);\n baseField.merged = 1;\n }\n\n copyrightYearHack(baseRecord, baseField, sourceField);\n\n mergeIndicators(baseField, sourceField, config);\n\n\n // We want to add the incoming subfields without punctuation, and add puctuation later on.\n // (Cloning is harmless, but probably not needed.)\n // NEW: we also drag the normalized version along. It is needed for the merge-or-add decision\n const normalizedSourceField = cloneAndNormalizeFieldForComparison(sourceField); // This is for comparison\n const strippedSourceField = cloneAndRemovePunctuation(sourceField); // This is for adding subfields\n\n //nvdebug(` MERGING SUBFIELDS OF '${fieldToString(sourceField)}' (original)`, debugDev);\n //nvdebug(` MERGING SUBFIELDS OF '${fieldToString(normalizedSourceField)}' (comparison)`, debugDev);\n nvdebug(` MERGING SUBFIELDS OF '${fieldToString(strippedSourceField)}' (merge/add)`, debugDev);\n\n sourceField.subfields.forEach((originalSubfield, index) => {\n //strippedSourceField.subfields.forEach((subfieldForMergeOrAdd, index) => {\n const normalizedSubfield = normalizedSourceField.subfields[index];\n const punctlessSubfield = strippedSourceField.subfields[index];\n const originalBaseValue = fieldToString(baseField);\n nvdebug(` TRYING TO MERGE SUBFIELD '${subfieldToString(originalSubfield)}' TO '${originalBaseValue}'`, debugDev);\n\n const subfieldData = {'tag': sourceField.tag, 'code': originalSubfield.code, 'originalValue': originalSubfield.value, 'normalizedValue': normalizedSubfield.value, 'punctuationlessValue': punctlessSubfield.value};\n\n mergeOrAddSubfield(baseField, subfieldData, candFieldPairs880); // candSubfield);\n const newValue = fieldToString(baseField);\n if (originalBaseValue !== newValue) {\n nvdebug(` SUBFIELD MERGE RESULT: '${newValue}'`, debugDev);\n //debug(` TODO: sort subfields, handle punctuation...`);\n }\n //else { debugDev(` mergeOrAddSubfield() did not add '\u2021${fieldToString(subfieldForMergeOrAdd)}' to '${originalValue}'`); }\n\n });\n}\n\n\nfunction skipMergeField(baseRecord, sourceField, config) {\n if (!mergableTag(sourceField.tag, config)) {\n nvdebug(`skipMergeField(): field '${fieldToString(sourceField)}' listed as skippable!`, debugDev);\n return true;\n }\n\n // Skip duplicate field:\n if (baseRecord.fields.some(baseField => !baseField.mergeCandidate && fieldsAreIdentical(sourceField, baseField))) {\n nvdebug(`skipMergeField(): field '${fieldToString(sourceField)}' already exists! No merge required!`, debugDev);\n sourceField.deleted = 1;\n return true;\n }\n\n return false;\n}\n\nfunction sourceRecordIsBetter(baseField, sourceField) {\n if (!baseField.subfields) {\n return;\n }\n // MELINDA-8978: prefer Asteri version\n if (isAsteriField(sourceField) && !isAsteriField(baseField)) {\n return 1;\n }\n\n function isAsteriField(field) {\n if (field.subfields.some(sf => sf.code === '0' && sf.value.match(/^\\((?:FI-ASTERI-[NW]|FIN1[13])\\)[0-9]{9}$/u))) {\n return true;\n }\n }\n return false;\n}\n\nfunction swapDataBetweenFields(field1, field2) {\n // NB! Does not support controlfields yet! Add support if the need arises.\n if (field1.subfields) { // If field1 has subfields, then also field2 has them. No need to check the other field here.\n swapNamedData('ind1');\n swapNamedData('ind2');\n swapNamedData('subfields');\n return;\n }\n return;\n\n function swapNamedData(name) {\n const data = field1[name];\n field1[name] = field2[name];\n field2[name] = data;\n }\n\n}\n\nexport function mergeField(baseRecord, sourceRecord, sourceField, config) {\n nvdebug(`SELF: ${fieldToString(sourceField)}`, debugDev);\n\n sourceField.mergeCandidate = true;\n // skip duplicates and special cases:\n if (skipMergeField(baseRecord, sourceField, config)) {\n nvdebug(`mergeField(): don't merge '${fieldToString(sourceField)}'`, debugDev);\n delete sourceField.mergeCandidate;\n return false;\n }\n\n nvdebug(`mergeField(): Try to merge '${fieldToString(sourceField)}'.`, debugDev);\n const counterpartField = getCounterpart(baseRecord, sourceRecord, sourceField, config);\n\n if (counterpartField) {\n if (sourceRecordIsBetter(counterpartField, sourceField)) {\n swapDataBetweenFields(counterpartField, sourceField);\n }\n\n const candFieldPairs880 = sourceField.tag === '880' ? undefined : fieldGetOccurrenceNumberPairs(sourceField, sourceRecord.fields);\n nvdebug(`mergeField(): Got counterpart: '${fieldToString(counterpartField)}'. Thus try merge...`, debugDev);\n nvdebug(`PAIR: ${candFieldPairs880 ? fieldsToString(candFieldPairs880) : 'NADA'}`, debugDev);\n mergeField2(baseRecord, counterpartField, sourceField, config, candFieldPairs880);\n sourceField.deleted = 1;\n delete sourceField.mergeCandidate;\n return true;\n }\n // NB! Counterpartless field is inserted to 7XX even if field.tag says 1XX:\n debugDev(`mergeField(): No mergable counterpart found for '${fieldToString(sourceField)}'.`);\n delete sourceField.mergeCandidate;\n return false;\n}\n\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["//import {MarcRecord} from '@natlibfi/marc-record';\nimport createDebugLogger from 'debug';\nimport {fieldToString, fieldsToString, fieldsAreIdentical, nvdebug, hasCopyright, removeCopyright, subfieldToString} from '../utils.js';\nimport {fieldGetOccurrenceNumberPairs} from '../subfield6Utils.js';\nimport {cloneAndNormalizeFieldForComparison, cloneAndRemovePunctuation, isEnnakkotietoSubfieldG} from '../normalizeFieldForComparison.js';\nimport {mergeOrAddSubfield} from './mergeOrAddSubfield.js';\nimport {mergeIndicators} from './mergeIndicator.js';\nimport {mergableTag} from './mergableTag.js';\nimport {getCounterpart} from './counterpartField.js';\n//import {default as normalizeEncoding} from '@natlibfi/marc-record-validators-melinda/dist/normalize-utf8-diacritics';\n//import {postprocessRecords} from './mergeOrAddPostprocess.js';\n//import {preprocessBeforeAdd} from './processFilter.js';\n\n//import fs from 'fs';\n//import path from 'path';\n\n\n//const defaultConfig = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'src', 'reducers', 'config.json'), 'utf8'));\n\n// Specs: https://workgroups.helsinki.fi/x/K1ohCw (though we occasionally differ from them)...\n\nconst debug = createDebugLogger('@natlibfi/melinda-marc-record-merge-reducers:mergeField');\n//const debugData = debug.extend('data');\nconst debugDev = debug.extend('dev');\n\n// NB! Can we do this via config.json?\nfunction removeEnnakkotieto(field) {\n const tmp = field.subfields.filter(subfield => !isEnnakkotietoSubfieldG(subfield));\n // remove only iff some other subfield remains\n if (tmp.length > 0) {\n field.subfields = tmp;\n }\n}\n\n\nfunction copyrightYearHack(baseRecord, baseField, sourceField) {\n if (baseField.tag !== '264' || sourceField.tag !== '260') {\n return;\n }\n const relevantSubfields = sourceField.subfields.filter(sf => sf.code === 'c' && hasCopyright(sf.value));\n\n relevantSubfields.forEach(sf => {\n // Add new:\n const value = sf.value.replace(/\\.$/u, '');\n baseRecord.insertField({'tag': '264', 'ind1': ' ', 'ind2': '4', 'subfields': [{'code': 'c', value}]});\n // Modify original subfield:\n sf.value = removeCopyright(sf.value);\n });\n}\n\nfunction mergeField2(baseRecord, baseField, sourceField, config, candFieldPairs880 = []) {\n //// Identical fields\n // No need to check every subfield separately.\n // Also no need to postprocess the resulting field.\n if (fieldToString(baseField) === fieldToString(sourceField)) {\n return baseRecord;\n }\n\n // If a base ennakkotieto is merged with real data, remove ennakkotieto subfield:\n // (If our prepub normalizations are ok, this should not be needed.\n // However, it's simple and works well enough, so let's keep it here.)\n if (baseField.subfields?.find(sf => isEnnakkotietoSubfieldG(sf)) && !sourceField.subfields?.find(sf => isEnnakkotietoSubfieldG(sf))) {\n removeEnnakkotieto(baseField);\n baseField.merged = 1;\n }\n\n copyrightYearHack(baseRecord, baseField, sourceField);\n\n mergeIndicators(baseField, sourceField, config);\n\n\n // We want to add the incoming subfields without punctuation, and add puctuation later on.\n // (Cloning is harmless, but probably not needed.)\n // NEW: we also drag the normalized version along. It is needed for the merge-or-add decision\n const normalizedSourceField = cloneAndNormalizeFieldForComparison(sourceField); // This is for comparison\n const strippedSourceField = cloneAndRemovePunctuation(sourceField); // This is for adding subfields\n\n //nvdebug(` MERGING SUBFIELDS OF '${fieldToString(sourceField)}' (original)`, debugDev);\n //nvdebug(` MERGING SUBFIELDS OF '${fieldToString(normalizedSourceField)}' (comparison)`, debugDev);\n nvdebug(` MERGING SUBFIELDS OF '${fieldToString(strippedSourceField)}' (merge/add)`, debugDev);\n\n sourceField.subfields.forEach((originalSubfield, index) => {\n //strippedSourceField.subfields.forEach((subfieldForMergeOrAdd, index) => {\n const normalizedSubfield = normalizedSourceField.subfields[index];\n const punctlessSubfield = strippedSourceField.subfields[index];\n const originalBaseValue = fieldToString(baseField);\n nvdebug(` TRYING TO MERGE SUBFIELD '${subfieldToString(originalSubfield)}' TO '${originalBaseValue}'`, debugDev);\n\n const subfieldData = {'tag': sourceField.tag, 'code': originalSubfield.code, 'originalValue': originalSubfield.value, 'normalizedValue': normalizedSubfield.value, 'punctuationlessValue': punctlessSubfield.value};\n\n mergeOrAddSubfield(baseField, subfieldData, candFieldPairs880); // candSubfield);\n const newValue = fieldToString(baseField);\n if (originalBaseValue !== newValue) {\n nvdebug(` SUBFIELD MERGE RESULT: '${newValue}'`, debugDev);\n //debug(` TODO: sort subfields, handle punctuation...`);\n }\n //else { debugDev(` mergeOrAddSubfield() did not add '\u2021${fieldToString(subfieldForMergeOrAdd)}' to '${originalValue}'`); }\n\n });\n}\n\n\nfunction skipMergeField(baseRecord, sourceField, config) {\n if (!mergableTag(sourceField.tag, config)) {\n nvdebug(`skipMergeField(): field '${fieldToString(sourceField)}' listed as skippable!`, debugDev);\n return true;\n }\n\n // Skip duplicate field when merging two records (NB! internal merge merges/removes the duplicate field):\n if (!baseRecord.internalMerge && baseRecord.fields.some(baseField => !baseField.mergeCandidate && fieldsAreIdentical(sourceField, baseField))) {\n nvdebug(`skipMergeField(): field '${fieldToString(sourceField)}' already exists! No merge required!`, debugDev);\n sourceField.deleted = 1;\n return true;\n }\n\n return false;\n}\n\nfunction sourceRecordIsBetter(baseField, sourceField) {\n if (!baseField.subfields) {\n return;\n }\n // MELINDA-8978: prefer Asteri version\n if (isAsteriField(sourceField) && !isAsteriField(baseField)) {\n return 1;\n }\n\n function isAsteriField(field) {\n if (field.subfields.some(sf => sf.code === '0' && sf.value.match(/^\\((?:FI-ASTERI-[NW]|FIN1[13])\\)[0-9]{9}$/u))) {\n return true;\n }\n }\n return false;\n}\n\nfunction swapDataBetweenFields(field1, field2) {\n // NB! Does not support controlfields yet! Add support if the need arises.\n if (field1.subfields) { // If field1 has subfields, then also field2 has them. No need to check the other field here.\n swapNamedData('ind1');\n swapNamedData('ind2');\n swapNamedData('subfields');\n return;\n }\n return;\n\n function swapNamedData(name) {\n const data = field1[name];\n field1[name] = field2[name];\n field2[name] = data;\n }\n\n}\n\nexport function mergeField(baseRecord, sourceRecord, sourceField, config) {\n nvdebug(`SELF: ${fieldToString(sourceField)}`, debugDev);\n\n sourceField.mergeCandidate = true;\n // skip duplicates and special cases:\n if (skipMergeField(baseRecord, sourceField, config)) {\n nvdebug(`mergeField(): don't merge '${fieldToString(sourceField)}'`, debugDev);\n delete sourceField.mergeCandidate;\n return false;\n }\n\n nvdebug(`mergeField(): Try to merge '${fieldToString(sourceField)}'.`, debugDev);\n const counterpartField = getCounterpart(baseRecord, sourceRecord, sourceField, config);\n\n if (counterpartField) {\n if (sourceRecordIsBetter(counterpartField, sourceField)) {\n swapDataBetweenFields(counterpartField, sourceField);\n }\n\n const candFieldPairs880 = sourceField.tag === '880' ? undefined : fieldGetOccurrenceNumberPairs(sourceField, sourceRecord.fields);\n nvdebug(`mergeField(): Got counterpart: '${fieldToString(counterpartField)}'. Thus try merge...`, debugDev);\n nvdebug(`PAIR: ${candFieldPairs880 ? fieldsToString(candFieldPairs880) : 'NADA'}`, debugDev);\n mergeField2(baseRecord, counterpartField, sourceField, config, candFieldPairs880);\n sourceField.deleted = 1;\n delete sourceField.mergeCandidate;\n return true;\n }\n // NB! Counterpartless field is inserted to 7XX even if field.tag says 1XX:\n debugDev(`mergeField(): No mergable counterpart found for '${fieldToString(sourceField)}'.`);\n delete sourceField.mergeCandidate;\n return false;\n}\n\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -4,6 +4,7 @@ import validatorFactory from "./merge-fields/index.js";
4
4
  import { READERS } from "@natlibfi/fixura";
5
5
  import generateTests from "@natlibfi/fixugen";
6
6
  import createDebugLogger from "debug";
7
+ import { nvdebug } from "./utils.js";
7
8
  generateTests({
8
9
  callback,
9
10
  path: [import.meta.dirname, "..", "test-fixtures", "merge-fields"],
@@ -25,12 +26,13 @@ async function testValidatorFactory() {
25
26
  assert.equal(typeof validator.description, "string");
26
27
  assert.equal(typeof validator.validate, "function");
27
28
  }
28
- async function callback({ getFixture, enabled = true, fix = false }) {
29
+ async function callback({ getFixture, enabled = true, fix = false, tagPattern = false }) {
29
30
  if (enabled === false) {
30
31
  debug("TEST SKIPPED!");
31
32
  return;
32
33
  }
33
- const validator = await validatorFactory();
34
+ nvdebug(`TAG PATTERN: ${tagPattern}`);
35
+ const validator = await validatorFactory(tagPattern);
34
36
  const record = new MarcRecord(getFixture("record.json"));
35
37
  const expectedResult = getFixture("expectedResult.json");
36
38
  if (!fix) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/merge-fields.test.js"],
4
- "sourcesContent": ["import assert from 'node:assert';\nimport {MarcRecord} from '@natlibfi/marc-record';\nimport validatorFactory from './merge-fields/index.js';\nimport {READERS} from '@natlibfi/fixura';\nimport generateTests from '@natlibfi/fixugen';\nimport createDebugLogger from 'debug';\n\ngenerateTests({\n callback,\n path: [import.meta.dirname, '..', 'test-fixtures', 'merge-fields'],\n useMetadataFile: true,\n recurse: false,\n fixura: {\n reader: READERS.JSON\n },\n hooks: {\n before: async () => {\n testValidatorFactory();\n }\n }\n});\nconst debug = createDebugLogger('@natlibfi/marc-record-validators-melinda/merge-fields:test');\n\nasync function testValidatorFactory() {\n const validator = await validatorFactory();\n\n assert.equal(typeof validator, 'object');\n assert.equal(typeof validator.description, 'string');\n assert.equal(typeof validator.validate, 'function');\n}\n\nasync function callback({getFixture, enabled = true, fix = false}) {\n if (enabled === false) {\n debug('TEST SKIPPED!');\n return;\n }\n\n const validator = await validatorFactory();\n const record = new MarcRecord(getFixture('record.json'));\n const expectedResult = getFixture('expectedResult.json');\n // console.log(expectedResult); // eslint-disable-line\n\n if (!fix) {\n const result = await validator.validate(record);\n assert.deepEqual(result, expectedResult);\n return;\n }\n\n await validator.fix(record);\n assert.deepEqual(record, expectedResult);\n}\n"],
5
- "mappings": "AAAA,OAAO,YAAY;AACnB,SAAQ,kBAAiB;AACzB,OAAO,sBAAsB;AAC7B,SAAQ,eAAc;AACtB,OAAO,mBAAmB;AAC1B,OAAO,uBAAuB;AAE9B,cAAc;AAAA,EACZ;AAAA,EACA,MAAM,CAAC,YAAY,SAAS,MAAM,iBAAiB,cAAc;AAAA,EACjE,iBAAiB;AAAA,EACjB,SAAS;AAAA,EACT,QAAQ;AAAA,IACN,QAAQ,QAAQ;AAAA,EAClB;AAAA,EACA,OAAO;AAAA,IACL,QAAQ,YAAY;AAClB,2BAAqB;AAAA,IACvB;AAAA,EACF;AACF,CAAC;AACD,MAAM,QAAQ,kBAAkB,4DAA4D;AAE5F,eAAe,uBAAuB;AACpC,QAAM,YAAY,MAAM,iBAAiB;AAEzC,SAAO,MAAM,OAAO,WAAW,QAAQ;AACvC,SAAO,MAAM,OAAO,UAAU,aAAa,QAAQ;AACnD,SAAO,MAAM,OAAO,UAAU,UAAU,UAAU;AACpD;AAEA,eAAe,SAAS,EAAC,YAAY,UAAU,MAAM,MAAM,MAAK,GAAG;AACjE,MAAI,YAAY,OAAO;AACrB,UAAM,eAAe;AACrB;AAAA,EACF;AAEA,QAAM,YAAY,MAAM,iBAAiB;AACzC,QAAM,SAAS,IAAI,WAAW,WAAW,aAAa,CAAC;AACvD,QAAM,iBAAiB,WAAW,qBAAqB;AAGvD,MAAI,CAAC,KAAK;AACR,UAAM,SAAS,MAAM,UAAU,SAAS,MAAM;AAC9C,WAAO,UAAU,QAAQ,cAAc;AACvC;AAAA,EACF;AAEA,QAAM,UAAU,IAAI,MAAM;AAC1B,SAAO,UAAU,QAAQ,cAAc;AACzC;",
4
+ "sourcesContent": ["import assert from 'node:assert';\nimport {MarcRecord} from '@natlibfi/marc-record';\nimport validatorFactory from './merge-fields/index.js';\nimport {READERS} from '@natlibfi/fixura';\nimport generateTests from '@natlibfi/fixugen';\nimport createDebugLogger from 'debug';\nimport { nvdebug } from './utils.js';\n\ngenerateTests({\n callback,\n path: [import.meta.dirname, '..', 'test-fixtures', 'merge-fields'],\n useMetadataFile: true,\n recurse: false,\n fixura: {\n reader: READERS.JSON\n },\n hooks: {\n before: async () => {\n testValidatorFactory();\n }\n }\n});\nconst debug = createDebugLogger('@natlibfi/marc-record-validators-melinda/merge-fields:test');\n\nasync function testValidatorFactory() {\n const validator = await validatorFactory();\n\n assert.equal(typeof validator, 'object');\n assert.equal(typeof validator.description, 'string');\n assert.equal(typeof validator.validate, 'function');\n}\n\nasync function callback({getFixture, enabled = true, fix = false, tagPattern = false}) {\n if (enabled === false) {\n debug('TEST SKIPPED!');\n return;\n }\n\n nvdebug(`TAG PATTERN: ${tagPattern}`);\n\n const validator = await validatorFactory(tagPattern);\n const record = new MarcRecord(getFixture('record.json'));\n const expectedResult = getFixture('expectedResult.json');\n // console.log(expectedResult); // eslint-disable-line\n\n // NB! This validator will only use tags matching /^[1678](?:00|10|11|30)$/ unless tagPattern is specified!\n if (!fix) {\n const result = await validator.validate(record);\n assert.deepEqual(result, expectedResult);\n return;\n }\n\n await validator.fix(record);\n assert.deepEqual(record, expectedResult);\n}\n"],
5
+ "mappings": "AAAA,OAAO,YAAY;AACnB,SAAQ,kBAAiB;AACzB,OAAO,sBAAsB;AAC7B,SAAQ,eAAc;AACtB,OAAO,mBAAmB;AAC1B,OAAO,uBAAuB;AAC9B,SAAS,eAAe;AAExB,cAAc;AAAA,EACZ;AAAA,EACA,MAAM,CAAC,YAAY,SAAS,MAAM,iBAAiB,cAAc;AAAA,EACjE,iBAAiB;AAAA,EACjB,SAAS;AAAA,EACT,QAAQ;AAAA,IACN,QAAQ,QAAQ;AAAA,EAClB;AAAA,EACA,OAAO;AAAA,IACL,QAAQ,YAAY;AAClB,2BAAqB;AAAA,IACvB;AAAA,EACF;AACF,CAAC;AACD,MAAM,QAAQ,kBAAkB,4DAA4D;AAE5F,eAAe,uBAAuB;AACpC,QAAM,YAAY,MAAM,iBAAiB;AAEzC,SAAO,MAAM,OAAO,WAAW,QAAQ;AACvC,SAAO,MAAM,OAAO,UAAU,aAAa,QAAQ;AACnD,SAAO,MAAM,OAAO,UAAU,UAAU,UAAU;AACpD;AAEA,eAAe,SAAS,EAAC,YAAY,UAAU,MAAM,MAAM,OAAO,aAAa,MAAK,GAAG;AACrF,MAAI,YAAY,OAAO;AACrB,UAAM,eAAe;AACrB;AAAA,EACF;AAEA,UAAQ,gBAAgB,UAAU,EAAE;AAEpC,QAAM,YAAY,MAAM,iBAAiB,UAAU;AACnD,QAAM,SAAS,IAAI,WAAW,WAAW,aAAa,CAAC;AACvD,QAAM,iBAAiB,WAAW,qBAAqB;AAIvD,MAAI,CAAC,KAAK;AACR,UAAM,SAAS,MAAM,UAAU,SAAS,MAAM;AAC9C,WAAO,UAAU,QAAQ,cAAc;AACvC;AAAA,EACF;AAEA,QAAM,UAAU,IAAI,MAAM;AAC1B,SAAO,UAAU,QAAQ,cAAc;AACzC;",
6
6
  "names": []
7
7
  }
@@ -210,7 +210,8 @@ function deriveIndividualDeletables(record) {
210
210
  return processTodoList([...stillToDo, ...moreToDo], [...deletables, tmp]);
211
211
  }
212
212
  const inferiorTerms2 = getPrepublicationTerms(currString);
213
- const newDeletables = [...deletables, ...subsets, ...accentless, ...d490, ...inferiorTerms2];
213
+ const aiBased = `${currString} \u20217 (dpenmw)AI`;
214
+ const newDeletables = [...deletables, ...subsets, ...accentless, ...d490, ...inferiorTerms2, aiBased];
214
215
  if (subsets.length) {
215
216
  return processTodoList([...stillToDo, ...moreToDo], newDeletables);
216
217
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/removeInferiorDataFields.js"],
4
- "sourcesContent": ["import createDebugLogger from 'debug';\nimport {fieldToChain, sameField} from './removeDuplicateDataFields.js';\nimport {fieldGetOccurrenceNumberPairs, fieldHasValidSubfield6, fieldSevenToOneOccurrenceNumber, fieldsToNormalizedString} from './subfield6Utils.js';\nimport {fieldHasSubfield, fieldsToString, fieldToString, nvdebug, uniqArray} from './utils.js';\nimport {fieldHasValidSubfield8} from './subfield8Utils.js';\nimport {encodingLevelIsBetterThanPrepublication, fieldRefersToKoneellisestiTuotettuTietue, getEncodingLevel} from './prepublicationUtils.js';\nimport {cloneAndNormalizeFieldForComparison} from './normalizeFieldForComparison.js';\nimport {fixComposition, precomposeFinnishLetters} from './normalize-utf8-diacritics.js';\n\n// Relocated from melinda-marc-record-merge-reducers (and renamed)\n\n// NB! This validator handles only full fields, and does not support subfield $8 removal.\n// Also, having multiple $8 subfields in same fields is not supported.\n// If this functionality is needed, see removeDuplicateDatafields.js for examples of subfield-only stuff.\nconst debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:removeInferiorDataFields');\n\nexport default function () {\n return {\n description: 'Remove subset data fields. Certain exceptions apply, mainly too complicated for chained fields',\n validate, fix\n };\n\n function fix(record) {\n nvdebug('Fix record: remove inferior (eg. subset) data fields', debug);\n const res = {message: [], fix: [], valid: true};\n removeInferiorDatafields(record, true);\n // This can not really fail...\n return res;\n }\n\n function validate(record) {\n // Check max, and check number of different indexes\n nvdebug('Validate record: remove inferior (eg. subset) data fields', debug);\n\n const duplicates = removeInferiorDatafields(record, false);\n\n const res = {message: duplicates};\n\n res.valid = res.message.length < 1;\n return res;\n }\n}\n\n\nfunction deriveInferiorChains(fields, record) {\n //nvdebug(`======= GOT ${fields.length} FIELDS TO CHAINIFY`);\n const hash = {};\n\n fields.forEach(f => fieldToChainToDeletables(f));\n\n return hash;\n\n //nvdebug(`WP1: GOT ${todoList.length} CHAINS`);\n\n\n // here we map deletableStringObject[str] => field. The idea is to help debugging. We don't actually need the field object...\n //return deriveChainDeletables(todoList);\n\n function fieldToChainToDeletables(field) {\n const chain = fieldToChain(field, record);\n if (chain.length < 2) {\n return;\n }\n const chainAsString = fieldsToNormalizedString(chain, 0, true, true);\n const arr = deriveChainDeletables([chainAsString]);\n //nvdebug(`GOT ${arr.length} DELETABLES FOR ${chainAsString}`);\n arr.forEach(val => {\n if (!(val in hash)) {\n hash[val] = field;\n }\n });\n }\n\n function deriveChainDeletables(todoList, deletables = []) {\n const [chainAsString, ...stillToDo] = todoList;\n if (chainAsString === undefined) {\n return deletables;\n }\n\n // Fix MRA-476 (part 1): one $6 value can be worse than the other\n const withoutScriptIdentificationCode = chainAsString.replace(/( \u20216 [0-9X][0-9][0-9]-(?:XX|[0-9]+))\\/[^ ]+/u, '$1');\n\n // Remove keepless versions:\n const keepless = chainAsString.replace(/ \u20219 [A-Z]+<KEEP>/u, '');\n\n // MRA-433: 490 ind1=1 vs ind1=0: remove latter (luckily no 2nd indicator etc)\n const linked490Ind1 = chainAsString.replace(/^490 1/u, '490 0').replace(/\\t880 1/ug, '\\t880 0');\n const arr = [withoutScriptIdentificationCode, keepless, linked490Ind1].filter(val => val !== chainAsString);\n if (arr.length > 0) {\n return deriveChainDeletables([...stillToDo, ...arr], [...deletables, ...arr]);\n }\n\n return deriveChainDeletables(stillToDo, deletables);\n }\n\n}\n\nfunction isRelevantChain6(field, record) {\n //Can't be a chain:\n if (!fieldHasValidSubfield6(field) && !fieldHasValidSubfield8(field)) {\n return false;\n }\n // Too short to be a chain:\n const chain = fieldToChain(field, record);\n if (chain.length < 2) {\n return false;\n }\n // No field can contains no more than one subfield $6\n if (chain.some(f => f.subfields.filter(sf => sf.code === '6').length > 1)) {\n return false;\n }\n\n // Check whether our field is the head of a chain:\n return sameField(field, chain[0]);\n}\n\nexport function removeInferiorChains(record, fix = true) {\n const fields = record.fields.filter(f => isRelevantChain6(f, record));\n //nvdebug(`WP2.0: GOT ${fields.length} chain(s)`);\n\n const deletableChainsAsKeys = deriveInferiorChains(fields, record);\n const nChains = Object.keys(deletableChainsAsKeys).length;\n //nvdebug(`WP2: GOT ${nChains} chain(s)`);\n if (nChains === 0) {\n return [];\n }\n\n //nvdebug(`removeInferiorChains() has ${fields.length} fields-in-chain(s), and a list of ${nChains} deletable(s)`);\n\n return innerRemoveInferiorChains(fields);\n\n function innerRemoveInferiorChains(fields, deletedStringsArray = []) {\n const [currField, ...remainingFields] = fields;\n\n if (currField === undefined) {\n return deletedStringsArray;\n }\n\n const chain = fieldToChain(currField, record);\n if (chain.length === 0 || !sameField(currField, chain[0])) {\n return innerRemoveInferiorChains(remainingFields, deletedStringsArray);\n }\n\n const chainAsString = fieldsToNormalizedString(chain, 0, true, true);\n if (!(chainAsString in deletableChainsAsKeys)) {\n return innerRemoveInferiorChains(remainingFields, deletedStringsArray);\n }\n\n const triggeringField = deletableChainsAsKeys[chainAsString];\n const triggeringChain = fieldToChain(triggeringField, record);\n\n // If the inferior (deletable) chain is 1XX-based, convert the triggering better chain from 7XX to 1XX:\n if (chainContains1XX(chain)) {\n triggeringChain.forEach(f => sevenToOne(f, triggeringChain));\n }\n //nvdebug(`iRIS6C: ${chainAsString}`);\n const deletedString = fieldsToString(chain);\n const message = `DEL: '${deletedString}' REASON: '${fieldsToString(triggeringChain)}'`;\n if (fix) {\n //nvdebug(`INFERIOR $6 CHAIN REMOVAL: ${message}}`, debug);\n chain.forEach(field => record.removeField(field));\n }\n return innerRemoveInferiorChains(remainingFields, [...deletedStringsArray, message]);\n }\n\n function chainContains1XX(chain) {\n return chain.some(f => f.tag.substring(0, 1) === '1');\n }\n\n function sevenToOne(field, chain) { // Change 7XX field to 1XX field. Also handle the corresponding 880$6 7XX-NN subfields\n // NB! This function should be called only if the original 1XX gets deleted!\n if (!['700', '710', '711', '730'].includes(field.tag)) {\n return;\n }\n // Retag field 7XX as 1XX and fix corresponding occurrence numbers as well:\n const pairs = fieldGetOccurrenceNumberPairs(field, chain);\n field.tag = `1${field.tag.substring(1)}`;\n // There should always be one pair, but I'm not sanity-checking this\n pairs.forEach(pairedField => fieldSevenToOneOccurrenceNumber(pairedField));\n }\n\n}\n\n\nfunction getIdentifierlessAndKeeplessSubsets(fieldAsString) {\n // The rules below are not perfect (in complex cases they don't catch all permutations), but good enough:\n // Remove identifier(s) (MELKEHITYS-2383-ish):\n\n const identifierlessString = fieldAsString.replace(/ \u2021[01] [^\u2021]+($| \u2021)/u, '$1');\n const keeplessString = fieldAsString.replace(/ \u20219 [A-Z]+<KEEP>/u, '');\n\n return [identifierlessString, keeplessString].filter(val => val !== fieldAsString);\n}\n\nfunction deriveIndividualDeletables490(todoList, deletables = []) {\n const [fieldAsString, ...stillToDo] = todoList;\n if (fieldAsString === undefined) {\n return deletables;\n }\n //nvdebug(`PROCESS ${fieldAsString}`);\n if (!fieldAsString.match(/^490/u)) {\n return deriveIndividualDeletables490(stillToDo, deletables);\n }\n\n // $6-less version (keep this first)\n const sixless = fieldAsString.replace(/ \u20216 [^\u2021]+ \u2021/u, ' \u2021');\n\n // Without final $v or $x:\n const withoutFinalVOrX = fieldAsString.replace(/ *[;,] \u2021[vx] [^\u2021]+$/u, '');\n // Add intermediate $x-less version\n const xless = fieldAsString.replace(/, \u2021x [^\u2021]+(, \u2021x| ; \u2021v)/u, '$1');\n\n // Add $xv-less version (handled by recursion?)\n const xvless = fieldAsString.replace(/, \u2021x [^\u2021]+ \u2021v [^\u2021]+$/u, '');\n\n // MRA-433-ish (non-chain): 490 ind1=1 vs ind1=0: remove latter\n const modifiedInd2 = fieldAsString.match(/^490 1/u) ? `490 0${fieldAsString.substring(5)}` : fieldAsString;\n\n const arr = [sixless, withoutFinalVOrX, xless, xvless, modifiedInd2].filter(val => val !== fieldAsString);\n\n /*\n if (arr.length) {\n nvdebug(`${arr.length} derivation(s) for ${fieldAsString}`);\n nvdebug(arr.join('\\n'));\n }\n */\n return arr;\n}\n\nfunction deriveIndividualDeletables(record) {\n const todoList = record.fields.map(f => fieldToString(f));\n //const finishedRecord = encodingLevelIsBetterThanPrepublication(getEncodingLevel(record));\n\n const deletableStringsArray = processTodoList(todoList);\n\n const inferiorTerms = getInferiorTerms(record);\n\n return uniqArray([...deletableStringsArray, ...inferiorTerms]);\n\n function getInferiorTerms(record) {\n const inputFields = record.fields.filter(f => ['648', '650', '651'].includes(f.tag) && f.subfields);\n const result = inputFields.flatMap(f => fieldToInferiorFields(f));\n\n // console.log(result.join('\\n')); // eslint-disable-line no-console\n return result;\n }\n\n function fieldToInferiorFields(field) {\n const aArray = field.subfields.filter(sf => sf.code === 'a');\n if (field.tag === '650') {\n return aArray.flatMap(sf => [`653 ## \u2021a ${sf.value}`, `653 #0 \u2021a ${sf.value}`]);\n }\n return aArray.map(sf => `653 ## \u2021a ${sf.value}`);\n }\n\n function processTodoList(thingsToDo, deletables = []) {\n const [currString, ...stillToDo] = thingsToDo;\n\n if (currString === undefined) {\n return deletables;\n }\n\n const accentless = getAccentlessVersion(currString);\n const d490 = deriveIndividualDeletables490([currString]);\n const subsets = getIdentifierlessAndKeeplessSubsets(currString);\n const moreToDo = [...accentless, ...d490, ...subsets];\n\n\n if (currString.match(/^[1678]00/u)) {\n // Proof-of-concept rule. Should be improved eventually...\n if (currString.match(/, \u2021e [^\u2021]+\\.$/u)) {\n const tmp = currString.replace(/, \u2021e [^\u2021]+\\.$/u, '.');\n return processTodoList([tmp, ...stillToDo, ...moreToDo], [...deletables, tmp]);\n }\n }\n\n if (currString.match(/^500 ## \u2021a Lis\u00E4painokset: Lis\u00E4painos /u)) { // MET-569\n const tmp1 = currString.replace(' Lis\u00E4painos ', ' [Lis\u00E4painos] ');\n const tmp2 = currString.replace(' Lis\u00E4painos ', ' ');\n if (tmp1 !== currString && tmp2 !== currString) {\n return processTodoList([...stillToDo, ...moreToDo], [...deletables, tmp1, tmp2]);\n }\n }\n\n if (currString.match(/^500 ## \u2021a Lis\u00E4painokset: \\[Lis\u00E4painos\\] /u)) { // MET-569\n const tmp = currString.replace(' [Lis\u00E4painos] ', ' ');\n if (tmp !== currString) {\n return processTodoList([...stillToDo, ...moreToDo], [...deletables, tmp]);\n }\n }\n\n if (currString.match(/^500 ## \u2021a Ei vastaanotettu\\.$/u)) { // MELKEHITYS-3147\n return processTodoList([...stillToDo, ...moreToDo], [...deletables, '500 ## \u2021a EI VASTAANOTETTU.']);\n }\n if (currString.match(/^500 ## \u2021a Ei ilmesty\\.$/u)) { // MELKEHITYS-3147\n return processTodoList([...stillToDo, ...moreToDo], [...deletables, '500 ## \u2021a EI ILMESTY.']);\n }\n\n if (currString.match(/^505 .0.*-- \u2021t/u)) { // MRA-413-ish\n const tmp = currString.replace(/ -- \u2021t /gu, ' -- '). // remove non-initial $t subfields\n replace(/ \u2021[rg] /gu, ' '). // remove $r and $g subfields\n replace(/ \u2021t /u, ' \u2021a '). // change first $t to $a\n // ind2: '1' => '#':\n replace(/^505 (.)0/u, '505 $1#');\n if (tmp !== currString) {\n return processTodoList([tmp, ...stillToDo, ...moreToDo], [...deletables, tmp]);\n }\n //nvdebug(`505 ORIGINAL: '${fieldAsString}'`)\n //nvdebug(`505 DERIVATE: '${tmp}'`)\n }\n\n if (currString.match(/^594 ## \u2021a Ei vastaanotettu \u20215 FENNI$/u)) { // MELKEHITYS-3147\n return processTodoList([...stillToDo, ...moreToDo], [...deletables, '594 ## \u2021a EI VASTAANOTETTU \u20215 FENNI']);\n }\n if (currString.match(/^594 ## \u2021a Ei ilmesty \u20215 FENNI$/u)) { // MELKEHITYS-3147\n return processTodoList([...stillToDo, ...moreToDo], [...deletables, '594 ## \u2021a EI ILMESTY \u20215 FENNI']);\n }\n\n // MET-381: remove occurence number TAG-00, if TAG-NN existists\n if (currString.match(/^880.* \u20216 [0-9][0-9][0-9]-(?:[1-9][0-9]|0[1-9])/u)) {\n const tmp = currString.replace(/( \u20216 [0-9][0-9][0-9])-[0-9]+/u, '$1-00');\n //nvdebug(`MET-381: ADD TO DELETABLES: '${tmp}'`);\n //deletableStringsArray.push(tmp);\n if (tmp.match(/ \u20216 [0-9][0-9][0-9]-00\\/[^ ]+ /u)) {\n const tmp2 = tmp.replace(/( \u20216 [0-9][0-9][0-9]-00)[^ ]+/u, '$1');\n //nvdebug(`MET-381: ADD TO DELETABLES: '${tmp2}'`);\n return processTodoList([...stillToDo, ...moreToDo], [...deletables, tmp, tmp2]);\n }\n return processTodoList([...stillToDo, ...moreToDo], [...deletables, tmp]);\n }\n\n // MET-575 (merge: applies in postprocessing)\n const inferiorTerms = getPrepublicationTerms(currString);\n\n const newDeletables = [...deletables, ...subsets, ...accentless, ...d490, ...inferiorTerms];\n\n if (subsets.length) {\n return processTodoList([...stillToDo, ...moreToDo], newDeletables);\n }\n\n return processTodoList([...stillToDo, ...moreToDo], newDeletables);\n }\n\n function getAccentlessVersion(string) { // MET-527\n //nvdebug(`START: '${string}`);\n // This is a sanity check: if precomposition does something, there's something wrong, and we don't want to proceed..\n if (string !== precomposeFinnishLetters(string)) {\n return [];\n }\n const accentless = String(fixComposition(string)).replace(/\\p{Diacritic}/gu, '');\n //nvdebug(`FROM '${string}'\\n TO '${accentless}'`);\n if (accentless === string) { // Don't self-destruct\n return [];\n }\n return [accentless];\n }\n\n function getPrepublicationTerms(fieldAsString) {\n if (fieldAsString.match(/^653./u)) {\n // MET-528 (extented by MET-575)\n return [`${fieldAsString} \u2021g ENNAKKOTIETO`, `${fieldAsString} \u2021g Ennakkotieto`, `${fieldAsString} \u2021g ennakkotieto`, `${fieldAsString} \u2021g ENNAKKOTIETO.`, `${fieldAsString} \u2021g Ennakkotieto.`, `${fieldAsString} \u2021g ennakkotieto.`];\n }\n\n return [];\n }\n\n}\n\nfunction fieldToNormalizedString(field) {\n const normalizedField = cloneAndNormalizeFieldForComparison(field);\n return fieldToString(normalizedField);\n}\n\nfunction deriveIndividualNormalizedDeletables(record) { // MET-461:\n const encodingLevel = getEncodingLevel(record);\n const recordIsFinished = encodingLevelIsBetterThanPrepublication(encodingLevel);\n const met495 = encodingLevel === '2' && record.fields.some(f => f.tag === '500' && fieldRefersToKoneellisestiTuotettuTietue(f));\n if (!recordIsFinished || met495) {\n return [];\n }\n const relevantFields = record.fields.filter(f => ['245', '246'].includes(f.tag) && fieldHasSubfield(f, 'a'));\n\n return deriveDeletable946s(relevantFields);\n\n function deriveDeletable946s(fields, results = []) {\n const [currField, ...remainingFields] = fields;\n if (currField === undefined) {\n return results;\n }\n\n const fieldAsNormalizedString = fieldToNormalizedString(currField);\n const tmp = fieldAsNormalizedString.replace(/^(?:...) ../u, '946 ##'). // <= Change tag to 946 and indicators to '##'\n replace(' \u2021a ', ' \u2021i nimeke onixissa \u2021a '). // Add $i before $a. NB! This is added in the normalized lower-cased form!\n replace(/(?: \\/)? \u2021c[^\u2021]+$/u, ''); // Remove $c. (Can $c be non-last?)\n const candArray = [tmp, `${tmp} \u20215 MELINDA`].filter(val => val !== fieldAsNormalizedString);\n if (candArray.length) {\n return deriveDeletable946s(remainingFields, [...results, ...candArray]);\n }\n return deriveDeletable946s(remainingFields, results);\n }\n}\n\nexport function removeIndividualInferiorDatafields(record, fix = true) { // No $6 nor $8 in field\n const deletableFieldsAsStrings = deriveIndividualDeletables(record);\n const deletableFieldsAsNormalizedStrings = deriveIndividualNormalizedDeletables(record);\n\n // nvdebug(`Deletables:\\n ${deletableFieldsAsStrings.join('\\n ')}`);\n // nvdebug(`Normalized deletables:\\n ${deletableFieldsAsNormalizedStrings.join('\\n ')}`);\n\n const hits = record.fields.filter(field => isDeletableField(field));\n\n const deletedFieldsAsStrings = hits.map(f => fieldToString(f));\n\n if (fix) {\n hits.forEach(field => {\n //nvdebug(`Remove inferior field: ${fieldToString(field)}`, debug);\n record.removeField(field);\n });\n }\n\n return deletedFieldsAsStrings;\n\n function isDeletableField(field) {\n const fieldAsString = fieldToString(field);\n if (deletableFieldsAsStrings.includes(fieldAsString)) {\n return true;\n }\n const fieldAsNormalizedString = fieldToNormalizedString(field);\n if (deletableFieldsAsNormalizedStrings.includes(fieldAsNormalizedString)) {\n return true;\n }\n\n return false;\n }\n}\n\n\nexport function removeInferiorDatafields(record, fix = true) {\n const removables = removeIndividualInferiorDatafields(record, fix); // Lone fields\n //const removables8 = removeDuplicateSubfield8Chains(record, fix); // Lone subfield $8 chains\n const removables6 = removeInferiorChains(record, fix); // Lone subfield $6 chains\n // HOW TO HANDLE $6+$8 combos? Skipping is relatively OK.\n\n nvdebug(`REMOVABLES:\\n ${removables.join('\\n ')}`, debug);\n nvdebug(`REMOVABLES 6:\\n ${removables6.join('\\n ')}`, debug);\n\n const removablesAll = removables.concat(removables6); //.concat(removables8);\n\n return removablesAll;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import createDebugLogger from 'debug';\nimport {fieldToChain, sameField} from './removeDuplicateDataFields.js';\nimport {fieldGetOccurrenceNumberPairs, fieldHasValidSubfield6, fieldSevenToOneOccurrenceNumber, fieldsToNormalizedString} from './subfield6Utils.js';\nimport {fieldHasSubfield, fieldsToString, fieldToString, nvdebug, uniqArray} from './utils.js';\nimport {fieldHasValidSubfield8} from './subfield8Utils.js';\nimport {encodingLevelIsBetterThanPrepublication, fieldRefersToKoneellisestiTuotettuTietue, getEncodingLevel} from './prepublicationUtils.js';\nimport {cloneAndNormalizeFieldForComparison} from './normalizeFieldForComparison.js';\nimport {fixComposition, precomposeFinnishLetters} from './normalize-utf8-diacritics.js';\n\n// Relocated from melinda-marc-record-merge-reducers (and renamed)\n\n// NB! This validator handles only full fields, and does not support subfield $8 removal.\n// Also, having multiple $8 subfields in same fields is not supported.\n// If this functionality is needed, see removeDuplicateDatafields.js for examples of subfield-only stuff.\nconst debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:removeInferiorDataFields');\n\nexport default function () {\n return {\n description: 'Remove subset data fields. Certain exceptions apply, mainly too complicated for chained fields',\n validate, fix\n };\n\n function fix(record) {\n nvdebug('Fix record: remove inferior (eg. subset) data fields', debug);\n const res = {message: [], fix: [], valid: true};\n removeInferiorDatafields(record, true);\n // This can not really fail...\n return res;\n }\n\n function validate(record) {\n // Check max, and check number of different indexes\n nvdebug('Validate record: remove inferior (eg. subset) data fields', debug);\n\n const duplicates = removeInferiorDatafields(record, false);\n\n const res = {message: duplicates};\n\n res.valid = res.message.length < 1;\n return res;\n }\n}\n\n\nfunction deriveInferiorChains(fields, record) {\n //nvdebug(`======= GOT ${fields.length} FIELDS TO CHAINIFY`);\n const hash = {};\n\n fields.forEach(f => fieldToChainToDeletables(f));\n\n return hash;\n\n //nvdebug(`WP1: GOT ${todoList.length} CHAINS`);\n\n\n // here we map deletableStringObject[str] => field. The idea is to help debugging. We don't actually need the field object...\n //return deriveChainDeletables(todoList);\n\n function fieldToChainToDeletables(field) {\n const chain = fieldToChain(field, record);\n if (chain.length < 2) {\n return;\n }\n const chainAsString = fieldsToNormalizedString(chain, 0, true, true);\n const arr = deriveChainDeletables([chainAsString]);\n //nvdebug(`GOT ${arr.length} DELETABLES FOR ${chainAsString}`);\n arr.forEach(val => {\n if (!(val in hash)) {\n hash[val] = field;\n }\n });\n }\n\n function deriveChainDeletables(todoList, deletables = []) {\n const [chainAsString, ...stillToDo] = todoList;\n if (chainAsString === undefined) {\n return deletables;\n }\n\n // Fix MRA-476 (part 1): one $6 value can be worse than the other\n const withoutScriptIdentificationCode = chainAsString.replace(/( \u20216 [0-9X][0-9][0-9]-(?:XX|[0-9]+))\\/[^ ]+/u, '$1');\n\n // Remove keepless versions:\n const keepless = chainAsString.replace(/ \u20219 [A-Z]+<KEEP>/u, '');\n\n // MRA-433: 490 ind1=1 vs ind1=0: remove latter (luckily no 2nd indicator etc)\n const linked490Ind1 = chainAsString.replace(/^490 1/u, '490 0').replace(/\\t880 1/ug, '\\t880 0');\n const arr = [withoutScriptIdentificationCode, keepless, linked490Ind1].filter(val => val !== chainAsString);\n if (arr.length > 0) {\n return deriveChainDeletables([...stillToDo, ...arr], [...deletables, ...arr]);\n }\n\n return deriveChainDeletables(stillToDo, deletables);\n }\n\n}\n\nfunction isRelevantChain6(field, record) {\n //Can't be a chain:\n if (!fieldHasValidSubfield6(field) && !fieldHasValidSubfield8(field)) {\n return false;\n }\n // Too short to be a chain:\n const chain = fieldToChain(field, record);\n if (chain.length < 2) {\n return false;\n }\n // No field can contains no more than one subfield $6\n if (chain.some(f => f.subfields.filter(sf => sf.code === '6').length > 1)) {\n return false;\n }\n\n // Check whether our field is the head of a chain:\n return sameField(field, chain[0]);\n}\n\nexport function removeInferiorChains(record, fix = true) {\n const fields = record.fields.filter(f => isRelevantChain6(f, record));\n //nvdebug(`WP2.0: GOT ${fields.length} chain(s)`);\n\n const deletableChainsAsKeys = deriveInferiorChains(fields, record);\n const nChains = Object.keys(deletableChainsAsKeys).length;\n //nvdebug(`WP2: GOT ${nChains} chain(s)`);\n if (nChains === 0) {\n return [];\n }\n\n //nvdebug(`removeInferiorChains() has ${fields.length} fields-in-chain(s), and a list of ${nChains} deletable(s)`);\n\n return innerRemoveInferiorChains(fields);\n\n function innerRemoveInferiorChains(fields, deletedStringsArray = []) {\n const [currField, ...remainingFields] = fields;\n\n if (currField === undefined) {\n return deletedStringsArray;\n }\n\n const chain = fieldToChain(currField, record);\n if (chain.length === 0 || !sameField(currField, chain[0])) {\n return innerRemoveInferiorChains(remainingFields, deletedStringsArray);\n }\n\n const chainAsString = fieldsToNormalizedString(chain, 0, true, true);\n if (!(chainAsString in deletableChainsAsKeys)) {\n return innerRemoveInferiorChains(remainingFields, deletedStringsArray);\n }\n\n const triggeringField = deletableChainsAsKeys[chainAsString];\n const triggeringChain = fieldToChain(triggeringField, record);\n\n // If the inferior (deletable) chain is 1XX-based, convert the triggering better chain from 7XX to 1XX:\n if (chainContains1XX(chain)) {\n triggeringChain.forEach(f => sevenToOne(f, triggeringChain));\n }\n //nvdebug(`iRIS6C: ${chainAsString}`);\n const deletedString = fieldsToString(chain);\n const message = `DEL: '${deletedString}' REASON: '${fieldsToString(triggeringChain)}'`;\n if (fix) {\n //nvdebug(`INFERIOR $6 CHAIN REMOVAL: ${message}}`, debug);\n chain.forEach(field => record.removeField(field));\n }\n return innerRemoveInferiorChains(remainingFields, [...deletedStringsArray, message]);\n }\n\n function chainContains1XX(chain) {\n return chain.some(f => f.tag.substring(0, 1) === '1');\n }\n\n function sevenToOne(field, chain) { // Change 7XX field to 1XX field. Also handle the corresponding 880$6 7XX-NN subfields\n // NB! This function should be called only if the original 1XX gets deleted!\n if (!['700', '710', '711', '730'].includes(field.tag)) {\n return;\n }\n // Retag field 7XX as 1XX and fix corresponding occurrence numbers as well:\n const pairs = fieldGetOccurrenceNumberPairs(field, chain);\n field.tag = `1${field.tag.substring(1)}`;\n // There should always be one pair, but I'm not sanity-checking this\n pairs.forEach(pairedField => fieldSevenToOneOccurrenceNumber(pairedField));\n }\n\n}\n\n\nfunction getIdentifierlessAndKeeplessSubsets(fieldAsString) {\n // The rules below are not perfect (in complex cases they don't catch all permutations), but good enough:\n // Remove identifier(s) (MELKEHITYS-2383-ish):\n\n const identifierlessString = fieldAsString.replace(/ \u2021[01] [^\u2021]+($| \u2021)/u, '$1');\n const keeplessString = fieldAsString.replace(/ \u20219 [A-Z]+<KEEP>/u, '');\n\n return [identifierlessString, keeplessString].filter(val => val !== fieldAsString);\n}\n\nfunction deriveIndividualDeletables490(todoList, deletables = []) {\n const [fieldAsString, ...stillToDo] = todoList;\n if (fieldAsString === undefined) {\n return deletables;\n }\n //nvdebug(`PROCESS ${fieldAsString}`);\n if (!fieldAsString.match(/^490/u)) {\n return deriveIndividualDeletables490(stillToDo, deletables);\n }\n\n // $6-less version (keep this first)\n const sixless = fieldAsString.replace(/ \u20216 [^\u2021]+ \u2021/u, ' \u2021');\n\n // Without final $v or $x:\n const withoutFinalVOrX = fieldAsString.replace(/ *[;,] \u2021[vx] [^\u2021]+$/u, '');\n // Add intermediate $x-less version\n const xless = fieldAsString.replace(/, \u2021x [^\u2021]+(, \u2021x| ; \u2021v)/u, '$1');\n\n // Add $xv-less version (handled by recursion?)\n const xvless = fieldAsString.replace(/, \u2021x [^\u2021]+ \u2021v [^\u2021]+$/u, '');\n\n // MRA-433-ish (non-chain): 490 ind1=1 vs ind1=0: remove latter\n const modifiedInd2 = fieldAsString.match(/^490 1/u) ? `490 0${fieldAsString.substring(5)}` : fieldAsString;\n\n const arr = [sixless, withoutFinalVOrX, xless, xvless, modifiedInd2].filter(val => val !== fieldAsString);\n\n /*\n if (arr.length) {\n nvdebug(`${arr.length} derivation(s) for ${fieldAsString}`);\n nvdebug(arr.join('\\n'));\n }\n */\n return arr;\n}\n\nfunction deriveIndividualDeletables(record) {\n const todoList = record.fields.map(f => fieldToString(f));\n //const finishedRecord = encodingLevelIsBetterThanPrepublication(getEncodingLevel(record));\n\n const deletableStringsArray = processTodoList(todoList);\n\n const inferiorTerms = getInferiorTerms(record);\n\n return uniqArray([...deletableStringsArray, ...inferiorTerms]);\n\n function getInferiorTerms(record) {\n const inputFields = record.fields.filter(f => ['648', '650', '651'].includes(f.tag) && f.subfields);\n const result = inputFields.flatMap(f => fieldToInferiorFields(f));\n\n // console.log(result.join('\\n')); // eslint-disable-line no-console\n return result;\n }\n\n function fieldToInferiorFields(field) {\n const aArray = field.subfields.filter(sf => sf.code === 'a');\n if (field.tag === '650') {\n return aArray.flatMap(sf => [`653 ## \u2021a ${sf.value}`, `653 #0 \u2021a ${sf.value}`]);\n }\n return aArray.map(sf => `653 ## \u2021a ${sf.value}`);\n }\n\n function processTodoList(thingsToDo, deletables = []) {\n const [currString, ...stillToDo] = thingsToDo;\n\n if (currString === undefined) {\n return deletables;\n }\n\n const accentless = getAccentlessVersion(currString);\n const d490 = deriveIndividualDeletables490([currString]);\n const subsets = getIdentifierlessAndKeeplessSubsets(currString);\n const moreToDo = [...accentless, ...d490, ...subsets];\n\n\n if (currString.match(/^[1678]00/u)) {\n // Proof-of-concept rule. Should be improved eventually...\n if (currString.match(/, \u2021e [^\u2021]+\\.$/u)) {\n const tmp = currString.replace(/, \u2021e [^\u2021]+\\.$/u, '.');\n return processTodoList([tmp, ...stillToDo, ...moreToDo], [...deletables, tmp]);\n }\n }\n\n if (currString.match(/^500 ## \u2021a Lis\u00E4painokset: Lis\u00E4painos /u)) { // MET-569\n const tmp1 = currString.replace(' Lis\u00E4painos ', ' [Lis\u00E4painos] ');\n const tmp2 = currString.replace(' Lis\u00E4painos ', ' ');\n if (tmp1 !== currString && tmp2 !== currString) {\n return processTodoList([...stillToDo, ...moreToDo], [...deletables, tmp1, tmp2]);\n }\n }\n\n if (currString.match(/^500 ## \u2021a Lis\u00E4painokset: \\[Lis\u00E4painos\\] /u)) { // MET-569\n const tmp = currString.replace(' [Lis\u00E4painos] ', ' ');\n if (tmp !== currString) {\n return processTodoList([...stillToDo, ...moreToDo], [...deletables, tmp]);\n }\n }\n\n if (currString.match(/^500 ## \u2021a Ei vastaanotettu\\.$/u)) { // MELKEHITYS-3147\n return processTodoList([...stillToDo, ...moreToDo], [...deletables, '500 ## \u2021a EI VASTAANOTETTU.']);\n }\n if (currString.match(/^500 ## \u2021a Ei ilmesty\\.$/u)) { // MELKEHITYS-3147\n return processTodoList([...stillToDo, ...moreToDo], [...deletables, '500 ## \u2021a EI ILMESTY.']);\n }\n\n if (currString.match(/^505 .0.*-- \u2021t/u)) { // MRA-413-ish\n const tmp = currString.replace(/ -- \u2021t /gu, ' -- '). // remove non-initial $t subfields\n replace(/ \u2021[rg] /gu, ' '). // remove $r and $g subfields\n replace(/ \u2021t /u, ' \u2021a '). // change first $t to $a\n // ind2: '1' => '#':\n replace(/^505 (.)0/u, '505 $1#');\n if (tmp !== currString) {\n return processTodoList([tmp, ...stillToDo, ...moreToDo], [...deletables, tmp]);\n }\n //nvdebug(`505 ORIGINAL: '${fieldAsString}'`)\n //nvdebug(`505 DERIVATE: '${tmp}'`)\n }\n\n if (currString.match(/^594 ## \u2021a Ei vastaanotettu \u20215 FENNI$/u)) { // MELKEHITYS-3147\n return processTodoList([...stillToDo, ...moreToDo], [...deletables, '594 ## \u2021a EI VASTAANOTETTU \u20215 FENNI']);\n }\n if (currString.match(/^594 ## \u2021a Ei ilmesty \u20215 FENNI$/u)) { // MELKEHITYS-3147\n return processTodoList([...stillToDo, ...moreToDo], [...deletables, '594 ## \u2021a EI ILMESTY \u20215 FENNI']);\n }\n\n // MET-381: remove occurence number TAG-00, if TAG-NN existists\n if (currString.match(/^880.* \u20216 [0-9][0-9][0-9]-(?:[1-9][0-9]|0[1-9])/u)) {\n const tmp = currString.replace(/( \u20216 [0-9][0-9][0-9])-[0-9]+/u, '$1-00');\n //nvdebug(`MET-381: ADD TO DELETABLES: '${tmp}'`);\n //deletableStringsArray.push(tmp);\n if (tmp.match(/ \u20216 [0-9][0-9][0-9]-00\\/[^ ]+ /u)) {\n const tmp2 = tmp.replace(/( \u20216 [0-9][0-9][0-9]-00)[^ ]+/u, '$1');\n //nvdebug(`MET-381: ADD TO DELETABLES: '${tmp2}'`);\n return processTodoList([...stillToDo, ...moreToDo], [...deletables, tmp, tmp2]);\n }\n return processTodoList([...stillToDo, ...moreToDo], [...deletables, tmp]);\n }\n\n // MET-575 (merge: applies in postprocessing)\n const inferiorTerms = getPrepublicationTerms(currString);\n\n // MELKEHITYS-3277-ish: non-AI is better than AI (a rare case where longer version is inferior):\n const aiBased = `${currString} \u20217 (dpenmw)AI`;\n\n const newDeletables = [...deletables, ...subsets, ...accentless, ...d490, ...inferiorTerms, aiBased];\n\n if (subsets.length) {\n return processTodoList([...stillToDo, ...moreToDo], newDeletables);\n }\n\n return processTodoList([...stillToDo, ...moreToDo], newDeletables);\n }\n\n function getAccentlessVersion(string) { // MET-527\n //nvdebug(`START: '${string}`);\n // This is a sanity check: if precomposition does something, there's something wrong, and we don't want to proceed..\n if (string !== precomposeFinnishLetters(string)) {\n return [];\n }\n const accentless = String(fixComposition(string)).replace(/\\p{Diacritic}/gu, '');\n //nvdebug(`FROM '${string}'\\n TO '${accentless}'`);\n if (accentless === string) { // Don't self-destruct\n return [];\n }\n return [accentless];\n }\n\n function getPrepublicationTerms(fieldAsString) {\n if (fieldAsString.match(/^653./u)) {\n // MET-528 (extented by MET-575)\n return [`${fieldAsString} \u2021g ENNAKKOTIETO`, `${fieldAsString} \u2021g Ennakkotieto`, `${fieldAsString} \u2021g ennakkotieto`, `${fieldAsString} \u2021g ENNAKKOTIETO.`, `${fieldAsString} \u2021g Ennakkotieto.`, `${fieldAsString} \u2021g ennakkotieto.`];\n }\n\n return [];\n }\n\n}\n\nfunction fieldToNormalizedString(field) {\n const normalizedField = cloneAndNormalizeFieldForComparison(field);\n return fieldToString(normalizedField);\n}\n\nfunction deriveIndividualNormalizedDeletables(record) { // MET-461:\n const encodingLevel = getEncodingLevel(record);\n const recordIsFinished = encodingLevelIsBetterThanPrepublication(encodingLevel);\n const met495 = encodingLevel === '2' && record.fields.some(f => f.tag === '500' && fieldRefersToKoneellisestiTuotettuTietue(f));\n if (!recordIsFinished || met495) {\n return [];\n }\n const relevantFields = record.fields.filter(f => ['245', '246'].includes(f.tag) && fieldHasSubfield(f, 'a'));\n\n return deriveDeletable946s(relevantFields);\n\n function deriveDeletable946s(fields, results = []) {\n const [currField, ...remainingFields] = fields;\n if (currField === undefined) {\n return results;\n }\n\n const fieldAsNormalizedString = fieldToNormalizedString(currField);\n const tmp = fieldAsNormalizedString.replace(/^(?:...) ../u, '946 ##'). // <= Change tag to 946 and indicators to '##'\n replace(' \u2021a ', ' \u2021i nimeke onixissa \u2021a '). // Add $i before $a. NB! This is added in the normalized lower-cased form!\n replace(/(?: \\/)? \u2021c[^\u2021]+$/u, ''); // Remove $c. (Can $c be non-last?)\n const candArray = [tmp, `${tmp} \u20215 MELINDA`].filter(val => val !== fieldAsNormalizedString);\n if (candArray.length) {\n return deriveDeletable946s(remainingFields, [...results, ...candArray]);\n }\n return deriveDeletable946s(remainingFields, results);\n }\n}\n\nexport function removeIndividualInferiorDatafields(record, fix = true) { // No $6 nor $8 in field\n const deletableFieldsAsStrings = deriveIndividualDeletables(record);\n const deletableFieldsAsNormalizedStrings = deriveIndividualNormalizedDeletables(record);\n\n // nvdebug(`Deletables:\\n ${deletableFieldsAsStrings.join('\\n ')}`);\n // nvdebug(`Normalized deletables:\\n ${deletableFieldsAsNormalizedStrings.join('\\n ')}`);\n\n const hits = record.fields.filter(field => isDeletableField(field));\n\n const deletedFieldsAsStrings = hits.map(f => fieldToString(f));\n\n if (fix) {\n hits.forEach(field => {\n //nvdebug(`Remove inferior field: ${fieldToString(field)}`, debug);\n record.removeField(field);\n });\n }\n\n return deletedFieldsAsStrings;\n\n function isDeletableField(field) {\n const fieldAsString = fieldToString(field);\n if (deletableFieldsAsStrings.includes(fieldAsString)) {\n return true;\n }\n const fieldAsNormalizedString = fieldToNormalizedString(field);\n if (deletableFieldsAsNormalizedStrings.includes(fieldAsNormalizedString)) {\n return true;\n }\n\n return false;\n }\n}\n\n\nexport function removeInferiorDatafields(record, fix = true) {\n const removables = removeIndividualInferiorDatafields(record, fix); // Lone fields\n //const removables8 = removeDuplicateSubfield8Chains(record, fix); // Lone subfield $8 chains\n const removables6 = removeInferiorChains(record, fix); // Lone subfield $6 chains\n // HOW TO HANDLE $6+$8 combos? Skipping is relatively OK.\n\n nvdebug(`REMOVABLES:\\n ${removables.join('\\n ')}`, debug);\n nvdebug(`REMOVABLES 6:\\n ${removables6.join('\\n ')}`, debug);\n\n const removablesAll = removables.concat(removables6); //.concat(removables8);\n\n return removablesAll;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["fields", "record", "inferiorTerms"]
7
7
  }
@@ -45,9 +45,9 @@ export default function(defaultTagPattern) {
45
45
  return res;
46
46
  }
47
47
  }
48
- const sortOrderForX00 = ["i", "a", "b", "q", "c", "d", "e", "t", "u", "l", "f", "x", "y", "z", "0", "5", "9"];
49
- const sortOrderForX10 = ["i", "a", "b", "t", "n", "c", "e", "v", "w", "x", "y", "z", "0", "5", "9"];
50
- const sortOrderForX11 = ["a", "n", "d", "c", "e", "g", "j", "0", "5", "9"];
48
+ const sortOrderForX00 = ["i", "a", "b", "q", "c", "d", "e", "t", "u", "l", "f", "x", "y", "z", "0", "1", "5", "9"];
49
+ const sortOrderForX10 = ["i", "a", "b", "t", "n", "c", "e", "v", "w", "x", "y", "z", "0", "1", "5", "9"];
50
+ const sortOrderForX11 = ["a", "n", "d", "c", "e", "g", "j", "0", "1", "5", "9"];
51
51
  const sortOrderFor7XX = ["8", "7", "i", "a", "s", "t", "b", "c", "d", "m", "h", "k", "o", "x", "z", "g", "q", "w"];
52
52
  const sortOrderFor246 = ["i", "a", "b", "n", "p", "f", "5", "9"];
53
53
  const subfieldSortOrder = [
@@ -67,7 +67,7 @@ const subfieldSortOrder = [
67
67
  { "tag": "245", "sortOrder": ["a", "b", "n", "p", "k", "f", "c"] },
68
68
  { "tag": "246", "sortOrder": sortOrderFor246 },
69
69
  { "tag": "382", "sortOrder": ["a"] },
70
- { "tag": "385", "sortOrder": ["8", "m", "n", "a", "2", "0"] },
70
+ { "tag": "385", "sortOrder": ["8", "m", "n", "a", "2", "0", "1"] },
71
71
  { "tag": "386", "sortOrder": ["8", "m", "n", "a"] },
72
72
  { "tag": "490", "sortOrder": ["a", "x", "y", "v", "l"] },
73
73
  { "tag": "505", "sortOrder": ["a"] },
@@ -169,7 +169,7 @@ export function sortAdjacentSubfields(field, externalSortOrder = []) {
169
169
  }
170
170
  moveSubfield6ToTheFront(field);
171
171
  const finnishWay = twoBeforeZero(field);
172
- const controlSubfieldOrder = finnishWay ? ["8", "7", "3", "a", "4", "2", "0", "1", "5", "9"] : ["8", "7", "3", "a", "4", "0", "2", "1", "5", "9"];
172
+ const controlSubfieldOrder = finnishWay ? ["8", "3", "a", "4", "2", "0", "1", "7", "5", "9"] : ["8", "7", "3", "a", "4", "0", "1", "2", "7", "5", "9"];
173
173
  swapSubfields(field, controlSubfieldOrder);
174
174
  const sortOrderForField = externalSortOrder.length > 0 ? externalSortOrder : getSubfieldSortOrder(field);
175
175
  const defaultSortOrder = finnishWay ? defaultSortOrderFinns : defaultSortOrderOthers;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/sortSubfields.js"],
4
- "sourcesContent": ["// Author(s): Nicholas Volk\n\nimport clone from 'clone';\nimport createDebugLogger from 'debug';\nimport {fieldToString, nvdebug} from './utils.js';\n\nconst debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:sortSubfields');\n//const debugData = debug.extend('data');\nconst debugDev = debug.extend('dev');\n\nconst defaultSortOrderStringFinns = '8673abcdefghijklmnopqrstuvwxyz420159'; // NB! We Finns like $2 before $0 in 6XX...\nconst defaultSortOrderStringOthers = '8673abcdefghijklmnopqrstuvwxyz402159';\n\nconst defaultSortOrderFinns = defaultSortOrderStringFinns.split('');\nconst defaultSortOrderOthers = defaultSortOrderStringOthers.split('');\n\n\nexport default function (defaultTagPattern) {\n\n return {\n description: 'Swap adjacent subfields',\n validate, fix\n };\n\n function getRelevantFields(record, tagPattern) {\n const datafields = record.fields.filter(f => f.subfields);\n if (!tagPattern) {\n return datafields;\n }\n\n const regexp = new RegExp(tagPattern, 'u');\n return datafields.filter(f => regexp.test(f.tag));\n }\n\n function fix(record, tagPattern = defaultTagPattern) {\n const res = {message: [], fix: [], valid: true};\n\n const relevantFields = getRelevantFields(record, tagPattern);\n\n relevantFields.forEach(field => {\n sortAdjacentSubfields(field);\n });\n\n return res;\n }\n\n function validate(record, tagPattern = defaultTagPattern) {\n const res = {message: []};\n\n const relevantFields = getRelevantFields(record, tagPattern);\n\n relevantFields.forEach(field => {\n const clonedField = clone(field);\n sortAdjacentSubfields(clonedField);\n const clonedFieldAsString = fieldToString(clonedField);\n const fieldAsString = fieldToString(field);\n if (fieldAsString !== clonedFieldAsString) {\n res.message.push(clonedFieldAsString);\n }\n });\n\n res.valid = !(res.message.length >= 1);\n return res;\n }\n}\n\n\n// X00, X10, X11 and X130 could also for their own sets...\n// (ouch! sometimes $c comes after $d...): LoC: 100 0# \u2021a Black Foot, \u2021c Chief, \u2021d d. 1877 \u2021c (Spirit)\nconst sortOrderForX00 = ['i', 'a', 'b', 'q', 'c', 'd', 'e', 't', 'u', 'l', 'f', 'x', 'y', 'z', '0', '5', '9']; // skip $g. Can't remember why, though...\nconst sortOrderForX10 = ['i', 'a', 'b', 't', 'n', 'c', 'e', 'v', 'w', 'x', 'y', 'z', '0', '5', '9']; // somewhat iffy\nconst sortOrderForX11 = ['a', 'n', 'd', 'c', 'e', 'g', 'j', '0', '5', '9'];\nconst sortOrderFor7XX = ['8', '7', 'i', 'a', 's', 't', 'b', 'c', 'd', 'm', 'h', 'k', 'o', 'x', 'z', 'g', 'q', 'w'];\nconst sortOrderFor246 = ['i', 'a', 'b', 'n', 'p', 'f', '5', '9']; // Used by field 946 as well\n\n// List *only* exceptional order here. Otherwise default order is used.\nconst subfieldSortOrder = [\n {'tag': '017', 'sortOrder': ['i', 'a', 'b', 'd']},\n {'tag': '028', 'sortOrder': ['b', 'a', 'q']}, // National convention\n //{'tag': '031', 'sortOrder': ['a', 'b', 'c', 'm', 'e', 'd']}, // utter guesswork\n {'tag': '040', 'sortOrder': ['8', 'a', 'b', 'e', 'c', 'd', 'x']},\n {'tag': '041', 'sortOrder': ['8', 'a', 'd', 'j', 'p', 'h', 'e', 'g', 'm']}, // guesswork\n {'tag': '048', 'sortOrder': ['8', 'b', 'a']},\n {'tag': '100', 'sortOrder': sortOrderForX00},\n {'tag': '110', 'sortOrder': sortOrderForX10},\n {'tag': '111', 'sortOrder': sortOrderForX11},\n {'tag': '130', 'sortOrder': ['a', 'n', 'p', 'k', 'l']},\n {'tag': '240', 'sortOrder': ['a', 'm', 'n', 'p', 's', 'l', '2', '0', '1', '5', '9']},\n {'tag': '245', 'sortOrder': ['a', 'b', 'n', 'p', 'k', 'f', 'c']},\n {'tag': '246', 'sortOrder': sortOrderFor246},\n {'tag': '382', 'sortOrder': ['a']},\n {'tag': '385', 'sortOrder': ['8', 'm', 'n', 'a', '2', '0']},\n {'tag': '386', 'sortOrder': ['8', 'm', 'n', 'a']},\n {'tag': '490', 'sortOrder': ['a', 'x', 'y', 'v', 'l']},\n {'tag': '505', 'sortOrder': ['a']},\n {'tag': '526', 'sortOrder': ['i', 'a', 'b', 'x', 'z']},\n {'tag': '534', 'sortOrder': ['3', 'p', 'a', 't', 'l', 'c', 'f', 'b', 'e', 'o', 'x', 'z']},\n {'tag': '540', 'sortOrder': ['a', 'b', 'c', 'd', 'f', '2', 'u']},\n {'tag': '600', 'sortOrder': sortOrderForX00},\n {'tag': '610', 'sortOrder': sortOrderForX10},\n {'tag': '611', 'sortOrder': sortOrderForX11},\n {'tag': '650', 'sortOrder': ['a', 'x', 'y', 'z']},\n {'tag': '700', 'sortOrder': sortOrderForX00},\n {'tag': '710', 'sortOrder': sortOrderForX10},\n {'tag': '711', 'sortOrder': sortOrderForX11},\n {'tag': '760', 'sortOrder': sortOrderFor7XX},\n {'tag': '762', 'sortOrder': sortOrderFor7XX},\n {'tag': '765', 'sortOrder': sortOrderFor7XX},\n {'tag': '767', 'sortOrder': sortOrderFor7XX},\n {'tag': '770', 'sortOrder': sortOrderFor7XX},\n {'tag': '772', 'sortOrder': sortOrderFor7XX},\n {'tag': '773', 'sortOrder': sortOrderFor7XX},\n {'tag': '774', 'sortOrder': sortOrderFor7XX},\n {'tag': '775', 'sortOrder': sortOrderFor7XX},\n {'tag': '776', 'sortOrder': sortOrderFor7XX},\n {'tag': '777', 'sortOrder': sortOrderFor7XX},\n {'tag': '780', 'sortOrder': sortOrderFor7XX},\n {'tag': '785', 'sortOrder': sortOrderFor7XX},\n {'tag': '786', 'sortOrder': sortOrderFor7XX},\n {'tag': '787', 'sortOrder': sortOrderFor7XX},\n {'tag': '788', 'sortOrder': sortOrderFor7XX},\n {'tag': '800', 'sortOrder': sortOrderForX00},\n {'tag': '810', 'sortOrder': sortOrderForX10},\n {'tag': '811', 'sortOrder': sortOrderForX11},\n {'tag': '830', 'sortOrder': ['a', 'n', 'x', 'v']}, // INCOMPLETE, SAME AS 490? APPARENTLY NOT...\n {'tag': '856', 'sortOrder': ['3', 'u', 'q', 'x', 'y', 'z', '5']}, // incomplete, LoC examples are inconclusive\n {'tag': '880', 'sortOrder': ['a']},\n {'tag': '946', 'sortOrder': sortOrderFor246},\n {'tag': 'LOW', 'sortOrder': ['a', 'b', 'c', 'l', 'h']},\n {'tag': 'SID', 'sortOrder': ['c', 'b']} // Hack, so that default order is not used\n];\n\nfunction getSubfieldSortOrder(field) {\n const entry = subfieldSortOrder.filter(currEntry => field.tag === currEntry.tag);\n if (entry.length > 0 && 'sortOrder' in entry[0]) {\n debugDev(`sort order for ${field.tag}: ${entry[0].sortOrder}`);\n return entry[0].sortOrder;\n }\n nvdebug(`WARNING!\\tNo subfield order found for ${field.tag}.`);\n return [];\n}\n\n\nfunction swapSubfields(field, sortOrder) {\n if (!field.subfields) {\n return;\n }\n\n const loopAgain = field.subfields.some((sf, index) => {\n if (index === 0) {\n return false;\n }\n const currPos = getPosition(sf, sortOrder);\n const prevPos = getPosition(field.subfields[index - 1], sortOrder);\n if (currPos === -1 || prevPos === -1 || currPos >= prevPos) {\n return false;\n }\n // Swap:\n const tmp = field.subfields[index - 1];\n field.subfields[index - 1] = sf;\n field.subfields[index] = tmp;\n return true;\n });\n\n if (loopAgain) {\n return swapSubfields(field, sortOrder);\n }\n\n return;\n\n function getPosition(subfield, sortOrder) {\n // Magic exception that *always* comes first, used by Aleph in linking overlong fields\n if (sortOrder.indexOf('9') > -1 && subfield.code === '9' && ['^', '^^'].includes(subfield.value)) {\n return -0.5; // normal \"best value\" is 0, and \"worst value\" is N\n }\n return sortOrder.indexOf(subfield.code);\n }\n}\n\n\nfunction twoBeforeZero(field) {\n const sf2 = field.subfields.filter(sf => sf.code === '2');\n if (sf2.length !== 1) {\n return true; // both true and false are ok here\n }\n // MRA-465: gcipplatform (field 753)\n // rdasco (344), creatorbio (353), gbd (668), lsch (eg. 385)\n if (['creatorbio', 'gbd', 'gcipplatform', 'lscsh', 'rdasco'].includes(sf2[0].value)) {\n return false;\n }\n return true;\n}\n\nfunction moveSubfield6ToTheFront(field) {\n // https://www.loc.gov/marc/bibliographic/ecbdcntf.html says \"Subfield $6 is always the first subfield in the field.\"\n const sf6s = field.subfields.filter(sf => sf.code === '6');\n const others = field.subfields.filter(sf => sf.code !== '6');\n field.subfields = [...sf6s, ...others];\n}\n\nexport function sortAdjacentSubfields(field, externalSortOrder = []) {\n if (!field.subfields) {\n return field;\n }\n\n moveSubfield6ToTheFront(field); // specs: \"Subfield $6 is always the first subfield in the field.\"\n\n // Features:\n // - Swap only sort adjacent pairs.\n // - No sorting over unlisted subfield codes. Thus a given subfield can not shift to wrong side of 700$t...\n\n // Implement: 880 field should use values from $6...\n\n // Should we support multiple sort orders per field?\n\n // Try to handle control subfield order. This is not 100% fool proof. Control subfields are pretty stable, though.\n // However, there are exceptions (eg. $9 ^^ comes first and $2 $0 is a Finnish convention...)\n\n\n const finnishWay = twoBeforeZero(field);\n const controlSubfieldOrder = finnishWay ? ['8', '7', '3', 'a', '4', '2', '0', '1', '5', '9'] : ['8', '7', '3', 'a', '4', '0', '2', '1', '5', '9'];\n swapSubfields(field, controlSubfieldOrder);\n\n const sortOrderForField = externalSortOrder.length > 0 ? externalSortOrder : getSubfieldSortOrder(field);\n //nvdebug(`INTERMEDIATE SUBFIELD ORDER FOR ${field.tag}: ${sortOrderForField.join(', ')}`);\n\n const defaultSortOrder = finnishWay ? defaultSortOrderFinns : defaultSortOrderOthers; // $2 vs $0\n const subfieldOrder = sortOrderForField.length > 0 ? sortOrderForField : defaultSortOrder;\n //nvdebug(`FINAL SUBFIELD ORDER (FINNISH=${finnishWay}) FOR ${field.tag}: ${subfieldOrder.join(', ')}`);\n //if (sortOrder === null) { return field; } //// Currently always sort..\n //nvdebug(`IN: ${fieldToString(field)}`);\n swapSubfields(field, subfieldOrder);\n //nvdebug(`OUT: ${fieldToString(field)}`);\n\n return field;\n}\n\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["// Author(s): Nicholas Volk\n\nimport clone from 'clone';\nimport createDebugLogger from 'debug';\nimport {fieldToString, nvdebug} from './utils.js';\n\nconst debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:sortSubfields');\n//const debugData = debug.extend('data');\nconst debugDev = debug.extend('dev');\n\nconst defaultSortOrderStringFinns = '8673abcdefghijklmnopqrstuvwxyz420159'; // NB! We Finns like $2 before $0 in 6XX...\nconst defaultSortOrderStringOthers = '8673abcdefghijklmnopqrstuvwxyz402159';\n\nconst defaultSortOrderFinns = defaultSortOrderStringFinns.split('');\nconst defaultSortOrderOthers = defaultSortOrderStringOthers.split('');\n\n\nexport default function (defaultTagPattern) {\n\n return {\n description: 'Swap adjacent subfields',\n validate, fix\n };\n\n function getRelevantFields(record, tagPattern) {\n const datafields = record.fields.filter(f => f.subfields);\n if (!tagPattern) {\n return datafields;\n }\n\n const regexp = new RegExp(tagPattern, 'u');\n return datafields.filter(f => regexp.test(f.tag));\n }\n\n function fix(record, tagPattern = defaultTagPattern) {\n const res = {message: [], fix: [], valid: true};\n\n const relevantFields = getRelevantFields(record, tagPattern);\n\n relevantFields.forEach(field => {\n sortAdjacentSubfields(field);\n });\n\n return res;\n }\n\n function validate(record, tagPattern = defaultTagPattern) {\n const res = {message: []};\n\n const relevantFields = getRelevantFields(record, tagPattern);\n\n relevantFields.forEach(field => {\n const clonedField = clone(field);\n sortAdjacentSubfields(clonedField);\n const clonedFieldAsString = fieldToString(clonedField);\n const fieldAsString = fieldToString(field);\n if (fieldAsString !== clonedFieldAsString) {\n res.message.push(clonedFieldAsString);\n }\n });\n\n res.valid = !(res.message.length >= 1);\n return res;\n }\n}\n\n\n// X00, X10, X11 and X130 could also for their own sets...\n// (ouch! sometimes $c comes after $d...): LoC: 100 0# \u2021a Black Foot, \u2021c Chief, \u2021d d. 1877 \u2021c (Spirit)\nconst 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...\nconst sortOrderForX10 = ['i', 'a', 'b', 't', 'n', 'c', 'e', 'v', 'w', 'x', 'y', 'z', '0', '1', '5', '9']; // somewhat iffy\nconst sortOrderForX11 = ['a', 'n', 'd', 'c', 'e', 'g', 'j', '0', '1', '5', '9'];\nconst sortOrderFor7XX = ['8', '7', 'i', 'a', 's', 't', 'b', 'c', 'd', 'm', 'h', 'k', 'o', 'x', 'z', 'g', 'q', 'w'];\nconst sortOrderFor246 = ['i', 'a', 'b', 'n', 'p', 'f', '5', '9']; // Used by field 946 as well\n\n// List *only* exceptional order here. Otherwise default order is used.\nconst subfieldSortOrder = [\n {'tag': '017', 'sortOrder': ['i', 'a', 'b', 'd']},\n {'tag': '028', 'sortOrder': ['b', 'a', 'q']}, // National convention\n //{'tag': '031', 'sortOrder': ['a', 'b', 'c', 'm', 'e', 'd']}, // utter guesswork\n {'tag': '040', 'sortOrder': ['8', 'a', 'b', 'e', 'c', 'd', 'x']},\n {'tag': '041', 'sortOrder': ['8', 'a', 'd', 'j', 'p', 'h', 'e', 'g', 'm']}, // guesswork\n {'tag': '048', 'sortOrder': ['8', 'b', 'a']},\n {'tag': '100', 'sortOrder': sortOrderForX00},\n {'tag': '110', 'sortOrder': sortOrderForX10},\n {'tag': '111', 'sortOrder': sortOrderForX11},\n {'tag': '130', 'sortOrder': ['a', 'n', 'p', 'k', 'l']},\n {'tag': '240', 'sortOrder': ['a', 'm', 'n', 'p', 's', 'l', '2', '0', '1', '5', '9']},\n {'tag': '245', 'sortOrder': ['a', 'b', 'n', 'p', 'k', 'f', 'c']},\n {'tag': '246', 'sortOrder': sortOrderFor246},\n {'tag': '382', 'sortOrder': ['a']},\n {'tag': '385', 'sortOrder': ['8', 'm', 'n', 'a', '2', '0', '1']},\n {'tag': '386', 'sortOrder': ['8', 'm', 'n', 'a']},\n {'tag': '490', 'sortOrder': ['a', 'x', 'y', 'v', 'l']},\n {'tag': '505', 'sortOrder': ['a']},\n {'tag': '526', 'sortOrder': ['i', 'a', 'b', 'x', 'z']},\n {'tag': '534', 'sortOrder': ['3', 'p', 'a', 't', 'l', 'c', 'f', 'b', 'e', 'o', 'x', 'z']},\n {'tag': '540', 'sortOrder': ['a', 'b', 'c', 'd', 'f', '2', 'u']},\n {'tag': '600', 'sortOrder': sortOrderForX00},\n {'tag': '610', 'sortOrder': sortOrderForX10},\n {'tag': '611', 'sortOrder': sortOrderForX11},\n {'tag': '650', 'sortOrder': ['a', 'x', 'y', 'z']},\n {'tag': '700', 'sortOrder': sortOrderForX00},\n {'tag': '710', 'sortOrder': sortOrderForX10},\n {'tag': '711', 'sortOrder': sortOrderForX11},\n {'tag': '760', 'sortOrder': sortOrderFor7XX},\n {'tag': '762', 'sortOrder': sortOrderFor7XX},\n {'tag': '765', 'sortOrder': sortOrderFor7XX},\n {'tag': '767', 'sortOrder': sortOrderFor7XX},\n {'tag': '770', 'sortOrder': sortOrderFor7XX},\n {'tag': '772', 'sortOrder': sortOrderFor7XX},\n {'tag': '773', 'sortOrder': sortOrderFor7XX},\n {'tag': '774', 'sortOrder': sortOrderFor7XX},\n {'tag': '775', 'sortOrder': sortOrderFor7XX},\n {'tag': '776', 'sortOrder': sortOrderFor7XX},\n {'tag': '777', 'sortOrder': sortOrderFor7XX},\n {'tag': '780', 'sortOrder': sortOrderFor7XX},\n {'tag': '785', 'sortOrder': sortOrderFor7XX},\n {'tag': '786', 'sortOrder': sortOrderFor7XX},\n {'tag': '787', 'sortOrder': sortOrderFor7XX},\n {'tag': '788', 'sortOrder': sortOrderFor7XX},\n {'tag': '800', 'sortOrder': sortOrderForX00},\n {'tag': '810', 'sortOrder': sortOrderForX10},\n {'tag': '811', 'sortOrder': sortOrderForX11},\n {'tag': '830', 'sortOrder': ['a', 'n', 'x', 'v']}, // INCOMPLETE, SAME AS 490? APPARENTLY NOT...\n {'tag': '856', 'sortOrder': ['3', 'u', 'q', 'x', 'y', 'z', '5']}, // incomplete, LoC examples are inconclusive\n {'tag': '880', 'sortOrder': ['a']},\n {'tag': '946', 'sortOrder': sortOrderFor246},\n {'tag': 'LOW', 'sortOrder': ['a', 'b', 'c', 'l', 'h']},\n {'tag': 'SID', 'sortOrder': ['c', 'b']} // Hack, so that default order is not used\n];\n\nfunction getSubfieldSortOrder(field) {\n const entry = subfieldSortOrder.filter(currEntry => field.tag === currEntry.tag);\n if (entry.length > 0 && 'sortOrder' in entry[0]) {\n debugDev(`sort order for ${field.tag}: ${entry[0].sortOrder}`);\n return entry[0].sortOrder;\n }\n nvdebug(`WARNING!\\tNo subfield order found for ${field.tag}.`);\n return [];\n}\n\n\nfunction swapSubfields(field, sortOrder) {\n if (!field.subfields) {\n return;\n }\n\n const loopAgain = field.subfields.some((sf, index) => {\n if (index === 0) {\n return false;\n }\n const currPos = getPosition(sf, sortOrder);\n const prevPos = getPosition(field.subfields[index - 1], sortOrder);\n if (currPos === -1 || prevPos === -1 || currPos >= prevPos) {\n return false;\n }\n // Swap:\n const tmp = field.subfields[index - 1];\n field.subfields[index - 1] = sf;\n field.subfields[index] = tmp;\n return true;\n });\n\n if (loopAgain) {\n return swapSubfields(field, sortOrder);\n }\n\n return;\n\n function getPosition(subfield, sortOrder) {\n // Magic exception that *always* comes first, used by Aleph in linking overlong fields\n if (sortOrder.indexOf('9') > -1 && subfield.code === '9' && ['^', '^^'].includes(subfield.value)) {\n return -0.5; // normal \"best value\" is 0, and \"worst value\" is N\n }\n return sortOrder.indexOf(subfield.code);\n }\n}\n\n\nfunction twoBeforeZero(field) {\n const sf2 = field.subfields.filter(sf => sf.code === '2');\n if (sf2.length !== 1) {\n return true; // both true and false are ok here\n }\n // MRA-465: gcipplatform (field 753)\n // rdasco (344), creatorbio (353), gbd (668), lsch (eg. 385)\n if (['creatorbio', 'gbd', 'gcipplatform', 'lscsh', 'rdasco'].includes(sf2[0].value)) {\n return false;\n }\n return true;\n}\n\nfunction moveSubfield6ToTheFront(field) {\n // https://www.loc.gov/marc/bibliographic/ecbdcntf.html says \"Subfield $6 is always the first subfield in the field.\"\n const sf6s = field.subfields.filter(sf => sf.code === '6');\n const others = field.subfields.filter(sf => sf.code !== '6');\n field.subfields = [...sf6s, ...others];\n}\n\nexport function sortAdjacentSubfields(field, externalSortOrder = []) {\n if (!field.subfields) {\n return field;\n }\n\n moveSubfield6ToTheFront(field); // specs: \"Subfield $6 is always the first subfield in the field.\"\n\n // Features:\n // - Swap only sort adjacent pairs.\n // - No sorting over unlisted subfield codes. Thus a given subfield can not shift to wrong side of 700$t...\n\n // Implement: 880 field should use values from $6...\n\n // Should we support multiple sort orders per field?\n\n // Try to handle control subfield order. This is not 100% fool proof. Control subfields are pretty stable, though.\n // However, there are exceptions (eg. $9 ^^ comes first and $2 $0 is a Finnish convention...)\n\n\n const finnishWay = twoBeforeZero(field);\n\n // Note: 760-789: '7' comes way earlier (after '6' and '8')\n const controlSubfieldOrder = finnishWay ? ['8', '3', 'a', '4', '2', '0', '1', '7', '5', '9'] : ['8', '7', '3', 'a', '4', '0', '1', '2', '7', '5', '9'];\n swapSubfields(field, controlSubfieldOrder);\n\n const sortOrderForField = externalSortOrder.length > 0 ? externalSortOrder : getSubfieldSortOrder(field);\n //nvdebug(`INTERMEDIATE SUBFIELD ORDER FOR ${field.tag}: ${sortOrderForField.join(', ')}`);\n\n const defaultSortOrder = finnishWay ? defaultSortOrderFinns : defaultSortOrderOthers; // $2 vs $0\n const subfieldOrder = sortOrderForField.length > 0 ? sortOrderForField : defaultSortOrder;\n //nvdebug(`FINAL SUBFIELD ORDER (FINNISH=${finnishWay}) FOR ${field.tag}: ${subfieldOrder.join(', ')}`);\n //if (sortOrder === null) { return field; } //// Currently always sort..\n //nvdebug(`IN: ${fieldToString(field)}`);\n swapSubfields(field, subfieldOrder);\n //nvdebug(`OUT: ${fieldToString(field)}`);\n\n return field;\n}\n\n"],
5
+ "mappings": "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",
6
6
  "names": ["sortOrder"]
7
7
  }
package/dist/utils.js CHANGED
@@ -171,4 +171,10 @@ export function hasCopyright(value) {
171
171
  const modValue = removeCopyright(value);
172
172
  return value !== modValue;
173
173
  }
174
+ export function subfieldArraysContainSameData(arr1, arr2) {
175
+ if (!arr1.every((sf) => arr2.some((sf2) => subfieldsAreIdentical(sf, sf2)))) {
176
+ return false;
177
+ }
178
+ return arr2.every((sf2) => arr1.some((sf) => subfieldsAreIdentical(sf, sf2)));
179
+ }
174
180
  //# sourceMappingURL=utils.js.map
package/dist/utils.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/utils.js"],
4
- "sourcesContent": ["import createDebugLogger from 'debug';\n\n//import fs from 'fs';\n//import path from 'path';\n\nconst debug = createDebugLogger('@natlibfi/melinda-marc-record-merge-reducers:utils');\n//const debugData = debug.extend('data');\nconst debugDev = debug.extend('dev');\n\nimport {melindaFieldSpecs} from './melindaCustomMergeFields.js';\n\n//JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'src', 'melindaCustomMergeFields.json'), 'utf8'));\n\nexport function isElectronicMaterial(record) {\n const f337s = record.get('337');\n\n return f337s.length > 0 && f337s.some(f => fieldHasSubfield(f, 'b', 'c') && fieldHasSubfield(f, '2', 'rdamedia'));\n}\n\nexport function nvdebug(message, func = undefined) {\n if (func) {\n func(message);\n }\n //console.info(message); // eslint-disable-line no-console\n}\n\nexport function fieldHasSubfield(field, subfieldCode, subfieldValue = null) {\n if (!field.subfields) {\n return false;\n }\n if (subfieldValue === null) {\n return field.subfields.some(sf => sf.code === subfieldCode);\n }\n return field.subfields.some(sf => sf.code === subfieldCode && subfieldValue === sf.value);\n}\n\nexport function subfieldToString(sf) {\n if (!sf.value) {\n return `\u2021${sf.code}`;\n }\n return `\u2021${sf.code} ${sf.value}`;\n}\n\nfunction normalizeIndicatorValue(val) {\n if (val === ' ') {\n return '#';\n }\n return val;\n}\n\nexport function recordToString(record) {\n const ldr = `LDR ${record.leader}`;\n const fields = record.fields.map(f => fieldToString(f));\n return `${ldr}\\n${fields.join('\\n')}`;\n}\n\nexport function removeSubfield(record, tag, subfieldCode) {\n record.fields = record.fields.map(field => {\n if (field.tag !== tag || !field.subfields) { // Don't procss irrelevant fields\n return field;\n }\n field.subfields = field.subfields.filter(sf => sf.code !== subfieldCode);\n if (field.subfields.length === 0) {\n return false;\n }\n return field;\n }).filter(field => field);\n}\n\nexport function recordRemoveValuelessSubfields(record) {\n record.fields = record.fields.map(field => {\n if (!field.subfields) { // Keep control fields\n return field;\n }\n // Remove empty subfields from datafields:\n field.subfields = field.subfields.filter(sf => sf.value);\n\n if (field.subfields && field.subfields.length === 0) {\n return false; // Return false instead of a field if field has no subfields left. These will soon be filtered out.\n }\n\n return field; //if field has subfields return it\n }).filter(field => field); // Filter those falses out\n}\n\nexport function fieldToString(f) {\n if ('subfields' in f) {\n return `${f.tag} ${normalizeIndicatorValue(f.ind1)}${normalizeIndicatorValue(f.ind2)}${formatSubfields(f)}`;\n }\n return `${f.tag} ${f.value}`;\n\n function formatSubfields(field) {\n return field.subfields.map(sf => ` ${subfieldToString(sf)}`).join('');\n }\n}\n\nexport function fieldsToString(fields) {\n return fields.map(f => fieldToString(f)).join('\\t__SEPARATOR__\\t');\n}\n\nexport function nvdebugFieldArray(fields, prefix = ' ', func = undefined) {\n fields.forEach(field => nvdebug(`${prefix}${fieldToString(field)}`, func));\n}\n\nexport function isControlSubfieldCode(subfieldCode) {\n if (['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'w'].includes(subfieldCode)) {\n return true;\n }\n return false;\n}\n\nexport function getCatalogingLanguage(record, defaultCatalogingLanguage = undefined) {\n const [field040] = record.get(/^040$/u);\n if (!field040) {\n return defaultCatalogingLanguage;\n }\n const [b] = field040.subfields.filter(sf => sf.code === 'b');\n if (!b) {\n return defaultCatalogingLanguage;\n }\n return b.value;\n}\n\n\nexport function uniqArray(arr) {\n return arr.filter((val, i) => arr.indexOf(val) === i);\n}\n\nexport function fieldsAreIdentical(field1, field2) {\n if (field1.tag !== field2.tag) { // NB! We are skipping normalizations here on purpose! They should be done beforehand...\n return false;\n }\n return fieldToString(field1) === fieldToString(field2);\n\n // The order of subfields is relevant! Bloody JS idiotisms make people use conditions such as:\n // return field1.subfields.every(sf => field2.subfields.some(sf2 => sf.code === sf2.code && sf.value === sf2.value));\n}\n\nexport function fieldHasNSubfields(field, subfieldCode/*, subfieldValue = null*/) {\n const relevantSubfields = field.subfields.filter(sf => sf.code === subfieldCode);\n //if (subfieldValue === null) {\n return relevantSubfields.length;\n //}\n //const subset = relevantSubfields.filter(value => value === subfieldValue);\n //return subset.length;\n}\n\nexport function removeCopyright(value) {\n return value.replace(/^(?:c|p|\u00A9|\u2117|Cop\\. ?) ?((?:1[0-9][0-9][0-9]|20[012][0-9])\\.?)$/ui, '$1');\n}\n\nfunction isNonStandardNonrepeatableSubfield(tag, subfieldCode) {\n // Put these into config or so...\n if (tag === '264') {\n return ['a', 'b', 'c'].includes(subfieldCode);\n }\n\n if (['336', '337', '338'].includes(tag)) {\n return ['a', 'b', '2'].includes(subfieldCode);\n }\n\n return false;\n}\n\n\nexport function subfieldIsRepeatable(tag, subfieldCode) {\n\n if (isNonStandardNonrepeatableSubfield(tag, subfieldCode)) {\n return false;\n }\n\n // These we know or \"know\":\n // NB! $5 is (according to MARC21 format) non-repeatable, and not usable in all fields, but Melinda has a local exception to this, see MET-300\n if ('0159'.indexOf(subfieldCode) > -1) {\n // Uh, can $0 appear on any field?\n return true;\n }\n\n const fieldSpecs = melindaFieldSpecs.fields.filter(field => field.tag === tag);\n if (fieldSpecs.length !== 1) {\n nvdebug(` WARNING! Getting field ${tag} data failed! ${fieldSpecs.length} hits. Default value true is used for'${subfieldCode}' .`, debugDev);\n return true;\n }\n\n const subfieldSpecs = fieldSpecs[0].subfields.filter(subfield => subfield.code === subfieldCode);\n // Currently we don't support multiple $6 fields due to re-indexing limitations...\n // Well, $6 is non-repeatable, isn't it?!?\n // (This might actually already be fixed... Marginal issue, but check eventually.)\n if (subfieldSpecs.length !== 1 || subfieldCode === '6') {\n return false; // repeatable if not specified, I guess. Maybe add log or warn?\n }\n return subfieldSpecs[0].repeatable;\n}\n\nfunction marc21GetTagsLegalIndicators(tag) {\n const fieldSpecs = melindaFieldSpecs.fields.filter(field => field.tag === tag);\n if (fieldSpecs.length === 0) {\n return undefined;\n }\n return fieldSpecs[0].indicators;\n}\n\nexport function marc21GetTagsLegalInd1Value(tag) {\n const indicator = marc21GetTagsLegalIndicators(tag);\n if (indicator === undefined) {\n return undefined;\n }\n return indicator.ind1;\n}\n\nexport function marc21GetTagsLegalInd2Value(tag) {\n const indicator = marc21GetTagsLegalIndicators(tag);\n if (indicator === undefined) {\n return undefined;\n }\n return indicator.ind2;\n}\n\nexport function nvdebugSubfieldArray(subfields, prefix = ' ', func = undefined) {\n subfields.forEach(subfield => nvdebug(`${prefix}${subfieldToString(subfield)}`, func));\n}\n\nexport function subfieldsAreIdentical(subfieldA, subfieldB) {\n return subfieldA.code === subfieldB.code && subfieldA.value === subfieldB.value;\n}\n\nexport function fieldHasMultipleSubfields(field, subfieldCode/*, subfieldValue = null*/) {\n return fieldHasNSubfields(field, subfieldCode) > 1;\n}\n\nexport function hasCopyright(value) {\n const modValue = removeCopyright(value);\n return value !== modValue;\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import createDebugLogger from 'debug';\n\n//import fs from 'fs';\n//import path from 'path';\n\nconst debug = createDebugLogger('@natlibfi/melinda-marc-record-merge-reducers:utils');\n//const debugData = debug.extend('data');\nconst debugDev = debug.extend('dev');\n\nimport {melindaFieldSpecs} from './melindaCustomMergeFields.js';\n\n//JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'src', 'melindaCustomMergeFields.json'), 'utf8'));\n\nexport function isElectronicMaterial(record) {\n const f337s = record.get('337');\n\n return f337s.length > 0 && f337s.some(f => fieldHasSubfield(f, 'b', 'c') && fieldHasSubfield(f, '2', 'rdamedia'));\n}\n\nexport function nvdebug(message, func = undefined) {\n if (func) {\n func(message);\n }\n //console.info(message); // eslint-disable-line no-console\n}\n\nexport function fieldHasSubfield(field, subfieldCode, subfieldValue = null) {\n if (!field.subfields) {\n return false;\n }\n if (subfieldValue === null) {\n return field.subfields.some(sf => sf.code === subfieldCode);\n }\n return field.subfields.some(sf => sf.code === subfieldCode && subfieldValue === sf.value);\n}\n\nexport function subfieldToString(sf) {\n if (!sf.value) {\n return `\u2021${sf.code}`;\n }\n return `\u2021${sf.code} ${sf.value}`;\n}\n\nfunction normalizeIndicatorValue(val) {\n if (val === ' ') {\n return '#';\n }\n return val;\n}\n\nexport function recordToString(record) {\n const ldr = `LDR ${record.leader}`;\n const fields = record.fields.map(f => fieldToString(f));\n return `${ldr}\\n${fields.join('\\n')}`;\n}\n\nexport function removeSubfield(record, tag, subfieldCode) {\n record.fields = record.fields.map(field => {\n if (field.tag !== tag || !field.subfields) { // Don't procss irrelevant fields\n return field;\n }\n field.subfields = field.subfields.filter(sf => sf.code !== subfieldCode);\n if (field.subfields.length === 0) {\n return false;\n }\n return field;\n }).filter(field => field);\n}\n\nexport function recordRemoveValuelessSubfields(record) {\n record.fields = record.fields.map(field => {\n if (!field.subfields) { // Keep control fields\n return field;\n }\n // Remove empty subfields from datafields:\n field.subfields = field.subfields.filter(sf => sf.value);\n\n if (field.subfields && field.subfields.length === 0) {\n return false; // Return false instead of a field if field has no subfields left. These will soon be filtered out.\n }\n\n return field; //if field has subfields return it\n }).filter(field => field); // Filter those falses out\n}\n\nexport function fieldToString(f) {\n if ('subfields' in f) {\n return `${f.tag} ${normalizeIndicatorValue(f.ind1)}${normalizeIndicatorValue(f.ind2)}${formatSubfields(f)}`;\n }\n return `${f.tag} ${f.value}`;\n\n function formatSubfields(field) {\n return field.subfields.map(sf => ` ${subfieldToString(sf)}`).join('');\n }\n}\n\nexport function fieldsToString(fields) {\n return fields.map(f => fieldToString(f)).join('\\t__SEPARATOR__\\t');\n}\n\nexport function nvdebugFieldArray(fields, prefix = ' ', func = undefined) {\n fields.forEach(field => nvdebug(`${prefix}${fieldToString(field)}`, func));\n}\n\nexport function isControlSubfieldCode(subfieldCode) {\n if (['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'w'].includes(subfieldCode)) {\n return true;\n }\n return false;\n}\n\nexport function getCatalogingLanguage(record, defaultCatalogingLanguage = undefined) {\n const [field040] = record.get(/^040$/u);\n if (!field040) {\n return defaultCatalogingLanguage;\n }\n const [b] = field040.subfields.filter(sf => sf.code === 'b');\n if (!b) {\n return defaultCatalogingLanguage;\n }\n return b.value;\n}\n\n\nexport function uniqArray(arr) {\n return arr.filter((val, i) => arr.indexOf(val) === i);\n}\n\nexport function fieldsAreIdentical(field1, field2) {\n if (field1.tag !== field2.tag) { // NB! We are skipping normalizations here on purpose! They should be done beforehand...\n return false;\n }\n return fieldToString(field1) === fieldToString(field2);\n\n // The order of subfields is relevant! Bloody JS idiotisms make people use conditions such as:\n // return field1.subfields.every(sf => field2.subfields.some(sf2 => sf.code === sf2.code && sf.value === sf2.value));\n}\n\nexport function fieldHasNSubfields(field, subfieldCode/*, subfieldValue = null*/) {\n const relevantSubfields = field.subfields.filter(sf => sf.code === subfieldCode);\n //if (subfieldValue === null) {\n return relevantSubfields.length;\n //}\n //const subset = relevantSubfields.filter(value => value === subfieldValue);\n //return subset.length;\n}\n\nexport function removeCopyright(value) {\n return value.replace(/^(?:c|p|\u00A9|\u2117|Cop\\. ?) ?((?:1[0-9][0-9][0-9]|20[012][0-9])\\.?)$/ui, '$1');\n}\n\nfunction isNonStandardNonrepeatableSubfield(tag, subfieldCode) {\n // Put these into config or so...\n if (tag === '264') {\n return ['a', 'b', 'c'].includes(subfieldCode);\n }\n\n if (['336', '337', '338'].includes(tag)) {\n return ['a', 'b', '2'].includes(subfieldCode);\n }\n\n return false;\n}\n\n\nexport function subfieldIsRepeatable(tag, subfieldCode) {\n\n if (isNonStandardNonrepeatableSubfield(tag, subfieldCode)) {\n return false;\n }\n\n // These we know or \"know\":\n // NB! $5 is (according to MARC21 format) non-repeatable, and not usable in all fields, but Melinda has a local exception to this, see MET-300\n if ('0159'.indexOf(subfieldCode) > -1) {\n // Uh, can $0 appear on any field?\n return true;\n }\n\n const fieldSpecs = melindaFieldSpecs.fields.filter(field => field.tag === tag);\n if (fieldSpecs.length !== 1) {\n nvdebug(` WARNING! Getting field ${tag} data failed! ${fieldSpecs.length} hits. Default value true is used for'${subfieldCode}' .`, debugDev);\n return true;\n }\n\n const subfieldSpecs = fieldSpecs[0].subfields.filter(subfield => subfield.code === subfieldCode);\n // Currently we don't support multiple $6 fields due to re-indexing limitations...\n // Well, $6 is non-repeatable, isn't it?!?\n // (This might actually already be fixed... Marginal issue, but check eventually.)\n if (subfieldSpecs.length !== 1 || subfieldCode === '6') {\n return false; // repeatable if not specified, I guess. Maybe add log or warn?\n }\n return subfieldSpecs[0].repeatable;\n}\n\nfunction marc21GetTagsLegalIndicators(tag) {\n const fieldSpecs = melindaFieldSpecs.fields.filter(field => field.tag === tag);\n if (fieldSpecs.length === 0) {\n return undefined;\n }\n return fieldSpecs[0].indicators;\n}\n\nexport function marc21GetTagsLegalInd1Value(tag) {\n const indicator = marc21GetTagsLegalIndicators(tag);\n if (indicator === undefined) {\n return undefined;\n }\n return indicator.ind1;\n}\n\nexport function marc21GetTagsLegalInd2Value(tag) {\n const indicator = marc21GetTagsLegalIndicators(tag);\n if (indicator === undefined) {\n return undefined;\n }\n return indicator.ind2;\n}\n\nexport function nvdebugSubfieldArray(subfields, prefix = ' ', func = undefined) {\n subfields.forEach(subfield => nvdebug(`${prefix}${subfieldToString(subfield)}`, func));\n}\n\nexport function subfieldsAreIdentical(subfieldA, subfieldB) {\n return subfieldA.code === subfieldB.code && subfieldA.value === subfieldB.value;\n}\n\nexport function fieldHasMultipleSubfields(field, subfieldCode/*, subfieldValue = null*/) {\n return fieldHasNSubfields(field, subfieldCode) > 1;\n}\n\nexport function hasCopyright(value) {\n const modValue = removeCopyright(value);\n return value !== modValue;\n}\n\n\n\nexport function subfieldArraysContainSameData(arr1, arr2) {\n if ( !arr1.every(sf => arr2.some(sf2 => subfieldsAreIdentical(sf, sf2))) ) {\n return false;\n }\n\n return arr2.every(sf2 => arr1.some(sf => subfieldsAreIdentical(sf, sf2)));\n}"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -15,7 +15,7 @@
15
15
  "url": "git@github.com:natlibfi/marc-record-validators-melinda.git"
16
16
  },
17
17
  "license": "MIT",
18
- "version": "12.0.0-alpha.4",
18
+ "version": "12.0.0-alpha.5",
19
19
  "main": "./dist/index.js",
20
20
  "publishConfig": {
21
21
  "access": "public"
@@ -45,7 +45,7 @@
45
45
  "cld3-asm": "^4.0.0",
46
46
  "clone": "^2.1.2",
47
47
  "debug": "^4.4.3",
48
- "isbn3": "^1.2.13",
48
+ "isbn3": "^1.2.15",
49
49
  "langs": "^2.0.0",
50
50
  "xml2js": "^0.6.2",
51
51
  "xregexp": "^5.1.2"
@@ -11,6 +11,7 @@ import {controlSubfieldsPermitMerge} from './controlSubfields.js';
11
11
  import {mergableIndicator1, mergableIndicator2} from './mergableIndicator.js';
12
12
  import {partsAgree} from '../normalizeSubfieldValueForComparison.js';
13
13
  import {normalizeForSamenessCheck, valueCarriesMeaning} from './worldKnowledge.js';
14
+ import { provenanceSubfieldsPermitMerge } from './dataProvenance.js';
14
15
 
15
16
  const debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:mergeField:counterpart');
16
17
  //const debugData = debug.extend('data');
@@ -375,6 +376,11 @@ function syntacticallyMergablePair(baseField, sourceField, config) {
375
376
  return false;
376
377
  }
377
378
 
379
+ if (!provenanceSubfieldsPermitMerge(baseField, sourceField)) {
380
+ nvdebug('non-mergable (reason: data provenance subfield)', debugDev);
381
+ return false;
382
+ }
383
+
378
384
  // NB! field1.tag and field2.tag might differ (1XX vs 7XX). Therefore required subfields might theoretically differ as well.
379
385
  // Note: Theoretically 260 $efg vs 264 with IND2=3 has already been handled by the preprocessor.
380
386
  // Thus check both:
@@ -0,0 +1,41 @@
1
+ // See https://www.loc.gov/marc/bibliographic/bdapndxj.html for details
2
+
3
+ import {subfieldArraysContainSameData} from "../utils.js";
4
+
5
+ function tagToDataProvenanceSubfieldCode(tag) {
6
+ if ( ['533', '800', '810', '811', '830'].includes(tag)) {
7
+ return 'y';
8
+ }
9
+ if ( tag === '856' || tag === '857' ) {
10
+ return 'e';
11
+ }
12
+
13
+ if ( tag.match(/^7[678]/u) ) {
14
+ return 'l'
15
+ }
16
+
17
+ if ( tag.match(/^00/u)) {
18
+ return undefined;
19
+ }
20
+ return '7';
21
+ }
22
+
23
+
24
+ export function provenanceSubfieldsPermitMerge(baseField, sourceField) {
25
+ const provinanceSubfieldCode = tagToDataProvenanceSubfieldCode(baseField.tag);
26
+ if (!baseField.subfields) {
27
+ return true;
28
+ }
29
+ if (provinanceSubfieldCode === undefined) {
30
+ return false;
31
+ }
32
+
33
+ const baseProvinanceSubfields = baseField.subfields.filter(sf => sf.code === provinanceSubfieldCode);
34
+ const sourceProvinanceSubfields = sourceField.subfields.filter(sf => sf.code === provinanceSubfieldCode);
35
+
36
+ // Currently we just compare two arrays. Later on we might do something more sophisticated with specific $7 data provenance category/relationship codes,
37
+ // or actual values.
38
+
39
+ return subfieldArraysContainSameData(baseProvinanceSubfields, sourceProvinanceSubfields);
40
+
41
+ }
@@ -18,16 +18,25 @@ import {mergeConfig as defaultConfig} from './mergeConfig.js';
18
18
 
19
19
  //const defaultConfig = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'src', 'merge-fields', 'config.json'), 'utf8'));
20
20
 
21
- export default function () {
21
+ export default function (defaultTagPattern = undefined) {
22
22
 
23
23
  return {
24
24
  description, validate, fix
25
25
  };
26
26
 
27
+ function getTagPattern(config) {
28
+ if (config && config.tagPattern) {
29
+ return config.tagPattern;
30
+ }
31
+ if (defaultTagPattern) { // Used by tests
32
+ return defaultTagPattern;
33
+ }
34
+ return '^[1678](?:00|10|11|30)$';
35
+ }
27
36
 
28
37
  function mergeFieldsWithinRecord(record, config) {
29
38
  //const candFields = record.fields.toReversed(); // Node 20+ only! Filter via config?
30
- const fields = config && config.tagPattern ? record.get(config.tagPattern) : record.get(/^[1678](?:00|10|11|30)$/u);
39
+ const fields = record.get(getTagPattern(config)); // config && config.tagPattern ? record.get(config.tagPattern) : record.get(/^[1678](?:00|10|11|30)$/u);
31
40
 
32
41
  fields.reverse();
33
42
  const mergedField = fields.find(f => mergeField(record, record, f, config));
@@ -106,8 +106,8 @@ function skipMergeField(baseRecord, sourceField, config) {
106
106
  return true;
107
107
  }
108
108
 
109
- // Skip duplicate field:
110
- if (baseRecord.fields.some(baseField => !baseField.mergeCandidate && fieldsAreIdentical(sourceField, baseField))) {
109
+ // Skip duplicate field when merging two records (NB! internal merge merges/removes the duplicate field):
110
+ if (!baseRecord.internalMerge && baseRecord.fields.some(baseField => !baseField.mergeCandidate && fieldsAreIdentical(sourceField, baseField))) {
111
111
  nvdebug(`skipMergeField(): field '${fieldToString(sourceField)}' already exists! No merge required!`, debugDev);
112
112
  sourceField.deleted = 1;
113
113
  return true;
@@ -4,6 +4,7 @@ import validatorFactory from './merge-fields/index.js';
4
4
  import {READERS} from '@natlibfi/fixura';
5
5
  import generateTests from '@natlibfi/fixugen';
6
6
  import createDebugLogger from 'debug';
7
+ import { nvdebug } from './utils.js';
7
8
 
8
9
  generateTests({
9
10
  callback,
@@ -29,17 +30,20 @@ async function testValidatorFactory() {
29
30
  assert.equal(typeof validator.validate, 'function');
30
31
  }
31
32
 
32
- async function callback({getFixture, enabled = true, fix = false}) {
33
+ async function callback({getFixture, enabled = true, fix = false, tagPattern = false}) {
33
34
  if (enabled === false) {
34
35
  debug('TEST SKIPPED!');
35
36
  return;
36
37
  }
37
38
 
38
- const validator = await validatorFactory();
39
+ nvdebug(`TAG PATTERN: ${tagPattern}`);
40
+
41
+ const validator = await validatorFactory(tagPattern);
39
42
  const record = new MarcRecord(getFixture('record.json'));
40
43
  const expectedResult = getFixture('expectedResult.json');
41
44
  // console.log(expectedResult); // eslint-disable-line
42
45
 
46
+ // NB! This validator will only use tags matching /^[1678](?:00|10|11|30)$/ unless tagPattern is specified!
43
47
  if (!fix) {
44
48
  const result = await validator.validate(record);
45
49
  assert.deepEqual(result, expectedResult);
@@ -332,7 +332,10 @@ function deriveIndividualDeletables(record) {
332
332
  // MET-575 (merge: applies in postprocessing)
333
333
  const inferiorTerms = getPrepublicationTerms(currString);
334
334
 
335
- const newDeletables = [...deletables, ...subsets, ...accentless, ...d490, ...inferiorTerms];
335
+ // MELKEHITYS-3277-ish: non-AI is better than AI (a rare case where longer version is inferior):
336
+ const aiBased = `${currString} ‡7 (dpenmw)AI`;
337
+
338
+ const newDeletables = [...deletables, ...subsets, ...accentless, ...d490, ...inferiorTerms, aiBased];
336
339
 
337
340
  if (subsets.length) {
338
341
  return processTodoList([...stillToDo, ...moreToDo], newDeletables);
@@ -67,9 +67,9 @@ export default function (defaultTagPattern) {
67
67
 
68
68
  // X00, X10, X11 and X130 could also for their own sets...
69
69
  // (ouch! sometimes $c comes after $d...): LoC: 100 0# ‡a Black Foot, ‡c Chief, ‡d d. 1877 ‡c (Spirit)
70
- const sortOrderForX00 = ['i', 'a', 'b', 'q', 'c', 'd', 'e', 't', 'u', 'l', 'f', 'x', 'y', 'z', '0', '5', '9']; // skip $g. Can't remember why, though...
71
- const sortOrderForX10 = ['i', 'a', 'b', 't', 'n', 'c', 'e', 'v', 'w', 'x', 'y', 'z', '0', '5', '9']; // somewhat iffy
72
- const sortOrderForX11 = ['a', 'n', 'd', 'c', 'e', 'g', 'j', '0', '5', '9'];
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
+ const sortOrderForX10 = ['i', 'a', 'b', 't', 'n', 'c', 'e', 'v', 'w', 'x', 'y', 'z', '0', '1', '5', '9']; // somewhat iffy
72
+ const sortOrderForX11 = ['a', 'n', 'd', 'c', 'e', 'g', 'j', '0', '1', '5', '9'];
73
73
  const sortOrderFor7XX = ['8', '7', 'i', 'a', 's', 't', 'b', 'c', 'd', 'm', 'h', 'k', 'o', 'x', 'z', 'g', 'q', 'w'];
74
74
  const sortOrderFor246 = ['i', 'a', 'b', 'n', 'p', 'f', '5', '9']; // Used by field 946 as well
75
75
 
@@ -89,7 +89,7 @@ const subfieldSortOrder = [
89
89
  {'tag': '245', 'sortOrder': ['a', 'b', 'n', 'p', 'k', 'f', 'c']},
90
90
  {'tag': '246', 'sortOrder': sortOrderFor246},
91
91
  {'tag': '382', 'sortOrder': ['a']},
92
- {'tag': '385', 'sortOrder': ['8', 'm', 'n', 'a', '2', '0']},
92
+ {'tag': '385', 'sortOrder': ['8', 'm', 'n', 'a', '2', '0', '1']},
93
93
  {'tag': '386', 'sortOrder': ['8', 'm', 'n', 'a']},
94
94
  {'tag': '490', 'sortOrder': ['a', 'x', 'y', 'v', 'l']},
95
95
  {'tag': '505', 'sortOrder': ['a']},
@@ -218,7 +218,9 @@ export function sortAdjacentSubfields(field, externalSortOrder = []) {
218
218
 
219
219
 
220
220
  const finnishWay = twoBeforeZero(field);
221
- const controlSubfieldOrder = finnishWay ? ['8', '7', '3', 'a', '4', '2', '0', '1', '5', '9'] : ['8', '7', '3', 'a', '4', '0', '2', '1', '5', '9'];
221
+
222
+ // Note: 760-789: '7' comes way earlier (after '6' and '8')
223
+ const controlSubfieldOrder = finnishWay ? ['8', '3', 'a', '4', '2', '0', '1', '7', '5', '9'] : ['8', '7', '3', 'a', '4', '0', '1', '2', '7', '5', '9'];
222
224
  swapSubfields(field, controlSubfieldOrder);
223
225
 
224
226
  const sortOrderForField = externalSortOrder.length > 0 ? externalSortOrder : getSubfieldSortOrder(field);
package/src/utils.js CHANGED
@@ -232,3 +232,13 @@ export function hasCopyright(value) {
232
232
  const modValue = removeCopyright(value);
233
233
  return value !== modValue;
234
234
  }
235
+
236
+
237
+
238
+ export function subfieldArraysContainSameData(arr1, arr2) {
239
+ if ( !arr1.every(sf => arr2.some(sf2 => subfieldsAreIdentical(sf, sf2))) ) {
240
+ return false;
241
+ }
242
+
243
+ return arr2.every(sf2 => arr1.some(sf => subfieldsAreIdentical(sf, sf2)));
244
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "_validationOptions": {},
3
+ "fields": [
4
+ { "tag": "001", "value": "f05" },
5
+
6
+ { "tag": "650", "ind1": " ", "ind2": "7", "subfields": [ { "code": "a", "value": "$7 does not merge"}, {"code": "2", "value": "yso/fin"}]},
7
+ { "tag": "650", "ind1": " ", "ind2": "7", "subfields": [ { "code": "a", "value": "$7 does not merge"}, {"code": "2", "value": "yso/fin"}, {"code": "7", "value": "(dpenmw)AI"}]},
8
+
9
+ { "tag": "650", "ind1": " ", "ind2": "7", "subfields": [ { "code": "a", "value": "non-$7 merges"}, {"code": "2", "value": "yso/fin"}, {"code": "0", "value": "something else"}]},
10
+
11
+ { "tag": "650", "ind1": " ", "ind2": "7", "subfields": [ { "code": "a", "value": "Identical $7s can merge"}, {"code": "2", "value": "yso/fin"}, {"code": "7", "value": "(dpenmw)AI"}]},
12
+
13
+
14
+ { "tag": "773", "ind1": " ", "ind2": " ", "subfields": [
15
+ { "code": "7", "value": "abcd"},
16
+ { "code": "t", "value": "Merges since $7 is not a data provenance subfield."},
17
+ { "code": "o", "value": "ABC-123"},
18
+ { "code": "x", "value": "1234567890"},
19
+ { "code": "w", "value": "(FI-MELINDA)020000000"}
20
+ ]}
21
+ ],
22
+ "leader": "01331cam a22003498i 4500"
23
+
24
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "description": "MELKEHITYS-3277: don't merge AI field",
3
+ "fix": true,
4
+ "tagPattern": "^(650|773)$",
5
+ "only": true
6
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "_validationOptions": {},
3
+ "fields": [
4
+ { "tag": "001", "value": "f05" },
5
+ { "tag": "650", "ind1": " ", "ind2": "7", "subfields": [ { "code": "a", "value": "$7 does not merge"}, {"code": "2", "value": "yso/fin"}]},
6
+ { "tag": "650", "ind1": " ", "ind2": "7", "subfields": [ { "code": "a", "value": "$7 does not merge"}, {"code": "2", "value": "yso/fin"}, {"code": "7", "value": "(dpenmw)AI"}]},
7
+
8
+ { "tag": "650", "ind1": " ", "ind2": "7", "subfields": [ { "code": "a", "value": "non-$7 merges"}, {"code": "2", "value": "yso/fin"}]},
9
+ { "tag": "650", "ind1": " ", "ind2": "7", "subfields": [ { "code": "a", "value": "non-$7 merges"}, {"code": "2", "value": "yso/fin"}, {"code": "0", "value": "something else"}]},
10
+
11
+ { "tag": "650", "ind1": " ", "ind2": "7", "subfields": [ { "code": "a", "value": "Identical $7s can merge"}, {"code": "2", "value": "yso/fin"}, {"code": "7", "value": "(dpenmw)AI"}]},
12
+ { "tag": "650", "ind1": " ", "ind2": "7", "subfields": [ { "code": "a", "value": "Identical $7s can merge"}, {"code": "2", "value": "yso/fin"}, {"code": "7", "value": "(dpenmw)AI"}]},
13
+
14
+
15
+ { "tag": "773", "ind1": " ", "ind2": " ", "subfields": [
16
+ { "code": "7", "value": "abcd"},
17
+ { "code": "t", "value": "Merges since $7 is not a data provenance subfield."},
18
+ { "code": "x", "value": "1234567890"},
19
+ { "code": "w", "value": "(FI-MELINDA)020000000"}
20
+ ]},
21
+ { "tag": "773", "ind1": " ", "ind2": " ", "subfields": [
22
+ { "code": "7", "value": "abcd"},
23
+ { "code": "t", "value": "Merges since $7 is not a data provenance subfield."},
24
+ { "code": "o", "value": "ABC-123"},
25
+ { "code": "w", "value": "(FI-MELINDA)020000000"}
26
+ ]}
27
+ ],
28
+ "leader": "01331cam a22003498i 4500"
29
+
30
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "_validationOptions": {},
3
+ "fields": [
4
+ { "tag": "001", "value": "f16" },
5
+ { "tag": "650", "ind1": " ", "ind2": "7", "subfields": [ { "code": "a", "value": "termi1"}, {"code": "2", "value": "yso/fin"}]},
6
+
7
+ { "tag": "650", "ind1": " ", "ind2": "7", "subfields": [ { "code": "a", "value": "termi2"}, {"code": "2", "value": "yso/fin"}]},
8
+ { "tag": "650", "ind1": " ", "ind2": "7", "subfields": [ { "code": "a", "value": "termi2"}, {"code": "2", "value": "yso/fin"}, {"code": "7", "value": "something else"}]}
9
+ ],
10
+ "leader": "01331cam a22003498i 4500"
11
+
12
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "description": "MELKEHITYS-3277-ish: delete AI field, if there's a corresponding non-AI field",
3
+ "fix": true,
4
+ "only": false
5
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "_validationOptions": {},
3
+ "fields": [
4
+ { "tag": "001", "value": "f16" },
5
+ { "tag": "650", "ind1": " ", "ind2": "7", "subfields": [ { "code": "a", "value": "termi1"}, {"code": "2", "value": "yso/fin"}]},
6
+ { "tag": "650", "ind1": " ", "ind2": "7", "subfields": [ { "code": "a", "value": "termi1"}, {"code": "2", "value": "yso/fin"}, {"code": "7", "value": "(dpenmw)AI"}]},
7
+
8
+ { "tag": "650", "ind1": " ", "ind2": "7", "subfields": [ { "code": "a", "value": "termi2"}, {"code": "2", "value": "yso/fin"}]},
9
+ { "tag": "650", "ind1": " ", "ind2": "7", "subfields": [ { "code": "a", "value": "termi2"}, {"code": "2", "value": "yso/fin"}, {"code": "7", "value": "something else"}]}
10
+
11
+ ],
12
+ "leader": "01331cam a22003498i 4500"
13
+
14
+ }