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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/dist/cyrillux.js +11 -11
  2. package/dist/cyrillux.js.map +2 -2
  3. package/dist/dataProvenanceUtils.js +19 -0
  4. package/dist/dataProvenanceUtils.js.map +7 -0
  5. package/dist/index.js +3 -1
  6. package/dist/index.js.map +2 -2
  7. package/dist/merge-fields/controlSubfields.js.map +2 -2
  8. package/dist/merge-fields/counterpartField.js +149 -152
  9. package/dist/merge-fields/counterpartField.js.map +3 -3
  10. package/dist/merge-fields/dataProvenance.js +6 -20
  11. package/dist/merge-fields/dataProvenance.js.map +2 -2
  12. package/dist/merge-fields/index.js +1 -1
  13. package/dist/merge-fields/index.js.map +2 -2
  14. package/dist/merge-fields/mergableIndicator.js +1 -2
  15. package/dist/merge-fields/mergableIndicator.js.map +2 -2
  16. package/dist/merge-fields/mergeConfig.js +2 -0
  17. package/dist/merge-fields/mergeConfig.js.map +2 -2
  18. package/dist/merge-fields/mergeConstraints.js +35 -32
  19. package/dist/merge-fields/mergeConstraints.js.map +3 -3
  20. package/dist/merge-fields/mergeField.js +4 -3
  21. package/dist/merge-fields/mergeField.js.map +2 -2
  22. package/dist/merge-fields/mergeOrAddSubfield.js +8 -7
  23. package/dist/merge-fields/mergeOrAddSubfield.js.map +2 -2
  24. package/dist/merge-fields/mergeSubfield.js +5 -1
  25. package/dist/merge-fields/mergeSubfield.js.map +2 -2
  26. package/dist/merge-fields/worldKnowledge.js +52 -0
  27. package/dist/merge-fields/worldKnowledge.js.map +2 -2
  28. package/dist/merge-fields.test.js +2 -2
  29. package/dist/merge-fields.test.js.map +2 -2
  30. package/dist/normalize-dashes.js +2 -2
  31. package/dist/normalize-dashes.js.map +2 -2
  32. package/dist/normalizeFieldForComparison.js +8 -14
  33. package/dist/normalizeFieldForComparison.js.map +2 -2
  34. package/dist/prepublicationUtils.js +1 -1
  35. package/dist/prepublicationUtils.js.map +2 -2
  36. package/dist/punctuation2.js +10 -10
  37. package/dist/punctuation2.js.map +2 -2
  38. package/dist/removeDuplicateDataFields.js +1 -24
  39. package/dist/removeDuplicateDataFields.js.map +2 -2
  40. package/dist/removeInferiorDataFields.js +3 -2
  41. package/dist/removeInferiorDataFields.js.map +2 -2
  42. package/dist/sortSubfields.js +19 -19
  43. package/dist/sortSubfields.js.map +2 -2
  44. package/dist/subfield6Utils.js +0 -1
  45. package/dist/subfield6Utils.js.map +2 -2
  46. package/dist/subfield8Utils.js +0 -5
  47. package/dist/subfield8Utils.js.map +2 -2
  48. package/dist/utils.js +29 -3
  49. package/dist/utils.js.map +2 -2
  50. package/package.json +4 -4
  51. package/src/cyrillux.js +11 -11
  52. package/src/dataProvenanceUtils.js +21 -0
  53. package/src/index.js +3 -1
  54. package/src/merge-fields/controlSubfields.js +0 -1
  55. package/src/merge-fields/counterpartField.js +191 -290
  56. package/src/merge-fields/dataProvenance.js +8 -25
  57. package/src/merge-fields/index.js +1 -1
  58. package/src/merge-fields/mergableIndicator.js +1 -2
  59. package/src/merge-fields/mergeConfig.js +2 -1
  60. package/src/merge-fields/mergeConstraints.js +39 -34
  61. package/src/merge-fields/mergeField.js +4 -7
  62. package/src/merge-fields/mergeOrAddSubfield.js +8 -7
  63. package/src/merge-fields/mergeSubfield.js +11 -2
  64. package/src/merge-fields/worldKnowledge.js +72 -3
  65. package/src/merge-fields.test.js +2 -2
  66. package/src/normalize-dashes.js +2 -2
  67. package/src/normalizeFieldForComparison.js +19 -20
  68. package/src/prepublicationUtils.js +1 -1
  69. package/src/punctuation2.js +10 -10
  70. package/src/removeDuplicateDataFields.js +24 -24
  71. package/src/removeInferiorDataFields.js +3 -2
  72. package/src/sortSubfields.js +19 -19
  73. package/src/subfield6Utils.js +1 -1
  74. package/src/subfield8Utils.js +5 -5
  75. package/src/utils.js +39 -12
  76. package/test-fixtures/cyrillux/f14/expectedResult.json +32 -0
  77. package/test-fixtures/cyrillux/f14/metadata.json +10 -0
  78. package/test-fixtures/cyrillux/f14/record.json +14 -0
  79. package/test-fixtures/merge-fields/f042_01/expectedResult.json +12 -0
  80. package/test-fixtures/merge-fields/f042_01/metadata.json +6 -0
  81. package/test-fixtures/merge-fields/f042_01/record.json +13 -0
  82. package/test-fixtures/merge-fields/f06/expectedResult.json +42 -0
  83. package/test-fixtures/merge-fields/f06/metadata.json +6 -0
  84. package/test-fixtures/merge-fields/f06/record.json +41 -0
  85. package/test-fixtures/merge-fields/f07/expectedResult.json +18 -0
  86. package/test-fixtures/merge-fields/f07/metadata.json +6 -0
  87. package/test-fixtures/merge-fields/f07/record.json +18 -0
  88. package/test-fixtures/merge-fields/f08/expectedResult.json +12 -0
  89. package/test-fixtures/merge-fields/f08/metadata.json +7 -0
  90. package/test-fixtures/merge-fields/f08/record.json +10 -0
  91. package/test-fixtures/merge-fields/f09/expectedResult.json +14 -0
  92. package/test-fixtures/merge-fields/f09/metadata.json +6 -0
  93. package/test-fixtures/merge-fields/f09/record.json +14 -0
  94. package/test-fixtures/merge-fields/f10/expectedResult.json +25 -0
  95. package/test-fixtures/merge-fields/f10/metadata.json +6 -0
  96. package/test-fixtures/merge-fields/f10/record.json +25 -0
  97. package/test-fixtures/merge-fields/f11/expectedResult.json +40 -0
  98. package/test-fixtures/merge-fields/f11/metadata.json +7 -0
  99. package/test-fixtures/merge-fields/f11/record.json +50 -0
  100. package/test-fixtures/merge-fields/f12/expectedResult.json +17 -0
  101. package/test-fixtures/merge-fields/f12/metadata.json +6 -0
  102. package/test-fixtures/merge-fields/f12/record.json +25 -0
  103. package/test-fixtures/merge-fields/f13/expectedResult.json +18 -0
  104. package/test-fixtures/merge-fields/f13/metadata.json +6 -0
  105. package/test-fixtures/merge-fields/f13/record.json +28 -0
  106. package/test-fixtures/merge-fields/f14/expectedResult.json +25 -0
  107. package/test-fixtures/merge-fields/f14/metadata.json +6 -0
  108. package/test-fixtures/merge-fields/f14/record.json +25 -0
  109. package/test-fixtures/merge-fields/f300_01/expectedResult.json +9 -0
  110. package/test-fixtures/merge-fields/f300_01/metadata.json +6 -0
  111. package/test-fixtures/merge-fields/f300_01/record.json +8 -0
  112. package/test-fixtures/merge-fields/f300_02/expectedResult.json +13 -0
  113. package/test-fixtures/merge-fields/f300_02/metadata.json +6 -0
  114. package/test-fixtures/merge-fields/f300_02/record.json +16 -0
  115. package/test-fixtures/merge-fields/f490_01/expectedResult.json +13 -0
  116. package/test-fixtures/merge-fields/f490_01/metadata.json +6 -0
  117. package/test-fixtures/merge-fields/f490_01/record.json +16 -0
  118. package/test-fixtures/remove-inferior-datafields/f17/expectedResult.json +11 -0
  119. package/test-fixtures/remove-inferior-datafields/f17/metadata.json +5 -0
  120. package/test-fixtures/remove-inferior-datafields/f17/record.json +15 -0
@@ -14,7 +14,59 @@ export function valueCarriesMeaning(tag, subfieldCode, value) {
14
14
  }
15
15
  return true;
16
16
  }
17
+ const synonyms = [
18
+ { tags: ["700", "710", "711", "730"], code: "i", "fin": "Sis\xE4lt\xE4\xE4 (ekspressio)", "swe": "Inneh\xE5ller (uttryck)" },
19
+ { tags: ["700", "710", "711", "730"], code: "i", "fin": "Sis\xE4lt\xE4\xE4 (teos)", "swe": "Inneh\xE5ller (verk)" },
20
+ { tags: ["700", "710", "711", "730"], code: "l", "fin": "Englanti", "swe": "Engelska" },
21
+ { tags: ["700", "710", "711", "730"], code: "l", "fin": "Ruotsi", "swe": "Svenska" },
22
+ { tags: ["700", "710", "711", "730"], code: "l", "fin": "Suomi", "swe": "Finska" }
23
+ // There might eventually be need for a list of terms is given language (eg. engl. paperback and softcover)
24
+ ];
25
+ export function getSynonyms(term, tag = void 0, subfieldCode = void 0, preferredLanguage = void 0, ignoreCase = true, relevantLanguagesString = "fin swe") {
26
+ if (!term) {
27
+ return [];
28
+ }
29
+ const relevantLanguges = relevantLanguagesString.split(/\s+/u);
30
+ const normalizedTerm = ignoreCase ? term.toLowerCase() : term;
31
+ const synonymsWithTag = tag ? synonyms.filter((s) => s.tags.includes(tag)) : synonyms;
32
+ if (synonymsWithTag.length === 0) {
33
+ return [];
34
+ }
35
+ const synonymsWithTagAndCode = subfieldCode ? synonymsWithTag.filter((s) => s.code === subfieldCode) : synonymsWithTag;
36
+ const matchingSynonyms = synonymsWithTagAndCode.filter((s) => termAndLangMatch(s));
37
+ if (preferredLanguage && matchingSynonyms.length > 0) {
38
+ return matchingSynonyms.map((s) => s[preferredLanguage]);
39
+ }
40
+ return matchingSynonyms;
41
+ function termAndLangMatch(synonym) {
42
+ if (relevantLanguges.includes("fin")) {
43
+ if (ignoreCase && synonym.fin.toLowerCase() === normalizedTerm) {
44
+ return true;
45
+ }
46
+ if (!ignoreCase && synonym.fin === term) {
47
+ return true;
48
+ }
49
+ }
50
+ if (relevantLanguges.includes("swe")) {
51
+ if (ignoreCase && synonym.swe.toLowerCase() === normalizedTerm) {
52
+ return true;
53
+ }
54
+ if (!ignoreCase && synonym.swe === term) {
55
+ return true;
56
+ }
57
+ }
58
+ return false;
59
+ }
60
+ }
61
+ export function getSynonym(tag, subfieldCode, originalValue) {
62
+ const finnishForm = getSynonyms(originalValue, tag, subfieldCode, "fin");
63
+ if (finnishForm.length === 1) {
64
+ return finnishForm[0];
65
+ }
66
+ return originalValue;
67
+ }
17
68
  export function normalizeForSamenessCheck(tag, subfieldCode, originalValue) {
69
+ originalValue = getSynonym(tag, subfieldCode, originalValue);
18
70
  if (subfieldCode === "a" && ["100", "600", "700", "800"].includes(tag)) {
19
71
  return normalizePersonalName(originalValue);
20
72
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/merge-fields/worldKnowledge.js"],
4
- "sourcesContent": ["//import {nvdebug} from '../utils';\n\nexport function valueCarriesMeaning(tag, subfieldCode, value) {\n // Some data is pretty meaningless and as meaningless is pretty close to nothing, this meaningless data should no prevent merge.\n // The list below is incomples (swedish translations etc)\n if (tag === '260' || tag === '264') {\n // We drop these, instead of normalizing, as KV does not put this information in place...\n if (subfieldCode === 'a') {\n if (value.match(/^[^a-z]*(?:Kustannuspaikka tuntematon|S\\.l)[^a-z]*$/ui)) {\n return false;\n }\n }\n if (subfieldCode === 'b') {\n if (value.match(/^[^a-z]*(?:Kustantaja tuntematon|S\\.n)[^a-z]*$/ui)) {\n return false;\n }\n }\n return true;\n }\n return true;\n}\n\nexport function normalizeForSamenessCheck(tag, subfieldCode, originalValue) {\n // NB! These work only for non-repeatable subfields!\n // Repeatable subfields are currently handled in mergeSubfields.js. Only non-repeatable subfields block field merge,\n // (This split is suboptiomal... Minimum fix: make this disctinction cleaner...)\n if (subfieldCode === 'a' && ['100', '600', '700', '800'].includes(tag)) {\n return normalizePersonalName(originalValue);\n }\n\n // NB! originalValue should already be lowercased, stripped on initial '[' chars and postpunctuation.\n if (tag === '250' && subfieldCode === 'a') {\n return normalizeEditionStatement(originalValue);\n }\n\n // 506 - Restrictions on Access Note (R), $a - Terms governing access (NR)\n if (tag === '506' && subfieldCode === 'a') {\n return normalize506a(originalValue);\n }\n\n if (tag === '534' && subfieldCode === 'p') {\n return normalizeOriginalVersionNoteIntroductoryPhrase(originalValue);\n }\n\n return originalValue;\n}\n\n\nfunction normalizePersonalName(originalValue) {\n // Use more readable \"Forename Surname\" format in comparisons:\n return originalValue.replace(/^([^,]+), ([^,]+)$/u, '$2 $1');\n}\n\nconst sallittu506a = ['sallittu kaikenik\u00E4isille', 'sallittu', 's']; // downcased, without punctuation\nfunction normalize506a(originalValue) {\n if (sallittu506a.includes(originalValue)) {\n return sallittu506a[0];\n }\n return originalValue;\n}\n\nconst introductoryPhrasesMeaning1 = ['alkuper\u00E4inen', 'alkuper\u00E4isen julkaisutiedot', 'alun perin julkaistu', 'alunperin julkaistu'];\nfunction normalizeOriginalVersionNoteIntroductoryPhrase(originalValue) {\n // MELKEHITYS-1935-ish:\n if (introductoryPhrasesMeaning1.includes(originalValue)) {\n return introductoryPhrasesMeaning1[0];\n }\n\n return originalValue;\n}\n\nfunction normalizeEditionStatement(originalValue) {\n const value = originalValue;\n\n // As normalization tries to translate things info Finnish, use this for similarity check only!\n if (value.match(/^[1-9][0-9]*(?:\\.|:a|nd|rd|st|th) (?:ed\\.?|edition|p\\.?|painos|uppl\\.?|upplagan)[.\\]]*$/ui)) {\n const nth = value.replace(/[^0-9].*$/u, '');\n return `${nth}. painos`;\n }\n\n // Quick and dirty fix for\n if (value.match(/^[1-9][0-9]*(?:\\.|:a|nd|rd|st|th)(?: f\u00F6rnyade|,? rev\\.| uud\\.| uudistettu) (?:ed\\.?|edition|p\\.?|painos|uppl\\.?|upplagan)[.\\]]*$/ui)) {\n const nth = value.replace(/[^0-9].*$/u, '');\n return `${nth}. uudistettu painos`;\n }\n\n if (value.match(/^(?:First|F\u00F6rsta|Ensimm\u00E4inen) (?:ed\\.?|edition|p\\.?|painos|uppl\\.?|upplagan)[.\\]]*$/ui)) {\n return `1. painos`;\n }\n\n if (value.match(/^(?:Andra|Second|Toinen) (?:ed\\.?|edition|p\\.?|painos|uppl\\.?|upplagan)[.\\]]*$/ui)) {\n return `2. painos`;\n }\n\n if (value.match(/^(?:Kolmas|Third|Tredje) (?:ed\\.?|edition|p\\.?|painos|uppl\\.?|upplagan)[.\\]]*$/ui)) {\n return `3. painos`;\n }\n\n if (value.match(/^(?:Fourth|Fj\u00E4rde|Nelj\u00E4s) (?:ed\\.?|edition|p\\.?|painos|uppl\\.?|upplagan)[.\\]]*$/ui)) {\n return `4. painos`;\n }\n\n return originalValue;\n}\n"],
5
- "mappings": "AAEO,gBAAS,oBAAoB,KAAK,cAAc,OAAO;AAG5D,MAAI,QAAQ,SAAS,QAAQ,OAAO;AAElC,QAAI,iBAAiB,KAAK;AACxB,UAAI,MAAM,MAAM,uDAAuD,GAAG;AACxE,eAAO;AAAA,MACT;AAAA,IACF;AACA,QAAI,iBAAiB,KAAK;AACxB,UAAI,MAAM,MAAM,kDAAkD,GAAG;AACnE,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEO,gBAAS,0BAA0B,KAAK,cAAc,eAAe;AAI1E,MAAI,iBAAiB,OAAO,CAAC,OAAO,OAAO,OAAO,KAAK,EAAE,SAAS,GAAG,GAAG;AACtE,WAAO,sBAAsB,aAAa;AAAA,EAC5C;AAGA,MAAI,QAAQ,SAAS,iBAAiB,KAAK;AACzC,WAAO,0BAA0B,aAAa;AAAA,EAChD;AAGA,MAAI,QAAQ,SAAS,iBAAiB,KAAK;AACzC,WAAO,cAAc,aAAa;AAAA,EACpC;AAEA,MAAI,QAAQ,SAAS,iBAAiB,KAAK;AACzC,WAAO,+CAA+C,aAAa;AAAA,EACrE;AAEA,SAAO;AACT;AAGA,SAAS,sBAAsB,eAAe;AAE5C,SAAO,cAAc,QAAQ,uBAAuB,OAAO;AAC7D;AAEA,MAAM,eAAe,CAAC,+BAA4B,YAAY,GAAG;AACjE,SAAS,cAAc,eAAe;AACpC,MAAI,aAAa,SAAS,aAAa,GAAG;AACxC,WAAO,aAAa,CAAC;AAAA,EACvB;AACA,SAAO;AACT;AAEA,MAAM,8BAA8B,CAAC,mBAAgB,kCAA+B,wBAAwB,qBAAqB;AACjI,SAAS,+CAA+C,eAAe;AAErE,MAAI,4BAA4B,SAAS,aAAa,GAAG;AACvD,WAAO,4BAA4B,CAAC;AAAA,EACtC;AAEA,SAAO;AACT;AAEA,SAAS,0BAA0B,eAAe;AAChD,QAAM,QAAQ;AAGd,MAAI,MAAM,MAAM,2FAA2F,GAAG;AAC5G,UAAM,MAAM,MAAM,QAAQ,cAAc,EAAE;AAC1C,WAAO,GAAG,GAAG;AAAA,EACf;AAGA,MAAI,MAAM,MAAM,oIAAoI,GAAG;AACrJ,UAAM,MAAM,MAAM,QAAQ,cAAc,EAAE;AAC1C,WAAO,GAAG,GAAG;AAAA,EACf;AAEA,MAAI,MAAM,MAAM,uFAAuF,GAAG;AACxG,WAAO;AAAA,EACT;AAEA,MAAI,MAAM,MAAM,kFAAkF,GAAG;AACnG,WAAO;AAAA,EACT;AAEA,MAAI,MAAM,MAAM,kFAAkF,GAAG;AACnG,WAAO;AAAA,EACT;AAEA,MAAI,MAAM,MAAM,mFAAmF,GAAG;AACpG,WAAO;AAAA,EACT;AAEA,SAAO;AACT;",
4
+ "sourcesContent": ["//import {nvdebug} from '../utils.js';\n\n// NB! This file (or at least synonyms) should eventually be moved away from merge to '..'.\n\n\nexport function valueCarriesMeaning(tag, subfieldCode, value) {\n // Some data is pretty meaningless and as meaningless is pretty close to nothing, this meaningless data should no prevent merge.\n // The list below is incomples (swedish translations etc)\n if (tag === '260' || tag === '264') {\n // We drop these, instead of normalizing, as KV does not put this information in place...\n if (subfieldCode === 'a') {\n if (value.match(/^[^a-z]*(?:Kustannuspaikka tuntematon|S\\.l)[^a-z]*$/ui)) {\n return false;\n }\n }\n if (subfieldCode === 'b') {\n if (value.match(/^[^a-z]*(?:Kustantaja tuntematon|S\\.n)[^a-z]*$/ui)) {\n return false;\n }\n }\n return true;\n }\n return true;\n}\n\nconst synonyms = [\n {tags: ['700', '710', '711', '730'], code: 'i', 'fin': 'Sis\u00E4lt\u00E4\u00E4 (ekspressio)', 'swe': 'Inneh\u00E5ller (uttryck)'},\n {tags: ['700', '710', '711', '730'], code: 'i', 'fin': 'Sis\u00E4lt\u00E4\u00E4 (teos)', 'swe': 'Inneh\u00E5ller (verk)'},\n {tags: ['700', '710', '711', '730'], code: 'l', 'fin': 'Englanti', 'swe': 'Engelska'},\n {tags: ['700', '710', '711', '730'], code: 'l', 'fin': 'Ruotsi', 'swe': 'Svenska'},\n {tags: ['700', '710', '711', '730'], code: 'l', 'fin': 'Suomi', 'swe': 'Finska'}\n // There might eventually be need for a list of terms is given language (eg. engl. paperback and softcover)\n];\n\nexport function getSynonyms(term, tag = undefined, subfieldCode = undefined, preferredLanguage = undefined, ignoreCase = true, relevantLanguagesString = 'fin swe',) {\n if (!term) {\n return [];\n }\n //nvdebug(`WP1 CANDS: ${synonyms.length} FOR '${term}'`);\n const relevantLanguges = relevantLanguagesString.split(/\\s+/u);\n const normalizedTerm = ignoreCase ? term.toLowerCase() : term;\n const synonymsWithTag = tag ? synonyms.filter(s => s.tags.includes(tag)) : synonyms;\n if (synonymsWithTag.length === 0) {\n return [];\n }\n //nvdebug(`WP2 (FILTER ${tag}) CANDS: ${synonymsWithTag.length}`);\n const synonymsWithTagAndCode = subfieldCode ? synonymsWithTag.filter(s => s.code === subfieldCode) : synonymsWithTag;\n //nvdebug(`WP3 (FILTER $${subfieldCode}) CANDS: ${synonymsWithTagAndCode.length}:\\n${JSON.stringify(synonymsWithTagAndCode)}`);\n const matchingSynonyms = synonymsWithTagAndCode.filter(s => termAndLangMatch(s));\n\n if (preferredLanguage && matchingSynonyms.length > 0) {\n //console.log(`USING PREFERRED LANG '${preferredLanguage}' for TERM '${term}':\\n${JSON.stringify(matchingSynonyms)}`);\n return matchingSynonyms.map(s => s[preferredLanguage]);\n }\n return matchingSynonyms;\n\n function termAndLangMatch(synonym) {\n if (relevantLanguges.includes('fin')) {\n if (ignoreCase && synonym.fin.toLowerCase() === normalizedTerm ) {\n return true;\n }\n if (!ignoreCase && synonym.fin === term) {\n return true;\n }\n }\n\n if (relevantLanguges.includes('swe')) {\n if (ignoreCase && synonym.swe.toLowerCase() === normalizedTerm ) {\n return true;\n }\n if (!ignoreCase && synonym.swe === term) {\n return true;\n }\n }\n return false;\n }\n}\n\nexport function getSynonym(tag, subfieldCode, originalValue) {\n const finnishForm = getSynonyms(originalValue, tag, subfieldCode, 'fin');\n if (finnishForm.length === 1) {\n //nvdebug(`FINNISH FORM FOR ${tag}$${subfieldCode}: '${finnishForm[0]}'`);\n return finnishForm[0];\n }\n return originalValue;\n}\n\nexport function normalizeForSamenessCheck(tag, subfieldCode, originalValue) {\n // NB! These work only for non-repeatable subfields!\n // Repeatable subfields are currently handled in mergeSubfields.js. Only non-repeatable subfields block field merge,\n // (This split is suboptiomal... Minimum fix: make this distinction cleaner...)\n\n //nvdebug(`TRYING TO DO ${tag}$${subfieldCode} '${originalValue}'`);\n originalValue = getSynonym(tag, subfieldCode, originalValue);\n\n if (subfieldCode === 'a' && ['100', '600', '700', '800'].includes(tag)) { // \"Etunimi Sukunimi\"...\n return normalizePersonalName(originalValue);\n }\n\n // NB! originalValue should already be lowercased, stripped on initial '[' chars and postpunctuation.\n if (tag === '250' && subfieldCode === 'a') {\n return normalizeEditionStatement(originalValue);\n }\n\n // 506 - Restrictions on Access Note (R), $a - Terms governing access (NR)\n if (tag === '506' && subfieldCode === 'a') {\n return normalize506a(originalValue);\n }\n\n if (tag === '534' && subfieldCode === 'p') {\n return normalizeOriginalVersionNoteIntroductoryPhrase(originalValue);\n }\n\n return originalValue;\n}\n\n\nfunction normalizePersonalName(originalValue) {\n // Use more readable \"Forename Surname\" format in comparisons:\n return originalValue.replace(/^([^,]+), ([^,]+)$/u, '$2 $1');\n}\n\nconst sallittu506a = ['sallittu kaikenik\u00E4isille', 'sallittu', 's']; // downcased, without punctuation\nfunction normalize506a(originalValue) {\n if (sallittu506a.includes(originalValue)) {\n return sallittu506a[0];\n }\n return originalValue;\n}\n\nconst introductoryPhrasesMeaning1 = ['alkuper\u00E4inen', 'alkuper\u00E4isen julkaisutiedot', 'alun perin julkaistu', 'alunperin julkaistu'];\nfunction normalizeOriginalVersionNoteIntroductoryPhrase(originalValue) {\n // MELKEHITYS-1935-ish:\n if (introductoryPhrasesMeaning1.includes(originalValue)) {\n return introductoryPhrasesMeaning1[0];\n }\n\n return originalValue;\n}\n\nfunction normalizeEditionStatement(originalValue) {\n const value = originalValue;\n\n // As normalization tries to translate things info Finnish, use this for similarity check only!\n if (value.match(/^[1-9][0-9]*(?:\\.|:a|nd|rd|st|th) (?:ed\\.?|edition|p\\.?|painos|uppl\\.?|upplagan)[.\\]]*$/ui)) {\n const nth = value.replace(/[^0-9].*$/u, '');\n return `${nth}. painos`;\n }\n\n // Quick and dirty fix for\n if (value.match(/^[1-9][0-9]*(?:\\.|:a|nd|rd|st|th)(?: f\u00F6rnyade|,? rev\\.| uud\\.| uudistettu) (?:ed\\.?|edition|p\\.?|painos|uppl\\.?|upplagan)[.\\]]*$/ui)) {\n const nth = value.replace(/[^0-9].*$/u, '');\n return `${nth}. uudistettu painos`;\n }\n\n if (value.match(/^(?:First|F\u00F6rsta|Ensimm\u00E4inen) (?:ed\\.?|edition|p\\.?|painos|uppl\\.?|upplagan)[.\\]]*$/ui)) {\n return `1. painos`;\n }\n\n if (value.match(/^(?:Andra|Second|Toinen) (?:ed\\.?|edition|p\\.?|painos|uppl\\.?|upplagan)[.\\]]*$/ui)) {\n return `2. painos`;\n }\n\n if (value.match(/^(?:Kolmas|Third|Tredje) (?:ed\\.?|edition|p\\.?|painos|uppl\\.?|upplagan)[.\\]]*$/ui)) {\n return `3. painos`;\n }\n\n if (value.match(/^(?:Fourth|Fj\u00E4rde|Nelj\u00E4s) (?:ed\\.?|edition|p\\.?|painos|uppl\\.?|upplagan)[.\\]]*$/ui)) {\n return `4. painos`;\n }\n\n return originalValue;\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,4 @@
1
- import assert from "node:assert";
1
+ import assert from "node:assert/strict";
2
2
  import { MarcRecord } from "@natlibfi/marc-record";
3
3
  import validatorFactory from "./merge-fields/index.js";
4
4
  import { READERS } from "@natlibfi/fixura";
@@ -36,6 +36,6 @@ async function callback({ getFixture, fix = false, tagPattern = false }) {
36
36
  return;
37
37
  }
38
38
  await validator.fix(record);
39
- assert.deepEqual(record, expectedResult);
39
+ assert.deepEqual(record, new MarcRecord(expectedResult));
40
40
  }
41
41
  //# sourceMappingURL=merge-fields.test.js.map
@@ -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\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, fix = false, tagPattern = false}) {\n debug(`Run test using 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;AAG9B,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,MAAM,OAAO,aAAa,MAAK,GAAG;AACrE,QAAM,8BAA8B,UAAU,EAAE;AAEhD,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;",
4
+ "sourcesContent": ["import assert from 'node:assert/strict';\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\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, fix = false, tagPattern = false}) {\n debug(`Run test using 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, new MarcRecord(expectedResult));\n}\n"],
5
+ "mappings": "AAAA,OAAO,YAAY;AACnB,SAAQ,kBAAiB;AACzB,OAAO,sBAAsB;AAC7B,SAAQ,eAAc;AACtB,OAAO,mBAAmB;AAC1B,OAAO,uBAAuB;AAG9B,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,MAAM,OAAO,aAAa,MAAK,GAAG;AACrE,QAAM,8BAA8B,UAAU,EAAE;AAEhD,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,IAAI,WAAW,cAAc,CAAC;AACzD;",
6
6
  "names": []
7
7
  }
@@ -1,5 +1,5 @@
1
1
  import clone from "clone";
2
- import { fieldToString, isControlSubfieldCode, nvdebug } from "./utils.js";
2
+ import { fieldToString, isContentSubfieldCode, nvdebug } from "./utils.js";
3
3
  export default function() {
4
4
  return {
5
5
  description: 'Normalize various dashes to "-"',
@@ -43,7 +43,7 @@ function fixDashes(field) {
43
43
  field.subfields.forEach((sf) => subfieldFixDashes(sf));
44
44
  return field;
45
45
  function subfieldFixDashes(subfield) {
46
- if (isControlSubfieldCode(subfield.code)) {
46
+ if (!isContentSubfieldCode(subfield.code, field.tag)) {
47
47
  return;
48
48
  }
49
49
  subfield.value = subfield.value.replace(/[\u2010\u2011\u2012\u2013\u2014\u2015]/ug, "-");
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/normalize-dashes.js"],
4
- "sourcesContent": ["//import createDebugLogger from 'debug';\nimport clone from 'clone';\nimport {fieldToString, isControlSubfieldCode, nvdebug} from './utils.js';\n\n// Author(s): Nicholas Volk\nexport default function () {\n\n return {\n description: 'Normalize various dashes to \"-\"',\n validate, fix\n };\n\n function fix(record) {\n nvdebug(`FIX ME`);\n record.fields.forEach(field => {\n fixDashes(field);\n });\n\n const res = {message: [], fix: [], valid: true};\n return res;\n }\n\n function validate(record) {\n const res = {message: []};\n\n nvdebug(`VALIDATE ME`);\n record.fields?.forEach(field => {\n validateField(field, res);\n });\n\n res.valid = !(res.message.length >= 1);\n return res;\n }\n\n function validateField(field, res) {\n const orig = fieldToString(field);\n nvdebug(` VALIDATE FIELD '${orig}'`);\n\n const normalizedField = fixDashes(clone(field));\n const mod = fieldToString(normalizedField);\n if (orig === mod) { // Fail as the input is \"broken\"/\"crap\"/sumthing\n return;\n }\n res.message.push(`'TODO: ${orig}' => '${mod}'`);\n return;\n }\n}\n\n\nfunction fixDashes(field) {\n if (!field.subfields) {\n return field;\n }\n\n nvdebug(`Dashing ${fieldToString(field)}`);\n\n field.subfields.forEach(sf => subfieldFixDashes(sf));\n\n return field;\n\n function subfieldFixDashes(subfield) {\n if (isControlSubfieldCode(subfield.code)) {\n return;\n }\n // Normalize dashes U+2010 ... U+2015 to '-':\n subfield.value = subfield.value.replace(/[\\u2010\\u2011\\u2012\\u2013\\u2014\\u2015]/ug, '-');\n }\n\n}\n"],
5
- "mappings": "AACA,OAAO,WAAW;AAClB,SAAQ,eAAe,uBAAuB,eAAc;AAG5D,0BAA2B;AAEzB,SAAO;AAAA,IACL,aAAa;AAAA,IACb;AAAA,IAAU;AAAA,EACZ;AAEA,WAAS,IAAI,QAAQ;AACnB,YAAQ,QAAQ;AAChB,WAAO,OAAO,QAAQ,WAAS;AAC7B,gBAAU,KAAK;AAAA,IACjB,CAAC;AAED,UAAM,MAAM,EAAC,SAAS,CAAC,GAAG,KAAK,CAAC,GAAG,OAAO,KAAI;AAC9C,WAAO;AAAA,EACT;AAEA,WAAS,SAAS,QAAQ;AACxB,UAAM,MAAM,EAAC,SAAS,CAAC,EAAC;AAExB,YAAQ,aAAa;AACrB,WAAO,QAAQ,QAAQ,WAAS;AAC9B,oBAAc,OAAO,GAAG;AAAA,IAC1B,CAAC;AAED,QAAI,QAAQ,EAAE,IAAI,QAAQ,UAAU;AACpC,WAAO;AAAA,EACT;AAEA,WAAS,cAAc,OAAO,KAAK;AACjC,UAAM,OAAO,cAAc,KAAK;AAChC,YAAQ,oBAAoB,IAAI,GAAG;AAEnC,UAAM,kBAAkB,UAAU,MAAM,KAAK,CAAC;AAC9C,UAAM,MAAM,cAAc,eAAe;AACzC,QAAI,SAAS,KAAK;AAChB;AAAA,IACF;AACA,QAAI,QAAQ,KAAK,UAAU,IAAI,SAAS,GAAG,GAAG;AAC9C;AAAA,EACF;AACF;AAGA,SAAS,UAAU,OAAO;AACxB,MAAI,CAAC,MAAM,WAAW;AACpB,WAAO;AAAA,EACT;AAEA,UAAQ,WAAW,cAAc,KAAK,CAAC,EAAE;AAEzC,QAAM,UAAU,QAAQ,QAAM,kBAAkB,EAAE,CAAC;AAEnD,SAAO;AAEP,WAAS,kBAAkB,UAAU;AACnC,QAAI,sBAAsB,SAAS,IAAI,GAAG;AACxC;AAAA,IACF;AAEA,aAAS,QAAQ,SAAS,MAAM,QAAQ,4CAA4C,GAAG;AAAA,EACzF;AAEF;",
4
+ "sourcesContent": ["//import createDebugLogger from 'debug';\nimport clone from 'clone';\nimport {fieldToString, isContentSubfieldCode, nvdebug} from './utils.js';\n\n// Author(s): Nicholas Volk\nexport default function () {\n\n return {\n description: 'Normalize various dashes to \"-\"',\n validate, fix\n };\n\n function fix(record) {\n nvdebug(`FIX ME`);\n record.fields.forEach(field => {\n fixDashes(field);\n });\n\n const res = {message: [], fix: [], valid: true};\n return res;\n }\n\n function validate(record) {\n const res = {message: []};\n\n nvdebug(`VALIDATE ME`);\n record.fields?.forEach(field => {\n validateField(field, res);\n });\n\n res.valid = !(res.message.length >= 1);\n return res;\n }\n\n function validateField(field, res) {\n const orig = fieldToString(field);\n nvdebug(` VALIDATE FIELD '${orig}'`);\n\n const normalizedField = fixDashes(clone(field));\n const mod = fieldToString(normalizedField);\n if (orig === mod) { // Fail as the input is \"broken\"/\"crap\"/sumthing\n return;\n }\n res.message.push(`'TODO: ${orig}' => '${mod}'`);\n return;\n }\n}\n\n\nfunction fixDashes(field) {\n if (!field.subfields) {\n return field;\n }\n\n nvdebug(`Dashing ${fieldToString(field)}`);\n\n field.subfields.forEach(sf => subfieldFixDashes(sf));\n\n return field;\n\n function subfieldFixDashes(subfield) {\n if (!isContentSubfieldCode(subfield.code, field.tag)) {\n return;\n }\n // Normalize dashes U+2010 ... U+2015 to '-':\n subfield.value = subfield.value.replace(/[\\u2010\\u2011\\u2012\\u2013\\u2014\\u2015]/ug, '-');\n }\n\n}\n"],
5
+ "mappings": "AACA,OAAO,WAAW;AAClB,SAAQ,eAAe,uBAAuB,eAAc;AAG5D,0BAA2B;AAEzB,SAAO;AAAA,IACL,aAAa;AAAA,IACb;AAAA,IAAU;AAAA,EACZ;AAEA,WAAS,IAAI,QAAQ;AACnB,YAAQ,QAAQ;AAChB,WAAO,OAAO,QAAQ,WAAS;AAC7B,gBAAU,KAAK;AAAA,IACjB,CAAC;AAED,UAAM,MAAM,EAAC,SAAS,CAAC,GAAG,KAAK,CAAC,GAAG,OAAO,KAAI;AAC9C,WAAO;AAAA,EACT;AAEA,WAAS,SAAS,QAAQ;AACxB,UAAM,MAAM,EAAC,SAAS,CAAC,EAAC;AAExB,YAAQ,aAAa;AACrB,WAAO,QAAQ,QAAQ,WAAS;AAC9B,oBAAc,OAAO,GAAG;AAAA,IAC1B,CAAC;AAED,QAAI,QAAQ,EAAE,IAAI,QAAQ,UAAU;AACpC,WAAO;AAAA,EACT;AAEA,WAAS,cAAc,OAAO,KAAK;AACjC,UAAM,OAAO,cAAc,KAAK;AAChC,YAAQ,oBAAoB,IAAI,GAAG;AAEnC,UAAM,kBAAkB,UAAU,MAAM,KAAK,CAAC;AAC9C,UAAM,MAAM,cAAc,eAAe;AACzC,QAAI,SAAS,KAAK;AAChB;AAAA,IACF;AACA,QAAI,QAAQ,KAAK,UAAU,IAAI,SAAS,GAAG,GAAG;AAC9C;AAAA,EACF;AACF;AAGA,SAAS,UAAU,OAAO;AACxB,MAAI,CAAC,MAAM,WAAW;AACpB,WAAO;AAAA,EACT;AAEA,UAAQ,WAAW,cAAc,KAAK,CAAC,EAAE;AAEzC,QAAM,UAAU,QAAQ,QAAM,kBAAkB,EAAE,CAAC;AAEnD,SAAO;AAEP,WAAS,kBAAkB,UAAU;AACnC,QAAI,CAAC,sBAAsB,SAAS,MAAM,MAAM,GAAG,GAAG;AACpD;AAAA,IACF;AAEA,aAAS,QAAQ,SAAS,MAAM,QAAQ,4CAA4C,GAAG;AAAA,EACzF;AAEF;",
6
6
  "names": []
7
7
  }
@@ -1,20 +1,13 @@
1
1
  import clone from "clone";
2
2
  import { fieldStripPunctuation } from "./punctuation2.js";
3
- import { fieldToString, isControlSubfieldCode } from "./utils.js";
3
+ import { fieldToString, isContentSubfieldCode } from "./utils.js";
4
4
  import { fieldNormalizeControlNumbers } from "./normalize-identifiers.js";
5
5
  import createDebugLogger from "debug";
6
6
  import { normalizePartData, subfieldContainsPartData } from "./normalizeSubfieldValueForComparison.js";
7
+ import { isEnnakkotietoSubfield } from "./prepublicationUtils.js";
8
+ import { getSynonym } from "./merge-fields/worldKnowledge.js";
7
9
  const debug = createDebugLogger("@natlibfi/melinda-marc-record-merge-reducers:normalizeFieldForComparison");
8
10
  const debugDev = debug.extend("dev");
9
- export function isEnnakkotietoSubfieldG(subfield) {
10
- if (valuelessSubfield(subfield)) {
11
- return false;
12
- }
13
- if (subfield.code !== "g") {
14
- return false;
15
- }
16
- return subfield.value.match(/^ENNAKKOTIETO\.?$/gui);
17
- }
18
11
  function debugFieldComparison(oldField, newField) {
19
12
  const oldString = fieldToString(oldField);
20
13
  const newString = fieldToString(newField);
@@ -39,13 +32,13 @@ function containsCorporateName(tag = "???", subfieldCode = void 0) {
39
32
  return false;
40
33
  }
41
34
  function skipAllSubfieldNormalizations(value, subfieldCode, tag) {
42
- if (isEnnakkotietoSubfieldG({ "code": subfieldCode, value })) {
35
+ if (isEnnakkotietoSubfield({ "code": subfieldCode, value })) {
43
36
  return true;
44
37
  }
45
38
  if (tag === "035" && ["a", "z"].includes(subfieldCode)) {
46
39
  return true;
47
40
  }
48
- if (isControlSubfieldCode(subfieldCode)) {
41
+ if (!isContentSubfieldCode(subfieldCode, tag)) {
49
42
  return true;
50
43
  }
51
44
  return false;
@@ -167,6 +160,8 @@ function removeDecomposedDiacritics(value = "") {
167
160
  return String(value).replace(/\p{Diacritic}/gu, "");
168
161
  }
169
162
  function normalizeSubfieldValue(value, subfieldCode, tag) {
163
+ value = removeCharsThatDontCarryMeaning(value, tag, subfieldCode);
164
+ value = getSynonym(tag, subfieldCode, value);
170
165
  value = subfieldValueLowercase(value, subfieldCode, tag);
171
166
  value = normalizePartData(value, subfieldCode, tag);
172
167
  value = value.replace(/^\[([^[\]]+)\]/gu, "$1");
@@ -203,7 +198,7 @@ function normalizeField(field) {
203
198
  return field;
204
199
  }
205
200
  export function cloneAndNormalizeFieldForComparison(field) {
206
- const clonedField = clone(field);
201
+ const clonedField = cloneAndRemovePunctuation(field);
207
202
  if (fieldSkipNormalization(field)) {
208
203
  return clonedField;
209
204
  }
@@ -212,7 +207,6 @@ export function cloneAndNormalizeFieldForComparison(field) {
212
207
  return;
213
208
  }
214
209
  sf.value = normalizeSubfieldValue(sf.value, sf.code, field.tag);
215
- sf.value = removeCharsThatDontCarryMeaning(sf.value, field.tag, sf.code);
216
210
  });
217
211
  normalizeField(clonedField);
218
212
  fieldRemoveDecomposedDiacritics(clonedField);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/normalizeFieldForComparison.js"],
4
- "sourcesContent": ["/*\n Note that this file contains very powerful normalizations and spells that are:\n - meant for comparing similarity/mergability of two fields (clone, normalize, compare),\n - and NOT for modifying the actual field!\n\n This is mainly used by melinda-marc-record-merge-reducers. However, also removeInferiorDataFields fixer also used this.\n Thus it is here. However, most of the testing is done via merge-reducers...\n*/\nimport clone from 'clone';\nimport {fieldStripPunctuation} from './punctuation2.js';\nimport {fieldToString, isControlSubfieldCode} from './utils.js';\n\nimport {fieldNormalizeControlNumbers/*, normalizeControlSubfieldValue*/} from './normalize-identifiers.js';\nimport createDebugLogger from 'debug';\nimport {normalizePartData, subfieldContainsPartData} from './normalizeSubfieldValueForComparison.js';\n\nconst debug = createDebugLogger('@natlibfi/melinda-marc-record-merge-reducers:normalizeFieldForComparison');\n//const debugData = debug.extend('data');\nconst debugDev = debug.extend('dev');\n\nexport function isEnnakkotietoSubfieldG(subfield) {\n if (valuelessSubfield(subfield)) {\n return false;\n }\n if (subfield.code !== 'g') {\n return false;\n }\n return subfield.value.match(/^ENNAKKOTIETO\\.?$/gui);\n}\n\nfunction debugFieldComparison(oldField, newField) { // NB: Debug-only function!\n /*\n // We may drop certain subfields:\n if (oldField.subfields.length === newField.subfields.length) {\n oldField.subfields.forEach((subfield, index) => {\n const newValue = newField.subfields[index].value;\n if (subfield.value !== newValue) {\n nvdebug(`NORMALIZE SUBFIELD: '${subfield.value}' => '${newValue}'`, debugDev);\n }\n });\n }\n */\n const oldString = fieldToString(oldField);\n const newString = fieldToString(newField);\n if (oldString === newString) {\n return;\n }\n //nvdebug(`NORMALIZE FIELD:\\n '${fieldToString(oldField)}' =>\\n '${fieldToString(newField)}'`, debugDev);\n}\n\nfunction containsHumanName(tag = '???', subfieldCode = undefined) {\n // NB! This set is for bibs! Auth has 400... What else...\n if (['100', '600', '700', '800'].includes(tag)) {\n if (subfieldCode === undefined || subfieldCode === 'a') {\n return true;\n }\n }\n // Others?\n return false;\n}\n\nfunction containsCorporateName(tag = '???', subfieldCode = undefined) {\n // NB! This set is for bibs! Auth has 400... What else...\n if (['110', '610', '710', '810'].includes(tag)) {\n if (subfieldCode === undefined || subfieldCode === 'a') {\n return true;\n }\n }\n // Others?\n return false;\n}\n\nfunction skipAllSubfieldNormalizations(value, subfieldCode, tag) {\n\n if (isEnnakkotietoSubfieldG({'code': subfieldCode, value})) {\n return true;\n }\n\n if (tag === '035' && ['a', 'z'].includes(subfieldCode)) { // A\n return true;\n }\n\n if (isControlSubfieldCode(subfieldCode)) {\n return true;\n }\n return false;\n}\n\nfunction skipSubfieldLowercase(value, subfieldCode, tag) {\n // These may contain Roman Numerals...\n if (subfieldContainsPartData(tag, subfieldCode)) {\n return true;\n }\n\n return skipAllSubfieldNormalizations(value, subfieldCode, tag);\n}\n\nfunction skipAllFieldNormalizations(tag) {\n if (['LOW', 'SID'].includes(tag)) {\n return true;\n }\n return false;\n}\n\n\nfunction subfieldValueLowercase(value, subfieldCode, tag) {\n if (skipSubfieldLowercase(value, subfieldCode, tag)) {\n return value;\n }\n\n //return value.toLowerCase();\n const newValue = value.toLowerCase();\n if (newValue !== value) {\n //nvdebug(`SVL ${tag} $${subfieldCode} '${value}' =>`, debugDev);\n //nvdebug(`SVL ${tag} $${subfieldCode} '${newValue}'`, debugDev);\n return newValue;\n }\n return value;\n}\n\nfunction subfieldLowercase(sf, tag) {\n if (valuelessSubfield(sf)) {\n return;\n }\n sf.value = subfieldValueLowercase(sf.value, sf.code, tag);\n}\n\nfunction fieldLowercase(field) {\n if (skipFieldLowercase(field)) {\n return;\n }\n\n field.subfields.forEach(sf => subfieldLowercase(sf, field.tag));\n\n function skipFieldLowercase(field) {\n if (skipAllFieldNormalizations(field.tag)) {\n return true;\n }\n // Skip non-interesting fields\n if (!containsHumanName(field.tag) && !containsCorporateName(field.tag) && !['240', '245', '630'].includes(field.tag)) {\n return true;\n }\n\n return false;\n }\n}\n\n\nfunction hack490SubfieldA(field) {\n if (field.tag !== '490') {\n return;\n }\n field.subfields.forEach(sf => removeSarja(sf));\n\n // NB! This won't work, if the punctuation has not been stripped beforehand!\n function removeSarja(subfield) {\n if (valuelessSubfield(subfield)) {\n return;\n }\n\n if (subfield.code !== 'a') {\n return;\n }\n const tmp = subfield.value.replace(/ ?-(?:[a-z]|\u00E4|\u00F6)*sarja$/u, '');\n if (tmp.length > 0) {\n subfield.value = tmp;\n return;\n }\n }\n}\n\nexport function tagAndSubfieldCodeReferToIsbn(tag, subfieldCode) {\n // NB! We don't do this to 020$z!\n if (subfieldCode === 'z' && ['765', '767', '770', '772', '773', '774', '776', '777', '780', '785', '786', '787'].includes(tag)) {\n return true;\n }\n if (tag === '020' && subfieldCode === 'a') {\n return true;\n }\n return false;\n}\n\nfunction looksLikeIsbn(value) {\n // Does not check validity!\n if (value.match(/^(?:[0-9]-?){9}(?:[0-9]-?[0-9]-?[0-9]-?)?[0-9Xx]$/u)) {\n return true;\n }\n return false;\n}\n\nfunction normalizeISBN(field) {\n if (!field.subfields) {\n return;\n }\n\n //nvdebug(`ISBN-field? ${fieldToString(field)}`);\n const relevantSubfields = field.subfields.filter(sf => tagAndSubfieldCodeReferToIsbn(field.tag, sf.code) && looksLikeIsbn(sf.value));\n relevantSubfields.forEach(sf => normalizeIsbnSubfield(sf));\n\n function normalizeIsbnSubfield(sf) {\n if (valuelessSubfield(sf)) {\n return;\n }\n //nvdebug(` ISBN-subfield? ${subfieldToString(sf)}`);\n sf.value = sf.value.replace(/-/ug, '');\n sf.value = sf.value.replace(/x/u, 'X');\n }\n\n}\n\nfunction fieldSpecificHacks(field) {\n normalizeISBN(field); // 020$a, not $z!\n hack490SubfieldA(field);\n}\n\nexport function fieldTrimSubfieldValues(field) {\n field.subfields?.forEach((sf) => {\n if (valuelessSubfield(sf)) {\n return;\n }\n sf.value = sf.value.replace(/^[ \\t\\n]+/u, '');\n sf.value = sf.value.replace(/[ \\t\\n]+$/u, '');\n sf.value = sf.value.replace(/[ \\t\\n]+/gu, ' ');\n });\n}\n\nfunction fieldRemoveDecomposedDiacritics(field) {\n // Raison d'\u00EAtre/motivation: \"Sir\u00E9n\" and diacriticless \"Siren\" might refer to a same surname, so this normalization\n // allows us to compare authors and avoid duplicate fields.\n field.subfields.forEach((sf) => {\n if (valuelessSubfield(sf)) {\n return;\n }\n sf.value = removeDecomposedDiacritics(sf.value);\n });\n}\n\nfunction removeDecomposedDiacritics(value = '') {\n // NB #1: Does nothing to precomposed letters. Do String.normalize('NFD') first, if you want to handle them.\n // NB #2: Finnish letters '\u00E5', '\u00E4', '\u00F6', '\u00C5', \u00C4', and '\u00D6' should be handled (=precomposed) before calling this. (= keep them as is)\n // NB #3: Calling our very own fixComposition() before this function handles both #1 and #2.\n return String(value).replace(/\\p{Diacritic}/gu, '');\n}\n\nfunction normalizeSubfieldValue(value, subfieldCode, tag) {\n // NB! For comparison of values only\n /* eslint-disable */\n value = subfieldValueLowercase(value, subfieldCode, tag);\n\n // Normalize: s. = sivut = pp.\n value = normalizePartData(value, subfieldCode, tag);\n value = value.replace(/^\\[([^[\\]]+)\\]/gu, '$1'); // eslint-disable-line functional/immutable-data\n\n if (['130', '730'].includes(tag) && subfieldCode === 'a') {\n value = value.replace(' : ', ', '); // \"Halloween ends (elokuva, 2022)\" vs \"Halloween ends (elokuva : 2023)\"\n }\n /* eslint-enable */\n\n // Not going to do these in the foreseeable future, but keeping them here for discussion:\n // Possible normalizations include but are not limited to:\n // \u00F8 => \u00F6? Might be language dependent: 041 $a fin => \u00F6, 041 $a eng => o?\n // \u00D8 => \u00D6?\n // \u00DF => ss\n // \u00FE => th (NB! Both upper and lower case)\n // ...\n // Probably nots:\n // \u00FC => y (probably not, though this correlates with Finnish letter-to-sound rules)\n // w => v (OK for Finnish sorting in certain cases, but we are not here, are we?)\n // I guess we should use decomposed values in code here. (Not sure what composition my examples above use.)\n return value;\n}\n\nexport function cloneAndRemovePunctuation(field) {\n const clonedField = clone(field);\n if (fieldSkipNormalization(field)) {\n return clonedField;\n }\n fieldStripPunctuation(clonedField);\n fieldTrimSubfieldValues(clonedField);\n debugDev('PUNC');\n debugFieldComparison(field, clonedField);\n\n return clonedField;\n}\n\nfunction removeCharsThatDontCarryMeaning(value, tag, subfieldCode) {\n if (tag === '080') {\n return value;\n }\n\n // 3\" refers to inches, but as this is for comparison only we don't mind...\n value = value.replace(/['\u2018\u2019\"\u201E\u201C\u201D\u00AB\u00BB]/gu, ''); // MET-570 et al. Subset of https://hexdocs.pm/ex_unicode/Unicode.Category.QuoteMarks.html\n // MRA-273: Handle X00$a name initials.\n // NB #1: that we remove spaces for comparison (as it simpler), though actually space should be used. Doesn't matter as this is comparison only.\n // NB #2: we might/should eventually write a validator/fixer that adds those spaces. After that point, this expection should become obsolete.\n if (subfieldCode === 'a' && ['100', '400', '600', '700', '800'].includes(tag)) { // 400 is used in auth records. It's not a bib field at all.\n value = value.replace(/([A-Z]|\u00C5|\u00C4|\u00D6)\\. +/ugi, '$1.');\n }\n\n return value;\n}\n\nfunction normalizeField(field) {\n //sf.value = removeDecomposedDiacritics(sf.value);\n fieldStripPunctuation(field);\n fieldLowercase(field);\n fieldNormalizeControlNumbers(field); // FIN11 vs FI-MELINDA etc.\n return field;\n}\n\nexport function cloneAndNormalizeFieldForComparison(field) {\n // NB! This new field is for comparison purposes only.\n // Some of the normalizations might be considered a bit overkill for other purposes.\n const clonedField = clone(field);\n if (fieldSkipNormalization(field)) {\n return clonedField;\n }\n clonedField.subfields.forEach((sf) => { // Do this for all fields or some fields?\n if (valuelessSubfield(sf)) {\n return;\n }\n sf.value = normalizeSubfieldValue(sf.value, sf.code, field.tag);\n sf.value = removeCharsThatDontCarryMeaning(sf.value, field.tag, sf.code);\n });\n\n normalizeField(clonedField);\n fieldRemoveDecomposedDiacritics(clonedField);\n fieldSpecificHacks(clonedField);\n fieldTrimSubfieldValues(clonedField);\n\n\n debugFieldComparison(field, clonedField); // For debugging purposes only\n\n return clonedField;\n}\n\nfunction fieldSkipNormalization(field) {\n if (!field.subfields || ['018', '066', '080', '083'].includes(field.tag)) {\n return true;\n }\n return false;\n}\n\nfunction valuelessSubfield(sf) {\n return sf.value === undefined;\n}"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/*\n Note that this file contains very powerful normalizations and spells that are:\n - meant for comparing similarity/mergability of two fields (clone, normalize, compare),\n - and NOT for modifying the actual field!\n\n This is mainly used by melinda-marc-record-merge-reducers. However, also removeInferiorDataFields fixer also used this.\n Thus it is here. However, most of the testing is done via merge-reducers...\n*/\nimport clone from 'clone';\nimport {fieldStripPunctuation} from './punctuation2.js';\nimport {fieldToString, isContentSubfieldCode} from './utils.js';\n\nimport {fieldNormalizeControlNumbers/*, normalizeControlSubfieldValue*/} from './normalize-identifiers.js';\nimport createDebugLogger from 'debug';\nimport {normalizePartData, subfieldContainsPartData} from './normalizeSubfieldValueForComparison.js';\nimport {isEnnakkotietoSubfield} from './prepublicationUtils.js';\nimport {getSynonym} from './merge-fields/worldKnowledge.js';\n\nconst debug = createDebugLogger('@natlibfi/melinda-marc-record-merge-reducers:normalizeFieldForComparison');\n//const debugData = debug.extend('data');\nconst debugDev = debug.extend('dev');\n\nfunction debugFieldComparison(oldField, newField) { // NB: Debug-only function!\n /*\n // We may drop certain subfields:\n if (oldField.subfields.length === newField.subfields.length) {\n oldField.subfields.forEach((subfield, index) => {\n const newValue = newField.subfields[index].value;\n if (subfield.value !== newValue) {\n nvdebug(`NORMALIZE SUBFIELD: '${subfield.value}' => '${newValue}'`, debugDev);\n }\n });\n }\n */\n const oldString = fieldToString(oldField);\n const newString = fieldToString(newField);\n if (oldString === newString) {\n return;\n }\n //nvdebug(`NORMALIZE FIELD:\\n '${fieldToString(oldField)}' =>\\n '${fieldToString(newField)}'`, debugDev);\n}\n\nfunction containsHumanName(tag = '???', subfieldCode = undefined) {\n // NB! This set is for bibs! Auth has 400... What else...\n if (['100', '600', '700', '800'].includes(tag)) {\n if (subfieldCode === undefined || subfieldCode === 'a') {\n return true;\n }\n }\n // Others?\n return false;\n}\n\nfunction containsCorporateName(tag = '???', subfieldCode = undefined) {\n // NB! This set is for bibs! Auth has 410... What else...\n if (['110', '610', '710', '810'].includes(tag)) {\n if (subfieldCode === undefined || subfieldCode === 'a') {\n return true;\n }\n }\n // Others?\n return false;\n}\n\nfunction skipAllSubfieldNormalizations(value, subfieldCode, tag) {\n\n if (isEnnakkotietoSubfield({'code': subfieldCode, value})) {\n return true;\n }\n\n if (tag === '035' && ['a', 'z'].includes(subfieldCode)) {\n return true;\n }\n\n if (!isContentSubfieldCode(subfieldCode, tag)) {\n return true;\n }\n return false;\n}\n\nfunction skipSubfieldLowercase(value, subfieldCode, tag) {\n // These may contain Roman Numerals...\n if (subfieldContainsPartData(tag, subfieldCode)) {\n return true;\n }\n\n return skipAllSubfieldNormalizations(value, subfieldCode, tag);\n}\n\nfunction skipAllFieldNormalizations(tag) {\n if (['LOW', 'SID'].includes(tag)) {\n return true;\n }\n return false;\n}\n\n\nfunction subfieldValueLowercase(value, subfieldCode, tag) {\n if (skipSubfieldLowercase(value, subfieldCode, tag)) {\n return value;\n }\n\n //return value.toLowerCase();\n const newValue = value.toLowerCase();\n if (newValue !== value) {\n //nvdebug(`SVL ${tag} $${subfieldCode} '${value}' =>`, debugDev);\n //nvdebug(`SVL ${tag} $${subfieldCode} '${newValue}'`, debugDev);\n return newValue;\n }\n return value;\n}\n\nfunction subfieldLowercase(sf, tag) {\n if (valuelessSubfield(sf)) {\n return;\n }\n sf.value = subfieldValueLowercase(sf.value, sf.code, tag);\n}\n\nfunction fieldLowercase(field) {\n if (skipFieldLowercase(field)) {\n return;\n }\n\n field.subfields.forEach(sf => subfieldLowercase(sf, field.tag));\n\n function skipFieldLowercase(field) {\n if (skipAllFieldNormalizations(field.tag)) {\n return true;\n }\n // Skip non-interesting fields\n if (!containsHumanName(field.tag) && !containsCorporateName(field.tag) && !['240', '245', '630'].includes(field.tag)) {\n return true;\n }\n\n return false;\n }\n}\n\n\nfunction hack490SubfieldA(field) {\n if (field.tag !== '490') {\n return;\n }\n field.subfields.forEach(sf => removeSarja(sf));\n\n // NB! This won't work, if the punctuation has not been stripped beforehand!\n function removeSarja(subfield) {\n if (valuelessSubfield(subfield)) {\n return;\n }\n\n if (subfield.code !== 'a') {\n return;\n }\n const tmp = subfield.value.replace(/ ?-(?:[a-z]|\u00E4|\u00F6)*sarja$/u, '');\n if (tmp.length > 0) {\n subfield.value = tmp;\n return;\n }\n }\n}\n\nexport function tagAndSubfieldCodeReferToIsbn(tag, subfieldCode) {\n // NB! We don't do this to 020$z!\n if (subfieldCode === 'z' && ['765', '767', '770', '772', '773', '774', '776', '777', '780', '785', '786', '787'].includes(tag)) {\n return true;\n }\n if (tag === '020' && subfieldCode === 'a') {\n return true;\n }\n return false;\n}\n\nfunction looksLikeIsbn(value) {\n // Does not check validity!\n if (value.match(/^(?:[0-9]-?){9}(?:[0-9]-?[0-9]-?[0-9]-?)?[0-9Xx]$/u)) {\n return true;\n }\n return false;\n}\n\nfunction normalizeISBN(field) {\n if (!field.subfields) {\n return;\n }\n\n //nvdebug(`ISBN-field? ${fieldToString(field)}`);\n const relevantSubfields = field.subfields.filter(sf => tagAndSubfieldCodeReferToIsbn(field.tag, sf.code) && looksLikeIsbn(sf.value));\n relevantSubfields.forEach(sf => normalizeIsbnSubfield(sf));\n\n function normalizeIsbnSubfield(sf) {\n if (valuelessSubfield(sf)) {\n return;\n }\n //nvdebug(` ISBN-subfield? ${subfieldToString(sf)}`);\n sf.value = sf.value.replace(/-/ug, '');\n sf.value = sf.value.replace(/x/u, 'X');\n }\n\n}\n\nfunction fieldSpecificHacks(field) {\n normalizeISBN(field); // 020$a, not $z!\n hack490SubfieldA(field);\n}\n\nexport function fieldTrimSubfieldValues(field) {\n field.subfields?.forEach((sf) => {\n if (valuelessSubfield(sf)) {\n return;\n }\n sf.value = sf.value.replace(/^[ \\t\\n]+/u, '');\n sf.value = sf.value.replace(/[ \\t\\n]+$/u, '');\n sf.value = sf.value.replace(/[ \\t\\n]+/gu, ' ');\n });\n}\n\nfunction fieldRemoveDecomposedDiacritics(field) {\n // Raison d'\u00EAtre/motivation: \"Sir\u00E9n\" and diacriticless \"Siren\" might refer to a same surname, so this normalization\n // allows us to compare authors and avoid duplicate fields.\n field.subfields.forEach((sf) => {\n if (valuelessSubfield(sf)) {\n return;\n }\n sf.value = removeDecomposedDiacritics(sf.value);\n });\n}\n\nfunction removeDecomposedDiacritics(value = '') {\n // NB #1: Does nothing to precomposed letters. Do String.normalize('NFD') first, if you want to handle them.\n // NB #2: Finnish letters '\u00E5', '\u00E4', '\u00F6', '\u00C5', \u00C4', and '\u00D6' should be handled (=precomposed) before calling this. (= keep them as is)\n // NB #3: Calling our very own fixComposition() before this function handles both #1 and #2.\n return String(value).replace(/\\p{Diacritic}/gu, '');\n}\n\nfunction normalizeSubfieldValue(value, subfieldCode, tag) {\n // NB! For comparison of values only\n /* beslint-disable */\n value = removeCharsThatDontCarryMeaning(value, tag, subfieldCode);\n value = getSynonym(tag, subfieldCode, value); // Must be done before punc stripping and lowercasing...\n value = subfieldValueLowercase(value, subfieldCode, tag);\n\n\n // Normalize: s. = sivut = pp.\n value = normalizePartData(value, subfieldCode, tag);\n value = value.replace(/^\\[([^[\\]]+)\\]/gu, '$1');\n\n if (['130', '730'].includes(tag) && subfieldCode === 'a') {\n value = value.replace(' : ', ', '); // \"Halloween ends (elokuva, 2022)\" vs \"Halloween ends (elokuva : 2023)\"\n }\n /* beslint-enable */\n\n // Not going to do these in the foreseeable future, but keeping them here for discussion:\n // Possible normalizations include but are not limited to:\n // \u00F8 => \u00F6? Might be language dependent: 041 $a fin => \u00F6, 041 $a eng => o?\n // \u00D8 => \u00D6?\n // \u00DF => ss\n // \u00FE => th (NB! Both upper and lower case)\n // ...\n // Probably nots:\n // \u00FC => y (probably not, though this correlates with Finnish letter-to-sound rules)\n // w => v (OK for Finnish sorting in certain cases, but we are not here, are we?)\n // I guess we should use decomposed values in code here. (Not sure what composition my examples above use.)\n return value;\n}\n\nexport function cloneAndRemovePunctuation(field) {\n const clonedField = clone(field);\n if (fieldSkipNormalization(field)) {\n return clonedField;\n }\n fieldStripPunctuation(clonedField);\n fieldTrimSubfieldValues(clonedField);\n debugDev('PUNC');\n debugFieldComparison(field, clonedField);\n\n return clonedField;\n}\n\nfunction removeCharsThatDontCarryMeaning(value, tag, subfieldCode) {\n if (tag === '080') {\n return value;\n }\n\n // 3\" refers to inches, but as this is for comparison only we don't mind...\n value = value.replace(/['\u2018\u2019\"\u201E\u201C\u201D\u00AB\u00BB]/gu, ''); // MET-570 et al. Subset of https://hexdocs.pm/ex_unicode/Unicode.Category.QuoteMarks.html\n // MRA-273: Handle X00$a name initials.\n // NB #1: that we remove spaces for comparison (as it simpler), though actually space should be used. Doesn't matter as this is comparison only.\n // NB #2: we might/should eventually write a validator/fixer that adds those spaces. After that point, this expection should become obsolete.\n if (subfieldCode === 'a' && ['100', '400', '600', '700', '800'].includes(tag)) { // 400 is used in auth records. It's not a bib field at all.\n value = value.replace(/([A-Z]|\u00C5|\u00C4|\u00D6)\\. +/ugi, '$1.');\n }\n\n return value;\n}\n\nfunction normalizeField(field) {\n //sf.value = removeDecomposedDiacritics(sf.value);\n fieldStripPunctuation(field);\n fieldLowercase(field);\n fieldNormalizeControlNumbers(field); // FIN11 vs FI-MELINDA etc.\n return field;\n}\n\nexport function cloneAndNormalizeFieldForComparison(field) {\n // NB! This new field is for comparison purposes only.\n // Some of the normalizations might be considered a bit overkill for other purposes.\n const clonedField = cloneAndRemovePunctuation(field); // was only clone(field)\n\n if (fieldSkipNormalization(field)) {\n return clonedField;\n }\n clonedField.subfields.forEach((sf) => { // Do this for all fields or some fields?\n if (valuelessSubfield(sf)) {\n return;\n }\n\n sf.value = normalizeSubfieldValue(sf.value, sf.code, field.tag);\n //sf.value = normalizeForSamenessCheck(field.tag, sf.code, sf.value);\n\n\n });\n\n normalizeField(clonedField);\n fieldRemoveDecomposedDiacritics(clonedField);\n fieldSpecificHacks(clonedField);\n fieldTrimSubfieldValues(clonedField);\n\n\n debugFieldComparison(field, clonedField); // For debugging purposes only\n\n return clonedField;\n}\n\nfunction fieldSkipNormalization(field) {\n if (!field.subfields || ['018', '066', '080', '083'].includes(field.tag)) {\n return true;\n }\n return false;\n}\n\nfunction valuelessSubfield(sf) {\n return sf.value === undefined;\n}"],
5
+ "mappings": "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",
6
6
  "names": ["field"]
7
7
  }
@@ -136,7 +136,7 @@ export function removeWorsePrepubField594s(record) {
136
136
  nonBest.forEach((field) => record.removeField(field));
137
137
  }
138
138
  export function isEnnakkotietoSubfield(subfield) {
139
- if (subfield.code !== "9" && subfield.code !== "g") {
139
+ if (!["g", "9", "7"].includes(subfield.code)) {
140
140
  return false;
141
141
  }
142
142
  if (subfield.value.length <= 13) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/prepublicationUtils.js"],
4
- "sourcesContent": ["import {fieldHasSubfield, nvdebug, nvdebugFieldArray} from './utils.js';\nimport createDebugLogger from 'debug';\n\nconst debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:prepublicationUtils');\n//const debugData = debug.extend('data');\nconst debugDev = debug.extend('dev');\n\nconst KONEELLISESTI_TUOTETTU_TIETUE = 1; // Best\nconst TARKISTETTU_ENNAKKOTIETO = 2;\nconst ENNAKKOTIETO = 3;\n//const EI_TASOA = 4;\n\nconst encodingLevelPreferenceArray = [' ', '1', '3', '4', '5', '2', '7', 'u', 'z', '8']; // MET-145\nconst prepublicationLevelIndex = encodingLevelPreferenceArray.indexOf('8');\n\nexport function prepublicationLevelIsKoneellisestiTuotettuTietueOrTarkistettuEnnakkotieto(prepublicationLevel) {\n return prepublicationLevel === KONEELLISESTI_TUOTETTU_TIETUE || prepublicationLevel === TARKISTETTU_ENNAKKOTIETO;\n}\n\n\nexport function encodingLevelIsBetterThanPrepublication(encodingLevel) {\n const index = encodingLevelPreferenceArray.indexOf(encodingLevel);\n return index > -1 && index < prepublicationLevelIndex;\n}\n\n// These three functions below all refer to field 500:\nexport function fieldRefersToKoneellisestiTuotettuTietue(field) {\n return field.subfields?.some(sf => sf.code === 'a' && sf.value.match(/^Koneellisesti tuotettu tietue/u));\n}\n\n\nexport function fieldRefersToTarkistettuEnnakkotieto(field) {\n return field.subfields?.some(sf => sf.code === 'a' && sf.value.match(/^tarkistettu ennakkotieto/ui));\n}\n\n\nexport function fieldRefersToEnnakkotieto(field) {\n // NB! This no longer matches 'TARKISTETTU ENNAKKOTIETO' case! Bug or Feature?\n if (field.subfields?.some(sf => sf.code === 'a' && sf.value.match(/^ennakkotieto(?:$|[. ])/ui))) {\n return true;\n }\n\n // MRA-420: \"EI VIEL\u00C4 ILMESTYNYT\" is a Helmet note, that is semantically similar to ENNAKKOTIETO:\n return field.subfields?.some(sf => sf.code === 'a' && sf.value.match(/^EI VIEL\u00C4 ILMESTYNYT/u));\n}\n\n\nexport function firstFieldHasBetterPrepubEncodingLevel(field1, field2) {\n if (fieldRefersToKoneellisestiTuotettuTietue(field2)) {\n return false;\n }\n if (fieldRefersToKoneellisestiTuotettuTietue(field1)) {\n return true;\n }\n if (fieldRefersToTarkistettuEnnakkotieto(field2)) {\n return false;\n }\n if (fieldRefersToTarkistettuEnnakkotieto(field1)) {\n return true;\n }\n if (fieldRefersToEnnakkotieto(field2)) {\n return false;\n }\n if (fieldRefersToEnnakkotieto(field1)) {\n return true;\n }\n return false;\n}\n\nexport function getRelevant5XXFields(record, f500 = false, f594 = false) {\n const cands = actualGetFields();\n //nvdebugFieldArray(cands, 'gR5XXa: ', debugDev);\n const filtered = cands.filter(field => hasRelevantPrepubData(field));\n //nvdebugFieldArray(filtered, 'gR5XXb: ', debugDev);\n return filtered;\n\n //return actualGetFields().filter(field => hasRelevantPrepubData(field));\n\n function hasRelevantPrepubData(field) {\n // Check prepub ($a):\n if (!fieldRefersToKoneellisestiTuotettuTietue(field) && !fieldRefersToTarkistettuEnnakkotieto(field) && !fieldRefersToEnnakkotieto(field)) {\n return false;\n }\n // Check relevance (594$5):\n if (field.tag === '500') {\n return field.subfields.every(sf => sf.code !== '5'); //true;\n }\n return field.subfields.some(sf => sf.code === '5' && ['FENNI', 'FIKKA', 'VIOLA'].includes(sf.value));\n }\n\n function actualGetFields() {\n if (f500 && f594) {\n return record.get(/^(?:500|594)$/u);\n }\n if (f500) {\n return record.get(/^500$/u);\n }\n if (f594) {\n return record.get(/^594$/u);\n }\n return [];\n }\n\n}\n\n\n// Very similar to getPrepublicationLevel() in melinda-record-match-validator's getPrepublicationLevel()...\n// We should use that and not have a copy here...\nexport function getPrepublicationLevel(record, f500 = false, f594 = false) {\n // Smaller return value is better\n const fields = getRelevant5XXFields(record, f500, f594);\n\n if (!fields) {\n return null;\n }\n if (fields.some(f => fieldRefersToKoneellisestiTuotettuTietue(f))) {\n return KONEELLISESTI_TUOTETTU_TIETUE;\n }\n\n if (fields.some(f => fieldRefersToTarkistettuEnnakkotieto(f))) {\n return TARKISTETTU_ENNAKKOTIETO;\n }\n\n if (fields.some(f => fieldRefersToEnnakkotieto(f))) {\n return ENNAKKOTIETO;\n }\n\n return null;\n}\n\n\nexport function baseHasEqualOrHigherEncodingLevel(baseEncodingLevel, sourceEncodingLevel) {\n const baseIndex = encodingLevelPreferenceArray.indexOf(baseEncodingLevel);\n const sourceIndex = encodingLevelPreferenceArray.indexOf(sourceEncodingLevel);\n\n if (baseIndex === -1) {\n // Base wins if both are bad:\n return sourceIndex === -1;\n }\n return baseIndex <= sourceIndex;\n}\n\n\nfunction hasFikkaLOW(record) {\n return record.fields.some(field => field.tag === 'LOW' && fieldHasSubfield(field, 'a', 'FIKKA'));\n}\n\n\nfunction hasNatLibFi042(record) {\n return record.fields.some(field => field.tag === '042' && (fieldHasSubfield(field, 'a', 'finb') || fieldHasSubfield(field, 'a', 'finbd')));\n}\n\n\nexport function isFikkaRecord(record) {\n // NB! Does not include Humaniora. Pienpainatteet (not that they'd have duplicates)?\n return hasFikkaLOW(record) && hasNatLibFi042(record);\n}\n\n\nexport function getEncodingLevel(record) {\n return record.leader.substring(17, 18);\n}\n\n\nexport function deleteAllPrepublicationNotesFromField500InNonPubRecord(record) {\n const encodingLevel = getEncodingLevel(record);\n // Skip prepublication (or theoretically even worse) records:\n if (!encodingLevelIsBetterThanPrepublication(encodingLevel)) {\n //if (['2', '8'].includes(encodingLevel)) { // MET-306: added '2' here\n return;\n }\n\n // MET-306: keep \"koneellisesti tuotettu tietue\" if encoding level is '2':\n const f500 = getRelevant5XXFields(record, true, false).filter(field => encodingLevel === '2' ? !fieldRefersToKoneellisestiTuotettuTietue(field) : true);\n if (f500.length === 0) {\n return;\n }\n\n\n nvdebug(`Delete all ${f500.length} instance(s) of field 500`, debugDev);\n f500.forEach(field => record.removeField(field));\n}\n\n\nexport function removeWorsePrepubField500s(record) {\n // Remove lower-level entries:\n const fields = getRelevant5XXFields(record, true, false); // 500=false, 594=true\n nvdebugFieldArray(fields, ' Candidates for non-best 500 b4 filtering: ', debugDev);\n const nonBest = fields.filter(field => fields.some(field2 => firstFieldHasBetterPrepubEncodingLevel(field2, field)));\n nvdebugFieldArray(nonBest, ' Remove non-best 500: ', debugDev);\n nonBest.forEach(field => record.removeField(field));\n}\n\n\nexport function removeWorsePrepubField594s(record) {\n // Remove lower-level entries:\n const fields594 = getRelevant5XXFields(record, false, true); // 500=false, 594=true\n nvdebugFieldArray(fields594, ' Candidates for non-best 594 b4 filtering: ', debugDev);\n const nonBest = fields594.filter(field => fields594.some(field2 => firstFieldHasBetterPrepubEncodingLevel(field2, field)));\n nvdebugFieldArray(nonBest, ' Remove non-best 594: ', debugDev);\n nonBest.forEach(field => record.removeField(field));\n}\n\n\nexport function isEnnakkotietoSubfield(subfield) {\n if (subfield.code !== '9' && subfield.code !== 'g') {\n return false;\n }\n // Length <= 13 allows punctuation, but does not require it:\n if (subfield.value.length <= 13) {\n const coreString = subfield.value.substr(0, 12);\n if (coreString.toLowerCase() === 'ennakkotieto') { // Lowercase term first seen in MET-575\n return true;\n }\n }\n return false;\n}\n\nexport function isEnnakkotietoField(field) {\n return field.subfields.some(sf => isEnnakkotietoSubfield(sf));\n}\n\nexport function isKingOfTheHill(field, opposingFields) {\n // Field is no better than at least one of the opposing fields\n return opposingFields.every(opposingField => firstFieldHasBetterPrepubEncodingLevel(field, opposingField));\n}\n\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {fieldHasSubfield, nvdebug, nvdebugFieldArray} from './utils.js';\nimport createDebugLogger from 'debug';\n\nconst debug = createDebugLogger('@natlibfi/marc-record-validators-melinda:prepublicationUtils');\n//const debugData = debug.extend('data');\nconst debugDev = debug.extend('dev');\n\nconst KONEELLISESTI_TUOTETTU_TIETUE = 1; // Best\nconst TARKISTETTU_ENNAKKOTIETO = 2;\nconst ENNAKKOTIETO = 3;\n//const EI_TASOA = 4;\n\nconst encodingLevelPreferenceArray = [' ', '1', '3', '4', '5', '2', '7', 'u', 'z', '8']; // MET-145\nconst prepublicationLevelIndex = encodingLevelPreferenceArray.indexOf('8');\n\nexport function prepublicationLevelIsKoneellisestiTuotettuTietueOrTarkistettuEnnakkotieto(prepublicationLevel) {\n return prepublicationLevel === KONEELLISESTI_TUOTETTU_TIETUE || prepublicationLevel === TARKISTETTU_ENNAKKOTIETO;\n}\n\n\nexport function encodingLevelIsBetterThanPrepublication(encodingLevel) {\n const index = encodingLevelPreferenceArray.indexOf(encodingLevel);\n return index > -1 && index < prepublicationLevelIndex;\n}\n\n// These three functions below all refer to field 500:\nexport function fieldRefersToKoneellisestiTuotettuTietue(field) {\n return field.subfields?.some(sf => sf.code === 'a' && sf.value.match(/^Koneellisesti tuotettu tietue/u));\n}\n\n\nexport function fieldRefersToTarkistettuEnnakkotieto(field) {\n return field.subfields?.some(sf => sf.code === 'a' && sf.value.match(/^tarkistettu ennakkotieto/ui));\n}\n\n\nexport function fieldRefersToEnnakkotieto(field) {\n // NB! This no longer matches 'TARKISTETTU ENNAKKOTIETO' case! Bug or Feature?\n if (field.subfields?.some(sf => sf.code === 'a' && sf.value.match(/^ennakkotieto(?:$|[. ])/ui))) {\n return true;\n }\n\n // MRA-420: \"EI VIEL\u00C4 ILMESTYNYT\" is a Helmet note, that is semantically similar to ENNAKKOTIETO:\n return field.subfields?.some(sf => sf.code === 'a' && sf.value.match(/^EI VIEL\u00C4 ILMESTYNYT/u));\n}\n\n\nexport function firstFieldHasBetterPrepubEncodingLevel(field1, field2) {\n if (fieldRefersToKoneellisestiTuotettuTietue(field2)) {\n return false;\n }\n if (fieldRefersToKoneellisestiTuotettuTietue(field1)) {\n return true;\n }\n if (fieldRefersToTarkistettuEnnakkotieto(field2)) {\n return false;\n }\n if (fieldRefersToTarkistettuEnnakkotieto(field1)) {\n return true;\n }\n if (fieldRefersToEnnakkotieto(field2)) {\n return false;\n }\n if (fieldRefersToEnnakkotieto(field1)) {\n return true;\n }\n return false;\n}\n\nexport function getRelevant5XXFields(record, f500 = false, f594 = false) {\n const cands = actualGetFields();\n //nvdebugFieldArray(cands, 'gR5XXa: ', debugDev);\n const filtered = cands.filter(field => hasRelevantPrepubData(field));\n //nvdebugFieldArray(filtered, 'gR5XXb: ', debugDev);\n return filtered;\n\n //return actualGetFields().filter(field => hasRelevantPrepubData(field));\n\n function hasRelevantPrepubData(field) {\n // Check prepub ($a):\n if (!fieldRefersToKoneellisestiTuotettuTietue(field) && !fieldRefersToTarkistettuEnnakkotieto(field) && !fieldRefersToEnnakkotieto(field)) {\n return false;\n }\n // Check relevance (594$5):\n if (field.tag === '500') {\n return field.subfields.every(sf => sf.code !== '5'); //true;\n }\n return field.subfields.some(sf => sf.code === '5' && ['FENNI', 'FIKKA', 'VIOLA'].includes(sf.value));\n }\n\n function actualGetFields() {\n if (f500 && f594) {\n return record.get(/^(?:500|594)$/u);\n }\n if (f500) {\n return record.get(/^500$/u);\n }\n if (f594) {\n return record.get(/^594$/u);\n }\n return [];\n }\n\n}\n\n\n// Very similar to getPrepublicationLevel() in melinda-record-match-validator's getPrepublicationLevel()...\n// We should use that and not have a copy here...\nexport function getPrepublicationLevel(record, f500 = false, f594 = false) {\n // Smaller return value is better\n const fields = getRelevant5XXFields(record, f500, f594);\n\n if (!fields) {\n return null;\n }\n if (fields.some(f => fieldRefersToKoneellisestiTuotettuTietue(f))) {\n return KONEELLISESTI_TUOTETTU_TIETUE;\n }\n\n if (fields.some(f => fieldRefersToTarkistettuEnnakkotieto(f))) {\n return TARKISTETTU_ENNAKKOTIETO;\n }\n\n if (fields.some(f => fieldRefersToEnnakkotieto(f))) {\n return ENNAKKOTIETO;\n }\n\n return null;\n}\n\n\nexport function baseHasEqualOrHigherEncodingLevel(baseEncodingLevel, sourceEncodingLevel) {\n const baseIndex = encodingLevelPreferenceArray.indexOf(baseEncodingLevel);\n const sourceIndex = encodingLevelPreferenceArray.indexOf(sourceEncodingLevel);\n\n if (baseIndex === -1) {\n // Base wins if both are bad:\n return sourceIndex === -1;\n }\n return baseIndex <= sourceIndex;\n}\n\n\nfunction hasFikkaLOW(record) {\n return record.fields.some(field => field.tag === 'LOW' && fieldHasSubfield(field, 'a', 'FIKKA'));\n}\n\n\nfunction hasNatLibFi042(record) {\n return record.fields.some(field => field.tag === '042' && (fieldHasSubfield(field, 'a', 'finb') || fieldHasSubfield(field, 'a', 'finbd')));\n}\n\n\nexport function isFikkaRecord(record) {\n // NB! Does not include Humaniora. Pienpainatteet (not that they'd have duplicates)?\n return hasFikkaLOW(record) && hasNatLibFi042(record);\n}\n\n\nexport function getEncodingLevel(record) {\n return record.leader.substring(17, 18);\n}\n\n\nexport function deleteAllPrepublicationNotesFromField500InNonPubRecord(record) {\n const encodingLevel = getEncodingLevel(record);\n // Skip prepublication (or theoretically even worse) records:\n if (!encodingLevelIsBetterThanPrepublication(encodingLevel)) {\n //if (['2', '8'].includes(encodingLevel)) { // MET-306: added '2' here\n return;\n }\n\n // MET-306: keep \"koneellisesti tuotettu tietue\" if encoding level is '2':\n const f500 = getRelevant5XXFields(record, true, false).filter(field => encodingLevel === '2' ? !fieldRefersToKoneellisestiTuotettuTietue(field) : true);\n if (f500.length === 0) {\n return;\n }\n\n\n nvdebug(`Delete all ${f500.length} instance(s) of field 500`, debugDev);\n f500.forEach(field => record.removeField(field));\n}\n\n\nexport function removeWorsePrepubField500s(record) {\n // Remove lower-level entries:\n const fields = getRelevant5XXFields(record, true, false); // 500=false, 594=true\n nvdebugFieldArray(fields, ' Candidates for non-best 500 b4 filtering: ', debugDev);\n const nonBest = fields.filter(field => fields.some(field2 => firstFieldHasBetterPrepubEncodingLevel(field2, field)));\n nvdebugFieldArray(nonBest, ' Remove non-best 500: ', debugDev);\n nonBest.forEach(field => record.removeField(field));\n}\n\n\nexport function removeWorsePrepubField594s(record) {\n // Remove lower-level entries:\n const fields594 = getRelevant5XXFields(record, false, true); // 500=false, 594=true\n nvdebugFieldArray(fields594, ' Candidates for non-best 594 b4 filtering: ', debugDev);\n const nonBest = fields594.filter(field => fields594.some(field2 => firstFieldHasBetterPrepubEncodingLevel(field2, field)));\n nvdebugFieldArray(nonBest, ' Remove non-best 594: ', debugDev);\n nonBest.forEach(field => record.removeField(field));\n}\n\n\nexport function isEnnakkotietoSubfield(subfield) {\n if (!['g', '9', '7'].includes(subfield.code)) {\n return false;\n }\n // Length <= 13 allows punctuation, but does not require it:\n if (subfield.value.length <= 13) {\n const coreString = subfield.value.substr(0, 12);\n if (coreString.toLowerCase() === 'ennakkotieto') { // Lowercase term first seen in MET-575\n return true;\n }\n }\n return false;\n}\n\nexport function isEnnakkotietoField(field) {\n return field.subfields.some(sf => isEnnakkotietoSubfield(sf));\n}\n\nexport function isKingOfTheHill(field, opposingFields) {\n // Field is no better than at least one of the opposing fields\n return opposingFields.every(opposingField => firstFieldHasBetterPrepubEncodingLevel(field, opposingField));\n}\n\n"],
5
+ "mappings": "AAAA,SAAQ,kBAAkB,SAAS,yBAAwB;AAC3D,OAAO,uBAAuB;AAE9B,MAAM,QAAQ,kBAAkB,8DAA8D;AAE9F,MAAM,WAAW,MAAM,OAAO,KAAK;AAEnC,MAAM,gCAAgC;AACtC,MAAM,2BAA2B;AACjC,MAAM,eAAe;AAGrB,MAAM,+BAA+B,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,KAAK,GAAG;AACtF,MAAM,2BAA2B,6BAA6B,QAAQ,GAAG;AAElE,gBAAS,0EAA0E,qBAAqB;AAC7G,SAAO,wBAAwB,iCAAiC,wBAAwB;AAC1F;AAGO,gBAAS,wCAAwC,eAAe;AACrE,QAAM,QAAQ,6BAA6B,QAAQ,aAAa;AAChE,SAAO,QAAQ,MAAM,QAAQ;AAC/B;AAGO,gBAAS,yCAAyC,OAAO;AAC9D,SAAO,MAAM,WAAW,KAAK,QAAM,GAAG,SAAS,OAAO,GAAG,MAAM,MAAM,iCAAiC,CAAC;AACzG;AAGO,gBAAS,qCAAqC,OAAO;AAC1D,SAAO,MAAM,WAAW,KAAK,QAAM,GAAG,SAAS,OAAO,GAAG,MAAM,MAAM,6BAA6B,CAAC;AACrG;AAGO,gBAAS,0BAA0B,OAAO;AAE/C,MAAI,MAAM,WAAW,KAAK,QAAM,GAAG,SAAS,OAAO,GAAG,MAAM,MAAM,2BAA2B,CAAC,GAAG;AAC/F,WAAO;AAAA,EACT;AAGA,SAAO,MAAM,WAAW,KAAK,QAAM,GAAG,SAAS,OAAO,GAAG,MAAM,MAAM,uBAAuB,CAAC;AAC/F;AAGO,gBAAS,uCAAuC,QAAQ,QAAQ;AACrE,MAAI,yCAAyC,MAAM,GAAG;AACpD,WAAO;AAAA,EACT;AACA,MAAI,yCAAyC,MAAM,GAAG;AACpD,WAAO;AAAA,EACT;AACA,MAAI,qCAAqC,MAAM,GAAG;AAChD,WAAO;AAAA,EACT;AACA,MAAI,qCAAqC,MAAM,GAAG;AAChD,WAAO;AAAA,EACT;AACA,MAAI,0BAA0B,MAAM,GAAG;AACrC,WAAO;AAAA,EACT;AACA,MAAI,0BAA0B,MAAM,GAAG;AACrC,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEO,gBAAS,qBAAqB,QAAQ,OAAO,OAAO,OAAO,OAAO;AACvE,QAAM,QAAQ,gBAAgB;AAE9B,QAAM,WAAW,MAAM,OAAO,WAAS,sBAAsB,KAAK,CAAC;AAEnE,SAAO;AAIP,WAAS,sBAAsB,OAAO;AAEpC,QAAI,CAAC,yCAAyC,KAAK,KAAK,CAAC,qCAAqC,KAAK,KAAK,CAAC,0BAA0B,KAAK,GAAG;AACzI,aAAO;AAAA,IACT;AAEA,QAAI,MAAM,QAAQ,OAAO;AACvB,aAAO,MAAM,UAAU,MAAM,QAAM,GAAG,SAAS,GAAG;AAAA,IACpD;AACA,WAAO,MAAM,UAAU,KAAK,QAAM,GAAG,SAAS,OAAO,CAAC,SAAS,SAAS,OAAO,EAAE,SAAS,GAAG,KAAK,CAAC;AAAA,EACrG;AAEA,WAAS,kBAAkB;AACzB,QAAI,QAAQ,MAAM;AAChB,aAAO,OAAO,IAAI,gBAAgB;AAAA,IACpC;AACA,QAAI,MAAM;AACR,aAAO,OAAO,IAAI,QAAQ;AAAA,IAC5B;AACA,QAAI,MAAM;AACR,aAAO,OAAO,IAAI,QAAQ;AAAA,IAC5B;AACA,WAAO,CAAC;AAAA,EACV;AAEF;AAKO,gBAAS,uBAAuB,QAAQ,OAAO,OAAO,OAAO,OAAO;AAEzE,QAAM,SAAS,qBAAqB,QAAQ,MAAM,IAAI;AAEtD,MAAI,CAAC,QAAQ;AACX,WAAO;AAAA,EACT;AACA,MAAI,OAAO,KAAK,OAAK,yCAAyC,CAAC,CAAC,GAAG;AACjE,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,KAAK,OAAK,qCAAqC,CAAC,CAAC,GAAG;AAC7D,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,KAAK,OAAK,0BAA0B,CAAC,CAAC,GAAG;AAClD,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAGO,gBAAS,kCAAkC,mBAAmB,qBAAqB;AACxF,QAAM,YAAY,6BAA6B,QAAQ,iBAAiB;AACxE,QAAM,cAAc,6BAA6B,QAAQ,mBAAmB;AAE5E,MAAI,cAAc,IAAI;AAEpB,WAAO,gBAAgB;AAAA,EACzB;AACA,SAAO,aAAa;AACtB;AAGA,SAAS,YAAY,QAAQ;AAC3B,SAAO,OAAO,OAAO,KAAK,WAAS,MAAM,QAAQ,SAAS,iBAAiB,OAAO,KAAK,OAAO,CAAC;AACjG;AAGA,SAAS,eAAe,QAAQ;AAC9B,SAAO,OAAO,OAAO,KAAK,WAAS,MAAM,QAAQ,UAAU,iBAAiB,OAAO,KAAK,MAAM,KAAK,iBAAiB,OAAO,KAAK,OAAO,EAAE;AAC3I;AAGO,gBAAS,cAAc,QAAQ;AAEpC,SAAO,YAAY,MAAM,KAAK,eAAe,MAAM;AACrD;AAGO,gBAAS,iBAAiB,QAAQ;AACvC,SAAO,OAAO,OAAO,UAAU,IAAI,EAAE;AACvC;AAGO,gBAAS,uDAAuD,QAAQ;AAC7E,QAAM,gBAAgB,iBAAiB,MAAM;AAE7C,MAAI,CAAC,wCAAwC,aAAa,GAAG;AAE3D;AAAA,EACF;AAGA,QAAM,OAAO,qBAAqB,QAAQ,MAAM,KAAK,EAAE,OAAO,WAAS,kBAAkB,MAAM,CAAC,yCAAyC,KAAK,IAAI,IAAI;AACtJ,MAAI,KAAK,WAAW,GAAG;AACrB;AAAA,EACF;AAGA,UAAQ,cAAc,KAAK,MAAM,6BAA6B,QAAQ;AACtE,OAAK,QAAQ,WAAS,OAAO,YAAY,KAAK,CAAC;AACjD;AAGO,gBAAS,2BAA2B,QAAQ;AAEjD,QAAM,SAAS,qBAAqB,QAAQ,MAAM,KAAK;AACvD,oBAAkB,QAAQ,gDAAgD,QAAQ;AAClF,QAAM,UAAU,OAAO,OAAO,WAAS,OAAO,KAAK,YAAU,uCAAuC,QAAQ,KAAK,CAAC,CAAC;AACnH,oBAAkB,SAAS,2BAA2B,QAAQ;AAC9D,UAAQ,QAAQ,WAAS,OAAO,YAAY,KAAK,CAAC;AACpD;AAGO,gBAAS,2BAA2B,QAAQ;AAEjD,QAAM,YAAY,qBAAqB,QAAQ,OAAO,IAAI;AAC1D,oBAAkB,WAAW,gDAAgD,QAAQ;AACrF,QAAM,UAAU,UAAU,OAAO,WAAS,UAAU,KAAK,YAAU,uCAAuC,QAAQ,KAAK,CAAC,CAAC;AACzH,oBAAkB,SAAS,2BAA2B,QAAQ;AAC9D,UAAQ,QAAQ,WAAS,OAAO,YAAY,KAAK,CAAC;AACpD;AAGO,gBAAS,uBAAuB,UAAU;AAC/C,MAAI,CAAC,CAAC,KAAK,KAAK,GAAG,EAAE,SAAS,SAAS,IAAI,GAAG;AAC5C,WAAO;AAAA,EACT;AAEA,MAAI,SAAS,MAAM,UAAU,IAAI;AAC/B,UAAM,aAAa,SAAS,MAAM,OAAO,GAAG,EAAE;AAC9C,QAAI,WAAW,YAAY,MAAM,gBAAgB;AAC/C,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEO,gBAAS,oBAAoB,OAAO;AACzC,SAAO,MAAM,UAAU,KAAK,QAAM,uBAAuB,EAAE,CAAC;AAC9D;AAEO,gBAAS,gBAAgB,OAAO,gBAAgB;AAErD,SAAO,eAAe,MAAM,mBAAiB,uCAAuC,OAAO,aAAa,CAAC;AAC3G;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  import { validateSingleField } from "./ending-punctuation.js";
2
- import { tagToDataProvenanceSubfieldCode } from "./merge-fields/dataProvenance.js";
2
+ import { tagToDataProvenanceSubfieldCode } from "./dataProvenanceUtils.js";
3
3
  import { fieldGetUnambiguousTag } from "./subfield6Utils.js";
4
- import { fieldToString, isControlSubfieldCode, nvdebug } from "./utils.js";
4
+ import { fieldToString, isContentSubfieldCode, nvdebug } from "./utils.js";
5
5
  import clone from "clone";
6
6
  const descriptionString = "Remove invalid and add valid punctuation to data fields";
7
7
  export default function() {
@@ -32,7 +32,7 @@ function isIrrelevantSubfield(subfield, tag) {
32
32
  if (subfield.code === dataProvenanceSubfieldCode) {
33
33
  return true;
34
34
  }
35
- return isControlSubfieldCode(subfield.code);
35
+ return !isContentSubfieldCode(subfield.code);
36
36
  }
37
37
  function getNextRelevantSubfield(field, currSubfieldIndex) {
38
38
  return field.subfields.find((subfield, index) => index > currSubfieldIndex && !isIrrelevantSubfield(subfield, field.tag));
@@ -73,14 +73,14 @@ const removeCommaBeforeLanguageSubfieldL = { "followedBy": "l", "remove": /,$/u
73
73
  const removeCommaBeforeTitleSubfieldT = { "followedBy": "t", "remove": /,$/u };
74
74
  const X00RemoveDotAfterBracket = { "code": "cq", "context": /\)\.$/u, "remove": /\.$/u };
75
75
  const cleanPuncBeforeLanguage = { "code": "atvxyz", "followedBy": "l", "context": puncIsProbablyPunc, "remove": / *[.,:;]$/u };
76
- const addX00aComma = { "add": ",", "code": "abcqej", "followedBy": "cdeg", "context": doesNotEndInPunc, "contextRHS": allowsPuncRHS };
76
+ const addX00aComma = { "add": ",", "code": "abcqejt", "followedBy": "cdegnr", "context": doesNotEndInPunc, "contextRHS": allowsPuncRHS };
77
77
  const addX00dComma = { "name": 'X00$d ending in "-" does not get comma', "add": ",", "code": "d", "followedBy": "cdeg", "context": /[^-,.!]$/u, "contextRHS": allowsPuncRHS };
78
78
  const addX00aComma2 = { "add": ",", "code": "abcdej", "followedBy": "cdeg", "context": /(?:[A-Z]|Å|Ä|Ö)\.$/u, "contextRHS": allowsPuncRHS };
79
79
  const addX00Dot = { "add": ".", "code": "abcdetv", "followedBy": "fklptu", "context": needsPuncAfterAlphanumeric };
80
80
  const addEntryFieldFinalDot = { "name": "X00 final dot", "add": ".", "code": "abcdefghijklmnopqrstuvwxyz", "followedBy": "#", "context": /[^.)!?-]$/u };
81
- const addX10iColon = { name: "Punctuate relationship information", add: ":", code: "i", context: defaultNeedsPuncAfter2 };
81
+ const addXX0iColon = { name: "Punctuate relationship information", add: ":", code: "i", context: defaultNeedsPuncAfter2 };
82
82
  const addX10bDot = { "name": "Add X10 pre-$b dot", "add": ".", "code": "ab", "followedBy": "b", "context": defaultNeedsPuncAfter2 };
83
- const addX10eComma = { "add": ",", "code": "abe", "followedBy": "e", "context": defaultNeedsPuncAfter2 };
83
+ const addX10Comma = { "add": ",", "code": "abet", "followedBy": "en", "context": defaultNeedsPuncAfter2 };
84
84
  const addX10Dot = { "name": "Add X10 final dot", "add": ".", "code": "abet", "followedBy": "tu#", "context": needsPuncAfterAlphanumeric };
85
85
  const addColonToRelationshipInformation = { "name": "Add ':' to 7X0 $i relationship info", "add": ":", "code": "i", "context": defaultNeedsPuncAfter2 };
86
86
  const addX11Spacecolon = { name: "611 space colon(y :-)", add: " :", code: "nd", followedBy: "dc", "context": defaultNeedsPuncAfter2 };
@@ -149,10 +149,10 @@ const cleanCrappyPunctuationRules = {
149
149
  "830": remove490And830Whatever,
150
150
  "946": crappy24X
151
151
  };
152
- const cleanLegalX00Comma = { "code": "abcde", "followedBy": "cdegj", "context": /.,$/u, "remove": /,$/u };
152
+ const cleanLegalX00Comma = { "code": "abcdetn", "followedBy": "cdegjnr", "context": /.,$/u, "remove": /,$/u };
153
153
  const cleanLegalX00bDot = { "code": "b", "followedBy": "t#", context: /^[IVXLCDM]+\.$/u, "remove": /\.$/u };
154
154
  const cleanLegalX00iColon = { "code": "i", "followedBy": "a", "remove": / *:$/u };
155
- const cleanLegalX00Dot = { "code": "abcdetvl", "followedBy": "tu#", "context": /(?:[a-z0-9)]|å|ä|ö)\.$/u, "remove": /\.$/u };
155
+ const cleanLegalX00Dot = { "code": "abcdetkvl", "followedBy": "tklu#", "context": /(?:[a-z0-9)]|å|ä|ö)\.$/u, "remove": /\.$/u };
156
156
  const cleanDotBeforeLanguageSubfieldL = { "name": "pre-language-$l dot", "followedBy": "l", "context": /.\.$/u, "remove": /\.$/u };
157
157
  const legalEntryField = [cleanDotBeforeLanguageSubfieldL];
158
158
  const legalX11SpaceColon = { name: "legal X11 spacecolony", code: "nd", followedBy: "dc", context: / :$/u, remove: / :$/u };
@@ -227,8 +227,8 @@ const cleanValidPunctuationRules = {
227
227
  "946": clean24X
228
228
  };
229
229
  const addToAllEntryFields = [addDotBeforeLanguageSubfieldL, addSemicolonBeforeVolumeDesignation, addColonToRelationshipInformation, addEntryFieldFinalDot];
230
- const addX00 = [addX00aComma, addX00aComma2, addX00Dot, addX00dComma, ...addToAllEntryFields];
231
- const addX10 = [addX10iColon, addX10bDot, addX10eComma, addX10Dot, ...addToAllEntryFields];
230
+ const addX00 = [addXX0iColon, addX00aComma, addX00aComma2, addX00Dot, addX00dComma, ...addToAllEntryFields];
231
+ const addX10 = [addXX0iColon, addX10bDot, addX10Comma, addX10Dot, ...addToAllEntryFields];
232
232
  const addX11 = [...addToAllEntryFields, addX11Spacecolon];
233
233
  const addX30 = [...addToAllEntryFields];
234
234
  const add24X = [